diff --git a/.gitignore b/.gitignore
index 164fc10a..86b0204d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+
+.run/
.gradle
build/
#!gradle/wrapper/gradle-wrapper.jar
diff --git a/Projekte/.run/MdgaApp.run.xml b/Projekte/.run/MdgaApp.run.xml
deleted file mode 100644
index 123a07af..00000000
--- a/Projekte/.run/MdgaApp.run.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Projekte/mdga/client/hs_err_pid54211.log b/Projekte/mdga/client/hs_err_pid54211.log
new file mode 100644
index 00000000..9b6e3d33
--- /dev/null
+++ b/Projekte/mdga/client/hs_err_pid54211.log
@@ -0,0 +1,21 @@
+#
+# A fatal error has been detected by the Java Runtime Environment:
+#
+# SIGSEGV (0xb) at pc=0x000070ba272e8104, pid=54211, tid=54346
+#
+# JRE version: OpenJDK Runtime Environment Temurin-20.0.2+9 (20.0.2+9) (build 20.0.2+9)
+# Java VM: OpenJDK 64-Bit Server VM Temurin-20.0.2+9 (20.0.2+9, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
+# Problematic frame:
+# V [libjvm.so+0xee8104] SystemDictionary::find_or_define_helper(Symbol*, Handle, InstanceKlass*, JavaThread*)+0x304
+#
+# Core dump will be written. Default location: Core dumps may be processed with "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h" (or dumping to /home/cedric/ProgProjekt/Gruppe-01/Projekte/mdga/client/core.54211)
+#
+# If you would like to submit a bug report, please visit:
+# https://github.com/adoptium/adoptium-support/issues
+#
+
+--------------- S U M M A R Y ------------
+
+Command Line: -Ddebugger.agent.enable.coroutines=true -Djava.util.logging.config.file=logging.properties -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=127.0.0.1:40559 -javaagent:/usr/share/idea/plugins/java/lib/rt/debugger-agent.jar -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea pp.mdga.client.MdgaApp
+
+Host: AMD Ryzen 5 8640HS w/ Radeon 760M Graphics, 12 cores, 14G,
\ No newline at end of file
diff --git a/Projekte/mdga/client/hs_err_pid60653.log b/Projekte/mdga/client/hs_err_pid60653.log
new file mode 100644
index 00000000..974a6ce1
--- /dev/null
+++ b/Projekte/mdga/client/hs_err_pid60653.log
@@ -0,0 +1,171 @@
+#
+# A fatal error has been detected by the Java Runtime Environment:
+#
+# SIGSEGV (0xb) at pc=0x00007fc14e0eef41, pid=60653, tid=60689
+#
+# JRE version: OpenJDK Runtime Environment (21.0.5+11) (build 21.0.5+11)
+# Java VM: OpenJDK 64-Bit Server VM (21.0.5+11, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
+# Problematic frame:
+# C [libgallium-24.2.8-arch1.1.so+0x8eef41]
+#
+# Core dump will be written. Default location: Core dumps may be processed with "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h" (or dumping to /home/cedric/ProgProjekt/Gruppe-01/Projekte/mdga/client/core.60653)
+#
+# If you would like to submit a bug report, please visit:
+# https://bugreport.java.com/bugreport/crash.jsp
+# The crash happened outside the Java Virtual Machine in native code.
+# See problematic frame for where to report the bug.
+#
+
+--------------- S U M M A R Y ------------
+
+Command Line: -Ddebugger.agent.enable.coroutines=true -Djava.util.logging.config.file=logging.properties -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=127.0.0.1:39131 -javaagent:/usr/share/idea/plugins/java/lib/rt/debugger-agent.jar -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea pp.mdga.client.MdgaApp
+
+Host: AMD Ryzen 5 8640HS w/ Radeon 760M Graphics, 12 cores, 14G, Manjaro Linux
+Time: Sun Dec 8 18:11:23 2024 CET elapsed time: 295.650309 seconds (0d 0h 4m 55s)
+
+--------------- T H R E A D ---------------
+
+Current thread (0x00007fc17ca803b0): JavaThread "jME3 Main" [_thread_in_native, id=60689, stack(0x00007fc159425000,0x00007fc159525000) (1024K)]
+
+Stack: [0x00007fc159425000,0x00007fc159525000], sp=0x00007fc159522300, free space=1012k
+Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
+C [libgallium-24.2.8-arch1.1.so+0x8eef41]
+C [libgallium-24.2.8-arch1.1.so+0xf4a849]
+C [libgallium-24.2.8-arch1.1.so+0x6ccb71]
+C [libgallium-24.2.8-arch1.1.so+0x6c58ae]
+C [libgallium-24.2.8-arch1.1.so+0x6c6013]
+C [libgallium-24.2.8-arch1.1.so+0x6cddf8]
+C [libgallium-24.2.8-arch1.1.so+0x141327]
+C [libgallium-24.2.8-arch1.1.so+0x5fc8b]
+C [libGLX_mesa.so.0+0x4f2e0]
+C [libGLX_mesa.so.0+0x40496]
+C [libGLX_mesa.so.0+0x2efa5]
+J 2042 org.lwjgl.system.JNI.invokePV(JJ)V (0 bytes) @ 0x00007fc16c1abb6f [0x00007fc16c1abaa0+0x00000000000000cf]
+J 4039 c1 org.lwjgl.glfw.GLFW.glfwSwapBuffers(J)V (21 bytes) @ 0x00007fc164d8684c [0x00007fc164d86760+0x00000000000000ec]
+J 3912 c1 com.jme3.system.lwjgl.LwjglWindow.runLoop()V (177 bytes) @ 0x00007fc164d44b34 [0x00007fc164d442c0+0x0000000000000874]
+j com.jme3.system.lwjgl.LwjglWindow.run()V+54
+j java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V+5 java.base@21.0.5
+j java.lang.Thread.run()V+19 java.base@21.0.5
+v ~StubRoutines::call_stub 0x00007fc16bb37cc6
+V [libjvm.so+0x6d0894]
+V [libjvm.so+0x6d14fd]
+V [libjvm.so+0x79a7be]
+V [libjvm.so+0x6cac55]
+V [libjvm.so+0x9d62c1]
+C [libc.so.6+0x9439d]
+Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
+J 2042 org.lwjgl.system.JNI.invokePV(JJ)V (0 bytes) @ 0x00007fc16c1abafc [0x00007fc16c1abaa0+0x000000000000005c]
+J 4039 c1 org.lwjgl.glfw.GLFW.glfwSwapBuffers(J)V (21 bytes) @ 0x00007fc164d8684c [0x00007fc164d86760+0x00000000000000ec]
+J 3912 c1 com.jme3.system.lwjgl.LwjglWindow.runLoop()V (177 bytes) @ 0x00007fc164d44b34 [0x00007fc164d442c0+0x0000000000000874]
+j com.jme3.system.lwjgl.LwjglWindow.run()V+54
+j java.lang.Thread.runWith(Ljava/lang/Object;Ljava/lang/Runnable;)V+5 java.base@21.0.5
+j java.lang.Thread.run()V+19 java.base@21.0.5
+v ~StubRoutines::call_stub 0x00007fc16bb37cc6
+
+siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x00000000000000c9
+
+Registers:
+RAX=0x0000000000000000, RBX=0x00007fc09825b2a0, RCX=0x0000000000000006, RDX=0x0000000028200000
+RSP=0x00007fc159522300, RBP=0x00007fc159522310, RSI=0x00007fc0998d3604, RDI=0x00007fc09825b7a0
+R8 =0x0000000000000006, R9 =0x00000000aa959a6a, R10=0x0000000000000263, R11=0x00007fc099bc36c0
+R12=0x00007fc0998d35e0, R13=0x000000058bc5f100, R14=0x000000058bc5f140, R15=0x0000000000000040
+RIP=0x00007fc14e0eef41, EFLAGS=0x0000000000010202, CSGSFS=0x002b000000000033, ERR=0x0000000000000004
+ TRAPNO=0x000000000000000e
+
+
+Register to memory mapping:
+
+RAX=0x0 is null
+RBX=0x00007fc09825b2a0 points into unknown readable memory: 0x00007fc098239c40 | 40 9c 23 98 c0 7f 00 00
+RCX=0x0000000000000006 is an unknown value
+RDX=0x0000000028200000 is an unknown value
+RSP=0x00007fc159522300 is pointing into the stack for thread: 0x00007fc17ca803b0
+RBP=0x00007fc159522310 is pointing into the stack for thread: 0x00007fc17ca803b0
+RSI=0x00007fc0998d3604 points into unknown readable memory: 00 00 00 00
+RDI=0x00007fc09825b7a0 points into unknown readable memory: 0x00001c7c00000265 | 65 02 00 00 7c 1c 00 00
+R8 =0x0000000000000006 is an unknown value
+R9 =0x00000000aa959a6a is an unknown value
+R10=0x0000000000000263 is an unknown value
+R11=0x00007fc099bc36c0 points into unknown readable memory: 0x00007fc14e121570 | 70 15 12 4e c1 7f 00 00
+R12=0x00007fc0998d35e0 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00
+R13=0x000000058bc5f100 is an unknown value
+R14=0x000000058bc5f140 is an unknown value
+R15=0x0000000000000040 is an unknown value
+
+Top of Stack: (sp=0x00007fc159522300)
+0x00007fc159522300: 00007fc09825b2a0 ffffffffffffffff
+0x00007fc159522310: 00007fc1595223d0 00007fc14e74a849
+0x00007fc159522320: 0000000000000000 00000000cc0016c8
+0x00007fc159522330: 00007fc09920b000 00007fc100000004
+0x00007fc159522340: 00007fc100000001 00007fc100000000
+0x00007fc159522350: ffff800100000002 00007fc09825b7a0
+0x00007fc159522360: 00007fc1595223e0 0000000100000b02
+0x00007fc159522370: 0000000000000000 00007fc0983eca88
+0x00007fc159522380: 00007fc100000000 00007fc099e33780
+0x00007fc159522390: 00007fc159523170 b36c5690f9bb0900
+0x00007fc1595223a0: 00007fc15951fd20 00007fc0983eca80
+0x00007fc1595223b0: 00007fc09825b2a0 00007fc0983edb60
+0x00007fc1595223c0: 00007fc098415b10 00007fc0983ec850
+0x00007fc1595223d0: 00007fc159523280 00007fc14deccb71
+0x00007fc1595223e0: 00000bdc00000000 00007fc100000000
+0x00007fc1595223f0: 00007fc159523290 00007fc159523294
+0x00007fc159522400: 00007fc159523320 00007fc159523324
+0x00007fc159522410: 00007fc1595231b0 00007fc1595231b4
+0x00007fc159522420: 00007fc159522860 00007fc164c8401c
+0x00007fc159522430: 00007fc159523310 00007fc159523314
+0x00007fc159522440: 00007fc1595232a0 00007fc1595232a4
+0x00007fc159522450: 00007fc159522860 00007fc159523334
+0x00007fc159522460: 00007fc159523400 00007fc159523404
+0x00007fc159522470: 00007fc1595232d0 00007fc1595232d4
+0x00007fc159522480: 00007fc16bc61f10 00007fc0c80123d0
+0x00007fc159522490: 00007fc1595231f0 00007fc1595231f4
+0x00007fc1595224a0: 00007fc1595223e0 00000000e31c914f
+0x00007fc1595224b0: 00007fc159523350 00007fc159523354
+0x00007fc1595224c0: 00007fc1595232e0 00007fc1595232e4
+0x00007fc1595224d0: 00007f0000000000 00000000e3193478
+0x00007fc1595224e0: 00007fc159523440 00007fc159523444
+0x00007fc1595224f0: 00007fc159523310 00007fc159523314
+
+Instructions: (pc=0x00007fc14e0eef41)
+0x00007fc14e0eee41: 0f 1e fa 55 48 83 c6 24 48 89 e5 41 55 41 54 53
+0x00007fc14e0eee51: 48 89 fb 48 83 ec 08 8b 97 00 05 00 00 44 0f b7
+0x00007fc14e0eee61: 6e f0 48 8b 87 08 05 00 00 49 89 d4 45 01 ec 48
+0x00007fc14e0eee71: 8d 3c 90 42 8d 14 ad 00 00 00 00 ff 15 ee 7e c3
+0x00007fc14e0eee81: 01 44 89 a3 00 05 00 00 48 83 c4 08 5b 41 5c 41
+0x00007fc14e0eee91: 5d 5d c3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3
+0x00007fc14e0eeea1: 0f 1e fa 55 48 89 e5 41 57 41 56 41 55 41 54 41
+0x00007fc14e0eeeb1: 89 f4 53 48 89 fb 48 83 ec 08 4e 8b b4 e7 88 09
+0x00007fc14e0eeec1: 00 00 8b 97 00 05 00 00 48 8b 87 08 05 00 00 45
+0x00007fc14e0eeed1: 0f b7 7e 14 49 89 d5 49 8d 76 24 48 8d 3c 90 45
+0x00007fc14e0eeee1: 01 fd 42 8d 14 bd 00 00 00 00 ff 15 7f 7e c3 01
+0x00007fc14e0eeef1: 44 89 ab 00 05 00 00 4e 89 b4 e3 d8 09 00 00 48
+0x00007fc14e0eef01: 83 c4 08 5b 41 5c 41 5d 41 5e 41 5f 5d c3 90 f3
+0x00007fc14e0eef11: 0f 1e fa 55 89 f0 48 89 e5 41 54 53 4c 8b a4 c7
+0x00007fc14e0eef21: 88 09 00 00 48 89 fb e8 73 ff ff ff 49 8b 84 24
+0x00007fc14e0eef31: 80 01 00 00 ba 00 00 20 28 48 8d bb 00 05 00 00
+0x00007fc14e0eef41: 0f b6 88 c9 00 00 00 48 8b b0 b0 00 00 00 48 8b
+0x00007fc14e0eef51: 83 f0 04 00 00 ff 90 00 01 00 00 49 8b 04 24 48
+0x00007fc14e0eef61: 85 c0 74 13 48 89 df be ff ff ff ff 5b 41 5c 5d
+0x00007fc14e0eef71: ff e0 0f 1f 44 00 00 5b 41 5c 5d c3 0f 1f 00 f3
+0x00007fc14e0eef81: 0f 1e fa 66 0f ef c0 31 c9 ba 01 00 00 00 0f 11
+0x00007fc14e0eef91: 87 d8 09 00 00 0f 11 87 e8 09 00 00 0f 11 87 f8
+0x00007fc14e0eefa1: 09 00 00 0f 11 87 08 0a 00 00 0f 11 87 18 0a 00
+0x00007fc14e0eefb1: 00 66 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 48
+0x00007fc14e0eefc1: 83 bc cf 88 09 00 00 00 74 0d 48 89 d0 48 d3 e0
+0x00007fc14e0eefd1: 48 09 87 80 09 00 00 48 83 c1 01 48 83 f9 0a 75
+0x00007fc14e0eefe1: de c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 f3
+0x00007fc14e0eeff1: 0f 1e fa 55 48 89 e5 41 56 41 89 d6 41 55 41 89
+0x00007fc14e0ef001: f5 8d 34 b5 28 00 00 00 41 54 49 89 fc bf 01 00
+0x00007fc14e0ef011: 00 00 53 ff 15 7e 89 c3 01 48 89 c3 48 85 c0 74
+0x00007fc14e0ef021: 2a 66 44 89 68 1a 41 0f b6 ce 48 8d 78 08 41 0f
+0x00007fc14e0ef031: b6 94 24 97 08 00 00 49 8d b4 24 90 02 00 00 c0
+
+
+Stack slot to memory mapping:
+
+stack at sp + 0 slots: 0x00007fc09825b2a0 points into unknown readable memory: 0x00007fc098239c40 | 40 9c 23 98 c0 7f 00 00
+stack at sp + 1 slots: 0xffffffffffffffff is an unknown value
+stack at sp + 2 slots: 0x00007fc1595223d0 is pointing into the stack for thread: 0x00007fc17ca803b0
+stack at sp + 3 slots: 0x00007fc14e74a849: in /usr/lib/libgallium-24.2.8-arch1.1.so at 0x00007fc14d800000
+stack at sp + 4 slots: 0x0 is null
+stack at sp + 5 slots:
\ No newline at end of file
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Asset.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Asset.java
index 1f3da542..9317f11d 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Asset.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Asset.java
@@ -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"),
@@ -30,15 +31,21 @@ public enum Asset {
tank,
world(1.2f),
shieldRing("Models/shieldRing/shieldRing.j3o", null),
- treeSmall,
- treeBig,
+ treeSmall(1.2f),
+ treeBig(1.2f),
turboCard,
- turboSymbol("Models/turboCard/turboSymbol.j3o", "Models/turboCard/turboCard_diff.j3o"),
+ turboSymbol("Models/turboCard/turboSymbol.j3o", "Models/turboCard/turboCard_diff.png"),
swapCard,
- swapSymbol("Models/swapCard/swapSymbol.j3o", "Models/swapCard/swapCard_diff.j3o"),
+ swapSymbol("Models/swapCard/swapSymbol.j3o", "Models/swapCard/swapCard_diff.png"),
shieldCard,
- shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.j3o"),
- dice
+ 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;
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/InputSynchronizer.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/InputSynchronizer.java
index 807a37c4..a643f203 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/InputSynchronizer.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/InputSynchronizer.java
@@ -27,6 +27,7 @@
import pp.mdga.notification.SelectableCardsNotification;
import java.util.List;
+import java.util.UUID;
public class InputSynchronizer {
@@ -39,6 +40,11 @@ public class InputSynchronizer {
private CardControl hoverCard;
private PieceControl hoverPiece;
+ private boolean clickAllowed = true;
+
+ private boolean isRotateLeft = false;
+ private boolean isRotateRight = false;
+
/**
* Constructor initializes the InputSynchronizer with the application context.
* Sets up input mappings and listeners for user interactions.
@@ -54,6 +60,18 @@ public class InputSynchronizer {
setupInput();
}
+ public void update(float tpf) {
+ if(isRotateLeft && isRotateRight) {
+ return;
+ }
+ if(isRotateLeft) {
+ rotationAngle += 180 * tpf;
+ }
+ if(isRotateRight) {
+ rotationAngle -= 180 * tpf;
+ }
+ }
+
/**
* Configures input mappings for various actions and binds them to listeners.
*/
@@ -61,22 +79,22 @@ private void setupInput() {
inputManager.addMapping("Settings", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_RETURN));
+ inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_Q));
+ inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_E));
+
inputManager.addMapping("RotateRightMouse", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping("MouseLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); // Left movement
inputManager.addMapping("MouseRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); // Right movement
- inputManager.addMapping("MouseVertical", new MouseAxisTrigger(MouseInput.AXIS_Y, false), new MouseAxisTrigger(MouseInput.AXIS_Y, true)); //Mouse Up Down movement
inputManager.addMapping("MouseScrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); // Scroll up
inputManager.addMapping("MouseScrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); // Scroll down
inputManager.addMapping("Test", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
- inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Test");
+ inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
-
- private boolean test = false;
-
+ UUID p = null;
/**
* Handles action-based input events such as key presses and mouse clicks.
*/
@@ -93,6 +111,10 @@ public void onAction(String name, boolean isPressed, float tpf) {
rightMousePressed = isPressed;
}
if(name.equals("Click") && isPressed) {
+ if(!clickAllowed) {
+ return;
+ }
+
if (app.getView() instanceof GameView gameView) {
DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
@@ -121,13 +143,41 @@ else if(boardSelect != null) {
}
- if(name.equals("Test") &&isPressed){
+ if (name.equals("Left")) {
+ isRotateLeft = !isRotateLeft;
+ }
+ if (name.equals("Right")) {
+ isRotateRight = !isRotateRight;
+ }
+ if(name.equals("Test2") &&isPressed){
if(app.getView() instanceof GameView gameView){
+
+ if(p == null) {
+ p = UUID.randomUUID();
+ gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
+ gameView.getBoardHandler().movePieceStartAnim(p,0);
+ gameView.getBoardHandler().outlineMove(List.of(p),List.of(2),List.of(false));
+ //gameView.getBoardHandler().movePieceAnim(p,0, 8);
+ } else {
+ gameView.getBoardHandler().throwPiece(p, Color.ARMY);
+ //gameView.getBoardHandler().movePieceStartAnim(p,0);
+ }
+
// gameView.getGuiHandler().rollRankingResult(Color.AIRFORCE, 1);
// gameView.getGuiHandler().rollRankingResult(Color.ARMY, 2);
// gameView.getGuiHandler().rollRankingResult(Color.NAVY, 3);
// gameView.getGuiHandler().rollRankingResult(Color.CYBER, 4);
- gameView.getGuiHandler().showDice();
+// gameView.getGuiHandler().showDice();
+// UUID p1 = UUID.randomUUID();
+
+// gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p1,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
+// gameView.getBoardHandler().movePieceStartAnim(p1,0);
+ //gameView.getGuiHandler().drawCard(Color.ARMY);
+ //gameView.getGuiHandler().addCardOwn(BonusCard.SHIELD);
+ //gameView.getGuiHandler().playCardOwn(BonusCard.SHIELD);
+
+
+
}
}
}
@@ -200,12 +250,13 @@ private void hoverPiece() {
PieceControl control = checkPiece();
if (control != null) {
if (control != hoverPiece) {
- pieceOff();
+ pieceOff(gameView);
hoverPiece = control;
- hoverPiece.hover();
+// hoverPiece.hover();
+ gameView.getBoardHandler().pieceHoverOn(hoverPiece);
}
} else {
- pieceOff();
+ pieceOff(gameView);
}
}
}
@@ -255,8 +306,11 @@ private CardControl checkCard(GameView gameView) {
/**
* 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) {
+ gameView.getBoardHandler().pieceHoverOff(hoverPiece);
+// hoverPiece.hoverOff();
+ }
hoverPiece = null;
}
@@ -302,4 +356,12 @@ public void setRotation(float rotationAngle){
public int getScroll() {
return scrollValue;
}
+
+ public void setClickAllowed(boolean allowed) {
+ clickAllowed = allowed;
+ }
+
+ public boolean isClickAllowed() {
+ return clickAllowed;
+ }
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java
index 9eca1ab8..6a69b6ba 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java
@@ -1,13 +1,19 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
-import com.jme3.system.AppSettings;
import com.simsilica.lemur.GuiGlobals;
+import com.sun.tools.javac.Main;
import pp.mdga.client.acoustic.AcousticHandler;
+import com.jme3.system.AppSettings;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.view.*;
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
import java.io.IOException;
+import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;
@@ -39,19 +45,19 @@ public class MdgaApp extends SimpleApplication {
private MdgaState state = null;
/** Scale for rendering images. */
- private float imageScale = prefs.getInt("scale", 1);
+ private final float imageScale = prefs.getInt("scale", 1);
/** The main menu view. */
- private MdgaView mainView;
+ private MainView mainView;
/** The lobby view. */
- private MdgaView lobbyView;
+ private LobbyView lobbyView;
/** The game view. */
- private MdgaView gameView;
+ private GameView gameView;
/** The ceremony view. */
- private MdgaView ceremonyView;
+ private CeremonyView ceremonyView;
/** The client game logic. */
private final ClientGameLogic clientGameLogic;
@@ -60,7 +66,9 @@ public class MdgaApp extends SimpleApplication {
private ServerConnection networkConnection;
- private MdgaApp() {
+ public static final int DEBUG_MULTIPLIER = 0;
+
+ public MdgaApp() {
networkConnection = new NetworkSupport(this);
this.clientGameLogic = new ClientGameLogic(networkConnection);
}
@@ -74,15 +82,30 @@ private MdgaApp() {
public static void main(String[] args) {
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
- settings.setWidth(prefs.getInt("width", 1280));
- settings.setHeight(prefs.getInt("height", 720));
+
+ if(prefs.getBoolean("fullscreen", false)) {
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ int screenWidth = (int) screenSize.getWidth();
+ int screenHeight = (int) screenSize.getHeight();
+
+ settings.setResolution(screenWidth, screenHeight);
+
+ settings.setFullscreen(true);
+ } else {
+ settings.setWidth(prefs.getInt("width", 1280));
+ settings.setHeight(prefs.getInt("height", 720));
+ }
+
settings.setCenterWindow(true);
settings.setVSync(false);
-
+ settings.setTitle("MDGA");
+ settings.setVSync(true);
MdgaApp app = new MdgaApp();
app.setSettings(settings);
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
+ app.setDisplayStatView(false);
+
app.start();
}
@@ -120,6 +143,7 @@ public void simpleUpdate(float tpf) {
view.update(tpf);
acousticHandler.update();
notificationSynchronizer.update();
+ inputSynchronizer.update(tpf);
}
/**
@@ -245,30 +269,60 @@ public ServerConnection getNetworkSupport(){
return networkConnection;
}
- public void updateResolution(int width, int height, float imageFactor) {
- prefs.putInt("width", width);
- prefs.putInt("height", height);
- prefs.putFloat("scale", imageFactor);
+ public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
+ if(isFullscreen) {
+ int baseWidth = 1280;
+ int baseHeight = 720;
+ float baseAspectRatio = (float) baseWidth / baseHeight;
+ float newAspectRatio = (float) width / height;
- try {
- restartApp();
- } catch (Exception e) {
- //nothing
+ float scaleFactor = Math.max((float) width / baseWidth, (float) height / baseHeight);
+
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ int screenWidth = (int) screenSize.getWidth();
+ int screenHeight = (int) screenSize.getHeight();
+ settings.setResolution(screenWidth, screenHeight);
+ settings.setFullscreen(true);
+
+ prefs.putFloat("scale", scaleFactor);
+ prefs.putBoolean("fullscreen", true);
+ } else {
+ prefs.putInt("width", width);
+ prefs.putInt("height", height);
+ prefs.putFloat("scale", imageFactor);
+ prefs.putBoolean("fullscreen", false);
}
}
- public static void restartApp() throws IOException {
- String javaBin = System.getProperty("java.home") + "/bin/java";
- String classPath = System.getProperty("java.class.path");
- String className = System.getProperty("sun.java.command");
+ public static void restartApp() {
+ try {
+ String javaBin = System.getProperty("java.home") + "/bin/java";
+ String classPath = System.getProperty("java.class.path");
+ String className = System.getProperty("sun.java.command");
- ProcessBuilder builder = new ProcessBuilder(
- javaBin, "-cp", classPath, className
- );
+ ProcessBuilder builder = new ProcessBuilder(
+ javaBin, "-cp", classPath, className
+ );
- builder.start();
+ builder.start();
- System.exit(0);
+ System.exit(0);
+ } catch (Exception e) {
+ throw new RuntimeException("restart failed");
+ }
+ }
+
+ public void afterGameCleanup() {
+ MainView main = (MainView) mainView;
+
+ main.getJoinDialog().disconnect();
+ main.getHostDialog().shutdownServer();
+
+ ceremonyView.afterGameCleanup();
+ }
+
+ public GameView getGameView(){
+ return gameView;
}
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/ModelSynchronizer.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/ModelSynchronizer.java
index 0b26a305..9971038b 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/ModelSynchronizer.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/ModelSynchronizer.java
@@ -1,16 +1,8 @@
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;
@@ -31,7 +23,11 @@ public class ModelSynchronizer {
}
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){
@@ -74,10 +70,11 @@ public void selectCard(BonusCard card) {
this.card = card;
GameView gameView = (GameView) app.getView();
+
if(card != null) {
gameView.needConfirm();
} else {
- gameView.noConfirm();
+ gameView.showNoPower();
}
}
@@ -86,21 +83,26 @@ public void confirm() {
GameView gameView = (GameView) app.getView();
+ gameView.getGuiHandler().hideText();
+
if(a != null && b != null) {
- selectPiece(a);
- selectPiece(b);
+ 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 (card != null){
- selectCard(card);
- gameView.getGuiHandler().clearSelectableCards();
} else {
- throw new RuntimeException("nothing to confirm");
+ app.getGameLogic().selectCard(card);
+ gameView.getGuiHandler().clearSelectableCards();
}
+ a = null;
+ b = null;
+ card = null;
+
gameView.noConfirm();
+ gameView.hideNoPower();
}
public void selectTsk(Color color) {
@@ -145,4 +147,8 @@ public void enter(MdgaState state) {
public void setSwap(boolean swap){
this.swap = swap;
}
+
+ public void force() {
+
+ }
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
index f23eda0e..4cb2ca07 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
@@ -1,48 +1,79 @@
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;
public class NotificationSynchronizer {
private final MdgaApp app;
private ArrayList notifications = new ArrayList<>();
+ private NanoTimer timer = new NanoTimer();
+ private float delay = 0;
+
+ private static final float STANDARD_DELAY = 2.5f;
+
+ public boolean waitForAnimation = false;
+
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
- public void addTestNotification(Notification n) {
- notifications.add(n);
- handleGame(n);
- }
-
public void update() {
- Notification n = app.getGameLogic().getNotification();
+ while (timer.getTimeInSeconds() >= delay) {
+ if(waitForAnimation) {
+ return;
+ }
- if(n != null) {
- switch (app.getState()) {
- case MAIN:
- handleMain(n);
- break;
- case LOBBY:
- handleLobby(n);
- break;
- case GAME:
- handleGame(n);
- break;
- case CEREMONY:
- handleCeremony(n);
- break;
- case NONE:
- throw new RuntimeException("no notification expected: " + n.toString());
+ 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) {
+ switch (app.getState()) {
+ case MAIN:
+ handleMain(n);
+ break;
+ case LOBBY:
+ handleLobby(n);
+ break;
+ case GAME:
+ handleGame(n);
+ break;
+ case CEREMONY:
+ handleCeremony(n);
+ break;
+ case NONE:
+ throw new RuntimeException("no notification expected: " + n.getClass().getName());
+ }
+
+ if(0 == MdgaApp.DEBUG_MULTIPLIER) {
+ delay = 0;
+ }
}
}
}
@@ -50,8 +81,10 @@ public void update() {
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: ");
+ throw new RuntimeException("notification not expected in main: "+ notification.getClass().getName());
}
}
@@ -61,16 +94,17 @@ private void handleLobby(Notification notification) {
if (notification instanceof TskSelectNotification n) {
lobbyView.setTaken(n.getColor(), true, n.isSelf(), n.getName());
} else if (notification instanceof StartDialogNotification) {
+ app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof TskUnselectNotification n) {
lobbyView.setTaken(n.getColor(), false, false, null);
} else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) {
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
} else if (notification instanceof GameNotification n) {
+ app.getGameView().setOwnColor(n.getOwnColor());
app.enter(MdgaState.GAME);
- ((GameView) app.getView()).setOwnColor(n.getOwnColor());
} else {
- throw new RuntimeException("notification not expected: " + notification.toString());
+ throw new RuntimeException("notification not expected in lobby: " + notification.getClass().getName());
}
}
@@ -79,12 +113,18 @@ private void handleGame(Notification notification) {
GuiHandler guiHandler = gameView.getGuiHandler();
BoardHandler boardHandler = gameView.getBoardHandler();
ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
+ Color ownColor = gameView.getOwnColor();
if (notification instanceof AcquireCardNotification n) {
- guiHandler.addCard(n.getBonusCard());
+ guiHandler.addCardOwn(n.getBonusCard());
+ app.getAcousticHandler().playSound(MdgaSound.BONUS);
+ delay = STANDARD_DELAY;
} else if (notification instanceof ActivePlayerNotification n) {
gameView.getGuiHandler().setActivePlayer(n.getColor());
- boardHandler.showDice(n.getColor());
+ boardHandler.hideDice();
+ 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();
@@ -116,60 +156,78 @@ private void handleGame(Notification notification) {
guiHandler.showDice();
} else if (notification instanceof DrawCardNotification n) {
guiHandler.drawCard(n.getColor());
+ delay = STANDARD_DELAY;
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
- } else if (notification instanceof InterruptNotification) {
- app.enter(MdgaState.LOBBY);
+ waitForAnimation = true;
+ } else if (notification instanceof InterruptNotification notification1) {
+ gameView.enterInterrupt(notification1.getColor());
} else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) {
//StartMove
boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
+ waitForAnimation = true;
}
else {
//InfieldMove
boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
+ waitForAnimation = true;
}
guiHandler.hideText();
} else if (notification instanceof ThrowPieceNotification n) {
- boardHandler.throwPieceAnim(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) {
- switch(n.getCard()){
- case SWAP -> guiHandler.swap();
- case TURBO -> guiHandler.turbo();
- case SHIELD -> guiHandler.shield();
- default -> throw new RuntimeException("invalid card");
+ 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());
} else if (notification instanceof ResumeNotification) {
- //TODO
+ 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);
+ waitForAnimation = true;
}
else {
- boardHandler.hideDice();
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
else guiHandler.showRolledDice(n.getEyes(), n.getColor());
}
} else if (notification instanceof SelectableCardsNotification n) {
guiHandler.setSelectableCards(n.getCards());
+ gameView.showNoPower();
} else if (notification instanceof ShieldActiveNotification n) {
boardHandler.shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification n) {
boardHandler.suppressShield(n.getPieceId());
} else if (notification instanceof StartDialogNotification) {
+ app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof SwapPieceNotification n) {
-// boardHandler.swapPieces(n.getFirstPiece(), n.getSecondPiece());
+ 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);
@@ -180,19 +238,20 @@ private void handleGame(Notification notification) {
boardHandler.outlineShield(n.getPieces());
modelSynchronizer.setSwap(false);
} else if (notification instanceof TurboActiveNotification){
- guiHandler.turbo();
+ //nothing
} else if (notification instanceof FinishNotification n){
guiHandler.finish(n.getColorFinished());
} else {
- throw new RuntimeException("notification not expected: " + notification.toString());
+ throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
}
}
private void handleCeremony(Notification notification) {
if (notification instanceof StartDialogNotification) {
+ app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else {
- throw new RuntimeException("notification not expected: " + notification.toString());
+ throw new RuntimeException("notification not expected in ceremony: " + notification.getClass().getName());
}
}
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Util.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Util.java
new file mode 100644
index 00000000..606c4beb
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Util.java
@@ -0,0 +1,47 @@
+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);
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/AcousticHandler.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/AcousticHandler.java
index 264197d4..20ac40c4 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/AcousticHandler.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/AcousticHandler.java
@@ -18,12 +18,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 +40,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 +64,8 @@ public void update() {
iterator.remove();
}
}
+
+ birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
}
/**
@@ -109,6 +115,48 @@ public void playSound(MdgaSound sound) {
case LEAVE:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND2, 0.6f, 0.0f));
break;
+ case JET:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.JET, 1.0f, 0.0f));
+ break;
+ case EXPLOSION:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
+ assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 0f));
+ assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 0f));
+ break;
+ case LOSE:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.LOSE, 1.0f, 0.0f));
+ break;
+ case BONUS:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.BONUS, 1.0f, 0.0f));
+ break;
+ case UI90:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.UI90, 1.0f, 0.0f));
+ break;
+ case MISSILE:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.MISSILE, 1.0f, 0.0f));
+ break;
+ case MATRIX:
+ assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
+ break;
+ 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;
}
@@ -130,6 +178,10 @@ public void playState(MdgaState state) {
}
MusicAsset asset = null;
+ birds.pause();
+
+ float pause = 0.0f;
+
switch (state) {
case MAIN:
playGame = false;
@@ -140,10 +192,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;
@@ -155,7 +209,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);
}
/**
@@ -431,7 +485,7 @@ public void setSoundVolume(float soundVolume) {
*/
float getMusicVolumeTotal() {
- return getMusicVolume() * getMainVolume();
+ return getMusicVolume() * getMainVolume() / 2;
}
/**
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MdgaSound.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MdgaSound.java
index 78898155..db6a819f 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MdgaSound.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MdgaSound.java
@@ -31,4 +31,17 @@ public enum MdgaSound {
OTHER_CONNECTED,
NOT_READY,
LEAVE,
+ JET,
+ EXPLOSION,
+ LOSE,
+ BONUS,
+ UI90,
+ MISSILE,
+ MATRIX,
+ TURRET_ROTATE,
+ TANK_SHOOT,
+ TANK_EXPLOSION,
+ SHIELD,
+ TURBO,
+ SWAP,
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MusicAsset.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MusicAsset.java
index 1b211523..e31d6d95 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MusicAsset.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/MusicAsset.java
@@ -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;
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/SoundAsset.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/SoundAsset.java
index 24497300..f2d232c0 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/SoundAsset.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/acoustic/SoundAsset.java
@@ -28,7 +28,23 @@ enum SoundAsset {
POWERUP("powerup.wav"),
ROBOT_READY("robotReady.wav"),
UNIT_READY("unitReady.wav"),
- CONNECTED("connected.wav");
+ JET("jet-overhead.wav"),
+ EXPLOSION_1("exp.ogg"),
+ EXPLOSION_2("exp2.ogg"),
+ THUNDER("thunder.ogg"),
+ UI90("ui90.ogg"),
+ BONUS("bonus.ogg"),
+ LOSE("lose.ogg"),
+ MISSILE("missile.ogg"),
+ MATRIX("matrix.wav"),
+ CONNECTED("connected.wav"),
+ TURRET_ROTATE("turret_rotate.ogg"),
+ TANK_SHOOT("tank_shoot.ogg"),
+ SHIELD("shield.ogg"),
+ SPEED("speed.ogg"),
+ SWAP("swap.ogg"),
+ ;
+
private final String path;
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ActionControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ActionControl.java
new file mode 100644
index 00000000..542a1f9c
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ActionControl.java
@@ -0,0 +1,19 @@
+package pp.mdga.client.animation;
+
+import pp.mdga.client.InitControl;
+
+public class ActionControl extends InitControl {
+ private final Runnable runnable;
+
+ public ActionControl(Runnable runnable){
+ this.runnable = runnable;
+ }
+
+
+
+ protected void action(){
+ if(null != runnable) {
+ runnable.run();
+ }
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Explosion.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Explosion.java
new file mode 100644
index 00000000..439e6ebc
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Explosion.java
@@ -0,0 +1,134 @@
+package pp.mdga.client.animation;
+
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.AbstractControl;
+import pp.mdga.client.MdgaApp;
+import pp.mdga.client.acoustic.MdgaSound;
+
+/**
+ * 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;
+ 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 Explosion(MdgaApp app, Node rootNode, Vector3f location) {
+ this.app = app;
+ this.rootNode = rootNode;
+ this.location = location;
+
+ this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
+ mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.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", 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().setVelocityVariation(0.4f);
+ fire.setStartSize(0.7f);
+ fire.setEndSize(1.8f);
+ fire.setGravity(0, 0, -0.1f);
+ fire.setLowLife(0.5f);
+ fire.setHighLife(2.2f);
+ fire.setParticlesPerSec(0);
+
+ fire.setLocalTranslation(location);
+
+ smoke = new ParticleEmitter("Effect2", Type.Triangle,40);
+ smoke.setMaterial(mat);
+ smoke.setImagesX(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().setVelocityVariation(0.5f);
+ smoke.setStartSize(0.8f);
+ smoke.setEndSize(1.5f);
+ smoke.setGravity(0, 0, -0.3f);
+ smoke.setLowLife(1.2f);
+ smoke.setHighLife(5.5f);
+ smoke.setParticlesPerSec(0);
+
+ smoke.setLocalTranslation(location);
+
+ app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
+ }
+
+ /**
+ * 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) {}
+ });
+ }
+}
+
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/FadeControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/FadeControl.java
new file mode 100644
index 00000000..8f4b2ccf
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/FadeControl.java
@@ -0,0 +1,70 @@
+package pp.mdga.client.animation;
+
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import pp.mdga.client.InitControl;
+
+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");
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/JetAnimation.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/JetAnimation.java
new file mode 100644
index 00000000..13dc23ee
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/JetAnimation.java
@@ -0,0 +1,197 @@
+package pp.mdga.client.animation;
+
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+import pp.mdga.client.Asset;
+import pp.mdga.client.MdgaApp;
+import pp.mdga.client.acoustic.MdgaSound;
+import pp.mdga.client.board.BoardHandler;
+import pp.mdga.client.view.GameView;
+
+import java.util.UUID;
+
+/**
+ * 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;
+ 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 Runnable actionAfter;
+
+ /**
+ * Constructor for the {@code JetAnimation} class.
+ *
+ * @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, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
+ Vector3f spawnPoint = targetPoint.add(170, 50, 50);
+
+ Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
+
+ Vector3f despawnPoint = targetPoint.add(-100, -100, 40);
+
+ this.app = app;
+ this.rootNode = rootNode;
+ this.spawnPoint = spawnPoint;
+ this.nodePoint = controlPoint;
+ this.despawnPoint = despawnPoint;
+ this.curveHeight = curveHeight;
+ this.animationDuration = animationDuration;
+
+ explosion = new Explosion(app, rootNode, targetPoint);
+ this.actionAfter = actionAfter;
+ }
+
+ /**
+ * 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);
+ spawnJet();
+ animateJet();
+ }
+
+ /**
+ * Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
+ */
+ private void spawnJet() {
+ jetModel = app.getAssetManager().loadModel(Asset.jet_noGear.getModelPath());
+ jetModel.setLocalTranslation(spawnPoint);
+ 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_noGear.getDiffPath()));
+ jetModel.setMaterial(mat);
+
+ rootNode.attachChild(jetModel);
+ }
+
+ /**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);
+ Vector3f controlPoint2 = nodePoint.add(0, curveHeight, 0);
+
+ BezierCurve3f curve = new BezierCurve3f(spawnPoint, controlPoint1, controlPoint2, despawnPoint);
+
+ app.getRootNode().addControl(new AbstractControl() {
+ private float elapsedTime = 0;
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ elapsedTime += tpf;
+ float progress = elapsedTime / animationDuration;
+
+ if(elapsedTime > 4.2f) {
+ explosion.trigger();
+ }
+
+ if (progress > 1) {
+ rootNode.detachChild(jetModel);
+ this.spatial.removeControl(this);
+ } else {
+ Vector3f currentPos = curve.interpolate(progress);
+ Vector3f direction = curve.interpolateDerivative(progress).normalizeLocal();
+ jetModel.setLocalTranslation(currentPos);
+ jetModel.lookAt(currentPos.add(direction), Vector3f.UNIT_Z);
+ jetModel.rotate(-FastMath.HALF_PI, 0, (float) Math.toRadians(-25));
+ }
+
+ if (elapsedTime > 6.0f) {
+ endAnim();
+ }
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {}
+ });
+ }
+
+ private void endAnim(){
+ actionAfter.run();
+ }
+
+ /**
+ * 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;
+ this.p2 = p2;
+ 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;
+ float uu = u * u;
+ float uuu = uu * u;
+ float ttt = tt * t;
+
+ Vector3f point = p0.mult(uuu);
+ point = point.add(p1.mult(3 * uu * t));
+ point = point.add(p2.mult(3 * u * tt));
+ point = point.add(p3.mult(ttt));
+ return point;
+ }
+
+ /**
+ * 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;
+
+ Vector3f derivative = p0.mult(-3 * u * u);
+ derivative = derivative.add(p1.mult(3 * u * u - 6 * u * t));
+ derivative = derivative.add(p2.mult(6 * u * t - 3 * tt));
+ derivative = derivative.add(p3.mult(3 * tt));
+ return derivative;
+ }
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MatrixAnimation.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MatrixAnimation.java
new file mode 100644
index 00000000..7e4170dc
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MatrixAnimation.java
@@ -0,0 +1,200 @@
+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.InitControl;
+import pp.mdga.client.MdgaApp;
+import pp.mdga.client.acoustic.MdgaSound;
+
+import java.util.*;
+
+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 activeEmitter = new ArrayList<>();
+ private ParticleEmitter radarEmitter = null;
+ private float timeElapsed = 0f;
+
+ private enum MatrixState{
+ RADAR_ON,
+ RADAR_OFF,
+ MATRIX_ON,
+ MATRIX_OFF
+ }
+
+ private MatrixState state;
+ public MatrixAnimation(MdgaApp app, Vector3f radarPos, Runnable runnable){
+ super(runnable);
+ this.app = app;
+ this.radarPos = radarPos;
+ }
+
+ @Override
+ protected void initSpatial() {
+ state = MatrixState.RADAR_ON;
+ timeElapsed = 0;
+ init = true;
+ radar();
+ }
+
+ @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);
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ app.getRootNode().detachChild(radarEmitter);
+ }
+ }, 3000);
+ }
+ }
+ 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();
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ for (ParticleEmitter particleEmitter : activeEmitter){
+ app.getRootNode().detachChild(particleEmitter);
+ }
+ }
+ }, 3000);
+ }
+ }
+ case MATRIX_OFF -> {
+ if(timeElapsed >= 0.5f){
+ init = false;
+ spatial.removeControl(this);
+ action();
+ }
+ }
+ }
+ }
+
+ private void turnOff(){
+ for (ParticleEmitter particleEmitter : activeEmitter){
+ particleEmitter.setParticlesPerSec(0f);
+ }
+ }
+
+ 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;
+ }
+
+ private void matrix(){
+ for(int i = 0; i < 5; i++){
+ particleStream(
+ generateMatrixColor(),
+ generateMatrixColor(),
+ getRandomFloat(0,1f),
+ getRandomPosition(),
+ getRandomFloat(1,2)
+ );
+ }
+ }
+
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MissileAnimation.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MissileAnimation.java
new file mode 100644
index 00000000..90cca657
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MissileAnimation.java
@@ -0,0 +1,185 @@
+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;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+import pp.mdga.client.Asset;
+import pp.mdga.client.MdgaApp;
+import pp.mdga.client.acoustic.MdgaSound;
+import pp.mdga.client.board.BoardHandler;
+
+import java.util.UUID;
+
+/**
+ * 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;
+ private final MdgaApp app;
+ private final Vector3f start;
+ private final Vector3f target;
+ private final float flightTime;
+ private Explosion explosion;
+ private Spatial missileModel;
+ private Runnable actionAfter;
+ private ParticleEmitter smoke;
+
+ private Node missileNode = new Node();
+
+ private final Material mat;
+
+ /**
+ * Constructor for the {@code MissileAnimation} class.
+ *
+ * @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, Vector3f target, float flightTime, Runnable actionAfter) {
+ this.app = app;
+ this.rootNode = rootNode;
+ this.flightTime = flightTime;
+ this.actionAfter = actionAfter;
+
+ explosion = new Explosion(app, rootNode, target);
+
+ 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);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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());
+ 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);
+
+ missileNode.setLocalTranslation(start);
+ missileNode.attachChild(missileModel);
+
+ rootNode.attachChild(missileNode);
+ }
+
+ /**
+ * 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() {
+ 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();
+ missileNode.detachChild(missileModel);
+ }
+
+ Vector3f currentPosition = computeParabolicPath(start, target, progress);
+ missileNode.setLocalTranslation(currentPosition);
+
+ Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
+ .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) {
+ }
+ });
+ }
+
+ private void endAnim(){
+ actionAfter.run();
+ }
+
+ /**
+ * Computes a position along a parabolic path at a given progress value {@code t}.
+ *
+ * @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);
+ midPoint.addLocal(0, 0, 20);
+
+ Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
+ Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
+ return FastMath.interpolateLinear(t, startToMid, midToTarget);
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MoveControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MoveControl.java
index 8aea4efc..5dacacbd 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MoveControl.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/MoveControl.java
@@ -1,7 +1,8 @@
package pp.mdga.client.animation;
import com.jme3.math.Vector3f;
-import pp.mdga.client.InitControl;
+
+import static pp.mdga.client.Util.*;
/**
* A control that smoothly moves a spatial from an initial position to an end position
@@ -12,16 +13,16 @@
* an ease-in-out curve to create a smooth start and stop effect.
*
*/
-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.
@@ -32,15 +33,22 @@ public class MoveControl extends InitControl {
* @param actionAfter A Runnable that will be executed after the movement finishes.
*/
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 +58,7 @@ public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
@Override
protected void initSpatial() {
moving = true;
- progress = 0;
+ timer = 0;
}
/**
@@ -63,10 +71,16 @@ 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();
+ 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();
}
/**
@@ -75,35 +89,11 @@ protected void controlUpdate(float tpf) {
*/
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);
- }
+
+
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellAnimation.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellAnimation.java
new file mode 100644
index 00000000..8a9325cb
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellAnimation.java
@@ -0,0 +1,167 @@
+package pp.mdga.client.animation;
+
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import pp.mdga.client.Asset;
+import pp.mdga.client.InitControl;
+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;
+import static com.jme3.material.Materials.UNSHADED;
+
+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;
+
+ public ShellAnimation(TankTopControl tankTopControl, MdgaApp app, Runnable actionAfter){
+ super(actionAfter);
+ this.tankTopControl = tankTopControl;
+ this.app = app;
+
+ }
+
+ @Override
+ protected void initSpatial() {
+ tankTopControl.rotate(spatial.getLocalTranslation(), this::shoot);
+ app.getAcousticHandler().playSound(MdgaSound.TURRET_ROTATE);
+ app.getRootNode().attachChild(createShell());
+ }
+
+ 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);
+ }
+
+ 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()));
+ }
+
+ 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;
+ }
+
+ 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)
+ );
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ action();
+ }
+ }, 800);
+ }
+
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellControl.java
new file mode 100644
index 00000000..72cd7bb8
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/ShellControl.java
@@ -0,0 +1,89 @@
+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;
+import pp.mdga.client.InitControl;
+
+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;
+
+ 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;
+ }
+
+ @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();
+ }
+
+ 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);
+ }
+
+ @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());
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Smoke.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Smoke.java
new file mode 100644
index 00000000..1786a656
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/animation/Smoke.java
@@ -0,0 +1,127 @@
+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) {}
+ });
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/BoardHandler.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/BoardHandler.java
index a00508e8..aaf07489 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/BoardHandler.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/BoardHandler.java
@@ -1,6 +1,9 @@
package pp.mdga.client.board;
import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
@@ -9,10 +12,10 @@
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
-import pp.mdga.client.animation.MoveControl;
+import pp.mdga.client.acoustic.MdgaSound;
+import pp.mdga.client.animation.*;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.game.Color;
-import pp.mdga.game.Piece;
import java.util.*;
@@ -50,10 +53,15 @@ public class BoardHandler {
// Flags and lists for handling piece selection and movement
private List selectableOwnPieces;
private List selectableEnemyPieces;
+ private Map selectedPieceNodeMap;
private List 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.
@@ -80,6 +88,7 @@ public void init() {
isInitialised = true;
selectableOwnPieces = new ArrayList<>();
selectableEnemyPieces = new ArrayList<>();
+ selectedPieceNodeMap = new HashMap<>();
outlineNodes = new ArrayList<>();
selectedOwnPiece = null;
selectedEnemyPiece = null;
@@ -146,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.
*
@@ -185,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;
}
@@ -200,7 +226,7 @@ private Spatial createModel(Asset asset, Vector3f pos, float rot) {
* @param y The y-coordinate on the grid
* @return The corresponding world position
*/
- private static Vector3f gridToWorld(int x, int y) {
+ public static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
@@ -216,16 +242,38 @@ 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 The type of control, extending {@code AbstractControl}.
+ * @return The control that was added to the spatial.
+ */
private T displayAndControl(AssetOnMap assetOnMap, T control) {
Spatial spatial = displayAsset(assetOnMap);
spatial.addControl(control);
return control;
}
+ /**
+ * 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());
}
+ /**
+ * 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> map, Color color, AssetOnMap assetOnMap){
List homeNodes = addItemToMapList(map, color, displayAndControl(assetOnMap, new NodeControl(app, fpp)));
if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
@@ -257,18 +305,30 @@ private float getRotationMove(Vector3f prev, Vector3f next) {
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);
}
+ /**
+ * 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 The type of items in the list.
+ * @param The type of the key in the map.
+ * @return The updated list associated with the specified key.
+ */
private List addItemToMapList(Map> map, E key, T item){
List list = map.getOrDefault(key, new ArrayList<>());
list.add(item);
@@ -276,12 +336,27 @@ private List addItemToMapList(Map> map, E key, T item){
return list;
}
+ /**
+ * 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 The type of items in the list.
+ * @param The type of the key in the map.
+ */
private void removeItemFromMapList(Map> map, E key, T item){
List list = map.getOrDefault(key, new ArrayList<>());
list.remove(item);
map.put(key, list);
}
+ /**
+ * 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());
}
@@ -367,6 +442,7 @@ private void moveHomePiece(UUID uuid, int index){
pieceControl.setRotation(getRotationMove(firstHomeNode.getLocation(), lastHomeNode.getLocation()));
app.getModelSynchronize().animationEnd();
+ app.getModelSynchronize().animationEnd();
}
/**
@@ -398,6 +474,7 @@ private void movePieceStart(UUID uuid, int nodeIndex){
removeItemFromMapList(waitingPiecesMap, color, pieceControl);
waitingNodes.get(color).remove(uuid);
app.getModelSynchronize().animationEnd();
+ app.getModelSynchronize().animationEnd();
}
/**
@@ -411,6 +488,7 @@ private void movePiece(UUID uuid, int curIndex, int moveIndex){
movePieceRek(uuid, curIndex, moveIndex);
app.getModelSynchronize().animationEnd();
+ app.getModelSynchronize().animationEnd();
}
/**
@@ -443,6 +521,7 @@ private void throwPiece(UUID uuid){
// Synchronisation oder Animation
pieceControl.rotateInit();
+ app.getAcousticHandler().playSound(MdgaSound.LOSE);
app.getModelSynchronize().animationEnd();
}
@@ -453,7 +532,6 @@ private void throwPiece(UUID uuid){
* @param uuid the UUID of the piece to shield
*/
public void shieldPiece(UUID uuid){
-
pieces.get(uuid).activateShield();
}
@@ -463,7 +541,6 @@ public void shieldPiece(UUID uuid){
* @param uuid the UUID of the piece to unshield
*/
public void unshieldPiece(UUID uuid){
-
pieces.get(uuid).deactivateShield();
}
@@ -531,6 +608,7 @@ public void outlineMove(List pieces, List moveIndexe, List K getKeyByValue(Map map, V value) {
* @param moveIndex the target index to animate the piece to
*/
public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
+
pieces.get(uuid).getSpatial().addControl(new MoveControl(
infield.get(curIndex).getLocation(),
infield.get(moveIndex).getLocation(),
@@ -720,10 +817,55 @@ public void movePieceStartAnim(UUID uuid, int moveIndex){
*/
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))
+ );
+ }
+
+ 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.
+ *
+ * @param uuid the UUID of the piece to animate
+ */
+ private void throwBomb(UUID uuid) {
+ Vector3f targetPoint = pieces.get(uuid).getLocation();
+
+ 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,()-> {
+ piece.addControl(new FadeControl(1,1,0,
+ () -> {
+ throwPiece(uuid);
+ piece.addControl(new FadeControl(1,0,1));
+ }
+ ));
+ }));
+ }
+
+ 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)));
}
/**
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/CameraHandler.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/CameraHandler.java
index 57da8393..431f74a7 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/CameraHandler.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/CameraHandler.java
@@ -7,6 +7,8 @@
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.FXAAFilter;
+import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
@@ -37,6 +39,8 @@ public class CameraHandler {
private Color ownColor;
private boolean init;
private boolean initRot;
+ private SSAOFilter ssaoFilter;
+ private FXAAFilter fxaaFilter;
/**
* Constructor for the CameraHandler. Initializes the camera settings and lighting.
@@ -65,6 +69,9 @@ public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
dlsf.setShadowIntensity(0.7f);
+ ssaoFilter = new SSAOFilter(6, 10f, 0.33f, 0.61f);
+// ssaoFilter = new SSAOFilter();
+ fxaaFilter = new FXAAFilter();
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI*1,0,FastMath.HALF_PI*0.2f);
@@ -82,6 +89,8 @@ public void init(Color ownColor) {
app.getRootNode().addLight(ambient);
app.getRootNode().attachChild(sky);
fpp.addFilter(dlsf);
+ fpp.addFilter(ssaoFilter);
+ fpp.addFilter(fxaaFilter);
init = true;
initRot = true;
this.ownColor = ownColor;
@@ -93,15 +102,18 @@ 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);
}
/**
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/MapLoader.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/MapLoader.java
index 9d283f8f..fe994855 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/MapLoader.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/MapLoader.java
@@ -107,8 +107,11 @@ private static Asset getLoadedAsset(String assetName) {
case "radar" -> Asset.radar;
case "ship" -> Asset.ship;
case "tank" -> Asset.tank;
- case "tree_small" -> Asset.treeSmall;
- case "tree_big" -> Asset.treeBig;
+ 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);
};
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/NodeControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/NodeControl.java
index d3f1d80e..9f37b9be 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/NodeControl.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/NodeControl.java
@@ -16,6 +16,12 @@ public class NodeControl extends OutlineControl {
private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
+ private static final ColorRGBA OUTLINE_SELECT_COLOR = ColorRGBA.Cyan;
+ private static final int OUTLINE_SELECT_WIDTH = 8;
+ private static final ColorRGBA OUTLINE_HOVER_COLOR = ColorRGBA.Yellow;
+ private static final int OUTLINE_HOVER_WIDTH = 6;
+ private boolean select = false;
+ private boolean highlight = false;
/**
* Constructs a {@link NodeControl} with the specified application and post processor.
@@ -38,11 +44,34 @@ 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() {
+ public void highlight(){
+ highlight = true;
super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
+
+ public void unHighlight(){
+ highlight = false;
+ deOutline();
+ }
+
+ public void select(){
+ select = true;
+ super.outline(OUTLINE_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
+ }
+
+ public void unSelect(){
+ select = false;
+ if(highlight) highlight();
+ else deOutline();
+ }
+
+ public void hover(){
+ super.outline(OUTLINE_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
+ }
+
+ public void hoverOff(){
+ if(select) select();
+ else if(highlight) highlight();
+ else deOutline();
+ }
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/OutlineControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/OutlineControl.java
index f0dfba51..aa1a999b 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/OutlineControl.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/OutlineControl.java
@@ -18,17 +18,37 @@ public class OutlineControl extends InitControl {
private static final int THICKNESS_DEFAULT = 6;
private MdgaApp app;
-
+ /**
+ * 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){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera(), app);
}
+ /**
+ * Constructs an {@code OutlineControl} with default thickness, allowing a custom camera to be specified.
+ *
+ * @param app The main application managing the outline control.
+ * @param fpp The {@code FilterPostProcessor} used for post-processing effects.
+ * @param cam The camera used for rendering the outlined objects.
+ */
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
+ /**
+ * Constructs an {@code OutlineControl} with a specified thickness and custom camera.
+ *
+ * @param app The main application managing the outline control.
+ * @param fpp The {@code FilterPostProcessor} used for post-processing effects.
+ * @param cam The camera used for rendering the outlined objects.
+ * @param thickness The thickness of the outline.
+ */
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thickness){
this.app = app;
outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
@@ -61,6 +81,11 @@ public void deOutline(){
outlineOwn.deselect(spatial);
}
+ /**
+ * Retrieves the instance of the {@code MdgaApp} associated with this control.
+ *
+ * @return The {@code MdgaApp} instance.
+ */
public MdgaApp getApp() {
return app;
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/PieceControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/PieceControl.java
index 4a75533d..d317342f 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/PieceControl.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/PieceControl.java
@@ -113,6 +113,7 @@ public Vector3f getLocation(){
protected void controlUpdate(float delta) {
if(shieldRing != null){
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
+ shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,0,SHIELD_Z)));
}
}
@@ -141,7 +142,7 @@ public void initSpatial(){
}
public void rotateInit() {
-// rotate(rotation - initRotation);
+ setRotation(initRotation);
}
/**
@@ -149,6 +150,9 @@ public void rotateInit() {
* This adds a visual shield effect in the form of a rotating ring.
*/
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));
@@ -278,4 +282,5 @@ public boolean isSelectable() {
public void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
+
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/board/TankTopControl.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/TankTopControl.java
new file mode 100644
index 00000000..d6dbb128
--- /dev/null
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/board/TankTopControl.java
@@ -0,0 +1,105 @@
+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;
+
+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;
+
+ @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();
+ }
+ }
+
+ 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
+ }
+
+ 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
+ }
+
+ 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
+ }
+}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/button/SliderButton.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/button/SliderButton.java
index 4a4ebf51..b4937576 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/button/SliderButton.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/button/SliderButton.java
@@ -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);
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/AudioSettingsDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/AudioSettingsDialog.java
index 80f5bf4f..0e8ed643 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/AudioSettingsDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/AudioSettingsDialog.java
@@ -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,6 +86,10 @@ 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) {
return;
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/CeremonyDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/CeremonyDialog.java
index 97c47e10..fd688bc3 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/CeremonyDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/CeremonyDialog.java
@@ -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> 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 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 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;
@@ -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;
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/Dialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/Dialog.java
index 46645895..d6e25481 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/Dialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/Dialog.java
@@ -4,30 +4,53 @@
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();
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/HostDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/HostDialog.java
index 829aff09..f09f9d80 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/HostDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/HostDialog.java
@@ -12,6 +12,10 @@
import java.util.prefs.Preferences;
+/**
+ * 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;
@@ -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();
}
-
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/InterruptDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/InterruptDialog.java
index 4fd87803..201ac65f 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/InterruptDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/InterruptDialog.java
@@ -1,4 +1,84 @@
package pp.mdga.client.dialog;
-public class InterruptDialog {
+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;
+
+ private LabelButton label;
+
+ 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));
+
+ 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:
+ text = "Luftwaffe";
+ break;
+ case ARMY:
+ text = "Heer";
+ break;
+ case NAVY:
+ text = "Marine";
+ break;
+ case CYBER:
+ text = "CIR";
+ break;
+ }
+ }
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/JoinDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/JoinDialog.java
index 067ba3c7..5a1657a0 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/JoinDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/JoinDialog.java
@@ -13,6 +13,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 +28,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 +57,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 +68,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,34 +79,70 @@ 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) {
+ try {
+ network.disconnect();
+ } catch (Exception e) {
+ System.err.println("Error while disconnecting: " + e.getMessage());
+ }
+ }
+ }
}
diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/NetworkDialog.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/NetworkDialog.java
index 15c680e9..c3d204c9 100644
--- a/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/NetworkDialog.java
+++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/dialog/NetworkDialog.java
@@ -8,27 +8,55 @@
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;
private String hostname;
private int portNumber;
private Future