324 Commits

Author SHA1 Message Date
Hanno Fleischer
b891e78647 Add README.md 2024-12-13 09:28:06 +00:00
Hanno Fleischer
ff41183643 Merge branch 'development2' into 'main'
Version 1.0

See merge request progproj/gruppen-ht24/Gruppe-01!40
2024-12-13 09:26:49 +00:00
Hanno Fleischer
3658a91b7b added possiblity to build jar file and override shutdown behavior when closing via alt-f4 or the close button of the window 2024-12-13 10:15:45 +01:00
Benjamin Feyer
1e5c5bd5eb added T016 2024-12-13 05:58:22 +01:00
Benjamin Feyer
23d4bf31a7 added testcase T004 2024-12-13 05:49:23 +01:00
Benjamin Feyer
5826d93be4 added test case T003 2024-12-13 05:47:30 +01:00
Benjamin Feyer
9c3b949e5a corrected javaDoc in GameTest 2024-12-13 05:24:15 +01:00
Benjamin Feyer
81223653f9 added Piecetest T005/T006 2024-12-13 05:15:30 +01:00
Benjamin Feyer
28c06c931e added the game-test 6/7 (T008-T009) 2024-12-13 04:25:37 +01:00
Benjamin Feyer
f93da332b3 added the game-test8 t010 2024-12-13 04:20:18 +01:00
Benjamin Feyer
379e4bcec1 corrected a pieceTest 2024-12-13 04:08:36 +01:00
Benjamin Feyer
dc7dae5db9 added some gameTests and reformatted ServerStateTests 2024-12-13 04:07:00 +01:00
Benjamin Feyer
702154c018 fixed PieceTest18 2024-12-13 02:52:11 +01:00
Benjamin Feyer
032cd76ec2 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-13 02:39:59 +01:00
Benjamin Feyer
5c8dce0626 finished the serverStateTests 2024-12-13 02:39:48 +01:00
Benjamin Feyer
911ab42101 edited some tests for the server 2024-12-13 02:18:45 +01:00
Benjamin Feyer
ea3f4df636 aaded some tests! 2024-12-13 01:59:34 +01:00
Timo Brennförder
065a626307 added Tests for LobbyStateTests 2024-12-13 00:44:37 +01:00
Timo Brennförder
2ec9b3a246 added NetworkDialogClientTests
testEnterIP -> passes
testEnterPort -> can't be tested in model
testConnectToServer -> passes
testCantConnectToServer -> passes
testCancelJoining -> passes
2024-12-12 20:53:04 +01:00
Benjamin Feyer
48d6516073 fixed and added some tests 2024-12-12 15:01:57 +01:00
Benjamin Feyer
0d4685f3c2 added some tests and corrected some tests and added logic for the tests like getter and setter 2024-12-12 13:46:23 +01:00
Benjamin Feyer
d12b0b6a77 fixed some tests 2024-12-12 02:58:30 +01:00
Benjamin Feyer
4dda9229b3 fixed some tests 2024-12-12 02:02:29 +01:00
Benjamin Feyer
e6453b2f1b Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-12 01:03:04 +01:00
Benjamin Feyer
af8ab21e5f fixed some tests 2024-12-12 01:02:55 +01:00
Hanno Fleischer
d21e963d33 Merge remote-tracking branch 'origin/development2' into development2 2024-12-12 00:21:13 +01:00
Hanno Fleischer
1f3b07709c added java docs to NoPieceState 2024-12-12 00:20:15 +01:00
Benjamin Feyer
a6215b9986 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-12 00:03:32 +01:00
Benjamin Feyer
6381d7b3b9 fixed some tests 2024-12-12 00:03:24 +01:00
Hanno Fleischer
176affa9c5 added java docs to view 2024-12-11 23:58:32 +01:00
Benjamin Feyer
67ea9ede18 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-11 23:32:30 +01:00
Benjamin Feyer
173cbe31e5 fixed some tests 2024-12-11 23:32:19 +01:00
Hanno Fleischer
c13152bb29 added java docs to model 2024-12-11 23:31:52 +01:00
Benjamin Feyer
a3d0056f7f finished clientStateTests 2024-12-11 21:54:14 +01:00
Benjamin Feyer
06dcc4c0e4 fixed buck 2024-12-11 20:19:15 +01:00
Benjamin Feyer
61e168cefb fixed buck 2024-12-11 20:08:26 +01:00
Benjamin Feyer
cecd7e1a23 fixed buck 2024-12-11 19:59:01 +01:00
Hanno Fleischer
f9772732c4 added better network support and disconnecting doesnt clos the client who hosts the server now 2024-12-11 19:10:46 +01:00
Benjamin Feyer
f09766eb42 Merge remote-tracking branch 'origin/development2' into development2
# Conflicts:
#	Projekte/mdga/model/src/main/java/pp/mdga/server/automaton/game/turn/rolldice/FirstRollState.java
#	Projekte/mdga/model/src/test/java/pp/mdga/client/clientState/ClientStateTest.java
#	Projekte/mdga/model/src/test/java/pp/mdga/game/PieceTest.java
2024-12-11 17:16:26 +01:00
Benjamin Feyer
62e3dd7932 fixed some tests 2024-12-11 17:13:59 +01:00
Hanno Fleischer
d80499a18a Merge remote-tracking branch 'origin/development2' into development2 2024-12-11 16:50:18 +01:00
Hanno Fleischer
4b665c4cf2 getter and setter fo isDied 2024-12-11 16:49:45 +01:00
Cedric Beck
3048caf5c9 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-11 14:23:44 +01:00
Cedric Beck
b8df688ba3 added highlight/hover/select to nodes 2024-12-11 14:23:24 +01:00
Hanno Fleischer
8201ab4b63 added error handling when the drawpile is empty 2024-12-11 14:11:07 +01:00
Felix Koppe
14a407b7d5 Merge commit 2024-12-11 13:57:43 +01:00
Felix Koppe
170a282077 Add selectNext 2024-12-11 13:56:55 +01:00
Hanno Fleischer
5854609b12 added notification to change to startdialog state when calling next in statitics state 2024-12-11 13:43:15 +01:00
Hanno Fleischer
ea25f66dae wrote getter for ceremonyview in mdga app 2024-12-11 13:34:38 +01:00
Felix Koppe
c069a61f2d Merge commit 2024-12-11 13:12:51 +01:00
Felix Koppe
07029407d8 Add ceremony 2024-12-11 13:11:48 +01:00
Hanno Fleischer
42a73ceb24 after switching from spectator to animation you should now be reset correctly into spectator 2024-12-11 13:03:39 +01:00
Hanno Fleischer
42283f29a0 fixed missing passthrough statement in choosepiecestate 2024-12-11 12:28:48 +01:00
Hanno Fleischer
574f8bb681 added java docs 2024-12-11 12:00:39 +01:00
Hanno Fleischer
c1641cdac1 added the possibility when someone rolls a 6 and then has no valid move he can dice again 2024-12-11 10:54:50 +01:00
Cedric Beck
f251bad97f fixed node selected after clear 2024-12-11 08:59:21 +00:00
Felix Koppe
f8a021b310 Fix error in notification syncronizer 2024-12-11 08:40:31 +01:00
Daniel Grigencha
aa44b84648 removed unused imports and reformatted the code for code style 2024-12-11 05:34:34 +01:00
Benjamin Feyer
d8816be811 fixed some tests 2024-12-11 01:03:18 +01:00
Benjamin Feyer
e61265fc99 fixed some tests 2024-12-11 00:25:14 +01:00
Benjamin Feyer
9e2a0819ca fixed some tests 2024-12-10 21:18:31 +01:00
Benjamin Feyer
cc756453a1 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-10 21:03:21 +01:00
Benjamin Feyer
f23f1f5abc fixed some tests 2024-12-10 21:03:07 +01:00
Benjamin Feyer
cc8b2abbde fixed some tests 2024-12-10 20:56:08 +01:00
Hanno Fleischer
8d7701cbf2 removed presentation die results 2024-12-10 20:50:48 +01:00
Hanno Fleischer
874bba0fe9 removed starting alone ability 2024-12-10 20:47:51 +01:00
Hanno Fleischer
3b73d1229b fixed ceremony statetransition 2024-12-10 19:30:58 +01:00
Hanno Fleischer
9f1dafece2 added Ceremony integration 2024-12-10 19:29:06 +01:00
Hanno Fleischer hanno.fleischer@unibw.de
997b31eba2 reverted turbo card percentages to normal 2024-12-10 15:56:16 +01:00
Felix Koppe
148b769232 Add missing sound 2024-12-10 15:43:35 +01:00
Hanno Fleischer
f0f4e0eb5e added presentation die results and powercards as well as fixed a bug where when a piece was thrown with a suppressed shield it wouldnt be cleared 2024-12-10 15:06:35 +01:00
Cedric Beck
bda1a40cdb Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-10 14:53:26 +01:00
Cedric Beck
0c8cb92fe8 fix anims 2024-12-10 14:53:16 +01:00
Fleischer Hanno
e952233d20 added a hashset to fix double checking of powercards in the visitor 2024-12-10 14:36:26 +01:00
Cedric Beck
9f8fd9c22f changed var name 2024-12-10 14:34:44 +01:00
Cedric Beck
0432dd6bd7 merge 2024-12-10 14:29:02 +01:00
Cedric Beck
4d31fc098b fixed swap bug 2024-12-10 14:27:50 +01:00
Benjamin Feyer
40a61daa58 added flag 2024-12-10 14:07:52 +01:00
Benjamin Feyer
6e54ad0196 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-10 14:06:25 +01:00
Benjamin Feyer
11fa303f1b added flag 2024-12-10 14:05:43 +01:00
Fleischer Hanno
d65111680b added RankingResponseNotification to be sent to the client 2024-12-10 13:55:58 +01:00
Felix Koppe
485b8f36d7 Merge commit7 2024-12-10 13:57:08 +01:00
Felix Koppe
4eee62079e Add RankingResponceNotification 2024-12-10 13:56:42 +01:00
Cedric Beck
888b52f314 moved timermanger to app 2024-12-10 13:38:26 +01:00
Cedric Beck
bc2c80dd27 added asfjiop 2024-12-10 13:28:40 +01:00
Cedric Beck
50f0cdfce6 added timermanager 2024-12-10 13:26:56 +01:00
Cedric Beck
0c7030659a removed fadeControl 2024-12-10 13:00:03 +01:00
Benjamin Feyer
30997eb571 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-10 12:47:22 +01:00
Benjamin Feyer
ea1978d5a8 edited flag=false in enter in rolldice 2024-12-10 12:46:56 +01:00
Cedric Beck
1ead0e2e9e fixed uuid reference bug 2024-12-10 12:46:04 +01:00
Benjamin Feyer
70c821f2ac added logger and simpliefied logic 2024-12-10 12:06:21 +01:00
Cedric Beck
8867e8156f merge 2024-12-10 11:55:17 +01:00
Cedric Beck
36c2f2efec commit 2024-12-10 11:54:45 +01:00
Benjamin Feyer
b8d992590f added logger for swap-card 2024-12-10 11:52:52 +01:00
Benjamin Feyer
a86a146091 added logger for swap-card 2024-12-10 11:52:05 +01:00
Benjamin Feyer
3e060897fc added the flag in the client roldice-states, so you can't roll twice 2024-12-10 11:18:10 +01:00
Benjamin Feyer
fe95a7c159 added the piecetest and implemented more logic in the moveLogic 2024-12-10 10:47:06 +01:00
Benjamin Feyer
d60c0347cc Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-10 08:13:33 +01:00
Benjamin Feyer
bbde17aa27 initial test commit for clientStateTest and ServerStatetest 2024-12-10 08:13:11 +01:00
Benjamin Feyer
38c865d135 reformatted code 2024-12-10 02:23:19 +01:00
Benjamin Feyer
91d4718179 fixed the move-logic 2024-12-10 02:19:48 +01:00
Benjamin Feyer
bb1bf22713 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 22:22:49 +01:00
Benjamin Feyer
380341114c minor changes 2024-12-09 22:21:52 +01:00
Cedric Beck
f56eb8cd60 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 22:03:39 +01:00
Cedric Beck
3797da1246 fixed crash get bonus 2024-12-09 22:03:34 +01:00
Benjamin Feyer
e79a1168c0 corrected movelogic 2024-12-09 21:02:46 +01:00
Fleischer Hanno
6ab8f2d90d fixed missing check on shield state after swap 2024-12-09 19:55:03 +01:00
Fleischer Hanno
ece249cf66 fixed bug in no turn state with double next color itteration 2024-12-09 19:50:22 +01:00
Fleischer Hanno
e95f0866d5 fixed shield state for waiting state 2024-12-09 19:43:50 +01:00
Fleischer Hanno
0fce2fb5d2 added the possiblity when the drawpile has only 1 card the discard pile will be shuffeled and then put into the draw pile. 2024-12-09 19:11:45 +01:00
Fleischer Hanno
0010717411 when triggering BonusNode it will now be addded to the hand 2024-12-09 19:05:28 +01:00
Felix Koppe
cf9acf981f Merge commit 2024-12-09 19:06:08 +01:00
Felix Koppe
e2a4f7f85c Fix serialize error 2024-12-09 19:05:59 +01:00
Felix Koppe
1ffabe6b19 Merge commit 2024-12-09 19:05:02 +01:00
Felix Koppe
0ba5a2f9fd Add serialization of SpectatorMessage 2024-12-09 19:04:47 +01:00
Cedric Beck
d0afaa57f5 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:59:56 +01:00
Cedric Beck
00a79ddce3 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:59:42 +01:00
Cedric Beck
bce6a1e0eb added cardlayerhandler back and fixed clear selectable 2024-12-09 18:59:38 +01:00
Benjamin Feyer
37dcc0122c Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:51:04 +01:00
Benjamin Feyer
4007036cb2 bug fixes 2024-12-09 18:50:54 +01:00
Hanno Fleischer
5b958740c4 Merge remote-tracking branch 'origin/development2' into development2 2024-12-09 18:45:58 +01:00
Hanno Fleischer
06cb25b6a9 fixed card selection, to use an already selected and discarded card 2024-12-09 18:45:23 +01:00
Cedric Beck
afec74416f fixed ChoosePowerCardState 2024-12-09 18:45:00 +01:00
Hanno Fleischer
ceb9e48f55 updated Error messages 2024-12-09 18:36:42 +01:00
Cedric Beck
f8179f191f Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:12:08 +01:00
Cedric Beck
ec77b9a6ef added more start cards for testing 2024-12-09 18:11:45 +01:00
Fleischer Hanno
2d01e4b31b Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:11:44 +01:00
Fleischer Hanno
0be25ecb29 made Intro state work with more PowerCards on the hand 2024-12-09 18:11:21 +01:00
Benjamin Feyer
1210324194 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 18:05:40 +01:00
Benjamin Feyer
569ce39d2a bug Fixed 2024-12-09 18:04:24 +01:00
Cedric Beck
80989310e5 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 17:52:53 +01:00
Cedric Beck
a2f57bcaae deleted node unselect in BoardHandler clearSelectable 2024-12-09 17:52:48 +01:00
Lukas Bauer
68702f11a4 Deleted the 'MdgaApp.run.xml. 2024-12-09 17:51:40 +01:00
Felix Koppe
13948ec4bb Fix error 2024-12-09 17:47:03 +01:00
Cedric Beck
570c915964 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 17:21:51 +01:00
Cedric Beck
ac719c55e6 revert commit 2024-12-09 17:20:34 +01:00
Hanno Fleischer
189e8a7ec3 fixed client shield state animation 2024-12-09 17:11:30 +01:00
Felix Koppe
5d2ece41b7 Merge commit 2024-12-09 17:05:55 +01:00
Felix Koppe
7f92d8183b Adjust delays 2024-12-09 17:05:42 +01:00
Cedric Beck
6bc769f5bb merge 2024-12-09 17:04:45 +01:00
Cedric Beck
a21794ddc1 added new card order in CardLayerHandler 2024-12-09 17:03:35 +01:00
Felix Koppe
e699f4556e Fix error 2024-12-09 16:58:05 +01:00
Fleischer Hanno
a56f68efdc fixed Turbo card with zero modifier 2024-12-09 16:54:26 +01:00
Felix Koppe
55328e9e69 Fixe dice 2024-12-09 16:43:21 +01:00
Felix Koppe
50dc634a94 Add powercard drawing 2024-12-09 16:37:08 +01:00
Felix Koppe
d37b9ee269 Add powercard drawing 2024-12-09 16:36:07 +01:00
Benjamin Feyer
3937b75bf7 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 16:11:32 +01:00
Benjamin Feyer
7d5b8a6b13 404 IQ-Move added move logic 2024-12-09 16:10:56 +01:00
Felix Koppe
39d5b43cb8 Quality of live improvement 2024-12-09 16:10:15 +01:00
Felix Koppe
de0f04b5b7 Quality of live improvement 2024-12-09 16:07:26 +01:00
Felix Koppe
6a4bdfa455 Fix swap card issue 2024-12-09 14:50:29 +01:00
Felix Koppe
db16b0d8b6 Fix shield card issue 2024-12-09 14:45:41 +01:00
Benjamin Feyer
08db05a7d6 added more logic for the move 2024-12-09 14:15:05 +01:00
Cedric Beck
e47fcd1643 merge 2024-12-09 14:14:43 +01:00
Cedric Beck
dfd361d8be added isStart + (DU) 2024-12-09 14:14:15 +01:00
Felix Koppe
bf3d800c10 Add turboCardLogic 2024-12-09 13:57:16 +01:00
Felix Koppe
8943dfb15e Allow all bonusCards 2024-12-09 13:48:05 +01:00
Felix Koppe
64f11eb99b Adjust diceRoll time 2024-12-09 13:42:15 +01:00
Hanno Fleischer
0e9ff609ec fixed a bug where if two player rolled the same number in DSP u would be stuck in an infinite loop 2024-12-09 12:57:47 +01:00
Hanno Fleischer
d37db68838 fixed bug with too early state transition after playing a powercard 2024-12-09 12:05:28 +01:00
Benjamin Feyer
25fff99ff0 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2
# Conflicts:
#	Projekte/mdga/model/src/main/java/pp/mdga/game/Game.java
#	Projekte/mdga/model/src/main/java/pp/mdga/server/automaton/game/turn/choosepiece/ChoosePieceAutomatonState.java
#	Projekte/mdga/model/src/main/java/pp/mdga/server/automaton/game/turn/choosepiece/SelectPieceState.java
2024-12-09 04:50:01 +01:00
Cedric Beck
2dbdb1e17b revert 2 dicenow 2024-12-09 04:42:55 +01:00
Fleischer Hanno
f954a24b32 fixed in firstroll catching of endanimation messages by non active palyers 2024-12-09 04:36:08 +01:00
Cedric Beck
c7a05011ff Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 04:34:52 +01:00
Cedric Beck
15b7aa9e8c fixed 2 dicenow notification 2024-12-09 04:34:47 +01:00
Felix Koppe
84a50ec215 Fix something 2024-12-09 04:33:21 +01:00
Benjamin Feyer
e05b057190 reenvented the wheel (; , rewrite the canMove-logic, etc 2024-12-09 04:32:55 +01:00
Cedric Beck
9ea6837e89 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 04:21:11 +01:00
Felix Koppe
a26a3d171f 99,99 IQ 2024-12-09 04:26:16 +01:00
Felix Koppe
a2e85cebc9 99,9 IQ 2024-12-09 04:25:36 +01:00
Cedric Beck
3fe2d90d75 added toString 2024-12-09 04:21:06 +01:00
Felix Koppe
4181456598 Adjust delay 2024-12-09 04:13:30 +01:00
Felix Koppe
6f34b1fe33 Adjust delay 2024-12-09 04:10:14 +01:00
Felix Koppe
659fef6c50 Add bonus sounds 2024-12-09 04:07:52 +01:00
Cedric Beck
9c7a13c568 fixed bug 2024-12-09 03:57:53 +01:00
Cedric Beck
14699e3edf fixed bug 2024-12-09 03:55:51 +01:00
Cedric Beck
961da990ce fixed no broadcast bug 2024-12-09 03:46:09 +01:00
Fleischer Hanno
be8d4b2d6e made turbo flag work with only one flag 2024-12-09 03:39:24 +01:00
Cedric Beck
38687b6d25 added turboflag 2024-12-09 03:37:35 +01:00
Cedric Beck
c038073bad Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 03:23:06 +01:00
Cedric Beck
5bccee96a2 working on turbo not 2024-12-09 03:23:02 +01:00
Fleischer Hanno
ecb751824c created two flags to be able to determine if a turbo card has been played 2024-12-09 03:20:54 +01:00
Felix Koppe
a7969d7a68 Minor change+ 2024-12-09 03:25:04 +01:00
Felix Koppe
3a1b17ed01 Merge commit 2024-12-09 03:17:47 +01:00
Felix Koppe
3dd6fc9f37 Add DEBUG_MULTIPLIER 2024-12-09 03:17:28 +01:00
Cedric Beck
3ae9028b82 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 03:11:49 +01:00
Cedric Beck
536c14bf7c working on turbocard 2024-12-09 03:11:43 +01:00
Fleischer Hanno
fb3a663db1 added overwrite of dicemodifier when receiving PlayCard Turbo message 2024-12-09 03:07:12 +01:00
Cedric Beck
b91d448ee6 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 02:55:57 +01:00
Cedric Beck
935577462b added shieldRing move 2024-12-09 02:55:52 +01:00
Felix Koppe
f79d590620 Merge commit 2024-12-09 02:50:57 +01:00
Felix Koppe
69d160d0fd Java23 2024-12-09 02:50:45 +01:00
Felix Koppe
de592cfa48 Move receive DrawCardMessage to GameState 2024-12-09 02:43:02 +01:00
Fleischer Hanno
8922cb8f1c added removeshield notification 2024-12-09 02:41:44 +01:00
Felix Koppe
f10df60ad3 Merge commit 2024-12-09 02:40:57 +01:00
Felix Koppe
1c87b566e9 Add DrawCardMessage logic 2024-12-09 02:40:45 +01:00
Cedric Beck
6ecf5a66bf added animation end in playCardNotification 2024-12-09 02:30:15 +01:00
Cedric Beck
3f93d6e569 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 02:18:35 +01:00
Cedric Beck
cc72b82be9 added shield Messages 2024-12-09 02:18:30 +01:00
Fleischer Hanno
41ac04f69d added in can infield move to return false when occupant is shielded 2024-12-09 02:15:45 +01:00
Felix Koppe
cb60cb1c42 Add bonus node logic 2024-12-09 02:19:43 +01:00
Fleischer Hanno
61d67fd833 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-09 02:04:11 +01:00
Felix Koppe
4ca7c38170 Minor change in notification processing 2024-12-09 02:09:09 +01:00
Fleischer Hanno
e457fe23d4 added a reset for the dice modifier in the server 2024-12-09 02:03:57 +01:00
Felix Koppe
b86aeb63e6 Merge commit 2024-12-09 01:53:33 +01:00
Felix Koppe
daa8c0bf9d 500IQ 2024-12-09 01:53:18 +01:00
Felix Koppe
6487bafed1 Merge commit 2024-12-09 01:47:47 +01:00
Felix Koppe
8af3b2d9d4 Fix uuid serialisation error 2024-12-09 01:47:32 +01:00
Cedric Beck
d38690ea48 merge 2024-12-09 01:43:25 +01:00
Cedric Beck
daa7d31bdd working on shield 2024-12-09 01:42:59 +01:00
Fleischer Hanno
5e79a4a64c added remove handcard 2024-12-09 01:35:37 +01:00
Felix Koppe
8e2fc6c1a1 Merge commit 2024-12-09 01:18:47 +01:00
Felix Koppe
5a12d8e96f Fix dice more than once error 2024-12-09 01:18:29 +01:00
Fleischer Hanno
3af6e94920 added DrawCardMessageSupport 2024-12-09 01:16:31 +01:00
Cedric Beck
4efc557849 added node hover/select with piece 2024-12-09 01:06:12 +01:00
Felix Koppe
1646526ce1 Try fix error 2024-12-09 00:36:38 +01:00
Cedric Beck
904aa17358 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-08 23:23:27 +01:00
Felix Koppe
f4e774ee5b Minor change 2024-12-08 23:28:26 +01:00
Cedric Beck
76b3e18dbe added jet_noGear 2024-12-08 23:23:21 +01:00
Cedric Beck
d4cbd0dda6 added swapnotification 2024-12-08 23:08:59 +01:00
Felix Koppe
7a189a98e3 Merge commit 2024-12-08 23:05:44 +01:00
Felix Koppe
00902d2e6b Add move throw sync 2024-12-08 23:05:30 +01:00
Fleischer Hanno
7256cde020 changed the ionput list for selectableMoveNotification 2024-12-08 22:52:42 +01:00
Cedric Beck
b817af29b5 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-08 22:28:57 +01:00
Cedric Beck
fa18ae4280 added swap functionality in model for server & client 2024-12-08 22:28:51 +01:00
Felix Koppe
521c7439c2 Add error in animation logixc 2024-12-08 22:04:16 +01:00
Fleischer Hanno
84c5553154 fixed former bug 2024-12-08 21:47:28 +01:00
Fleischer Hanno
2c94737023 fixed bug when moving pieces from waiting to infield for non active players 2024-12-08 21:37:22 +01:00
Fleischer Hanno
d3d75d7f49 added missing error messages 2024-12-08 21:23:16 +01:00
Fleischer Hanno
5e67b2d0c7 fixed a bug where the home index of piece was wrong 2024-12-08 21:18:34 +01:00
Fleischer Hanno
992efd403d fixed waiting piece move bug 2024-12-08 21:14:20 +01:00
Cedric Beck
2d7fddf09a fixed swap select 2024-12-08 21:13:44 +01:00
Felix Koppe
f7c886f084 333IQ 2024-12-08 21:12:09 +01:00
Fleischer Hanno
a1d85177c6 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-08 20:46:58 +01:00
Fleischer Hanno
e27d325faa added serializable tag to choosepiecestatemessage 2024-12-08 20:45:53 +01:00
Cedric Beck
bd98f301c8 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-08 20:44:28 +01:00
Cedric Beck
a7850f7d43 fixed rotation 2024-12-08 20:42:57 +01:00
Fleischer Hanno
3040595193 minor fixes to initialize method in noPiece state of server 2024-12-08 20:28:09 +01:00
Fleischer Hanno
5d45cf2934 fixed an error in clientstates where a missing servermessage received istn overwritte 2024-12-08 20:24:05 +01:00
Fleischer Hanno
a5d949b7e5 added the functionality of multiple roll dice when all pieces in waiting 2024-12-08 20:17:53 +01:00
Cedric Beck
f4a224621e deleted unwanted code (next Color) 2024-12-08 20:05:37 +01:00
Felix Koppe
876c238db3 Fix colorNext 2024-12-08 19:48:26 +01:00
Felix Koppe
b61b8214fe Fix colorNext 2024-12-08 19:46:09 +01:00
Felix Koppe
5dc3124533 Fix colorNext 2024-12-08 19:40:10 +01:00
Fleischer Hanno
c25e17fd90 fixed waitingpiece move 2024-12-08 19:14:48 +01:00
Cedric Beck
5e27473875 fixed bugs in MoveMessage 2024-12-08 19:10:25 +01:00
Cedric Beck
1be2d6aa13 Merge branch 'development2' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development2 2024-12-08 18:06:10 +01:00
Felix Koppe
ab9de3acf8 Replace waitMoveNotification with moveNotification 2024-12-08 18:09:13 +01:00
Cedric Beck
2ab19d0fc8 200 iq move 2024-12-08 18:05:35 +01:00
Cedric Beck
f489357bbb fixed calcTargetIndex 2024-12-08 17:24:36 +01:00
Felix Koppe
34cde15a0d Fix error 2024-12-08 17:24:18 +01:00
Felix Koppe
1e52df2812 Fix error 2024-12-08 17:14:22 +01:00
Felix Koppe
884c5afc4e Remove moveOccupant 2024-12-08 17:12:35 +01:00
Cedric Beck
d6e44c2d29 added logger 2024-12-08 16:44:28 +01:00
Felix Koppe
d593233fa3 Fix error 2024-12-08 15:29:23 +01:00
Felix Koppe
d1b4aa9dda Fix error 2024-12-08 15:24:59 +01:00
Felix Koppe
25b6480c42 Fix error 2024-12-08 15:10:14 +01:00
Felix Koppe
3dd81ea02c Add trace statement 2024-12-08 14:58:07 +01:00
Felix Koppe
e6dbedab0f Try fix active player logic 2024-12-08 14:33:37 +01:00
Felix Koppe
f5b0481d3c Adjust print statements 2024-12-08 14:25:38 +01:00
Felix Koppe
f9f381ac2d Fix color.next 2024-12-08 14:16:14 +01:00
Felix Koppe
ee94d901f4 Adjust print statements 2024-12-08 13:54:39 +01:00
Felix Koppe
bca02bfe4b Adjust print statements 2024-12-08 13:51:05 +01:00
Felix Koppe
acdf5ec6a9 Add missing registration of SelectPieceMessage 2024-12-08 13:29:44 +01:00
Cedric Beck
41d6f70d51 added logger 2024-12-08 12:50:58 +01:00
Felix Koppe
4c064cb615 Merge commit+ 2024-12-08 12:45:36 +01:00
Felix Koppe
121f47d070 Fix selectPieceState and startPieceState logic 2024-12-08 12:45:08 +01:00
Cedric Beck
ae436589a2 edited MoveMessage in WaitingState 2024-12-08 12:38:59 +01:00
Cedric Beck
bc399b1bf9 edited String queals in Piece 2024-12-08 12:03:05 +01:00
Cedric Beck
98a6f2e689 added debug 2024-12-08 11:57:30 +01:00
Cedric Beck
9a07375fed Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-08 11:39:42 +01:00
Felix Koppe
498c2eb054 Merge remote-tracking branch 'origin/development' into development 2024-12-08 11:44:38 +01:00
Cedric Beck
ce55ca8bb5 added further node select implementation 2024-12-08 11:39:37 +01:00
Felix Koppe
9c729059bf Merge branch 'refs/heads/dev/server_h' into development 2024-12-08 11:30:09 +01:00
Cedric Beck
3b7ef37364 added selectNode when right piece is selected 2024-12-08 11:27:56 +01:00
Fleischer Hanno
ec295c94f1 fixed the equals method in piece 2024-12-08 11:23:53 +01:00
Cedric Beck
adfe2b94b8 fixed cardLayer shutdown bug 2024-12-08 11:19:00 +01:00
Cedric Beck
69108063a0 fixed Notification bug 2024-12-08 10:33:56 +01:00
Hanno Fleischer
16e7488fae Merge branch 'dev/server_h' into 'development'
added more communication fixes, states now use correct messages

See merge request progproj/gruppen-ht24/Gruppe-01!38
2024-12-08 09:13:47 +00:00
Hanno Fleischer
c9c99709ba added more communication fixes, states now use correct messages 2024-12-08 09:52:23 +01:00
Felix Koppe
e069017375 Merge remote-tracking branch 'origin/dev/server_h' into development 2024-12-08 09:44:23 +01:00
Hanno Fleischer
8b27ccce22 adjusted stattransition methods to work correctly 2024-12-08 03:08:47 +01:00
Hanno Fleischer
8c22d935a9 implemented rest of the server logic in choosepiece substates, and began to fix bugs after testing 2024-12-08 01:59:29 +01:00
Cedric Beck
c8d7d91de0 added import in NotiSync 2024-12-07 21:57:41 +01:00
Cedric Beck
389d1b6056 merge development into dev/client_beck 2024-12-07 17:04:43 +01:00
Cedric Beck
4430b37581 fixed wrong dice rotation because of fps drop 2024-12-07 17:00:42 +01:00
Cedric Beck
e5abcbdc8c added jet_noGear.j3o 2024-12-07 16:51:38 +01:00
Cedric Beck
e14b8cb510 added converted assets 2024-12-07 16:50:43 +01:00
Felix Koppe
bf84bfa0f9 Update animations and remove test setup 2024-12-07 16:40:45 +01:00
Hanno Fleischer
0c49d7ed1c Merge branch 'dev/server_h' of https://athene1.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/server_h 2024-12-07 15:37:02 +01:00
Hanno Fleischer
2ba6a22422 Merge branch 'development' into 'dev/server_h'
Development

See merge request progproj/gruppen-ht24/Gruppe-01!37
2024-12-07 14:36:25 +00:00
Hanno Fleischer
c37bac4614 replaced DicaAgain message with DicveNow 2024-12-07 15:34:34 +01:00
Felix Koppe
06b37584cb Merge dev/client_beck into development 2024-12-07 15:05:03 +01:00
Felix Koppe
0c42a2df88 Merge branch 'dev/client_beck' into development
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/Asset.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/InputSynchronizer.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/AcousticHandler.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MdgaSound.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/SoundAsset.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/animation/JetAnimation.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/board/BoardHandler.java
#	Projekte/mdga/model/src/main/java/pp/mdga/client/gamestate/GameStates.java
2024-12-07 15:00:27 +01:00
Felix Koppe
d75d704878 Add smoke effekt to missileAnimation 2024-12-07 14:55:08 +01:00
Cedric Beck
6d3c733f91 added effect for shell flying 2024-12-07 14:45:39 +01:00
Felix Koppe
f96da2c46c Add notification delay 2024-12-07 14:37:19 +01:00
Cedric Beck
1a079dad44 added shell asset for ShellAnimation 2024-12-07 14:22:36 +01:00
Cedric Beck
32f49a6181 added shellAnimation without shell asset 2024-12-07 13:30:30 +01:00
Felix Koppe
525809899e Minor improvements 2024-12-07 11:03:30 +01:00
Felix Koppe
fd9708752c Merge dev/model into development 2024-12-06 18:49:15 +01:00
Hanno Fleischer
5a9f7a8118 added AnimationEndMessages to 'RollDiceState', 'MovePieceState' and 'PlayPowerCardState' 2024-12-06 18:45:14 +01:00
Felix Koppe
236d3db930 Add ambience 2024-12-06 18:10:09 +01:00
Cedric Beck
29c6b13300 added MatrixAnimation 2024-12-06 17:09:06 +01:00
Felix Koppe
6059e93276 Fix interruptDialog 2024-12-06 16:52:08 +01:00
Felix Koppe
f2eeb6dab4 Fix logic error regarding cardSelection 2024-12-06 16:27:01 +01:00
Hanno Fleischer
2e1fe3c050 fixed a missing method call ind TurnState and removed debug sout statements ind 'RollDiceMessage' 2024-12-06 15:19:04 +01:00
Cedric Beck
2ac2de645b working on matrix-animation 2024-12-06 14:56:04 +01:00
Felix Koppe
d39f85fbe9 Add some javaDoc to client 2024-12-06 14:26:13 +01:00
Felix Koppe
960a57caba Fix broken lose sound 2024-12-06 13:28:38 +01:00
Felix Koppe
36631df2e9 Fix broken jet-sound 2024-12-06 13:26:28 +01:00
Felix Koppe
df27c23cd5 Fix shadercode 2024-12-06 13:19:17 +01:00
Felix Koppe
acd64d1507 Fix shadercode 2024-12-06 13:14:54 +01:00
Felix Koppe
76f86c8a66 Improve audioSettings 2024-12-06 11:35:05 +01:00
Hanno Fleischer
308b592b65 Merge branch 'development' into 'dev/model'
Development merge

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

See merge request progproj/gruppen-ht24/Gruppe-01!35
2024-12-06 09:09:29 +00:00
Felix Koppe
95635f5fb7 Fix ownColor in gameView 2024-12-06 09:59:28 +01:00
Daniel Grigencha
4904b32ea3 Updated 'ChoosePieceState' class.
Updated the 'ChoosePieceState' class by adding the 'RequestMoveMessage' handling to it.
2024-12-06 08:58:51 +01:00
Daniel Grigencha
b00219c4fb Updated 'PlayPowerCardState' class.
Updated the 'PlayPowerCardState' class by adding the 'AnimationEndMessage' handling to it.
2024-12-06 08:58:01 +01:00
Daniel Grigencha
12cf5f3e71 Updated 'PowerCardState' class.
Updated the 'PowerCardState' class by adding the 'SelectedPiecesMessage' handling to it.
2024-12-06 08:57:08 +01:00
Daniel Grigencha
77b0207214 Updated 'TurnState' class.
Updated the 'TurnState' class by adding the 'SelectedPiecesMessage', 'NoPowerCardMessage', 'RequestDieMessage' and 'ReuqestMoveMessage' handling to it.
2024-12-06 08:56:21 +01:00
Daniel Grigencha
a18165bc02 Updated 'Game' class.
Updated the 'Game' class by commenting out the creation of turbo and shield cards. This is only for testing purposes.
2024-12-06 08:55:01 +01:00
221 changed files with 14058 additions and 5585 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@@ -26,3 +26,14 @@ implementation project(":mdga:model")
mainClass = 'pp.mdga.client.MdgaApp'
applicationName = 'MDGA'
}
tasks.register('fatJar', Jar) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes 'Main-Class': 'pp.mdga.client.MdgaApp'
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@ public enum Asset {
cir,
heer,
jet,
jet_noGear("Models/jet/jet_noGear.j3o", "Models/jet/jet_diff.png"),
lw,
marine,
node_home_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
@@ -40,7 +41,11 @@ public enum Asset {
shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
dice,
missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
;
tankShoot("Models/tank/tankShoot_bot.j3o", "Models/tank/tank_diff.png"),
tankShootTop("Models/tank/tankShoot_top.j3o", "Models/tank/tank_diff.png"),
treesSmallBackground("Models/treeSmall/treesSmallBackground.j3o", "Models/treeSmall/treeSmall_diff.png", 1.2f),
treesBigBackground("Models/treeBig/treesBigBackground.j3o", "Models/treeBig/treeBig_diff.png", 1.2f),
shell;
private final String modelPath;
private final String diffPath;
@@ -77,7 +82,8 @@ public enum Asset {
Asset(String modelPath) {
String folderFileName = "./" + ROOT + name() + "/" + name();
this.modelPath = modelPath;
this.diffPath = folderFileName + "_diff.png";;
this.diffPath = folderFileName + "_diff.png";
;
this.size = 1f;
}
@@ -100,7 +106,7 @@ public enum Asset {
* @param diffPath Path to the diffuse texture file.
* @param size Scaling factor for the asset.
*/
Asset(String modelPath, String diffPath, float size){
Asset(String modelPath, String diffPath, float size) {
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = size;

View File

@@ -12,19 +12,14 @@
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.OutlineOEControl;
import pp.mdga.client.board.PieceControl;
import pp.mdga.client.gui.CardControl;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.client.view.GameView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.game.Piece;
import pp.mdga.notification.FinishNotification;
import pp.mdga.notification.MovePieceNotification;
import pp.mdga.notification.SelectableCardsNotification;
import java.util.List;
import java.util.UUID;
@@ -38,7 +33,7 @@ public class InputSynchronizer {
private float rotationAngle = 180f;
private int scrollValue = 0;
private CardControl hoverCard;
private PieceControl hoverPiece;
private OutlineOEControl hoverPiece;
private boolean clickAllowed = true;
@@ -60,14 +55,19 @@ public class InputSynchronizer {
setupInput();
}
/**
* Updates the rotation angle based on user input.
*
* @param tpf The time per frame.
*/
public void update(float tpf) {
if(isRotateLeft && isRotateRight) {
if (isRotateLeft && isRotateRight) {
return;
}
if(isRotateLeft) {
if (isRotateLeft) {
rotationAngle += 180 * tpf;
}
if(isRotateRight) {
if (isRotateRight) {
rotationAngle -= 180 * tpf;
}
}
@@ -94,6 +94,7 @@ private void setupInput() {
inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
UUID p = null;
/**
* Handles action-based input events such as key presses and mouse clicks.
@@ -110,33 +111,25 @@ public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("RotateRightMouse")) {
rightMousePressed = isPressed;
}
if(name.equals("Click") && isPressed) {
if(!clickAllowed) {
if (name.equals("Click") && isPressed) {
if (!clickAllowed) {
return;
}
if (app.getView() instanceof GameView gameView) {
DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
OutlineControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineControl.class);
OutlineOEControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
if(diceSelect != null) {
if (diceSelect != null) {
app.getModelSynchronize().rolledDice();
}
else if(cardLayerSelect != null) {
} else if (cardLayerSelect != null) {
//cardSelect
if(cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
}
else if(boardSelect != null) {
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 {
if (boardSelect.isSelectable()) gameView.getBoardHandler().pieceSelect(boardSelect);
} else {
//both null
}
}
@@ -149,16 +142,17 @@ else if(boardSelect != null) {
if (name.equals("Right")) {
isRotateRight = !isRotateRight;
}
if(name.equals("Test2") &&isPressed){
if(app.getView() instanceof GameView gameView){
if (name.equals("Test2") && isPressed) {
if (app.getView() instanceof GameView gameView) {
if(p == null) {
if (p == null) {
p = UUID.randomUUID();
gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
gameView.getBoardHandler().movePieceStartAnim(p,0);
gameView.getBoardHandler().addPlayer(Color.AIRFORCE, List.of(p, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()));
gameView.getBoardHandler().movePieceStartAnim(p, 0);
gameView.getBoardHandler().outlineMove(List.of(p), List.of(2), List.of(false));
//gameView.getBoardHandler().movePieceAnim(p,0, 8);
} else {
gameView.getBoardHandler().throwMissileAnim(p);
gameView.getBoardHandler().throwPiece(p, Color.ARMY);
//gameView.getBoardHandler().movePieceStartAnim(p,0);
}
@@ -176,7 +170,6 @@ else if(boardSelect != null) {
//gameView.getGuiHandler().playCardOwn(BonusCard.SHIELD);
}
}
}
@@ -190,17 +183,13 @@ else if(boardSelect != null) {
public void onAnalog(String name, float value, float tpf) {
if (name.equals("MouseLeft") && rightMousePressed) {
rotationAngle -= value * 360f;
}
else if (name.equals("MouseRight") && rightMousePressed) {
} else if (name.equals("MouseRight") && rightMousePressed) {
rotationAngle += value * 360f;
}
else if (name.equals("MouseScrollUp")) {
} else if (name.equals("MouseScrollUp")) {
scrollValue = Math.max(1, scrollValue - 5);
}
else if (name.equals("MouseScrollDown")) {
} else if (name.equals("MouseScrollDown")) {
scrollValue = Math.min(100, scrollValue + 5);
}
else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")){
} else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")) {
hoverPiece();
hoverCard();
}
@@ -211,12 +200,13 @@ else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("M
* Detects the hovered piece and updates its hover state.
*/
private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T> controlType) {
if(cam == null || root == null || controlType == null) return null;
if (cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Ray ray = new Ray(cam.getLocation(), getMousePos(cam).subtract(cam.getLocation()).normalize());
root.collideWith(ray, results);
for(CollisionResult collisionResult : results){
if(collisionResult.getGeometry().getControl(controlType) != null) return collisionResult.getGeometry().getControl(controlType);
for (CollisionResult collisionResult : results) {
if (collisionResult.getGeometry().getControl(controlType) != null)
return collisionResult.getGeometry().getControl(controlType);
}
return null;
}
@@ -225,16 +215,16 @@ private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T>
* Detects the hovered card and updates its hover state.
*/
private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Class<T> controlType) {
if(cam == null || root == null || controlType == null) return null;
if (cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Vector3f mousePos = getMousePos(cam);
mousePos.setZ(cam.getLocation().getZ());
Ray ray = new Ray(mousePos, getMousePos(cam).subtract(mousePos).normalize());
root.collideWith(ray, results);
if (results.size() > 0) {
for(CollisionResult res : results ){
for (CollisionResult res : results) {
T control = res.getGeometry().getControl(controlType);
if(control != null) return control;
if (control != null) return control;
}
}
return null;
@@ -246,15 +236,15 @@ private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Cla
*/
private void hoverPiece() {
if (app.getView() instanceof GameView gameView) {
PieceControl control = checkPiece();
OutlineOEControl control = checkPiece();
if (control != null) {
if (control != hoverPiece) {
pieceOff();
pieceOff(gameView);
hoverPiece = control;
hoverPiece.hover();
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOn(hoverPiece);
}
} else {
pieceOff();
pieceOff(gameView);
}
}
}
@@ -270,7 +260,7 @@ private void hoverCard() {
if (control != hoverCard) {
cardOff();
hoverCard = control;
hoverCard.hover();
hoverCard.hoverOn();
}
} else {
cardOff();
@@ -283,8 +273,8 @@ private void hoverCard() {
*
* @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);
private OutlineOEControl checkPiece() {
return checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
}
/**
@@ -295,17 +285,19 @@ private PieceControl checkPiece() {
*/
private CardControl checkCard(GameView gameView) {
return checkHoverOrtho(
gameView.getGuiHandler().getCardLayerCamera(),
gameView.getGuiHandler().getCardLayerRootNode(),
CardControl.class
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();
private void pieceOff(GameView gameView) {
if (hoverPiece != null) {
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOff(hoverPiece);
}
hoverPiece = null;
}
@@ -339,7 +331,7 @@ public float getRotation() {
return (rotationAngle / 2) % 360;
}
public void setRotation(float rotationAngle){
public void setRotation(float rotationAngle) {
this.rotationAngle = rotationAngle;
}

View File

@@ -1,19 +1,14 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
import com.simsilica.lemur.GuiGlobals;
import com.sun.tools.javac.Main;
import pp.mdga.client.acoustic.AcousticHandler;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.GuiGlobals;
import pp.mdga.client.acoustic.AcousticHandler;
import pp.mdga.client.animation.TimerManager;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.view.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;
@@ -26,46 +21,79 @@ public class MdgaApp extends SimpleApplication {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/** Handles acoustic effects and state-based sounds. */
/**
* Handles acoustic effects and state-based sounds.
*/
private AcousticHandler acousticHandler;
/** Synchronizes notifications throughout the application. */
/**
* Synchronizes notifications throughout the application.
*/
private NotificationSynchronizer notificationSynchronizer;
/** Manages input events and synchronization. */
/**
* Manages input events and synchronization.
*/
private InputSynchronizer inputSynchronizer;
/** Synchronizes game models. */
/**
* Synchronizes game models.
*/
private ModelSynchronizer modelSynchronizer;
/** The currently active view in the application. */
/**
* The currently active view in the application.
*/
private MdgaView view = null;
/** The current state of the application. */
/**
* The current state of the application.
*/
private MdgaState state = null;
/** Scale for rendering images. */
/**
* Scale for rendering images.
*/
private final float imageScale = prefs.getInt("scale", 1);
/** The main menu view. */
/**
* The main menu view.
*/
private MainView mainView;
/** The lobby view. */
/**
* The lobby view.
*/
private LobbyView lobbyView;
/** The game view. */
/**
* The game view.
*/
private GameView gameView;
/** The ceremony view. */
/**
* The ceremony view.
*/
private CeremonyView ceremonyView;
/** The client game logic. */
/**
* The client game logic.
*/
private final ClientGameLogic clientGameLogic;
private ExecutorService executor;
private ServerConnection networkConnection;
private final TimerManager timerManager = new TimerManager();
public static final int DEBUG_MULTIPLIER = 1;
/**
* Constructs a new MdgaApp instance.
* Initializes the network connection and client game logic.
*/
public MdgaApp() {
networkConnection = new NetworkSupport(this);
this.clientGameLogic = new ClientGameLogic(networkConnection);
@@ -81,7 +109,7 @@ public static void main(String[] args) {
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
if(prefs.getBoolean("fullscreen", false)) {
if (prefs.getBoolean("fullscreen", false)) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
@@ -142,6 +170,7 @@ public void simpleUpdate(float tpf) {
acousticHandler.update();
notificationSynchronizer.update();
inputSynchronizer.update(tpf);
timerManager.update(tpf);
}
/**
@@ -247,14 +276,22 @@ public NotificationSynchronizer getNotificationSynchronizer() {
* Prepares the app for a new game cycle.
*/
public void setup() {
}
/**
* Gets the client game logic.
*
* @return the {@link ClientGameLogic} instance
*/
public ClientGameLogic getGameLogic() {
return clientGameLogic;
}
/**
* Gets the executor service.
*
* @return the {@link ExecutorService} instance
*/
public ExecutorService getExecutor() {
if (this.executor == null) {
this.executor = Executors.newCachedThreadPool();
@@ -263,12 +300,25 @@ public ExecutorService getExecutor() {
return this.executor;
}
public ServerConnection getNetworkSupport(){
/**
* Gets the network connection.
*
* @return the {@link ServerConnection} instance
*/
public ServerConnection getNetworkSupport() {
return networkConnection;
}
/**
* Updates the resolution settings.
*
* @param width the new width
* @param height the new height
* @param imageFactor the new image factor
* @param isFullscreen whether the game is in fullscreen mode
*/
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if(isFullscreen) {
if (isFullscreen) {
int baseWidth = 1280;
int baseHeight = 720;
float baseAspectRatio = (float) baseWidth / baseHeight;
@@ -292,6 +342,9 @@ public void updateResolution(int width, int height, float imageFactor, boolean i
}
}
/**
* Restarts the application.
*/
public static void restartApp() {
try {
String javaBin = System.getProperty("java.home") + "/bin/java";
@@ -299,7 +352,7 @@ public static void restartApp() {
String className = System.getProperty("sun.java.command");
ProcessBuilder builder = new ProcessBuilder(
javaBin, "-cp", classPath, className
javaBin, "-cp", classPath, className
);
builder.start();
@@ -310,17 +363,54 @@ public static void restartApp() {
}
}
/**
* Cleans up the application after a game.
*/
public void afterGameCleanup() {
MainView main = (MainView) mainView;
main.getJoinDialog().disconnect();
main.getHostDialog().shutdownServer();
if (clientGameLogic.isHost()) {
main.getHostDialog().shutdownServer();
}
ceremonyView.afterGameCleanup();
}
public GameView getGameView(){
/**
* Gets the game view.
*
* @return the {@link GameView} instance
*/
public GameView getGameView() {
return gameView;
}
/**
* Gets the timer manager.
*
* @return the {@link TimerManager} instance
*/
public TimerManager getTimerManager() {
return timerManager;
}
/**
* Gets the ceremony view.
*
* @return the {@link CeremonyView} instance
*/
public CeremonyView getCeremonyView() {
return ceremonyView;
}
@Override
public void destroy() {
afterGameCleanup();
if (executor != null) {
executor.shutdown();
}
super.destroy();
}
}

View File

@@ -1,156 +1,243 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.server.MdgaServer;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.message.client.LobbyReadyMessage;
import pp.mdga.notification.AcquireCardNotification;
import pp.mdga.notification.DrawCardNotification;
import pp.mdga.notification.TskSelectNotification;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The ModelSynchronizer class is responsible for synchronizing the model state with the view and game logic.
*/
public class ModelSynchronizer {
private static final Logger LOGGER = Logger.getLogger(ModelSynchronizer.class.getName());
private MdgaApp app;
private UUID a;
private UUID b;
private BonusCard card;
private BonusCard card;
private boolean swap;
/**
* Constructor for ModelSynchronizer.
*
* @param app the MdgaApp instance
*/
ModelSynchronizer(MdgaApp app) {
this.app = app;
swap = false;
}
/**
* Handles the end of an animation.
*/
public void animationEnd() {
app.getGameLogic().selectAnimationEnd();
if (app.getNotificationSynchronizer().waitForAnimation) {
app.getNotificationSynchronizer().waitForAnimation = false;
} else {
app.getGameLogic().selectAnimationEnd();
}
}
public void select(UUID a, UUID b){
if(swap) selectSwap(a,b);
/**
* Selects a piece or swap based on the current state.
*
* @param a the first UUID
* @param b the second UUID
*/
public void select(UUID a, UUID b) {
if (swap) selectSwap(a, b);
else selectPiece(a);
}
/**
* Selects a swap between two pieces.
*
* @param a the first UUID
* @param b the second UUID
*/
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) {
if (a != null && b != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
/**
* Selects a single piece.
*
* @param piece the UUID of the piece
*/
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) {
if (piece != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
/**
* Selects a bonus card.
*
* @param card the BonusCard instance
*/
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) {
if (card != null) {
gameView.needConfirm();
} else {
gameView.needNoPower();
gameView.showNoPower();
}
}
/**
* Confirms the current selection.
*/
public void confirm() {
LOGGER.log(Level.INFO, "confirm");
GameView gameView = (GameView) app.getView();
if(a != null && b != null) {
selectPiece(a);
selectPiece(b);
gameView.getGuiHandler().hideText();
if (a != null && b != null) {
app.getGameLogic().selectPiece(a);
app.getGameLogic().selectPiece(b);
gameView.getBoardHandler().clearSelectable();
} else if (a != null) {
selectPiece(a);
app.getGameLogic().selectPiece(a);
gameView.getBoardHandler().clearSelectable();
} else {
if(null == card) {
selectCard(null);
} else {
selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
}
app.getGameLogic().selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
}
a = null;
b = null;
card = null;
gameView.noConfirm();
gameView.noNoPower();
gameView.hideNoPower();
}
/**
* Selects a TSK color.
*
* @param color the Color instance
*/
public void selectTsk(Color color) {
app.getGameLogic().selectTsk(color);
}
/**
* Unselects a TSK color.
*
* @param color the Color instance
*/
public void unselectTsk(Color color) {
app.getGameLogic().deselectTSK(color);
}
/**
* Handles the event of rolling dice.
*/
public void rolledDice() {
app.getGameLogic().selectDice();
}
/**
* Sets the player's name.
*
* @param name the player's name
*/
public void setName(String name) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "setName: {0}", name);
app.getGameLogic().selectName(name);
}
/**
* Sets the player's ready status.
*
* @param ready the ready status
*/
public void setReady(boolean ready) {
app.getGameLogic().selectReady(ready);
}
/**
* Sets the host port.
*
* @param port the host port
*/
public void setHost(int port) {
app.getGameLogic().selectJoin("");
}
/**
* Sets the join IP and port.
*
* @param ip the IP address
* @param port the port number
*/
public void setJoin(String ip, int port) {
app.getGameLogic().selectJoin(ip);
}
/**
* Handles the event of leaving the game.
*/
public void leave() {
app.getGameLogic().selectLeave();
}
/**
* Enters a specific game state.
*
* @param state the MdgaState instance
*/
public void enter(MdgaState state) {
LOGGER.log(Level.INFO, "enter: {0}", state);
//app.enter(state);
}
public void setSwap(boolean swap){
/**
* Proceeds to the next game state.
*/
public void next() {
app.getGameLogic().selectNext();
}
/**
* Sets the swap state.
*
* @param swap the swap state
*/
public void setSwap(boolean swap) {
this.swap = swap;
}
/**
* Forces an action.
*/
public void force() {
// Implementation needed
}
}

View File

@@ -6,23 +6,47 @@
import java.io.IOException;
/**
* The NetworkSupport class provides support for network communication between the client and server.
* It implements the MessageListener and ClientStateListener interfaces to handle incoming messages
* and client state changes, respectively.
*/
public class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
private static final System.Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
private final MdgaApp app;
private Client client;
/**
* Constructor for NetworkSupport.
*
* @param app the MdgaApp instance
*/
public NetworkSupport(MdgaApp app) {
this.app = app;
}
/**
* Returns the MdgaApp instance.
*
* @return the MdgaApp instance
*/
public MdgaApp getApp() {
return this.app;
}
/**
* Returns whether the client is connected to the server.
*
* @return true if the client is connected, false otherwise
*/
public boolean isConnected() {
return this.client != null && this.client.isConnected();
}
/**
* Connects the client to the server.
*/
public void connect() {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
@@ -36,6 +60,9 @@ public void connect() {
}
/**
* Disconnects the client from the server.
*/
public void disconnect() {
if (this.client != null) {
this.client.close();
@@ -44,6 +71,13 @@ public void disconnect() {
}
}
/**
* Initializes the network connection to the server.
*
* @param host the server host
* @param port the server port
* @throws IOException if an I/O error occurs
*/
public void initNetwork(String host, int port) throws IOException {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
@@ -55,6 +89,12 @@ public void initNetwork(String host, int port) throws IOException {
}
}
/**
* Handles incoming messages from the server.
*
* @param client the client
* @param message the message
*/
public void messageReceived(Client client, Message message) {
LOGGER.log(System.Logger.Level.INFO, "message received from server: {0}", new Object[]{message});
if (message instanceof ServerMessage serverMessage) {
@@ -63,10 +103,21 @@ public void messageReceived(Client client, Message message) {
}
/**
* Handles client connection to the server.
*
* @param client the client
*/
public void clientConnected(Client client) {
LOGGER.log(System.Logger.Level.INFO, "Client connected: {0}", new Object[]{client});
}
/**
* Handles client disconnection from the server.
*
* @param client the client
* @param disconnectInfo the disconnect information
*/
public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo disconnectInfo) {
LOGGER.log(System.Logger.Level.INFO, "Client {0} disconnected: {1}", new Object[]{client, disconnectInfo});
if (this.client != client) {
@@ -78,6 +129,11 @@ public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo
}
}
/**
* Sends a message to the server.
*
* @param message the message
*/
@Override
public void send(ClientMessage message) {
LOGGER.log(System.Logger.Level.INFO, "sending {0}", new Object[]{message});

View File

@@ -1,34 +1,72 @@
package pp.mdga.client;
import com.jme3.system.NanoTimer;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.notification.*;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
/**
* The NotificationSynchronizer class is responsible for handling and synchronizing notifications
* received from the game logic and updating the application state accordingly.
*/
public class NotificationSynchronizer {
private final MdgaApp app;
private ArrayList<Notification> notifications = new ArrayList<>();
private NanoTimer timer = new NanoTimer();
private float delay = 0;
private static final float STANDARD_DELAY = 2.5f;
public boolean waitForAnimation = false;
/**
* Constructs a NotificationSynchronizer with the specified MdgaApp instance.
*
* @param app the MdgaApp instance
*/
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
/**
* Updates the notification synchronizer by processing notifications from the game logic.
* Handles different types of notifications based on the current application state.
*/
public void update() {
Notification n = app.getGameLogic().getNotification();
while (n != null) {
if(n instanceof InfoNotification infoNotification) {
while (timer.getTimeInSeconds() >= delay) {
if (waitForAnimation) {
return;
}
Notification n = app.getGameLogic().getNotification();
if (n == null) {
return;
}
System.out.println("receive notification:" + n.getClass().getName());
timer.reset();
delay = 0;
if (n instanceof InfoNotification infoNotification) {
app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
return;
}
if(n != null) {
if (n != null) {
switch (app.getState()) {
case MAIN:
handleMain(n);
@@ -45,22 +83,34 @@ public void update() {
case NONE:
throw new RuntimeException("no notification expected: " + n.getClass().getName());
}
}
n = app.getGameLogic().getNotification();
if (0 == MdgaApp.DEBUG_MULTIPLIER) {
delay = 0;
}
}
}
}
/**
* Handles notifications when the application is in the MAIN state.
*
* @param notification the notification to handle
*/
private void handleMain(Notification notification) {
if (notification instanceof LobbyDialogNotification) {
app.enter(MdgaState.LOBBY);
} else if (notification instanceof StartDialogNotification) {
//nothing
} else {
throw new RuntimeException("notification not expected in main: "+ notification.getClass().getName());
throw new RuntimeException("notification not expected in main: " + notification.getClass().getName());
}
}
/**
* Handles notifications when the application is in the LOBBY state.
*
* @param notification the notification to handle
*/
private void handleLobby(Notification notification) {
LobbyView lobbyView = (LobbyView) app.getView();
@@ -71,7 +121,7 @@ private void handleLobby(Notification notification) {
app.enter(MdgaState.MAIN);
} else if (notification instanceof TskUnselectNotification n) {
lobbyView.setTaken(n.getColor(), false, false, null);
} else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) {
} else if (notification instanceof LobbyReadyNotification lobbyReadyNotification) {
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
} else if (notification instanceof GameNotification n) {
app.getGameView().setOwnColor(n.getOwnColor());
@@ -81,30 +131,45 @@ private void handleLobby(Notification notification) {
}
}
/**
* Handles notifications when the application is in the GAME state.
*
* @param notification the notification to handle
*/
private void handleGame(Notification notification) {
GameView gameView = (GameView) app.getView();
GuiHandler guiHandler = gameView.getGuiHandler();
BoardHandler boardHandler = gameView.getBoardHandler();
ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
Color ownColor = gameView.getOwnColor();
if (notification instanceof AcquireCardNotification n) {
guiHandler.addCardOwn(n.getBonusCard());
app.getAcousticHandler().playSound(MdgaSound.BONUS);
delay = STANDARD_DELAY;
} else if (notification instanceof RankingResponceNotification n) {
guiHandler.hideText();
n.getRankingResults().forEach((c, i) -> {
guiHandler.rollRankingResult(c, i);
});
delay = STANDARD_DELAY;
} else if (notification instanceof ActivePlayerNotification n) {
guiHandler.hideText();
boardHandler.hideDice();
gameView.getGuiHandler().setActivePlayer(n.getColor());
boardHandler.showDice(n.getColor());
if (n.getColor() != ownColor) boardHandler.showDice(n.getColor());
app.getAcousticHandler().playSound(MdgaSound.UI90);
delay = STANDARD_DELAY;
} else if (notification instanceof CeremonyNotification ceremonyNotification) {
app.enter(MdgaState.CEREMONY);
CeremonyView ceremonyView = (CeremonyView) app.getView();
CeremonyView ceremonyView = app.getCeremonyView();
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) {
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.");
}
@@ -118,53 +183,77 @@ private void handleGame(Notification notification) {
int v5 = ceremonyNotification.getNodesMoved().get(i);
int v6 = ceremonyNotification.getBonusNodes().get(i);
ceremonyView.addCeremonyParticipant(color, i, name);
if(i < size - 1) {
ceremonyView.addCeremonyParticipant(color, i + 1, name);
}
ceremonyView.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
}
app.enter(MdgaState.CEREMONY);
} else if (notification instanceof DiceNowNotification) {
guiHandler.hideText();
guiHandler.showDice();
} else if (notification instanceof DrawCardNotification n) {
app.getAcousticHandler().playSound(MdgaSound.BONUS);
guiHandler.drawCard(n.getColor());
delay = STANDARD_DELAY;
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
waitForAnimation = true;
} else if (notification instanceof InterruptNotification notification1) {
gameView.enterInterrupt(notification1.getColor());
} else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) {
if (n.isMoveStart()) {
//StartMove
boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
}
else {
waitForAnimation = true;
} else {
//InfieldMove
boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
waitForAnimation = true;
}
guiHandler.hideText();
} else if (notification instanceof ThrowPieceNotification n) {
boardHandler.throwBombAnim(n.getPieceId());
} else if (notification instanceof NoShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceId());
boardHandler.throwPiece(n.getPieceId(), n.getThrowColor());
waitForAnimation = true;
} else if (notification instanceof RemoveShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceUuid());
} else if (notification instanceof PlayCardNotification n) {
if(n.getColor() == gameView.getOwnColor()) guiHandler.playCardOwn(n.getCard());
if (n.getCard() == BonusCard.TURBO) {
app.getAcousticHandler().playSound(MdgaSound.TURBO);
guiHandler.turbo();
} else if (n.getCard() == BonusCard.SHIELD) {
app.getAcousticHandler().playSound(MdgaSound.SHIELD);
} else if (n.getCard() == BonusCard.SWAP) {
app.getAcousticHandler().playSound(MdgaSound.SWAP);
}
if (n.getColor() == ownColor) guiHandler.playCardOwn(n.getCard());
else guiHandler.playCardEnemy(n.getColor(), n.getCard());
new Timer().schedule(new TimerTask() {
@Override
public void run() {
app.getModelSynchronize().animationEnd();
}
}, 2200 * MdgaApp.DEBUG_MULTIPLIER);
} else if (notification instanceof PlayerInGameNotification n) {
boardHandler.addPlayer(n.getColor(),n.getPiecesList());
guiHandler.addPlayer(n.getColor(),n.getName());
boardHandler.addPlayer(n.getColor(), n.getPiecesList());
guiHandler.addPlayer(n.getColor(), n.getName());
} else if (notification instanceof ResumeNotification) {
gameView.leaveInterrupt();
} else if (notification instanceof RollDiceNotification n) {
gameView.getGuiHandler().hideText();
if(n.getColor() == gameView.getOwnColor()){
if (n.getColor() == ownColor) {
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
}
else {
boardHandler.hideDice();
waitForAnimation = true;
} 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());
gameView.needNoPower();
gameView.showNoPower();
} else if (notification instanceof ShieldActiveNotification n) {
boardHandler.shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification n) {
@@ -173,28 +262,33 @@ private void handleGame(Notification notification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof SwapPieceNotification n) {
// boardHandler.swapPieces(n.getFirstPiece(), n.getSecondPiece());
boardHandler.swapPieceAnim(n.getFirstPiece(), n.getSecondPiece());
guiHandler.swap();
} else if (notification instanceof WaitMoveNotification) {
//TODO ???
//nothing
} else if (notification instanceof SelectableMoveNotification n) {
boardHandler.outlineMove(n.getPieces(), n.getMoveIndices(), n.getHomeMoves());
modelSynchronizer.setSwap(false);
} else if (notification instanceof SelectableSwapNotification n) {
boardHandler.outlineSwap(n.getOwnPieces(), n.getEnemyPieces());
modelSynchronizer.setSwap(true);
} else if (notification instanceof SelectableShieldNotification n) {
} else if (notification instanceof SelectableShieldNotification n) {
boardHandler.outlineShield(n.getPieces());
modelSynchronizer.setSwap(false);
} else if (notification instanceof TurboActiveNotification){
guiHandler.turbo();
} else if (notification instanceof FinishNotification n){
} else if (notification instanceof TurboActiveNotification) {
//nothing
} else if (notification instanceof FinishNotification n) {
guiHandler.finish(n.getColorFinished());
} else {
throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
}
}
/**
* Handles notifications when the application is in the CEREMONY state.
*
* @param notification the notification to handle
*/
private void handleCeremony(Notification notification) {
if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();

View File

@@ -0,0 +1,48 @@
package pp.mdga.client;
import com.jme3.math.Vector3f;
public class Util {
private Util() {
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The ending value.
* @param t A parameter between 0 and 1 representing the interpolation progress.
* @return The interpolated value.
*/
public static float linInt(float start, float end, float t) {
return start + t * (end - start);
}
/**
* Performs quadratic interpolation between three points.
*
* @param p1 The initial point.
* @param p2 The middle point.
* @param p3 The final point.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated point.
*/
public static Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
// Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
float oneMinusT = 1 - t;
return p1.mult(oneMinusT * oneMinusT)
.add(p2.mult(2 * oneMinusT * t))
.add(p3.mult(t * t));
}
/**
* A smooth ease-in-out function for interpolation.
* It accelerates and decelerates the interpolation for a smoother effect.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The adjusted interpolation value.
*/
public static float easeInOut(float x) {
return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
}
}

View File

@@ -4,7 +4,10 @@
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import java.util.prefs.Preferences;
public class AcousticHandler {
@@ -18,12 +21,14 @@ public class AcousticHandler {
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 FADE_DURATION = 2.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 GameMusic birds;
private float mainVolume = 0.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
@@ -38,6 +43,8 @@ public AcousticHandler(MdgaApp app) {
mainVolume = prefs.getFloat("mainVolume", 1.0f);
musicVolume = prefs.getFloat("musicVolume", 1.0f);
soundVolume = prefs.getFloat("soundVolume", 1.0f);
birds = new GameMusic(app, MusicAsset.BIRDS, getSoundVolumeTotal(), MusicAsset.BIRDS.getSubVolume(), MusicAsset.BIRDS.getLoop(), 0.0f);
}
/**
@@ -60,6 +67,8 @@ public void update() {
iterator.remove();
}
}
birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
}
/**
@@ -132,6 +141,25 @@ public void playSound(MdgaSound sound) {
case MATRIX:
assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
break;
case TURRET_ROTATE:
assets.add(new SoundAssetDelayVolume(SoundAsset.TURRET_ROTATE, 0.7f, 0f));
break;
case TANK_SHOOT:
assets.add(new SoundAssetDelayVolume(SoundAsset.TANK_SHOOT, 0.7f, 0f));
break;
case TANK_EXPLOSION:
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
break;
case SHIELD:
assets.add(new SoundAssetDelayVolume(SoundAsset.SHIELD, 1.0f, 0f));
break;
case TURBO:
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 0.1f));
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 1.3f));
break;
case SWAP:
assets.add(new SoundAssetDelayVolume(SoundAsset.SWAP, 1.0f, 0f));
break;
default:
break;
}
@@ -153,6 +181,10 @@ public void playState(MdgaState state) {
}
MusicAsset asset = null;
birds.pause();
float pause = 0.0f;
switch (state) {
case MAIN:
playGame = false;
@@ -163,10 +195,12 @@ public void playState(MdgaState state) {
asset = MusicAsset.LOBBY;
break;
case GAME:
birds.play();
addGameTracks();
playGame = true;
assert (!gameTracks.isEmpty()) : "no more game music available";
asset = gameTracks.remove(0);
pause = 2.0f;
break;
case CEREMONY:
playGame = false;
@@ -178,7 +212,7 @@ public void playState(MdgaState state) {
assert (null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f);
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), pause);
}
/**
@@ -195,20 +229,20 @@ private float lerp(float start, float end, float t) {
/**
* Updates the state of audio playback, handling track transitions and volume adjustments.
*
* <p>
* 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.
*
* <p>
* 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.
* 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.
* 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.
*
* <p>
* 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.
@@ -260,23 +294,23 @@ private void updateVolumeAndTrack() {
/**
* Manages the fading process during audio track transitions.
*
* <p>
* 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.
*
* <p>
* 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.
* 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.
*
* the new track (`playing`) and initiates the infade process.
* <p>
* 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.
*
* <p>
* Preconditions:
* - `fading` is expected to be `true` when this method is called.
* - The method is invoked as part of the `updateVolumeAndTrack` process.
@@ -312,23 +346,23 @@ private void handleFadeProcess() {
/**
* Manages the fade-in process for the currently playing track.
*
* <p>
* 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.
*
* <p>
* 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`.
* `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.
*
* - Marks the fade process (`fading`) as complete.
* - Ensures the `playing` track is updated to its full volume.
* <p>
* 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.
*
* <p>
* 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.
@@ -371,7 +405,7 @@ private void addGameTracks() {
* 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) {
if (null == playing) {
return;
}
@@ -454,7 +488,7 @@ public void setSoundVolume(float soundVolume) {
*/
float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume();
return getMusicVolume() * getMainVolume() / 2;
}
/**

View File

@@ -38,4 +38,10 @@ public enum MdgaSound {
UI90,
MISSILE,
MATRIX,
TURRET_ROTATE,
TANK_SHOOT,
TANK_EXPLOSION,
SHIELD,
TURBO,
SWAP,
}

View File

@@ -10,12 +10,13 @@ enum MusicAsset {
MAIN_MENU("Spaceship.wav", true, 1.0f),
LOBBY("DeadPlanet.wav", true, 1.0f),
CEREMONY("80s,Disco,Life.wav", true, 1.0f),
GAME_1("NeonRoadTrip.wav", 1.0f),
GAME_2("NoPressureTrance.wav", 1.0f),
GAME_3("TheSynthRave.wav", 1.0f),
GAME_4("LaserParty.wav", 1.0f),
GAME_5("RetroNoir.wav", 1.0f),
GAME_6("SpaceInvaders.wav", 1.0f);
GAME_1("NeonRoadTrip.wav", 0.5f),
GAME_2("NoPressureTrance.wav", 0.5f),
GAME_3("TheSynthRave.wav", 0.5f),
GAME_4("LaserParty.wav", 0.5f),
GAME_5("RetroNoir.wav", 0.5f),
GAME_6("SpaceInvaders.wav", 0.5f),
BIRDS("nature-ambience.ogg", true, 1.0f);
private final String path;
private final boolean loop;

View File

@@ -37,7 +37,14 @@ enum SoundAsset {
LOSE("lose.ogg"),
MISSILE("missile.ogg"),
MATRIX("matrix.wav"),
CONNECTED("connected.wav");
CONNECTED("connected.wav"),
TURRET_ROTATE("turret_rotate.ogg"),
TANK_SHOOT("tank_shoot.ogg"),
SHIELD("shield.ogg"),
SPEED("speed.ogg"),
SWAP("swap.ogg"),
;
private final String path;

View File

@@ -4,4 +4,5 @@
* 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

@@ -0,0 +1,25 @@
package pp.mdga.client.animation;
import pp.mdga.client.InitControl;
public class ActionControl extends InitControl {
private final Runnable runnable;
/**
* Constructs a new ActionControl object with the specified action.
*
* @param runnable The action to be performed.
*/
public ActionControl(Runnable runnable) {
this.runnable = runnable;
}
/**
* Performs the action associated with this control.
*/
protected void action() {
if (null != runnable) {
runnable.run();
}
}
}

View File

@@ -10,6 +10,10 @@
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* The {@code Explosion} class represents an explosion effect in a 3D environment.
* It manages the creation, configuration, and triggering of particle emitters for fire and smoke effects.
*/
public class Explosion {
private final Node rootNode;
@@ -23,11 +27,11 @@ public class Explosion {
private final Material mat;
/**
* Konstruktor für die Explosion.
* Constructor for the {@code Explosion} class.
*
* @param app Die Hauptanwendung.
* @param rootNode Der Root-Knoten, an den die Explosion angefügt wird.
* @param location Der Ort der Explosion in World-Koordinaten.
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
@@ -35,20 +39,24 @@ public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.png"));
}
/**
* Initialisiert den Partikel-Emitter für die Explosion.
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", Type.Triangle,50);
fire = new ParticleEmitter("Effect", Type.Triangle, 50);
fire.setMaterial(mat);
fire.setImagesX(2);
fire.setImagesY(2);
fire.setStartColor(ColorRGBA.Yellow);
fire.setEndColor(ColorRGBA.Red);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f,0.2f,4f));
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.1f);
fire.setEndSize(0.8f);
fire.setStartSize(0.7f);
fire.setEndSize(1.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(2.2f);
@@ -56,16 +64,16 @@ private void initializeEmitter() {
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", Type.Triangle,40);
smoke = new ParticleEmitter("Effect2", Type.Triangle, 40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,0.7f));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.7f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.2f);
smoke.setEndSize(0.5f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(5.5f);
@@ -77,7 +85,8 @@ private void initializeEmitter() {
}
/**
* Löst die Explosion aus.
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {
@@ -100,7 +109,8 @@ protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
rootNode.attachChild(smoke);
@@ -118,7 +128,8 @@ protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
}
}

View File

@@ -0,0 +1,69 @@
package pp.mdga.client.animation;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import static pp.mdga.client.Util.linInt;
public class FadeControl extends ActionControl {
private float duration; // Duration of the fade effect
private float timeElapsed = 0;
private boolean init = false;
private float startAlpha;
private float endAlpha;
public FadeControl(float duration, float startAlpha, float endAlpha, Runnable actionAfter) {
super(actionAfter);
this.duration = duration;
this.startAlpha = startAlpha;
this.endAlpha = endAlpha;
}
public FadeControl(float duration, float startAlpha, float endAlpha) {
this(duration, startAlpha, endAlpha, null);
}
@Override
protected void initSpatial() {
init = true;
}
@Override
protected void controlUpdate(float tpf) {
if (!init) return;
timeElapsed += tpf;
float t = timeElapsed / duration; // Calculate progress (0 to 1)
if (t >= 1) {
// Fade complete
t = 1;
init = false;
spatial.removeControl(this);
action();
}
float alpha = linInt(startAlpha, endAlpha, t); // Interpolate alpha
// Update the material's alpha
if (spatial instanceof Geometry geometry) {
Material mat = geometry.getMaterial();
if (mat != null) {
ColorRGBA diffuse = (ColorRGBA) mat.getParam("Diffuse").getValue();
mat.setColor("Diffuse", new ColorRGBA(diffuse.r, diffuse.g, diffuse.b, alpha));
ColorRGBA ambient = (ColorRGBA) mat.getParam("Ambient").getValue();
mat.setColor("Ambient", new ColorRGBA(ambient.r, ambient.g, ambient.b, alpha));
// Disable shadows when the object is nearly invisible
if (alpha <= 0.1f) {
geometry.setShadowMode(RenderQueue.ShadowMode.Off);
} else {
geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
}
} else throw new RuntimeException("Material is null");
} else throw new RuntimeException("Spatial is not instance of Geometry");
}
}

View File

@@ -12,35 +12,35 @@
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.view.GameView;
import java.util.UUID;
/**
* The {@code JetAnimation} class handles the animation of a jet model in a 3D environment.
* It creates a jet model, animates its movement along a curved path, triggers an explosion at a target point,
* and performs additional actions upon animation completion.
*/
public class JetAnimation {
private final MdgaApp app; // Referenz auf die Hauptanwendung
private final Node rootNode; // Root-Knoten, an dem die Animation hängt
private Spatial jetModel; // Das Model des "jet"
private final Vector3f spawnPoint; // Spawnpunkt des Jets
private final Vector3f nodePoint; // Punkt des überflogenen Knotens
private final Vector3f despawnPoint; // Punkt, an dem der Jet despawnt
private final float curveHeight; // Maximale Höhe der Kurve
private final float animationDuration; // Dauer der Animation
private final MdgaApp app;
private final Node rootNode;
private Spatial jetModel;
private final Vector3f spawnPoint;
private final Vector3f nodePoint;
private final Vector3f despawnPoint;
private final float curveHeight;
private final float animationDuration;
private Explosion explosion;
private final UUID id;
private Runnable actionAfter;
/**
* Konstruktor für die ThrowAnimation-Klasse.
* Constructor for the {@code JetAnimation} class.
*
* @param app Die Hauptanwendung
* @param rootNode Der Root-Knoten, an dem der Jet angefügt wird
* @param uuid Die UUID des pieces
* @param targetPoint Der Punkt, an dem der Jet spawnt
* @param curveHeight Die maximale Höhe der Flugkurve
* @param animationDuration Die Gesamtdauer der Animation in Sekunden
* @param app The main application managing the jet animation.
* @param rootNode The root node to which the jet model will be attached.
* @param targetPoint The target point where the explosion will occur.
* @param curveHeight The height of the curve for the jet's flight path.
* @param animationDuration The total duration of the jet animation.
*/
public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint, float curveHeight, float animationDuration) {
public JetAnimation(MdgaApp app, Node rootNode, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
Vector3f spawnPoint = targetPoint.add(170, 50, 50);
Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
@@ -55,13 +55,12 @@ public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint,
this.curveHeight = curveHeight;
this.animationDuration = animationDuration;
id = uuid;
explosion = new Explosion(app, rootNode, targetPoint);
this.actionAfter = actionAfter;
}
/**
* Startet die Animation.
* Starts the jet animation by spawning the jet model and initiating its movement along the predefined path.
*/
public void start() {
app.getAcousticHandler().playSound(MdgaSound.JET);
@@ -70,23 +69,25 @@ public void start() {
}
/**
* Spawnt den Jet an der spezifizierten Position.
* Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
*/
private void spawnJet() {
jetModel = app.getAssetManager().loadModel(Asset.jet.getModelPath());
jetModel = app.getAssetManager().loadModel(Asset.jet_noGear.getModelPath());
jetModel.setLocalTranslation(spawnPoint);
jetModel.scale(Asset.jet.getSize());
jetModel.scale(Asset.jet_noGear.getSize());
jetModel.rotate(FastMath.HALF_PI, 0, 0);
jetModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet.getDiffPath()));
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet_noGear.getDiffPath()));
jetModel.setMaterial(mat);
rootNode.attachChild(jetModel);
}
/**
* Animiert den Jet entlang einer Kurve und lässt ihn anschließend verschwinden.
* actionAfter
* Animates the jet along a Bezier curve path, triggers the explosion effect at the appropriate time,
* and performs cleanup operations after the animation completes.
*/
private void animateJet() {
Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
@@ -102,7 +103,7 @@ protected void controlUpdate(float tpf) {
elapsedTime += tpf;
float progress = elapsedTime / animationDuration;
if(elapsedTime > 4.2f) {
if (elapsedTime > 4.2f) {
explosion.trigger();
}
@@ -118,26 +119,35 @@ protected void controlUpdate(float tpf) {
}
if (elapsedTime > 6.0f) {
GameView gameView = (GameView) app.getView();
BoardHandler boardHandler = gameView.getBoardHandler();
boardHandler.throwPieceAnim(id);
endAnim();
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// Wird hier nicht benötigt
}
});
}
private void endAnim() {
actionAfter.run();
}
/**
* Repräsentiert eine 3D-Bezier-Kurve mit vier Kontrollpunkten.
* The {@code BezierCurve3f} class represents a 3D cubic Bezier curve.
* It provides methods to interpolate positions and derivatives along the curve.
*/
private static class BezierCurve3f {
private final Vector3f p0, p1, p2, p3;
/**
* Constructor for the {@code BezierCurve3f} class.
*
* @param p0 The starting point of the curve.
* @param p1 The first control point influencing the curve's shape.
* @param p2 The second control point influencing the curve's shape.
* @param p3 The endpoint of the curve.
*/
public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p0 = p0;
this.p1 = p1;
@@ -145,6 +155,12 @@ public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p3 = p3;
}
/**
* Interpolates a position along the curve at a given progress value {@code t}.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The interpolated position on the curve.
*/
public Vector3f interpolate(float t) {
float u = 1 - t;
float tt = t * t;
@@ -159,6 +175,12 @@ public Vector3f interpolate(float t) {
return point;
}
/**
* Computes the derivative at a given progress value {@code t}, representing the direction along the curve.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The derivative (direction vector) at the specified progress.
*/
public Vector3f interpolateDerivative(float t) {
float u = 1 - t;
float tt = t * t;

View File

@@ -0,0 +1,253 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import pp.mdga.client.MdgaApp;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* MatrixAnimation class handles the animation of radar and matrix particle effects.
*/
public class MatrixAnimation extends ActionControl {
private MdgaApp app;
private static final Random RANDOM = new Random();
private Vector3f radarPos;
private Runnable runnable;
private boolean init = false;
private List<ParticleEmitter> activeEmitter = new ArrayList<>();
private ParticleEmitter radarEmitter = null;
private float timeElapsed = 0f;
/**
* Enum representing the states of the matrix animation.
*/
private enum MatrixState {
RADAR_ON,
RADAR_OFF,
MATRIX_ON,
MATRIX_OFF
}
private MatrixState state;
/**
* Constructor for MatrixAnimation.
*
* @param app the application instance
* @param radarPos the position of the radar
* @param runnable the runnable action to be executed
*/
public MatrixAnimation(MdgaApp app, Vector3f radarPos, Runnable runnable) {
super(runnable);
this.app = app;
this.radarPos = radarPos;
}
/**
* Initializes the spatial and sets the initial state to RADAR_ON.
*/
@Override
protected void initSpatial() {
state = MatrixState.RADAR_ON;
timeElapsed = 0;
init = true;
radar();
}
/**
* Updates the control based on the time per frame (tpf).
*
* @param tpf the time per frame
*/
@Override
protected void controlUpdate(float tpf) {
if (!init) return;
timeElapsed += tpf;
switch (state) {
case RADAR_ON -> {
if (timeElapsed >= 2f) {
state = MatrixState.RADAR_OFF;
timeElapsed = 0;
radarEmitter.setParticlesPerSec(0);
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
app.getRootNode().detachChild(radarEmitter);
System.out.println("delete radar");
return null;
}));
}
}
case RADAR_OFF -> {
if (timeElapsed >= 0.1f) {
state = MatrixState.MATRIX_ON;
timeElapsed = 0;
matrix();
}
}
case MATRIX_ON -> {
if (timeElapsed >= 3f) {
state = MatrixState.MATRIX_OFF;
timeElapsed = 0;
turnOff();
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
for (ParticleEmitter particleEmitter : activeEmitter) {
app.getRootNode().detachChild(particleEmitter);
}
System.out.println("delete particle");
return null;
}));
}
}
case MATRIX_OFF -> {
if (timeElapsed >= 0.5f) {
init = false;
spatial.removeControl(this);
action();
}
}
}
}
/**
* Turns off all active particle emitters.
*/
private void turnOff() {
for (ParticleEmitter particleEmitter : activeEmitter) {
particleEmitter.setParticlesPerSec(0f);
}
}
/**
* Initializes the radar particle emitter.
*/
private void radar() {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/radar_beam.png"));
ParticleEmitter emitter = new ParticleEmitter("Effect", Type.Triangle, 50);
emitter.setMaterial(mat);
emitter.setImagesX(1); // columns
emitter.setImagesY(1); // rows
emitter.setSelectRandomImage(true);
emitter.setStartColor(ColorRGBA.White);
emitter.setEndColor(ColorRGBA.Black);
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, 2));
emitter.getParticleInfluencer().setVelocityVariation(0f);
emitter.setStartSize(0.1f);
emitter.setEndSize(10);
emitter.setGravity(0, 0, 0);
float life = 2.6f;
emitter.setLowLife(life);
emitter.setHighLife(life);
emitter.setLocalTranslation(radarPos.add(new Vector3f(0, 0, 5)));
emitter.setParticlesPerSec(1.8f);
app.getRootNode().attachChild(emitter);
radarEmitter = emitter;
}
/**
* Initializes multiple matrix particle streams.
*/
private void matrix() {
for (int i = 0; i < 5; i++) {
particleStream(
generateMatrixColor(),
generateMatrixColor(),
getRandomFloat(0, 1f),
getRandomPosition(),
getRandomFloat(1, 2)
);
}
}
/**
* Creates a particle stream with the specified parameters.
*
* @param start the start color of the particles
* @param end the end color of the particles
* @param speedVar the speed variation of the particles
* @param pos the position of the particles
* @param spawnVar the spawn rate variation of the particles
*/
private void particleStream(ColorRGBA start, ColorRGBA end, float speedVar, Vector3f pos, float spawnVar) {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/particle_cir.png"));
ParticleEmitter matrix = new ParticleEmitter("Effect", Type.Triangle, 50);
matrix.setMaterial(mat);
matrix.setImagesX(2); // columns
matrix.setImagesY(1); // rows
matrix.setSelectRandomImage(true);
matrix.setStartColor(start);
matrix.setEndColor(end);
matrix.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, -6f - speedVar));
matrix.getParticleInfluencer().setVelocityVariation(0f);
matrix.setStartSize(0.4f);
matrix.setEndSize(0.6f);
matrix.setGravity(0, 0, 2f);
matrix.setLowLife(3f);
matrix.setHighLife(3f);
matrix.setLocalTranslation(spatial.getLocalTranslation().add(pos).add(new Vector3f(0, 0, 15)));
matrix.setParticlesPerSec(spawnVar);
app.getRootNode().attachChild(matrix);
activeEmitter.add(matrix);
}
/**
* Generates a random position vector.
*
* @return a random position vector
*/
public static Vector3f getRandomPosition() {
// Generate a random angle in radians (0 to 2π)
float angle = (float) (2 * Math.PI * RANDOM.nextDouble());
// Generate a random radius with uniform distribution
float radius = (float) Math.sqrt(RANDOM.nextDouble());
radius *= 1f;
// Convert polar coordinates to Cartesian
float x = radius * (float) Math.cos(angle);
float y = radius * (float) Math.sin(angle);
return new Vector3f(x, y, 0);
}
/**
* Generates a random float between the specified start and end values.
*
* @param start the start value
* @param end the end value
* @return a random float between start and end
*/
public static float getRandomFloat(float start, float end) {
if (start > end) {
throw new IllegalArgumentException("Start must be less than or equal to end.");
}
return start + RANDOM.nextFloat() * (end - start);
}
/**
* Generates a random color for the matrix particles.
*
* @return a random ColorRGBA object
*/
public static ColorRGBA generateMatrixColor() {
// Red is dominant
float red = 0.8f + RANDOM.nextFloat() * 0.2f; // Red channel: 0.8 to 1.0
// Green is moderately high
float green = 0.4f + RANDOM.nextFloat() * 0.3f; // Green channel: 0.4 to 0.7
// Blue is minimal
float blue = RANDOM.nextFloat() * 0.2f; // Blue channel: 0.0 to 0.2
float alpha = 1.0f; // Fully opaque
return new ColorRGBA(red, green, blue, alpha);
}
}

View File

@@ -1,6 +1,9 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
@@ -14,123 +17,165 @@
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import java.util.UUID;
/**
* The {@code MissileAnimation} class handles the animation of a missile moving along a parabolic path
* towards a target point in a 3D environment. It also triggers an explosion at the target upon impact.
*/
public class MissileAnimation {
private final Node rootNode; // Root-Knoten, an den die Animation gehängt wird
private final MdgaApp app; // Referenz auf die Hauptanwendung
private final Vector3f start; // Startpunkt der Rakete
private final Vector3f target; // Zielpunkt der Rakete
private final float flightTime; // Gesamtdauer des Flugs
private final Node rootNode;
private final MdgaApp app;
private final Vector3f start;
private final Vector3f target;
private final float flightTime;
private Explosion explosion;
private Spatial missileModel; // 3D-Modell der Rakete
private Spatial missileModel;
private Runnable actionAfter;
private ParticleEmitter smoke;
private UUID id;
private Node missileNode = new Node();
private final Material mat;
/**
* Konstruktor für die MissileAnimation.
* Constructor for the {@code MissileAnimation} class.
*
* @param app Die Hauptanwendung.
* @param rootNode Der Root-Knoten, an den die Animation gehängt wird.
* @param target Der Zielpunkt der Rakete.
* @param flightTime Die Zeit, die die Rakete für den gesamten Flug benötigt.
* @param app The main application managing the missile animation.
* @param rootNode The root node to which the missile model will be attached.
* @param target The target point where the missile will explode.
* @param flightTime The total flight time of the missile.
*/
public MissileAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f target, float flightTime) {
public MissileAnimation(MdgaApp app, Node rootNode, Vector3f target, float flightTime, Runnable actionAfter) {
this.app = app;
this.rootNode = rootNode;
this.flightTime = flightTime;
this.actionAfter = actionAfter;
explosion = new Explosion(app, rootNode, target);
id = uuid;
this.target = target.add(new Vector3f(1.5f, -1, 0));
start = BoardHandler.gridToWorld(12, 0);
start.add(new Vector3f(0, 0, 0));
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 400);
smoke.setMaterial(mat);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
smoke.getParticleInfluencer().setVelocityVariation(0.1f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(3.5f);
smoke.setParticlesPerSec(100);
missileNode.attachChild(smoke);
smoke.move(1, 0.85f, 1.0f);
}
/**
* Startet die Raketenanimation.
* Starts the missile animation by loading the missile model and initiating its parabolic movement.
*/
public void start() {
Smoke s = new Smoke(app, rootNode, start);
s.trigger();
loadMissile();
app.getAcousticHandler().playSound(MdgaSound.MISSILE);
animateMissile();
}
/**
* Lädt das Raketenmodell und setzt es auf den Startpunkt.
* Loads the missile model into the scene, applies scaling, material, and sets its initial position.
*/
private void loadMissile() {
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath()); // Lade das Missile-Modell
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath());
missileModel.scale(Asset.missile.getSize());
missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
missileModel.setMaterial(mat);
missileModel.setLocalTranslation(start); // Setze Startposition
rootNode.attachChild(missileModel); // Füge das Modell zur Szene hinzu
missileNode.setLocalTranslation(start);
missileNode.attachChild(missileModel);
rootNode.attachChild(missileNode);
}
/**
* Animiert die Rakete entlang einer Parabel.
* Animates the missile along a parabolic path, triggers the explosion near the target,
* and removes the missile model after the animation completes.
*/
private void animateMissile() {
missileModel.addControl(new AbstractControl() {
missileNode.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
if (elapsedTime > 6) {
endAnim();
rootNode.detachChild(missileNode);
this.spatial.removeControl(this);
}
elapsedTime += tpf;
float progress = elapsedTime / flightTime;
if (progress >= 0.55) {
smoke.setParticlesPerSec(30);
}
if (progress >= 0.7) {
smoke.setParticlesPerSec(0);
}
if (progress >= 0.95f) {
explosion.trigger();
}
if (progress >= 1) {
explosion.trigger();
// Flug abgeschlossen
rootNode.detachChild(missileModel); // Entferne Rakete nach dem Ziel
this.spatial.removeControl(this); // Entferne die Steuerung
return;
missileNode.detachChild(missileModel);
}
// Berechne die aktuelle Position entlang der Parabel
Vector3f currentPosition = computeParabolicPath(start, target, progress);
missileModel.setLocalTranslation(currentPosition);
missileNode.setLocalTranslation(currentPosition);
// Passe die Ausrichtung an (Nase der Rakete zeigt in Flugrichtung)
Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
.subtract(currentPosition)
.normalizeLocal();
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y); // Z ist oben, Y ist "Up"
.subtract(currentPosition)
.normalizeLocal();
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y);
missileModel.rotate(0, FastMath.HALF_PI, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// Keine Render-Logik benötigt
}
});
}
private void endAnim() {
actionAfter.run();
}
/**
* Berechnet eine Parabelbewegung von `start` zu `target`.
* Computes a position along a parabolic path at a given progress value {@code t}.
*
* @param start Der Startpunkt der Rakete.
* @param target Der Zielpunkt der Rakete.
* @param t Der Fortschritt des Flugs (0 bis 1).
* @return Die Position der Rakete entlang der Parabel.
* @param start The starting point of the missile's flight.
* @param target The target point of the missile's flight.
* @param t The progress value (0.0 to 1.0) along the flight path.
* @return The interpolated position along the parabolic path.
*/
private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
Vector3f midPoint = start.add(target).multLocal(0.5f); // Berechne die Mitte zwischen Start und Ziel
midPoint.addLocal(0, 0, 20); // Erhöhe den Scheitelpunkt der Parabel entlang der Z-Achse
Vector3f midPoint = start.add(target).multLocal(0.5f);
midPoint.addLocal(0, 0, 20);
// Quadratische Interpolation (Parabel)
Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
return FastMath.interpolateLinear(t, startToMid, midToTarget);

View File

@@ -1,7 +1,9 @@
package pp.mdga.client.animation;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
import static pp.mdga.client.Util.easeInOut;
import static pp.mdga.client.Util.quadInt;
/**
* A control that smoothly moves a spatial from an initial position to an end position
@@ -12,35 +14,42 @@
* an ease-in-out curve to create a smooth start and stop effect.
* </p>
*/
public class MoveControl extends InitControl {
public class MoveControl extends ActionControl {
private boolean moving;
private final Vector3f initPos;
private final Vector3f endPos;
private final Vector3f middlePos;
private final static float HEIGHT = 2;
private final static float MOVE_SPEED = 1f;
private float progress = 0;
private final Runnable actionAfter;
private final float height;
private final float duration;
private float timer = 0;
private boolean easing;
/**
* Creates a new MoveControl with specified initial and end positions, and an action to run after the movement.
* The movement follows a path with a midpoint at a fixed height.
*
* @param initPos The starting position of the spatial.
* @param endPos The target position of the spatial.
* @param initPos The starting position of the spatial.
* @param endPos The target position of the spatial.
* @param actionAfter A Runnable that will be executed after the movement finishes.
*/
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter) {
this(initPos, endPos, actionAfter, 2, 1, true);
}
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter, float height, float duration, boolean easing) {
super(actionAfter);
moving = false;
this.initPos = initPos;
this.endPos = endPos;
this.height = height;
this.duration = duration;
this.easing = easing;
middlePos = new Vector3f(
(initPos.x + endPos.x) / 2,
(initPos.y + endPos.y) / 2,
HEIGHT
(initPos.x + endPos.x) / 2,
(initPos.y + endPos.y) / 2,
height
);
this.actionAfter = actionAfter;
}
/**
@@ -50,7 +59,7 @@ public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
@Override
protected void initSpatial() {
moving = true;
progress = 0;
timer = 0;
}
/**
@@ -62,48 +71,28 @@ protected void initSpatial() {
*/
@Override
protected void controlUpdate(float tpf) {
if(!moving) return;
progress += tpf * MOVE_SPEED;
if(progress > 1) progress = 1;
spatial.setLocalTranslation(quadInt(initPos,middlePos,endPos, easeInOut(progress)));
if(progress == 1) end();
if (!moving) return;
timer += tpf;
float t = timer / duration;
if (t >= 1) t = 1;
float interpolated = easing ? easeInOut(t) : t;
spatial.setLocalTranslation(quadInt(initPos, middlePos, endPos, interpolated));
if (t >= 1) end();
}
/**
* Ends the movement by stopping the interpolation, running the action after the movement,
* and removing this control from the spatial.
*/
private void end(){
private void end() {
moving = false;
actionAfter.run();
spatial.removeControl(this);
action();
}
/**
* Performs quadratic interpolation between three points.
*
* @param p1 The initial point.
* @param p2 The middle point.
* @param p3 The final point.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated point.
*/
private Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
// Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
float oneMinusT = 1 - t;
return p1.mult(oneMinusT * oneMinusT)
.add(p2.mult(2 * oneMinusT * t))
.add(p3.mult(t * t));
}
/**
* A smooth ease-in-out function for interpolation.
* It accelerates and decelerates the interpolation for a smoother effect.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The adjusted interpolation value.
*/
private float easeInOut(float x){
return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
}
}

View File

@@ -0,0 +1,199 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.TankTopControl;
import java.util.Timer;
import java.util.TimerTask;
import static com.jme3.material.Materials.LIGHTING;
/**
* ShellAnimation class handles the animation of a shell being fired from a tank.
*/
public class ShellAnimation extends ActionControl {
private static final float FLYING_DURATION = 1.25f;
private static final float FLYING_HEIGHT = 12f;
private TankTopControl tankTopControl;
private MdgaApp app;
/**
* Constructor for ShellAnimation.
*
* @param tankTopControl the control for the tank top
* @param app the application instance
* @param actionAfter the action to perform after the animation
*/
public ShellAnimation(TankTopControl tankTopControl, MdgaApp app, Runnable actionAfter) {
super(actionAfter);
this.tankTopControl = tankTopControl;
this.app = app;
}
/**
* Initializes the spatial for the animation.
*/
@Override
protected void initSpatial() {
tankTopControl.rotate(spatial.getLocalTranslation(), this::shoot);
app.getAcousticHandler().playSound(MdgaSound.TURRET_ROTATE);
//app.getRootNode().attachChild(createShell());
}
/**
* Calculates the shooting position based on the tank's turret rotation.
*
* @return the shooting position as a Vector3f
*/
private Vector3f getShootPos() {
Vector3f localOffset = new Vector3f(0, -5.4f, 2.9f);
Quaternion turretRotation = tankTopControl.getSpatial().getLocalRotation();
Vector3f transformedOffset = turretRotation.mult(localOffset);
return tankTopControl.getSpatial().getLocalTranslation().add(transformedOffset);
}
/**
* Handles the shooting action, including sound and visual effects.
*/
private void shoot() {
app.getAcousticHandler().playSound(MdgaSound.TANK_SHOOT);
Vector3f shootPos = getShootPos();
createEffect(
shootPos,
"Images/particle/flame.png",
2, 2,
1, 3,
1f,
0.3f, 0.7f,
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
new ColorRGBA(1f, 0f, 0f, 0f)
);
createEffect(
shootPos,
"Images/particle/vapor_cloud.png",
3, 3,
0.3f, 0.8f,
10,
0.1f, 0.35f,
new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
ColorRGBA.Black
);
Spatial shell = createShell();
app.getRootNode().attachChild(shell);
shell.addControl(new ShellControl(this::hitExplosion, shootPos, spatial.getLocalTranslation(), FLYING_HEIGHT, FLYING_DURATION, app.getAssetManager()));
}
/**
* Creates the shell model and sets its initial properties.
*
* @return the created shell as a Spatial
*/
private Spatial createShell() {
Spatial model = app.getAssetManager().loadModel(Asset.shell.getModelPath());
model.scale(.16f);
model.setLocalTranslation(tankTopControl.getSpatial().getLocalTranslation());
Vector3f shootPos = tankTopControl.getSpatial().getLocalTranslation();
Vector3f targetPos = spatial.getLocalTranslation();
Vector3f direction = targetPos.subtract(shootPos).normalize();
Quaternion rotation = new Quaternion();
rotation.lookAt(direction, new Vector3f(1, 0, 0)); // Assuming UNIT_Y is the up vector
model.setLocalRotation(rotation);
model.rotate(FastMath.HALF_PI, 0, 0);
Material mat = new Material(app.getAssetManager(), LIGHTING);
mat.setBoolean("UseMaterialColors", true);
ColorRGBA color = ColorRGBA.fromRGBA255(143, 117, 0, 255);
mat.setColor("Diffuse", color);
mat.setColor("Ambient", color);
model.setMaterial(mat);
return model;
}
/**
* Handles the explosion effect when the shell hits a target.
*/
private void hitExplosion() {
app.getAcousticHandler().playSound(MdgaSound.TANK_EXPLOSION);
createEffect(
spatial.getLocalTranslation().setZ(1),
"Images/particle/flame.png",
2, 2,
1, 5,
2f,
0.3f, 0.7f,
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
new ColorRGBA(1f, 0f, 0f, 0f)
);
app.getTimerManager().addTask(0.8f, super::action);
}
/**
* Creates a particle effect at the specified position.
*
* @param shootPos the position to create the effect
* @param image the image to use for the particles
* @param x the number of columns in the texture
* @param y the number of rows in the texture
* @param startSize the initial size of the particles
* @param endSize the final size of the particles
* @param velocity the initial velocity of the particles
* @param lowLife the minimum lifetime of the particles
* @param highLife the maximum lifetime of the particles
* @param start the starting color of the particles
* @param end the ending color of the particles
*/
private void createEffect(Vector3f shootPos,
String image,
int x, int y,
float startSize, float endSize,
float velocity,
float lowLife, float highLife,
ColorRGBA start, ColorRGBA end) {
// Create a particle emitter for the explosion
ParticleEmitter explosionEmitter = new ParticleEmitter("Explosion", ParticleMesh.Type.Triangle, 100);
Material explosionMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
explosionMat.setTexture("Texture", app.getAssetManager().loadTexture(image));
explosionEmitter.setMaterial(explosionMat);
// Particle properties
explosionEmitter.setImagesX(x); // Columns in the texture
explosionEmitter.setImagesY(y); // Rows in the texture
explosionEmitter.setSelectRandomImage(true); // Randomize images for variety
explosionEmitter.setStartColor(start); // Bright yellowish orange
explosionEmitter.setEndColor(end); // Fade to transparent red
explosionEmitter.setStartSize(startSize); // Initial size
explosionEmitter.setEndSize(endSize); // Final size
explosionEmitter.setLowLife(lowLife); // Minimum lifetime of particles
explosionEmitter.setHighLife(highLife); // Maximum lifetime of particles
explosionEmitter.setGravity(0, 0, 1); // Gravity to pull particles down
explosionEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, velocity));
explosionEmitter.getParticleInfluencer().setVelocityVariation(1f); // Adds randomness to the initial velocity
explosionEmitter.setFacingVelocity(true); // Particles face their velocity direction
explosionEmitter.setLocalTranslation(shootPos);
explosionEmitter.setParticlesPerSec(0);
explosionEmitter.emitAllParticles();
app.getRootNode().attachChild(explosionEmitter);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
app.getRootNode().detachChild(explosionEmitter);
}
}, 1000);
}
}

View File

@@ -0,0 +1,112 @@
package pp.mdga.client.animation;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
/**
* ShellControl is responsible for controlling the movement and visual effects of a shell.
*/
public class ShellControl extends ActionControl {
private final Vector3f shootPos;
private final Vector3f endPos;
private final float height;
private final float duration;
private Vector3f oldPos;
private ParticleEmitter emitter;
private AssetManager assetManager;
/**
* Constructs a new ShellControl.
*
* @param runnable the action to perform when the shell reaches its destination
* @param shootPos the starting position of the shell
* @param endPos the ending position of the shell
* @param height the height of the shell's trajectory
* @param duration the duration of the shell's flight
* @param assetManager the asset manager to load resources
*/
public ShellControl(Runnable runnable, Vector3f shootPos, Vector3f endPos, float height, float duration, AssetManager assetManager) {
super(runnable);
this.shootPos = shootPos;
this.endPos = endPos;
this.height = height;
this.duration = duration;
this.assetManager = assetManager;
}
/**
* Initializes the spatial with the necessary controls and particle emitter.
*/
@Override
protected void initSpatial() {
spatial.addControl(new MoveControl(
shootPos,
endPos,
() -> {
emitter.killAllParticles();
emitter.setParticlesPerSec(0);
emitter.removeFromParent();
spatial.removeControl(this);
spatial.removeFromParent();
action();
},
height,
duration,
false
));
oldPos = spatial.getLocalTranslation().clone();
createEmitter();
}
/**
* Creates and configures the particle emitter for the shell trail.
*/
private void createEmitter() {
emitter = new ParticleEmitter("ShellTrail", ParticleMesh.Type.Triangle, 200);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", assetManager.loadTexture("Images/particle/line.png")); // Nutze eine schmale, linienartige Textur
emitter.setMaterial(mat);
// Comic-Style Farben
emitter.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); // Reinweiß
emitter.setEndColor(new ColorRGBA(1f, 1f, 1f, 0f)); // Transparent
// Partikelgröße und Lebensdauer
emitter.setStartSize(0.15f); // Startgröße
emitter.setEndSize(0.1f); // Endgröße
emitter.setLowLife(0.14f); // Sehr kurze Lebensdauer
emitter.setHighLife(0.14f);
emitter.setGravity(0, 0, 0); // Keine Gravitation
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
emitter.getParticleInfluencer().setVelocityVariation(0f); // Kein Variationsspielraum
// Hohe Dichte für eine glatte Spur
emitter.setParticlesPerSec(500);
// Zur Shell hinzufügen
spatial.getParent().attachChild(emitter);
}
/**
* Updates the control, adjusting the shell's rotation and emitter position.
*
* @param tpf time per frame
*/
@Override
protected void controlUpdate(float tpf) {
Vector3f direction = spatial.getLocalTranslation().subtract(oldPos).normalize();
if (direction.lengthSquared() > 0) {
spatial.getLocalRotation().lookAt(direction, Vector3f.UNIT_X);
spatial.rotate(FastMath.HALF_PI, 0, 0);
}
oldPos = spatial.getLocalTranslation().clone();
emitter.setLocalTranslation(spatial.getLocalTranslation().clone());
}
}

View File

@@ -0,0 +1,129 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
public class Smoke {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f location;
private ParticleEmitter fire;
private ParticleEmitter smoke;
private boolean triggered = false;
private final Material mat;
/**
* Constructor for the {@code Explosion} class.
*
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Smoke(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
this.rootNode = rootNode;
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
}
/**
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle, 50);
fire.setMaterial(mat);
fire.setStartColor(ColorRGBA.DarkGray);
fire.setEndColor(ColorRGBA.DarkGray);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.7f);
fire.setEndSize(3.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(1.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 2f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.5f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(2.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {
triggered = true;
initializeEmitter();
}
rootNode.attachChild(fire);
fire.emitAllParticles();
fire.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(fire);
fire.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
rootNode.attachChild(smoke);
smoke.emitAllParticles();
smoke.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(smoke);
smoke.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
}
}

View File

@@ -172,8 +172,8 @@ public void turbo() {
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.animation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TimerManager {
private final List<TimedTask> tasks = new ArrayList<>();
/**
* Add a timed task that will execute after the specified delay.
*
* @param delaySeconds The delay in seconds.
* @param task The Runnable task to execute after the delay.
*/
public void addTask(float delaySeconds, Runnable task) {
tasks.add(new TimedTask(delaySeconds, task));
}
/**
* Update the timer manager to process and execute tasks when their delay has elapsed.
* This should be called in the `controlUpdate` method or a similar update loop.
*
* @param tpf Time per frame (delta time) provided by the update loop.
*/
public void update(float tpf) {
Iterator<TimedTask> iterator = tasks.iterator();
while (iterator.hasNext()) {
TimedTask task = iterator.next();
task.update(tpf);
if (task.isReady()) {
task.run();
iterator.remove();
}
}
}
/**
* Clears all pending tasks from the manager.
*/
public void clearTasks() {
tasks.clear();
}
/**
* Checks if the manager has any pending tasks.
*
* @return True if there are pending tasks, otherwise false.
*/
public boolean hasPendingTasks() {
return !tasks.isEmpty();
}
/**
* Internal class representing a single timed task.
*/
private static class TimedTask {
private float remainingTime;
private final Runnable task;
public TimedTask(float delaySeconds, Runnable task) {
this.remainingTime = delaySeconds;
this.task = task;
}
public void update(float tpf) {
remainingTime -= tpf;
}
public boolean isReady() {
return remainingTime <= 0;
}
public void run() {
task.run();
}
}
}

View File

@@ -17,8 +17,8 @@ public class ZoomControl extends InitControl {
private float zoomFactor = 1f;
/**
* Constructs a new ZoomControl with the default zoom speed.
*/
* Constructs a new ZoomControl with the default zoom speed.
*/
public ZoomControl() {
}
@@ -78,8 +78,8 @@ private void end() {
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {

View File

@@ -5,4 +5,5 @@
/**
* Record for holding Asset information
*/
record AssetOnMap(Asset asset, int x, int y, float rot) {}
record AssetOnMap(Asset asset, int x, int y, float rot) {
}

View File

@@ -1,6 +1,8 @@
package pp.mdga.client.board;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
@@ -10,11 +12,10 @@
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.animation.MissileAnimation;
import pp.mdga.client.animation.MoveControl;
import pp.mdga.client.animation.JetAnimation;
import pp.mdga.client.animation.*;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.game.Color;
import pp.mdga.game.Piece;
import java.util.*;
@@ -52,21 +53,26 @@ public class BoardHandler {
// Flags and lists for handling piece selection and movement
private List<PieceControl> selectableOwnPieces;
private List<PieceControl> selectableEnemyPieces;
private Map<PieceControl, NodeControl> selectedPieceNodeMap;
private List<NodeControl> outlineNodes;
private PieceControl selectedOwnPiece;
private PieceControl selectedEnemyPiece;
private DiceControl diceControl;
//Radar Position for Matrix animation
private Vector3f radarPos;
//TankTop for shellAnimation
private TankTopControl tankTop;
/**
* Creates a new BoardHandler.
*
* @param app The main application instance
* @param app The main application instance
* @param rootNode The root node where the board will be attached
* @param fpp The post-processor for effects like shadows or filters
* @param fpp The post-processor for effects like shadows or filters
* @throws RuntimeException if the app is null
*/
public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
if(app == null) throw new RuntimeException("app is null");
if (app == null) throw new RuntimeException("app is null");
this.app = app;
this.fpp = fpp;
@@ -82,6 +88,7 @@ public void init() {
isInitialised = true;
selectableOwnPieces = new ArrayList<>();
selectableEnemyPieces = new ArrayList<>();
selectedPieceNodeMap = new HashMap<>();
outlineNodes = new ArrayList<>();
selectedOwnPiece = null;
selectedEnemyPiece = null;
@@ -92,7 +99,7 @@ public void init() {
/**
* Shuts down the board handler by detaching all board-related nodes and clearing selected pieces.
*/
public void shutdown(){
public void shutdown() {
clearSelectable();
isInitialised = false;
rootNode.detachChild(rootNodeBoard);
@@ -101,7 +108,7 @@ public void shutdown(){
/**
* Adds an asset to the map of player assets, ensuring that the player does not have too many assets.
*
* @param col The color of the player
* @param col The color of the player
* @param assetOnMap The asset to be added
* @throws RuntimeException if there are too many assets for the player
*/
@@ -122,7 +129,7 @@ private void initMap() {
waitingPiecesMap = new HashMap<>();
pieceColor = new HashMap<>();
diceControl = new DiceControl(app.getAssetManager());
diceControl.create(new Vector3f(0,0,0), 0.7f, true);
diceControl.create(new Vector3f(0, 0, 0), 0.7f, true);
waitingNodes = new HashMap<>();
waitingNodes.put(Color.AIRFORCE, new HashMap<>());
waitingNodes.put(Color.ARMY, new HashMap<>());
@@ -139,7 +146,7 @@ private void initMap() {
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)));
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);
@@ -148,12 +155,24 @@ private void initMap() {
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);
case radar -> addRadar(assetOnMap);
case tankShoot -> addTankShoot(assetOnMap);
default -> displayAsset(assetOnMap);
}
}
}
private void addTankShoot(AssetOnMap assetOnMap) {
displayAsset(assetOnMap);
tankTop = displayAndControl(new AssetOnMap(Asset.tankShootTop, assetOnMap.x(), assetOnMap.y(), assetOnMap.rot()), new TankTopControl());
}
private void addRadar(AssetOnMap assetOnMap) {
radarPos = gridToWorld(assetOnMap.x(), assetOnMap.y());
displayAsset(assetOnMap);
}
/**
* Converts an asset to its corresponding color.
*
@@ -175,8 +194,8 @@ private Color assetToColor(Asset asset) {
* Creates a 3D model of an asset and adds it to the board.
*
* @param asset The asset to be displayed
* @param pos The position of the asset on the board
* @param rot The rotation of the asset
* @param pos The position of the asset on the board
* @param rot The rotation of the asset
* @return The Spatial representation of the asset
*/
private Spatial createModel(Asset asset, Vector3f pos, float rot) {
@@ -187,11 +206,16 @@ private Spatial createModel(Asset asset, Vector3f pos, float rot) {
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));
mat.setBoolean("UseMaterialColors", true); // Required for Material Colors
mat.setColor("Diffuse", new ColorRGBA(1, 1, 1, 1)); // White color with full alpha
mat.setColor("Ambient", new ColorRGBA(1, 1, 1, 1)); // Ambient color with full alpha
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
model.setMaterial(mat);
rootNodeBoard.attachChild(model);
rootNodeBoard.attachChild(model);
return model;
}
@@ -218,17 +242,39 @@ private Spatial displayAsset(AssetOnMap assetOnMap) {
return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
}
/**
* Adds a visual representation of an asset to the scene, attaches a control to it, and returns the control.
*
* @param assetOnMap The asset to be displayed in the 3D environment.
* @param control The control to be added to the spatial representing the asset.
* @param <T> The type of control, extending {@code AbstractControl}.
* @return The control that was added to the spatial.
*/
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){
/**
* Moves a piece in the 3D environment to the location of a specified node.
*
* @param pieceControl The control managing the piece to be moved.
* @param nodeControl The control managing the target node to which the piece will move.
*/
private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl) {
pieceControl.setLocation(nodeControl.getLocation());
}
private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap){
/**
* Adds a home node for a specific player color, attaching it to the map of home nodes.
*
* @param map The map storing lists of home nodes by player color.
* @param color The color associated with the home nodes to be added.
* @param assetOnMap The asset representing the home node in the 3D environment.
* @throws RuntimeException if more than 4 home nodes are added for a single color.
*/
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);
}
@@ -244,47 +290,74 @@ 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;
if (newRot < 0) newRot += 360;
return newRot;
}
/**
* Recursively moves a piece from its current index to the destination index,
* to keep track of the piece rotation.
*
* @param uuid The UUID of the piece to move.
* @param curIndex The current index of the piece.
* @param moveIndex The target index to move the piece to.
* Recursively moves a piece from its current index to the destination index,
* to keep track of the piece rotation.
*
* @param uuid The UUID of the piece to move.
* @param curIndex The current index of the piece.
* @param moveIndex The target index to move the piece to.
*/
private void movePieceRek(UUID uuid, int curIndex, int moveIndex){
private void movePieceRek(UUID uuid, int curIndex, int moveIndex) {
if (curIndex == moveIndex) return;
curIndex = (curIndex + 1) % infield.size();
int nextIndex = (curIndex + 1) % infield.size();
PieceControl pieceControl = pieces.get(uuid);
NodeControl nodeControl = infield.get(curIndex);
NodeControl nodeCur = infield.get(curIndex);
NodeControl nodeMove = infield.get(nextIndex);
pieceControl.setRotation(getRotationMove(pieceControl.getLocation(),nodeControl.getLocation()));
pieceControl.setRotation(getRotationMove(nodeCur.getLocation(), nodeMove.getLocation()));
movePieceToNode(pieceControl, nodeControl);
movePieceToNode(pieceControl, nodeMove);
movePieceRek(uuid, curIndex, moveIndex);
movePieceRek(uuid, nextIndex, moveIndex);
}
private <T, E> List<T> addItemToMapList(Map<E,List<T>> map, E key, T item){
/**
* Adds an item to a list in a map. If the key does not exist in the map, a new list is created.
*
* @param map The map containing lists of items.
* @param key The key associated with the list in the map.
* @param item The item to be added to the list.
* @param <T> The type of items in the list.
* @param <E> The type of the key in the map.
* @return The updated list associated with the specified key.
*/
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){
/**
* Removes an item from a list in a map. If the key does not exist in the map, a new list is created.
*
* @param map The map containing lists of items.
* @param key The key associated with the list in the map.
* @param item The item to be removed from the list.
* @param <T> The type of items in the list.
* @param <E> The type of the key in the map.
*/
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){
/**
* Calculates the mean position of the waiting nodes for a specific color.
*
* @param color The color associated with the waiting nodes.
* @return The mean position of the waiting nodes as a {@code Vector3f}.
*/
private Vector3f getWaitingPos(Color color) {
return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
}
@@ -308,20 +381,22 @@ public static Vector3f getMeanPosition(List<Vector3f> vectors) {
* Adds a player to the game by associating a color and a list of UUIDs to corresponding assets and waiting nodes.
*
* @param color the color of the player
* @param uuid the list of UUIDs representing the player's assets
* @param uuid the list of UUIDs representing the player's assets
* @throws RuntimeException if the number of assets or waiting nodes does not match the provided UUIDs
*/
public void addPlayer(Color color, List<UUID> uuid) {
List<AssetOnMap> playerAssets = colorAssetsMap.get(color);
if (playerAssets == null) throw new RuntimeException("Assets for Player color are not defined");
if (uuid.size() != playerAssets.size()) throw new RuntimeException("UUID array and playerAssets are not the same size");
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");
if (waitNodes.size() != playerAssets.size())
throw new RuntimeException("waitNodes size does not match playerAssets size");
for (int i = 0; i < playerAssets.size(); i++){
for (int i = 0; i < playerAssets.size(); i++) {
AssetOnMap assetOnMap = playerAssets.get(i);
UUID pieceUuid = uuid.get(i);
@@ -346,18 +421,18 @@ public void addPlayer(Color color, List<UUID> uuid) {
/**
* Moves a piece to its corresponding home node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param uuid the UUID of the piece to move
* @param index the index of the home node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or if the home nodes are not properly defined
*/
private void moveHomePiece(UUID uuid, int index){
private void moveHomePiece(UUID uuid, int index) {
Color color = pieceColor.get(uuid);
if(color == null) throw new RuntimeException("uuid is not mapped to a color");
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");
if (homeNodesMap.size() != 4) throw new RuntimeException("HomeNodes for" + color + " are not properly defined");
PieceControl pieceControl = pieces.get(uuid);
NodeControl nodeControl = homeNodes.get(index);
@@ -365,21 +440,22 @@ private void moveHomePiece(UUID uuid, int index){
//rotate piece in direction of homeNodes
NodeControl firstHomeNode = homeNodes.get(0);
NodeControl lastHomeNode = homeNodes.get(homeNodes.size()-1);
NodeControl lastHomeNode = homeNodes.get(homeNodes.size() - 1);
pieceControl.setRotation(getRotationMove(firstHomeNode.getLocation(), lastHomeNode.getLocation()));
app.getModelSynchronize().animationEnd();
app.getModelSynchronize().animationEnd();
}
/**
* Starts the movement of a piece to a target node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param uuid the UUID of the piece to move
* @param nodeIndex the index of the target node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
* @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
* @throws IllegalArgumentException if the node index is invalid
*/
private void movePieceStart(UUID uuid, int nodeIndex){
private void movePieceStart(UUID uuid, int nodeIndex) {
// Farbe des Pieces abrufen
Color color = pieceColor.get(uuid);
@@ -400,19 +476,21 @@ private void movePieceStart(UUID uuid, int nodeIndex){
removeItemFromMapList(waitingPiecesMap, color, pieceControl);
waitingNodes.get(color).remove(uuid);
app.getModelSynchronize().animationEnd();
app.getModelSynchronize().animationEnd();
}
/**
* Moves a piece from its current position to the target position based on the given indexes.
*
* @param uuid the UUID of the piece to move
* @param curIndex the current index of the piece
* @param uuid the UUID of the piece to move
* @param curIndex the current index of the piece
* @param moveIndex the target index of the move
*/
private void movePiece(UUID uuid, int curIndex, int moveIndex){
private void movePiece(UUID uuid, int curIndex, int moveIndex) {
movePieceRek(uuid, curIndex, moveIndex);
app.getModelSynchronize().animationEnd();
app.getModelSynchronize().animationEnd();
}
/**
@@ -421,7 +499,7 @@ private void movePiece(UUID uuid, int curIndex, int moveIndex){
* @param uuid the UUID of the piece to throw
* @throws RuntimeException if the UUID is not mapped to a color or if no available waiting nodes are found
*/
private void throwPiece(UUID uuid){
private void throwPiece(UUID uuid) {
// Farbe des Pieces abrufen
Color color = pieceColor.get(uuid);
@@ -455,8 +533,7 @@ private void throwPiece(UUID uuid){
*
* @param uuid the UUID of the piece to shield
*/
public void shieldPiece(UUID uuid){
public void shieldPiece(UUID uuid) {
pieces.get(uuid).activateShield();
}
@@ -465,8 +542,7 @@ public void shieldPiece(UUID uuid){
*
* @param uuid the UUID of the piece to unshield
*/
public void unshieldPiece(UUID uuid){
public void unshieldPiece(UUID uuid) {
pieces.get(uuid).deactivateShield();
}
@@ -475,7 +551,7 @@ public void unshieldPiece(UUID uuid){
*
* @param uuid the UUID of the piece to suppress the shield
*/
public void suppressShield(UUID uuid){
public void suppressShield(UUID uuid) {
pieces.get(uuid).suppressShield();
}
@@ -483,14 +559,14 @@ public void suppressShield(UUID uuid){
/**
* Swaps the positions and rotations of two pieces.
*
* @param p1 the first piece to swap
* @param p2 the second piece to swap
* @param p1 the first piece to swap
* @param p2 the second piece to swap
* @param loc1 the original location of the first piece
* @param rot1 the original rotation of the first piece
* @param loc2 the original location of the second piece
* @param rot2 the original rotation of the second piece
*/
private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2){
private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2) {
p1.setLocation(loc2);
p2.setLocation(loc1);
@@ -503,13 +579,14 @@ private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float r
/**
* Outlines the possible move nodes for a list of pieces based on the move indices and whether it's a home move.
*
* @param pieces the list of UUIDs representing the pieces to outline
* @param pieces the list of UUIDs representing the pieces to outline
* @param moveIndexe the list of indices for the target move nodes
* @param homeMoves the list indicating whether the move is a home move
* @param homeMoves the list indicating whether the move is a home move
* @throws RuntimeException if the sizes of the input lists do not match
*/
public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) {
if(pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size()) throw new RuntimeException("arrays are not the same size");
if (pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size())
throw new RuntimeException("arrays are not the same size");
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
@@ -524,44 +601,38 @@ public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolea
if (homeMoves.get(i)) {
Color color = pieceColor.get(uuid);
nodeControl = homeNodesMap.get(color).get(moveIndexe.get(i));
}
else {
} else {
nodeControl = infield.get(moveIndexe.get(i));
}
nodeControl.highlight();
pieceControl.highlight(false);
pieceControl.setHoverable(true);
pieceControl.setSelectable(true);
pieceControl.selectableOwn();
nodeControl.selectableOwn();
outlineNodes.add(nodeControl);
selectableOwnPieces.add(pieceControl);
selectedPieceNodeMap.put(pieceControl, nodeControl);
}
}
/**
* Outlines the pieces that can be swapped based on the provided own and enemy pieces.
*
* @param ownPieces the list of UUIDs representing the player's pieces
* @param ownPieces the list of UUIDs representing the player's pieces
* @param enemyPieces the list of UUIDs representing the enemy's pieces
*/
public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces) {
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
selectedOwnPiece = null;
selectedEnemyPiece = null;
for(UUID uuid : ownPieces) {
for (UUID uuid : ownPieces) {
PieceControl p = pieces.get(uuid);
p.highlight(false);
p.setHoverable(true);
p.setSelectable(true);
p.selectableOwn();
selectableOwnPieces.add(p);
}
for(UUID uuid : enemyPieces) {
for (UUID uuid : enemyPieces) {
PieceControl p = pieces.get(uuid);
p.highlight(true);
p.setHoverable(true);
p.setSelectable(true);
p.selectableEnemy();
selectableEnemyPieces.add(p);
}
}
@@ -571,17 +642,15 @@ public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
*
* @param pieces the list of UUIDs representing the pieces to be shielded
*/
public void outlineShield(List<UUID> pieces){
public void outlineShield(List<UUID> pieces) {
selectableOwnPieces.clear();
selectableEnemyPieces.clear();
selectedOwnPiece = null;
selectedEnemyPiece = null;
for (UUID uuid : pieces){
for (UUID uuid : pieces) {
PieceControl p = this.pieces.get(uuid);
p.highlight(false);
p.setHoverable(true);
p.setSelectable(true);
p.selectableOwn();
selectableOwnPieces.add(p);
}
}
@@ -591,59 +660,87 @@ public void outlineShield(List<UUID> pieces){
*
* @param pieceSelected the PieceControl instance representing the piece selected by the user
*/
public void pieceSelect(PieceControl pieceSelected) {
boolean isSelected = pieceSelected.isSelected();
if(selectableOwnPieces.contains(pieceSelected)){
for(PieceControl p : selectableOwnPieces) {
p.unSelect();
public void pieceSelect(OutlineOEControl selected) {
PieceControl piece = getPieceByOE(selected);
NodeControl node = selectedPieceNodeMap.get(piece);
boolean isSelected = piece.isSelected();
if (selectableOwnPieces.contains(piece)) {
for (PieceControl p : selectableOwnPieces) {
p.selectOff();
NodeControl n = selectedPieceNodeMap.get(p);
if (n != null) n.selectOff();
}
if (!isSelected) {
pieceSelected.select();
selectedOwnPiece = pieceSelected;
}
else {
pieceSelected.unSelect();
piece.selectOn();
if (node != null) node.selectOn();
selectedOwnPiece = piece;
} else {
piece.selectOff();
if (node != null) node.selectOff();;
selectedOwnPiece = null;
}
}
else if(selectableEnemyPieces.contains(pieceSelected)) {
for(PieceControl p : selectableEnemyPieces) {
p.unSelect();
} else if (selectableEnemyPieces.contains(piece)) {
for (PieceControl p : selectableEnemyPieces) {
p.selectOff();
}
if (!isSelected) {
pieceSelected.select();
selectedEnemyPiece = pieceSelected;
}
else {
pieceSelected.unSelect();
piece.selectOn();
selectedEnemyPiece = piece;
} else {
piece.selectOff();
selectedEnemyPiece = null;
}
}
else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
} else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece));
}
public void hoverOn(OutlineOEControl hover) {
PieceControl piece = getPieceByOE(hover);
NodeControl node = selectedPieceNodeMap.get(piece);
piece.hoverOn();
if(node != null) node.hoverOn();
}
public void hoverOff(OutlineOEControl hover) {
PieceControl piece = getPieceByOE(hover);
NodeControl node = selectedPieceNodeMap.get(piece);
piece.hoverOff();
if(node != null) node.hoverOff();
}
private PieceControl getPieceByOE(OutlineOEControl control){
PieceControl piece;
if (control instanceof PieceControl p){
piece = p;
}
else if (control instanceof NodeControl n){
piece = getKeyByValue(selectedPieceNodeMap, n);
}
else throw new RuntimeException("selected is not instanceof piece or node");
return piece;
}
/**
* Clears all highlighted, selectable, and selected pieces and nodes.
*/
public void clearSelectable(){
for(PieceControl p : selectableEnemyPieces) {
p.unSelect();
p.unHighlight();
p.setSelectable(false);
public void clearSelectable() {
for (PieceControl p : selectableOwnPieces) {
p.selectableOff();
NodeControl n = selectedPieceNodeMap.get(p);
if(n != null) n.selectableOff();
}
for(PieceControl p : selectableOwnPieces) {
p.unSelect();
p.unHighlight();
p.setSelectable(false);
}
for(NodeControl n : outlineNodes){
n.deOutline();
for (PieceControl p : selectableEnemyPieces) {
p.selectableOff();
}
outlineNodes.clear();
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
selectedPieceNodeMap.clear();
selectedEnemyPiece = null;
selectedOwnPiece = null;
}
@@ -653,16 +750,16 @@ public void clearSelectable(){
*
* @param color the color of the player whose dice should be displayed
*/
public void showDice(Color color){
public void showDice(Color color) {
rootNodeBoard.attachChild(diceControl.getSpatial());
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4)));
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0, 0, 4)));
diceControl.spin();
}
/**
* Hides the dice from the view.
*/
public void hideDice(){
public void hideDice() {
diceControl.hide();
}
@@ -678,41 +775,42 @@ private <K, V> K getKeyByValue(Map<K, V> map, V value) {
/**
* Animates the movement of a piece from its current index to a target index.
*
* @param uuid the UUID of the piece to animate
* @param curIndex the current index of the piece
* @param uuid the UUID of the piece to animate
* @param curIndex the current index of the piece
* @param moveIndex the target index to animate the piece to
*/
public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
public void movePieceAnim(UUID uuid, int curIndex, int moveIndex) {
pieces.get(uuid).getSpatial().addControl(new MoveControl(
infield.get(curIndex).getLocation(),
infield.get(moveIndex).getLocation(),
()->movePiece(uuid,curIndex,moveIndex)));
infield.get(curIndex).getLocation(),
infield.get(moveIndex).getLocation(),
() -> movePiece(uuid, curIndex, moveIndex)));
}
/**
* Animates the movement of a piece to its home position based on the given home index.
*
* @param uuid the UUID of the piece to animate
* @param uuid the UUID of the piece to animate
* @param homeIndex the index of the home node to move the piece to
*/
public void movePieceHomeAnim(UUID uuid, int homeIndex){
public void movePieceHomeAnim(UUID uuid, int homeIndex) {
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(),
homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
()->moveHomePiece(uuid,homeIndex)));
pieces.get(uuid).getLocation(),
homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
() -> moveHomePiece(uuid, homeIndex)));
}
/**
* Animates the start of the movement of a piece to a target index.
*
* @param uuid the UUID of the piece to animate
* @param uuid the UUID of the piece to animate
* @param moveIndex the target index to animate the piece to
*/
public void movePieceStartAnim(UUID uuid, int moveIndex){
public void movePieceStartAnim(UUID uuid, int moveIndex) {
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(),
infield.get(moveIndex).getLocation(),
()->movePieceStart(uuid, moveIndex)
pieces.get(uuid).getLocation(),
infield.get(moveIndex).getLocation(),
() -> movePieceStart(uuid, moveIndex)
));
}
@@ -721,43 +819,61 @@ public void movePieceStartAnim(UUID uuid, int moveIndex){
*
* @param uuid the UUID of the piece to animate
*/
public void throwPieceAnim(UUID uuid){
public void throwPieceAnim(UUID uuid) {
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(), ()->throwPiece(uuid))
pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(),
() -> throwPiece(uuid))
);
}
/**
* Animates the throwing of a piece to the next available waiting node and plays jet animation.
*
* @param uuid the UUID of the piece to animate
*/
public void throwBombAnim(UUID uuid){
Vector3f targetPoint = pieces.get(uuid).getLocation();
JetAnimation anim = new JetAnimation(app, rootNode, uuid, targetPoint, 40, 6);
anim.start();
public void throwPiece(UUID uuid, Color throwColor) {
switch (throwColor) {
case ARMY -> throwShell(uuid);
case NAVY -> throwMissile(uuid);
case CYBER -> throwMatrix(uuid);
case AIRFORCE -> throwBomb(uuid);
default -> throw new RuntimeException("invalid color");
}
}
/**
* Animates the throwing of a piece to the next available waiting node and plays ship animation.
* Animates the throwing of a piece to the next available waiting node.
*
* @param uuid the UUID of the piece to animate
*/
public void throwMissileAnim(UUID uuid){
private void throwBomb(UUID uuid) {
Vector3f targetPoint = pieces.get(uuid).getLocation();
MissileAnimation anim = new MissileAnimation(app, rootNode, uuid, targetPoint, 2);
JetAnimation anim = new JetAnimation(app, rootNode, targetPoint, 40, 6, () -> throwPieceAnim(uuid));
anim.start();
}
private void throwMatrix(UUID uuid) {
app.getAcousticHandler().playSound(MdgaSound.MATRIX);
Spatial piece = pieces.get(uuid).getSpatial();
piece.addControl(new MatrixAnimation(app, radarPos, () -> {
throwPieceAnim(uuid);
}));
}
private void throwMissile(UUID uuid) {
Vector3f targetPoint = pieces.get(uuid).getLocation();
MissileAnimation anim = new MissileAnimation(app, rootNode, targetPoint, 2f, () -> throwPieceAnim(uuid));
anim.start();
}
private void throwShell(UUID uuid) {
pieces.get(uuid).getSpatial().addControl(new ShellAnimation(tankTop, app, () -> throwPieceAnim(uuid)));
}
/**
* Animates the swapping of two pieces by swapping their positions and rotations.
*
* @param piece1 the UUID of the first piece
* @param piece2 the UUID of the second piece
*/
public void swapPieceAnim(UUID piece1, UUID piece2){
public void swapPieceAnim(UUID piece1, UUID piece2) {
PieceControl piece1Control = pieces.get(piece1);
PieceControl piece2Control = pieces.get(piece2);
@@ -767,14 +883,15 @@ public void swapPieceAnim(UUID piece1, UUID piece2){
float rot2 = piece2Control.getRotation();
piece1Control.getSpatial().addControl(new MoveControl(
piece1Control.getLocation().clone(),
piece2Control.getLocation().clone(),
()->{}
piece1Control.getLocation().clone(),
piece2Control.getLocation().clone(),
() -> {
}
));
piece2Control.getSpatial().addControl(new MoveControl(
piece2Control.getLocation().clone(),
piece1Control.getLocation().clone(),
()->swapPieces(piece1Control,piece2Control,loc1,rot1,loc2,rot2)
piece2Control.getLocation().clone(),
piece1Control.getLocation().clone(),
() -> swapPieces(piece1Control, piece2Control, loc1, rot1, loc2, rot2)
));
}

View File

@@ -73,7 +73,7 @@ public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
// ssaoFilter = new SSAOFilter();
fxaaFilter = new FXAAFilter();
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI*1,0,FastMath.HALF_PI*0.2f);
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI * 1, 0, FastMath.HALF_PI * 0.2f);
}
@@ -94,7 +94,7 @@ public void init(Color ownColor) {
init = true;
initRot = true;
this.ownColor = ownColor;
app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor)*2);
app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor) * 2);
}
/**
@@ -102,26 +102,29 @@ public void init(Color ownColor) {
* and resets the camera position and rotation to its default state.
*/
public void shutdown() {
app.getRootNode().removeLight(sun);
app.getRootNode().removeLight(ambient);
init = false;
fpp.removeFilter(fxaaFilter);
fpp.removeFilter(ssaoFilter);
fpp.removeFilter(dlsf);
app.getRootNode().detachChild(sky);
app.getRootNode().removeLight(ambient);
app.getRootNode().removeLight(sun);
// Reset the camera to its default state
app.getCamera().setLocation(defaultCameraPosition);
app.getCamera().setRotation(defaultCameraRotation);
fpp.removeFilter(dlsf);
}
/**
* Updates the camera position and rotation based on user input (scroll and rotation).
* Adjusts the vertical angle and radius based on zoom and rotation values.
*
* @param scroll The scroll input, determining zoom level.
* @param scroll The scroll input, determining zoom level.
* @param rotation The rotation input, determining camera orientation.
*/
public void update(float scroll, float rotation) {
if(!init) return;
if (!init) return;
float scrollValue = Math.max(0, Math.min(scroll, 100));
float rotationValue = rotation % 360;
@@ -159,8 +162,8 @@ public void update(float scroll, float rotation) {
* @param color The color used to determine the camera angle.
* @return The camera angle in degrees.
*/
private float getAngleByColor(Color color){
return switch (color){
private float getAngleByColor(Color color) {
return switch (color) {
case ARMY -> 0;
case AIRFORCE -> 90;
case NAVY -> 270;
@@ -175,7 +178,7 @@ private float getAngleByColor(Color color){
* @param color The color used to determine the camera angle.
* @return The initial camera angle in degrees.
*/
private float getInitAngleByColor(Color color){
private float getInitAngleByColor(Color color) {
return (getAngleByColor(color) + 180) % 360;
}

View File

@@ -28,15 +28,15 @@ private MapLoader() {
*
* @param mapName The name of the map file to load. The file is expected to be located in the resources directory.
* @return A list of {@link AssetOnMap} objects representing the assets placed on the map.
* @throws IOException If an error occurs while reading the map file.
* @throws IOException If an error occurs while reading the map file.
* @throws IllegalArgumentException If the map file contains invalid data.
*/
public static List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
) {
while (true) {
@@ -65,11 +65,9 @@ public static List<AssetOnMap> loadMap(String mapName) {
Asset asset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
}
}
catch (IOException e) {
} catch (IOException e) {
e.printStackTrace();
}
catch (IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
@@ -109,6 +107,9 @@ private static Asset getLoadedAsset(String assetName) {
case "tank" -> Asset.tank;
case "treeSmall" -> Asset.treeSmall;
case "treeBig" -> Asset.treeBig;
case "tank_shoot" -> Asset.tankShoot;
case "treesBigBackground" -> Asset.treesBigBackground;
case "treesSmallBackground" -> Asset.treesSmallBackground;
default -> throw new IllegalStateException("Unexpected value: " + assetName);
};
}

View File

@@ -3,19 +3,13 @@
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
/**
* A control that adds highlighting functionality to a node in the game.
* This class extends {@link OutlineControl} to add an outline effect when the node is highlighted.
*/
public class NodeControl extends OutlineControl {
private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
public class NodeControl extends OutlineOEControl {
/**
* Constructs a {@link NodeControl} with the specified application and post processor.
@@ -25,7 +19,7 @@ public class NodeControl extends OutlineControl {
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
super(app, fpp);
super(app, fpp, app.getCamera());
}
/**
@@ -34,15 +28,7 @@ public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
*
* @return The {@link Vector3f} representing the node's location.
*/
public Vector3f getLocation(){
public Vector3f getLocation() {
return this.getSpatial().getLocalTranslation();
}
/**
* Highlights the node by applying an outline effect.
* The outline color and width are predefined as white and 6, respectively.
*/
public void highlight() {
super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
}

View File

@@ -3,8 +3,8 @@
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.InitControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.outline.SelectObjectOutliner;
/**
@@ -13,35 +13,52 @@
* object, allowing it to be highlighted or deselected.
*/
public class OutlineControl extends InitControl {
/** The {@link SelectObjectOutliner} responsible for managing the outline effect. */
private final SelectObjectOutliner outlineOwn;
private static final int THICKNESS_DEFAULT = 6;
private MdgaApp app;
/**
* The {@link SelectObjectOutliner} responsible for managing the outline effect.
*/
private final SelectObjectOutliner selectObjectOutliner;
private final MdgaApp app;
private boolean hoverable = false;
private boolean highlight = false;
private boolean selectable = false;
private boolean select = false;
private ColorRGBA highlightColor;
private int highlightWidth;
private ColorRGBA hoverColor;
private int hoverWidth;
private ColorRGBA selectColor;
private int selectWidth;
public OutlineControl(MdgaApp app, FilterPostProcessor fpp){
/**
* Constructs an {@code OutlineControl} with default thickness for the object outline.
*
* @param app The main application managing the outline control.
* @param fpp The {@code FilterPostProcessor} used for post-processing effects.
*/
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam,
ColorRGBA highlightColor, int highlightWidth,
ColorRGBA hoverColor, int hoverWidth,
ColorRGBA selectColor, int selectWidth
) {
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera(), app);
this.highlightColor = highlightColor;
this.highlightWidth = highlightWidth;
this.hoverColor = hoverColor;
this.hoverWidth = hoverWidth;
this.selectColor = selectColor;
this.selectWidth = selectWidth;
selectObjectOutliner = new SelectObjectOutliner(fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thickness){
this.app = app;
outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
/**
* Applies an outline to the spatial object with the given color.
*
* @param color The {@link ColorRGBA} representing the color of the outline.
*/
public void outline(ColorRGBA color){
outlineOwn.select(spatial, color);
}
// public void outline(ColorRGBA color) {
// selectObjectOutliner.select(spatial, color);
// }
/**
* Applies an outline to the spatial object with the given color and width.
@@ -49,19 +66,117 @@ public void outline(ColorRGBA color){
* @param color The {@link ColorRGBA} representing the color of the outline.
* @param width The width of the outline.
*/
public void outline(ColorRGBA color, int width){
deOutline();
outlineOwn.select(spatial, color, width);
public void outline(ColorRGBA color, int width) {
outlineOff();
selectObjectOutliner.select(spatial, color, width);
}
/**
* Removes the outline effect from the spatial object.
*/
public void deOutline(){
outlineOwn.deselect(spatial);
public void outlineOff() {
selectObjectOutliner.deselect(spatial);
}
/**
* Retrieves the instance of the {@code MdgaApp} associated with this control.
*
* @return The {@code MdgaApp} instance.
*/
public MdgaApp getApp() {
return app;
}
public void highlightOn() {
highlight = true;
outline(highlightColor, highlightWidth);
}
public void highlightOff() {
highlight = false;
outlineOff();
}
public void hoverOn() {
if (!hoverable) return;
outline(hoverColor, hoverWidth);
}
public void hoverOff() {
if (!hoverable) return;
if (select) selectOn();
else if (highlight) highlightOn();
else outlineOff();
}
public void selectOn() {
if (!selectable) return;
select = true;
outline(selectColor, selectWidth);
}
public void selectOff() {
select = false;
if (highlight) highlightOn();
else outlineOff();
}
public void selectableOn(){
setSelectable(true);
setHoverable(true);
highlightOn();
select = false;
}
public void selectableOff(){
setSelectable(false);
setHoverable(false);
highlightOff();
select = false;
}
private void setSelectable(boolean selectable) {
this.selectable = selectable;
}
public boolean isSelected() {
return select;
}
public boolean isSelectable() {
return selectable;
}
public boolean isHoverable() {
return hoverable;
}
private void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
public void setHighlightColor(ColorRGBA color){
highlightColor = color;
}
public void setHighlightWidth(int width){
highlightWidth = width;
}
public void setHoverColor(ColorRGBA color){
hoverColor = color;
}
public void setHoverWidth(int width){
hoverWidth = width;
}
public void setSelectColor(ColorRGBA color){
selectColor = color;
}
public void setSelectWidth(int width){
selectWidth = width;
}
}

View File

@@ -0,0 +1,57 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import pp.mdga.client.MdgaApp;
/**
* OutlineOEControl class extends OutlineControl to manage outline colors and widths
* for own and enemy objects, including hover and select states.
*/
public class OutlineOEControl extends OutlineControl{
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;
/**
* Constructor for OutlineOEControl.
*
* @param app the MdgaApp instance
* @param fpp the FilterPostProcessor instance
* @param cam the Camera instance
*/
public OutlineOEControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
super(app, fpp, cam,
OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH,
OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH,
OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH
);
}
/**
* Sets the outline colors and enables selection for own objects.
*/
public void selectableOwn(){
setHighlightColor(OUTLINE_OWN_COLOR);
setHoverColor(OUTLINE_OWN_HOVER_COLOR);
setSelectColor(OUTLINE_OWN_SELECT_COLOR);
selectableOn();
}
/**
* Sets the outline colors and enables selection for enemy objects.
*/
public void selectableEnemy(){
setHighlightColor(OUTLINE_ENEMY_COLOR);
setHoverColor(OUTLINE_ENEMY_HOVER_COLOR);
setSelectColor(OUTLINE_ENEMY_SELECT_COLOR);
selectableOn();
}
}

View File

@@ -20,7 +20,7 @@
* to provide outline functionality and includes additional features like shield effects,
* hover states, and selection states.
*/
public class PieceControl extends OutlineControl {
public class PieceControl extends OutlineOEControl {
private final float initRotation;
private final AssetManager assetManager;
private Spatial shieldRing;
@@ -32,16 +32,6 @@ public class PieceControl extends OutlineControl {
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;
@@ -55,22 +45,17 @@ public class PieceControl extends OutlineControl {
*
* @param initRotation The initial rotation of the piece in degrees.
* @param assetManager The {@link AssetManager} used for loading models and materials.
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){
super(app, fpp);
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp) {
super(app, fpp, app.getCamera());
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;
}
/**
@@ -79,7 +64,7 @@ public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app,
* @return The rotation of the piece in degrees.
*/
public float getRotation() {
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1)));
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0, 0, 1)));
}
/**
@@ -87,11 +72,11 @@ public float getRotation() {
*
* @param rot The rotation in degrees to set.
*/
public void setRotation(float rot){
if(rot < 0) rot =- 360;
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));
quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0, 0, 1));
spatial.setLocalRotation(quaternion);
}
@@ -100,7 +85,7 @@ public void setRotation(float rot){
*
* @return The location of the piece as a {@link Vector3f}.
*/
public Vector3f getLocation(){
public Vector3f getLocation() {
return spatial.getLocalTranslation();
}
@@ -111,8 +96,9 @@ public Vector3f getLocation(){
*/
@Override
protected void controlUpdate(float delta) {
if(shieldRing != null){
if (shieldRing != null) {
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
}
}
@@ -121,7 +107,7 @@ protected void controlUpdate(float delta) {
*
* @param loc The location to set as a {@link Vector3f}.
*/
public void setLocation(Vector3f loc){
public void setLocation(Vector3f loc) {
this.spatial.setLocalTranslation(loc);
}
@@ -130,7 +116,7 @@ public void setLocation(Vector3f loc){
* This also moves the spatial to a new parent node for organizational purposes.
*/
@Override
public void initSpatial(){
public void initSpatial() {
setRotation(this.initRotation);
Node oldParent = spatial.getParent();
@@ -141,18 +127,21 @@ public void initSpatial(){
}
public void rotateInit() {
// rotate(rotation - initRotation);
setRotation(initRotation);
}
/**
* Activates the shield around the piece.
* This adds a visual shield effect in the form of a rotating ring.
*/
public void activateShield(){
public void activateShield() {
if (shieldRing != null) {
deactivateShield();
}
shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
shieldRing.scale(1f);
shieldRing.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(0));
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,0,SHIELD_Z)));
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
shieldRing.setQueueBucket(RenderQueue.Bucket.Transparent); // Render in the transparent bucket
@@ -166,7 +155,7 @@ public void activateShield(){
* Deactivates the shield by removing the shield ring from the scene.
*/
public void deactivateShield(){
public void deactivateShield() {
parentNode.detachChild(shieldRing);
shieldRing = null;
}
@@ -174,108 +163,16 @@ public void deactivateShield(){
/**
* Suppresses the shield, changing its color to a suppressed state.
*/
public void suppressShield(){
assert(shieldRing != null) : "PieceControl: shieldRing is not set";
public void suppressShield() {
assert (shieldRing != null) : "PieceControl: shieldRing is not set";
shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
}
public void setMaterial(Material mat){
public void setMaterial(Material mat) {
spatial.setMaterial(mat);
}
public Material getMaterial(){
public Material getMaterial() {
return ((Geometry) getSpatial()).getMaterial();
}
/**
* Highlights the piece with the appropriate outline color based on whether it is an enemy or not.
*
* @param enemy True if the piece is an enemy, false if it is owned by the player.
*/
public void highlight(boolean enemy) {
this.enemy = enemy;
highlight = true;
super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
/**
* Removes the highlight effect from the piece.
*/
public void unHighlight(){
highlight = false;
deOutline();
}
/**
* Applies a hover effect on the piece if it is hoverable.
*/
public void hover(){
if(!hoverable) return;
super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
}
/**
* Removes the hover effect from the piece.
*/
public void hoverOff(){
if(!hoverable) return;
if(select) select();
else if(highlight) highlight(enemy);
else deOutline();
}
/**
* Deselects the piece and removes the selection outline. If the piece was highlighted,
* it will be re-highlighted. Otherwise, the outline is removed.
*/
public void unSelect(){
select = false;
if(highlight) highlight(enemy);
else deOutline();
}
/**
* Selects the piece and applies the selection outline. If the piece is an enemy, it will
* be outlined with the enemy selection color; otherwise, the own selection color will be used.
*/
public void select(){
if(!selectable) return;
select = true;
super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
}
/**
* Sets whether the piece is selectable.
*
* @param selectable True if the piece can be selected, false otherwise.
*/
public void setSelectable(boolean selectable){
this.selectable = selectable;
}
/**
* Checks if the piece is selected.
*
* @return True if the piece is selected, false otherwise.
*/
public boolean isSelected() { return select; }
/**
* Checks if the piece is selectable.
*
* @return True if the piece is selectable, false otherwise.
*/
public boolean isSelectable() {
return selectable;
}
/**
* Sets whether the piece is hoverable.
*
* @param hoverable True if the piece can be hovered over, false otherwise.
*/
public void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
}

View File

@@ -0,0 +1,130 @@
package pp.mdga.client.board;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
import static pp.mdga.client.Util.linInt;
/**
* Controls the rotation of the tank's top part to face an enemy position.
*/
public class TankTopControl extends InitControl {
private float timer = 0; // Time elapsed
private final static float DURATION = 1.5f; // Total rotation duration in seconds
private boolean rotating = false; // Flag to track if rotation is active
private float startAngle = 0;
private float endAngle = 0;
private Runnable actionAfter = null;
/**
* Updates the control each frame.
*
* @param tpf Time per frame
*/
@Override
protected void controlUpdate(float tpf) {
if (!rotating) return;
// Update the timer
timer += tpf;
// Calculate interpolation factor (0 to 1)
float t = timer / DURATION;
if (t >= 1) t = 1;
float curAngle = linInt(startAngle, endAngle, t);
// Interpolate the rotation
Quaternion interpolatedRotation = new Quaternion();
interpolatedRotation.fromAngleAxis((float) Math.toRadians(curAngle), Vector3f.UNIT_Z);
// Apply the interpolated rotation to the spatial
spatial.setLocalRotation(interpolatedRotation);
if (t >= 1) {
rotating = false;
if (actionAfter != null) actionAfter.run();
}
}
/**
* Initiates the rotation of the tank's top part to face the enemy position.
*
* @param enemyPos The position of the enemy
* @param actionAfter The action to execute after the rotation is complete
*/
public void rotate(Vector3f enemyPos, Runnable actionAfter) {
if (spatial == null) throw new RuntimeException("spatial is null");
startAngle = getOwnAngle();
endAngle = getEnemyAngle(enemyPos);
// Adjust endAngle to ensure the shortest path
float deltaAngle = endAngle - startAngle;
if (deltaAngle > 180) {
endAngle -= 360; // Rotate counterclockwise
} else if (deltaAngle < -180) {
endAngle += 360; // Rotate clockwise
}
timer = 0;
rotating = true;
this.actionAfter = actionAfter; // Store the action to execute after rotation
}
/**
* Calculates the angle to the enemy position.
*
* @param enemyPos The position of the enemy
* @return The angle to the enemy in degrees
*/
private float getEnemyAngle(Vector3f enemyPos) {
// Direction to the enemy in the XY plane
Vector3f direction = enemyPos.subtract(spatial.getLocalTranslation());
direction.z = 0; // Project to XY plane
direction.normalizeLocal();
Vector3f reference = Vector3f.UNIT_Y.mult(-1);
// Calculate the angle between the direction vector and the reference vector
float angle = FastMath.acos(reference.dot(direction));
// Determine rotation direction using the cross product
Vector3f cross = reference.cross(direction);
if (cross.z < 0) {
angle = -angle;
}
return (float) Math.toDegrees(angle); // Return the absolute angle in degrees
}
/**
* Calculates the tank's current angle.
*
* @return The tank's current angle in degrees
*/
private float getOwnAngle() {
// Tank's forward direction in the XY plane
Vector3f forward = spatial.getLocalRotation().mult(Vector3f.UNIT_Y);
forward.z = 0; // Project to XY plane
forward.normalizeLocal();
// Reference vector: Positive X-axis
Vector3f reference = Vector3f.UNIT_Y;
// Calculate the angle between the forward vector and the reference vector
float angle = FastMath.acos(reference.dot(forward));
// Determine rotation direction using the cross product
Vector3f cross = reference.cross(forward);
if (cross.z < 0) { // For Z-up, check the Z component of the cross product
angle = -angle;
}
return (float) Math.toDegrees(angle); // Return the absolute angle in radians
}
}

View File

@@ -115,7 +115,7 @@ public abstract class AbstractButton {
* 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 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) {

View File

@@ -3,7 +3,6 @@
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.

View File

@@ -69,15 +69,16 @@ public enum Pos {
* 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 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
* @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));
super(app, node, () -> {
}, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
this.node3d = node3d;
@@ -217,9 +218,9 @@ public void update(float tpf) {
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
@@ -236,9 +237,9 @@ private void createModel(Asset asset, Vector3f pos) {
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

View File

@@ -41,12 +41,12 @@ public abstract class ClickButton extends AbstractButton {
/**
* 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 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
* @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);
@@ -203,9 +203,9 @@ protected void setImageRelative(Picture picture) {
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
instance.getLocalTranslation().x - larger / 2,
(instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
instance.getLocalTranslation().z + 0.01f
);
}
}

View File

@@ -38,7 +38,8 @@ public class LabelButton extends ClickButton {
* @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);
super(app, node, () -> {
}, label, size, pos);
this.isButton = isButton;

View File

@@ -1,17 +1,13 @@
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;
@@ -101,11 +97,11 @@ public enum Taken {
/**
* 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)
* @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));
@@ -261,9 +257,9 @@ public void update(float tpf) {
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
@@ -280,9 +276,9 @@ private void createModel(Asset asset, Vector3f pos) {
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

View File

@@ -13,10 +13,10 @@ 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
* @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));

View File

@@ -2,8 +2,6 @@
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;

View File

@@ -77,12 +77,17 @@ public SliderButton(MdgaApp app, Node node, String label) {
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
slider.setBackground(background);
// Set label background
QuadBackgroundComponent labelBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
this.label.setBackground(labelBackground);
// Configure the label font
this.label.setFont(font);
this.label.setTextHAlignment(HAlignment.Center);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(5.5f, 1);
size = new Vector2f(6f, 1);
// Add label and slider to container
container.addChild(this.label);

View File

@@ -7,6 +7,11 @@
import pp.mdga.client.button.SliderButton;
import pp.mdga.client.view.MdgaView;
/**
* The {@code AudioSettingsDialog} class represents a dialog for adjusting audio settings in the application.
* It provides controls for managing main volume, music volume, and sound effect volume, and includes
* a button to return to the previous menu.
*/
public class AudioSettingsDialog extends Dialog {
private final MdgaView view;
@@ -18,6 +23,13 @@ public class AudioSettingsDialog extends Dialog {
private boolean active = false;
/**
* Constructs an {@code AudioSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The current view, used for navigation and interaction with the dialog.
*/
public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -42,6 +54,9 @@ public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Initializes and displays the volume controls and back button.
*/
@Override
protected void onShow() {
active = true;
@@ -57,6 +72,9 @@ protected void onShow() {
soundVolume.show();
}
/**
* Called when the dialog is hidden. Hides all volume controls and the back button.
*/
@Override
protected void onHide() {
active = false;
@@ -68,8 +86,12 @@ protected void onHide() {
soundVolume.hide();
}
/**
* Updates the application audio settings based on the current values of the sliders.
* This method is called continuously while the dialog is active.
*/
public void update() {
if(!active) {
if (!active) {
return;
}

View File

@@ -10,17 +10,30 @@
import java.util.ArrayList;
/**
* The {@code CeremonyDialog} class displays a dialog containing statistical data in a tabular format.
* It allows adding rows of statistics and manages their visibility when shown or hidden.
*/
public class CeremonyDialog extends Dialog {
private ArrayList<ArrayList<LabelButton>> labels;
float offsetX;
/**
* Constructs a {@code CeremonyDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public CeremonyDialog(MdgaApp app, Node node) {
super(app, node);
prepare();
}
/**
* Called when the dialog is shown. Makes all label buttons in the table visible.
*/
@Override
protected void onShow() {
for (ArrayList<LabelButton> row : labels) {
@@ -30,6 +43,9 @@ protected void onShow() {
}
}
/**
* Called when the dialog is hidden. Hides all label buttons in the table.
*/
@Override
protected void onHide() {
for (ArrayList<LabelButton> row : labels) {
@@ -39,6 +55,17 @@ protected void onHide() {
}
}
/**
* Adds a row of statistical data to the dialog.
*
* @param name The name of the player or category for the row.
* @param v1 The value for the first column.
* @param v2 The value for the second column.
* @param v3 The value for the third column.
* @param v4 The value for the fourth column.
* @param v5 The value for the fifth column.
* @param v6 The value for the sixth column.
*/
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
float offsetYSmall = 0.5f;
@@ -61,7 +88,7 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
int j = 0;
for (LabelButton b : row) {
if(j > 0) {
if (j > 0) {
b.setColor(colorText, colorButton);
}
@@ -76,6 +103,9 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
labels.add(row);
}
/**
* Prepares the initial layout of the dialog, including header labels.
*/
public void prepare() {
offsetX = 0.5f;

View File

@@ -1,33 +1,55 @@
package pp.mdga.client.dialog;
import com.jme3.scene.Node;
import com.simsilica.lemur.Container;
import pp.mdga.client.MdgaApp;
/**
* The {@code Dialog} class serves as an abstract base class for dialogs in the application.
* It provides functionality for showing and hiding the dialog and defines abstract methods
* for custom behavior when the dialog is shown or hidden.
*/
public abstract class Dialog {
protected final MdgaApp app;
protected final Node node = new Node();
private final Node root;
/**
* Constructs a {@code Dialog}.
*
* @param app The main application managing the dialog.
* @param node The root node to which the dialog's node will be attached.
*/
Dialog(MdgaApp app, Node node) {
this.app = app;
this.root = node;
}
/**
* Shows the dialog by attaching its node to the root node and invoking the {@code onShow} method.
*/
public void show() {
root.attachChild(node);
onShow();
}
/**
* Hides the dialog by detaching its node from the root node and invoking the {@code onHide} method.
*/
public void hide() {
root.detachChild(node);
onHide();
}
/**
* Called when the dialog is shown. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onShow();
/**
* Called when the dialog is hidden. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onHide();
}

View File

@@ -12,7 +12,11 @@
import java.util.prefs.Preferences;
public class HostDialog extends NetworkDialog {
/**
* The {@code HostDialog} class represents a dialog for hosting a network game session.
* It allows users to input a port number, start hosting a server, and navigate back to the previous view.
*/
public class HostDialog extends NetworkDialog {
private InputButton portInput;
private ButtonRight hostButton;
@@ -22,6 +26,13 @@ public class HostDialog extends NetworkDialog {
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/**
* Constructs a {@code HostDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public HostDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
@@ -39,6 +50,9 @@ public HostDialog(MdgaApp app, Node node, MainView view) {
offset += 1.5f;
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
portInput.show();
@@ -46,6 +60,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
portInput.hide();
@@ -53,27 +70,44 @@ protected void onHide() {
backButton.hide();
}
/**
* Updates the state of the port input field.
* This method is called periodically to synchronize the dialog state.
*/
public void update() {
portInput.update();
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("hostPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("hostPort", "11111");
}
/**
* Starts the server to host a network game.
*/
public void hostServer() {
startServer();
}
/**
* Connects to the server as a client.
*/
public void connectServerAsClient() {
connectServer();
}
}

View File

@@ -3,13 +3,15 @@
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import pp.mdga.game.Color;
/**
* The {@code InterruptDialog} class represents a dialog that interrupts the game flow,
* providing a message and the option to force an action if the user is a host.
*/
public class InterruptDialog extends Dialog {
private ButtonRight forceButton;
@@ -17,33 +19,50 @@ public class InterruptDialog extends Dialog {
private String text = "";
/**
* Constructs an {@code InterruptDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public InterruptDialog(MdgaApp app, Node node) {
super(app, node);
forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
}
/**
* Called when the dialog is shown. Displays the label and optionally the force button if the user is the host.
*/
@Override
protected void onShow() {
if (app.getGameLogic().isHost()) {
forceButton.show();
}
label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
float offset = 2.8f;
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
}
@Override
protected void onShow() {
if(app.getGameLogic().isHost()) {
forceButton.show();
}
label.show();
}
/**
* Called when the dialog is hidden. Hides the label and the force button.
*/
@Override
protected void onHide() {
forceButton.hide();
label.hide();
}
/**
* Sets the displayed text based on the specified color.
*
* @param color The color used to determine the text (e.g., "Luftwaffe" for AIRFORCE).
*/
public void setColor(Color color) {
switch (color) {
case AIRFORCE:

View File

@@ -4,7 +4,6 @@
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.acoustic.AcousticHandler;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.InputButton;
@@ -13,6 +12,10 @@
import java.util.prefs.Preferences;
/**
* The {@code JoinDialog} class represents a dialog for joining a network game.
* It allows users to input an IP address and port number, connect to a server, or navigate back to the previous view.
*/
public class JoinDialog extends NetworkDialog {
private InputButton ipInput;
private InputButton portInput;
@@ -24,6 +27,13 @@ public class JoinDialog extends NetworkDialog {
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/**
* Constructs a {@code JoinDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public JoinDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
@@ -46,6 +56,9 @@ public JoinDialog(MdgaApp app, Node node, MainView view) {
offset += 1.5f;
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
ipInput.show();
@@ -54,6 +67,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
ipInput.hide();
@@ -62,37 +78,62 @@ protected void onHide() {
backButton.hide();
}
/**
* Updates the state of the input fields. This method is called periodically to synchronize the dialog state.
*/
public void update() {
ipInput.update();
portInput.update();
}
/**
* Retrieves the currently entered IP address, saves it to preferences, and sets it as the hostname.
*
* @return The IP address as a string.
*/
public String getIpt() {
prefs.put("joinIp", ipInput.getString());
setHostname(ipInput.getString());
return ipInput.getString();
}
/**
* Resets the IP input field to its default value and updates preferences accordingly.
*/
public void resetIp() {
ipInput.reset();
prefs.put("joinIp", "");
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("joinPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("joinPort", "11111");
}
/**
* Connects to the server using the current IP address and port number.
*/
public void connectToServer() {
connectServer();
}
/**
* Disconnects from the server if a network connection exists.
*/
public void disconnect() {
NetworkSupport network = getNetwork();
if (network != null) {
@@ -104,4 +145,3 @@ public void disconnect() {
}
}
}

View File

@@ -8,6 +8,11 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* The {@code NetworkDialog} class serves as an abstract base class for dialogs
* that involve network-related functionalities, such as connecting to a server or hosting a game.
* It provides methods for initializing, connecting to, and managing a network server.
*/
public abstract class NetworkDialog extends Dialog {
private NetworkSupport network;
@@ -17,19 +22,41 @@ public abstract class NetworkDialog extends Dialog {
private MdgaServer serverInstance;
private Thread serverThread;
/**
* Constructs a {@code NetworkDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param network The network support instance for managing network interactions.
*/
public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
super(app, node);
this.network = network;
}
/**
* Sets the hostname for the network connection.
*
* @param hostname The hostname or IP address of the server.
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* Sets the port number for the network connection.
*
* @param portNumber The port number to use for the connection.
*/
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
/**
* Initializes the network connection using the current hostname and port number.
*
* @return {@code null} if successful, otherwise throws an exception.
*/
protected Object initNetwork() {
try {
this.network.initNetwork(this.hostname, this.portNumber);
@@ -39,6 +66,9 @@ protected Object initNetwork() {
}
}
/**
* Starts the process of connecting to a server asynchronously.
*/
protected void connectServer() {
try {
connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
@@ -47,6 +77,9 @@ protected void connectServer() {
}
}
/**
* Starts hosting a server in a separate thread.
*/
protected void startServer() {
serverThread = new Thread(() -> {
try {
@@ -60,31 +93,28 @@ protected void startServer() {
serverThread.start();
}
/**
* Shuts down the hosted server and cleans up resources.
*/
public void shutdownServer() {
serverInstance.shutdown();
// Wait for the server to shut down
try {
Thread.sleep(1000);
serverThread.join(); // Wait for the server thread to finish
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread was interrupted: " + e.getMessage());
}
if (serverInstance != null) {
serverInstance.shutdown();
serverInstance = null;
}
if (serverThread != null && serverThread.isAlive()) {
serverThread.interrupt();
try {
serverThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
serverThread = null;
}
System.out.println("Server shutdown successfully.");
}
/**
* Updates the state of the connection process.
*
* @param delta The time elapsed since the last update call.
*/
public void update(float delta) {
if (this.connectionFuture != null && this.connectionFuture.isDone()) {
try {
@@ -97,6 +127,11 @@ public void update(float delta) {
}
}
/**
* Retrieves the {@code NetworkSupport} instance associated with this dialog.
*
* @return The {@code NetworkSupport} instance.
*/
public NetworkSupport getNetwork() {
return network;
}

View File

@@ -3,11 +3,13 @@
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;
/**
* The {@code SettingsDialog} class represents a dialog for navigating to various settings sections,
* such as video and audio settings, or returning to the previous view.
*/
public class SettingsDialog extends Dialog {
private MenuButton videoButton;
private MenuButton audioButton;
@@ -15,6 +17,13 @@ public class SettingsDialog extends Dialog {
private final MdgaView view;
/**
* Constructs a {@code SettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the settings dialog.
*/
public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -34,6 +43,9 @@ public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onShow() {
videoButton.show();
@@ -41,6 +53,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onHide() {
videoButton.hide();

View File

@@ -1,19 +1,18 @@
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;
/**
* The {@code StartDialog} class represents the initial dialog in the application,
* allowing the user to input their name, host or join a game, or exit the application.
*/
public class StartDialog extends Dialog {
private InputButton nameInput;
@@ -23,6 +22,13 @@ public class StartDialog extends Dialog {
private final MainView view;
/**
* Constructs a {@code StartDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public StartDialog(MdgaApp app, Node node, MainView view) {
super(app, node);
@@ -48,6 +54,9 @@ public StartDialog(MdgaApp app, Node node, MainView view) {
endButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays the name input field and all buttons.
*/
@Override
protected void onShow() {
nameInput.show();
@@ -57,9 +66,11 @@ protected void onShow() {
endButton.show();
}
/**
* Called when the dialog is hidden. Hides the name input field and all buttons.
*/
@Override
protected void onHide ()
{
protected void onHide() {
nameInput.hide();
hostButton.hide();
@@ -67,219 +78,227 @@ protected void onHide ()
endButton.hide();
}
/**
* Updates the state of the name input field. This method is called periodically to synchronize the dialog state.
*/
public void update() {
nameInput.update();
}
/**
* Retrieves the name entered by the user. If no name is provided, a random name is generated.
*
* @return The user's name or a randomly generated name.
*/
public String getName() {
String name = nameInput.getString();
if (name == null || name.trim().isEmpty()) {
String[] names = {
"PixelPirat",
"NoobJäger",
"LagMeister",
"KnopfDrücker",
"SpawnCamper",
"AFKHeld",
"RageQuitter",
"GameOverPro",
"Checkpoint",
"RespawnHeld",
"Teebeutel",
"GlitchHexer",
"QuickScope",
"LootSammler",
"EpicLauch",
"KartoffelPro",
"StilleKlinge",
"TastenHeld",
"PixelKrieger",
"HacknSlash",
"JoystickJoe",
"SpawnFalle",
"OneHitWanda",
"CamperKing",
"GameGenie",
"HighPing",
"CheesePro",
"Speedy",
"GigaGamer",
"LevelNoob",
"SkillTobi",
"HeadshotMax",
"PentaPaul",
"CritKarl",
"ManaLeerer",
"Nachlader",
"ClutchKönig",
"FriendlyFe",
"ZonenHeld",
"SchleichKatze",
"ShotgunPro",
"SniperUdo",
"BossHunter",
"HeldenNoob",
"KillFranz",
"FragKarl",
"TeamNiete",
"LootPaul",
"UltraNoob",
"ProfiScout",
"PunkteKlaus",
"KrüppelKill",
"PixelNinja",
"NoobCrusher",
"LagBoss",
"SpawnKing",
"AFKSlayer",
"RespawnPro",
"Killjoy",
"GameBreaker",
"FastFingers",
"LootKing",
"QuickFlick",
"SilentShot",
"HackGod",
"GlitchHero",
"SpeedyBot",
"AimWizard",
"FragMaster",
"OneTapPro",
"KnifeLord",
"MetaHunter",
"PingWarrior",
"KeyBash",
"ClutchPro",
"ScopeBot",
"TrollMage",
"PowerLooter",
"TankHero",
"CampLord",
"SmurfSlayer",
"SkillThief",
"SniperGod",
"LevelHack",
"GhostAim",
"BossTamer",
"ShotgunJoe",
"AimRider",
"KillCount",
"PixelManiac",
"TrollOver",
"SneakPro",
"ReloadKing",
"SpawnTrap",
"LagLover",
"MetaHater",
"BoomMaker",
"WipeLord",
"CarryPro",
"ProBaiter",
"GameWarden",
"KartoffelKönig",
"SaufenderWolf",
"WurstGriller",
"Flitzekacke",
"BratwurstBub",
"Hoppeldoppels",
"BananenMensch",
"KlopapierGuru",
"SchnitzelKing",
"NerdNomade",
"Dönertänzer",
"GlitzerGurke",
"SchinkenShrek",
"KäseKalle",
"SchokoSchnecke",
"KeksKämpfer",
"QuarkPiraten",
"Müslimonster",
"KnuddelNase",
"FantaFighter",
"SchnapsSaurier",
"Wackelpudding",
"ZitronenZock",
"FettWurst",
"PlüschPanda",
"Zuckerschnur",
"FluffiKopf",
"DonutDöner",
"VollpfostenX",
"Schraubenschlüssel",
"Witzepumper",
"ToastTraum",
"FroschFighter",
"KrümelTiger",
"RegenWolke",
"PuddingPower",
"KoffeinKrieger",
"SpeckSchlumpf",
"SuperSuppe",
"BierBärchen",
"FischBär",
"Flauschi",
"Schokomonster",
"ChaosKäse",
"FlitzLappen",
"WurstWombat",
"KrümelMensch",
"PuddingBär",
"ZickZack",
"Schwabel",
"Fluffi",
"RülpsFrosch",
"PommesPapa",
"QuarkBär",
"KnusperKönig",
"ToastBrot",
"Ploppster",
"Schleimschwein",
"Äpfelchen",
"Knallbonbon",
"KaffeeKopf",
"WackelWurst",
"RennKeks",
"BröselBub",
"ZockerBrot",
"BierWurm",
"StinkFlummi",
"SchlumpfKing",
"PurzelBär",
"FlinkFluff",
"PloppPudel",
"Schnorchel",
"FliegenKopf",
"PixelPommes",
"SchwipsWürst",
"WutzBär",
"KnuddelKeks",
"FantaFlumm",
"ZockerKäse",
"LachHäufchen",
"GurkenGuru",
"PonySchnitzel",
"NudelNinja",
"VulkanKeks",
"WasserToast",
"MenschSalat",
"KampfKohlenhydrate",
"SockenZirkus",
"SchwimmBärchen",
"TanzenderDachgepäckträger",
"PizzamarktMensch",
"ZahnarztZocker",
"RollerCoasterTester",
"WaschmaschinenPilot",
"WitzigeZwiebel",
"Pillenschlucker",
"ZwiebelReiter",
"HüpfenderKaktus",
"KochenderAsteroid",
"ChaosKarotte",
"WolkenFurz",
"SchnitzelPartikel",
"WackelBiene",
"PixelPirat",
"NoobJäger",
"LagMeister",
"KnopfDrücker",
"SpawnCamper",
"AFKHeld",
"RageQuitter",
"GameOverPro",
"Checkpoint",
"RespawnHeld",
"Teebeutel",
"GlitchHexer",
"QuickScope",
"LootSammler",
"EpicLauch",
"KartoffelPro",
"StilleKlinge",
"TastenHeld",
"PixelKrieger",
"HacknSlash",
"JoystickJoe",
"SpawnFalle",
"OneHitWanda",
"CamperKing",
"GameGenie",
"HighPing",
"CheesePro",
"Speedy",
"GigaGamer",
"LevelNoob",
"SkillTobi",
"HeadshotMax",
"PentaPaul",
"CritKarl",
"ManaLeerer",
"Nachlader",
"ClutchKönig",
"FriendlyFe",
"ZonenHeld",
"SchleichKatze",
"ShotgunPro",
"SniperUdo",
"BossHunter",
"HeldenNoob",
"KillFranz",
"FragKarl",
"TeamNiete",
"LootPaul",
"UltraNoob",
"ProfiScout",
"PunkteKlaus",
"KrüppelKill",
"PixelNinja",
"NoobCrusher",
"LagBoss",
"SpawnKing",
"AFKSlayer",
"RespawnPro",
"Killjoy",
"GameBreaker",
"FastFingers",
"LootKing",
"QuickFlick",
"SilentShot",
"HackGod",
"GlitchHero",
"SpeedyBot",
"AimWizard",
"FragMaster",
"OneTapPro",
"KnifeLord",
"MetaHunter",
"PingWarrior",
"KeyBash",
"ClutchPro",
"ScopeBot",
"TrollMage",
"PowerLooter",
"TankHero",
"CampLord",
"SmurfSlayer",
"SkillThief",
"SniperGod",
"LevelHack",
"GhostAim",
"BossTamer",
"ShotgunJoe",
"AimRider",
"KillCount",
"PixelManiac",
"TrollOver",
"SneakPro",
"ReloadKing",
"SpawnTrap",
"LagLover",
"MetaHater",
"BoomMaker",
"WipeLord",
"CarryPro",
"ProBaiter",
"GameWarden",
"KartoffelKönig",
"SaufenderWolf",
"WurstGriller",
"Flitzekacke",
"BratwurstBub",
"Hoppeldoppels",
"BananenMensch",
"KlopapierGuru",
"SchnitzelKing",
"NerdNomade",
"Dönertänzer",
"GlitzerGurke",
"SchinkenShrek",
"KäseKalle",
"SchokoSchnecke",
"KeksKämpfer",
"QuarkPiraten",
"Müslimonster",
"KnuddelNase",
"FantaFighter",
"SchnapsSaurier",
"Wackelpudding",
"ZitronenZock",
"FettWurst",
"PlüschPanda",
"Zuckerschnur",
"FluffiKopf",
"DonutDöner",
"VollpfostenX",
"Waschlappen",
"Witzepumper",
"ToastTraum",
"FroschFighter",
"KrümelTiger",
"RegenWolke",
"PuddingPower",
"KoffeinKrieger",
"SpeckSchlumpf",
"SuperSuppe",
"BierBärchen",
"FischBär",
"Flauschi",
"Schokomonster",
"ChaosKäse",
"FlitzLappen",
"WurstWombat",
"KrümelMensch",
"PuddingBär",
"ZickZack",
"Schwabel",
"Fluffi",
"RülpsFrosch",
"PommesPapa",
"QuarkBär",
"KnusperKönig",
"ToastBrot",
"Ploppster",
"Schleimschwein",
"Äpfelchen",
"Knallbonbon",
"KaffeeKopf",
"WackelWurst",
"RennKeks",
"BröselBub",
"ZockerBrot",
"BierWurm",
"StinkFlummi",
"SchlumpfKing",
"PurzelBär",
"FlinkFluff",
"PloppPudel",
"Schnorchel",
"FliegenKopf",
"PixelPommes",
"SchwipsWürst",
"WutzBär",
"KnuddelKeks",
"FantaFlumm",
"ZockerKäse",
"LachHäufchen",
"GurkenGuru",
"PonySchnitzel",
"NudelNinja",
"VulkanKeks",
"WasserToast",
"MenschSalat",
"KampfKohl",
"SockenZirkus",
"SchwimmBärchen",
"TanzenderPudel",
"PizzamarktMensch",
"ZahnarztZocker",
"RollerRudi",
"PupsPilot",
"WitzigeZwiebel",
"Pillenschlucker",
"ZwiebelReiter",
"HüpfenderKaktus",
"AsteroidenAlf",
"ChaosKarotte",
"WolkenFurz",
"Krümelmonster",
"WackelBiene",
};
Random random = new Random();

View File

@@ -3,7 +3,6 @@
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.MenuButton;
@@ -11,6 +10,11 @@
import java.util.prefs.Preferences;
/**
* The {@code VideoSettingsDialog} class represents a dialog for configuring video settings,
* such as resolution and fullscreen mode. It also provides an option to restart the application
* when certain settings are changed.
*/
public class VideoSettingsDialog extends Dialog {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
@@ -29,6 +33,13 @@ public class VideoSettingsDialog extends Dialog {
private boolean active = false;
/**
* Constructs a {@code VideoSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the video settings dialog.
*/
public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -67,6 +78,9 @@ public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays all buttons and marks the dialog as active.
*/
@Override
protected void onShow() {
active = true;
@@ -83,6 +97,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all buttons and marks the dialog as inactive.
*/
@Override
protected void onHide() {
active = false;
@@ -100,14 +117,25 @@ protected void onHide() {
restartButton.hide();
}
/**
* Updates the dialog's state. This method can be used for periodic updates while the dialog is active.
*/
public void update() {
if(!active) {
if (!active) {
return;
}
}
/**
* Updates the resolution settings and optionally triggers the restart button if changes are detected.
*
* @param width The width of the resolution.
* @param height The height of the resolution.
* @param imageFactor The scaling factor for the resolution.
* @param isFullscreen {@code true} if fullscreen mode is enabled, {@code false} otherwise.
*/
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if(width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
if (width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
restartButton.show();
}

View File

@@ -10,13 +10,26 @@
import pp.mdga.client.animation.ZoomControl;
import pp.mdga.game.Color;
class ActionTextHandler {
/**
* The {@code ActionTextHandler} class manages the display of animated and stylized text messages in the game's UI.
* It supports dynamic text creation with spacing, color, and effects, such as dice rolls, player actions, and rankings.
*/
class ActionTextHandler {
private Node root;
private BitmapFont font;
private AppSettings appSettings;
private int ranking;
ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
float paddingRanked = 100;
/**
* Constructs an {@code ActionTextHandler}.
*
* @param guiNode The GUI node where the text messages will be displayed.
* @param assetManager The asset manager used to load fonts and other assets.
* @param appSettings The application settings for positioning and sizing.
*/
ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
root = new Node("actionTextRoot");
guiNode.attachChild(root);
@@ -26,13 +39,23 @@ class ActionTextHandler {
ranking = 0;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and colors for each segment of the text.
*
* @param textArr An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param colorArr An array of {@code ColorRGBA} representing the color for each string in {@code textArr}.
* @return A {@code Node} containing the styled text with spacing and color applied.
* @throws RuntimeException if the lengths of {@code textArr} and {@code colorArr} do not match.
*/
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");
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++){
for (int i = 0; i < textArr.length; i++) {
String text = textArr[i];
ColorRGBA color = colorArr[i];
for (char c : text.toCharArray()) {
@@ -40,90 +63,199 @@ private Node createTextWithSpacing(String[] textArr, float spacing, float size,
letter.setColor(color);
letter.setSize(size);
letter.setText(Character.toString(c));
letter.setLocalTranslation(xOffset, letter.getHeight()/2, 0);
letter.setLocalTranslation(xOffset, letter.getHeight() / 2, 0);
center.attachChild(letter);
xOffset += letter.getLineWidth() + spacing;
}
}
center.setLocalTranslation(new Vector3f(-xOffset/2,0,0));
center.setLocalTranslation(new Vector3f(-xOffset / 2, 0, 0));
textNode.attachChild(center);
return textNode;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and a single color.
*
* @param text The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @return A {@code Node} containing the styled text.
*/
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);
/**
* Calculates the center position of a rectangle given its width, height, and an origin position.
*
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
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){
/**
* Creates and positions a single-line text at the top of the screen with a specified vertical offset.
*
* @param name The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the top.
*/
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){
/**
* Creates and positions multi-line text at the top of the screen with specified vertical offset, spacing, and colors.
*
* @param name An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color An array of {@code ColorRGBA} representing the color for each string in {@code name}.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the 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);
text.setLocalTranslation(0, (appSettings.getHeight() / 2f) * 0.8f - top, 0);
root.attachChild(text);
return text;
}
private Vector3f centerText(float width, float height, Vector3f pos){
/**
* Calculates the center position of a rectangle with negative width offset.
*
* @param width The negative width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
private Vector3f centerText(float width, float height, Vector3f pos) {
return center(-width, height, pos);
}
void activePlayer(String name, Color color){
createTopText(new String[]{name," ist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating the active player.
*
* @param name The name of the active player.
* @param color The color representing the player's team.
*/
void activePlayer(String name, Color color) {
createTopText(new String[]{name, " ist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void ownActive(Color color){
createTopText(new String[]{"Du"," bist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating that the current player is active.
*
* @param color The color representing the player's team.
*/
void ownActive(Color color) {
createTopText(new String[]{"Du", " bist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void diceNum(int diceNum, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
/**
* Displays a dice roll result for a player.
*
* @param diceNum The number rolled on the dice.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
void diceNum(int diceNum, String name, Color color) {
createTopText(new String[]{name, " würfelt:"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0);
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 100);
}
void diceNumMult(int diceNum,int mult, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
/**
* Displays a dice roll result with a multiplier for a player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
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);
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 100);
}
void ownDice(int diceNum){
/**
* Displays the dice roll result for the current player.
*
* @param diceNum The number rolled on the dice.
*/
void ownDice(int diceNum) {
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
}
void ownDiceMult(int diceNum, int mult){
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 0);
/**
* Displays the dice roll result with a multiplier for the current player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
*/
void ownDiceMult(int diceNum, int mult) {
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 0);
}
void drawCard(String name, Color color){
createTopText(new String[]{name," erhält eine Bonuskarte"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating that a specified player received a bonus card.
*
* @param name The name of the player who received the bonus card.
* @param color The color representing the player's team.
*/
void drawCard(String name, Color color) {
createTopText(new String[]{name, " erhält eine Bonuskarte"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void drawCardOwn(Color color){
createTopText(new String[]{"Du"," erhälst eine Bonuskarte"}, 5,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating that the current player received a bonus card.
*
* @param color The color representing the player's team.
*/
void drawCardOwn(Color color) {
createTopText(new String[]{"Du", " erhälst eine Bonuskarte"}, 5, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void finishText(String name, Color color){
createTopText(new String[]{name," ist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating that a specified player has completed their turn or action.
*
* @param name The name of the player who finished.
* @param color The color representing the player's team.
*/
void finishText(String name, Color color) {
createTopText(new String[]{name, " ist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void finishTextOwn(Color color){
createTopText(new String[]{"Du", " bist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
/**
* Displays a message indicating that the current player has completed their turn or action.
*
* @param color The color representing the player's team.
*/
void finishTextOwn(Color color) {
createTopText(new String[]{"Du", " bist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
private ColorRGBA playerColorToColorRGBA(Color color){
return switch (color){
/**
* Converts a player's team color to a corresponding {@code ColorRGBA}.
*
* @param color The player's team color.
* @return The corresponding {@code ColorRGBA}.
* @throws RuntimeException if the color is invalid.
*/
private ColorRGBA playerColorToColorRGBA(Color color) {
return switch (color) {
case ARMY -> ColorRGBA.Green;
case NAVY -> ColorRGBA.Blue;
case CYBER -> ColorRGBA.Orange;
@@ -132,25 +264,41 @@ private ColorRGBA playerColorToColorRGBA(Color color){
};
}
void hide(){
ranking = 0;
root.detachAllChildren();
/**
* Hides all text messages displayed by the handler and resets the ranking counter.
*/
void hide() {
ranking = 0;
root.detachAllChildren();
}
float paddingRanked = 100;
void rollRankingResult(String name, Color color, int eye){
createTopText(new String[]{name,": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
/**
* Displays a ranked dice roll result for a specified player.
*
* @param name The name of the player.
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResult(String name, Color color, int eye) {
createTopText(new String[]{name, ": " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
ranking++;
}
void rollRankingResultOwn(Color color, int eye){
createTopText(new String[]{"Du",": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
ranking++;
}
void diceNow(){
createTopText("Klicke zum Würfeln", 5, 80, ColorRGBA.White, 0);
}
/**
* Displays a ranked dice roll result for the current player.
*
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResultOwn(Color color, int eye) {
createTopText(new String[]{"Du", ": " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
ranking++;
}
/**
* Displays a message prompting the player to roll the dice.
*/
void diceNow() {
createTopText("Klicke zum Würfeln", 5, 80, ColorRGBA.White, 0);
}
}

View File

@@ -7,23 +7,18 @@
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.*;
/**
* CardControl class extends OutlineControl to manage the visual representation
* and behavior of a card in the game.
*/
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;
@@ -33,29 +28,40 @@ public class CardControl extends OutlineControl {
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);
/**
* Constructor for CardControl.
*
* @param app the application instance
* @param fpp the FilterPostProcessor instance
* @param cam the Camera instance
* @param root the root Node
*/
public CardControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, Node root) {
super(app, fpp, cam,
HIGHLIGHT_COLOR, HIGHLIGHT_WIDTH,
HOVER_COLOR, HOVER_WIDTH,
SELECT_COLOR, SELECT_WIDTH
);
this.root = root;
Node rootNum = createNum();
rootNum.setLocalTranslation(new Vector3f(0.35f,0.8f,0));
rootNum.setLocalTranslation(new Vector3f(0.35f, 0.8f, 0));
root.attachChild(rootNum);
}
private Node createNum(){
/**
* Creates a Node containing a number and a circle geometry.
*
* @return the created Node
*/
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));
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);
@@ -72,79 +78,30 @@ private Node createNum(){
return rootNum;
}
public void setNumCard(int num){
/**
* Sets the number displayed on the card.
*
* @param num the number to display
*/
public void setNumCard(int num) {
this.num.setText(String.valueOf(num));
}
/**
* Gets the root Node of the card.
*
* @return the root Node
*/
public Node getRoot() {
return root;
}
/**
* Initializes the spatial properties of the card.
*/
@Override
public void initSpatial(){
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;
}
private final static Vector3f HIGHLIGHT_Y = new Vector3f(0, 0.4f, 0);
}

View File

@@ -8,6 +8,7 @@
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.ComposeFilter;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
@@ -18,8 +19,13 @@
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
/**
* CardLayer is an application state that manages the rendering and updating of card objects
* in a separate viewport with post-processing effects.
*/
public class CardLayer extends AbstractAppState {
public static final int SHADOWMAP_SIZE = 1024 * 8;
@@ -31,35 +37,58 @@ public class CardLayer extends AbstractAppState {
private List<Spatial> cardBuffer;
private final FilterPostProcessor fpp;
private final Camera overlayCam;
Texture2D backTexture;
private Texture2D backTexture;
private FXAAFilter fxaaFilter;
private ViewPort view;
private DirectionalLightShadowFilter dlsf;
DirectionalLight sun;
ComposeFilter compose;
/**
* Constructs a new CardLayer with the specified post-processor, camera, and background texture.
*
* @param fpp the FilterPostProcessor to use for post-processing effects
* @param overlayCam the Camera to use for the overlay
* @param backTexture the Texture2D to use as the background texture
*/
public CardLayer(FilterPostProcessor fpp, Camera overlayCam, Texture2D backTexture) {
this.overlayCam = overlayCam;
this.fpp = fpp;
this.cardBuffer = new ArrayList<>();
init = false;
this.backTexture = backTexture;
cardBuffer = new ArrayList<>();
init = false;
fxaaFilter = new FXAAFilter();
view = null;
dlsf = null;
sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(.5f, -.5f, -1));
compose = new ComposeFilter(backTexture);
root = new Node("Under gui viewport Root");
}
/**
* Initializes the CardLayer, setting up the viewport, filters, and lighting.
*
* @param stateManager the AppStateManager managing this state
* @param app the Application instance
*/
@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 = 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));
fpp.addFilter(compose);
fpp.addFilter(fxaaFilter);
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(.5f, -.5f, -1));
root.addLight(sun);
DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
dlsf.setLight(sun);
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
@@ -71,17 +100,35 @@ public void initialize(AppStateManager stateManager, Application app) {
if (!init) init = true;
}
/**
* Shuts down the CardLayer, removing filters, lights, and clearing buffers.
*/
public void shutdown() {
fpp.removeFilter(dlsf);
dlsf = null;
root.removeLight(sun);
fpp.removeFilter(fxaaFilter);
view.detachScene(root);
cardBuffer.clear();
root.detachAllChildren();
}
/**
* Renders the CardLayer, updating the geometric state of the root node.
*
* @param rm the RenderManager handling the rendering
*/
@Override
public void render(RenderManager rm) {
root.updateGeometricState();
}
/**
* Updates the CardLayer, attaching buffered cards to the root node and updating its logical state.
*
* @param tpf time per frame
*/
@Override
public void update(float tpf) {
if (init && !cardBuffer.isEmpty()) {
@@ -93,19 +140,39 @@ public void update(float tpf) {
root.updateLogicalState(tpf);
}
/**
* Adds a spatial card to the CardLayer.
*
* @param card the Spatial card to add
*/
public void addSpatial(Spatial card) {
if(root == null) cardBuffer.add(card);
if (root == null) cardBuffer.add(card);
else root.attachChild(card);
}
/**
* Deletes a spatial card from the CardLayer.
*
* @param spatial the Spatial card to delete
*/
public void deleteSpatial(Spatial spatial) {
root.detachChild(spatial);
}
/**
* Gets the overlay camera used by the CardLayer.
*
* @return the overlay camera
*/
public Camera getOverlayCam() {
return overlayCam;
}
/**
* Gets the root node of the CardLayer.
*
* @return the root node
*/
public Node getRootNode() {
return root;
}

View File

@@ -15,6 +15,9 @@
import java.util.*;
/**
* Handles the card layer in the GUI, including card management and dice control.
*/
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);
@@ -29,17 +32,28 @@ public class CardLayerHandler {
private DiceControl diceControl;
private final Map<BonusCard, CardControl> bonusCardControlMap = new HashMap<>();
private final List<BonusCard> cardOrder = new ArrayList<>();
private final Map<BonusCard, Integer> bonusCardIntegerMap = new HashMap<>();
private final Set<CardControl> selectableCards = new HashSet<>();
private BonusCard cardSelect = null;
private boolean show = false;
/**
* Constructs a CardLayerHandler.
*
* @param app the application instance
* @param backTexture the background texture
*/
public CardLayerHandler(MdgaApp app, Texture2D backTexture) {
this.app = app;
this.fpp = new FilterPostProcessor(app.getAssetManager());
this.backTexture = backTexture;
}
/**
* Initializes the card layer and dice control.
*/
public void init() {
cardLayerCamera = createOverlayCam();
cardLayer = new CardLayer(fpp, cardLayerCamera, backTexture);
@@ -49,114 +63,188 @@ public void init() {
diceControl.create(new Vector3f(0, 0, 0), 1f, false);
}
/**
* Shuts down the card layer and clears selectable cards.
*/
public void shutdown() {
clearSelectableCards();
if (cardLayer != null) {
cardLayer.shutdown();
clearSelectableCards();
app.getStateManager().detach(cardLayer);
}
cardLayer = null;
}
/**
* Rolls the dice with a specified number and action to perform after rolling.
*
* @param rollNum the number to roll (must be between 1 and 6)
* @param actionAfter the action to perform after rolling
*/
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);
}
/**
* Shows the dice on the card layer.
*/
public void showDice() {
if (show) return;
show = true;
cardLayer.addSpatial(diceControl.getSpatial());
diceControl.spin();
}
/**
* Hides the dice from the card layer.
*/
public void hideDice() {
show = false;
diceControl.hide();
}
/**
* Adds a card to the card layer.
*
* @param card the card to add
*/
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());
cardOrder.add(card);
}
int newNum = bonusCardIntegerMap.getOrDefault(card, 0) + 1;
bonusCardIntegerMap.put(card, newNum);
bonusCardControlMap.get(card).setNumCard(newNum);
updateCard();
}
public void removeCard(BonusCard card){
if(bonusCardControlMap.containsKey(card)){
/**
* Removes a card from the card layer.
*
* @param card the card to remove
*/
public void removeCard(BonusCard card) {
if (bonusCardControlMap.containsKey(card)) {
bonusCardIntegerMap.put(card, bonusCardIntegerMap.get(card) - 1);
if(bonusCardIntegerMap.get(card) <= 0){
cardLayer.deleteSpatial(bonusCardControlMap.get(card).getRoot());
if (bonusCardIntegerMap.get(card) <= 0) {
bonusCardIntegerMap.remove(card);
bonusCardControlMap.remove(card);
cardOrder.remove(card);
}
}
updateCard();
} else throw new RuntimeException("card is not in bonusCardControlMap");
}
/**
* Clears all selectable cards.
*/
public void clearSelectableCards() {
for (CardControl control : selectableCards) {
control.setSelectable(false);
control.setHoverable(false);
control.unHighlight();
control.unSelect();
control.selectableOff();
}
selectableCards.clear();
cardSelect = null;
}
/**
* Updates the card layer with the current cards.
*/
private void updateCard() {
for (BonusCard card : bonusCardControlMap.keySet()) {
CardControl control = bonusCardControlMap.get(card);
cardLayer.deleteSpatial(control.getRoot());
}
bonusCardControlMap.clear();
for (int i = 0; i < cardOrder.size(); i++) {
BonusCard card = cardOrder.get(i);
CardControl control = createCard(bonusToAsset(card), nextPos(i));
control.setNumCard(bonusCardIntegerMap.get(card));
cardLayer.addSpatial(control.getRoot());
bonusCardControlMap.put(card, control);
}
}
/**
* Sets the selectable cards.
*
* @param select the list of cards to set as selectable
*/
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();
control.selectableOn();
}
}
/**
* Selects a card control.
*
* @param cardControl the card control to select
*/
public void selectCard(CardControl cardControl) {
if (cardControl.isSelected()) {
cardControl.unSelect();
cardControl.selectOff();
cardSelect = null;
} else {
for (CardControl control : selectableCards) {
control.unSelect();
control.selectOff();
}
cardControl.select();
cardControl.selectOn();
cardSelect = getKeyByValue(bonusCardControlMap, cardControl);
}
app.getModelSynchronize().selectCard(cardSelect);
}
/**
* Gets the card layer camera.
*
* @return the card layer camera
*/
public Camera getCardLayerCamera() {
return cardLayerCamera;
}
public void shield(){
/**
* Adds a shield symbol to the card layer.
*/
public void shield() {
SymbolControl control = createSymbol(Asset.shieldSymbol);
cardLayer.addSpatial(control.getSpatial());
control.shield();
}
public void swap(){
/**
* Adds a swap symbol to the card layer.
*/
public void swap() {
SymbolControl control = createSymbol(Asset.swapSymbol);
cardLayer.addSpatial(control.getSpatial());
control.swap();
}
public void turbo(){
/**
* Adds a turbo symbol to the card layer.
*/
public void turbo() {
SymbolControl control = createSymbol(Asset.turboSymbol);
cardLayer.addSpatial(control.getSpatial());
control.turbo();
}
/**
* Converts a bonus card to its corresponding asset.
*
* @param card the bonus card
* @return the corresponding asset
*/
private Asset bonusToAsset(BonusCard card) {
return switch (card) {
case TURBO -> Asset.turboCard;
@@ -166,10 +254,21 @@ private Asset bonusToAsset(BonusCard card) {
};
}
private Vector3f nextPos() {
return START.add(MARGIN.mult(bonusCardControlMap.size()));
/**
* Calculates the next position for a card.
*
* @param i the index of the card
* @return the next position vector
*/
private Vector3f nextPos(int i) {
return START.add(MARGIN.mult(i));
}
/**
* Creates an overlay camera for the card layer.
*
* @return the created overlay camera
*/
private Camera createOverlayCam() {
Camera originalCam = app.getCamera();
Camera overlayCam = new Camera(originalCam.getWidth(), originalCam.getHeight());
@@ -182,6 +281,15 @@ private Camera createOverlayCam() {
return overlayCam;
}
/**
* Gets the key associated with a value in a map.
*
* @param map the map to search
* @param value the value to find the key for
* @param <K> the type of keys in the map
* @param <V> the type of values in the map
* @return the key associated with the value, or null if not found
*/
private <K, V> K getKeyByValue(Map<K, V> map, V value) {
for (Map.Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
@@ -191,6 +299,9 @@ private <K, V> K getKeyByValue(Map<K, V> map, V value) {
return null;
}
/**
* Test method to add sample cards to the card layer.
*/
public void test() {
addCard(BonusCard.SHIELD);
addCard(BonusCard.SHIELD);
@@ -198,7 +309,14 @@ public void test() {
addCard(BonusCard.SWAP);
}
private CardControl createCard(Asset card, Vector3f pos){
/**
* Creates a card control for a given asset and position.
*
* @param card the asset representing the card
* @param pos the position to place the card
* @return the created card control
*/
private CardControl createCard(Asset card, Vector3f pos) {
Node rootCard = new Node("Root Card");
Spatial spatial = app.getAssetManager().loadModel(card.getModelPath());
rootCard.attachChild(spatial);
@@ -207,26 +325,37 @@ private CardControl createCard(Asset card, Vector3f pos){
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
rootCard.setLocalTranslation(pos);
spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
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){
/**
* Creates a symbol control for a given asset.
*
* @param asset the asset representing the symbol
* @return the created symbol 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));
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(){
/**
* Gets the card layer.
*
* @return the card layer
*/
public CardLayer getCardLayer() {
return cardLayer;
}
}

View File

@@ -11,16 +11,18 @@
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import java.util.Random;
import pp.mdga.client.MdgaApp;
import static com.jme3.material.Materials.LIGHTING;
import static com.jme3.material.Materials.UNSHADED;
/**
* DiceControl class handles the rolling and spinning behavior of a dice in the game.
*/
public class DiceControl extends AbstractControl {
private Quaternion targetRotation;
private final Vector3f angularVelocity = new Vector3f();
private float deceleration = 0.5f;
private float deceleration = 1f;
private float timeElapsed = 0.0f;
private float rollDuration = 1f;
private static final int ANGULAR_MIN = 5;
@@ -32,31 +34,39 @@ public class DiceControl extends AbstractControl {
private final AssetManager assetManager;
private Runnable actionAfter;
public DiceControl(AssetManager assetManager){
/**
* Constructor for DiceControl.
*
* @param assetManager the asset manager to load models and textures
*/
public DiceControl(AssetManager assetManager) {
this.assetManager = assetManager;
}
/**
* Updates the control each frame.
*
* @param tpf time per frame
*/
@Override
protected void controlUpdate(float tpf) {
float clampedTpf = Math.min(tpf, 0.05f); // Max 50 ms per frame
if (isRolling) {
if(!slerp) {
if (!slerp) {
// Apply rotational velocity to the dice
spinWithAngularVelocity(tpf);
spinWithAngularVelocity(clampedTpf);
// Gradually reduce rotational velocity (simulate deceleration)
angularVelocity.subtractLocal(
angularVelocity.mult(deceleration * tpf)
angularVelocity.mult(deceleration * clampedTpf)
);
// Stop rolling when angular velocity is close to zero
if (angularVelocity.lengthSquared() < 3f) {
if (angularVelocity.lengthSquared() <= 3f || MdgaApp.DEBUG_MULTIPLIER == 0) {
slerp = true;
}
}
else {
timeElapsed += tpf * rollDuration;
} else {
timeElapsed += clampedTpf * rollDuration;
if (timeElapsed > 1.0f) timeElapsed = 1.0f;
Quaternion interpolated = spatial.getLocalRotation().clone();
@@ -64,18 +74,23 @@ protected void controlUpdate(float tpf) {
spatial.setLocalRotation(interpolated);
// Stop rolling once duration is complete
if (timeElapsed >= 1.0f) {
if (timeElapsed >= 1.0f * MdgaApp.DEBUG_MULTIPLIER) {
isRolling = false;
slerp = false;
actionAfter.run();
}
}
}else if(spin){
spinWithAngularVelocity(tpf);
} else if (spin) {
spinWithAngularVelocity(clampedTpf);
}
}
private void spinWithAngularVelocity(float tpf){
/**
* Applies rotational velocity to the dice.
*
* @param tpf time per frame
*/
private void spinWithAngularVelocity(float tpf) {
Quaternion currentRotation = spatial.getLocalRotation();
Quaternion deltaRotation = new Quaternion();
deltaRotation.fromAngles(
@@ -86,20 +101,32 @@ private void spinWithAngularVelocity(float tpf){
spatial.setLocalRotation(currentRotation.mult(deltaRotation));
}
/**
* Renders the control.
*
* @param rm the render manager
* @param vp the viewport
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
/**
* Initiates the dice roll.
*
* @param diceNum the number on the dice to roll to
* @param actionAfter the action to perform after the roll
*/
public void rollDice(int diceNum, Runnable actionAfter) {
if (isRolling) return;
spin = false;
slerp = false;
timeElapsed = 0;
this.actionAfter = actionAfter;
angularVelocity.set(
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX)
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX)
);
targetRotation = getRotationForDiceNum(diceNum);
@@ -107,6 +134,12 @@ public void rollDice(int diceNum, Runnable actionAfter) {
isRolling = true;
}
/**
* Gets the target rotation for a given dice number.
*
* @param diceNum the number on the dice
* @return the target rotation as a Quaternion
*/
private Quaternion getRotationForDiceNum(int diceNum) {
return switch (diceNum) {
case 1 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_X);
@@ -119,10 +152,19 @@ private Quaternion getRotationForDiceNum(int diceNum) {
};
}
/**
* Linear interpolation function.
*
* @param t the interpolation factor
* @return the interpolated value
*/
public static float lerp(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
/**
* Sets a random rotation for the dice.
*/
public void randomRotation() {
Quaternion randomRotation = new Quaternion();
randomRotation.fromAngles(
@@ -133,38 +175,55 @@ public void randomRotation() {
spatial.setLocalRotation(randomRotation);
}
public void spin(){
angularVelocity.set(ANGULAR_SPIN,ANGULAR_SPIN,ANGULAR_SPIN);
/**
* Initiates the dice spin.
*/
public void spin() {
angularVelocity.set(ANGULAR_SPIN, ANGULAR_SPIN, ANGULAR_SPIN);
spin = true;
}
public void hide(){
/**
* Hides the dice by removing it from the parent node.
*/
public void hide() {
spatial.removeFromParent();
spin = false;
isRolling = false;
slerp = false;
}
public void create(Vector3f pos, float scale, boolean shadow){
/**
* Creates the dice model and sets its initial properties.
*
* @param pos the position to place the dice
* @param scale the scale of the dice
* @param shadow whether the dice should cast and receive shadows
*/
public void create(Vector3f pos, float scale, boolean shadow) {
Spatial spatial = assetManager.loadModel(Asset.dice.getModelPath());
Material mat;
if(shadow){
if (shadow) {
mat = new Material(assetManager, LIGHTING);
mat.setTexture("DiffuseMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
}
else{
} 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.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
spatial.addControl(this);
}
public void setPos(Vector3f pos){
/**
* Sets the position of the dice.
*
* @param pos the new position
*/
public void setPos(Vector3f pos) {
spatial.setLocalTranslation(pos);
}

View File

@@ -6,11 +6,14 @@
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import java.util.List;
/**
* Handles the GUI elements and interactions for the game.
*/
public class GuiHandler {
private final MdgaApp app;
private final CardLayerHandler cardLayerHandler;
@@ -20,6 +23,12 @@ public class GuiHandler {
private FrameBuffer backFrameBuffer;
/**
* Constructs a new GuiHandler.
*
* @param app the application instance
* @param guiNode the GUI node
*/
public GuiHandler(MdgaApp app, Node guiNode) {
this.app = app;
this.ownColor = ownColor;
@@ -34,6 +43,11 @@ public GuiHandler(MdgaApp app, Node guiNode) {
actionTextHandler = new ActionTextHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
}
/**
* Initializes the GUI handler with the player's color.
*
* @param ownColor the player's color
*/
public void init(Color ownColor) {
cardLayerHandler.init();
playerNameHandler.show();
@@ -41,59 +55,108 @@ public void init(Color ownColor) {
app.getViewPort().setOutputFrameBuffer(backFrameBuffer);
}
/**
* Shuts down the GUI handler.
*/
public void shutdown() {
cardLayerHandler.shutdown();
app.getViewPort().setOutputFrameBuffer(null);
}
/**
* Rolls the dice and handles the result.
*
* @param rollNum the number rolled
* @param mult the multiplier
*/
public void rollDice(int rollNum, int mult) {
cardLayerHandler.rollDice(rollNum, ()->{
if(mult == -1) actionTextHandler.ownDice(rollNum);
cardLayerHandler.rollDice(rollNum, () -> {
if (mult == -1) actionTextHandler.ownDice(rollNum);
else actionTextHandler.ownDiceMult(rollNum, mult);
hideDice();
app.getModelSynchronize().animationEnd();
app.getModelSynchronize().animationEnd();
});
}
/**
* Shows the rolled dice with a multiplier for a specific player.
*
* @param rollNum the number rolled
* @param mult the multiplier
* @param color the player's color
*/
public void showRolledDiceMult(int rollNum, int mult, Color color) {
String name = playerNameHandler.getName(color);
if(mult == -1) actionTextHandler.diceNum(rollNum, name, color);
if (mult == -1) actionTextHandler.diceNum(rollNum, name, color);
else actionTextHandler.diceNumMult(rollNum, mult, name, color);
}
/**
* Shows the rolled dice for a specific player.
*
* @param rollNum the number rolled
* @param color the player's color
*/
public void showRolledDice(int rollNum, Color color) {
actionTextHandler.diceNum(rollNum, playerNameHandler.getName(color), color);
}
/**
* Displays the dice on the screen.
*/
public void showDice() {
cardLayerHandler.showDice();
actionTextHandler.diceNow();
}
/**
* Hides the dice from the screen.
*/
public void hideDice() {
cardLayerHandler.hideDice();
}
//add own handCard
/**
* Adds a card to the player's hand.
*
* @param card the card to add
*/
public void addCardOwn(BonusCard card) {
cardLayerHandler.addCard(card);
playerNameHandler.addCard(ownColor);
actionTextHandler.drawCardOwn(ownColor);
}
public void playCardOwn(BonusCard card){
/**
* Plays a card from the player's hand.
*
* @param card the card to play
*/
public void playCardOwn(BonusCard card) {
getEffectByCard(card);
cardLayerHandler.removeCard(card);
playerNameHandler.removeCard(ownColor);
}
/**
* Plays a card from an enemy player's hand.
*
* @param color the enemy player's color
* @param card the card to play
*/
public void playCardEnemy(Color color, BonusCard card) {
getEffectByCard(card);
playerNameHandler.removeCard(color);
}
private void getEffectByCard(BonusCard bonus){
switch(bonus){
/**
* Gets the effect of a card and applies it.
*
* @param bonus the card to get the effect from
*/
private void getEffectByCard(BonusCard bonus) {
switch (bonus) {
case SWAP -> swap();
case TURBO -> turbo();
case SHIELD -> shield();
@@ -101,30 +164,64 @@ private void getEffectByCard(BonusCard bonus){
}
}
/**
* Clears all selectable cards.
*/
public void clearSelectableCards() {
cardLayerHandler.clearSelectableCards();
}
/**
* Sets the selectable cards.
*
* @param select the list of selectable cards
*/
public void setSelectableCards(List<BonusCard> select) {
cardLayerHandler.setSelectableCards(select);
}
/**
* Selects a card.
*
* @param cardControl the card control to select
*/
public void selectCard(CardControl cardControl) {
cardLayerHandler.selectCard(cardControl);
}
/**
* Gets the camera for the card layer.
*
* @return the card layer camera
*/
public Camera getCardLayerCamera() {
return cardLayerHandler.getCardLayerCamera();
}
public Node getCardLayerRootNode(){
/**
* Gets the root node for the card layer.
*
* @return the card layer root node
*/
public Node getCardLayerRootNode() {
return cardLayerHandler.getCardLayer().getRootNode();
}
/**
* Adds a player to the game.
*
* @param color the player's color
* @param name the player's name
*/
public void addPlayer(Color color, String name) {
playerNameHandler.addPlayer(color, name, color == ownColor);
}
/**
* Sets the active player.
*
* @param color the active player's color
*/
public void setActivePlayer(Color color) {
playerNameHandler.setActivePlayer(color);
@@ -132,40 +229,63 @@ public void setActivePlayer(Color color) {
else actionTextHandler.activePlayer(playerNameHandler.getName(color), color);
}
public void shield(){
/**
* Applies the shield effect.
*/
public void shield() {
cardLayerHandler.shield();
}
public void swap(){
/**
* Applies the swap effect.
*/
public void swap() {
cardLayerHandler.swap();
}
public void turbo(){
/**
* Applies the turbo effect.
*/
public void turbo() {
cardLayerHandler.turbo();
}
public void hideText(){
/**
* Hides the action text.
*/
public void hideText() {
actionTextHandler.hide();
}
//addCard Enemy (DrawCardNotification)
/**
* Draws a card for an enemy player.
*
* @param color the enemy player's color
*/
public void drawCard(Color color) {
//Color != ownColor
actionTextHandler.drawCard(playerNameHandler.getName(color), color);
playerNameHandler.addCard(color);
}
public void finish(Color color){
if(ownColor == color) actionTextHandler.finishTextOwn(color);
/**
* Displays the finish text for a player.
*
* @param color the player's color
*/
public void finish(Color color) {
if (ownColor == color) actionTextHandler.finishTextOwn(color);
else actionTextHandler.finishText(playerNameHandler.getName(color), color);
}
public void rollRankingResult(Color color, int eye){
if(ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
/**
* Displays the ranking result for a player.
*
* @param color the player's color
* @param eye the ranking result
*/
public void rollRankingResult(Color color, int eye) {
if (ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
else actionTextHandler.rollRankingResult(playerNameHandler.getName(color), color, eye);
}
}

View File

@@ -4,20 +4,20 @@
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.ui.Picture;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* Handles the display and management of player names and their associated data.
*/
public class PlayerNameHandler {
private final BitmapFont playerFont;
private final Node playerNameNode;
@@ -40,7 +40,14 @@ public class PlayerNameHandler {
private final Node guiNode;
public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
/**
* Constructs a PlayerNameHandler.
*
* @param guiNode the GUI node to attach player names to
* @param assetManager the asset manager to load resources
* @param appSettings the application settings
*/
public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
this.guiNode = guiNode;
playerFont = assetManager.loadFont("Fonts/Gunplay.fnt");
@@ -52,120 +59,187 @@ public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings ap
this.assetManager = assetManager;
}
/**
* Shows the player names on the GUI.
*/
public void show() {
guiNode.attachChild(playerNameNode);
}
/**
* Hides the player names from the GUI.
*/
public void hide() {
guiNode.detachChild(playerNameNode);
}
private void drawPlayers(){
/**
* Draws the player names and their associated data on the GUI.
*/
private void drawPlayers() {
playerNameNode.detachAllChildren();
for(int i = 0; i < playerOrder.size(); i++) {
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");
if (!colorNameMap.containsKey(color)) throw new RuntimeException(color + " isn't mapped to a name");
Node nameParent = new Node("nameParent");
nameParent.attachChild(createColor(color));
BitmapText name = createName(colorNameMap.get(color), i == 0, color == ownColor);
nameParent.attachChild(name);
if(colorCardMap.getOrDefault(color, 0) > 0){
if (colorCardMap.getOrDefault(color, 0) > 0) {
Picture pic = createHandCard(name.getLineWidth());
nameParent.attachChild(pic);
nameParent.attachChild(createCardNum(colorCardMap.get(color), pic.getWidth(), pic.getLocalTranslation().getX()));
}
nameParent.setLocalTranslation(50,appSettings.getWindowHeight()-PADDING_TOP- MARGIN_NAMES *i,0);
nameParent.setLocalTranslation(50, appSettings.getWindowHeight() - PADDING_TOP - MARGIN_NAMES * i, 0);
playerNameNode.attachChild(nameParent);
}
}
private Spatial createCardNum(int num, float lastWidth, float lastX ) {
/**
* Creates a BitmapText object to display the number of cards a player has.
*
* @param num the number of cards
* @param lastWidth the width of the last element
* @param lastX the x position of the last element
* @return a BitmapText object displaying the number of cards
*/
private Spatial createCardNum(int num, float lastWidth, float lastX) {
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(NORMAL_COLOR);
hudText.setText(String.valueOf(num));
hudText.setLocalTranslation(lastX + lastWidth + 20,hudText.getHeight()/2, 0);
hudText.setLocalTranslation(lastX + lastWidth + 20, hudText.getHeight() / 2, 0);
return hudText;
}
/**
* Creates a Picture object to display a hand card image.
*
* @param width the width of the previous element
* @return a Picture object displaying a hand card image
*/
private Picture createHandCard(float width) {
Picture pic = new Picture("HUD Picture");
pic.setImage(assetManager, "./Images/handcard.png", true);
pic.setWidth(IMAGE_SIZE);
pic.setHeight(IMAGE_SIZE);
pic.setPosition(-pic.getWidth()/2 + width + PADDING_LEFT * 2 ,-pic.getHeight()/2);
pic.setPosition(-pic.getWidth() / 2 + width + PADDING_LEFT * 2, -pic.getHeight() / 2);
return pic;
}
private String imagePath(Color color){
/**
* Returns the image path for a given color.
*
* @param color the color to get the image path for
* @return the image path for the given color
*/
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";
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");
};
}
/**
* Creates a Picture object to display a color image.
*
* @param color the color to create the image for
* @return a Picture object displaying the color image
*/
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);
pic.setPosition(-pic.getWidth() / 2, -pic.getHeight() / 2);
return pic;
}
private BitmapText createName(String name, boolean first, boolean own){
/**
* Creates a BitmapText object to display a player's name.
*
* @param name the player's name
* @param first whether the player is the first in the list
* @param own whether the player is the current user
* @return a BitmapText object displaying the player's name
*/
private BitmapText createName(String name, boolean first, boolean own) {
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(first ? ACTIVE_COLOR : own ? OWN_COLOR : NORMAL_COLOR);
hudText.setText(name);
hudText.setLocalTranslation(PADDING_LEFT,hudText.getHeight()/2, 0);
hudText.setText(own ? name + " (Du)" : name);
hudText.setLocalTranslation(PADDING_LEFT, hudText.getHeight() / 2, 0);
return hudText;
}
public void addPlayer(Color color, String name, boolean own){
if(own) ownColor = color;
/**
* Adds a player to the handler.
*
* @param color the color associated with the player
* @param name the name of the player
* @param own whether the player is the current user
*/
public void addPlayer(Color color, String name, boolean own) {
if (own) ownColor = color;
colorNameMap.put(color, name);
playerOrder.add(color);
drawPlayers();
}
/**
* Sets the active player.
*
* @param color the color associated with the active player
*/
public void setActivePlayer(Color color) {
Color lastFirst = playerOrder.remove(0);
if (playerOrder.get(0) == color) return;
Color oldFirst = playerOrder.remove(0);
playerOrder.remove(color);
playerOrder.add(0, color);
playerOrder.add(lastFirst);
playerOrder.add(oldFirst);
drawPlayers();
}
public String getName(Color color){
if(!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
/**
* Gets the name of a player by their color.
*
* @param color the color associated with the player
* @return the name of the player
*/
public String getName(Color color) {
if (!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
return colorNameMap.get(color);
}
public void addCard(Color color){
/**
* Adds a card to a player.
*
* @param color the color associated with the player
*/
public void addCard(Color color) {
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) + 1);
drawPlayers();
}
public void removeCard(Color color){
if(colorCardMap.containsKey(color)){
/**
* Removes a card from a player.
*
* @param color the color associated with the player
*/
public void removeCard(Color color) {
if (colorCardMap.containsKey(color)) {
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) - 1);
if(colorCardMap.get(color) <= 0) colorCardMap.remove(color);
if (colorCardMap.get(color) <= 0) colorCardMap.remove(color);
}
drawPlayers();
}
}

View File

@@ -11,69 +11,129 @@
import com.jme3.texture.FrameBuffer;
/**
* OutlineFilter is a custom filter for rendering outlines around objects.
*/
public class OutlineFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
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;
}
/**
* Constructs an OutlineFilter with the specified OutlinePreFilter.
*
* @param outlinePreFilter the pre-filter used for outlining
*/
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);
}
/**
* Initializes the filter with the given parameters.
*
* @param assetManager the asset manager
* @param renderManager the render manager
* @param vp the viewport
* @param w the width of the viewport
* @param h the height of the viewport
*/
@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());
/**
* Called before each frame is rendered.
*
* @param tpf time per frame
*/
@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);
/**
* Called after each frame is rendered.
*
* @param renderManager the render manager
* @param viewPort the viewport
* @param prevFilterBuffer the previous filter buffer
* @param sceneBuffer the scene buffer
*/
@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;
}
/**
* Returns the material used by this filter.
*
* @return the material
*/
@Override
protected Material getMaterial() {
return material;
}
public ColorRGBA getOutlineColor() {
return outlineColor;
}
/**
* Returns the outline color.
*
* @return the outline color
*/
public ColorRGBA getOutlineColor() {
return outlineColor;
}
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
/**
* Sets the outline color.
*
* @param outlineColor the new outline color
*/
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
public float getOutlineWidth() {
return outlineWidth;
}
/**
* Returns the outline width.
*
* @return the outline width
*/
public float getOutlineWidth() {
return outlineWidth;
}
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
/**
* Sets the outline width.
*
* @param outlineWidth the new outline width
*/
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
/**
* Returns the OutlinePreFilter used by this filter.
*
* @return the OutlinePreFilter
*/
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -12,56 +12,100 @@
import com.jme3.texture.Texture;
/**
* OutlinePreFilter is a custom filter used to apply an outline effect to objects.
*/
public class OutlinePreFilter extends Filter {
private Pass normalPass;
private RenderManager renderManager;
private Pass normalPass;
private RenderManager renderManager;
/**
* Creates a OutlinePreFilter
*/
public OutlinePreFilter() {
super("OutlinePreFilter");
}
/**
* Creates an OutlinePreFilter.
*/
public OutlinePreFilter() {
super("OutlinePreFilter");
}
@Override
protected boolean isRequiresDepthTexture() {
return true;
}
/**
* Indicates that this filter requires a depth texture.
*
* @return true, indicating that a depth texture is required.
*/
@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);
}
/**
* Called after the render queue is processed.
*
* @param queue the render queue.
*/
@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);
/**
* Called after the frame is rendered.
*
* @param renderManager the render manager.
* @param viewPort the viewport.
* @param prevFilterBuffer the previous filter buffer.
* @param sceneBuffer the scene buffer.
*/
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
}
}
/**
* Returns the material used by this filter.
*
* @return the material.
*/
@Override
protected Material getMaterial() {
return material;
}
@Override
protected Material getMaterial() {
return material;
}
/**
* Returns the texture containing the outline.
*
* @return the outline texture.
*/
public Texture getOutlineTexture() {
return normalPass.getRenderedTexture();
}
public Texture getOutlineTexture() {
return normalPass.getRenderedTexture();
}
/**
* Initializes the filter.
*
* @param manager the asset manager.
* @param renderManager the render manager.
* @param vp the viewport.
* @param w the width.
* @param h the height.
*/
@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 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);
}
/**
* Cleans up the filter.
*
* @param r the renderer.
*/
@Override
protected void cleanUpFilter(Renderer r) {
normalPass.cleanup(r);
}
}

View File

@@ -10,17 +10,34 @@
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
/**
* OutlineProFilter is a custom filter for rendering outlines around objects.
*/
public class OutlineProFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
/**
* Constructs an OutlineProFilter with the specified OutlinePreFilter.
*
* @param outlinePreFilter the pre-filter used for outlining
*/
public OutlineProFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
/**
* Initializes the filter with the given parameters.
*
* @param assetManager the asset manager
* @param renderManager the render manager
* @param vp the viewport
* @param w the width of the viewport
* @param h the height of the viewport
*/
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/OutlinePro.j3md");
@@ -30,29 +47,54 @@ protected void initFilter(AssetManager assetManager, RenderManager renderManager
material.setFloat("OutlineWidth", outlineWidth);
}
/**
* Called before rendering each frame.
*
* @param tpf time per frame
*/
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
// System.out.println("OutlineFilter.preFrame()");
}
/**
* Called after rendering each frame.
*
* @param renderManager the render manager
* @param viewPort the viewport
* @param prevFilterBuffer the previous filter buffer
* @param sceneBuffer the scene buffer
*/
@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()");
}
/**
* Returns the material used by this filter.
*
* @return the material
*/
@Override
protected Material getMaterial() {
return material;
}
/**
* Returns the outline color.
*
* @return the outline color
*/
public ColorRGBA getOutlineColor() {
return outlineColor;
}
/**
* Sets the outline color.
*
* @param outlineColor the new outline color
*/
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
@@ -60,10 +102,20 @@ public void setOutlineColor(ColorRGBA outlineColor) {
}
}
/**
* Returns the outline width.
*
* @return the outline width
*/
public float getOutlineWidth() {
return outlineWidth;
}
/**
* Sets the outline width.
*
* @param outlineWidth the new outline width
*/
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
@@ -71,9 +123,13 @@ public void setOutlineWidth(float outlineWidth) {
}
}
/**
* Returns the OutlinePreFilter.
*
* @return the OutlinePreFilter
*/
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -9,80 +9,113 @@
import com.jme3.scene.Spatial;
import pp.mdga.client.MdgaApp;
/**
* This class is responsible for outlining selected objects in the scene.
*/
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 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) {
/**
* Constructor for SelectObjectOutliner.
*
* @param fpp the FilterPostProcessor
* @param renderManager the RenderManager
* @param assetManager the AssetManager
* @param cam the Camera
* @param app the MdgaApp instance
*/
public SelectObjectOutliner(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;
}
/**
* Deselects the currently selected object, removing the outline effect.
*
* @param model the Spatial model to deselect
*/
public void deselect(Spatial model) {
if(selected){
if (selected) {
selected = false;
hideOutlineFilterEffect(model);
}
}
public void select(Spatial model, ColorRGBA color) {
if(!selected){
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
// /**
// * Selects an object and applies an outline effect.
// *
// * @param model the Spatial model to select
// * @param color the ColorRGBA for the outline
// */
// public void select(Spatial model, ColorRGBA color) {
// if (!selected) {
// selected = true;
// showOutlineFilterEffect(model, width, color);
// }
// }
/**
* Selects an object and applies an outline effect.
*
* @param model the Spatial model to select
* @param color the ColorRGBA for the outline
* @param width the width of the outline
*/
public void select(Spatial model, ColorRGBA color, int width) {
if(!selected){
if (!selected) {
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
/**
* Hides the outline effect from the selected object.
*
* @param model the Spatial model to hide the outline effect from
*/
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;
// });
outlineFilter.setEnabled(false);
outlineFilter.getOutlinePreFilter().setEnabled(false);
fpp.removeFilter(outlineFilter);
outlineViewport.detachScene(model);
outlineViewport.clearProcessors();
renderManager.removePreView(outlineViewport);
outlineViewport = null;
}
/**
* Shows the outline effect on the selected object.
*
* @param model the Spatial model to show the outline effect on
* @param width the width of the outline
* @param color the ColorRGBA for the outline
*/
private void showOutlineFilterEffect(Spatial model, int width, ColorRGBA color) {
// app.enqueue(() -> {
outlineViewport = renderManager.createPreView("outlineViewport", cam);
FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
outlineViewport = renderManager.createPreView("outlineViewport", cam);
FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
outlineFpp.addFilter(outlinePreFilter);
OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
outlineFpp.addFilter(outlinePreFilter);
outlineViewport.attachScene(model);
outlineViewport.addProcessor(outlineFpp);
outlineViewport.attachScene(model);
outlineViewport.addProcessor(outlineFpp);
outlineFilter = new OutlineProFilter(outlinePreFilter);
outlineFilter.setOutlineColor(color);
outlineFilter.setOutlineWidth(width);
outlineFilter = new OutlineProFilter(outlinePreFilter);
outlineFilter.setOutlineColor(color);
outlineFilter.setOutlineWidth(width);
fpp.addFilter(outlineFilter);
// return null;
// });
fpp.addFilter(outlineFilter);
}
}

View File

@@ -15,6 +15,9 @@
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
@@ -31,6 +34,7 @@ public class MdgaServer implements MessageListener<HostedConnection>, Connection
private static int port;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
private volatile boolean running = true;
static {
// Configure logging
@@ -55,21 +59,23 @@ public MdgaServer(int port) {
}
/**
*
* Main method to start the server.
*/
public void run() {
startServer();
while (true)
while (running) {
processNextMessage();
}
shutdownServerResources();
}
/**
*
* Starts the server and initializes listeners.
*/
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
LOGGER.log(Level.INFO, "Starting server...");
unlockSerializers();//NON-NLS
myServer = Network.createServer(port);
initializeSerializables();
myServer.start();
@@ -77,20 +83,28 @@ private void startServer() {
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);
exit();
}
}
/**
* Process the next message in the queue.
*/
private void processNextMessage() {
try {
pendingMessages.take().process(logic);
ReceivedMessage message = pendingMessages.take(); // This is a blocking call
message.process(logic);
} catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
LOGGER.log(Level.INFO, "Server thread interrupted, shutting down..."); //NON-NLS
Thread.currentThread().interrupt();
}
}
/**
* Register serializable classes.
*/
private void initializeSerializables() {
Serializer.registerClass(UUID.class, new UUIDSerializer());
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(ClientStartGameMessage.class);
@@ -108,6 +122,7 @@ private void initializeSerializables() {
Serializer.registerClass(RequestPlayCardMessage.class);
Serializer.registerClass(SelectCardMessage.class);
Serializer.registerClass(SelectedPiecesMessage.class);
Serializer.registerClass(SelectPieceMessage.class);
Serializer.registerClass(SelectTSKMessage.class);
Serializer.registerClass(ActivePlayerMessage.class);
Serializer.registerClass(AnyPieceMessage.class);
@@ -138,6 +153,7 @@ private void initializeSerializables() {
Serializer.registerClass(UpdateTSKMessage.class);
Serializer.registerClass(WaitPieceMessage.class);
Serializer.registerClass(IncorrectRequestMessage.class);
Serializer.registerClass(SpectatorMessage.class);
Serializer.registerClass(Player.class);
Serializer.registerClass(Statistic.class);
Serializer.registerClass(Board.class);
@@ -151,6 +167,8 @@ private void initializeSerializables() {
Serializer.registerClass(SwapCard.class);
Serializer.registerClass(ShieldCard.class);
Serializer.registerClass(HiddenCard.class);
Serializer.registerClass(ChoosePieceStateMessage.class);
Serializer.registerClass(DrawCardMessage.class);
Serializer.registerClass(Color.class, new EnumSerializer());
Serializer.registerClass(PieceState.class, new EnumSerializer());
@@ -158,6 +176,9 @@ private void initializeSerializables() {
Serializer.registerClass(BonusCard.class, new EnumSerializer());
}
/**
* Register listeners for the server.
*/
private void registerListeners() {
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addMessageListener(this, ClientStartGameMessage.class);
@@ -204,7 +225,7 @@ public void messageReceived(HostedConnection source, Message message) {
* @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
System.out.println("server received from: " + source.getId() + " " + message.getClass().getName());
pendingMessages.add(new ReceivedMessage(message, source.getId()));
}
@@ -258,12 +279,12 @@ 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);
/**
* Stops the server thread gracefully.
*/
public void exit() {
LOGGER.log(Level.INFO, "Requesting server shutdown"); //NON-NLS
running = false;
}
/**
@@ -279,10 +300,10 @@ public void send(int id, ServerMessage message) {
return;
}
final HostedConnection connection = myServer.getConnection(id);
if (connection != null)
if (connection != null) {
System.out.println("server sends to: " + id + " " + message.getClass().getName());
connection.send(message);
else
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
} else LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
}
/**
@@ -305,7 +326,11 @@ public void broadcast(ServerMessage message) {
*/
@Override
public void disconnectClient(int id) {
this.myServer.getConnection(id).close("");
if (myServer.getConnection(id) != null) {
this.myServer.getConnection(id).close("");
} else {
LOGGER.log(Level.ERROR, "no connection with id={0}", id); //NON-NLS
}
}
/**
@@ -315,13 +340,33 @@ public void disconnectClient(int id) {
*/
@Override
public void shutdown() {
for (HostedConnection client : this.myServer.getConnections()) {
if (client != null) {
client.close("Host closed the server.");
}
}
this.exit();
}
this.myServer.close();
this.exit(0);
/**
* Gracefully shutdown server resources like connections and sockets.
*/
private void shutdownServerResources() {
LOGGER.log(Level.INFO, "Shutting down server resources"); //NON-NLS
if (myServer != null && myServer.isRunning()) {
for (HostedConnection client : myServer.getConnections()) {
if (client != null) client.close("Server shutting down.");
}
myServer.close();
}
}
/**
* This method will be used to unlock the Serializer registry.
*/
private static void unlockSerializers() {
try {
Field lockField = Serializer.class.getDeclaredField("locked");
lockField.setAccessible(true);
lockField.setBoolean(null, false); // Unlock the Serializer registry
} catch (NoSuchFieldException | IllegalAccessException e) {
System.err.println("Failed to unlock the Serializer registry: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -3,7 +3,16 @@
import pp.mdga.message.client.ClientInterpreter;
import pp.mdga.message.client.ClientMessage;
/**
* Represents a message received from the server.
*/
public record ReceivedMessage(ClientMessage msg, int from) {
/**
* Processes the received message using the specified client interpreter.
*
* @param interpreter the client interpreter to use for processing the message
*/
void process(ClientInterpreter interpreter) {
msg.accept(interpreter, from);
}

View File

@@ -1,26 +1,52 @@
package pp.mdga.client.server;
import com.jme3.network.serializing.Serializer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
import com.jme3.network.serializing.Serializer;
/**
* Serializer for UUID objects to be used with jME3 networking.
*/
public class UUIDSerializer extends Serializer {
public class UUIDSerializer extends Serializer
{
/**
* Reads a UUID object from the given ByteBuffer.
*
* @param data the ByteBuffer containing the serialized UUID
* @param c the class type of the object to be read
* @param <T> the type of the object to be read
* @return the deserialized UUID object, or null if the UUID is a placeholder
* @throws IOException if an I/O error occurs
*/
@Override
public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException
{
byte[] uuid = new byte[36];
data.get(uuid);
public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
byte[] uuid = new byte[36];
data.get(uuid);
return (T) UUID.fromString(new String(uuid));
}
if (uuid.equals(new UUID(1, 1))) {
return null;
} else {
return (T) UUID.fromString(new String(uuid));
}
}
/**
* Writes a UUID object to the given ByteBuffer.
*
* @param buffer the ByteBuffer to write the serialized UUID to
* @param object the UUID object to be serialized
* @throws IOException if an I/O error occurs
*/
@Override
public void writeObject(ByteBuffer buffer, Object object) throws IOException
{
public void writeObject(ByteBuffer buffer, Object object) throws IOException {
UUID uuid = (UUID) object;
buffer.put(uuid.toString().getBytes());
if (uuid != null) {
buffer.put(uuid.toString().getBytes());
} else {
buffer.put(new UUID(1, 1).toString().getBytes());
}
}
}

View File

@@ -19,7 +19,13 @@
import java.util.ArrayList;
/**
* CeremonyView class handles the display and interaction of the ceremony view in the application.
*/
public class CeremonyView extends MdgaView {
/**
* Enum representing the sub-states of the CeremonyView.
*/
private enum SubState {
AWARD_CEREMONY,
STATISTICS,
@@ -39,6 +45,11 @@ private enum SubState {
private AmbientLight ambient = new AmbientLight();
/**
* Constructor for CeremonyView.
*
* @param app The application instance.
*/
public CeremonyView(MdgaApp app) {
super(app);
@@ -52,6 +63,9 @@ public CeremonyView(MdgaApp app) {
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
}
/**
* Method called when entering the CeremonyView.
*/
@Override
public void onEnter() {
rootNode.addLight(ambient);
@@ -98,12 +112,15 @@ public void onEnter() {
enterSub(SubState.AWARD_CEREMONY);
}
/**
* Method called when leaving the CeremonyView.
*/
@Override
public void onLeave() {
backButton.hide();
continueButton.hide();
if(null != background) {
if (null != background) {
guiNode.detachChild(background);
}
@@ -116,18 +133,33 @@ public void onLeave() {
rootNode.detachChild(background);
}
/**
* Method called when entering an overlay.
*
* @param overlay The overlay being entered.
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
if(rootNode.hasChild(podest)) {
if (rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
}
}
/**
* Method called when leaving an overlay.
*
* @param overlay The overlay being left.
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
enterSub(state);
}
/**
* Method called to update the CeremonyView.
*
* @param tpf Time per frame.
*/
@Override
protected void onUpdate(float tpf) {
for (CeremonyButton c : ceremonyButtons) {
@@ -135,6 +167,9 @@ protected void onUpdate(float tpf) {
}
}
/**
* Handles the award ceremony sub-state.
*/
private void awardCeremony() {
continueButton.show();
@@ -145,6 +180,9 @@ private void awardCeremony() {
}
}
/**
* Handles the statistics sub-state.
*/
private void statistics() {
//background = createBackground("Images/b2.png");
//guiNode.attachChild(background);
@@ -154,10 +192,15 @@ private void statistics() {
ceremonyDialog.show();
}
/**
* Enters a sub-state of the CeremonyView.
*
* @param state The sub-state to enter.
*/
private void enterSub(SubState state) {
this.state = state;
if(rootNode.hasChild(podest)) {
if (rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
}
@@ -178,17 +221,23 @@ private void enterSub(SubState state) {
}
}
/**
* Handles the forward button action.
*/
public void forward() {
switch (state) {
case AWARD_CEREMONY:
enterSub(SubState.STATISTICS);
break;
case STATISTICS:
app.getModelSynchronize().enter(MdgaState.MAIN);
app.getModelSynchronize().next();
break;
}
}
/**
* Handles the back button action.
*/
private void back() {
switch (state) {
case AWARD_CEREMONY:
@@ -200,17 +249,35 @@ private void back() {
}
}
/**
* Adds a participant to the ceremony.
*
* @param color The color of the participant.
* @param pos The position of the participant.
* @param name The name of the participant.
*/
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)) {
if (state != null && state.equals(SubState.AWARD_CEREMONY)) {
button.hide();
button.show();
}
}
/**
* Adds a row of statistics.
*
* @param name The name of the row.
* @param v1 Value 1.
* @param v2 Value 2.
* @param v3 Value 3.
* @param v4 Value 4.
* @param v5 Value 5.
* @param v6 Value 6.
*/
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);
@@ -218,6 +285,9 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
ceremonyDialog.show();
}
/**
* Cleans up after the game.
*/
public void afterGameCleanup() {
ceremonyDialog.prepare();
}

View File

@@ -2,23 +2,26 @@
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.board.CameraHandler;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.dialog.InterruptDialog;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.game.Color;
/**
* Represents the view for the game.
*/
public class GameView extends MdgaView {
private BoardHandler boardHandler;
private CameraHandler camera;
private GuiHandler guiHandler;
private ButtonLeft leaveButton;
private ButtonRight confirmButton;
public ButtonRight confirmButton;
private ButtonRight noPowerButton;
@@ -28,12 +31,19 @@ public class GameView extends MdgaView {
private FilterPostProcessor fpp;
public boolean needConfirm = false;
public boolean needNoPower = false;
private Node guiHandlerNode = new Node();
/**
* Constructs a new GameView.
*
* @param app the application instance
*/
public GameView(MdgaApp app) {
super(app);
setOwnColor(Color.AIRFORCE);
leaveButton = new ButtonLeft(app, settingsNode, () -> app.getModelSynchronize().leave(), "Spiel verlassen", 1);
confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
@@ -51,6 +61,9 @@ public GameView(MdgaApp app) {
guiNode.attachChild(guiHandlerNode);
}
/**
* Called when entering the view.
*/
@Override
public void onEnter() {
camera.init(ownColor);
@@ -62,76 +75,140 @@ public void onEnter() {
app.getAcousticHandler().playSound(MdgaSound.START);
}
/**
* Called when leaving the view.
*/
@Override
public void onLeave() {
boardHandler.shutdown();
guiHandler.shutdown();
camera.shutdown();
confirmButton.hide();
noPowerButton.hide();
app.getViewPort().removeProcessor(fpp);
}
/**
* Called to update the view.
*
* @param tpf time per frame
*/
@Override
public void onUpdate(float tpf) {
camera.update(app.getInputSynchronize().getScroll(), app.getInputSynchronize().getRotation());
}
/**
* Called when entering an overlay.
*
* @param overlay the overlay being entered
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
if(overlay == Overlay.SETTINGS) {
if (overlay == Overlay.SETTINGS) {
leaveButton.show();
}
}
/**
* Called when leaving an overlay.
*
* @param overlay the overlay being left
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
if(overlay == Overlay.SETTINGS) {
if (overlay == Overlay.SETTINGS) {
leaveButton.hide();
}
}
/**
* Leaves the game.
*/
private void leaveGame() {
app.getModelSynchronize().leave();
}
/**
* Gets the board handler.
*
* @return the board handler
*/
public BoardHandler getBoardHandler() {
return boardHandler;
return boardHandler;
}
/**
* Gets the GUI handler.
*
* @return the GUI handler
*/
public GuiHandler getGuiHandler() {
return guiHandler;
}
/**
* Sets the player's color.
*
* @param ownColor the player's color
*/
public void setOwnColor(Color ownColor) {
this.ownColor = ownColor;
}
/**
* Gets the player's color.
*
* @return the player's color
*/
public Color getOwnColor() {
return ownColor;
}
/**
* Shows the confirm button and hides the no power button.
*/
public void needConfirm() {
noPowerButton.hide();
confirmButton.show();
needConfirm = true;
}
/**
* Hides the confirm button.
*/
public void noConfirm() {
confirmButton.hide();
needConfirm = false;
}
public void needNoPower() {
/**
* Shows the no power button and hides the confirm button.
*/
public void showNoPower() {
confirmButton.hide();
noPowerButton.show();
needNoPower = true;
}
public void noNoPower() {
noPowerButton.show();
/**
* Hides the no power button.
*/
public void hideNoPower() {
noPowerButton.hide();
needNoPower = false;
}
/**
* Enters the interrupt state with the specified color.
*
* @param color the color to set
*/
public void enterInterrupt(Color color) {
enterOverlay(Overlay.INTERRUPT);
@@ -144,6 +221,9 @@ public void enterInterrupt(Color color) {
interruptDialog.show();
}
/**
* Leaves the interrupt state.
*/
public void leaveInterrupt() {
leaveOverlay(Overlay.INTERRUPT);

View File

@@ -5,22 +5,19 @@
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import com.jme3.util.SkyFactory;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LobbyButton;
import pp.mdga.client.button.SettingsButton;
import pp.mdga.game.Color;
import pp.mdga.message.client.StartGameMessage;
import pp.mdga.notification.GameNotification;
/**
* Represents the lobby view in the game.
*/
public class LobbyView extends MdgaView {
private Geometry background;
@@ -39,6 +36,11 @@ public class LobbyView extends MdgaView {
private Color own = null;
/**
* Constructs a new LobbyView.
*
* @param app the application instance
*/
public LobbyView(MdgaApp app) {
super(app);
@@ -54,6 +56,9 @@ public LobbyView(MdgaApp app) {
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
}
/**
* Called when entering the lobby view.
*/
@Override
public void onEnter() {
app.getCamera().setParallelProjection(true);
@@ -64,7 +69,7 @@ public void onEnter() {
leaveButton.show();
readyButton.show();
if(app.getGameLogic().isHost()) {
if (app.getGameLogic().isHost()) {
startButton.show();
}
@@ -98,6 +103,9 @@ public void onEnter() {
rootNode.attachChild(background);
}
/**
* Called when leaving the lobby view.
*/
@Override
public void onLeave() {
leaveButton.hide();
@@ -114,10 +122,10 @@ public void onLeave() {
app.getCamera().setParallelProjection(false);
app.getCamera().setFrustumPerspective(
45.0f,
(float) app.getCamera().getWidth() / app.getCamera().getHeight(),
0.1f,
1000.0f
45.0f,
(float) app.getCamera().getWidth() / app.getCamera().getHeight(),
0.1f,
1000.0f
);
app.getCamera().setLocation(new Vector3f(0, 0, 10));
@@ -136,6 +144,11 @@ public void onLeave() {
rootNode.detachChild(background);
}
/**
* Called on each frame update.
*
* @param tpf time per frame
*/
@Override
protected void onUpdate(float tpf) {
airforceButton.update(tpf);
@@ -146,27 +159,34 @@ protected void onUpdate(float tpf) {
@Override
protected void onEnterOverlay(Overlay overlay) {
// No implementation needed
}
@Override
protected void onLeaveOverlay(Overlay overlay)
{
protected void onLeaveOverlay(Overlay overlay) {
// No implementation needed
}
/**
* Sets the taken status of a color.
*
* @param color the color
* @param isTaken whether the color is taken
* @param isSelf whether the color is taken by the player
* @param name the name of the player who took the color
*/
public void setTaken(Color color, boolean isTaken, boolean isSelf, String name) {
LobbyButton.Taken taken;
if(isTaken) {
if(isSelf) {
if (isTaken) {
if (isSelf) {
own = color;
taken = LobbyButton.Taken.SELF;
} else {
taken = LobbyButton.Taken.OTHER;
}
} else {
if(isSelf) {
if (isSelf) {
own = null;
}
@@ -189,6 +209,12 @@ public void setTaken(Color color, boolean isTaken, boolean isSelf, String name)
}
}
/**
* Sets the ready status of a color.
*
* @param color the color
* @param isReady whether the color is ready
*/
public void setReady(Color color, boolean isReady) {
LobbyButton button = switch (color) {
case CYBER -> cyberButton;
@@ -204,15 +230,20 @@ public void setReady(Color color, boolean isReady) {
this.isReady = isReady;
}
if(!isReady) {
if (!isReady) {
app.getAcousticHandler().playSound(MdgaSound.NOT_READY);
} else {
if(button.getTaken() != LobbyButton.Taken.SELF) {
if (button.getTaken() != LobbyButton.Taken.SELF) {
app.getAcousticHandler().playSound(MdgaSound.OTHER_READY);
}
}
}
/**
* Toggles the task selection for a color.
*
* @param color the color
*/
private void toggleTsk(Color color) {
LobbyButton.Taken taken = LobbyButton.Taken.NOT;
@@ -231,7 +262,7 @@ private void toggleTsk(Color color) {
break;
}
if(isReady) {
if (isReady) {
setReady(own, false);
}
@@ -248,19 +279,25 @@ private void toggleTsk(Color color) {
}
}
/**
* Sets the player as ready.
*/
public void ready() {
if(own == null) {
if (own == null) {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
if(!isReady) {
if (!isReady) {
app.getAcousticHandler().playSound(MdgaSound.SELF_READY);
}
app.getModelSynchronize().setReady(!isReady);
}
/**
* Leaves the lobby.
*/
private void leaveLobby() {
app.getModelSynchronize().leave();
}

View File

@@ -7,7 +7,13 @@
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.dialog.StartDialog;
/**
* MainView class that extends MdgaView and manages the main view of the application.
*/
public class MainView extends MdgaView {
/**
* Enum representing the different sub-states of the MainView.
*/
private enum SubState {
HOST,
JOIN,
@@ -22,6 +28,11 @@ private enum SubState {
private JoinDialog joinDialog;
private HostDialog hostDialog;
/**
* Constructor for MainView.
*
* @param app the MdgaApp instance
*/
public MainView(MdgaApp app) {
super(app);
@@ -32,6 +43,9 @@ public MainView(MdgaApp app) {
background = createBackground("Images/startmenu.png");
}
/**
* Called when the view is entered.
*/
@Override
public void onEnter() {
app.setup();
@@ -41,6 +55,9 @@ public void onEnter() {
enterSub(SubState.MAIN);
}
/**
* Called when the view is left.
*/
@Override
public void onLeave() {
startDialog.hide();
@@ -50,6 +67,11 @@ public void onLeave() {
guiNode.detachChild(background);
}
/**
* Called to update the view.
*
* @param tpf time per frame
*/
@Override
public void onUpdate(float tpf) {
startDialog.update();
@@ -57,18 +79,31 @@ public void onUpdate(float tpf) {
hostDialog.update();
}
/**
* Called when an overlay is entered.
*
* @param overlay the overlay being entered
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
guiNode.detachChild(background);
settingsNode.attachChild(background);
}
/**
* Called when an overlay is left.
*
* @param overlay the overlay being left
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
settingsNode.detachChild(background);
guiNode.attachChild(background);
}
/**
* Shows the join menu.
*/
private void joinMenu() {
startDialog.hide();
hostDialog.hide();
@@ -76,6 +111,9 @@ private void joinMenu() {
joinDialog.show();
}
/**
* Shows the host menu.
*/
private void hostMenu() {
startDialog.hide();
joinDialog.hide();
@@ -83,6 +121,9 @@ private void hostMenu() {
hostDialog.show();
}
/**
* Shows the main menu.
*/
private void mainMenu() {
joinDialog.hide();
hostDialog.hide();
@@ -90,6 +131,9 @@ private void mainMenu() {
startDialog.show();
}
/**
* Attempts to host a game.
*/
private void tryHost() {
int port = 0;
String text = hostDialog.getPort();
@@ -98,7 +142,7 @@ private void tryHost() {
try {
port = Integer.parseInt(text);
if(port >= 1 && port <= 65535) {
if (port >= 1 && port <= 65535) {
app.getModelSynchronize().setName(startDialog.getName());
hostDialog.hostServer();
try {
@@ -117,6 +161,9 @@ private void tryHost() {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
/**
* Attempts to join a game.
*/
private void tryJoin() {
int port = 0;
String ip = joinDialog.getIpt();
@@ -146,20 +193,31 @@ private void tryJoin() {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
/**
* Validates an IP address.
*
* @param ip the IP address to validate
* @return true if the IP address is valid, false otherwise
*/
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]?)$";
"^(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);
}
/**
* Enters a sub-state.
*
* @param state the sub-state to enter
*/
private void enterSub(SubState state) {
this.state = state;
if(null != background) {
if (null != background) {
rootNode.detachChild(background);
}
@@ -176,6 +234,9 @@ private void enterSub(SubState state) {
}
}
/**
* Forwards the state based on the current sub-state.
*/
public void forward() {
switch (state) {
case HOST:
@@ -189,6 +250,11 @@ public void forward() {
}
}
/**
* Forwards the state based on the current sub-state and a boolean flag.
*
* @param host a boolean flag indicating whether to host or join
*/
public void forward(boolean host) {
switch (state) {
case HOST:
@@ -198,7 +264,7 @@ public void forward(boolean host) {
tryJoin();
break;
case MAIN:
if(host) {
if (host) {
enterSub(SubState.HOST);
//TODO playSound
} else {
@@ -209,6 +275,9 @@ public void forward(boolean host) {
}
}
/**
* Goes back to the main menu from the current sub-state.
*/
public void back() {
switch (state) {
case HOST:
@@ -225,12 +294,21 @@ public void back() {
}
}
/**
* Gets the JoinDialog instance.
*
* @return the JoinDialog instance
*/
public JoinDialog getJoinDialog() {
return joinDialog;
}
/**
* Gets the HostDialog instance.
*
* @return the HostDialog instance
*/
public HostDialog getHostDialog() {
return hostDialog;
}
}

View File

@@ -11,12 +11,20 @@
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.button.AbstractButton;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.SettingsButton;
import pp.mdga.client.dialog.AudioSettingsDialog;
import pp.mdga.client.dialog.SettingsDialog;
import pp.mdga.client.dialog.VideoSettingsDialog;
/**
* Abstract class representing a view in the MDGA application.
*/
public abstract class MdgaView {
/**
* Enum representing different types of overlays.
*/
public enum Overlay {
INTERRUPT,
SETTINGS,
@@ -38,6 +46,11 @@ public enum Overlay {
private int settingsDepth = 0;
/**
* Constructor for MdgaView.
*
* @param app the application instance
*/
public MdgaView(MdgaApp app) {
this.app = app;
settingsButton = new SettingsButton(app, guiNode, this::enterSettings);
@@ -47,6 +60,9 @@ public MdgaView(MdgaApp app) {
audioSettingsDialog = new AudioSettingsDialog(app, settingsNode, this);
}
/**
* Method to enter the view.
*/
public void enter() {
app.getRootNode().attachChild(rootNode);
app.getGuiNode().attachChild(guiNode);
@@ -56,10 +72,12 @@ public void enter() {
onEnter();
}
/**
* Method to leave the view.
*/
public void leave() {
onLeave();
settingsButton.hide();
while (settingsDepth > 0) {
@@ -70,18 +88,33 @@ public void leave() {
app.getGuiNode().detachChild(guiNode);
}
/**
* Method to enter an overlay.
*
* @param overlay the overlay to enter
*/
public void enterOverlay(Overlay overlay) {
app.getGuiNode().detachChild(guiNode);
onEnterOverlay(overlay);
}
/**
* Method to leave an overlay.
*
* @param overlay the overlay to leave
*/
public void leaveOverlay(Overlay overlay) {
app.getGuiNode().attachChild(guiNode);
onLeaveOverlay(overlay);
}
/**
* Method to update the view.
*
* @param tpf time per frame
*/
public void update(float tpf) {
videoSettingsDialog.update();
audioSettingsDialog.update();
@@ -94,17 +127,44 @@ public void update(float tpf) {
onUpdate(tpf);
}
/**
* Abstract method to handle entering the view.
*/
protected abstract void onEnter();
/**
* Abstract method to handle leaving the view.
*/
protected abstract void onLeave();
/**
* Method to handle updating the view.
*
* @param tpf time per frame
*/
protected void onUpdate(float tpf) {
}
/**
* Abstract method to handle entering an overlay.
*
* @param overlay the overlay to enter
*/
protected abstract void onEnterOverlay(Overlay overlay);
/**
* Abstract method to handle leaving an overlay.
*
* @param overlay the overlay to leave
*/
protected abstract void onLeaveOverlay(Overlay overlay);
/**
* Method to create a background geometry with a texture.
*
* @param texturePath the path to the texture
* @return the created background geometry
*/
protected Geometry createBackground(String texturePath) {
TextureKey key = new TextureKey(texturePath, true);
Texture backgroundTexture = app.getAssetManager().loadTexture(key);
@@ -121,6 +181,9 @@ protected Geometry createBackground(String texturePath) {
return background;
}
/**
* Method to enter the settings view.
*/
public void enterSettings() {
enterOverlay(Overlay.SETTINGS);
@@ -131,6 +194,9 @@ public void enterSettings() {
settingsDepth++;
}
/**
* Method to leave the settings view.
*/
public void leaveSettings() {
leaveOverlay(Overlay.SETTINGS);
@@ -141,6 +207,9 @@ public void leaveSettings() {
settingsDepth--;
}
/**
* Method to enter the video settings view.
*/
public void enterVideoSettings() {
settingsDialog.hide();
videoSettingsDialog.show();
@@ -148,6 +217,9 @@ public void enterVideoSettings() {
settingsDepth++;
}
/**
* Method to leave the video settings view.
*/
public void leaveVideoSettings() {
settingsDialog.show();
videoSettingsDialog.hide();
@@ -155,6 +227,9 @@ public void leaveVideoSettings() {
settingsDepth--;
}
/**
* Method to enter the audio settings view.
*/
public void enterAudioSettings() {
settingsDialog.hide();
audioSettingsDialog.show();
@@ -162,6 +237,9 @@ public void enterAudioSettings() {
settingsDepth++;
}
/**
* Method to leave the audio settings view.
*/
public void leaveAudioSettings() {
settingsDialog.show();
audioSettingsDialog.hide();
@@ -169,6 +247,9 @@ public void leaveAudioSettings() {
settingsDepth--;
}
/**
* Method to leave advanced settings.
*/
private void leaveAdvanced() {
settingsDialog.show();
audioSettingsDialog.hide();
@@ -176,6 +257,9 @@ private void leaveAdvanced() {
settingsDepth--;
}
/**
* Method to handle pressing the escape key.
*/
public void pressEscape() {
if (settingsDepth == 0) {
enterSettings();
@@ -186,6 +270,9 @@ public void pressEscape() {
}
}
/**
* Method to handle pressing the forward key.
*/
public void pressForward() {
if (this instanceof MainView mainView) {
mainView.forward(false);
@@ -197,7 +284,13 @@ public void pressForward() {
}
if (this instanceof GameView gameView) {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
if (gameView.needConfirm) {
app.getModelSynchronize().confirm();
} else if (gameView.needNoPower) {
app.getModelSynchronize().confirm();
} else {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
}
if (this instanceof CeremonyView ceremonyView) {
@@ -205,10 +298,16 @@ public void pressForward() {
}
}
/**
* Method to show information on the view.
*
* @param error the error message
* @param isError flag indicating if it is an error
*/
public void showInfo(String error, boolean isError) {
infoTimer.reset();
if(null != infoLabel) {
if (null != infoLabel) {
infoLabel.hide();
}
@@ -216,7 +315,7 @@ public void showInfo(String error, boolean isError) {
ColorRGBA color;
if(isError) {
if (isError) {
color = ColorRGBA.Red.clone();
} else {
color = ColorRGBA.Green.clone();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,4 +1,6 @@
world 0,0 90
treesBigBackground 0,0 90
treesSmallBackground 0,0 90
#Marine Pos
@@ -56,7 +58,8 @@ big_tent -10,-9 130
big_tent 9,-10 225
radar 0,10 -20
tank -1,-10 135
tank 0,-18 180
#tank 0,-18 180
tank_shoot 0,-18 180
tank 3,-18 180
tank -3,-18 180
@@ -270,3 +273,4 @@ treeBig 12,22 360

View File

@@ -1,7 +1,6 @@
// Samplers for textures
uniform sampler2D m_Texture;
uniform sampler2D m_OutlineDepthTexture;
uniform sampler2D m_DepthTexture;
// Input texture coordinates from the vertex shader
in vec2 texCoord;
@@ -15,26 +14,25 @@ uniform float m_OutlineWidth;
out vec4 fragColor;
void main() {
// Sample depth textures
// Sample depth textures at various offsets
vec4 depth = texture(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
// Sample the main texture
vec4 color = texture(m_Texture, texCoord);
// Determine whether to apply the outline color
if (depth == vec4(0.0) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
fragColor = m_OutlineColor; // Apply outline color
} else {
fragColor = color; // Use the original texture color
}
// Check if an outline should be applied
bool isEdge = (depth == vec4(0.0)) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth);
// Output the final color
fragColor = isEdge ? m_OutlineColor : color;
}

View File

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

View File

@@ -1,109 +1,78 @@
// Uniform samplers
uniform sampler2D m_Texture;
uniform sampler2D m_OutlineDepthTexture;
uniform sampler2D m_DepthTexture;
varying vec2 texCoord;
// Input texture coordinates from the vertex shader
in vec2 texCoord;
// Uniforms for resolution, outline color, and width
uniform vec2 m_Resolution;
uniform vec4 m_OutlineColor;
uniform float m_OutlineWidth;
// Output variable for fragment color
out vec4 fragColor;
void main() {
vec4 depth = texture2D(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,m_OutlineWidth))/m_Resolution);
vec4 depth2 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
vec4 depth3 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,m_OutlineWidth))/m_Resolution);
vec4 depth4 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
vec4 depth5 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,m_OutlineWidth))/m_Resolution);
vec4 depth6 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-m_OutlineWidth))/m_Resolution);
vec4 depth7 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,0.))/m_Resolution);
vec4 depth8 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,0.))/m_Resolution);
vec4 color = texture2D(m_Texture, texCoord);
//如果是背景
float ratio=0.;
if(depth==vec4(0.) && (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth||depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)){
float dist=m_OutlineWidth;
//距离边的像素
vec4 nearDepth;
if(depth1 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth2 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth3 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth4 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth5 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth6 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth7 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,0.))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth8 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,0.))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}
//0:场景颜色 1:outline颜色
ratio = clamp(1.- dist/m_OutlineWidth,0.,1.);
//float off = (1.-ratio*ratio)*(1.-ratio*ratio);
gl_FragColor = color*(1.-ratio) +m_OutlineColor*ratio;
//gl_FragColor = m_OutlineColor;
}else{
gl_FragColor = color;
}
//debug
//gl_FragColor = vec4(0.,(1.-ratio),0.,1.);
}
vec4 depth = texture(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
vec4 color = texture(m_Texture, texCoord);
float ratio = 0.0;
if (depth == vec4(0.0) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
float dist = m_OutlineWidth;
vec4 nearDepth;
// Iterate to find the distance to the nearest edge
for (float i = 0.0; i < m_OutlineWidth; i++) {
if (depth1 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth2 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth3 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth4 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth5 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth6 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth7 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, 0.0)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth8 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, 0.0)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
}
}
// Calculate ratio for outline blending
ratio = clamp(1.0 - dist / m_OutlineWidth, 0.0, 1.0);
// Blend the outline color with the base color
fragColor = color * (1.0 - ratio) + m_OutlineColor * ratio;
} else {
// No outline, use the base texture color
fragColor = color;
}
// Optional: Debugging outline visualization
// fragColor = vec4(0.0, 1.0 - ratio, 0.0, 1.0);
}

View File

@@ -1,8 +1,8 @@
// Use 'in' for vertex attributes
// Vertex attributes
in vec4 inPosition;
in vec2 inTexCoord;
// Use 'out' for passing data to the fragment shader
// Output to fragment shader
out vec2 texCoord;
void main() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

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