Compare commits
	
		
			324 Commits
		
	
	
		
			dev/client
			...
			Version1.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b891e78647 | ||
| 
						 | 
					ff41183643 | ||
| 
						 | 
					3658a91b7b | ||
| 
						 | 
					1e5c5bd5eb | ||
| 
						 | 
					23d4bf31a7 | ||
| 
						 | 
					5826d93be4 | ||
| 
						 | 
					9c3b949e5a | ||
| 
						 | 
					81223653f9 | ||
| 
						 | 
					28c06c931e | ||
| 
						 | 
					f93da332b3 | ||
| 
						 | 
					379e4bcec1 | ||
| 
						 | 
					dc7dae5db9 | ||
| 
						 | 
					702154c018 | ||
| 
						 | 
					032cd76ec2 | ||
| 
						 | 
					5c8dce0626 | ||
| 
						 | 
					911ab42101 | ||
| 
						 | 
					ea3f4df636 | ||
| 
						 | 
					065a626307 | ||
| 
						 | 
					2ec9b3a246 | ||
| 
						 | 
					48d6516073 | ||
| 
						 | 
					0d4685f3c2 | ||
| 
						 | 
					d12b0b6a77 | ||
| 
						 | 
					4dda9229b3 | ||
| 
						 | 
					e6453b2f1b | ||
| 
						 | 
					af8ab21e5f | ||
| 
						 | 
					d21e963d33 | ||
| 
						 | 
					1f3b07709c | ||
| 
						 | 
					a6215b9986 | ||
| 
						 | 
					6381d7b3b9 | ||
| 
						 | 
					176affa9c5 | ||
| 
						 | 
					67ea9ede18 | ||
| 
						 | 
					173cbe31e5 | ||
| 
						 | 
					c13152bb29 | ||
| 
						 | 
					a3d0056f7f | ||
| 
						 | 
					06dcc4c0e4 | ||
| 
						 | 
					61e168cefb | ||
| 
						 | 
					cecd7e1a23 | ||
| 
						 | 
					f9772732c4 | ||
| 
						 | 
					f09766eb42 | ||
| 
						 | 
					62e3dd7932 | ||
| 
						 | 
					d80499a18a | ||
| 
						 | 
					4b665c4cf2 | ||
| 
						 | 
					3048caf5c9 | ||
| 
						 | 
					b8df688ba3 | ||
| 
						 | 
					8201ab4b63 | ||
| 
						 | 
					14a407b7d5 | ||
| 
						 | 
					170a282077 | ||
| 
						 | 
					5854609b12 | ||
| 
						 | 
					ea25f66dae | ||
| 
						 | 
					c069a61f2d | ||
| 
						 | 
					07029407d8 | ||
| 
						 | 
					42a73ceb24 | ||
| 
						 | 
					42283f29a0 | ||
| 
						 | 
					574f8bb681 | ||
| 
						 | 
					c1641cdac1 | ||
| 
						 | 
					f251bad97f | ||
| 
						 | 
					f8a021b310 | ||
| 
						 | 
					aa44b84648 | ||
| 
						 | 
					d8816be811 | ||
| 
						 | 
					e61265fc99 | ||
| 
						 | 
					9e2a0819ca | ||
| 
						 | 
					cc756453a1 | ||
| 
						 | 
					f23f1f5abc | ||
| 
						 | 
					cc8b2abbde | ||
| 
						 | 
					8d7701cbf2 | ||
| 
						 | 
					874bba0fe9 | ||
| 
						 | 
					3b73d1229b | ||
| 
						 | 
					9f1dafece2 | ||
| 
						 | 
					997b31eba2 | ||
| 
						 | 
					148b769232 | ||
| 
						 | 
					f0f4e0eb5e | ||
| 
						 | 
					bda1a40cdb | ||
| 
						 | 
					0c8cb92fe8 | ||
| 
						 | 
					e952233d20 | ||
| 
						 | 
					9f8fd9c22f | ||
| 
						 | 
					0432dd6bd7 | ||
| 
						 | 
					4d31fc098b | ||
| 
						 | 
					40a61daa58 | ||
| 
						 | 
					6e54ad0196 | ||
| 
						 | 
					11fa303f1b | ||
| 
						 | 
					d65111680b | ||
| 
						 | 
					485b8f36d7 | ||
| 
						 | 
					4eee62079e | ||
| 
						 | 
					888b52f314 | ||
| 
						 | 
					bc2c80dd27 | ||
| 
						 | 
					50f0cdfce6 | ||
| 
						 | 
					0c7030659a | ||
| 
						 | 
					30997eb571 | ||
| 
						 | 
					ea1978d5a8 | ||
| 
						 | 
					1ead0e2e9e | ||
| 
						 | 
					70c821f2ac | ||
| 
						 | 
					8867e8156f | ||
| 
						 | 
					36c2f2efec | ||
| 
						 | 
					b8d992590f | ||
| 
						 | 
					a86a146091 | ||
| 
						 | 
					3e060897fc | ||
| 
						 | 
					fe95a7c159 | ||
| 
						 | 
					d60c0347cc | ||
| 
						 | 
					bbde17aa27 | ||
| 
						 | 
					38c865d135 | ||
| 
						 | 
					91d4718179 | ||
| 
						 | 
					bb1bf22713 | ||
| 
						 | 
					380341114c | ||
| 
						 | 
					f56eb8cd60 | ||
| 
						 | 
					3797da1246 | ||
| 
						 | 
					e79a1168c0 | ||
| 
						 | 
					6ab8f2d90d | ||
| 
						 | 
					ece249cf66 | ||
| 
						 | 
					e95f0866d5 | ||
| 
						 | 
					0fce2fb5d2 | ||
| 
						 | 
					0010717411 | ||
| 
						 | 
					cf9acf981f | ||
| 
						 | 
					e2a4f7f85c | ||
| 
						 | 
					1ffabe6b19 | ||
| 
						 | 
					0ba5a2f9fd | ||
| 
						 | 
					d0afaa57f5 | ||
| 
						 | 
					00a79ddce3 | ||
| 
						 | 
					bce6a1e0eb | ||
| 
						 | 
					37dcc0122c | ||
| 
						 | 
					4007036cb2 | ||
| 
						 | 
					5b958740c4 | ||
| 
						 | 
					06cb25b6a9 | ||
| 
						 | 
					afec74416f | ||
| 
						 | 
					ceb9e48f55 | ||
| 
						 | 
					f8179f191f | ||
| 
						 | 
					ec77b9a6ef | ||
| 
						 | 
					2d01e4b31b | ||
| 
						 | 
					0be25ecb29 | ||
| 
						 | 
					1210324194 | ||
| 
						 | 
					569ce39d2a | ||
| 
						 | 
					80989310e5 | ||
| 
						 | 
					a2f57bcaae | ||
| 
						 | 
					68702f11a4 | ||
| 
						 | 
					13948ec4bb | ||
| 
						 | 
					570c915964 | ||
| 
						 | 
					ac719c55e6 | ||
| 
						 | 
					189e8a7ec3 | ||
| 
						 | 
					5d2ece41b7 | ||
| 
						 | 
					7f92d8183b | ||
| 
						 | 
					6bc769f5bb | ||
| 
						 | 
					a21794ddc1 | ||
| 
						 | 
					e699f4556e | ||
| 
						 | 
					a56f68efdc | ||
| 
						 | 
					55328e9e69 | ||
| 
						 | 
					50dc634a94 | ||
| 
						 | 
					d37b9ee269 | ||
| 
						 | 
					3937b75bf7 | ||
| 
						 | 
					7d5b8a6b13 | ||
| 
						 | 
					39d5b43cb8 | ||
| 
						 | 
					de0f04b5b7 | ||
| 
						 | 
					6a4bdfa455 | ||
| 
						 | 
					db16b0d8b6 | ||
| 
						 | 
					08db05a7d6 | ||
| 
						 | 
					e47fcd1643 | ||
| 
						 | 
					dfd361d8be | ||
| 
						 | 
					bf3d800c10 | ||
| 
						 | 
					8943dfb15e | ||
| 
						 | 
					64f11eb99b | ||
| 
						 | 
					0e9ff609ec | ||
| 
						 | 
					d37db68838 | ||
| 
						 | 
					25fff99ff0 | ||
| 
						 | 
					2dbdb1e17b | ||
| 
						 | 
					f954a24b32 | ||
| 
						 | 
					c7a05011ff | ||
| 
						 | 
					15b7aa9e8c | ||
| 
						 | 
					84a50ec215 | ||
| 
						 | 
					e05b057190 | ||
| 
						 | 
					9ea6837e89 | ||
| 
						 | 
					a26a3d171f | ||
| 
						 | 
					a2e85cebc9 | ||
| 
						 | 
					3fe2d90d75 | ||
| 
						 | 
					4181456598 | ||
| 
						 | 
					6f34b1fe33 | ||
| 
						 | 
					659fef6c50 | ||
| 
						 | 
					9c7a13c568 | ||
| 
						 | 
					14699e3edf | ||
| 
						 | 
					961da990ce | ||
| 
						 | 
					be8d4b2d6e | ||
| 
						 | 
					38687b6d25 | ||
| 
						 | 
					c038073bad | ||
| 
						 | 
					5bccee96a2 | ||
| 
						 | 
					ecb751824c | ||
| 
						 | 
					a7969d7a68 | ||
| 
						 | 
					3a1b17ed01 | ||
| 
						 | 
					3dd6fc9f37 | ||
| 
						 | 
					3ae9028b82 | ||
| 
						 | 
					536c14bf7c | ||
| 
						 | 
					fb3a663db1 | ||
| 
						 | 
					b91d448ee6 | ||
| 
						 | 
					935577462b | ||
| 
						 | 
					f79d590620 | ||
| 
						 | 
					69d160d0fd | ||
| 
						 | 
					de592cfa48 | ||
| 
						 | 
					8922cb8f1c | ||
| 
						 | 
					f10df60ad3 | ||
| 
						 | 
					1c87b566e9 | ||
| 
						 | 
					6ecf5a66bf | ||
| 
						 | 
					3f93d6e569 | ||
| 
						 | 
					cc72b82be9 | ||
| 
						 | 
					41ac04f69d | ||
| 
						 | 
					cb60cb1c42 | ||
| 
						 | 
					61d67fd833 | ||
| 
						 | 
					4ca7c38170 | ||
| 
						 | 
					e457fe23d4 | ||
| 
						 | 
					b86aeb63e6 | ||
| 
						 | 
					daa8c0bf9d | ||
| 
						 | 
					6487bafed1 | ||
| 
						 | 
					8af3b2d9d4 | ||
| 
						 | 
					d38690ea48 | ||
| 
						 | 
					daa7d31bdd | ||
| 
						 | 
					5e79a4a64c | ||
| 
						 | 
					8e2fc6c1a1 | ||
| 
						 | 
					5a12d8e96f | ||
| 
						 | 
					3af6e94920 | ||
| 
						 | 
					4efc557849 | ||
| 
						 | 
					1646526ce1 | ||
| 
						 | 
					904aa17358 | ||
| 
						 | 
					f4e774ee5b | ||
| 
						 | 
					76b3e18dbe | ||
| 
						 | 
					d4cbd0dda6 | ||
| 
						 | 
					7a189a98e3 | ||
| 
						 | 
					00902d2e6b | ||
| 
						 | 
					7256cde020 | ||
| 
						 | 
					b817af29b5 | ||
| 
						 | 
					fa18ae4280 | ||
| 
						 | 
					521c7439c2 | ||
| 
						 | 
					84c5553154 | ||
| 
						 | 
					2c94737023 | ||
| 
						 | 
					d3d75d7f49 | ||
| 
						 | 
					5e67b2d0c7 | ||
| 
						 | 
					992efd403d | ||
| 
						 | 
					2d7fddf09a | ||
| 
						 | 
					f7c886f084 | ||
| 
						 | 
					a1d85177c6 | ||
| 
						 | 
					e27d325faa | ||
| 
						 | 
					bd98f301c8 | ||
| 
						 | 
					a7850f7d43 | ||
| 
						 | 
					3040595193 | ||
| 
						 | 
					5d45cf2934 | ||
| 
						 | 
					a5d949b7e5 | ||
| 
						 | 
					f4a224621e | ||
| 
						 | 
					876c238db3 | ||
| 
						 | 
					b61b8214fe | ||
| 
						 | 
					5dc3124533 | ||
| 
						 | 
					c25e17fd90 | ||
| 
						 | 
					5e27473875 | ||
| 
						 | 
					1be2d6aa13 | ||
| 
						 | 
					ab9de3acf8 | ||
| 
						 | 
					2ab19d0fc8 | ||
| 
						 | 
					f489357bbb | ||
| 
						 | 
					34cde15a0d | ||
| 
						 | 
					1e52df2812 | ||
| 
						 | 
					884c5afc4e | ||
| 
						 | 
					d6e44c2d29 | ||
| 
						 | 
					d593233fa3 | ||
| 
						 | 
					d1b4aa9dda | ||
| 
						 | 
					25b6480c42 | ||
| 
						 | 
					3dd81ea02c | ||
| 
						 | 
					e6dbedab0f | ||
| 
						 | 
					f5b0481d3c | ||
| 
						 | 
					f9f381ac2d | ||
| 
						 | 
					ee94d901f4 | ||
| 
						 | 
					bca02bfe4b | ||
| 
						 | 
					acdf5ec6a9 | ||
| 
						 | 
					41d6f70d51 | ||
| 
						 | 
					4c064cb615 | ||
| 
						 | 
					121f47d070 | ||
| 
						 | 
					ae436589a2 | ||
| 
						 | 
					bc399b1bf9 | ||
| 
						 | 
					98a6f2e689 | ||
| 
						 | 
					9a07375fed | ||
| 
						 | 
					498c2eb054 | ||
| 
						 | 
					ce55ca8bb5 | ||
| 
						 | 
					9c729059bf | ||
| 
						 | 
					3b7ef37364 | ||
| 
						 | 
					ec295c94f1 | ||
| 
						 | 
					adfe2b94b8 | ||
| 
						 | 
					69108063a0 | ||
| 
						 | 
					16e7488fae | ||
| 
						 | 
					c9c99709ba | ||
| 
						 | 
					e069017375 | ||
| 
						 | 
					8b27ccce22 | ||
| 
						 | 
					8c22d935a9 | ||
| 
						 | 
					c8d7d91de0 | ||
| 
						 | 
					389d1b6056 | ||
| 
						 | 
					4430b37581 | ||
| 
						 | 
					e5abcbdc8c | ||
| 
						 | 
					e14b8cb510 | ||
| 
						 | 
					bf84bfa0f9 | ||
| 
						 | 
					0c49d7ed1c | ||
| 
						 | 
					2ba6a22422 | ||
| 
						 | 
					c37bac4614 | ||
| 
						 | 
					06b37584cb | ||
| 
						 | 
					0c42a2df88 | ||
| 
						 | 
					d75d704878 | ||
| 
						 | 
					6d3c733f91 | ||
| 
						 | 
					f96da2c46c | ||
| 
						 | 
					1a079dad44 | ||
| 
						 | 
					32f49a6181 | ||
| 
						 | 
					525809899e | ||
| 
						 | 
					fd9708752c | ||
| 
						 | 
					5a9f7a8118 | ||
| 
						 | 
					236d3db930 | ||
| 
						 | 
					29c6b13300 | ||
| 
						 | 
					6059e93276 | ||
| 
						 | 
					f2eeb6dab4 | ||
| 
						 | 
					2e1fe3c050 | ||
| 
						 | 
					2ac2de645b | ||
| 
						 | 
					d39f85fbe9 | ||
| 
						 | 
					960a57caba | ||
| 
						 | 
					36631df2e9 | ||
| 
						 | 
					df27c23cd5 | ||
| 
						 | 
					acd64d1507 | ||
| 
						 | 
					76f86c8a66 | ||
| 
						 | 
					308b592b65 | ||
| 
						 | 
					c4e7fb1d41 | ||
| 
						 | 
					aacc0440b3 | ||
| 
						 | 
					43c0e3bcc7 | ||
| 
						 | 
					95635f5fb7 | ||
| 
						 | 
					4904b32ea3 | ||
| 
						 | 
					b00219c4fb | ||
| 
						 | 
					12cf5f3e71 | ||
| 
						 | 
					77b0207214 | ||
| 
						 | 
					a18165bc02 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +1,5 @@
 | 
			
		||||
 | 
			
		||||
.run/
 | 
			
		||||
.gradle
 | 
			
		||||
build/
 | 
			
		||||
#!gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
 | 
			
		||||
    <option name="ALTERNATIVE_JRE_PATH" value="temurin-20" />
 | 
			
		||||
    <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
 | 
			
		||||
    <option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp" />
 | 
			
		||||
    <module name="Projekte.mdga.client.main" />
 | 
			
		||||
    <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties -ea" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
 | 
			
		||||
    <extension name="coverage">
 | 
			
		||||
      <pattern>
 | 
			
		||||
        <option name="PATTERN" value="pp.mdga.client.board.outline.*" />
 | 
			
		||||
        <option name="ENABLED" value="true" />
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -26,3 +26,14 @@ implementation project(":mdga:model")
 | 
			
		||||
    mainClass = 'pp.mdga.client.MdgaApp'
 | 
			
		||||
    applicationName = 'MDGA'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register('fatJar', Jar) {
 | 
			
		||||
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 | 
			
		||||
    manifest {
 | 
			
		||||
        attributes 'Main-Class': 'pp.mdga.client.MdgaApp'
 | 
			
		||||
    }
 | 
			
		||||
    from {
 | 
			
		||||
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
 | 
			
		||||
    }
 | 
			
		||||
    with jar
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ public enum Asset {
 | 
			
		||||
    cir,
 | 
			
		||||
    heer,
 | 
			
		||||
    jet,
 | 
			
		||||
    jet_noGear("Models/jet/jet_noGear.j3o", "Models/jet/jet_diff.png"),
 | 
			
		||||
    lw,
 | 
			
		||||
    marine,
 | 
			
		||||
    node_home_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
 | 
			
		||||
@@ -40,7 +41,11 @@ public enum Asset {
 | 
			
		||||
    shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
 | 
			
		||||
    dice,
 | 
			
		||||
    missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
 | 
			
		||||
    ;
 | 
			
		||||
    tankShoot("Models/tank/tankShoot_bot.j3o", "Models/tank/tank_diff.png"),
 | 
			
		||||
    tankShootTop("Models/tank/tankShoot_top.j3o", "Models/tank/tank_diff.png"),
 | 
			
		||||
    treesSmallBackground("Models/treeSmall/treesSmallBackground.j3o", "Models/treeSmall/treeSmall_diff.png", 1.2f),
 | 
			
		||||
    treesBigBackground("Models/treeBig/treesBigBackground.j3o", "Models/treeBig/treeBig_diff.png", 1.2f),
 | 
			
		||||
    shell;
 | 
			
		||||
 | 
			
		||||
    private final String modelPath;
 | 
			
		||||
    private final String diffPath;
 | 
			
		||||
@@ -77,7 +82,8 @@ public enum Asset {
 | 
			
		||||
    Asset(String modelPath) {
 | 
			
		||||
        String folderFileName = "./" + ROOT + name() + "/" + name();
 | 
			
		||||
        this.modelPath = modelPath;
 | 
			
		||||
        this.diffPath = folderFileName + "_diff.png";;
 | 
			
		||||
        this.diffPath = folderFileName + "_diff.png";
 | 
			
		||||
        ;
 | 
			
		||||
        this.size = 1f;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -100,7 +106,7 @@ public enum Asset {
 | 
			
		||||
     * @param diffPath  Path to the diffuse texture file.
 | 
			
		||||
     * @param size      Scaling factor for the asset.
 | 
			
		||||
     */
 | 
			
		||||
    Asset(String modelPath, String diffPath, float size){
 | 
			
		||||
    Asset(String modelPath, String diffPath, float size) {
 | 
			
		||||
        this.modelPath = modelPath;
 | 
			
		||||
        this.diffPath = diffPath;
 | 
			
		||||
        this.size = size;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,19 +12,14 @@
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import com.jme3.scene.control.Control;
 | 
			
		||||
import pp.mdga.client.board.NodeControl;
 | 
			
		||||
import pp.mdga.client.board.OutlineControl;
 | 
			
		||||
import pp.mdga.client.board.OutlineOEControl;
 | 
			
		||||
import pp.mdga.client.board.PieceControl;
 | 
			
		||||
import pp.mdga.client.gui.CardControl;
 | 
			
		||||
import pp.mdga.client.gui.DiceControl;
 | 
			
		||||
import pp.mdga.client.view.GameView;
 | 
			
		||||
import pp.mdga.game.BonusCard;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.game.Piece;
 | 
			
		||||
import pp.mdga.notification.FinishNotification;
 | 
			
		||||
import pp.mdga.notification.MovePieceNotification;
 | 
			
		||||
import pp.mdga.notification.SelectableCardsNotification;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
@@ -38,7 +33,7 @@ public class InputSynchronizer {
 | 
			
		||||
    private float rotationAngle = 180f;
 | 
			
		||||
    private int scrollValue = 0;
 | 
			
		||||
    private CardControl hoverCard;
 | 
			
		||||
    private PieceControl hoverPiece;
 | 
			
		||||
    private OutlineOEControl hoverPiece;
 | 
			
		||||
 | 
			
		||||
    private boolean clickAllowed = true;
 | 
			
		||||
 | 
			
		||||
@@ -60,14 +55,19 @@ public class InputSynchronizer {
 | 
			
		||||
        setupInput();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the rotation angle based on user input.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf The time per frame.
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        if(isRotateLeft && isRotateRight) {
 | 
			
		||||
        if (isRotateLeft && isRotateRight) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if(isRotateLeft) {
 | 
			
		||||
        if (isRotateLeft) {
 | 
			
		||||
            rotationAngle += 180 * tpf;
 | 
			
		||||
        }
 | 
			
		||||
        if(isRotateRight) {
 | 
			
		||||
        if (isRotateRight) {
 | 
			
		||||
            rotationAngle -= 180 * tpf;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -94,6 +94,7 @@ private void setupInput() {
 | 
			
		||||
        inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
 | 
			
		||||
        inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UUID p = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles action-based input events such as key presses and mouse clicks.
 | 
			
		||||
@@ -110,33 +111,25 @@ public void onAction(String name, boolean isPressed, float tpf) {
 | 
			
		||||
            if (name.equals("RotateRightMouse")) {
 | 
			
		||||
                rightMousePressed = isPressed;
 | 
			
		||||
            }
 | 
			
		||||
            if(name.equals("Click") && isPressed) {
 | 
			
		||||
                if(!clickAllowed) {
 | 
			
		||||
            if (name.equals("Click") && isPressed) {
 | 
			
		||||
                if (!clickAllowed) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (app.getView() instanceof GameView gameView) {
 | 
			
		||||
                    DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
 | 
			
		||||
                    CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
 | 
			
		||||
                    OutlineControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineControl.class);
 | 
			
		||||
                    OutlineOEControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
 | 
			
		||||
 | 
			
		||||
                    if(diceSelect != null) {
 | 
			
		||||
                    if (diceSelect != null) {
 | 
			
		||||
                        app.getModelSynchronize().rolledDice();
 | 
			
		||||
                    }
 | 
			
		||||
                    else if(cardLayerSelect != null) {
 | 
			
		||||
                    } else if (cardLayerSelect != null) {
 | 
			
		||||
                        //cardSelect
 | 
			
		||||
                        if(cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
 | 
			
		||||
                    }
 | 
			
		||||
                    else if(boardSelect != null) {
 | 
			
		||||
                        if (cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
 | 
			
		||||
                    } else if (boardSelect != null) {
 | 
			
		||||
                        //boardSelect
 | 
			
		||||
                        if(boardSelect instanceof PieceControl pieceControl){
 | 
			
		||||
                            if(pieceControl.isSelectable()) gameView.getBoardHandler().pieceSelect(pieceControl);
 | 
			
		||||
                        }
 | 
			
		||||
                        if(boardSelect instanceof NodeControl nodeControl){
 | 
			
		||||
//
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        if (boardSelect.isSelectable()) gameView.getBoardHandler().pieceSelect(boardSelect);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        //both null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -149,16 +142,17 @@ else if(boardSelect != null) {
 | 
			
		||||
            if (name.equals("Right")) {
 | 
			
		||||
                isRotateRight = !isRotateRight;
 | 
			
		||||
            }
 | 
			
		||||
            if(name.equals("Test2") &&isPressed){
 | 
			
		||||
                if(app.getView() instanceof GameView gameView){
 | 
			
		||||
            if (name.equals("Test2") && isPressed) {
 | 
			
		||||
                if (app.getView() instanceof GameView gameView) {
 | 
			
		||||
 | 
			
		||||
                    if(p == null) {
 | 
			
		||||
                    if (p == null) {
 | 
			
		||||
                        p = UUID.randomUUID();
 | 
			
		||||
                        gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
 | 
			
		||||
                        gameView.getBoardHandler().movePieceStartAnim(p,0);
 | 
			
		||||
                        gameView.getBoardHandler().addPlayer(Color.AIRFORCE, List.of(p, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()));
 | 
			
		||||
                        gameView.getBoardHandler().movePieceStartAnim(p, 0);
 | 
			
		||||
                        gameView.getBoardHandler().outlineMove(List.of(p), List.of(2), List.of(false));
 | 
			
		||||
                        //gameView.getBoardHandler().movePieceAnim(p,0, 8);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        gameView.getBoardHandler().throwMissileAnim(p);
 | 
			
		||||
                        gameView.getBoardHandler().throwPiece(p, Color.ARMY);
 | 
			
		||||
                        //gameView.getBoardHandler().movePieceStartAnim(p,0);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@@ -176,7 +170,6 @@ else if(boardSelect != null) {
 | 
			
		||||
                    //gameView.getGuiHandler().playCardOwn(BonusCard.SHIELD);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -190,17 +183,13 @@ else if(boardSelect != null) {
 | 
			
		||||
        public void onAnalog(String name, float value, float tpf) {
 | 
			
		||||
            if (name.equals("MouseLeft") && rightMousePressed) {
 | 
			
		||||
                rotationAngle -= value * 360f;
 | 
			
		||||
            }
 | 
			
		||||
            else if (name.equals("MouseRight") && rightMousePressed) {
 | 
			
		||||
            } else if (name.equals("MouseRight") && rightMousePressed) {
 | 
			
		||||
                rotationAngle += value * 360f;
 | 
			
		||||
            }
 | 
			
		||||
            else if (name.equals("MouseScrollUp")) {
 | 
			
		||||
            } else if (name.equals("MouseScrollUp")) {
 | 
			
		||||
                scrollValue = Math.max(1, scrollValue - 5);
 | 
			
		||||
            }
 | 
			
		||||
            else if (name.equals("MouseScrollDown")) {
 | 
			
		||||
            } else if (name.equals("MouseScrollDown")) {
 | 
			
		||||
                scrollValue = Math.min(100, scrollValue + 5);
 | 
			
		||||
            }
 | 
			
		||||
            else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")){
 | 
			
		||||
            } else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")) {
 | 
			
		||||
                hoverPiece();
 | 
			
		||||
                hoverCard();
 | 
			
		||||
            }
 | 
			
		||||
@@ -211,12 +200,13 @@ else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("M
 | 
			
		||||
     * Detects the hovered piece and updates its hover state.
 | 
			
		||||
     */
 | 
			
		||||
    private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T> controlType) {
 | 
			
		||||
        if(cam == null || root == null || controlType == null) return null;
 | 
			
		||||
        if (cam == null || root == null || controlType == null) return null;
 | 
			
		||||
        CollisionResults results = new CollisionResults();
 | 
			
		||||
        Ray ray = new Ray(cam.getLocation(), getMousePos(cam).subtract(cam.getLocation()).normalize());
 | 
			
		||||
        root.collideWith(ray, results);
 | 
			
		||||
        for(CollisionResult collisionResult : results){
 | 
			
		||||
            if(collisionResult.getGeometry().getControl(controlType) != null) return collisionResult.getGeometry().getControl(controlType);
 | 
			
		||||
        for (CollisionResult collisionResult : results) {
 | 
			
		||||
            if (collisionResult.getGeometry().getControl(controlType) != null)
 | 
			
		||||
                return collisionResult.getGeometry().getControl(controlType);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -225,16 +215,16 @@ private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T>
 | 
			
		||||
     * Detects the hovered card and updates its hover state.
 | 
			
		||||
     */
 | 
			
		||||
    private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Class<T> controlType) {
 | 
			
		||||
        if(cam == null || root == null || controlType == null) return null;
 | 
			
		||||
        if (cam == null || root == null || controlType == null) return null;
 | 
			
		||||
        CollisionResults results = new CollisionResults();
 | 
			
		||||
        Vector3f mousePos = getMousePos(cam);
 | 
			
		||||
        mousePos.setZ(cam.getLocation().getZ());
 | 
			
		||||
        Ray ray = new Ray(mousePos, getMousePos(cam).subtract(mousePos).normalize());
 | 
			
		||||
        root.collideWith(ray, results);
 | 
			
		||||
        if (results.size() > 0) {
 | 
			
		||||
            for(CollisionResult res : results ){
 | 
			
		||||
            for (CollisionResult res : results) {
 | 
			
		||||
                T control = res.getGeometry().getControl(controlType);
 | 
			
		||||
                if(control != null) return control;
 | 
			
		||||
                if (control != null) return control;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
@@ -246,15 +236,15 @@ private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Cla
 | 
			
		||||
     */
 | 
			
		||||
    private void hoverPiece() {
 | 
			
		||||
        if (app.getView() instanceof GameView gameView) {
 | 
			
		||||
            PieceControl control = checkPiece();
 | 
			
		||||
            OutlineOEControl control = checkPiece();
 | 
			
		||||
            if (control != null) {
 | 
			
		||||
                if (control != hoverPiece) {
 | 
			
		||||
                    pieceOff();
 | 
			
		||||
                    pieceOff(gameView);
 | 
			
		||||
                    hoverPiece = control;
 | 
			
		||||
                    hoverPiece.hover();
 | 
			
		||||
                    if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOn(hoverPiece);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                pieceOff();
 | 
			
		||||
                pieceOff(gameView);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -270,7 +260,7 @@ private void hoverCard() {
 | 
			
		||||
                if (control != hoverCard) {
 | 
			
		||||
                    cardOff();
 | 
			
		||||
                    hoverCard = control;
 | 
			
		||||
                    hoverCard.hover();
 | 
			
		||||
                    hoverCard.hoverOn();
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                cardOff();
 | 
			
		||||
@@ -283,8 +273,8 @@ private void hoverCard() {
 | 
			
		||||
     *
 | 
			
		||||
     * @return The PieceControl of the hovered piece, or null if no piece is hovered.
 | 
			
		||||
     */
 | 
			
		||||
    private PieceControl checkPiece() {
 | 
			
		||||
        return checkHover(app.getCamera(), app.getRootNode(), PieceControl.class);
 | 
			
		||||
    private OutlineOEControl checkPiece() {
 | 
			
		||||
        return checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -295,17 +285,19 @@ private PieceControl checkPiece() {
 | 
			
		||||
     */
 | 
			
		||||
    private CardControl checkCard(GameView gameView) {
 | 
			
		||||
        return checkHoverOrtho(
 | 
			
		||||
            gameView.getGuiHandler().getCardLayerCamera(),
 | 
			
		||||
            gameView.getGuiHandler().getCardLayerRootNode(),
 | 
			
		||||
            CardControl.class
 | 
			
		||||
                gameView.getGuiHandler().getCardLayerCamera(),
 | 
			
		||||
                gameView.getGuiHandler().getCardLayerRootNode(),
 | 
			
		||||
                CardControl.class
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disables the hover effect on the currently hovered piece, if any.
 | 
			
		||||
     */
 | 
			
		||||
    private void pieceOff() {
 | 
			
		||||
        if (hoverPiece != null) hoverPiece.hoverOff();
 | 
			
		||||
    private void pieceOff(GameView gameView) {
 | 
			
		||||
        if (hoverPiece != null) {
 | 
			
		||||
            if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOff(hoverPiece);
 | 
			
		||||
        }
 | 
			
		||||
        hoverPiece = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -339,7 +331,7 @@ public float getRotation() {
 | 
			
		||||
        return (rotationAngle / 2) % 360;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setRotation(float rotationAngle){
 | 
			
		||||
    public void setRotation(float rotationAngle) {
 | 
			
		||||
        this.rotationAngle = rotationAngle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,14 @@
 | 
			
		||||
package pp.mdga.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.SimpleApplication;
 | 
			
		||||
import com.simsilica.lemur.GuiGlobals;
 | 
			
		||||
import com.sun.tools.javac.Main;
 | 
			
		||||
import pp.mdga.client.acoustic.AcousticHandler;
 | 
			
		||||
import com.jme3.system.AppSettings;
 | 
			
		||||
import com.simsilica.lemur.GuiGlobals;
 | 
			
		||||
import pp.mdga.client.acoustic.AcousticHandler;
 | 
			
		||||
import pp.mdga.client.animation.TimerManager;
 | 
			
		||||
import pp.mdga.client.dialog.JoinDialog;
 | 
			
		||||
import pp.mdga.client.view.*;
 | 
			
		||||
 | 
			
		||||
import javax.imageio.ImageIO;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.image.BufferedImage;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
@@ -26,46 +21,79 @@ public class MdgaApp extends SimpleApplication {
 | 
			
		||||
 | 
			
		||||
    private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
 | 
			
		||||
 | 
			
		||||
    /** Handles acoustic effects and state-based sounds. */
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles acoustic effects and state-based sounds.
 | 
			
		||||
     */
 | 
			
		||||
    private AcousticHandler acousticHandler;
 | 
			
		||||
 | 
			
		||||
    /** Synchronizes notifications throughout the application. */
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronizes notifications throughout the application.
 | 
			
		||||
     */
 | 
			
		||||
    private NotificationSynchronizer notificationSynchronizer;
 | 
			
		||||
 | 
			
		||||
    /** Manages input events and synchronization. */
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages input events and synchronization.
 | 
			
		||||
     */
 | 
			
		||||
    private InputSynchronizer inputSynchronizer;
 | 
			
		||||
 | 
			
		||||
    /** Synchronizes game models. */
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronizes game models.
 | 
			
		||||
     */
 | 
			
		||||
    private ModelSynchronizer modelSynchronizer;
 | 
			
		||||
 | 
			
		||||
    /** The currently active view in the application. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The currently active view in the application.
 | 
			
		||||
     */
 | 
			
		||||
    private MdgaView view = null;
 | 
			
		||||
 | 
			
		||||
    /** The current state of the application. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The current state of the application.
 | 
			
		||||
     */
 | 
			
		||||
    private MdgaState state = null;
 | 
			
		||||
 | 
			
		||||
    /** Scale for rendering images. */
 | 
			
		||||
    /**
 | 
			
		||||
     * Scale for rendering images.
 | 
			
		||||
     */
 | 
			
		||||
    private final float imageScale = prefs.getInt("scale", 1);
 | 
			
		||||
 | 
			
		||||
    /** The main menu view. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The main menu view.
 | 
			
		||||
     */
 | 
			
		||||
    private MainView mainView;
 | 
			
		||||
 | 
			
		||||
    /** The lobby view. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The lobby view.
 | 
			
		||||
     */
 | 
			
		||||
    private LobbyView lobbyView;
 | 
			
		||||
 | 
			
		||||
    /** The game view. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The game view.
 | 
			
		||||
     */
 | 
			
		||||
    private GameView gameView;
 | 
			
		||||
 | 
			
		||||
    /** The ceremony view. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The ceremony view.
 | 
			
		||||
     */
 | 
			
		||||
    private CeremonyView ceremonyView;
 | 
			
		||||
 | 
			
		||||
    /** The client game logic. */
 | 
			
		||||
    /**
 | 
			
		||||
     * The client game logic.
 | 
			
		||||
     */
 | 
			
		||||
    private final ClientGameLogic clientGameLogic;
 | 
			
		||||
 | 
			
		||||
    private ExecutorService executor;
 | 
			
		||||
 | 
			
		||||
    private ServerConnection networkConnection;
 | 
			
		||||
 | 
			
		||||
    private final TimerManager timerManager = new TimerManager();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static final int DEBUG_MULTIPLIER = 1;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new MdgaApp instance.
 | 
			
		||||
     * Initializes the network connection and client game logic.
 | 
			
		||||
     */
 | 
			
		||||
    public MdgaApp() {
 | 
			
		||||
        networkConnection = new NetworkSupport(this);
 | 
			
		||||
        this.clientGameLogic = new ClientGameLogic(networkConnection);
 | 
			
		||||
@@ -81,7 +109,7 @@ public static void main(String[] args) {
 | 
			
		||||
        AppSettings settings = new AppSettings(true);
 | 
			
		||||
        settings.setSamples(128);
 | 
			
		||||
 | 
			
		||||
        if(prefs.getBoolean("fullscreen", false)) {
 | 
			
		||||
        if (prefs.getBoolean("fullscreen", false)) {
 | 
			
		||||
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 | 
			
		||||
            int screenWidth = (int) screenSize.getWidth();
 | 
			
		||||
            int screenHeight = (int) screenSize.getHeight();
 | 
			
		||||
@@ -142,6 +170,7 @@ public void simpleUpdate(float tpf) {
 | 
			
		||||
        acousticHandler.update();
 | 
			
		||||
        notificationSynchronizer.update();
 | 
			
		||||
        inputSynchronizer.update(tpf);
 | 
			
		||||
        timerManager.update(tpf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -247,14 +276,22 @@ public NotificationSynchronizer getNotificationSynchronizer() {
 | 
			
		||||
     * Prepares the app for a new game cycle.
 | 
			
		||||
     */
 | 
			
		||||
    public void setup() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the client game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link ClientGameLogic} instance
 | 
			
		||||
     */
 | 
			
		||||
    public ClientGameLogic getGameLogic() {
 | 
			
		||||
        return clientGameLogic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the executor service.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link ExecutorService} instance
 | 
			
		||||
     */
 | 
			
		||||
    public ExecutorService getExecutor() {
 | 
			
		||||
        if (this.executor == null) {
 | 
			
		||||
            this.executor = Executors.newCachedThreadPool();
 | 
			
		||||
@@ -263,12 +300,25 @@ public ExecutorService getExecutor() {
 | 
			
		||||
        return this.executor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ServerConnection getNetworkSupport(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the network connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link ServerConnection} instance
 | 
			
		||||
     */
 | 
			
		||||
    public ServerConnection getNetworkSupport() {
 | 
			
		||||
        return networkConnection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the resolution settings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width the new width
 | 
			
		||||
     * @param height the new height
 | 
			
		||||
     * @param imageFactor the new image factor
 | 
			
		||||
     * @param isFullscreen whether the game is in fullscreen mode
 | 
			
		||||
     */
 | 
			
		||||
    public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
 | 
			
		||||
        if(isFullscreen) {
 | 
			
		||||
        if (isFullscreen) {
 | 
			
		||||
            int baseWidth = 1280;
 | 
			
		||||
            int baseHeight = 720;
 | 
			
		||||
            float baseAspectRatio = (float) baseWidth / baseHeight;
 | 
			
		||||
@@ -292,6 +342,9 @@ public void updateResolution(int width, int height, float imageFactor, boolean i
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restarts the application.
 | 
			
		||||
     */
 | 
			
		||||
    public static void restartApp() {
 | 
			
		||||
        try {
 | 
			
		||||
            String javaBin = System.getProperty("java.home") + "/bin/java";
 | 
			
		||||
@@ -299,7 +352,7 @@ public static void restartApp() {
 | 
			
		||||
            String className = System.getProperty("sun.java.command");
 | 
			
		||||
 | 
			
		||||
            ProcessBuilder builder = new ProcessBuilder(
 | 
			
		||||
                javaBin, "-cp", classPath, className
 | 
			
		||||
                    javaBin, "-cp", classPath, className
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            builder.start();
 | 
			
		||||
@@ -310,17 +363,54 @@ public static void restartApp() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cleans up the application after a game.
 | 
			
		||||
     */
 | 
			
		||||
    public void afterGameCleanup() {
 | 
			
		||||
        MainView main = (MainView) mainView;
 | 
			
		||||
 | 
			
		||||
        main.getJoinDialog().disconnect();
 | 
			
		||||
        main.getHostDialog().shutdownServer();
 | 
			
		||||
        if (clientGameLogic.isHost()) {
 | 
			
		||||
            main.getHostDialog().shutdownServer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ceremonyView.afterGameCleanup();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public GameView getGameView(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the game view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link GameView} instance
 | 
			
		||||
     */
 | 
			
		||||
    public GameView getGameView() {
 | 
			
		||||
        return gameView;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the timer manager.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link TimerManager} instance
 | 
			
		||||
     */
 | 
			
		||||
    public TimerManager getTimerManager() {
 | 
			
		||||
        return timerManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the ceremony view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the {@link CeremonyView} instance
 | 
			
		||||
     */
 | 
			
		||||
    public CeremonyView getCeremonyView() {
 | 
			
		||||
        return ceremonyView;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
        afterGameCleanup();
 | 
			
		||||
        if (executor != null) {
 | 
			
		||||
            executor.shutdown();
 | 
			
		||||
        }
 | 
			
		||||
        super.destroy();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,156 +1,243 @@
 | 
			
		||||
package pp.mdga.client;
 | 
			
		||||
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.server.MdgaServer;
 | 
			
		||||
import pp.mdga.client.view.CeremonyView;
 | 
			
		||||
import pp.mdga.client.view.GameView;
 | 
			
		||||
import pp.mdga.client.view.LobbyView;
 | 
			
		||||
import pp.mdga.game.BonusCard;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.message.client.LobbyReadyMessage;
 | 
			
		||||
import pp.mdga.notification.AcquireCardNotification;
 | 
			
		||||
import pp.mdga.notification.DrawCardNotification;
 | 
			
		||||
import pp.mdga.notification.TskSelectNotification;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The ModelSynchronizer class is responsible for synchronizing the model state with the view and game logic.
 | 
			
		||||
 */
 | 
			
		||||
public class ModelSynchronizer {
 | 
			
		||||
    private static final Logger LOGGER = Logger.getLogger(ModelSynchronizer.class.getName());
 | 
			
		||||
    private MdgaApp app;
 | 
			
		||||
 | 
			
		||||
    private UUID a;
 | 
			
		||||
    private UUID b;
 | 
			
		||||
    private  BonusCard card;
 | 
			
		||||
    private BonusCard card;
 | 
			
		||||
    private boolean swap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for ModelSynchronizer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    ModelSynchronizer(MdgaApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        swap = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the end of an animation.
 | 
			
		||||
     */
 | 
			
		||||
    public void animationEnd() {
 | 
			
		||||
        app.getGameLogic().selectAnimationEnd();
 | 
			
		||||
        if (app.getNotificationSynchronizer().waitForAnimation) {
 | 
			
		||||
            app.getNotificationSynchronizer().waitForAnimation = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            app.getGameLogic().selectAnimationEnd();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void select(UUID a, UUID b){
 | 
			
		||||
        if(swap) selectSwap(a,b);
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a piece or swap based on the current state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param a the first UUID
 | 
			
		||||
     * @param b the second UUID
 | 
			
		||||
     */
 | 
			
		||||
    public void select(UUID a, UUID b) {
 | 
			
		||||
        if (swap) selectSwap(a, b);
 | 
			
		||||
        else selectPiece(a);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a swap between two pieces.
 | 
			
		||||
     *
 | 
			
		||||
     * @param a the first UUID
 | 
			
		||||
     * @param b the second UUID
 | 
			
		||||
     */
 | 
			
		||||
    public void selectSwap(UUID a, UUID b) {
 | 
			
		||||
        // TODO call from somewhere
 | 
			
		||||
        LOGGER.log(Level.INFO, "selectPiece");
 | 
			
		||||
        this.a = a;
 | 
			
		||||
        this.b = b;
 | 
			
		||||
 | 
			
		||||
        GameView gameView = (GameView) app.getView();
 | 
			
		||||
        if(a != null && b != null) {
 | 
			
		||||
        if (a != null && b != null) {
 | 
			
		||||
            gameView.needConfirm();
 | 
			
		||||
        } else {
 | 
			
		||||
            gameView.noConfirm();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a single piece.
 | 
			
		||||
     *
 | 
			
		||||
     * @param piece the UUID of the piece
 | 
			
		||||
     */
 | 
			
		||||
    public void selectPiece(UUID piece) {
 | 
			
		||||
        // TODO call from somewhere
 | 
			
		||||
        LOGGER.log(Level.INFO, "selectPiece");
 | 
			
		||||
 | 
			
		||||
        this.a = piece;
 | 
			
		||||
 | 
			
		||||
        GameView gameView = (GameView) app.getView();
 | 
			
		||||
        if(piece != null) {
 | 
			
		||||
        if (piece != null) {
 | 
			
		||||
            gameView.needConfirm();
 | 
			
		||||
        } else {
 | 
			
		||||
            gameView.noConfirm();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a bonus card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the BonusCard instance
 | 
			
		||||
     */
 | 
			
		||||
    public void selectCard(BonusCard card) {
 | 
			
		||||
        // TODO call from somewhere
 | 
			
		||||
        LOGGER.log(Level.INFO, "selectCard");
 | 
			
		||||
 | 
			
		||||
        this.card = card;
 | 
			
		||||
 | 
			
		||||
        GameView gameView = (GameView) app.getView();
 | 
			
		||||
 | 
			
		||||
        if(card == null) {
 | 
			
		||||
        if (card != null) {
 | 
			
		||||
            gameView.needConfirm();
 | 
			
		||||
        } else {
 | 
			
		||||
            gameView.needNoPower();
 | 
			
		||||
            gameView.showNoPower();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Confirms the current selection.
 | 
			
		||||
     */
 | 
			
		||||
    public void confirm() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "confirm");
 | 
			
		||||
 | 
			
		||||
        GameView gameView = (GameView) app.getView();
 | 
			
		||||
 | 
			
		||||
        if(a != null && b != null) {
 | 
			
		||||
            selectPiece(a);
 | 
			
		||||
            selectPiece(b);
 | 
			
		||||
        gameView.getGuiHandler().hideText();
 | 
			
		||||
 | 
			
		||||
        if (a != null && b != null) {
 | 
			
		||||
            app.getGameLogic().selectPiece(a);
 | 
			
		||||
            app.getGameLogic().selectPiece(b);
 | 
			
		||||
            gameView.getBoardHandler().clearSelectable();
 | 
			
		||||
        } else if (a != null) {
 | 
			
		||||
            selectPiece(a);
 | 
			
		||||
            app.getGameLogic().selectPiece(a);
 | 
			
		||||
            gameView.getBoardHandler().clearSelectable();
 | 
			
		||||
        } else {
 | 
			
		||||
            if(null == card) {
 | 
			
		||||
                selectCard(null);
 | 
			
		||||
            } else {
 | 
			
		||||
                selectCard(card);
 | 
			
		||||
                gameView.getGuiHandler().clearSelectableCards();
 | 
			
		||||
            }
 | 
			
		||||
            app.getGameLogic().selectCard(card);
 | 
			
		||||
            gameView.getGuiHandler().clearSelectableCards();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        a = null;
 | 
			
		||||
        b = null;
 | 
			
		||||
        card = null;
 | 
			
		||||
 | 
			
		||||
        gameView.noConfirm();
 | 
			
		||||
        gameView.noNoPower();
 | 
			
		||||
        gameView.hideNoPower();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a TSK color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the Color instance
 | 
			
		||||
     */
 | 
			
		||||
    public void selectTsk(Color color) {
 | 
			
		||||
        app.getGameLogic().selectTsk(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unselects a TSK color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the Color instance
 | 
			
		||||
     */
 | 
			
		||||
    public void unselectTsk(Color color) {
 | 
			
		||||
        app.getGameLogic().deselectTSK(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the event of rolling dice.
 | 
			
		||||
     */
 | 
			
		||||
    public void rolledDice() {
 | 
			
		||||
        app.getGameLogic().selectDice();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the player's name.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name the player's name
 | 
			
		||||
     */
 | 
			
		||||
    public void setName(String name) {
 | 
			
		||||
        // TODO call from somewhere
 | 
			
		||||
        LOGGER.log(Level.INFO, "setName: {0}", name);
 | 
			
		||||
        app.getGameLogic().selectName(name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the player's ready status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ready the ready status
 | 
			
		||||
     */
 | 
			
		||||
    public void setReady(boolean ready) {
 | 
			
		||||
        app.getGameLogic().selectReady(ready);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the host port.
 | 
			
		||||
     *
 | 
			
		||||
     * @param port the host port
 | 
			
		||||
     */
 | 
			
		||||
    public void setHost(int port) {
 | 
			
		||||
        app.getGameLogic().selectJoin("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the join IP and port.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip the IP address
 | 
			
		||||
     * @param port the port number
 | 
			
		||||
     */
 | 
			
		||||
    public void setJoin(String ip, int port) {
 | 
			
		||||
        app.getGameLogic().selectJoin(ip);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the event of leaving the game.
 | 
			
		||||
     */
 | 
			
		||||
    public void leave() {
 | 
			
		||||
        app.getGameLogic().selectLeave();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enters a specific game state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param state the MdgaState instance
 | 
			
		||||
     */
 | 
			
		||||
    public void enter(MdgaState state) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "enter: {0}", state);
 | 
			
		||||
        //app.enter(state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSwap(boolean swap){
 | 
			
		||||
    /**
 | 
			
		||||
     * Proceeds to the next game state.
 | 
			
		||||
     */
 | 
			
		||||
    public void next() {
 | 
			
		||||
        app.getGameLogic().selectNext();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the swap state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param swap the swap state
 | 
			
		||||
     */
 | 
			
		||||
    public void setSwap(boolean swap) {
 | 
			
		||||
        this.swap = swap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forces an action.
 | 
			
		||||
     */
 | 
			
		||||
    public void force() {
 | 
			
		||||
 | 
			
		||||
        // Implementation needed
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,23 +6,47 @@
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The NetworkSupport class provides support for network communication between the client and server.
 | 
			
		||||
 * It implements the MessageListener and ClientStateListener interfaces to handle incoming messages
 | 
			
		||||
 * and client state changes, respectively.
 | 
			
		||||
 */
 | 
			
		||||
public class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
 | 
			
		||||
 | 
			
		||||
    private static final System.Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private Client client;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for NetworkSupport.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public NetworkSupport(MdgaApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the MdgaApp instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public MdgaApp getApp() {
 | 
			
		||||
        return this.app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the client is connected to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the client is connected, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isConnected() {
 | 
			
		||||
        return this.client != null && this.client.isConnected();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects the client to the server.
 | 
			
		||||
     */
 | 
			
		||||
    public void connect() {
 | 
			
		||||
        if (this.client != null) {
 | 
			
		||||
            throw new IllegalStateException("trying to join a game again");
 | 
			
		||||
@@ -36,6 +60,9 @@ public void connect() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects the client from the server.
 | 
			
		||||
     */
 | 
			
		||||
    public void disconnect() {
 | 
			
		||||
        if (this.client != null) {
 | 
			
		||||
            this.client.close();
 | 
			
		||||
@@ -44,6 +71,13 @@ public void disconnect() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the network connection to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param host the server host
 | 
			
		||||
     * @param port the server port
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    public void initNetwork(String host, int port) throws IOException {
 | 
			
		||||
        if (this.client != null) {
 | 
			
		||||
            throw new IllegalStateException("trying to join a game again");
 | 
			
		||||
@@ -55,6 +89,12 @@ public void initNetwork(String host, int port) throws IOException {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles incoming messages from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client the client
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    public void messageReceived(Client client, Message message) {
 | 
			
		||||
        LOGGER.log(System.Logger.Level.INFO, "message received from server: {0}", new Object[]{message});
 | 
			
		||||
        if (message instanceof ServerMessage serverMessage) {
 | 
			
		||||
@@ -63,10 +103,21 @@ public void messageReceived(Client client, Message message) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles client connection to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client the client
 | 
			
		||||
     */
 | 
			
		||||
    public void clientConnected(Client client) {
 | 
			
		||||
        LOGGER.log(System.Logger.Level.INFO, "Client connected: {0}", new Object[]{client});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles client disconnection from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client the client
 | 
			
		||||
     * @param disconnectInfo the disconnect information
 | 
			
		||||
     */
 | 
			
		||||
    public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo disconnectInfo) {
 | 
			
		||||
        LOGGER.log(System.Logger.Level.INFO, "Client {0} disconnected: {1}", new Object[]{client, disconnectInfo});
 | 
			
		||||
        if (this.client != client) {
 | 
			
		||||
@@ -78,6 +129,11 @@ public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void send(ClientMessage message) {
 | 
			
		||||
        LOGGER.log(System.Logger.Level.INFO, "sending {0}", new Object[]{message});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,72 @@
 | 
			
		||||
package pp.mdga.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.system.NanoTimer;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.board.BoardHandler;
 | 
			
		||||
import pp.mdga.client.gui.GuiHandler;
 | 
			
		||||
import pp.mdga.client.view.CeremonyView;
 | 
			
		||||
import pp.mdga.client.view.GameView;
 | 
			
		||||
import pp.mdga.client.view.LobbyView;
 | 
			
		||||
import pp.mdga.game.BonusCard;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.notification.*;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Timer;
 | 
			
		||||
import java.util.TimerTask;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The NotificationSynchronizer class is responsible for handling and synchronizing notifications
 | 
			
		||||
 * received from the game logic and updating the application state accordingly.
 | 
			
		||||
 */
 | 
			
		||||
public class NotificationSynchronizer {
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
 | 
			
		||||
    private ArrayList<Notification> notifications = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    private NanoTimer timer = new NanoTimer();
 | 
			
		||||
    private float delay = 0;
 | 
			
		||||
 | 
			
		||||
    private static final float STANDARD_DELAY = 2.5f;
 | 
			
		||||
 | 
			
		||||
    public boolean waitForAnimation = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a NotificationSynchronizer with the specified MdgaApp instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    NotificationSynchronizer(MdgaApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the notification synchronizer by processing notifications from the game logic.
 | 
			
		||||
     * Handles different types of notifications based on the current application state.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        Notification n = app.getGameLogic().getNotification();
 | 
			
		||||
        while (n != null) {
 | 
			
		||||
            if(n instanceof InfoNotification infoNotification) {
 | 
			
		||||
        while (timer.getTimeInSeconds() >= delay) {
 | 
			
		||||
            if (waitForAnimation) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Notification n = app.getGameLogic().getNotification();
 | 
			
		||||
 | 
			
		||||
            if (n == null) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            System.out.println("receive notification:" + n.getClass().getName());
 | 
			
		||||
 | 
			
		||||
            timer.reset();
 | 
			
		||||
            delay = 0;
 | 
			
		||||
 | 
			
		||||
            if (n instanceof InfoNotification infoNotification) {
 | 
			
		||||
                app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(n != null) {
 | 
			
		||||
            if (n != null) {
 | 
			
		||||
                switch (app.getState()) {
 | 
			
		||||
                    case MAIN:
 | 
			
		||||
                        handleMain(n);
 | 
			
		||||
@@ -45,22 +83,34 @@ public void update() {
 | 
			
		||||
                    case NONE:
 | 
			
		||||
                        throw new RuntimeException("no notification expected: " + n.getClass().getName());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            n = app.getGameLogic().getNotification();
 | 
			
		||||
                if (0 == MdgaApp.DEBUG_MULTIPLIER) {
 | 
			
		||||
                    delay = 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles notifications when the application is in the MAIN state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notification the notification to handle
 | 
			
		||||
     */
 | 
			
		||||
    private void handleMain(Notification notification) {
 | 
			
		||||
        if (notification instanceof LobbyDialogNotification) {
 | 
			
		||||
            app.enter(MdgaState.LOBBY);
 | 
			
		||||
        } else if (notification instanceof StartDialogNotification) {
 | 
			
		||||
            //nothing
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new RuntimeException("notification not expected in main: "+ notification.getClass().getName());
 | 
			
		||||
            throw new RuntimeException("notification not expected in main: " + notification.getClass().getName());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles notifications when the application is in the LOBBY state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notification the notification to handle
 | 
			
		||||
     */
 | 
			
		||||
    private void handleLobby(Notification notification) {
 | 
			
		||||
        LobbyView lobbyView = (LobbyView) app.getView();
 | 
			
		||||
 | 
			
		||||
@@ -71,7 +121,7 @@ private void handleLobby(Notification notification) {
 | 
			
		||||
            app.enter(MdgaState.MAIN);
 | 
			
		||||
        } else if (notification instanceof TskUnselectNotification n) {
 | 
			
		||||
            lobbyView.setTaken(n.getColor(), false, false, null);
 | 
			
		||||
            } else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) {
 | 
			
		||||
        } else if (notification instanceof LobbyReadyNotification lobbyReadyNotification) {
 | 
			
		||||
            lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
 | 
			
		||||
        } else if (notification instanceof GameNotification n) {
 | 
			
		||||
            app.getGameView().setOwnColor(n.getOwnColor());
 | 
			
		||||
@@ -81,30 +131,45 @@ private void handleLobby(Notification notification) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles notifications when the application is in the GAME state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notification the notification to handle
 | 
			
		||||
     */
 | 
			
		||||
    private void handleGame(Notification notification) {
 | 
			
		||||
        GameView gameView = (GameView) app.getView();
 | 
			
		||||
        GuiHandler guiHandler = gameView.getGuiHandler();
 | 
			
		||||
        BoardHandler boardHandler = gameView.getBoardHandler();
 | 
			
		||||
        ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
 | 
			
		||||
        Color ownColor = gameView.getOwnColor();
 | 
			
		||||
 | 
			
		||||
        if (notification instanceof AcquireCardNotification n) {
 | 
			
		||||
            guiHandler.addCardOwn(n.getBonusCard());
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.BONUS);
 | 
			
		||||
            delay = STANDARD_DELAY;
 | 
			
		||||
        } else if (notification instanceof RankingResponceNotification n) {
 | 
			
		||||
            guiHandler.hideText();
 | 
			
		||||
            n.getRankingResults().forEach((c, i) -> {
 | 
			
		||||
                guiHandler.rollRankingResult(c, i);
 | 
			
		||||
            });
 | 
			
		||||
            delay = STANDARD_DELAY;
 | 
			
		||||
        } else if (notification instanceof ActivePlayerNotification n) {
 | 
			
		||||
            guiHandler.hideText();
 | 
			
		||||
            boardHandler.hideDice();
 | 
			
		||||
            gameView.getGuiHandler().setActivePlayer(n.getColor());
 | 
			
		||||
            boardHandler.showDice(n.getColor());
 | 
			
		||||
            if (n.getColor() != ownColor) boardHandler.showDice(n.getColor());
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.UI90);
 | 
			
		||||
            delay = STANDARD_DELAY;
 | 
			
		||||
        } else if (notification instanceof CeremonyNotification ceremonyNotification) {
 | 
			
		||||
            app.enter(MdgaState.CEREMONY);
 | 
			
		||||
            CeremonyView ceremonyView = (CeremonyView) app.getView();
 | 
			
		||||
            CeremonyView ceremonyView = app.getCeremonyView();
 | 
			
		||||
            int size = ceremonyNotification.getNames().size();
 | 
			
		||||
 | 
			
		||||
            if (ceremonyNotification.getPiecesThrown().size() != size ||
 | 
			
		||||
                ceremonyNotification.getPiecesLost().size() != size ||
 | 
			
		||||
                ceremonyNotification.getBonusCardsPlayed().size() != size ||
 | 
			
		||||
                ceremonyNotification.getSixes().size() != size ||
 | 
			
		||||
                ceremonyNotification.getNodesMoved().size() != size ||
 | 
			
		||||
                ceremonyNotification.getBonusNodes().size() != size) {
 | 
			
		||||
                    ceremonyNotification.getPiecesLost().size() != size ||
 | 
			
		||||
                    ceremonyNotification.getBonusCardsPlayed().size() != size ||
 | 
			
		||||
                    ceremonyNotification.getSixes().size() != size ||
 | 
			
		||||
                    ceremonyNotification.getNodesMoved().size() != size ||
 | 
			
		||||
                    ceremonyNotification.getBonusNodes().size() != size) {
 | 
			
		||||
                throw new IllegalArgumentException("All data lists in CeremonyNotification must have the same size.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -118,53 +183,77 @@ private void handleGame(Notification notification) {
 | 
			
		||||
                int v5 = ceremonyNotification.getNodesMoved().get(i);
 | 
			
		||||
                int v6 = ceremonyNotification.getBonusNodes().get(i);
 | 
			
		||||
 | 
			
		||||
                ceremonyView.addCeremonyParticipant(color, i, name);
 | 
			
		||||
                if(i < size - 1) {
 | 
			
		||||
                    ceremonyView.addCeremonyParticipant(color, i + 1, name);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ceremonyView.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
 | 
			
		||||
            }
 | 
			
		||||
            app.enter(MdgaState.CEREMONY);
 | 
			
		||||
        } else if (notification instanceof DiceNowNotification) {
 | 
			
		||||
            guiHandler.hideText();
 | 
			
		||||
            guiHandler.showDice();
 | 
			
		||||
        } else if (notification instanceof DrawCardNotification n) {
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.BONUS);
 | 
			
		||||
            guiHandler.drawCard(n.getColor());
 | 
			
		||||
            delay = STANDARD_DELAY;
 | 
			
		||||
        } else if (notification instanceof HomeMoveNotification home) {
 | 
			
		||||
            boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
 | 
			
		||||
            guiHandler.hideText();
 | 
			
		||||
            waitForAnimation = true;
 | 
			
		||||
        } else if (notification instanceof InterruptNotification notification1) {
 | 
			
		||||
            gameView.enterInterrupt(notification1.getColor());
 | 
			
		||||
        } else if (notification instanceof MovePieceNotification n) {
 | 
			
		||||
            if(n.isMoveStart()) {
 | 
			
		||||
            if (n.isMoveStart()) {
 | 
			
		||||
                //StartMove
 | 
			
		||||
                boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                waitForAnimation = true;
 | 
			
		||||
            } else {
 | 
			
		||||
                //InfieldMove
 | 
			
		||||
                boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
 | 
			
		||||
                waitForAnimation = true;
 | 
			
		||||
            }
 | 
			
		||||
            guiHandler.hideText();
 | 
			
		||||
        } else if (notification instanceof ThrowPieceNotification n) {
 | 
			
		||||
            boardHandler.throwBombAnim(n.getPieceId());
 | 
			
		||||
        } else if (notification instanceof NoShieldNotification n) {
 | 
			
		||||
            boardHandler.unshieldPiece(n.getPieceId());
 | 
			
		||||
            boardHandler.throwPiece(n.getPieceId(), n.getThrowColor());
 | 
			
		||||
            waitForAnimation = true;
 | 
			
		||||
        } else if (notification instanceof RemoveShieldNotification n) {
 | 
			
		||||
            boardHandler.unshieldPiece(n.getPieceUuid());
 | 
			
		||||
        } else if (notification instanceof PlayCardNotification n) {
 | 
			
		||||
            if(n.getColor() == gameView.getOwnColor()) guiHandler.playCardOwn(n.getCard());
 | 
			
		||||
            if (n.getCard() == BonusCard.TURBO) {
 | 
			
		||||
                app.getAcousticHandler().playSound(MdgaSound.TURBO);
 | 
			
		||||
                guiHandler.turbo();
 | 
			
		||||
            } else if (n.getCard() == BonusCard.SHIELD) {
 | 
			
		||||
                app.getAcousticHandler().playSound(MdgaSound.SHIELD);
 | 
			
		||||
            } else if (n.getCard() == BonusCard.SWAP) {
 | 
			
		||||
                app.getAcousticHandler().playSound(MdgaSound.SWAP);
 | 
			
		||||
            }
 | 
			
		||||
            if (n.getColor() == ownColor) guiHandler.playCardOwn(n.getCard());
 | 
			
		||||
            else guiHandler.playCardEnemy(n.getColor(), n.getCard());
 | 
			
		||||
 | 
			
		||||
            new Timer().schedule(new TimerTask() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void run() {
 | 
			
		||||
                    app.getModelSynchronize().animationEnd();
 | 
			
		||||
                }
 | 
			
		||||
            }, 2200 * MdgaApp.DEBUG_MULTIPLIER);
 | 
			
		||||
        } else if (notification instanceof PlayerInGameNotification n) {
 | 
			
		||||
            boardHandler.addPlayer(n.getColor(),n.getPiecesList());
 | 
			
		||||
            guiHandler.addPlayer(n.getColor(),n.getName());
 | 
			
		||||
            boardHandler.addPlayer(n.getColor(), n.getPiecesList());
 | 
			
		||||
            guiHandler.addPlayer(n.getColor(), n.getName());
 | 
			
		||||
        } else if (notification instanceof ResumeNotification) {
 | 
			
		||||
            gameView.leaveInterrupt();
 | 
			
		||||
        } else if (notification instanceof RollDiceNotification n) {
 | 
			
		||||
            gameView.getGuiHandler().hideText();
 | 
			
		||||
            if(n.getColor() == gameView.getOwnColor()){
 | 
			
		||||
            if (n.getColor() == ownColor) {
 | 
			
		||||
                guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                boardHandler.hideDice();
 | 
			
		||||
                waitForAnimation = true;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
 | 
			
		||||
                else guiHandler.showRolledDice(n.getEyes(), n.getColor());
 | 
			
		||||
            }
 | 
			
		||||
        } else if (notification instanceof SelectableCardsNotification n) {
 | 
			
		||||
            guiHandler.setSelectableCards(n.getCards());
 | 
			
		||||
            gameView.needNoPower();
 | 
			
		||||
            gameView.showNoPower();
 | 
			
		||||
        } else if (notification instanceof ShieldActiveNotification n) {
 | 
			
		||||
            boardHandler.shieldPiece(n.getPieceId());
 | 
			
		||||
        } else if (notification instanceof ShieldSuppressedNotification n) {
 | 
			
		||||
@@ -173,28 +262,33 @@ private void handleGame(Notification notification) {
 | 
			
		||||
            app.afterGameCleanup();
 | 
			
		||||
            app.enter(MdgaState.MAIN);
 | 
			
		||||
        } else if (notification instanceof SwapPieceNotification n) {
 | 
			
		||||
//            boardHandler.swapPieces(n.getFirstPiece(), n.getSecondPiece());
 | 
			
		||||
            boardHandler.swapPieceAnim(n.getFirstPiece(), n.getSecondPiece());
 | 
			
		||||
            guiHandler.swap();
 | 
			
		||||
        } else if (notification instanceof WaitMoveNotification) {
 | 
			
		||||
            //TODO ???
 | 
			
		||||
            //nothing
 | 
			
		||||
        } else if (notification instanceof SelectableMoveNotification n) {
 | 
			
		||||
            boardHandler.outlineMove(n.getPieces(), n.getMoveIndices(), n.getHomeMoves());
 | 
			
		||||
            modelSynchronizer.setSwap(false);
 | 
			
		||||
        } else if (notification instanceof SelectableSwapNotification n) {
 | 
			
		||||
            boardHandler.outlineSwap(n.getOwnPieces(), n.getEnemyPieces());
 | 
			
		||||
            modelSynchronizer.setSwap(true);
 | 
			
		||||
       } else if (notification instanceof SelectableShieldNotification n) {
 | 
			
		||||
        } else if (notification instanceof SelectableShieldNotification n) {
 | 
			
		||||
            boardHandler.outlineShield(n.getPieces());
 | 
			
		||||
            modelSynchronizer.setSwap(false);
 | 
			
		||||
        } else if (notification instanceof TurboActiveNotification){
 | 
			
		||||
            guiHandler.turbo();
 | 
			
		||||
        } else if (notification instanceof FinishNotification n){
 | 
			
		||||
        } else if (notification instanceof TurboActiveNotification) {
 | 
			
		||||
            //nothing
 | 
			
		||||
        } else if (notification instanceof FinishNotification n) {
 | 
			
		||||
            guiHandler.finish(n.getColorFinished());
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles notifications when the application is in the CEREMONY state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notification the notification to handle
 | 
			
		||||
     */
 | 
			
		||||
    private void handleCeremony(Notification notification) {
 | 
			
		||||
        if (notification instanceof StartDialogNotification) {
 | 
			
		||||
            app.afterGameCleanup();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								Projekte/mdga/client/src/main/java/pp/mdga/client/Util.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,48 @@
 | 
			
		||||
package pp.mdga.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
 | 
			
		||||
public class Util {
 | 
			
		||||
    private Util() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs linear interpolation between two values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start The starting value.
 | 
			
		||||
     * @param end   The ending value.
 | 
			
		||||
     * @param t     A parameter between 0 and 1 representing the interpolation progress.
 | 
			
		||||
     * @return The interpolated value.
 | 
			
		||||
     */
 | 
			
		||||
    public static float linInt(float start, float end, float t) {
 | 
			
		||||
        return start + t * (end - start);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs quadratic interpolation between three points.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p1 The initial point.
 | 
			
		||||
     * @param p2 The middle point.
 | 
			
		||||
     * @param p3 The final point.
 | 
			
		||||
     * @param t  The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @return The interpolated point.
 | 
			
		||||
     */
 | 
			
		||||
    public static Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
 | 
			
		||||
        // Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
 | 
			
		||||
        float oneMinusT = 1 - t;
 | 
			
		||||
        return p1.mult(oneMinusT * oneMinusT)
 | 
			
		||||
                .add(p2.mult(2 * oneMinusT * t))
 | 
			
		||||
                .add(p3.mult(t * t));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A smooth ease-in-out function for interpolation.
 | 
			
		||||
     * It accelerates and decelerates the interpolation for a smoother effect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x The interpolation parameter (0 <= x <= 1).
 | 
			
		||||
     * @return The adjusted interpolation value.
 | 
			
		||||
     */
 | 
			
		||||
    public static float easeInOut(float x) {
 | 
			
		||||
        return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,10 @@
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.MdgaState;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
public class AcousticHandler {
 | 
			
		||||
@@ -18,12 +21,14 @@ public class AcousticHandler {
 | 
			
		||||
 | 
			
		||||
    private boolean fading = false; // Indicates if a fade is in progress
 | 
			
		||||
    private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
 | 
			
		||||
    private static final float FADE_DURATION = 3.0f; // Duration for outfade
 | 
			
		||||
    private static final float FADE_DURATION = 2.0f; // Duration for outfade
 | 
			
		||||
    private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade
 | 
			
		||||
    private GameMusic playing = null; // Currently playing track
 | 
			
		||||
    private GameMusic scheduled = null; // Scheduled track to play next
 | 
			
		||||
    private GameMusic old = null; // Old track being faded out
 | 
			
		||||
 | 
			
		||||
    private GameMusic birds;
 | 
			
		||||
 | 
			
		||||
    private float mainVolume = 0.0f;
 | 
			
		||||
    private float musicVolume = 1.0f;
 | 
			
		||||
    private float soundVolume = 1.0f;
 | 
			
		||||
@@ -38,6 +43,8 @@ public AcousticHandler(MdgaApp app) {
 | 
			
		||||
        mainVolume = prefs.getFloat("mainVolume", 1.0f);
 | 
			
		||||
        musicVolume = prefs.getFloat("musicVolume", 1.0f);
 | 
			
		||||
        soundVolume = prefs.getFloat("soundVolume", 1.0f);
 | 
			
		||||
 | 
			
		||||
        birds = new GameMusic(app, MusicAsset.BIRDS, getSoundVolumeTotal(), MusicAsset.BIRDS.getSubVolume(), MusicAsset.BIRDS.getLoop(), 0.0f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -60,6 +67,8 @@ public void update() {
 | 
			
		||||
                iterator.remove();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -132,6 +141,25 @@ public void playSound(MdgaSound sound) {
 | 
			
		||||
            case MATRIX:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
 | 
			
		||||
                break;
 | 
			
		||||
            case TURRET_ROTATE:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.TURRET_ROTATE, 0.7f, 0f));
 | 
			
		||||
                break;
 | 
			
		||||
            case TANK_SHOOT:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.TANK_SHOOT, 0.7f, 0f));
 | 
			
		||||
                break;
 | 
			
		||||
            case TANK_EXPLOSION:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
 | 
			
		||||
                break;
 | 
			
		||||
            case SHIELD:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.SHIELD, 1.0f, 0f));
 | 
			
		||||
                break;
 | 
			
		||||
            case TURBO:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 0.1f));
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 1.3f));
 | 
			
		||||
                break;
 | 
			
		||||
            case SWAP:
 | 
			
		||||
                assets.add(new SoundAssetDelayVolume(SoundAsset.SWAP, 1.0f, 0f));
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
@@ -153,6 +181,10 @@ public void playState(MdgaState state) {
 | 
			
		||||
        }
 | 
			
		||||
        MusicAsset asset = null;
 | 
			
		||||
 | 
			
		||||
        birds.pause();
 | 
			
		||||
 | 
			
		||||
        float pause = 0.0f;
 | 
			
		||||
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case MAIN:
 | 
			
		||||
                playGame = false;
 | 
			
		||||
@@ -163,10 +195,12 @@ public void playState(MdgaState state) {
 | 
			
		||||
                asset = MusicAsset.LOBBY;
 | 
			
		||||
                break;
 | 
			
		||||
            case GAME:
 | 
			
		||||
                birds.play();
 | 
			
		||||
                addGameTracks();
 | 
			
		||||
                playGame = true;
 | 
			
		||||
                assert (!gameTracks.isEmpty()) : "no more game music available";
 | 
			
		||||
                asset = gameTracks.remove(0);
 | 
			
		||||
                pause = 2.0f;
 | 
			
		||||
                break;
 | 
			
		||||
            case CEREMONY:
 | 
			
		||||
                playGame = false;
 | 
			
		||||
@@ -178,7 +212,7 @@ public void playState(MdgaState state) {
 | 
			
		||||
 | 
			
		||||
        assert (null != asset) : "music sceduling went wrong";
 | 
			
		||||
 | 
			
		||||
        scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f);
 | 
			
		||||
        scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), pause);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -195,20 +229,20 @@ private float lerp(float start, float end, float t) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of audio playback, handling track transitions and volume adjustments.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method ensures smooth transitions between tracks using fade-in and fade-out effects.
 | 
			
		||||
     * It also handles cases where no track is playing, starting a scheduled track immediately at full volume.
 | 
			
		||||
     * The method prioritizes the latest scheduled track if multiple scheduling occurs quickly.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Behavior:
 | 
			
		||||
     * 1. If nothing is scheduled and no track is playing, it exits early.
 | 
			
		||||
     * 2. If a scheduled track exists and no track is playing, the scheduled track starts immediately at full volume.
 | 
			
		||||
     * 3. If a scheduled track exists while a track is playing, it initiates a fade-out for the currently playing track
 | 
			
		||||
     *    and prepares for the new track to fade in.
 | 
			
		||||
     * and prepares for the new track to fade in.
 | 
			
		||||
     * 4. If a track transition is in progress (fading), it processes the fade-out and fade-in states.
 | 
			
		||||
     *    If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
 | 
			
		||||
     * If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
 | 
			
		||||
     * 5. If no fading is needed and a track is playing, it ensures the track's volume is updated.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Special cases:
 | 
			
		||||
     * - If no track is playing and a new track is scheduled, it starts the track immediately without fading.
 | 
			
		||||
     * - If a new track is scheduled during fading, it resets the transition to prioritize the new track.
 | 
			
		||||
@@ -260,23 +294,23 @@ private void updateVolumeAndTrack() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages the fading process during audio track transitions.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method handles the fade-out of the currently playing (old) track, manages any pause between the fade-out
 | 
			
		||||
     * and fade-in, and initiates the fade-in for the new track if applicable. It ensures smooth transitions between
 | 
			
		||||
     * tracks while maintaining the correct volume adjustments.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Behavior:
 | 
			
		||||
     * 1. **Outfade:** Gradually decreases the volume of the `old` track over the duration of `FADE_DURATION`.
 | 
			
		||||
     *    Once the outfade completes, the `old` track is paused and cleared.
 | 
			
		||||
     * Once the outfade completes, the `old` track is paused and cleared.
 | 
			
		||||
     * 2. **Pause Handling:** Waits for a defined pause (if applicable) before initiating the infade for the next track.
 | 
			
		||||
     * 3. **Infade:** If a `scheduled` track exists and the outfade and pause are complete, it begins playing
 | 
			
		||||
     *    the new track (`playing`) and initiates the infade process.
 | 
			
		||||
     *
 | 
			
		||||
     * the new track (`playing`) and initiates the infade process.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Key Details:
 | 
			
		||||
     * - The outfade volume adjustment is interpolated linearly from full volume to zero using the `lerp` function.
 | 
			
		||||
     * - The pause duration is retrieved from the scheduled track if it is specified.
 | 
			
		||||
     * - If a new track is scheduled during the fade process, it is handled by external logic to prioritize transitions.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Preconditions:
 | 
			
		||||
     * - `fading` is expected to be `true` when this method is called.
 | 
			
		||||
     * - The method is invoked as part of the `updateVolumeAndTrack` process.
 | 
			
		||||
@@ -312,23 +346,23 @@ private void handleFadeProcess() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages the fade-in process for the currently playing track.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method gradually increases the volume of the `playing` track from zero to full volume
 | 
			
		||||
     * over the duration of `CROSSFADE_DURATION`. It ensures a smooth transition into the new track.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Behavior:
 | 
			
		||||
     * 1. If no track is set as `playing`, the method exits early, as there is nothing to fade in.
 | 
			
		||||
     * 2. Linearly interpolates the volume of the `playing` track from 0.0 to 1.0 based on the elapsed
 | 
			
		||||
     *    `infadeTime` and the specified `CROSSFADE_DURATION`.
 | 
			
		||||
     * `infadeTime` and the specified `CROSSFADE_DURATION`.
 | 
			
		||||
     * 3. Once the fade-in is complete (when `infadeTime` exceeds `CROSSFADE_DURATION`), the method:
 | 
			
		||||
     *    - Marks the fade process (`fading`) as complete.
 | 
			
		||||
     *    - Ensures the `playing` track is updated to its full volume.
 | 
			
		||||
     *
 | 
			
		||||
     * - Marks the fade process (`fading`) as complete.
 | 
			
		||||
     * - Ensures the `playing` track is updated to its full volume.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Key Details:
 | 
			
		||||
     * - Uses the `lerp` function to calculate the volume level for the `playing` track during the fade-in.
 | 
			
		||||
     * - Ensures the volume is always a value between 0.0 and 1.0.
 | 
			
		||||
     * - The `infadeTime` parameter should be relative to the start of the fade-in process.
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Preconditions:
 | 
			
		||||
     * - The `playing` track must be initialized and actively fading in for this method to have an effect.
 | 
			
		||||
     * - The method is invoked as part of the `updateVolumeAndTrack` process.
 | 
			
		||||
@@ -371,7 +405,7 @@ private void addGameTracks() {
 | 
			
		||||
     * a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
 | 
			
		||||
     */
 | 
			
		||||
    private void updateGameTracks() {
 | 
			
		||||
        if(null == playing) {
 | 
			
		||||
        if (null == playing) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -454,7 +488,7 @@ public void setSoundVolume(float soundVolume) {
 | 
			
		||||
     */
 | 
			
		||||
    float getMusicVolumeTotal() {
 | 
			
		||||
 | 
			
		||||
        return getMusicVolume() * getMainVolume();
 | 
			
		||||
        return getMusicVolume() * getMainVolume() / 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -38,4 +38,10 @@ public enum MdgaSound {
 | 
			
		||||
    UI90,
 | 
			
		||||
    MISSILE,
 | 
			
		||||
    MATRIX,
 | 
			
		||||
    TURRET_ROTATE,
 | 
			
		||||
    TANK_SHOOT,
 | 
			
		||||
    TANK_EXPLOSION,
 | 
			
		||||
    SHIELD,
 | 
			
		||||
    TURBO,
 | 
			
		||||
    SWAP,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,14 @@ enum SoundAsset {
 | 
			
		||||
    LOSE("lose.ogg"),
 | 
			
		||||
    MISSILE("missile.ogg"),
 | 
			
		||||
    MATRIX("matrix.wav"),
 | 
			
		||||
    CONNECTED("connected.wav");
 | 
			
		||||
    CONNECTED("connected.wav"),
 | 
			
		||||
    TURRET_ROTATE("turret_rotate.ogg"),
 | 
			
		||||
    TANK_SHOOT("tank_shoot.ogg"),
 | 
			
		||||
    SHIELD("shield.ogg"),
 | 
			
		||||
    SPEED("speed.ogg"),
 | 
			
		||||
    SWAP("swap.ogg"),
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final String path;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,4 +4,5 @@
 | 
			
		||||
 * A record that encapsulates a sound asset along with its playback settings:
 | 
			
		||||
 * the relative volume (subVolume) and a delay before it starts playing.
 | 
			
		||||
 */
 | 
			
		||||
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {}
 | 
			
		||||
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import pp.mdga.client.InitControl;
 | 
			
		||||
 | 
			
		||||
public class ActionControl extends InitControl {
 | 
			
		||||
    private final Runnable runnable;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ActionControl object with the specified action.
 | 
			
		||||
     *
 | 
			
		||||
     * @param runnable The action to be performed.
 | 
			
		||||
     */
 | 
			
		||||
    public ActionControl(Runnable runnable) {
 | 
			
		||||
        this.runnable = runnable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the action associated with this control.
 | 
			
		||||
     */
 | 
			
		||||
    protected void action() {
 | 
			
		||||
        if (null != runnable) {
 | 
			
		||||
            runnable.run();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,10 @@
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code Explosion} class represents an explosion effect in a 3D environment.
 | 
			
		||||
 * It manages the creation, configuration, and triggering of particle emitters for fire and smoke effects.
 | 
			
		||||
 */
 | 
			
		||||
public class Explosion {
 | 
			
		||||
 | 
			
		||||
    private final Node rootNode;
 | 
			
		||||
@@ -23,11 +27,11 @@ public class Explosion {
 | 
			
		||||
    private final Material mat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Konstruktor für die Explosion.
 | 
			
		||||
     * Constructor for the {@code Explosion} class.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app      Die Hauptanwendung.
 | 
			
		||||
     * @param rootNode Der Root-Knoten, an den die Explosion angefügt wird.
 | 
			
		||||
     * @param location Der Ort der Explosion in World-Koordinaten.
 | 
			
		||||
     * @param app      The main application managing the explosion.
 | 
			
		||||
     * @param rootNode The root node to which the explosion effects will be attached.
 | 
			
		||||
     * @param location The location of the explosion in world coordinates.
 | 
			
		||||
     */
 | 
			
		||||
    public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
@@ -35,20 +39,24 @@ public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
 | 
			
		||||
        this.location = location;
 | 
			
		||||
 | 
			
		||||
        this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.png"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialisiert den Partikel-Emitter für die Explosion.
 | 
			
		||||
     * Initializes the particle emitters for the explosion effect.
 | 
			
		||||
     * Configures the fire and smoke emitters with appearance, behavior, and lifespan.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeEmitter() {
 | 
			
		||||
        fire = new ParticleEmitter("Effect", Type.Triangle,50);
 | 
			
		||||
        fire = new ParticleEmitter("Effect", Type.Triangle, 50);
 | 
			
		||||
        fire.setMaterial(mat);
 | 
			
		||||
        fire.setImagesX(2);
 | 
			
		||||
        fire.setImagesY(2);
 | 
			
		||||
        fire.setStartColor(ColorRGBA.Yellow);
 | 
			
		||||
        fire.setEndColor(ColorRGBA.Red);
 | 
			
		||||
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f,0.2f,4f));
 | 
			
		||||
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
 | 
			
		||||
        fire.getParticleInfluencer().setVelocityVariation(0.4f);
 | 
			
		||||
        fire.setStartSize(0.1f);
 | 
			
		||||
        fire.setEndSize(0.8f);
 | 
			
		||||
        fire.setStartSize(0.7f);
 | 
			
		||||
        fire.setEndSize(1.8f);
 | 
			
		||||
        fire.setGravity(0, 0, -0.1f);
 | 
			
		||||
        fire.setLowLife(0.5f);
 | 
			
		||||
        fire.setHighLife(2.2f);
 | 
			
		||||
@@ -56,16 +64,16 @@ private void initializeEmitter() {
 | 
			
		||||
 | 
			
		||||
        fire.setLocalTranslation(location);
 | 
			
		||||
 | 
			
		||||
        smoke = new ParticleEmitter("Effect2", Type.Triangle,40);
 | 
			
		||||
        smoke = new ParticleEmitter("Effect2", Type.Triangle, 40);
 | 
			
		||||
        smoke.setMaterial(mat);
 | 
			
		||||
        smoke.setImagesX(2);
 | 
			
		||||
        smoke.setImagesY(2);
 | 
			
		||||
        smoke.setImagesX(3);
 | 
			
		||||
        smoke.setImagesY(3);
 | 
			
		||||
        smoke.setStartColor(ColorRGBA.DarkGray);
 | 
			
		||||
        smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
 | 
			
		||||
        smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,0.7f));
 | 
			
		||||
        smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.7f));
 | 
			
		||||
        smoke.getParticleInfluencer().setVelocityVariation(0.5f);
 | 
			
		||||
        smoke.setStartSize(0.2f);
 | 
			
		||||
        smoke.setEndSize(0.5f);
 | 
			
		||||
        smoke.setStartSize(0.8f);
 | 
			
		||||
        smoke.setEndSize(1.5f);
 | 
			
		||||
        smoke.setGravity(0, 0, -0.3f);
 | 
			
		||||
        smoke.setLowLife(1.2f);
 | 
			
		||||
        smoke.setHighLife(5.5f);
 | 
			
		||||
@@ -77,7 +85,8 @@ private void initializeEmitter() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Löst die Explosion aus.
 | 
			
		||||
     * Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
 | 
			
		||||
     * Both emitters are automatically detached after a predefined duration.
 | 
			
		||||
     */
 | 
			
		||||
    public void trigger() {
 | 
			
		||||
        if (!triggered) {
 | 
			
		||||
@@ -100,7 +109,8 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        rootNode.attachChild(smoke);
 | 
			
		||||
@@ -118,7 +128,8 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
 | 
			
		||||
import static pp.mdga.client.Util.linInt;
 | 
			
		||||
 | 
			
		||||
public class FadeControl extends ActionControl {
 | 
			
		||||
    private float duration; // Duration of the fade effect
 | 
			
		||||
    private float timeElapsed = 0;
 | 
			
		||||
    private boolean init = false;
 | 
			
		||||
    private float startAlpha;
 | 
			
		||||
    private float endAlpha;
 | 
			
		||||
 | 
			
		||||
    public FadeControl(float duration, float startAlpha, float endAlpha, Runnable actionAfter) {
 | 
			
		||||
        super(actionAfter);
 | 
			
		||||
        this.duration = duration;
 | 
			
		||||
        this.startAlpha = startAlpha;
 | 
			
		||||
        this.endAlpha = endAlpha;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public FadeControl(float duration, float startAlpha, float endAlpha) {
 | 
			
		||||
        this(duration, startAlpha, endAlpha, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initSpatial() {
 | 
			
		||||
        init = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        if (!init) return;
 | 
			
		||||
 | 
			
		||||
        timeElapsed += tpf;
 | 
			
		||||
        float t = timeElapsed / duration; // Calculate progress (0 to 1)
 | 
			
		||||
 | 
			
		||||
        if (t >= 1) {
 | 
			
		||||
            // Fade complete
 | 
			
		||||
            t = 1;
 | 
			
		||||
            init = false;
 | 
			
		||||
            spatial.removeControl(this);
 | 
			
		||||
            action();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        float alpha = linInt(startAlpha, endAlpha, t); // Interpolate alpha
 | 
			
		||||
 | 
			
		||||
        // Update the material's alpha
 | 
			
		||||
        if (spatial instanceof Geometry geometry) {
 | 
			
		||||
            Material mat = geometry.getMaterial();
 | 
			
		||||
            if (mat != null) {
 | 
			
		||||
                ColorRGBA diffuse = (ColorRGBA) mat.getParam("Diffuse").getValue();
 | 
			
		||||
                mat.setColor("Diffuse", new ColorRGBA(diffuse.r, diffuse.g, diffuse.b, alpha));
 | 
			
		||||
 | 
			
		||||
                ColorRGBA ambient = (ColorRGBA) mat.getParam("Ambient").getValue();
 | 
			
		||||
                mat.setColor("Ambient", new ColorRGBA(ambient.r, ambient.g, ambient.b, alpha));
 | 
			
		||||
 | 
			
		||||
                // Disable shadows when the object is nearly invisible
 | 
			
		||||
                if (alpha <= 0.1f) {
 | 
			
		||||
                    geometry.setShadowMode(RenderQueue.ShadowMode.Off);
 | 
			
		||||
                } else {
 | 
			
		||||
                    geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
                }
 | 
			
		||||
            } else throw new RuntimeException("Material is null");
 | 
			
		||||
        } else throw new RuntimeException("Spatial is not instance of Geometry");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,35 +12,35 @@
 | 
			
		||||
import pp.mdga.client.Asset;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.board.BoardHandler;
 | 
			
		||||
import pp.mdga.client.view.GameView;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code JetAnimation} class handles the animation of a jet model in a 3D environment.
 | 
			
		||||
 * It creates a jet model, animates its movement along a curved path, triggers an explosion at a target point,
 | 
			
		||||
 * and performs additional actions upon animation completion.
 | 
			
		||||
 */
 | 
			
		||||
public class JetAnimation {
 | 
			
		||||
 | 
			
		||||
    private final MdgaApp app;         // Referenz auf die Hauptanwendung
 | 
			
		||||
    private final Node rootNode;      // Root-Knoten, an dem die Animation hängt
 | 
			
		||||
    private Spatial jetModel;         // Das Model des "jet"
 | 
			
		||||
    private final Vector3f spawnPoint; // Spawnpunkt des Jets
 | 
			
		||||
    private final Vector3f nodePoint; // Punkt des überflogenen Knotens
 | 
			
		||||
    private final Vector3f despawnPoint; // Punkt, an dem der Jet despawnt
 | 
			
		||||
    private final float curveHeight;  // Maximale Höhe der Kurve
 | 
			
		||||
    private final float animationDuration; // Dauer der Animation
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private final Node rootNode;
 | 
			
		||||
    private Spatial jetModel;
 | 
			
		||||
    private final Vector3f spawnPoint;
 | 
			
		||||
    private final Vector3f nodePoint;
 | 
			
		||||
    private final Vector3f despawnPoint;
 | 
			
		||||
    private final float curveHeight;
 | 
			
		||||
    private final float animationDuration;
 | 
			
		||||
    private Explosion explosion;
 | 
			
		||||
    private final UUID id;
 | 
			
		||||
    private Runnable actionAfter;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Konstruktor für die ThrowAnimation-Klasse.
 | 
			
		||||
     * Constructor for the {@code JetAnimation} class.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app                Die Hauptanwendung
 | 
			
		||||
     * @param rootNode           Der Root-Knoten, an dem der Jet angefügt wird
 | 
			
		||||
     * @param uuid              Die UUID des pieces
 | 
			
		||||
     * @param targetPoint         Der Punkt, an dem der Jet spawnt
 | 
			
		||||
     * @param curveHeight        Die maximale Höhe der Flugkurve
 | 
			
		||||
     * @param animationDuration  Die Gesamtdauer der Animation in Sekunden
 | 
			
		||||
     * @param app               The main application managing the jet animation.
 | 
			
		||||
     * @param rootNode          The root node to which the jet model will be attached.
 | 
			
		||||
     * @param targetPoint       The target point where the explosion will occur.
 | 
			
		||||
     * @param curveHeight       The height of the curve for the jet's flight path.
 | 
			
		||||
     * @param animationDuration The total duration of the jet animation.
 | 
			
		||||
     */
 | 
			
		||||
    public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint, float curveHeight, float animationDuration) {
 | 
			
		||||
    public JetAnimation(MdgaApp app, Node rootNode, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
 | 
			
		||||
        Vector3f spawnPoint = targetPoint.add(170, 50, 50);
 | 
			
		||||
 | 
			
		||||
        Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
 | 
			
		||||
@@ -55,13 +55,12 @@ public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint,
 | 
			
		||||
        this.curveHeight = curveHeight;
 | 
			
		||||
        this.animationDuration = animationDuration;
 | 
			
		||||
 | 
			
		||||
        id = uuid;
 | 
			
		||||
 | 
			
		||||
        explosion = new Explosion(app, rootNode, targetPoint);
 | 
			
		||||
        this.actionAfter = actionAfter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Startet die Animation.
 | 
			
		||||
     * Starts the jet animation by spawning the jet model and initiating its movement along the predefined path.
 | 
			
		||||
     */
 | 
			
		||||
    public void start() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.JET);
 | 
			
		||||
@@ -70,23 +69,25 @@ public void start() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Spawnt den Jet an der spezifizierten Position.
 | 
			
		||||
     * Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
 | 
			
		||||
     */
 | 
			
		||||
    private void spawnJet() {
 | 
			
		||||
        jetModel = app.getAssetManager().loadModel(Asset.jet.getModelPath());
 | 
			
		||||
        jetModel = app.getAssetManager().loadModel(Asset.jet_noGear.getModelPath());
 | 
			
		||||
        jetModel.setLocalTranslation(spawnPoint);
 | 
			
		||||
        jetModel.scale(Asset.jet.getSize());
 | 
			
		||||
        jetModel.scale(Asset.jet_noGear.getSize());
 | 
			
		||||
        jetModel.rotate(FastMath.HALF_PI, 0, 0);
 | 
			
		||||
        jetModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
 | 
			
		||||
        mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet.getDiffPath()));
 | 
			
		||||
        mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet_noGear.getDiffPath()));
 | 
			
		||||
        jetModel.setMaterial(mat);
 | 
			
		||||
 | 
			
		||||
        rootNode.attachChild(jetModel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animiert den Jet entlang einer Kurve und lässt ihn anschließend verschwinden.
 | 
			
		||||
     * actionAfter
 | 
			
		||||
     * Animates the jet along a Bezier curve path, triggers the explosion effect at the appropriate time,
 | 
			
		||||
     * and performs cleanup operations after the animation completes.
 | 
			
		||||
     */
 | 
			
		||||
    private void animateJet() {
 | 
			
		||||
        Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
 | 
			
		||||
@@ -102,7 +103,7 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
                elapsedTime += tpf;
 | 
			
		||||
                float progress = elapsedTime / animationDuration;
 | 
			
		||||
 | 
			
		||||
                if(elapsedTime > 4.2f) {
 | 
			
		||||
                if (elapsedTime > 4.2f) {
 | 
			
		||||
                    explosion.trigger();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -118,26 +119,35 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (elapsedTime > 6.0f) {
 | 
			
		||||
                    GameView gameView = (GameView) app.getView();
 | 
			
		||||
                    BoardHandler boardHandler = gameView.getBoardHandler();
 | 
			
		||||
 | 
			
		||||
                    boardHandler.throwPieceAnim(id);
 | 
			
		||||
                    endAnim();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
                // Wird hier nicht benötigt
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void endAnim() {
 | 
			
		||||
        actionAfter.run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Repräsentiert eine 3D-Bezier-Kurve mit vier Kontrollpunkten.
 | 
			
		||||
     * The {@code BezierCurve3f} class represents a 3D cubic Bezier curve.
 | 
			
		||||
     * It provides methods to interpolate positions and derivatives along the curve.
 | 
			
		||||
     */
 | 
			
		||||
    private static class BezierCurve3f {
 | 
			
		||||
        private final Vector3f p0, p1, p2, p3;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Constructor for the {@code BezierCurve3f} class.
 | 
			
		||||
         *
 | 
			
		||||
         * @param p0 The starting point of the curve.
 | 
			
		||||
         * @param p1 The first control point influencing the curve's shape.
 | 
			
		||||
         * @param p2 The second control point influencing the curve's shape.
 | 
			
		||||
         * @param p3 The endpoint of the curve.
 | 
			
		||||
         */
 | 
			
		||||
        public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
 | 
			
		||||
            this.p0 = p0;
 | 
			
		||||
            this.p1 = p1;
 | 
			
		||||
@@ -145,6 +155,12 @@ public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
 | 
			
		||||
            this.p3 = p3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Interpolates a position along the curve at a given progress value {@code t}.
 | 
			
		||||
         *
 | 
			
		||||
         * @param t The progress value (0.0 to 1.0) along the curve.
 | 
			
		||||
         * @return The interpolated position on the curve.
 | 
			
		||||
         */
 | 
			
		||||
        public Vector3f interpolate(float t) {
 | 
			
		||||
            float u = 1 - t;
 | 
			
		||||
            float tt = t * t;
 | 
			
		||||
@@ -159,6 +175,12 @@ public Vector3f interpolate(float t) {
 | 
			
		||||
            return point;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Computes the derivative at a given progress value {@code t}, representing the direction along the curve.
 | 
			
		||||
         *
 | 
			
		||||
         * @param t The progress value (0.0 to 1.0) along the curve.
 | 
			
		||||
         * @return The derivative (direction vector) at the specified progress.
 | 
			
		||||
         */
 | 
			
		||||
        public Vector3f interpolateDerivative(float t) {
 | 
			
		||||
            float u = 1 - t;
 | 
			
		||||
            float tt = t * t;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,253 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh.Type;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * MatrixAnimation class handles the animation of radar and matrix particle effects.
 | 
			
		||||
 */
 | 
			
		||||
public class MatrixAnimation extends ActionControl {
 | 
			
		||||
    private MdgaApp app;
 | 
			
		||||
    private static final Random RANDOM = new Random();
 | 
			
		||||
    private Vector3f radarPos;
 | 
			
		||||
    private Runnable runnable;
 | 
			
		||||
    private boolean init = false;
 | 
			
		||||
    private List<ParticleEmitter> activeEmitter = new ArrayList<>();
 | 
			
		||||
    private ParticleEmitter radarEmitter = null;
 | 
			
		||||
    private float timeElapsed = 0f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum representing the states of the matrix animation.
 | 
			
		||||
     */
 | 
			
		||||
    private enum MatrixState {
 | 
			
		||||
        RADAR_ON,
 | 
			
		||||
        RADAR_OFF,
 | 
			
		||||
        MATRIX_ON,
 | 
			
		||||
        MATRIX_OFF
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private MatrixState state;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for MatrixAnimation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app       the application instance
 | 
			
		||||
     * @param radarPos  the position of the radar
 | 
			
		||||
     * @param runnable  the runnable action to be executed
 | 
			
		||||
     */
 | 
			
		||||
    public MatrixAnimation(MdgaApp app, Vector3f radarPos, Runnable runnable) {
 | 
			
		||||
        super(runnable);
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.radarPos = radarPos;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the spatial and sets the initial state to RADAR_ON.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initSpatial() {
 | 
			
		||||
        state = MatrixState.RADAR_ON;
 | 
			
		||||
        timeElapsed = 0;
 | 
			
		||||
        init = true;
 | 
			
		||||
        radar();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the control based on the time per frame (tpf).
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf the time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        if (!init) return;
 | 
			
		||||
 | 
			
		||||
        timeElapsed += tpf;
 | 
			
		||||
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case RADAR_ON -> {
 | 
			
		||||
                if (timeElapsed >= 2f) {
 | 
			
		||||
                    state = MatrixState.RADAR_OFF;
 | 
			
		||||
                    timeElapsed = 0;
 | 
			
		||||
                    radarEmitter.setParticlesPerSec(0);
 | 
			
		||||
                    app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
 | 
			
		||||
                        app.getRootNode().detachChild(radarEmitter);
 | 
			
		||||
                        System.out.println("delete radar");
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case RADAR_OFF -> {
 | 
			
		||||
                if (timeElapsed >= 0.1f) {
 | 
			
		||||
                    state = MatrixState.MATRIX_ON;
 | 
			
		||||
                    timeElapsed = 0;
 | 
			
		||||
                    matrix();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case MATRIX_ON -> {
 | 
			
		||||
                if (timeElapsed >= 3f) {
 | 
			
		||||
                    state = MatrixState.MATRIX_OFF;
 | 
			
		||||
                    timeElapsed = 0;
 | 
			
		||||
                    turnOff();
 | 
			
		||||
                    app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
 | 
			
		||||
                        for (ParticleEmitter particleEmitter : activeEmitter) {
 | 
			
		||||
                            app.getRootNode().detachChild(particleEmitter);
 | 
			
		||||
                        }
 | 
			
		||||
                        System.out.println("delete particle");
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case MATRIX_OFF -> {
 | 
			
		||||
                if (timeElapsed >= 0.5f) {
 | 
			
		||||
                    init = false;
 | 
			
		||||
                    spatial.removeControl(this);
 | 
			
		||||
                    action();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Turns off all active particle emitters.
 | 
			
		||||
     */
 | 
			
		||||
    private void turnOff() {
 | 
			
		||||
        for (ParticleEmitter particleEmitter : activeEmitter) {
 | 
			
		||||
            particleEmitter.setParticlesPerSec(0f);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the radar particle emitter.
 | 
			
		||||
     */
 | 
			
		||||
    private void radar() {
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/radar_beam.png"));
 | 
			
		||||
        ParticleEmitter emitter = new ParticleEmitter("Effect", Type.Triangle, 50);
 | 
			
		||||
        emitter.setMaterial(mat);
 | 
			
		||||
        emitter.setImagesX(1); // columns
 | 
			
		||||
        emitter.setImagesY(1); // rows
 | 
			
		||||
        emitter.setSelectRandomImage(true);
 | 
			
		||||
        emitter.setStartColor(ColorRGBA.White);
 | 
			
		||||
        emitter.setEndColor(ColorRGBA.Black);
 | 
			
		||||
        emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, 2));
 | 
			
		||||
        emitter.getParticleInfluencer().setVelocityVariation(0f);
 | 
			
		||||
        emitter.setStartSize(0.1f);
 | 
			
		||||
        emitter.setEndSize(10);
 | 
			
		||||
        emitter.setGravity(0, 0, 0);
 | 
			
		||||
        float life = 2.6f;
 | 
			
		||||
        emitter.setLowLife(life);
 | 
			
		||||
        emitter.setHighLife(life);
 | 
			
		||||
        emitter.setLocalTranslation(radarPos.add(new Vector3f(0, 0, 5)));
 | 
			
		||||
        emitter.setParticlesPerSec(1.8f);
 | 
			
		||||
        app.getRootNode().attachChild(emitter);
 | 
			
		||||
        radarEmitter = emitter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes multiple matrix particle streams.
 | 
			
		||||
     */
 | 
			
		||||
    private void matrix() {
 | 
			
		||||
        for (int i = 0; i < 5; i++) {
 | 
			
		||||
            particleStream(
 | 
			
		||||
                    generateMatrixColor(),
 | 
			
		||||
                    generateMatrixColor(),
 | 
			
		||||
                    getRandomFloat(0, 1f),
 | 
			
		||||
                    getRandomPosition(),
 | 
			
		||||
                    getRandomFloat(1, 2)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a particle stream with the specified parameters.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start     the start color of the particles
 | 
			
		||||
     * @param end       the end color of the particles
 | 
			
		||||
     * @param speedVar  the speed variation of the particles
 | 
			
		||||
     * @param pos       the position of the particles
 | 
			
		||||
     * @param spawnVar  the spawn rate variation of the particles
 | 
			
		||||
     */
 | 
			
		||||
    private void particleStream(ColorRGBA start, ColorRGBA end, float speedVar, Vector3f pos, float spawnVar) {
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 | 
			
		||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/particle_cir.png"));
 | 
			
		||||
        ParticleEmitter matrix = new ParticleEmitter("Effect", Type.Triangle, 50);
 | 
			
		||||
        matrix.setMaterial(mat);
 | 
			
		||||
        matrix.setImagesX(2); // columns
 | 
			
		||||
        matrix.setImagesY(1); // rows
 | 
			
		||||
        matrix.setSelectRandomImage(true);
 | 
			
		||||
        matrix.setStartColor(start);
 | 
			
		||||
        matrix.setEndColor(end);
 | 
			
		||||
        matrix.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, -6f - speedVar));
 | 
			
		||||
        matrix.getParticleInfluencer().setVelocityVariation(0f);
 | 
			
		||||
        matrix.setStartSize(0.4f);
 | 
			
		||||
        matrix.setEndSize(0.6f);
 | 
			
		||||
        matrix.setGravity(0, 0, 2f);
 | 
			
		||||
        matrix.setLowLife(3f);
 | 
			
		||||
        matrix.setHighLife(3f);
 | 
			
		||||
        matrix.setLocalTranslation(spatial.getLocalTranslation().add(pos).add(new Vector3f(0, 0, 15)));
 | 
			
		||||
        matrix.setParticlesPerSec(spawnVar);
 | 
			
		||||
        app.getRootNode().attachChild(matrix);
 | 
			
		||||
        activeEmitter.add(matrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a random position vector.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a random position vector
 | 
			
		||||
     */
 | 
			
		||||
    public static Vector3f getRandomPosition() {
 | 
			
		||||
        // Generate a random angle in radians (0 to 2π)
 | 
			
		||||
        float angle = (float) (2 * Math.PI * RANDOM.nextDouble());
 | 
			
		||||
 | 
			
		||||
        // Generate a random radius with uniform distribution
 | 
			
		||||
        float radius = (float) Math.sqrt(RANDOM.nextDouble());
 | 
			
		||||
        radius *= 1f;
 | 
			
		||||
 | 
			
		||||
        // Convert polar coordinates to Cartesian
 | 
			
		||||
        float x = radius * (float) Math.cos(angle);
 | 
			
		||||
        float y = radius * (float) Math.sin(angle);
 | 
			
		||||
 | 
			
		||||
        return new Vector3f(x, y, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a random float between the specified start and end values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start the start value
 | 
			
		||||
     * @param end   the end value
 | 
			
		||||
     * @return a random float between start and end
 | 
			
		||||
     */
 | 
			
		||||
    public static float getRandomFloat(float start, float end) {
 | 
			
		||||
        if (start > end) {
 | 
			
		||||
            throw new IllegalArgumentException("Start must be less than or equal to end.");
 | 
			
		||||
        }
 | 
			
		||||
        return start + RANDOM.nextFloat() * (end - start);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a random color for the matrix particles.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a random ColorRGBA object
 | 
			
		||||
     */
 | 
			
		||||
    public static ColorRGBA generateMatrixColor() {
 | 
			
		||||
        // Red is dominant
 | 
			
		||||
        float red = 0.8f + RANDOM.nextFloat() * 0.2f;  // Red channel: 0.8 to 1.0
 | 
			
		||||
        // Green is moderately high
 | 
			
		||||
        float green = 0.4f + RANDOM.nextFloat() * 0.3f;  // Green channel: 0.4 to 0.7
 | 
			
		||||
        // Blue is minimal
 | 
			
		||||
        float blue = RANDOM.nextFloat() * 0.2f;  // Blue channel: 0.0 to 0.2
 | 
			
		||||
        float alpha = 1.0f;  // Fully opaque
 | 
			
		||||
 | 
			
		||||
        return new ColorRGBA(red, green, blue, alpha);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.FastMath;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
@@ -14,123 +17,165 @@
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.board.BoardHandler;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code MissileAnimation} class handles the animation of a missile moving along a parabolic path
 | 
			
		||||
 * towards a target point in a 3D environment. It also triggers an explosion at the target upon impact.
 | 
			
		||||
 */
 | 
			
		||||
public class MissileAnimation {
 | 
			
		||||
 | 
			
		||||
    private final Node rootNode;       // Root-Knoten, an den die Animation gehängt wird
 | 
			
		||||
    private final MdgaApp app;        // Referenz auf die Hauptanwendung
 | 
			
		||||
    private final Vector3f start;     // Startpunkt der Rakete
 | 
			
		||||
    private final Vector3f target;    // Zielpunkt der Rakete
 | 
			
		||||
    private final float flightTime;   // Gesamtdauer des Flugs
 | 
			
		||||
    private final Node rootNode;
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private final Vector3f start;
 | 
			
		||||
    private final Vector3f target;
 | 
			
		||||
    private final float flightTime;
 | 
			
		||||
    private Explosion explosion;
 | 
			
		||||
    private Spatial missileModel;     // 3D-Modell der Rakete
 | 
			
		||||
    private Spatial missileModel;
 | 
			
		||||
    private Runnable actionAfter;
 | 
			
		||||
    private ParticleEmitter smoke;
 | 
			
		||||
 | 
			
		||||
    private UUID id;
 | 
			
		||||
    private Node missileNode = new Node();
 | 
			
		||||
 | 
			
		||||
    private final Material mat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Konstruktor für die MissileAnimation.
 | 
			
		||||
     * Constructor for the {@code MissileAnimation} class.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app       Die Hauptanwendung.
 | 
			
		||||
     * @param rootNode  Der Root-Knoten, an den die Animation gehängt wird.
 | 
			
		||||
     * @param target    Der Zielpunkt der Rakete.
 | 
			
		||||
     * @param flightTime Die Zeit, die die Rakete für den gesamten Flug benötigt.
 | 
			
		||||
     * @param app        The main application managing the missile animation.
 | 
			
		||||
     * @param rootNode   The root node to which the missile model will be attached.
 | 
			
		||||
     * @param target     The target point where the missile will explode.
 | 
			
		||||
     * @param flightTime The total flight time of the missile.
 | 
			
		||||
     */
 | 
			
		||||
    public MissileAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f target, float flightTime) {
 | 
			
		||||
    public MissileAnimation(MdgaApp app, Node rootNode, Vector3f target, float flightTime, Runnable actionAfter) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.rootNode = rootNode;
 | 
			
		||||
        this.flightTime = flightTime;
 | 
			
		||||
        this.actionAfter = actionAfter;
 | 
			
		||||
 | 
			
		||||
        explosion = new Explosion(app, rootNode, target);
 | 
			
		||||
        id = uuid;
 | 
			
		||||
 | 
			
		||||
        this.target = target.add(new Vector3f(1.5f, -1, 0));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        start = BoardHandler.gridToWorld(12, 0);
 | 
			
		||||
        start.add(new Vector3f(0, 0, 0));
 | 
			
		||||
 | 
			
		||||
        this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
 | 
			
		||||
 | 
			
		||||
        smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 400);
 | 
			
		||||
        smoke.setMaterial(mat);
 | 
			
		||||
        smoke.setImagesX(3);
 | 
			
		||||
        smoke.setImagesY(3);
 | 
			
		||||
        smoke.setStartColor(ColorRGBA.DarkGray);
 | 
			
		||||
        smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
 | 
			
		||||
        smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
 | 
			
		||||
        smoke.getParticleInfluencer().setVelocityVariation(0.1f);
 | 
			
		||||
        smoke.setStartSize(0.8f);
 | 
			
		||||
        smoke.setEndSize(1.5f);
 | 
			
		||||
        smoke.setGravity(0, 0, -0.3f);
 | 
			
		||||
        smoke.setLowLife(1.2f);
 | 
			
		||||
        smoke.setHighLife(3.5f);
 | 
			
		||||
        smoke.setParticlesPerSec(100);
 | 
			
		||||
        missileNode.attachChild(smoke);
 | 
			
		||||
        smoke.move(1, 0.85f, 1.0f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Startet die Raketenanimation.
 | 
			
		||||
     * Starts the missile animation by loading the missile model and initiating its parabolic movement.
 | 
			
		||||
     */
 | 
			
		||||
    public void start() {
 | 
			
		||||
        Smoke s = new Smoke(app, rootNode, start);
 | 
			
		||||
        s.trigger();
 | 
			
		||||
        loadMissile();
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.MISSILE);
 | 
			
		||||
        animateMissile();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lädt das Raketenmodell und setzt es auf den Startpunkt.
 | 
			
		||||
     * Loads the missile model into the scene, applies scaling, material, and sets its initial position.
 | 
			
		||||
     */
 | 
			
		||||
    private void loadMissile() {
 | 
			
		||||
        missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath()); // Lade das Missile-Modell
 | 
			
		||||
        missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath());
 | 
			
		||||
        missileModel.scale(Asset.missile.getSize());
 | 
			
		||||
        missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
 | 
			
		||||
        mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
 | 
			
		||||
        missileModel.setMaterial(mat);
 | 
			
		||||
        missileModel.setLocalTranslation(start); // Setze Startposition
 | 
			
		||||
        rootNode.attachChild(missileModel); // Füge das Modell zur Szene hinzu
 | 
			
		||||
 | 
			
		||||
        missileNode.setLocalTranslation(start);
 | 
			
		||||
        missileNode.attachChild(missileModel);
 | 
			
		||||
 | 
			
		||||
        rootNode.attachChild(missileNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animiert die Rakete entlang einer Parabel.
 | 
			
		||||
     * Animates the missile along a parabolic path, triggers the explosion near the target,
 | 
			
		||||
     * and removes the missile model after the animation completes.
 | 
			
		||||
     */
 | 
			
		||||
    private void animateMissile() {
 | 
			
		||||
        missileModel.addControl(new AbstractControl() {
 | 
			
		||||
        missileNode.addControl(new AbstractControl() {
 | 
			
		||||
            private float elapsedTime = 0;
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlUpdate(float tpf) {
 | 
			
		||||
                if (elapsedTime > 6) {
 | 
			
		||||
                    endAnim();
 | 
			
		||||
                    rootNode.detachChild(missileNode);
 | 
			
		||||
                    this.spatial.removeControl(this);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                elapsedTime += tpf;
 | 
			
		||||
                float progress = elapsedTime / flightTime;
 | 
			
		||||
 | 
			
		||||
                if (progress >= 0.55) {
 | 
			
		||||
                    smoke.setParticlesPerSec(30);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (progress >= 0.7) {
 | 
			
		||||
                    smoke.setParticlesPerSec(0);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (progress >= 0.95f) {
 | 
			
		||||
                    explosion.trigger();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (progress >= 1) {
 | 
			
		||||
                    explosion.trigger();
 | 
			
		||||
 | 
			
		||||
                    // Flug abgeschlossen
 | 
			
		||||
                    rootNode.detachChild(missileModel); // Entferne Rakete nach dem Ziel
 | 
			
		||||
                    this.spatial.removeControl(this);   // Entferne die Steuerung
 | 
			
		||||
                    return;
 | 
			
		||||
                    missileNode.detachChild(missileModel);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Berechne die aktuelle Position entlang der Parabel
 | 
			
		||||
                Vector3f currentPosition = computeParabolicPath(start, target, progress);
 | 
			
		||||
                missileModel.setLocalTranslation(currentPosition);
 | 
			
		||||
                missileNode.setLocalTranslation(currentPosition);
 | 
			
		||||
 | 
			
		||||
                // Passe die Ausrichtung an (Nase der Rakete zeigt in Flugrichtung)
 | 
			
		||||
                Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
 | 
			
		||||
                    .subtract(currentPosition)
 | 
			
		||||
                    .normalizeLocal();
 | 
			
		||||
                missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y); // Z ist oben, Y ist "Up"
 | 
			
		||||
                        .subtract(currentPosition)
 | 
			
		||||
                        .normalizeLocal();
 | 
			
		||||
                missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y);
 | 
			
		||||
                missileModel.rotate(0, FastMath.HALF_PI, 0);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
                // Keine Render-Logik benötigt
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void endAnim() {
 | 
			
		||||
        actionAfter.run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Berechnet eine Parabelbewegung von `start` zu `target`.
 | 
			
		||||
     * Computes a position along a parabolic path at a given progress value {@code t}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start Der Startpunkt der Rakete.
 | 
			
		||||
     * @param target Der Zielpunkt der Rakete.
 | 
			
		||||
     * @param t Der Fortschritt des Flugs (0 bis 1).
 | 
			
		||||
     * @return Die Position der Rakete entlang der Parabel.
 | 
			
		||||
     * @param start  The starting point of the missile's flight.
 | 
			
		||||
     * @param target The target point of the missile's flight.
 | 
			
		||||
     * @param t      The progress value (0.0 to 1.0) along the flight path.
 | 
			
		||||
     * @return The interpolated position along the parabolic path.
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
 | 
			
		||||
        Vector3f midPoint = start.add(target).multLocal(0.5f); // Berechne die Mitte zwischen Start und Ziel
 | 
			
		||||
        midPoint.addLocal(0, 0, 20); // Erhöhe den Scheitelpunkt der Parabel entlang der Z-Achse
 | 
			
		||||
        Vector3f midPoint = start.add(target).multLocal(0.5f);
 | 
			
		||||
        midPoint.addLocal(0, 0, 20);
 | 
			
		||||
 | 
			
		||||
        // Quadratische Interpolation (Parabel)
 | 
			
		||||
        Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
 | 
			
		||||
        Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
 | 
			
		||||
        return FastMath.interpolateLinear(t, startToMid, midToTarget);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import pp.mdga.client.InitControl;
 | 
			
		||||
 | 
			
		||||
import static pp.mdga.client.Util.easeInOut;
 | 
			
		||||
import static pp.mdga.client.Util.quadInt;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A control that smoothly moves a spatial from an initial position to an end position
 | 
			
		||||
@@ -12,35 +14,42 @@
 | 
			
		||||
 * an ease-in-out curve to create a smooth start and stop effect.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class MoveControl extends InitControl {
 | 
			
		||||
public class MoveControl extends ActionControl {
 | 
			
		||||
 | 
			
		||||
    private boolean moving;
 | 
			
		||||
    private final Vector3f initPos;
 | 
			
		||||
    private final Vector3f endPos;
 | 
			
		||||
    private final Vector3f middlePos;
 | 
			
		||||
    private final static float HEIGHT = 2;
 | 
			
		||||
    private final static float MOVE_SPEED = 1f;
 | 
			
		||||
    private float progress = 0;
 | 
			
		||||
    private final Runnable actionAfter;
 | 
			
		||||
    private final float height;
 | 
			
		||||
    private final float duration;
 | 
			
		||||
    private float timer = 0;
 | 
			
		||||
    private boolean easing;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new MoveControl with specified initial and end positions, and an action to run after the movement.
 | 
			
		||||
     * The movement follows a path with a midpoint at a fixed height.
 | 
			
		||||
     *
 | 
			
		||||
     * @param initPos The starting position of the spatial.
 | 
			
		||||
     * @param endPos The target position of the spatial.
 | 
			
		||||
     * @param initPos     The starting position of the spatial.
 | 
			
		||||
     * @param endPos      The target position of the spatial.
 | 
			
		||||
     * @param actionAfter A Runnable that will be executed after the movement finishes.
 | 
			
		||||
     */
 | 
			
		||||
    public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
 | 
			
		||||
    public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter) {
 | 
			
		||||
        this(initPos, endPos, actionAfter, 2, 1, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter, float height, float duration, boolean easing) {
 | 
			
		||||
        super(actionAfter);
 | 
			
		||||
        moving = false;
 | 
			
		||||
        this.initPos = initPos;
 | 
			
		||||
        this.endPos = endPos;
 | 
			
		||||
        this.height = height;
 | 
			
		||||
        this.duration = duration;
 | 
			
		||||
        this.easing = easing;
 | 
			
		||||
        middlePos = new Vector3f(
 | 
			
		||||
            (initPos.x + endPos.x) / 2,
 | 
			
		||||
            (initPos.y + endPos.y) / 2,
 | 
			
		||||
            HEIGHT
 | 
			
		||||
                (initPos.x + endPos.x) / 2,
 | 
			
		||||
                (initPos.y + endPos.y) / 2,
 | 
			
		||||
                height
 | 
			
		||||
        );
 | 
			
		||||
        this.actionAfter = actionAfter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -50,7 +59,7 @@ public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initSpatial() {
 | 
			
		||||
        moving = true;
 | 
			
		||||
        progress = 0;
 | 
			
		||||
        timer = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -62,48 +71,28 @@ protected void initSpatial() {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        if(!moving) return;
 | 
			
		||||
        progress += tpf * MOVE_SPEED;
 | 
			
		||||
        if(progress > 1) progress = 1;
 | 
			
		||||
        spatial.setLocalTranslation(quadInt(initPos,middlePos,endPos, easeInOut(progress)));
 | 
			
		||||
        if(progress == 1) end();
 | 
			
		||||
        if (!moving) return;
 | 
			
		||||
        timer += tpf;
 | 
			
		||||
 | 
			
		||||
        float t = timer / duration;
 | 
			
		||||
        if (t >= 1) t = 1;
 | 
			
		||||
 | 
			
		||||
        float interpolated = easing ? easeInOut(t) : t;
 | 
			
		||||
 | 
			
		||||
        spatial.setLocalTranslation(quadInt(initPos, middlePos, endPos, interpolated));
 | 
			
		||||
 | 
			
		||||
        if (t >= 1) end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ends the movement by stopping the interpolation, running the action after the movement,
 | 
			
		||||
     * and removing this control from the spatial.
 | 
			
		||||
     */
 | 
			
		||||
    private void end(){
 | 
			
		||||
    private void end() {
 | 
			
		||||
        moving = false;
 | 
			
		||||
        actionAfter.run();
 | 
			
		||||
        spatial.removeControl(this);
 | 
			
		||||
        action();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs quadratic interpolation between three points.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p1 The initial point.
 | 
			
		||||
     * @param p2 The middle point.
 | 
			
		||||
     * @param p3 The final point.
 | 
			
		||||
     * @param t The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @return The interpolated point.
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
 | 
			
		||||
        // Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
 | 
			
		||||
        float oneMinusT = 1 - t;
 | 
			
		||||
        return p1.mult(oneMinusT * oneMinusT)
 | 
			
		||||
                 .add(p2.mult(2 * oneMinusT * t))
 | 
			
		||||
                 .add(p3.mult(t * t));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A smooth ease-in-out function for interpolation.
 | 
			
		||||
     * It accelerates and decelerates the interpolation for a smoother effect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x The interpolation parameter (0 <= x <= 1).
 | 
			
		||||
     * @return The adjusted interpolation value.
 | 
			
		||||
     */
 | 
			
		||||
    private float easeInOut(float x){
 | 
			
		||||
            return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,199 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.FastMath;
 | 
			
		||||
import com.jme3.math.Quaternion;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import pp.mdga.client.Asset;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.board.TankTopControl;
 | 
			
		||||
 | 
			
		||||
import java.util.Timer;
 | 
			
		||||
import java.util.TimerTask;
 | 
			
		||||
 | 
			
		||||
import static com.jme3.material.Materials.LIGHTING;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ShellAnimation class handles the animation of a shell being fired from a tank.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellAnimation extends ActionControl {
 | 
			
		||||
    private static final float FLYING_DURATION = 1.25f;
 | 
			
		||||
    private static final float FLYING_HEIGHT = 12f;
 | 
			
		||||
    private TankTopControl tankTopControl;
 | 
			
		||||
    private MdgaApp app;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for ShellAnimation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tankTopControl the control for the tank top
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     * @param actionAfter the action to perform after the animation
 | 
			
		||||
     */
 | 
			
		||||
    public ShellAnimation(TankTopControl tankTopControl, MdgaApp app, Runnable actionAfter) {
 | 
			
		||||
        super(actionAfter);
 | 
			
		||||
        this.tankTopControl = tankTopControl;
 | 
			
		||||
        this.app = app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the spatial for the animation.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initSpatial() {
 | 
			
		||||
        tankTopControl.rotate(spatial.getLocalTranslation(), this::shoot);
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.TURRET_ROTATE);
 | 
			
		||||
        //app.getRootNode().attachChild(createShell());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the shooting position based on the tank's turret rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the shooting position as a Vector3f
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f getShootPos() {
 | 
			
		||||
        Vector3f localOffset = new Vector3f(0, -5.4f, 2.9f);
 | 
			
		||||
        Quaternion turretRotation = tankTopControl.getSpatial().getLocalRotation();
 | 
			
		||||
        Vector3f transformedOffset = turretRotation.mult(localOffset);
 | 
			
		||||
        return tankTopControl.getSpatial().getLocalTranslation().add(transformedOffset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the shooting action, including sound and visual effects.
 | 
			
		||||
     */
 | 
			
		||||
    private void shoot() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.TANK_SHOOT);
 | 
			
		||||
        Vector3f shootPos = getShootPos();
 | 
			
		||||
        createEffect(
 | 
			
		||||
                shootPos,
 | 
			
		||||
                "Images/particle/flame.png",
 | 
			
		||||
                2, 2,
 | 
			
		||||
                1, 3,
 | 
			
		||||
                1f,
 | 
			
		||||
                0.3f, 0.7f,
 | 
			
		||||
                new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
 | 
			
		||||
                new ColorRGBA(1f, 0f, 0f, 0f)
 | 
			
		||||
        );
 | 
			
		||||
        createEffect(
 | 
			
		||||
                shootPos,
 | 
			
		||||
                "Images/particle/vapor_cloud.png",
 | 
			
		||||
                3, 3,
 | 
			
		||||
                0.3f, 0.8f,
 | 
			
		||||
                10,
 | 
			
		||||
                0.1f, 0.35f,
 | 
			
		||||
                new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
 | 
			
		||||
                ColorRGBA.Black
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Spatial shell = createShell();
 | 
			
		||||
        app.getRootNode().attachChild(shell);
 | 
			
		||||
        shell.addControl(new ShellControl(this::hitExplosion, shootPos, spatial.getLocalTranslation(), FLYING_HEIGHT, FLYING_DURATION, app.getAssetManager()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the shell model and sets its initial properties.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the created shell as a Spatial
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createShell() {
 | 
			
		||||
        Spatial model = app.getAssetManager().loadModel(Asset.shell.getModelPath());
 | 
			
		||||
        model.scale(.16f);
 | 
			
		||||
        model.setLocalTranslation(tankTopControl.getSpatial().getLocalTranslation());
 | 
			
		||||
 | 
			
		||||
        Vector3f shootPos = tankTopControl.getSpatial().getLocalTranslation();
 | 
			
		||||
        Vector3f targetPos = spatial.getLocalTranslation();
 | 
			
		||||
        Vector3f direction = targetPos.subtract(shootPos).normalize();
 | 
			
		||||
 | 
			
		||||
        Quaternion rotation = new Quaternion();
 | 
			
		||||
        rotation.lookAt(direction, new Vector3f(1, 0, 0)); // Assuming UNIT_Y is the up vector
 | 
			
		||||
 | 
			
		||||
        model.setLocalRotation(rotation);
 | 
			
		||||
        model.rotate(FastMath.HALF_PI, 0, 0);
 | 
			
		||||
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), LIGHTING);
 | 
			
		||||
        mat.setBoolean("UseMaterialColors", true);
 | 
			
		||||
        ColorRGBA color = ColorRGBA.fromRGBA255(143, 117, 0, 255);
 | 
			
		||||
        mat.setColor("Diffuse", color);
 | 
			
		||||
        mat.setColor("Ambient", color);
 | 
			
		||||
        model.setMaterial(mat);
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the explosion effect when the shell hits a target.
 | 
			
		||||
     */
 | 
			
		||||
    private void hitExplosion() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.TANK_EXPLOSION);
 | 
			
		||||
        createEffect(
 | 
			
		||||
                spatial.getLocalTranslation().setZ(1),
 | 
			
		||||
                "Images/particle/flame.png",
 | 
			
		||||
                2, 2,
 | 
			
		||||
                1, 5,
 | 
			
		||||
                2f,
 | 
			
		||||
                0.3f, 0.7f,
 | 
			
		||||
                new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
 | 
			
		||||
                new ColorRGBA(1f, 0f, 0f, 0f)
 | 
			
		||||
        );
 | 
			
		||||
        app.getTimerManager().addTask(0.8f, super::action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a particle effect at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shootPos the position to create the effect
 | 
			
		||||
     * @param image the image to use for the particles
 | 
			
		||||
     * @param x the number of columns in the texture
 | 
			
		||||
     * @param y the number of rows in the texture
 | 
			
		||||
     * @param startSize the initial size of the particles
 | 
			
		||||
     * @param endSize the final size of the particles
 | 
			
		||||
     * @param velocity the initial velocity of the particles
 | 
			
		||||
     * @param lowLife the minimum lifetime of the particles
 | 
			
		||||
     * @param highLife the maximum lifetime of the particles
 | 
			
		||||
     * @param start the starting color of the particles
 | 
			
		||||
     * @param end the ending color of the particles
 | 
			
		||||
     */
 | 
			
		||||
    private void createEffect(Vector3f shootPos,
 | 
			
		||||
                              String image,
 | 
			
		||||
                              int x, int y,
 | 
			
		||||
                              float startSize, float endSize,
 | 
			
		||||
                              float velocity,
 | 
			
		||||
                              float lowLife, float highLife,
 | 
			
		||||
                              ColorRGBA start, ColorRGBA end) {
 | 
			
		||||
        // Create a particle emitter for the explosion
 | 
			
		||||
        ParticleEmitter explosionEmitter = new ParticleEmitter("Explosion", ParticleMesh.Type.Triangle, 100);
 | 
			
		||||
        Material explosionMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        explosionMat.setTexture("Texture", app.getAssetManager().loadTexture(image));
 | 
			
		||||
        explosionEmitter.setMaterial(explosionMat);
 | 
			
		||||
 | 
			
		||||
        // Particle properties
 | 
			
		||||
        explosionEmitter.setImagesX(x); // Columns in the texture
 | 
			
		||||
        explosionEmitter.setImagesY(y); // Rows in the texture
 | 
			
		||||
        explosionEmitter.setSelectRandomImage(true); // Randomize images for variety
 | 
			
		||||
 | 
			
		||||
        explosionEmitter.setStartColor(start); // Bright yellowish orange
 | 
			
		||||
        explosionEmitter.setEndColor(end); // Fade to transparent red
 | 
			
		||||
 | 
			
		||||
        explosionEmitter.setStartSize(startSize); // Initial size
 | 
			
		||||
        explosionEmitter.setEndSize(endSize); // Final size
 | 
			
		||||
        explosionEmitter.setLowLife(lowLife); // Minimum lifetime of particles
 | 
			
		||||
        explosionEmitter.setHighLife(highLife); // Maximum lifetime of particles
 | 
			
		||||
        explosionEmitter.setGravity(0, 0, 1); // Gravity to pull particles down
 | 
			
		||||
        explosionEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, velocity));
 | 
			
		||||
        explosionEmitter.getParticleInfluencer().setVelocityVariation(1f); // Adds randomness to the initial velocity
 | 
			
		||||
        explosionEmitter.setFacingVelocity(true); // Particles face their velocity direction
 | 
			
		||||
        explosionEmitter.setLocalTranslation(shootPos);
 | 
			
		||||
        explosionEmitter.setParticlesPerSec(0);
 | 
			
		||||
        explosionEmitter.emitAllParticles();
 | 
			
		||||
        app.getRootNode().attachChild(explosionEmitter);
 | 
			
		||||
        new Timer().schedule(new TimerTask() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                app.getRootNode().detachChild(explosionEmitter);
 | 
			
		||||
            }
 | 
			
		||||
        }, 1000);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,112 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.asset.AssetManager;
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.FastMath;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ShellControl is responsible for controlling the movement and visual effects of a shell.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellControl extends ActionControl {
 | 
			
		||||
    private final Vector3f shootPos;
 | 
			
		||||
    private final Vector3f endPos;
 | 
			
		||||
    private final float height;
 | 
			
		||||
    private final float duration;
 | 
			
		||||
    private Vector3f oldPos;
 | 
			
		||||
    private ParticleEmitter emitter;
 | 
			
		||||
    private AssetManager assetManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ShellControl.
 | 
			
		||||
     *
 | 
			
		||||
     * @param runnable the action to perform when the shell reaches its destination
 | 
			
		||||
     * @param shootPos the starting position of the shell
 | 
			
		||||
     * @param endPos the ending position of the shell
 | 
			
		||||
     * @param height the height of the shell's trajectory
 | 
			
		||||
     * @param duration the duration of the shell's flight
 | 
			
		||||
     * @param assetManager the asset manager to load resources
 | 
			
		||||
     */
 | 
			
		||||
    public ShellControl(Runnable runnable, Vector3f shootPos, Vector3f endPos, float height, float duration, AssetManager assetManager) {
 | 
			
		||||
        super(runnable);
 | 
			
		||||
        this.shootPos = shootPos;
 | 
			
		||||
        this.endPos = endPos;
 | 
			
		||||
        this.height = height;
 | 
			
		||||
        this.duration = duration;
 | 
			
		||||
        this.assetManager = assetManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the spatial with the necessary controls and particle emitter.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initSpatial() {
 | 
			
		||||
        spatial.addControl(new MoveControl(
 | 
			
		||||
                shootPos,
 | 
			
		||||
                endPos,
 | 
			
		||||
                () -> {
 | 
			
		||||
                    emitter.killAllParticles();
 | 
			
		||||
                    emitter.setParticlesPerSec(0);
 | 
			
		||||
                    emitter.removeFromParent();
 | 
			
		||||
                    spatial.removeControl(this);
 | 
			
		||||
                    spatial.removeFromParent();
 | 
			
		||||
                    action();
 | 
			
		||||
                },
 | 
			
		||||
                height,
 | 
			
		||||
                duration,
 | 
			
		||||
                false
 | 
			
		||||
        ));
 | 
			
		||||
        oldPos = spatial.getLocalTranslation().clone();
 | 
			
		||||
        createEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates and configures the particle emitter for the shell trail.
 | 
			
		||||
     */
 | 
			
		||||
    private void createEmitter() {
 | 
			
		||||
        emitter = new ParticleEmitter("ShellTrail", ParticleMesh.Type.Triangle, 200);
 | 
			
		||||
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.setTexture("Texture", assetManager.loadTexture("Images/particle/line.png")); // Nutze eine schmale, linienartige Textur
 | 
			
		||||
        emitter.setMaterial(mat);
 | 
			
		||||
 | 
			
		||||
        // Comic-Style Farben
 | 
			
		||||
        emitter.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); // Reinweiß
 | 
			
		||||
        emitter.setEndColor(new ColorRGBA(1f, 1f, 1f, 0f)); // Transparent
 | 
			
		||||
 | 
			
		||||
        // Partikelgröße und Lebensdauer
 | 
			
		||||
        emitter.setStartSize(0.15f); // Startgröße
 | 
			
		||||
        emitter.setEndSize(0.1f); // Endgröße
 | 
			
		||||
        emitter.setLowLife(0.14f); // Sehr kurze Lebensdauer
 | 
			
		||||
        emitter.setHighLife(0.14f);
 | 
			
		||||
 | 
			
		||||
        emitter.setGravity(0, 0, 0); // Keine Gravitation
 | 
			
		||||
        emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
 | 
			
		||||
        emitter.getParticleInfluencer().setVelocityVariation(0f); // Kein Variationsspielraum
 | 
			
		||||
 | 
			
		||||
        // Hohe Dichte für eine glatte Spur
 | 
			
		||||
        emitter.setParticlesPerSec(500);
 | 
			
		||||
 | 
			
		||||
        // Zur Shell hinzufügen
 | 
			
		||||
        spatial.getParent().attachChild(emitter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the control, adjusting the shell's rotation and emitter position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        Vector3f direction = spatial.getLocalTranslation().subtract(oldPos).normalize();
 | 
			
		||||
        if (direction.lengthSquared() > 0) {
 | 
			
		||||
            spatial.getLocalRotation().lookAt(direction, Vector3f.UNIT_X);
 | 
			
		||||
            spatial.rotate(FastMath.HALF_PI, 0, 0);
 | 
			
		||||
        }
 | 
			
		||||
        oldPos = spatial.getLocalTranslation().clone();
 | 
			
		||||
 | 
			
		||||
        emitter.setLocalTranslation(spatial.getLocalTranslation().clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,129 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
 | 
			
		||||
public class Smoke {
 | 
			
		||||
 | 
			
		||||
    private final Node rootNode;
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private final Vector3f location;
 | 
			
		||||
    private ParticleEmitter fire;
 | 
			
		||||
    private ParticleEmitter smoke;
 | 
			
		||||
 | 
			
		||||
    private boolean triggered = false;
 | 
			
		||||
 | 
			
		||||
    private final Material mat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for the {@code Explosion} class.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app      The main application managing the explosion.
 | 
			
		||||
     * @param rootNode The root node to which the explosion effects will be attached.
 | 
			
		||||
     * @param location The location of the explosion in world coordinates.
 | 
			
		||||
     */
 | 
			
		||||
    public Smoke(MdgaApp app, Node rootNode, Vector3f location) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.rootNode = rootNode;
 | 
			
		||||
        this.location = location;
 | 
			
		||||
 | 
			
		||||
        this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the particle emitters for the explosion effect.
 | 
			
		||||
     * Configures the fire and smoke emitters with appearance, behavior, and lifespan.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeEmitter() {
 | 
			
		||||
        fire = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle, 50);
 | 
			
		||||
        fire.setMaterial(mat);
 | 
			
		||||
        fire.setStartColor(ColorRGBA.DarkGray);
 | 
			
		||||
        fire.setEndColor(ColorRGBA.DarkGray);
 | 
			
		||||
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
 | 
			
		||||
        fire.getParticleInfluencer().setVelocityVariation(0.4f);
 | 
			
		||||
        fire.setStartSize(0.7f);
 | 
			
		||||
        fire.setEndSize(3.8f);
 | 
			
		||||
        fire.setGravity(0, 0, -0.1f);
 | 
			
		||||
        fire.setLowLife(0.5f);
 | 
			
		||||
        fire.setHighLife(1.2f);
 | 
			
		||||
        fire.setParticlesPerSec(0);
 | 
			
		||||
 | 
			
		||||
        fire.setLocalTranslation(location);
 | 
			
		||||
 | 
			
		||||
        smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 40);
 | 
			
		||||
        smoke.setMaterial(mat);
 | 
			
		||||
        smoke.setImagesX(2);
 | 
			
		||||
        smoke.setImagesY(2);
 | 
			
		||||
        smoke.setStartColor(ColorRGBA.DarkGray);
 | 
			
		||||
        smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
 | 
			
		||||
        smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 2f));
 | 
			
		||||
        smoke.getParticleInfluencer().setVelocityVariation(0.5f);
 | 
			
		||||
        smoke.setStartSize(0.5f);
 | 
			
		||||
        smoke.setEndSize(1.5f);
 | 
			
		||||
        smoke.setGravity(0, 0, -0.3f);
 | 
			
		||||
        smoke.setLowLife(1.2f);
 | 
			
		||||
        smoke.setHighLife(2.5f);
 | 
			
		||||
        smoke.setParticlesPerSec(0);
 | 
			
		||||
 | 
			
		||||
        smoke.setLocalTranslation(location);
 | 
			
		||||
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
 | 
			
		||||
     * Both emitters are automatically detached after a predefined duration.
 | 
			
		||||
     */
 | 
			
		||||
    public void trigger() {
 | 
			
		||||
        if (!triggered) {
 | 
			
		||||
            triggered = true;
 | 
			
		||||
            initializeEmitter();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        rootNode.attachChild(fire);
 | 
			
		||||
        fire.emitAllParticles();
 | 
			
		||||
        fire.addControl(new AbstractControl() {
 | 
			
		||||
            private float elapsedTime = 0;
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlUpdate(float tpf) {
 | 
			
		||||
                elapsedTime += tpf;
 | 
			
		||||
                if (elapsedTime > 10f) {
 | 
			
		||||
                    rootNode.detachChild(fire);
 | 
			
		||||
                    fire.removeControl(this);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        rootNode.attachChild(smoke);
 | 
			
		||||
        smoke.emitAllParticles();
 | 
			
		||||
        smoke.addControl(new AbstractControl() {
 | 
			
		||||
            private float elapsedTime = 0;
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlUpdate(float tpf) {
 | 
			
		||||
                elapsedTime += tpf;
 | 
			
		||||
                if (elapsedTime > 10f) {
 | 
			
		||||
                    rootNode.detachChild(smoke);
 | 
			
		||||
                    smoke.removeControl(this);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -172,8 +172,8 @@ public void turbo() {
 | 
			
		||||
     * Performs linear interpolation between two values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start The starting value.
 | 
			
		||||
     * @param end The target value.
 | 
			
		||||
     * @param t The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @param end   The target value.
 | 
			
		||||
     * @param t     The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @return The interpolated value.
 | 
			
		||||
     */
 | 
			
		||||
    private static float lerp(float start, float end, float t) {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
package pp.mdga.client.animation;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class TimerManager {
 | 
			
		||||
    private final List<TimedTask> tasks = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a timed task that will execute after the specified delay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delaySeconds The delay in seconds.
 | 
			
		||||
     * @param task         The Runnable task to execute after the delay.
 | 
			
		||||
     */
 | 
			
		||||
    public void addTask(float delaySeconds, Runnable task) {
 | 
			
		||||
        tasks.add(new TimedTask(delaySeconds, task));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the timer manager to process and execute tasks when their delay has elapsed.
 | 
			
		||||
     * This should be called in the `controlUpdate` method or a similar update loop.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf Time per frame (delta time) provided by the update loop.
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        Iterator<TimedTask> iterator = tasks.iterator();
 | 
			
		||||
        while (iterator.hasNext()) {
 | 
			
		||||
            TimedTask task = iterator.next();
 | 
			
		||||
            task.update(tpf);
 | 
			
		||||
            if (task.isReady()) {
 | 
			
		||||
                task.run();
 | 
			
		||||
                iterator.remove();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears all pending tasks from the manager.
 | 
			
		||||
     */
 | 
			
		||||
    public void clearTasks() {
 | 
			
		||||
        tasks.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the manager has any pending tasks.
 | 
			
		||||
     *
 | 
			
		||||
     * @return True if there are pending tasks, otherwise false.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean hasPendingTasks() {
 | 
			
		||||
        return !tasks.isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Internal class representing a single timed task.
 | 
			
		||||
     */
 | 
			
		||||
    private static class TimedTask {
 | 
			
		||||
        private float remainingTime;
 | 
			
		||||
        private final Runnable task;
 | 
			
		||||
 | 
			
		||||
        public TimedTask(float delaySeconds, Runnable task) {
 | 
			
		||||
            this.remainingTime = delaySeconds;
 | 
			
		||||
            this.task = task;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void update(float tpf) {
 | 
			
		||||
            remainingTime -= tpf;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean isReady() {
 | 
			
		||||
            return remainingTime <= 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void run() {
 | 
			
		||||
            task.run();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,8 +17,8 @@ public class ZoomControl extends InitControl {
 | 
			
		||||
    private float zoomFactor = 1f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
      * Constructs a new ZoomControl with the default zoom speed.
 | 
			
		||||
      */
 | 
			
		||||
     * Constructs a new ZoomControl with the default zoom speed.
 | 
			
		||||
     */
 | 
			
		||||
    public ZoomControl() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -78,8 +78,8 @@ private void end() {
 | 
			
		||||
     * Performs linear interpolation between two values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param start The starting value.
 | 
			
		||||
     * @param end The target value.
 | 
			
		||||
     * @param t The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @param end   The target value.
 | 
			
		||||
     * @param t     The interpolation parameter (0 <= t <= 1).
 | 
			
		||||
     * @return The interpolated value.
 | 
			
		||||
     */
 | 
			
		||||
    private static float lerp(float start, float end, float t) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,4 +5,5 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Record for holding Asset information
 | 
			
		||||
 */
 | 
			
		||||
record AssetOnMap(Asset asset, int x, int y, float rot) {}
 | 
			
		||||
record AssetOnMap(Asset asset, int x, int y, float rot) {
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package pp.mdga.client.board;
 | 
			
		||||
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue;
 | 
			
		||||
@@ -10,11 +12,10 @@
 | 
			
		||||
import pp.mdga.client.Asset;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.animation.MissileAnimation;
 | 
			
		||||
import pp.mdga.client.animation.MoveControl;
 | 
			
		||||
import pp.mdga.client.animation.JetAnimation;
 | 
			
		||||
import pp.mdga.client.animation.*;
 | 
			
		||||
import pp.mdga.client.gui.DiceControl;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.game.Piece;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
@@ -52,21 +53,26 @@ public class BoardHandler {
 | 
			
		||||
    // Flags and lists for handling piece selection and movement
 | 
			
		||||
    private List<PieceControl> selectableOwnPieces;
 | 
			
		||||
    private List<PieceControl> selectableEnemyPieces;
 | 
			
		||||
    private Map<PieceControl, NodeControl> selectedPieceNodeMap;
 | 
			
		||||
    private List<NodeControl> outlineNodes;
 | 
			
		||||
    private PieceControl selectedOwnPiece;
 | 
			
		||||
    private PieceControl selectedEnemyPiece;
 | 
			
		||||
    private DiceControl diceControl;
 | 
			
		||||
    //Radar Position for Matrix animation
 | 
			
		||||
    private Vector3f radarPos;
 | 
			
		||||
    //TankTop for shellAnimation
 | 
			
		||||
    private TankTopControl tankTop;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new BoardHandler.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app The main application instance
 | 
			
		||||
     * @param app      The main application instance
 | 
			
		||||
     * @param rootNode The root node where the board will be attached
 | 
			
		||||
     * @param fpp The post-processor for effects like shadows or filters
 | 
			
		||||
     * @param fpp      The post-processor for effects like shadows or filters
 | 
			
		||||
     * @throws RuntimeException if the app is null
 | 
			
		||||
     */
 | 
			
		||||
    public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
 | 
			
		||||
        if(app == null) throw new RuntimeException("app is null");
 | 
			
		||||
        if (app == null) throw new RuntimeException("app is null");
 | 
			
		||||
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.fpp = fpp;
 | 
			
		||||
@@ -82,6 +88,7 @@ public void init() {
 | 
			
		||||
        isInitialised = true;
 | 
			
		||||
        selectableOwnPieces = new ArrayList<>();
 | 
			
		||||
        selectableEnemyPieces = new ArrayList<>();
 | 
			
		||||
        selectedPieceNodeMap = new HashMap<>();
 | 
			
		||||
        outlineNodes = new ArrayList<>();
 | 
			
		||||
        selectedOwnPiece = null;
 | 
			
		||||
        selectedEnemyPiece = null;
 | 
			
		||||
@@ -92,7 +99,7 @@ public void init() {
 | 
			
		||||
    /**
 | 
			
		||||
     * Shuts down the board handler by detaching all board-related nodes and clearing selected pieces.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdown(){
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        clearSelectable();
 | 
			
		||||
        isInitialised = false;
 | 
			
		||||
        rootNode.detachChild(rootNodeBoard);
 | 
			
		||||
@@ -101,7 +108,7 @@ public void shutdown(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds an asset to the map of player assets, ensuring that the player does not have too many assets.
 | 
			
		||||
     *
 | 
			
		||||
     * @param col The color of the player
 | 
			
		||||
     * @param col        The color of the player
 | 
			
		||||
     * @param assetOnMap The asset to be added
 | 
			
		||||
     * @throws RuntimeException if there are too many assets for the player
 | 
			
		||||
     */
 | 
			
		||||
@@ -122,7 +129,7 @@ private void initMap() {
 | 
			
		||||
        waitingPiecesMap = new HashMap<>();
 | 
			
		||||
        pieceColor = new HashMap<>();
 | 
			
		||||
        diceControl = new DiceControl(app.getAssetManager());
 | 
			
		||||
        diceControl.create(new Vector3f(0,0,0), 0.7f, true);
 | 
			
		||||
        diceControl.create(new Vector3f(0, 0, 0), 0.7f, true);
 | 
			
		||||
        waitingNodes = new HashMap<>();
 | 
			
		||||
        waitingNodes.put(Color.AIRFORCE, new HashMap<>());
 | 
			
		||||
        waitingNodes.put(Color.ARMY, new HashMap<>());
 | 
			
		||||
@@ -139,7 +146,7 @@ private void initMap() {
 | 
			
		||||
                case cir -> addFigureToPlayerMap(assetToColor(Asset.cir), assetOnMap);
 | 
			
		||||
                case marine -> addFigureToPlayerMap(assetToColor(Asset.marine), assetOnMap);
 | 
			
		||||
                case node_normal, node_bonus, node_start ->
 | 
			
		||||
                    infield.add(displayAndControl(assetOnMap, new NodeControl(app, fpp)));
 | 
			
		||||
                        infield.add(displayAndControl(assetOnMap, new NodeControl(app, fpp)));
 | 
			
		||||
                case node_home_black -> addHomeNode(homeNodesMap, Color.AIRFORCE, assetOnMap);
 | 
			
		||||
                case node_home_blue -> addHomeNode(homeNodesMap, Color.NAVY, assetOnMap);
 | 
			
		||||
                case node_home_green -> addHomeNode(homeNodesMap, Color.ARMY, assetOnMap);
 | 
			
		||||
@@ -148,12 +155,24 @@ private void initMap() {
 | 
			
		||||
                case node_wait_blue -> addHomeNode(waitingNodesMap, Color.NAVY, assetOnMap);
 | 
			
		||||
                case node_wait_green -> addHomeNode(waitingNodesMap, Color.ARMY, assetOnMap);
 | 
			
		||||
                case node_wait_yellow -> addHomeNode(waitingNodesMap, Color.CYBER, assetOnMap);
 | 
			
		||||
                case radar -> addRadar(assetOnMap);
 | 
			
		||||
                case tankShoot -> addTankShoot(assetOnMap);
 | 
			
		||||
 | 
			
		||||
                default -> displayAsset(assetOnMap);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addTankShoot(AssetOnMap assetOnMap) {
 | 
			
		||||
        displayAsset(assetOnMap);
 | 
			
		||||
        tankTop = displayAndControl(new AssetOnMap(Asset.tankShootTop, assetOnMap.x(), assetOnMap.y(), assetOnMap.rot()), new TankTopControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addRadar(AssetOnMap assetOnMap) {
 | 
			
		||||
        radarPos = gridToWorld(assetOnMap.x(), assetOnMap.y());
 | 
			
		||||
        displayAsset(assetOnMap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts an asset to its corresponding color.
 | 
			
		||||
     *
 | 
			
		||||
@@ -175,8 +194,8 @@ private Color assetToColor(Asset asset) {
 | 
			
		||||
     * Creates a 3D model of an asset and adds it to the board.
 | 
			
		||||
     *
 | 
			
		||||
     * @param asset The asset to be displayed
 | 
			
		||||
     * @param pos The position of the asset on the board
 | 
			
		||||
     * @param rot The rotation of the asset
 | 
			
		||||
     * @param pos   The position of the asset on the board
 | 
			
		||||
     * @param rot   The rotation of the asset
 | 
			
		||||
     * @return The Spatial representation of the asset
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createModel(Asset asset, Vector3f pos, float rot) {
 | 
			
		||||
@@ -187,11 +206,16 @@ private Spatial createModel(Asset asset, Vector3f pos, float rot) {
 | 
			
		||||
        model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(rot));
 | 
			
		||||
        model.setLocalTranslation(pos);
 | 
			
		||||
        model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
 | 
			
		||||
        mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
 | 
			
		||||
        mat.setBoolean("UseMaterialColors", true); // Required for Material Colors
 | 
			
		||||
        mat.setColor("Diffuse", new ColorRGBA(1, 1, 1, 1)); // White color with full alpha
 | 
			
		||||
        mat.setColor("Ambient", new ColorRGBA(1, 1, 1, 1)); // Ambient color with full alpha
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 | 
			
		||||
        model.setMaterial(mat);
 | 
			
		||||
        rootNodeBoard.attachChild(model);
 | 
			
		||||
 | 
			
		||||
        rootNodeBoard.attachChild(model);
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -218,17 +242,39 @@ private Spatial displayAsset(AssetOnMap assetOnMap) {
 | 
			
		||||
        return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a visual representation of an asset to the scene, attaches a control to it, and returns the control.
 | 
			
		||||
     *
 | 
			
		||||
     * @param assetOnMap The asset to be displayed in the 3D environment.
 | 
			
		||||
     * @param control    The control to be added to the spatial representing the asset.
 | 
			
		||||
     * @param <T>        The type of control, extending {@code AbstractControl}.
 | 
			
		||||
     * @return The control that was added to the spatial.
 | 
			
		||||
     */
 | 
			
		||||
    private <T extends AbstractControl> T displayAndControl(AssetOnMap assetOnMap, T control) {
 | 
			
		||||
        Spatial spatial = displayAsset(assetOnMap);
 | 
			
		||||
        spatial.addControl(control);
 | 
			
		||||
        return control;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl){
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves a piece in the 3D environment to the location of a specified node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pieceControl The control managing the piece to be moved.
 | 
			
		||||
     * @param nodeControl  The control managing the target node to which the piece will move.
 | 
			
		||||
     */
 | 
			
		||||
    private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl) {
 | 
			
		||||
        pieceControl.setLocation(nodeControl.getLocation());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a home node for a specific player color, attaching it to the map of home nodes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map        The map storing lists of home nodes by player color.
 | 
			
		||||
     * @param color      The color associated with the home nodes to be added.
 | 
			
		||||
     * @param assetOnMap The asset representing the home node in the 3D environment.
 | 
			
		||||
     * @throws RuntimeException if more than 4 home nodes are added for a single color.
 | 
			
		||||
     */
 | 
			
		||||
    private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap) {
 | 
			
		||||
        List<NodeControl> homeNodes = addItemToMapList(map, color, displayAndControl(assetOnMap, new NodeControl(app, fpp)));
 | 
			
		||||
        if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
 | 
			
		||||
    }
 | 
			
		||||
@@ -244,47 +290,74 @@ private float getRotationMove(Vector3f prev, Vector3f next) {
 | 
			
		||||
        Vector3f direction = next.subtract(prev).normalizeLocal();
 | 
			
		||||
        //I had to reverse dir.y, because then it worked.
 | 
			
		||||
        float newRot = (float) Math.toDegrees(Math.atan2(direction.x, -direction.y));
 | 
			
		||||
        if(newRot < 0) newRot += 360;
 | 
			
		||||
        if (newRot < 0) newRot += 360;
 | 
			
		||||
        return newRot;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
        * Recursively moves a piece from its current index to the destination index,
 | 
			
		||||
        * to keep track of the piece rotation.
 | 
			
		||||
        *
 | 
			
		||||
        * @param uuid The UUID of the piece to move.
 | 
			
		||||
        * @param curIndex The current index of the piece.
 | 
			
		||||
        * @param moveIndex The target index to move the piece to.
 | 
			
		||||
     * Recursively moves a piece from its current index to the destination index,
 | 
			
		||||
     * to keep track of the piece rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid      The UUID of the piece to move.
 | 
			
		||||
     * @param curIndex  The current index of the piece.
 | 
			
		||||
     * @param moveIndex The target index to move the piece to.
 | 
			
		||||
     */
 | 
			
		||||
    private void movePieceRek(UUID uuid, int curIndex, int moveIndex){
 | 
			
		||||
    private void movePieceRek(UUID uuid, int curIndex, int moveIndex) {
 | 
			
		||||
        if (curIndex == moveIndex) return;
 | 
			
		||||
 | 
			
		||||
        curIndex = (curIndex + 1) % infield.size();
 | 
			
		||||
        int nextIndex = (curIndex + 1) % infield.size();
 | 
			
		||||
 | 
			
		||||
        PieceControl pieceControl = pieces.get(uuid);
 | 
			
		||||
        NodeControl nodeControl = infield.get(curIndex);
 | 
			
		||||
        NodeControl nodeCur = infield.get(curIndex);
 | 
			
		||||
        NodeControl nodeMove = infield.get(nextIndex);
 | 
			
		||||
 | 
			
		||||
        pieceControl.setRotation(getRotationMove(pieceControl.getLocation(),nodeControl.getLocation()));
 | 
			
		||||
        pieceControl.setRotation(getRotationMove(nodeCur.getLocation(), nodeMove.getLocation()));
 | 
			
		||||
 | 
			
		||||
        movePieceToNode(pieceControl, nodeControl);
 | 
			
		||||
        movePieceToNode(pieceControl, nodeMove);
 | 
			
		||||
 | 
			
		||||
        movePieceRek(uuid, curIndex, moveIndex);
 | 
			
		||||
 | 
			
		||||
        movePieceRek(uuid, nextIndex, moveIndex);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T, E> List<T> addItemToMapList(Map<E,List<T>> map, E key, T item){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds an item to a list in a map. If the key does not exist in the map, a new list is created.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map  The map containing lists of items.
 | 
			
		||||
     * @param key  The key associated with the list in the map.
 | 
			
		||||
     * @param item The item to be added to the list.
 | 
			
		||||
     * @param <T>  The type of items in the list.
 | 
			
		||||
     * @param <E>  The type of the key in the map.
 | 
			
		||||
     * @return The updated list associated with the specified key.
 | 
			
		||||
     */
 | 
			
		||||
    private <T, E> List<T> addItemToMapList(Map<E, List<T>> map, E key, T item) {
 | 
			
		||||
        List<T> list = map.getOrDefault(key, new ArrayList<>());
 | 
			
		||||
        list.add(item);
 | 
			
		||||
        map.put(key, list);
 | 
			
		||||
        return list;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T, E> void removeItemFromMapList(Map<E,List<T>> map, E key, T item){
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes an item from a list in a map. If the key does not exist in the map, a new list is created.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map  The map containing lists of items.
 | 
			
		||||
     * @param key  The key associated with the list in the map.
 | 
			
		||||
     * @param item The item to be removed from the list.
 | 
			
		||||
     * @param <T>  The type of items in the list.
 | 
			
		||||
     * @param <E>  The type of the key in the map.
 | 
			
		||||
     */
 | 
			
		||||
    private <T, E> void removeItemFromMapList(Map<E, List<T>> map, E key, T item) {
 | 
			
		||||
        List<T> list = map.getOrDefault(key, new ArrayList<>());
 | 
			
		||||
        list.remove(item);
 | 
			
		||||
        map.put(key, list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Vector3f getWaitingPos(Color color){
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the mean position of the waiting nodes for a specific color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color associated with the waiting nodes.
 | 
			
		||||
     * @return The mean position of the waiting nodes as a {@code Vector3f}.
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f getWaitingPos(Color color) {
 | 
			
		||||
        return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -308,20 +381,22 @@ public static Vector3f getMeanPosition(List<Vector3f> vectors) {
 | 
			
		||||
     * Adds a player to the game by associating a color and a list of UUIDs to corresponding assets and waiting nodes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color of the player
 | 
			
		||||
     * @param uuid the list of UUIDs representing the player's assets
 | 
			
		||||
     * @param uuid  the list of UUIDs representing the player's assets
 | 
			
		||||
     * @throws RuntimeException if the number of assets or waiting nodes does not match the provided UUIDs
 | 
			
		||||
     */
 | 
			
		||||
    public void addPlayer(Color color, List<UUID> uuid) {
 | 
			
		||||
 | 
			
		||||
        List<AssetOnMap> playerAssets = colorAssetsMap.get(color);
 | 
			
		||||
        if (playerAssets == null) throw new RuntimeException("Assets for Player color are not defined");
 | 
			
		||||
        if (uuid.size() != playerAssets.size()) throw new RuntimeException("UUID array and playerAssets are not the same size");
 | 
			
		||||
        if (uuid.size() != playerAssets.size())
 | 
			
		||||
            throw new RuntimeException("UUID array and playerAssets are not the same size");
 | 
			
		||||
 | 
			
		||||
        List<NodeControl> waitNodes = waitingNodesMap.get(color);
 | 
			
		||||
        if (waitNodes.size() != playerAssets.size()) throw new RuntimeException("waitNodes size does not match playerAssets size");
 | 
			
		||||
        if (waitNodes.size() != playerAssets.size())
 | 
			
		||||
            throw new RuntimeException("waitNodes size does not match playerAssets size");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < playerAssets.size(); i++){
 | 
			
		||||
        for (int i = 0; i < playerAssets.size(); i++) {
 | 
			
		||||
            AssetOnMap assetOnMap = playerAssets.get(i);
 | 
			
		||||
            UUID pieceUuid = uuid.get(i);
 | 
			
		||||
 | 
			
		||||
@@ -346,18 +421,18 @@ public void addPlayer(Color color, List<UUID> uuid) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves a piece to its corresponding home node based on the given index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to move
 | 
			
		||||
     * @param uuid  the UUID of the piece to move
 | 
			
		||||
     * @param index the index of the home node to move the piece to
 | 
			
		||||
     * @throws RuntimeException if the UUID is not mapped to a color or if the home nodes are not properly defined
 | 
			
		||||
     */
 | 
			
		||||
    private void moveHomePiece(UUID uuid, int index){
 | 
			
		||||
    private void moveHomePiece(UUID uuid, int index) {
 | 
			
		||||
 | 
			
		||||
        Color color = pieceColor.get(uuid);
 | 
			
		||||
        if(color == null) throw new RuntimeException("uuid is not mapped to a color");
 | 
			
		||||
        if (color == null) throw new RuntimeException("uuid is not mapped to a color");
 | 
			
		||||
 | 
			
		||||
        List<NodeControl> homeNodes = homeNodesMap.get(color);
 | 
			
		||||
 | 
			
		||||
        if(homeNodesMap.size() != 4) throw new RuntimeException("HomeNodes for" + color + " are not properly defined");
 | 
			
		||||
        if (homeNodesMap.size() != 4) throw new RuntimeException("HomeNodes for" + color + " are not properly defined");
 | 
			
		||||
 | 
			
		||||
        PieceControl pieceControl = pieces.get(uuid);
 | 
			
		||||
        NodeControl nodeControl = homeNodes.get(index);
 | 
			
		||||
@@ -365,21 +440,22 @@ private void moveHomePiece(UUID uuid, int index){
 | 
			
		||||
 | 
			
		||||
        //rotate piece in direction of homeNodes
 | 
			
		||||
        NodeControl firstHomeNode = homeNodes.get(0);
 | 
			
		||||
        NodeControl lastHomeNode = homeNodes.get(homeNodes.size()-1);
 | 
			
		||||
        NodeControl lastHomeNode = homeNodes.get(homeNodes.size() - 1);
 | 
			
		||||
 | 
			
		||||
        pieceControl.setRotation(getRotationMove(firstHomeNode.getLocation(), lastHomeNode.getLocation()));
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the movement of a piece to a target node based on the given index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to move
 | 
			
		||||
     * @param uuid      the UUID of the piece to move
 | 
			
		||||
     * @param nodeIndex the index of the target node to move the piece to
 | 
			
		||||
     * @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
 | 
			
		||||
     * @throws RuntimeException         if the UUID is not mapped to a color or the piece control is not found
 | 
			
		||||
     * @throws IllegalArgumentException if the node index is invalid
 | 
			
		||||
     */
 | 
			
		||||
    private void movePieceStart(UUID uuid, int nodeIndex){
 | 
			
		||||
    private void movePieceStart(UUID uuid, int nodeIndex) {
 | 
			
		||||
 | 
			
		||||
        // Farbe des Pieces abrufen
 | 
			
		||||
        Color color = pieceColor.get(uuid);
 | 
			
		||||
@@ -400,19 +476,21 @@ private void movePieceStart(UUID uuid, int nodeIndex){
 | 
			
		||||
        removeItemFromMapList(waitingPiecesMap, color, pieceControl);
 | 
			
		||||
        waitingNodes.get(color).remove(uuid);
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves a piece from its current position to the target position based on the given indexes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to move
 | 
			
		||||
     * @param curIndex the current index of the piece
 | 
			
		||||
     * @param uuid      the UUID of the piece to move
 | 
			
		||||
     * @param curIndex  the current index of the piece
 | 
			
		||||
     * @param moveIndex the target index of the move
 | 
			
		||||
     */
 | 
			
		||||
    private void movePiece(UUID uuid, int curIndex, int moveIndex){
 | 
			
		||||
    private void movePiece(UUID uuid, int curIndex, int moveIndex) {
 | 
			
		||||
 | 
			
		||||
        movePieceRek(uuid, curIndex, moveIndex);
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
        app.getModelSynchronize().animationEnd();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -421,7 +499,7 @@ private void movePiece(UUID uuid, int curIndex, int moveIndex){
 | 
			
		||||
     * @param uuid the UUID of the piece to throw
 | 
			
		||||
     * @throws RuntimeException if the UUID is not mapped to a color or if no available waiting nodes are found
 | 
			
		||||
     */
 | 
			
		||||
    private void throwPiece(UUID uuid){
 | 
			
		||||
    private void throwPiece(UUID uuid) {
 | 
			
		||||
 | 
			
		||||
        // Farbe des Pieces abrufen
 | 
			
		||||
        Color color = pieceColor.get(uuid);
 | 
			
		||||
@@ -455,8 +533,7 @@ private void throwPiece(UUID uuid){
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to shield
 | 
			
		||||
     */
 | 
			
		||||
    public void shieldPiece(UUID uuid){
 | 
			
		||||
 | 
			
		||||
    public void shieldPiece(UUID uuid) {
 | 
			
		||||
        pieces.get(uuid).activateShield();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -465,8 +542,7 @@ public void shieldPiece(UUID uuid){
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to unshield
 | 
			
		||||
     */
 | 
			
		||||
    public void unshieldPiece(UUID uuid){
 | 
			
		||||
 | 
			
		||||
    public void unshieldPiece(UUID uuid) {
 | 
			
		||||
        pieces.get(uuid).deactivateShield();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -475,7 +551,7 @@ public void unshieldPiece(UUID uuid){
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to suppress the shield
 | 
			
		||||
     */
 | 
			
		||||
    public void suppressShield(UUID uuid){
 | 
			
		||||
    public void suppressShield(UUID uuid) {
 | 
			
		||||
 | 
			
		||||
        pieces.get(uuid).suppressShield();
 | 
			
		||||
    }
 | 
			
		||||
@@ -483,14 +559,14 @@ public void suppressShield(UUID uuid){
 | 
			
		||||
    /**
 | 
			
		||||
     * Swaps the positions and rotations of two pieces.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p1 the first piece to swap
 | 
			
		||||
     * @param p2 the second piece to swap
 | 
			
		||||
     * @param p1   the first piece to swap
 | 
			
		||||
     * @param p2   the second piece to swap
 | 
			
		||||
     * @param loc1 the original location of the first piece
 | 
			
		||||
     * @param rot1 the original rotation of the first piece
 | 
			
		||||
     * @param loc2 the original location of the second piece
 | 
			
		||||
     * @param rot2 the original rotation of the second piece
 | 
			
		||||
     */
 | 
			
		||||
    private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2){
 | 
			
		||||
    private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2) {
 | 
			
		||||
        p1.setLocation(loc2);
 | 
			
		||||
        p2.setLocation(loc1);
 | 
			
		||||
 | 
			
		||||
@@ -503,13 +579,14 @@ private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float r
 | 
			
		||||
    /**
 | 
			
		||||
     * Outlines the possible move nodes for a list of pieces based on the move indices and whether it's a home move.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pieces the list of UUIDs representing the pieces to outline
 | 
			
		||||
     * @param pieces     the list of UUIDs representing the pieces to outline
 | 
			
		||||
     * @param moveIndexe the list of indices for the target move nodes
 | 
			
		||||
     * @param homeMoves the list indicating whether the move is a home move
 | 
			
		||||
     * @param homeMoves  the list indicating whether the move is a home move
 | 
			
		||||
     * @throws RuntimeException if the sizes of the input lists do not match
 | 
			
		||||
     */
 | 
			
		||||
    public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) {
 | 
			
		||||
        if(pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size()) throw new RuntimeException("arrays are not the same size");
 | 
			
		||||
        if (pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size())
 | 
			
		||||
            throw new RuntimeException("arrays are not the same size");
 | 
			
		||||
 | 
			
		||||
        selectableEnemyPieces.clear();
 | 
			
		||||
        selectableOwnPieces.clear();
 | 
			
		||||
@@ -524,44 +601,38 @@ public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolea
 | 
			
		||||
            if (homeMoves.get(i)) {
 | 
			
		||||
                Color color = pieceColor.get(uuid);
 | 
			
		||||
                nodeControl = homeNodesMap.get(color).get(moveIndexe.get(i));
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
            } else {
 | 
			
		||||
                nodeControl = infield.get(moveIndexe.get(i));
 | 
			
		||||
            }
 | 
			
		||||
            nodeControl.highlight();
 | 
			
		||||
            pieceControl.highlight(false);
 | 
			
		||||
            pieceControl.setHoverable(true);
 | 
			
		||||
            pieceControl.setSelectable(true);
 | 
			
		||||
            pieceControl.selectableOwn();
 | 
			
		||||
            nodeControl.selectableOwn();
 | 
			
		||||
            outlineNodes.add(nodeControl);
 | 
			
		||||
            selectableOwnPieces.add(pieceControl);
 | 
			
		||||
            selectedPieceNodeMap.put(pieceControl, nodeControl);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Outlines the pieces that can be swapped based on the provided own and enemy pieces.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownPieces the list of UUIDs representing the player's pieces
 | 
			
		||||
     * @param ownPieces   the list of UUIDs representing the player's pieces
 | 
			
		||||
     * @param enemyPieces the list of UUIDs representing the enemy's pieces
 | 
			
		||||
     */
 | 
			
		||||
    public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
 | 
			
		||||
    public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces) {
 | 
			
		||||
 | 
			
		||||
        selectableEnemyPieces.clear();
 | 
			
		||||
        selectableOwnPieces.clear();
 | 
			
		||||
        selectedOwnPiece = null;
 | 
			
		||||
        selectedEnemyPiece = null;
 | 
			
		||||
 | 
			
		||||
        for(UUID uuid : ownPieces) {
 | 
			
		||||
        for (UUID uuid : ownPieces) {
 | 
			
		||||
            PieceControl p = pieces.get(uuid);
 | 
			
		||||
            p.highlight(false);
 | 
			
		||||
            p.setHoverable(true);
 | 
			
		||||
            p.setSelectable(true);
 | 
			
		||||
            p.selectableOwn();
 | 
			
		||||
            selectableOwnPieces.add(p);
 | 
			
		||||
        }
 | 
			
		||||
        for(UUID uuid : enemyPieces) {
 | 
			
		||||
        for (UUID uuid : enemyPieces) {
 | 
			
		||||
            PieceControl p = pieces.get(uuid);
 | 
			
		||||
            p.highlight(true);
 | 
			
		||||
            p.setHoverable(true);
 | 
			
		||||
            p.setSelectable(true);
 | 
			
		||||
            p.selectableEnemy();
 | 
			
		||||
            selectableEnemyPieces.add(p);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -571,17 +642,15 @@ public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
 | 
			
		||||
     *
 | 
			
		||||
     * @param pieces the list of UUIDs representing the pieces to be shielded
 | 
			
		||||
     */
 | 
			
		||||
    public void outlineShield(List<UUID> pieces){
 | 
			
		||||
    public void outlineShield(List<UUID> pieces) {
 | 
			
		||||
        selectableOwnPieces.clear();
 | 
			
		||||
        selectableEnemyPieces.clear();
 | 
			
		||||
        selectedOwnPiece = null;
 | 
			
		||||
        selectedEnemyPiece = null;
 | 
			
		||||
 | 
			
		||||
        for (UUID uuid : pieces){
 | 
			
		||||
        for (UUID uuid : pieces) {
 | 
			
		||||
            PieceControl p = this.pieces.get(uuid);
 | 
			
		||||
            p.highlight(false);
 | 
			
		||||
            p.setHoverable(true);
 | 
			
		||||
            p.setSelectable(true);
 | 
			
		||||
            p.selectableOwn();
 | 
			
		||||
            selectableOwnPieces.add(p);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -591,59 +660,87 @@ public void outlineShield(List<UUID> pieces){
 | 
			
		||||
     *
 | 
			
		||||
     * @param pieceSelected the PieceControl instance representing the piece selected by the user
 | 
			
		||||
     */
 | 
			
		||||
    public void pieceSelect(PieceControl pieceSelected) {
 | 
			
		||||
        boolean isSelected = pieceSelected.isSelected();
 | 
			
		||||
        if(selectableOwnPieces.contains(pieceSelected)){
 | 
			
		||||
            for(PieceControl p : selectableOwnPieces) {
 | 
			
		||||
                p.unSelect();
 | 
			
		||||
    public void pieceSelect(OutlineOEControl selected) {
 | 
			
		||||
        PieceControl piece = getPieceByOE(selected);
 | 
			
		||||
        NodeControl node = selectedPieceNodeMap.get(piece);
 | 
			
		||||
 | 
			
		||||
        boolean isSelected = piece.isSelected();
 | 
			
		||||
        if (selectableOwnPieces.contains(piece)) {
 | 
			
		||||
            for (PieceControl p : selectableOwnPieces) {
 | 
			
		||||
                p.selectOff();
 | 
			
		||||
                NodeControl n = selectedPieceNodeMap.get(p);
 | 
			
		||||
                if (n != null) n.selectOff();
 | 
			
		||||
            }
 | 
			
		||||
            if (!isSelected) {
 | 
			
		||||
                pieceSelected.select();
 | 
			
		||||
                selectedOwnPiece = pieceSelected;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                pieceSelected.unSelect();
 | 
			
		||||
                piece.selectOn();
 | 
			
		||||
                if (node != null) node.selectOn();
 | 
			
		||||
                selectedOwnPiece = piece;
 | 
			
		||||
            } else {
 | 
			
		||||
                piece.selectOff();
 | 
			
		||||
                if (node != null) node.selectOff();;
 | 
			
		||||
                selectedOwnPiece = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if(selectableEnemyPieces.contains(pieceSelected)) {
 | 
			
		||||
            for(PieceControl p : selectableEnemyPieces) {
 | 
			
		||||
                p.unSelect();
 | 
			
		||||
        } else if (selectableEnemyPieces.contains(piece)) {
 | 
			
		||||
            for (PieceControl p : selectableEnemyPieces) {
 | 
			
		||||
                p.selectOff();
 | 
			
		||||
            }
 | 
			
		||||
            if (!isSelected) {
 | 
			
		||||
                pieceSelected.select();
 | 
			
		||||
                selectedEnemyPiece = pieceSelected;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                pieceSelected.unSelect();
 | 
			
		||||
                piece.selectOn();
 | 
			
		||||
                selectedEnemyPiece = piece;
 | 
			
		||||
            } else {
 | 
			
		||||
                piece.selectOff();
 | 
			
		||||
                selectedEnemyPiece = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
 | 
			
		||||
        } else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
 | 
			
		||||
 | 
			
		||||
        app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hoverOn(OutlineOEControl hover) {
 | 
			
		||||
        PieceControl piece = getPieceByOE(hover);
 | 
			
		||||
        NodeControl node = selectedPieceNodeMap.get(piece);
 | 
			
		||||
 | 
			
		||||
        piece.hoverOn();
 | 
			
		||||
        if(node != null) node.hoverOn();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hoverOff(OutlineOEControl hover) {
 | 
			
		||||
        PieceControl piece = getPieceByOE(hover);
 | 
			
		||||
        NodeControl node = selectedPieceNodeMap.get(piece);
 | 
			
		||||
 | 
			
		||||
        piece.hoverOff();
 | 
			
		||||
        if(node != null) node.hoverOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PieceControl getPieceByOE(OutlineOEControl control){
 | 
			
		||||
        PieceControl piece;
 | 
			
		||||
        if (control instanceof PieceControl p){
 | 
			
		||||
            piece = p;
 | 
			
		||||
        }
 | 
			
		||||
        else if (control instanceof NodeControl n){
 | 
			
		||||
            piece = getKeyByValue(selectedPieceNodeMap, n);
 | 
			
		||||
        }
 | 
			
		||||
        else throw new RuntimeException("selected is not instanceof piece or node");
 | 
			
		||||
        return piece;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears all highlighted, selectable, and selected pieces and nodes.
 | 
			
		||||
     */
 | 
			
		||||
    public void clearSelectable(){
 | 
			
		||||
        for(PieceControl p : selectableEnemyPieces) {
 | 
			
		||||
            p.unSelect();
 | 
			
		||||
            p.unHighlight();
 | 
			
		||||
            p.setSelectable(false);
 | 
			
		||||
    public void clearSelectable() {
 | 
			
		||||
        for (PieceControl p : selectableOwnPieces) {
 | 
			
		||||
            p.selectableOff();
 | 
			
		||||
            NodeControl n = selectedPieceNodeMap.get(p);
 | 
			
		||||
            if(n != null) n.selectableOff();
 | 
			
		||||
        }
 | 
			
		||||
        for(PieceControl p : selectableOwnPieces) {
 | 
			
		||||
            p.unSelect();
 | 
			
		||||
            p.unHighlight();
 | 
			
		||||
            p.setSelectable(false);
 | 
			
		||||
        }
 | 
			
		||||
        for(NodeControl n : outlineNodes){
 | 
			
		||||
            n.deOutline();
 | 
			
		||||
        for (PieceControl p : selectableEnemyPieces) {
 | 
			
		||||
            p.selectableOff();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        outlineNodes.clear();
 | 
			
		||||
        selectableEnemyPieces.clear();
 | 
			
		||||
        selectableOwnPieces.clear();
 | 
			
		||||
        selectedPieceNodeMap.clear();
 | 
			
		||||
        selectedEnemyPiece = null;
 | 
			
		||||
        selectedOwnPiece = null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -653,16 +750,16 @@ public void clearSelectable(){
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color of the player whose dice should be displayed
 | 
			
		||||
     */
 | 
			
		||||
    public void showDice(Color color){
 | 
			
		||||
    public void showDice(Color color) {
 | 
			
		||||
        rootNodeBoard.attachChild(diceControl.getSpatial());
 | 
			
		||||
        diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4)));
 | 
			
		||||
        diceControl.setPos(getWaitingPos(color).add(new Vector3f(0, 0, 4)));
 | 
			
		||||
        diceControl.spin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the dice from the view.
 | 
			
		||||
     */
 | 
			
		||||
    public void hideDice(){
 | 
			
		||||
    public void hideDice() {
 | 
			
		||||
        diceControl.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -678,41 +775,42 @@ private <K, V> K getKeyByValue(Map<K, V> map, V value) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the movement of a piece from its current index to a target index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     * @param curIndex the current index of the piece
 | 
			
		||||
     * @param uuid      the UUID of the piece to animate
 | 
			
		||||
     * @param curIndex  the current index of the piece
 | 
			
		||||
     * @param moveIndex the target index to animate the piece to
 | 
			
		||||
     */
 | 
			
		||||
    public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
 | 
			
		||||
    public void movePieceAnim(UUID uuid, int curIndex, int moveIndex) {
 | 
			
		||||
 | 
			
		||||
        pieces.get(uuid).getSpatial().addControl(new MoveControl(
 | 
			
		||||
            infield.get(curIndex).getLocation(),
 | 
			
		||||
            infield.get(moveIndex).getLocation(),
 | 
			
		||||
            ()->movePiece(uuid,curIndex,moveIndex)));
 | 
			
		||||
                infield.get(curIndex).getLocation(),
 | 
			
		||||
                infield.get(moveIndex).getLocation(),
 | 
			
		||||
                () -> movePiece(uuid, curIndex, moveIndex)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the movement of a piece to its home position based on the given home index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     * @param uuid      the UUID of the piece to animate
 | 
			
		||||
     * @param homeIndex the index of the home node to move the piece to
 | 
			
		||||
     */
 | 
			
		||||
    public void movePieceHomeAnim(UUID uuid, int homeIndex){
 | 
			
		||||
    public void movePieceHomeAnim(UUID uuid, int homeIndex) {
 | 
			
		||||
        pieces.get(uuid).getSpatial().addControl(new MoveControl(
 | 
			
		||||
            pieces.get(uuid).getLocation(),
 | 
			
		||||
            homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
 | 
			
		||||
            ()->moveHomePiece(uuid,homeIndex)));
 | 
			
		||||
                pieces.get(uuid).getLocation(),
 | 
			
		||||
                homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
 | 
			
		||||
                () -> moveHomePiece(uuid, homeIndex)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the start of the movement of a piece to a target index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     * @param uuid      the UUID of the piece to animate
 | 
			
		||||
     * @param moveIndex the target index to animate the piece to
 | 
			
		||||
     */
 | 
			
		||||
    public void movePieceStartAnim(UUID uuid, int moveIndex){
 | 
			
		||||
    public void movePieceStartAnim(UUID uuid, int moveIndex) {
 | 
			
		||||
        pieces.get(uuid).getSpatial().addControl(new MoveControl(
 | 
			
		||||
            pieces.get(uuid).getLocation(),
 | 
			
		||||
            infield.get(moveIndex).getLocation(),
 | 
			
		||||
            ()->movePieceStart(uuid, moveIndex)
 | 
			
		||||
                pieces.get(uuid).getLocation(),
 | 
			
		||||
                infield.get(moveIndex).getLocation(),
 | 
			
		||||
                () -> movePieceStart(uuid, moveIndex)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -721,43 +819,61 @@ public void movePieceStartAnim(UUID uuid, int moveIndex){
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     */
 | 
			
		||||
    public void throwPieceAnim(UUID uuid){
 | 
			
		||||
    public void throwPieceAnim(UUID uuid) {
 | 
			
		||||
        pieces.get(uuid).getSpatial().addControl(new MoveControl(
 | 
			
		||||
            pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(), ()->throwPiece(uuid))
 | 
			
		||||
                pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(),
 | 
			
		||||
                () -> throwPiece(uuid))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the throwing of a piece to the next available waiting node and plays jet animation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     */
 | 
			
		||||
    public void throwBombAnim(UUID uuid){
 | 
			
		||||
        Vector3f targetPoint = pieces.get(uuid).getLocation();
 | 
			
		||||
 | 
			
		||||
        JetAnimation anim = new JetAnimation(app, rootNode, uuid, targetPoint, 40, 6);
 | 
			
		||||
        anim.start();
 | 
			
		||||
    public void throwPiece(UUID uuid, Color throwColor) {
 | 
			
		||||
        switch (throwColor) {
 | 
			
		||||
            case ARMY -> throwShell(uuid);
 | 
			
		||||
            case NAVY -> throwMissile(uuid);
 | 
			
		||||
            case CYBER -> throwMatrix(uuid);
 | 
			
		||||
            case AIRFORCE -> throwBomb(uuid);
 | 
			
		||||
            default -> throw new RuntimeException("invalid color");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the throwing of a piece to the next available waiting node and plays ship animation.
 | 
			
		||||
     * Animates the throwing of a piece to the next available waiting node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param uuid the UUID of the piece to animate
 | 
			
		||||
     */
 | 
			
		||||
    public void throwMissileAnim(UUID uuid){
 | 
			
		||||
    private void throwBomb(UUID uuid) {
 | 
			
		||||
        Vector3f targetPoint = pieces.get(uuid).getLocation();
 | 
			
		||||
 | 
			
		||||
        MissileAnimation anim = new MissileAnimation(app, rootNode, uuid, targetPoint, 2);
 | 
			
		||||
        JetAnimation anim = new JetAnimation(app, rootNode, targetPoint, 40, 6, () -> throwPieceAnim(uuid));
 | 
			
		||||
        anim.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void throwMatrix(UUID uuid) {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.MATRIX);
 | 
			
		||||
        Spatial piece = pieces.get(uuid).getSpatial();
 | 
			
		||||
        piece.addControl(new MatrixAnimation(app, radarPos, () -> {
 | 
			
		||||
            throwPieceAnim(uuid);
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void throwMissile(UUID uuid) {
 | 
			
		||||
        Vector3f targetPoint = pieces.get(uuid).getLocation();
 | 
			
		||||
 | 
			
		||||
        MissileAnimation anim = new MissileAnimation(app, rootNode, targetPoint, 2f, () -> throwPieceAnim(uuid));
 | 
			
		||||
        anim.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void throwShell(UUID uuid) {
 | 
			
		||||
        pieces.get(uuid).getSpatial().addControl(new ShellAnimation(tankTop, app, () -> throwPieceAnim(uuid)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Animates the swapping of two pieces by swapping their positions and rotations.
 | 
			
		||||
     *
 | 
			
		||||
     * @param piece1 the UUID of the first piece
 | 
			
		||||
     * @param piece2 the UUID of the second piece
 | 
			
		||||
     */
 | 
			
		||||
    public void swapPieceAnim(UUID piece1, UUID piece2){
 | 
			
		||||
    public void swapPieceAnim(UUID piece1, UUID piece2) {
 | 
			
		||||
        PieceControl piece1Control = pieces.get(piece1);
 | 
			
		||||
        PieceControl piece2Control = pieces.get(piece2);
 | 
			
		||||
 | 
			
		||||
@@ -767,14 +883,15 @@ public void swapPieceAnim(UUID piece1, UUID piece2){
 | 
			
		||||
        float rot2 = piece2Control.getRotation();
 | 
			
		||||
 | 
			
		||||
        piece1Control.getSpatial().addControl(new MoveControl(
 | 
			
		||||
            piece1Control.getLocation().clone(),
 | 
			
		||||
            piece2Control.getLocation().clone(),
 | 
			
		||||
            ()->{}
 | 
			
		||||
                piece1Control.getLocation().clone(),
 | 
			
		||||
                piece2Control.getLocation().clone(),
 | 
			
		||||
                () -> {
 | 
			
		||||
                }
 | 
			
		||||
        ));
 | 
			
		||||
        piece2Control.getSpatial().addControl(new MoveControl(
 | 
			
		||||
            piece2Control.getLocation().clone(),
 | 
			
		||||
            piece1Control.getLocation().clone(),
 | 
			
		||||
            ()->swapPieces(piece1Control,piece2Control,loc1,rot1,loc2,rot2)
 | 
			
		||||
                piece2Control.getLocation().clone(),
 | 
			
		||||
                piece1Control.getLocation().clone(),
 | 
			
		||||
                () -> swapPieces(piece1Control, piece2Control, loc1, rot1, loc2, rot2)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
 | 
			
		||||
//        ssaoFilter = new SSAOFilter();
 | 
			
		||||
        fxaaFilter = new FXAAFilter();
 | 
			
		||||
 | 
			
		||||
        sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI*1,0,FastMath.HALF_PI*0.2f);
 | 
			
		||||
        sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI * 1, 0, FastMath.HALF_PI * 0.2f);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -94,7 +94,7 @@ public void init(Color ownColor) {
 | 
			
		||||
        init = true;
 | 
			
		||||
        initRot = true;
 | 
			
		||||
        this.ownColor = ownColor;
 | 
			
		||||
        app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor)*2);
 | 
			
		||||
        app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor) * 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -102,26 +102,29 @@ public void init(Color ownColor) {
 | 
			
		||||
     * and resets the camera position and rotation to its default state.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        app.getRootNode().removeLight(sun);
 | 
			
		||||
        app.getRootNode().removeLight(ambient);
 | 
			
		||||
        init = false;
 | 
			
		||||
        fpp.removeFilter(fxaaFilter);
 | 
			
		||||
        fpp.removeFilter(ssaoFilter);
 | 
			
		||||
        fpp.removeFilter(dlsf);
 | 
			
		||||
        app.getRootNode().detachChild(sky);
 | 
			
		||||
        app.getRootNode().removeLight(ambient);
 | 
			
		||||
        app.getRootNode().removeLight(sun);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Reset the camera to its default state
 | 
			
		||||
        app.getCamera().setLocation(defaultCameraPosition);
 | 
			
		||||
        app.getCamera().setRotation(defaultCameraRotation);
 | 
			
		||||
 | 
			
		||||
        fpp.removeFilter(dlsf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the camera position and rotation based on user input (scroll and rotation).
 | 
			
		||||
     * Adjusts the vertical angle and radius based on zoom and rotation values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param scroll The scroll input, determining zoom level.
 | 
			
		||||
     * @param scroll   The scroll input, determining zoom level.
 | 
			
		||||
     * @param rotation The rotation input, determining camera orientation.
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float scroll, float rotation) {
 | 
			
		||||
        if(!init) return;
 | 
			
		||||
        if (!init) return;
 | 
			
		||||
        float scrollValue = Math.max(0, Math.min(scroll, 100));
 | 
			
		||||
 | 
			
		||||
        float rotationValue = rotation % 360;
 | 
			
		||||
@@ -159,8 +162,8 @@ public void update(float scroll, float rotation) {
 | 
			
		||||
     * @param color The color used to determine the camera angle.
 | 
			
		||||
     * @return The camera angle in degrees.
 | 
			
		||||
     */
 | 
			
		||||
    private float getAngleByColor(Color color){
 | 
			
		||||
        return switch (color){
 | 
			
		||||
    private float getAngleByColor(Color color) {
 | 
			
		||||
        return switch (color) {
 | 
			
		||||
            case ARMY -> 0;
 | 
			
		||||
            case AIRFORCE -> 90;
 | 
			
		||||
            case NAVY -> 270;
 | 
			
		||||
@@ -175,7 +178,7 @@ private float getAngleByColor(Color color){
 | 
			
		||||
     * @param color The color used to determine the camera angle.
 | 
			
		||||
     * @return The initial camera angle in degrees.
 | 
			
		||||
     */
 | 
			
		||||
    private float getInitAngleByColor(Color color){
 | 
			
		||||
    private float getInitAngleByColor(Color color) {
 | 
			
		||||
        return (getAngleByColor(color) + 180) % 360;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,15 +28,15 @@ private MapLoader() {
 | 
			
		||||
     *
 | 
			
		||||
     * @param mapName The name of the map file to load. The file is expected to be located in the resources directory.
 | 
			
		||||
     * @return A list of {@link AssetOnMap} objects representing the assets placed on the map.
 | 
			
		||||
     * @throws IOException If an error occurs while reading the map file.
 | 
			
		||||
     * @throws IOException              If an error occurs while reading the map file.
 | 
			
		||||
     * @throws IllegalArgumentException If the map file contains invalid data.
 | 
			
		||||
     */
 | 
			
		||||
    public static List<AssetOnMap> loadMap(String mapName) {
 | 
			
		||||
        List<AssetOnMap> assetsOnMap = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        try (
 | 
			
		||||
            InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
 | 
			
		||||
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
 | 
			
		||||
                InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
 | 
			
		||||
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
 | 
			
		||||
        ) {
 | 
			
		||||
 | 
			
		||||
            while (true) {
 | 
			
		||||
@@ -65,11 +65,9 @@ public static List<AssetOnMap> loadMap(String mapName) {
 | 
			
		||||
                Asset asset = getLoadedAsset(assetName);
 | 
			
		||||
                assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IllegalArgumentException e) {
 | 
			
		||||
        } catch (IllegalArgumentException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -109,6 +107,9 @@ private static Asset getLoadedAsset(String assetName) {
 | 
			
		||||
            case "tank" -> Asset.tank;
 | 
			
		||||
            case "treeSmall" -> Asset.treeSmall;
 | 
			
		||||
            case "treeBig" -> Asset.treeBig;
 | 
			
		||||
            case "tank_shoot" -> Asset.tankShoot;
 | 
			
		||||
            case "treesBigBackground" -> Asset.treesBigBackground;
 | 
			
		||||
            case "treesSmallBackground" -> Asset.treesSmallBackground;
 | 
			
		||||
            default -> throw new IllegalStateException("Unexpected value: " + assetName);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,19 +3,13 @@
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A control that adds highlighting functionality to a node in the game.
 | 
			
		||||
 * This class extends {@link OutlineControl} to add an outline effect when the node is highlighted.
 | 
			
		||||
 */
 | 
			
		||||
public class NodeControl extends OutlineControl {
 | 
			
		||||
 | 
			
		||||
    private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
 | 
			
		||||
    private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
 | 
			
		||||
public class NodeControl extends OutlineOEControl {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@link NodeControl} with the specified application and post processor.
 | 
			
		||||
@@ -25,7 +19,7 @@ public class NodeControl extends OutlineControl {
 | 
			
		||||
     * @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
 | 
			
		||||
     */
 | 
			
		||||
    public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
 | 
			
		||||
        super(app, fpp);
 | 
			
		||||
        super(app, fpp, app.getCamera());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -34,15 +28,7 @@ public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link Vector3f} representing the node's location.
 | 
			
		||||
     */
 | 
			
		||||
    public Vector3f getLocation(){
 | 
			
		||||
    public Vector3f getLocation() {
 | 
			
		||||
        return this.getSpatial().getLocalTranslation();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Highlights the node by applying an outline effect.
 | 
			
		||||
     * The outline color and width are predefined as white and 6, respectively.
 | 
			
		||||
     */
 | 
			
		||||
    public void highlight() {
 | 
			
		||||
        super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.InitControl;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.outline.SelectObjectOutliner;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -13,35 +13,52 @@
 | 
			
		||||
 * object, allowing it to be highlighted or deselected.
 | 
			
		||||
 */
 | 
			
		||||
public class OutlineControl extends InitControl {
 | 
			
		||||
    /** The {@link SelectObjectOutliner} responsible for managing the outline effect. */
 | 
			
		||||
    private final SelectObjectOutliner outlineOwn;
 | 
			
		||||
    private static final int THICKNESS_DEFAULT = 6;
 | 
			
		||||
    private MdgaApp app;
 | 
			
		||||
    /**
 | 
			
		||||
     * The {@link SelectObjectOutliner} responsible for managing the outline effect.
 | 
			
		||||
     */
 | 
			
		||||
    private final SelectObjectOutliner selectObjectOutliner;
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private boolean hoverable = false;
 | 
			
		||||
    private boolean highlight = false;
 | 
			
		||||
    private boolean selectable = false;
 | 
			
		||||
    private boolean select = false;
 | 
			
		||||
    private ColorRGBA highlightColor;
 | 
			
		||||
    private int highlightWidth;
 | 
			
		||||
    private ColorRGBA hoverColor;
 | 
			
		||||
    private int hoverWidth;
 | 
			
		||||
    private ColorRGBA selectColor;
 | 
			
		||||
    private int selectWidth;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public OutlineControl(MdgaApp app, FilterPostProcessor fpp){
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an {@code OutlineControl} with default thickness for the object outline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app The main application managing the outline control.
 | 
			
		||||
     * @param fpp The {@code FilterPostProcessor} used for post-processing effects.
 | 
			
		||||
     */
 | 
			
		||||
    public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam,
 | 
			
		||||
                          ColorRGBA highlightColor, int highlightWidth,
 | 
			
		||||
                          ColorRGBA hoverColor, int hoverWidth,
 | 
			
		||||
                          ColorRGBA selectColor, int selectWidth
 | 
			
		||||
    ) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera(), app);
 | 
			
		||||
        this.highlightColor = highlightColor;
 | 
			
		||||
        this.highlightWidth = highlightWidth;
 | 
			
		||||
        this.hoverColor = hoverColor;
 | 
			
		||||
        this.hoverWidth = hoverWidth;
 | 
			
		||||
        this.selectColor = selectColor;
 | 
			
		||||
        this.selectWidth = selectWidth;
 | 
			
		||||
        selectObjectOutliner = new SelectObjectOutliner(fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thickness){
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies an outline to the spatial object with the given color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The {@link ColorRGBA} representing the color of the outline.
 | 
			
		||||
     */
 | 
			
		||||
    public void outline(ColorRGBA color){
 | 
			
		||||
        outlineOwn.select(spatial, color);
 | 
			
		||||
    }
 | 
			
		||||
//    public void outline(ColorRGBA color) {
 | 
			
		||||
//        selectObjectOutliner.select(spatial, color);
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies an outline to the spatial object with the given color and width.
 | 
			
		||||
@@ -49,19 +66,117 @@ public void outline(ColorRGBA color){
 | 
			
		||||
     * @param color The {@link ColorRGBA} representing the color of the outline.
 | 
			
		||||
     * @param width The width of the outline.
 | 
			
		||||
     */
 | 
			
		||||
    public void outline(ColorRGBA color, int width){
 | 
			
		||||
        deOutline();
 | 
			
		||||
        outlineOwn.select(spatial, color, width);
 | 
			
		||||
    public void outline(ColorRGBA color, int width) {
 | 
			
		||||
        outlineOff();
 | 
			
		||||
        selectObjectOutliner.select(spatial, color, width);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the outline effect from the spatial object.
 | 
			
		||||
     */
 | 
			
		||||
    public void deOutline(){
 | 
			
		||||
        outlineOwn.deselect(spatial);
 | 
			
		||||
    public void outlineOff() {
 | 
			
		||||
        selectObjectOutliner.deselect(spatial);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the instance of the {@code MdgaApp} associated with this control.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@code MdgaApp} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public MdgaApp getApp() {
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void highlightOn() {
 | 
			
		||||
        highlight = true;
 | 
			
		||||
        outline(highlightColor, highlightWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void highlightOff() {
 | 
			
		||||
        highlight = false;
 | 
			
		||||
        outlineOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hoverOn() {
 | 
			
		||||
        if (!hoverable) return;
 | 
			
		||||
        outline(hoverColor, hoverWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hoverOff() {
 | 
			
		||||
        if (!hoverable) return;
 | 
			
		||||
 | 
			
		||||
        if (select) selectOn();
 | 
			
		||||
        else if (highlight) highlightOn();
 | 
			
		||||
        else outlineOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void selectOn() {
 | 
			
		||||
        if (!selectable) return;
 | 
			
		||||
        select = true;
 | 
			
		||||
        outline(selectColor, selectWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void selectOff() {
 | 
			
		||||
        select = false;
 | 
			
		||||
        if (highlight) highlightOn();
 | 
			
		||||
        else outlineOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void selectableOn(){
 | 
			
		||||
        setSelectable(true);
 | 
			
		||||
        setHoverable(true);
 | 
			
		||||
        highlightOn();
 | 
			
		||||
        select = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void selectableOff(){
 | 
			
		||||
        setSelectable(false);
 | 
			
		||||
        setHoverable(false);
 | 
			
		||||
        highlightOff();
 | 
			
		||||
        select = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setSelectable(boolean selectable) {
 | 
			
		||||
        this.selectable = selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isSelected() {
 | 
			
		||||
        return select;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isSelectable() {
 | 
			
		||||
        return selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isHoverable() {
 | 
			
		||||
        return hoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setHoverable(boolean hoverable) {
 | 
			
		||||
        this.hoverable = hoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHighlightColor(ColorRGBA color){
 | 
			
		||||
        highlightColor = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHighlightWidth(int width){
 | 
			
		||||
        highlightWidth = width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoverColor(ColorRGBA color){
 | 
			
		||||
        hoverColor = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoverWidth(int width){
 | 
			
		||||
        hoverWidth = width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSelectColor(ColorRGBA color){
 | 
			
		||||
        selectColor = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSelectWidth(int width){
 | 
			
		||||
        selectWidth = width;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
package pp.mdga.client.board;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OutlineOEControl class extends OutlineControl to manage outline colors and widths
 | 
			
		||||
 * for own and enemy objects, including hover and select states.
 | 
			
		||||
 */
 | 
			
		||||
public class OutlineOEControl extends OutlineControl{
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_HOVER_COLOR = ColorRGBA.Yellow;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_HOVER_COLOR = ColorRGBA.Green;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_SELECT_COLOR = ColorRGBA.Cyan;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_SELECT_COLOR = ColorRGBA.Orange;
 | 
			
		||||
    private static final int OUTLINE_HIGHLIGHT_WIDTH = 8;
 | 
			
		||||
    private static final int OUTLINE_HOVER_WIDTH = 8;
 | 
			
		||||
    private static final int OUTLINE_SELECT_WIDTH = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for OutlineOEControl.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     * @param fpp the FilterPostProcessor instance
 | 
			
		||||
     * @param cam the Camera instance
 | 
			
		||||
     */
 | 
			
		||||
    public OutlineOEControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
 | 
			
		||||
        super(app, fpp, cam,
 | 
			
		||||
          OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH,
 | 
			
		||||
          OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH,
 | 
			
		||||
          OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline colors and enables selection for own objects.
 | 
			
		||||
     */
 | 
			
		||||
    public void selectableOwn(){
 | 
			
		||||
        setHighlightColor(OUTLINE_OWN_COLOR);
 | 
			
		||||
        setHoverColor(OUTLINE_OWN_HOVER_COLOR);
 | 
			
		||||
        setSelectColor(OUTLINE_OWN_SELECT_COLOR);
 | 
			
		||||
        selectableOn();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline colors and enables selection for enemy objects.
 | 
			
		||||
     */
 | 
			
		||||
    public void selectableEnemy(){
 | 
			
		||||
        setHighlightColor(OUTLINE_ENEMY_COLOR);
 | 
			
		||||
        setHoverColor(OUTLINE_ENEMY_HOVER_COLOR);
 | 
			
		||||
        setSelectColor(OUTLINE_ENEMY_SELECT_COLOR);
 | 
			
		||||
        selectableOn();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 * to provide outline functionality and includes additional features like shield effects,
 | 
			
		||||
 * hover states, and selection states.
 | 
			
		||||
 */
 | 
			
		||||
public class PieceControl extends OutlineControl {
 | 
			
		||||
public class PieceControl extends OutlineOEControl {
 | 
			
		||||
    private final float initRotation;
 | 
			
		||||
    private final AssetManager assetManager;
 | 
			
		||||
    private Spatial shieldRing;
 | 
			
		||||
@@ -32,16 +32,6 @@ public class PieceControl extends OutlineControl {
 | 
			
		||||
    private static final ColorRGBA SHIELD_SUPPRESSED_COLOR = new ColorRGBA(1f, 0.5f, 0, SHIELD_TRANSPARENCY);
 | 
			
		||||
    private static final float SHIELD_Z = 0f;
 | 
			
		||||
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_HOVER_COLOR = ColorRGBA.Yellow;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_HOVER_COLOR = ColorRGBA.Green;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_OWN_SELECT_COLOR = ColorRGBA.Cyan;
 | 
			
		||||
    private static final ColorRGBA OUTLINE_ENEMY_SELECT_COLOR = ColorRGBA.Orange;
 | 
			
		||||
    private static final int OUTLINE_HIGHLIGHT_WIDTH = 8;
 | 
			
		||||
    private static final int OUTLINE_HOVER_WIDTH = 8;
 | 
			
		||||
    private static final int OUTLINE_SELECT_WIDTH = 10;
 | 
			
		||||
 | 
			
		||||
    private final Node parentNode;
 | 
			
		||||
    private boolean enemy;
 | 
			
		||||
    private boolean hoverable;
 | 
			
		||||
@@ -55,22 +45,17 @@ public class PieceControl extends OutlineControl {
 | 
			
		||||
     *
 | 
			
		||||
     * @param initRotation The initial rotation of the piece in degrees.
 | 
			
		||||
     * @param assetManager The {@link AssetManager} used for loading models and materials.
 | 
			
		||||
     * @param app The {@link MdgaApp} instance to use for the application context.
 | 
			
		||||
     * @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
 | 
			
		||||
     * @param app          The {@link MdgaApp} instance to use for the application context.
 | 
			
		||||
     * @param fpp          The {@link FilterPostProcessor} to apply post-processing effects.
 | 
			
		||||
     */
 | 
			
		||||
    public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){
 | 
			
		||||
        super(app, fpp);
 | 
			
		||||
    public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp) {
 | 
			
		||||
        super(app, fpp, app.getCamera());
 | 
			
		||||
        this.parentNode = new Node();
 | 
			
		||||
        this.initRotation = initRotation;
 | 
			
		||||
        this.assetManager = assetManager;
 | 
			
		||||
        this.shieldRing = null;
 | 
			
		||||
        this.shieldMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
 | 
			
		||||
        this.shieldMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 | 
			
		||||
        enemy = false;
 | 
			
		||||
        hoverable = false;
 | 
			
		||||
        highlight = false;
 | 
			
		||||
        selectable = false;
 | 
			
		||||
        select = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -79,7 +64,7 @@ public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app,
 | 
			
		||||
     * @return The rotation of the piece in degrees.
 | 
			
		||||
     */
 | 
			
		||||
    public float getRotation() {
 | 
			
		||||
        return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1)));
 | 
			
		||||
        return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0, 0, 1)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -87,11 +72,11 @@ public float getRotation() {
 | 
			
		||||
     *
 | 
			
		||||
     * @param rot The rotation in degrees to set.
 | 
			
		||||
     */
 | 
			
		||||
    public void setRotation(float rot){
 | 
			
		||||
        if(rot < 0) rot =- 360;
 | 
			
		||||
    public void setRotation(float rot) {
 | 
			
		||||
        if (rot < 0) rot = -360;
 | 
			
		||||
 | 
			
		||||
        Quaternion quaternion = new Quaternion();
 | 
			
		||||
        quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0,0,1));
 | 
			
		||||
        quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0, 0, 1));
 | 
			
		||||
        spatial.setLocalRotation(quaternion);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -100,7 +85,7 @@ public void setRotation(float rot){
 | 
			
		||||
     *
 | 
			
		||||
     * @return The location of the piece as a {@link Vector3f}.
 | 
			
		||||
     */
 | 
			
		||||
    public Vector3f getLocation(){
 | 
			
		||||
    public Vector3f getLocation() {
 | 
			
		||||
        return spatial.getLocalTranslation();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -111,8 +96,9 @@ public Vector3f getLocation(){
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float delta) {
 | 
			
		||||
        if(shieldRing != null){
 | 
			
		||||
        if (shieldRing != null) {
 | 
			
		||||
            shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
 | 
			
		||||
            shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -121,7 +107,7 @@ protected void controlUpdate(float delta) {
 | 
			
		||||
     *
 | 
			
		||||
     * @param loc The location to set as a {@link Vector3f}.
 | 
			
		||||
     */
 | 
			
		||||
    public void setLocation(Vector3f loc){
 | 
			
		||||
    public void setLocation(Vector3f loc) {
 | 
			
		||||
        this.spatial.setLocalTranslation(loc);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -130,7 +116,7 @@ public void setLocation(Vector3f loc){
 | 
			
		||||
     * This also moves the spatial to a new parent node for organizational purposes.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initSpatial(){
 | 
			
		||||
    public void initSpatial() {
 | 
			
		||||
        setRotation(this.initRotation);
 | 
			
		||||
 | 
			
		||||
        Node oldParent = spatial.getParent();
 | 
			
		||||
@@ -141,18 +127,21 @@ public void initSpatial(){
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void rotateInit() {
 | 
			
		||||
//        rotate(rotation - initRotation);
 | 
			
		||||
        setRotation(initRotation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Activates the shield around the piece.
 | 
			
		||||
     * This adds a visual shield effect in the form of a rotating ring.
 | 
			
		||||
     */
 | 
			
		||||
    public void activateShield(){
 | 
			
		||||
    public void activateShield() {
 | 
			
		||||
        if (shieldRing != null) {
 | 
			
		||||
            deactivateShield();
 | 
			
		||||
        }
 | 
			
		||||
        shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
 | 
			
		||||
        shieldRing.scale(1f);
 | 
			
		||||
        shieldRing.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(0));
 | 
			
		||||
        shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,0,SHIELD_Z)));
 | 
			
		||||
        shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        shieldRing.setQueueBucket(RenderQueue.Bucket.Transparent); // Render in the transparent bucket
 | 
			
		||||
@@ -166,7 +155,7 @@ public void activateShield(){
 | 
			
		||||
     * Deactivates the shield by removing the shield ring from the scene.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    public void deactivateShield(){
 | 
			
		||||
    public void deactivateShield() {
 | 
			
		||||
        parentNode.detachChild(shieldRing);
 | 
			
		||||
        shieldRing = null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -174,108 +163,16 @@ public void deactivateShield(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Suppresses the shield, changing its color to a suppressed state.
 | 
			
		||||
     */
 | 
			
		||||
    public void suppressShield(){
 | 
			
		||||
        assert(shieldRing != null) : "PieceControl: shieldRing is not set";
 | 
			
		||||
    public void suppressShield() {
 | 
			
		||||
        assert (shieldRing != null) : "PieceControl: shieldRing is not set";
 | 
			
		||||
        shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaterial(Material mat){
 | 
			
		||||
    public void setMaterial(Material mat) {
 | 
			
		||||
        spatial.setMaterial(mat);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Material getMaterial(){
 | 
			
		||||
    public Material getMaterial() {
 | 
			
		||||
        return ((Geometry) getSpatial()).getMaterial();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Highlights the piece with the appropriate outline color based on whether it is an enemy or not.
 | 
			
		||||
     *
 | 
			
		||||
     * @param enemy True if the piece is an enemy, false if it is owned by the player.
 | 
			
		||||
     */
 | 
			
		||||
    public void highlight(boolean enemy) {
 | 
			
		||||
        this.enemy = enemy;
 | 
			
		||||
        highlight = true;
 | 
			
		||||
        super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the highlight effect from the piece.
 | 
			
		||||
     */
 | 
			
		||||
    public void unHighlight(){
 | 
			
		||||
        highlight = false;
 | 
			
		||||
        deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies a hover effect on the piece if it is hoverable.
 | 
			
		||||
     */
 | 
			
		||||
    public void hover(){
 | 
			
		||||
        if(!hoverable) return;
 | 
			
		||||
        super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the hover effect from the piece.
 | 
			
		||||
     */
 | 
			
		||||
    public void hoverOff(){
 | 
			
		||||
        if(!hoverable) return;
 | 
			
		||||
 | 
			
		||||
        if(select) select();
 | 
			
		||||
        else if(highlight) highlight(enemy);
 | 
			
		||||
        else deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deselects the piece and removes the selection outline. If the piece was highlighted,
 | 
			
		||||
     * it will be re-highlighted. Otherwise, the outline is removed.
 | 
			
		||||
     */
 | 
			
		||||
    public void unSelect(){
 | 
			
		||||
        select = false;
 | 
			
		||||
        if(highlight) highlight(enemy);
 | 
			
		||||
        else deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects the piece and applies the selection outline. If the piece is an enemy, it will
 | 
			
		||||
     * be outlined with the enemy selection color; otherwise, the own selection color will be used.
 | 
			
		||||
     */
 | 
			
		||||
    public void select(){
 | 
			
		||||
        if(!selectable) return;
 | 
			
		||||
        select = true;
 | 
			
		||||
        super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets whether the piece is selectable.
 | 
			
		||||
     *
 | 
			
		||||
     * @param selectable True if the piece can be selected, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public void setSelectable(boolean selectable){
 | 
			
		||||
        this.selectable = selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the piece is selected.
 | 
			
		||||
     *
 | 
			
		||||
     * @return True if the piece is selected, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isSelected() { return select; }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the piece is selectable.
 | 
			
		||||
     *
 | 
			
		||||
     * @return True if the piece is selectable, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isSelectable() {
 | 
			
		||||
        return selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets whether the piece is hoverable.
 | 
			
		||||
     *
 | 
			
		||||
     * @param hoverable True if the piece can be hovered over, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public void setHoverable(boolean hoverable) {
 | 
			
		||||
        this.hoverable = hoverable;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,130 @@
 | 
			
		||||
package pp.mdga.client.board;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.FastMath;
 | 
			
		||||
import com.jme3.math.Quaternion;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import pp.mdga.client.InitControl;
 | 
			
		||||
 | 
			
		||||
import static pp.mdga.client.Util.linInt;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the rotation of the tank's top part to face an enemy position.
 | 
			
		||||
 */
 | 
			
		||||
public class TankTopControl extends InitControl {
 | 
			
		||||
 | 
			
		||||
    private float timer = 0; // Time elapsed
 | 
			
		||||
    private final static float DURATION = 1.5f; // Total rotation duration in seconds
 | 
			
		||||
    private boolean rotating = false; // Flag to track if rotation is active
 | 
			
		||||
    private float startAngle = 0;
 | 
			
		||||
    private float endAngle = 0;
 | 
			
		||||
    private Runnable actionAfter = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the control each frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf Time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        if (!rotating) return;
 | 
			
		||||
 | 
			
		||||
        // Update the timer
 | 
			
		||||
        timer += tpf;
 | 
			
		||||
 | 
			
		||||
        // Calculate interpolation factor (0 to 1)
 | 
			
		||||
        float t = timer / DURATION;
 | 
			
		||||
 | 
			
		||||
        if (t >= 1) t = 1;
 | 
			
		||||
 | 
			
		||||
        float curAngle = linInt(startAngle, endAngle, t);
 | 
			
		||||
 | 
			
		||||
        // Interpolate the rotation
 | 
			
		||||
        Quaternion interpolatedRotation = new Quaternion();
 | 
			
		||||
        interpolatedRotation.fromAngleAxis((float) Math.toRadians(curAngle), Vector3f.UNIT_Z);
 | 
			
		||||
 | 
			
		||||
        // Apply the interpolated rotation to the spatial
 | 
			
		||||
        spatial.setLocalRotation(interpolatedRotation);
 | 
			
		||||
 | 
			
		||||
        if (t >= 1) {
 | 
			
		||||
            rotating = false;
 | 
			
		||||
            if (actionAfter != null) actionAfter.run();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiates the rotation of the tank's top part to face the enemy position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param enemyPos The position of the enemy
 | 
			
		||||
     * @param actionAfter The action to execute after the rotation is complete
 | 
			
		||||
     */
 | 
			
		||||
    public void rotate(Vector3f enemyPos, Runnable actionAfter) {
 | 
			
		||||
        if (spatial == null) throw new RuntimeException("spatial is null");
 | 
			
		||||
 | 
			
		||||
        startAngle = getOwnAngle();
 | 
			
		||||
        endAngle = getEnemyAngle(enemyPos);
 | 
			
		||||
 | 
			
		||||
        // Adjust endAngle to ensure the shortest path
 | 
			
		||||
        float deltaAngle = endAngle - startAngle;
 | 
			
		||||
        if (deltaAngle > 180) {
 | 
			
		||||
            endAngle -= 360; // Rotate counterclockwise
 | 
			
		||||
        } else if (deltaAngle < -180) {
 | 
			
		||||
            endAngle += 360; // Rotate clockwise
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        timer = 0;
 | 
			
		||||
        rotating = true;
 | 
			
		||||
        this.actionAfter = actionAfter; // Store the action to execute after rotation
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the angle to the enemy position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param enemyPos The position of the enemy
 | 
			
		||||
     * @return The angle to the enemy in degrees
 | 
			
		||||
     */
 | 
			
		||||
    private float getEnemyAngle(Vector3f enemyPos) {
 | 
			
		||||
        // Direction to the enemy in the XY plane
 | 
			
		||||
        Vector3f direction = enemyPos.subtract(spatial.getLocalTranslation());
 | 
			
		||||
        direction.z = 0; // Project to XY plane
 | 
			
		||||
        direction.normalizeLocal();
 | 
			
		||||
 | 
			
		||||
        Vector3f reference = Vector3f.UNIT_Y.mult(-1);
 | 
			
		||||
 | 
			
		||||
        // Calculate the angle between the direction vector and the reference vector
 | 
			
		||||
        float angle = FastMath.acos(reference.dot(direction));
 | 
			
		||||
 | 
			
		||||
        // Determine rotation direction using the cross product
 | 
			
		||||
        Vector3f cross = reference.cross(direction);
 | 
			
		||||
        if (cross.z < 0) {
 | 
			
		||||
            angle = -angle;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (float) Math.toDegrees(angle); // Return the absolute angle in degrees
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the tank's current angle.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The tank's current angle in degrees
 | 
			
		||||
     */
 | 
			
		||||
    private float getOwnAngle() {
 | 
			
		||||
        // Tank's forward direction in the XY plane
 | 
			
		||||
        Vector3f forward = spatial.getLocalRotation().mult(Vector3f.UNIT_Y);
 | 
			
		||||
        forward.z = 0; // Project to XY plane
 | 
			
		||||
        forward.normalizeLocal();
 | 
			
		||||
 | 
			
		||||
        // Reference vector: Positive X-axis
 | 
			
		||||
        Vector3f reference = Vector3f.UNIT_Y;
 | 
			
		||||
 | 
			
		||||
        // Calculate the angle between the forward vector and the reference vector
 | 
			
		||||
        float angle = FastMath.acos(reference.dot(forward));
 | 
			
		||||
 | 
			
		||||
        // Determine rotation direction using the cross product
 | 
			
		||||
        Vector3f cross = reference.cross(forward);
 | 
			
		||||
        if (cross.z < 0) { // For Z-up, check the Z component of the cross product
 | 
			
		||||
            angle = -angle;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (float) Math.toDegrees(angle); // Return the absolute angle in radians
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -115,7 +115,7 @@ public abstract class AbstractButton {
 | 
			
		||||
     * Constructs an AbstractButton instance with the specified application context and scene node.
 | 
			
		||||
     * Initializes the button's visual elements and font.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance for accessing resources
 | 
			
		||||
     * @param app  the application instance for accessing resources
 | 
			
		||||
     * @param node the node in the scene graph to which the button is attached
 | 
			
		||||
     */
 | 
			
		||||
    public AbstractButton(MdgaApp app, Node node) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import com.jme3.ui.Picture;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a specific implementation of a clickable button positioned on the left side.
 | 
			
		||||
 
 | 
			
		||||
@@ -69,15 +69,16 @@ public enum Pos {
 | 
			
		||||
     * Constructs a CeremonyButton with specified attributes such as type, position, and label.
 | 
			
		||||
     * The button supports both 2D and 3D components for UI and visual effects.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app   the application instance for accessing resources and settings
 | 
			
		||||
     * @param node  the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param app    the application instance for accessing resources and settings
 | 
			
		||||
     * @param node   the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param node3d the node for 3D scene components associated with this button
 | 
			
		||||
     * @param tsk   the type/color associated with the button
 | 
			
		||||
     * @param pos   the position of the button in the ceremony layout
 | 
			
		||||
     * @param name  the label or name displayed on the button
 | 
			
		||||
     * @param tsk    the type/color associated with the button
 | 
			
		||||
     * @param pos    the position of the button in the ceremony layout
 | 
			
		||||
     * @param name   the label or name displayed on the button
 | 
			
		||||
     */
 | 
			
		||||
    public CeremonyButton(MdgaApp app, Node node, Node node3d, Color tsk, Pos pos, String name) {
 | 
			
		||||
        super(app, node, () -> {}, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
 | 
			
		||||
        super(app, node, () -> {
 | 
			
		||||
        }, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
 | 
			
		||||
 | 
			
		||||
        this.node3d = node3d;
 | 
			
		||||
 | 
			
		||||
@@ -217,9 +218,9 @@ public void update(float tpf) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        model.setLocalRotation(new Quaternion().fromAngles(
 | 
			
		||||
            (float) Math.toRadians(90),
 | 
			
		||||
            (float) Math.toRadians(rot),
 | 
			
		||||
            (float) Math.toRadians(180)
 | 
			
		||||
                (float) Math.toRadians(90),
 | 
			
		||||
                (float) Math.toRadians(rot),
 | 
			
		||||
                (float) Math.toRadians(180)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -236,9 +237,9 @@ private void createModel(Asset asset, Vector3f pos) {
 | 
			
		||||
        model = app.getAssetManager().loadModel(modelName);
 | 
			
		||||
        model.scale(asset.getSize() / 2);
 | 
			
		||||
        model.rotate(
 | 
			
		||||
            (float) Math.toRadians(90),
 | 
			
		||||
            (float) Math.toRadians(rot),
 | 
			
		||||
            (float) Math.toRadians(180)
 | 
			
		||||
                (float) Math.toRadians(90),
 | 
			
		||||
                (float) Math.toRadians(rot),
 | 
			
		||||
                (float) Math.toRadians(180)
 | 
			
		||||
        );
 | 
			
		||||
        model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,12 +41,12 @@ public abstract class ClickButton extends AbstractButton {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ClickButton with the specified properties.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app   the application instance for accessing resources
 | 
			
		||||
     * @param node  the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param app    the application instance for accessing resources
 | 
			
		||||
     * @param node   the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param action the action to execute on button click
 | 
			
		||||
     * @param label the text label displayed on the button
 | 
			
		||||
     * @param size  the size of the button
 | 
			
		||||
     * @param pos   the position of the button in relative units
 | 
			
		||||
     * @param label  the text label displayed on the button
 | 
			
		||||
     * @param size   the size of the button
 | 
			
		||||
     * @param pos    the position of the button in relative units
 | 
			
		||||
     */
 | 
			
		||||
    ClickButton(MdgaApp app, Node node, Runnable action, String label, Vector2f size, Vector2f pos) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
@@ -203,9 +203,9 @@ protected void setImageRelative(Picture picture) {
 | 
			
		||||
        picture.setHeight(instance.getPreferredSize().y + larger);
 | 
			
		||||
 | 
			
		||||
        picture.setLocalTranslation(
 | 
			
		||||
            instance.getLocalTranslation().x - larger / 2,
 | 
			
		||||
            (instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
 | 
			
		||||
            instance.getLocalTranslation().z + 0.01f
 | 
			
		||||
                instance.getLocalTranslation().x - larger / 2,
 | 
			
		||||
                (instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
 | 
			
		||||
                instance.getLocalTranslation().z + 0.01f
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@ public class LabelButton extends ClickButton {
 | 
			
		||||
     * @param isButton whether this component acts as a button or a simple label
 | 
			
		||||
     */
 | 
			
		||||
    public LabelButton(MdgaApp app, Node node, String label, Vector2f size, Vector2f pos, boolean isButton) {
 | 
			
		||||
        super(app, node, () -> {}, label, size, pos);
 | 
			
		||||
        super(app, node, () -> {
 | 
			
		||||
        }, label, size, pos);
 | 
			
		||||
 | 
			
		||||
        this.isButton = isButton;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,13 @@
 | 
			
		||||
package pp.mdga.client.button;
 | 
			
		||||
 | 
			
		||||
import com.jme3.light.AmbientLight;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Quaternion;
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Quad;
 | 
			
		||||
import com.jme3.texture.Texture;
 | 
			
		||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
 | 
			
		||||
import pp.mdga.client.Asset;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
@@ -101,11 +97,11 @@ public enum Taken {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a LobbyButton with specified properties, including a 3D model and label.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app     the application instance for accessing resources
 | 
			
		||||
     * @param node    the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param node3d  the node for 3D scene components associated with this button
 | 
			
		||||
     * @param action  the action to execute when the button is clicked
 | 
			
		||||
     * @param tsk     the type or category of the button (e.g., CYBER, AIRFORCE)
 | 
			
		||||
     * @param app    the application instance for accessing resources
 | 
			
		||||
     * @param node   the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param node3d the node for 3D scene components associated with this button
 | 
			
		||||
     * @param action the action to execute when the button is clicked
 | 
			
		||||
     * @param tsk    the type or category of the button (e.g., CYBER, AIRFORCE)
 | 
			
		||||
     */
 | 
			
		||||
    public LobbyButton(MdgaApp app, Node node, Node node3d, Runnable action, Color tsk) {
 | 
			
		||||
        super(app, node, action, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
 | 
			
		||||
@@ -261,9 +257,9 @@ public void update(float tpf) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        model.setLocalRotation(new Quaternion().fromAngles(
 | 
			
		||||
            (float) Math.toRadians(90),
 | 
			
		||||
            (float) Math.toRadians(rot),
 | 
			
		||||
            (float) Math.toRadians(180)
 | 
			
		||||
                (float) Math.toRadians(90),
 | 
			
		||||
                (float) Math.toRadians(rot),
 | 
			
		||||
                (float) Math.toRadians(180)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -280,9 +276,9 @@ private void createModel(Asset asset, Vector3f pos) {
 | 
			
		||||
        model = app.getAssetManager().loadModel(modelName);
 | 
			
		||||
        model.scale(asset.getSize() / 2);
 | 
			
		||||
        model.rotate(
 | 
			
		||||
            (float) Math.toRadians(90),
 | 
			
		||||
            (float) Math.toRadians(rot),
 | 
			
		||||
            (float) Math.toRadians(180)
 | 
			
		||||
                (float) Math.toRadians(90),
 | 
			
		||||
                (float) Math.toRadians(rot),
 | 
			
		||||
                (float) Math.toRadians(180)
 | 
			
		||||
        );
 | 
			
		||||
        model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,10 @@ public class MenuButton extends ClickButton {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a MenuButton with specified properties.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app     the application instance for accessing resources
 | 
			
		||||
     * @param node    the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param action  the action to execute when the button is clicked
 | 
			
		||||
     * @param label   the text label displayed on the button
 | 
			
		||||
     * @param app    the application instance for accessing resources
 | 
			
		||||
     * @param node   the node in the scene graph to which the button belongs
 | 
			
		||||
     * @param action the action to execute when the button is clicked
 | 
			
		||||
     * @param label  the text label displayed on the button
 | 
			
		||||
     */
 | 
			
		||||
    public MenuButton(MdgaApp app, Node node, Runnable action, String label) {
 | 
			
		||||
        super(app, node, action, label, new Vector2f(5.5f, 2), new Vector2f(0, 0));
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,6 @@
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.simsilica.lemur.HAlignment;
 | 
			
		||||
import com.simsilica.lemur.VAlignment;
 | 
			
		||||
import com.simsilica.lemur.component.IconComponent;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,11 @@
 | 
			
		||||
import pp.mdga.client.button.SliderButton;
 | 
			
		||||
import pp.mdga.client.view.MdgaView;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code AudioSettingsDialog} class represents a dialog for adjusting audio settings in the application.
 | 
			
		||||
 * It provides controls for managing main volume, music volume, and sound effect volume, and includes
 | 
			
		||||
 * a button to return to the previous menu.
 | 
			
		||||
 */
 | 
			
		||||
public class AudioSettingsDialog extends Dialog {
 | 
			
		||||
    private final MdgaView view;
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +23,13 @@ public class AudioSettingsDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private boolean active = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an {@code AudioSettingsDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The current view, used for navigation and interaction with the dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
@@ -42,6 +54,9 @@ public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        backButton.setPos(new Vector2f(0, 1.8f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Initializes and displays the volume controls and back button.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        active = true;
 | 
			
		||||
@@ -57,6 +72,9 @@ protected void onShow() {
 | 
			
		||||
        soundVolume.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all volume controls and the back button.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        active = false;
 | 
			
		||||
@@ -68,8 +86,12 @@ protected void onHide() {
 | 
			
		||||
        soundVolume.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the application audio settings based on the current values of the sliders.
 | 
			
		||||
     * This method is called continuously while the dialog is active.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        if(!active) {
 | 
			
		||||
        if (!active) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,17 +10,30 @@
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code CeremonyDialog} class displays a dialog containing statistical data in a tabular format.
 | 
			
		||||
 * It allows adding rows of statistics and manages their visibility when shown or hidden.
 | 
			
		||||
 */
 | 
			
		||||
public class CeremonyDialog extends Dialog {
 | 
			
		||||
    private ArrayList<ArrayList<LabelButton>> labels;
 | 
			
		||||
 | 
			
		||||
    float offsetX;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code CeremonyDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     */
 | 
			
		||||
    public CeremonyDialog(MdgaApp app, Node node) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
        prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Makes all label buttons in the table visible.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        for (ArrayList<LabelButton> row : labels) {
 | 
			
		||||
@@ -30,6 +43,9 @@ protected void onShow() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all label buttons in the table.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        for (ArrayList<LabelButton> row : labels) {
 | 
			
		||||
@@ -39,6 +55,17 @@ protected void onHide() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a row of statistical data to the dialog.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name The name of the player or category for the row.
 | 
			
		||||
     * @param v1   The value for the first column.
 | 
			
		||||
     * @param v2   The value for the second column.
 | 
			
		||||
     * @param v3   The value for the third column.
 | 
			
		||||
     * @param v4   The value for the fourth column.
 | 
			
		||||
     * @param v5   The value for the fifth column.
 | 
			
		||||
     * @param v6   The value for the sixth column.
 | 
			
		||||
     */
 | 
			
		||||
    public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
 | 
			
		||||
        float offsetYSmall = 0.5f;
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +88,7 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
 | 
			
		||||
 | 
			
		||||
        int j = 0;
 | 
			
		||||
        for (LabelButton b : row) {
 | 
			
		||||
            if(j > 0) {
 | 
			
		||||
            if (j > 0) {
 | 
			
		||||
                b.setColor(colorText, colorButton);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -76,6 +103,9 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
 | 
			
		||||
        labels.add(row);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prepares the initial layout of the dialog, including header labels.
 | 
			
		||||
     */
 | 
			
		||||
    public void prepare() {
 | 
			
		||||
        offsetX = 0.5f;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,55 @@
 | 
			
		||||
package pp.mdga.client.dialog;
 | 
			
		||||
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code Dialog} class serves as an abstract base class for dialogs in the application.
 | 
			
		||||
 * It provides functionality for showing and hiding the dialog and defines abstract methods
 | 
			
		||||
 * for custom behavior when the dialog is shown or hidden.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class Dialog {
 | 
			
		||||
    protected final MdgaApp app;
 | 
			
		||||
    protected final Node node = new Node();
 | 
			
		||||
 | 
			
		||||
    private final Node root;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code Dialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node to which the dialog's node will be attached.
 | 
			
		||||
     */
 | 
			
		||||
    Dialog(MdgaApp app, Node node) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.root = node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the dialog by attaching its node to the root node and invoking the {@code onShow} method.
 | 
			
		||||
     */
 | 
			
		||||
    public void show() {
 | 
			
		||||
        root.attachChild(node);
 | 
			
		||||
 | 
			
		||||
        onShow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the dialog by detaching its node from the root node and invoking the {@code onHide} method.
 | 
			
		||||
     */
 | 
			
		||||
    public void hide() {
 | 
			
		||||
        root.detachChild(node);
 | 
			
		||||
 | 
			
		||||
        onHide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Subclasses must implement this method to define custom behavior.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onShow();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Subclasses must implement this method to define custom behavior.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onHide();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,11 @@
 | 
			
		||||
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
public class HostDialog  extends NetworkDialog {
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code HostDialog} class represents a dialog for hosting a network game session.
 | 
			
		||||
 * It allows users to input a port number, start hosting a server, and navigate back to the previous view.
 | 
			
		||||
 */
 | 
			
		||||
public class HostDialog extends NetworkDialog {
 | 
			
		||||
    private InputButton portInput;
 | 
			
		||||
 | 
			
		||||
    private ButtonRight hostButton;
 | 
			
		||||
@@ -22,6 +26,13 @@ public class HostDialog  extends NetworkDialog {
 | 
			
		||||
 | 
			
		||||
    private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code HostDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The main view used for navigation and interaction with the dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public HostDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        super(app, node, (NetworkSupport) app.getNetworkSupport());
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +50,9 @@ public HostDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        offset += 1.5f;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays all input fields and buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        portInput.show();
 | 
			
		||||
@@ -46,6 +60,9 @@ protected void onShow() {
 | 
			
		||||
        backButton.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all input fields and buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        portInput.hide();
 | 
			
		||||
@@ -53,27 +70,44 @@ protected void onHide() {
 | 
			
		||||
        backButton.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the port input field.
 | 
			
		||||
     * This method is called periodically to synchronize the dialog state.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        portInput.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The port number as a string.
 | 
			
		||||
     */
 | 
			
		||||
    public String getPort() {
 | 
			
		||||
        prefs.put("hostPort", portInput.getString());
 | 
			
		||||
        setPortNumber(Integer.parseInt(portInput.getString()));
 | 
			
		||||
        return portInput.getString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets the port input field to its default value and updates preferences accordingly.
 | 
			
		||||
     */
 | 
			
		||||
    public void resetPort() {
 | 
			
		||||
        portInput.reset();
 | 
			
		||||
        prefs.put("hostPort", "11111");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the server to host a network game.
 | 
			
		||||
     */
 | 
			
		||||
    public void hostServer() {
 | 
			
		||||
        startServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects to the server as a client.
 | 
			
		||||
     */
 | 
			
		||||
    public void connectServerAsClient() {
 | 
			
		||||
        connectServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,15 @@
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.button.AbstractButton;
 | 
			
		||||
import pp.mdga.client.button.ButtonRight;
 | 
			
		||||
import pp.mdga.client.button.LabelButton;
 | 
			
		||||
import pp.mdga.client.button.MenuButton;
 | 
			
		||||
import pp.mdga.client.view.MdgaView;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code InterruptDialog} class represents a dialog that interrupts the game flow,
 | 
			
		||||
 * providing a message and the option to force an action if the user is a host.
 | 
			
		||||
 */
 | 
			
		||||
public class InterruptDialog extends Dialog {
 | 
			
		||||
    private ButtonRight forceButton;
 | 
			
		||||
 | 
			
		||||
@@ -17,33 +19,50 @@ public class InterruptDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private String text = "";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an {@code InterruptDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     */
 | 
			
		||||
    public InterruptDialog(MdgaApp app, Node node) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
        forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays the label and optionally the force button if the user is the host.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        if (app.getGameLogic().isHost()) {
 | 
			
		||||
            forceButton.show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
 | 
			
		||||
 | 
			
		||||
        float offset = 2.8f;
 | 
			
		||||
 | 
			
		||||
        label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        if(app.getGameLogic().isHost()) {
 | 
			
		||||
            forceButton.show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        label.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides the label and the force button.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        forceButton.hide();
 | 
			
		||||
        label.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the displayed text based on the specified color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color used to determine the text (e.g., "Luftwaffe" for AIRFORCE).
 | 
			
		||||
     */
 | 
			
		||||
    public void setColor(Color color) {
 | 
			
		||||
        switch (color) {
 | 
			
		||||
            case AIRFORCE:
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.NetworkSupport;
 | 
			
		||||
import pp.mdga.client.acoustic.AcousticHandler;
 | 
			
		||||
import pp.mdga.client.button.ButtonLeft;
 | 
			
		||||
import pp.mdga.client.button.ButtonRight;
 | 
			
		||||
import pp.mdga.client.button.InputButton;
 | 
			
		||||
@@ -13,6 +12,10 @@
 | 
			
		||||
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code JoinDialog} class represents a dialog for joining a network game.
 | 
			
		||||
 * It allows users to input an IP address and port number, connect to a server, or navigate back to the previous view.
 | 
			
		||||
 */
 | 
			
		||||
public class JoinDialog extends NetworkDialog {
 | 
			
		||||
    private InputButton ipInput;
 | 
			
		||||
    private InputButton portInput;
 | 
			
		||||
@@ -24,6 +27,13 @@ public class JoinDialog extends NetworkDialog {
 | 
			
		||||
 | 
			
		||||
    private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code JoinDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The main view used for navigation and interaction with the dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public JoinDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        super(app, node, (NetworkSupport) app.getNetworkSupport());
 | 
			
		||||
 | 
			
		||||
@@ -46,6 +56,9 @@ public JoinDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        offset += 1.5f;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays all input fields and buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        ipInput.show();
 | 
			
		||||
@@ -54,6 +67,9 @@ protected void onShow() {
 | 
			
		||||
        backButton.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all input fields and buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        ipInput.hide();
 | 
			
		||||
@@ -62,37 +78,62 @@ protected void onHide() {
 | 
			
		||||
        backButton.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the input fields. This method is called periodically to synchronize the dialog state.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        ipInput.update();
 | 
			
		||||
        portInput.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the currently entered IP address, saves it to preferences, and sets it as the hostname.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The IP address as a string.
 | 
			
		||||
     */
 | 
			
		||||
    public String getIpt() {
 | 
			
		||||
        prefs.put("joinIp", ipInput.getString());
 | 
			
		||||
        setHostname(ipInput.getString());
 | 
			
		||||
        return ipInput.getString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets the IP input field to its default value and updates preferences accordingly.
 | 
			
		||||
     */
 | 
			
		||||
    public void resetIp() {
 | 
			
		||||
        ipInput.reset();
 | 
			
		||||
        prefs.put("joinIp", "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The port number as a string.
 | 
			
		||||
     */
 | 
			
		||||
    public String getPort() {
 | 
			
		||||
        prefs.put("joinPort", portInput.getString());
 | 
			
		||||
        setPortNumber(Integer.parseInt(portInput.getString()));
 | 
			
		||||
        return portInput.getString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets the port input field to its default value and updates preferences accordingly.
 | 
			
		||||
     */
 | 
			
		||||
    public void resetPort() {
 | 
			
		||||
        portInput.reset();
 | 
			
		||||
        prefs.put("joinPort", "11111");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects to the server using the current IP address and port number.
 | 
			
		||||
     */
 | 
			
		||||
    public void connectToServer() {
 | 
			
		||||
        connectServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects from the server if a network connection exists.
 | 
			
		||||
     */
 | 
			
		||||
    public void disconnect() {
 | 
			
		||||
        NetworkSupport network = getNetwork();
 | 
			
		||||
        if (network != null) {
 | 
			
		||||
@@ -104,4 +145,3 @@ public void disconnect() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,11 @@
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code NetworkDialog} class serves as an abstract base class for dialogs
 | 
			
		||||
 * that involve network-related functionalities, such as connecting to a server or hosting a game.
 | 
			
		||||
 * It provides methods for initializing, connecting to, and managing a network server.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class NetworkDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private NetworkSupport network;
 | 
			
		||||
@@ -17,19 +22,41 @@ public abstract class NetworkDialog extends Dialog {
 | 
			
		||||
    private MdgaServer serverInstance;
 | 
			
		||||
    private Thread serverThread;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code NetworkDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app     The main application managing the dialog.
 | 
			
		||||
     * @param node    The root node for attaching UI elements.
 | 
			
		||||
     * @param network The network support instance for managing network interactions.
 | 
			
		||||
     */
 | 
			
		||||
    public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
        this.network = network;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the hostname for the network connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param hostname The hostname or IP address of the server.
 | 
			
		||||
     */
 | 
			
		||||
    public void setHostname(String hostname) {
 | 
			
		||||
        this.hostname = hostname;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the port number for the network connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param portNumber The port number to use for the connection.
 | 
			
		||||
     */
 | 
			
		||||
    public void setPortNumber(int portNumber) {
 | 
			
		||||
        this.portNumber = portNumber;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the network connection using the current hostname and port number.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code null} if successful, otherwise throws an exception.
 | 
			
		||||
     */
 | 
			
		||||
    protected Object initNetwork() {
 | 
			
		||||
        try {
 | 
			
		||||
            this.network.initNetwork(this.hostname, this.portNumber);
 | 
			
		||||
@@ -39,6 +66,9 @@ protected Object initNetwork() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the process of connecting to a server asynchronously.
 | 
			
		||||
     */
 | 
			
		||||
    protected void connectServer() {
 | 
			
		||||
        try {
 | 
			
		||||
            connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
 | 
			
		||||
@@ -47,6 +77,9 @@ protected void connectServer() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts hosting a server in a separate thread.
 | 
			
		||||
     */
 | 
			
		||||
    protected void startServer() {
 | 
			
		||||
        serverThread = new Thread(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
@@ -60,31 +93,28 @@ protected void startServer() {
 | 
			
		||||
        serverThread.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shuts down the hosted server and cleans up resources.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdownServer() {
 | 
			
		||||
 | 
			
		||||
        serverInstance.shutdown();
 | 
			
		||||
 | 
			
		||||
        // Wait for the server to shut down
 | 
			
		||||
        try {
 | 
			
		||||
            Thread.sleep(1000);
 | 
			
		||||
            serverThread.join(); // Wait for the server thread to finish
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            Thread.currentThread().interrupt();
 | 
			
		||||
            System.err.println("Thread was interrupted: " + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (serverInstance != null) {
 | 
			
		||||
            serverInstance.shutdown();
 | 
			
		||||
            serverInstance = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (serverThread != null && serverThread.isAlive()) {
 | 
			
		||||
            serverThread.interrupt();
 | 
			
		||||
            try {
 | 
			
		||||
                serverThread.join();
 | 
			
		||||
            } catch (InterruptedException e) {
 | 
			
		||||
                Thread.currentThread().interrupt();
 | 
			
		||||
            }
 | 
			
		||||
            serverThread = null;
 | 
			
		||||
        }
 | 
			
		||||
        System.out.println("Server shutdown successfully.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the connection process.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta The time elapsed since the last update call.
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        if (this.connectionFuture != null && this.connectionFuture.isDone()) {
 | 
			
		||||
            try {
 | 
			
		||||
@@ -97,6 +127,11 @@ public void update(float delta) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the {@code NetworkSupport} instance associated with this dialog.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@code NetworkSupport} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public NetworkSupport getNetwork() {
 | 
			
		||||
        return network;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,13 @@
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.button.InputButton;
 | 
			
		||||
import pp.mdga.client.button.MenuButton;
 | 
			
		||||
import pp.mdga.client.view.MainView;
 | 
			
		||||
import pp.mdga.client.view.MdgaView;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code SettingsDialog} class represents a dialog for navigating to various settings sections,
 | 
			
		||||
 * such as video and audio settings, or returning to the previous view.
 | 
			
		||||
 */
 | 
			
		||||
public class SettingsDialog extends Dialog {
 | 
			
		||||
    private MenuButton videoButton;
 | 
			
		||||
    private MenuButton audioButton;
 | 
			
		||||
@@ -15,6 +17,13 @@ public class SettingsDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private final MdgaView view;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code SettingsDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The view managing navigation and interaction with the settings dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +43,9 @@ public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        backButton.setPos(new Vector2f(0, 1.8f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays all buttons for video settings, audio settings, and back navigation.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        videoButton.show();
 | 
			
		||||
@@ -41,6 +53,9 @@ protected void onShow() {
 | 
			
		||||
        backButton.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all buttons for video settings, audio settings, and back navigation.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        videoButton.hide();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,18 @@
 | 
			
		||||
package pp.mdga.client.dialog;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.button.InputButton;
 | 
			
		||||
import pp.mdga.client.button.MenuButton;
 | 
			
		||||
import pp.mdga.client.view.MainView;
 | 
			
		||||
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.random.RandomGenerator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code StartDialog} class represents the initial dialog in the application,
 | 
			
		||||
 * allowing the user to input their name, host or join a game, or exit the application.
 | 
			
		||||
 */
 | 
			
		||||
public class StartDialog extends Dialog {
 | 
			
		||||
    private InputButton nameInput;
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +22,13 @@ public class StartDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private final MainView view;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code StartDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The main view used for navigation and interaction with the dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public StartDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
@@ -48,6 +54,9 @@ public StartDialog(MdgaApp app, Node node, MainView view) {
 | 
			
		||||
        endButton.setPos(new Vector2f(0, 1.8f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays the name input field and all buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        nameInput.show();
 | 
			
		||||
@@ -57,9 +66,11 @@ protected void onShow() {
 | 
			
		||||
        endButton.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides the name input field and all buttons.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide ()
 | 
			
		||||
    {
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        nameInput.hide();
 | 
			
		||||
 | 
			
		||||
        hostButton.hide();
 | 
			
		||||
@@ -67,219 +78,227 @@ protected void onHide ()
 | 
			
		||||
        endButton.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the name input field. This method is called periodically to synchronize the dialog state.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        nameInput.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the name entered by the user. If no name is provided, a random name is generated.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The user's name or a randomly generated name.
 | 
			
		||||
     */
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        String name = nameInput.getString();
 | 
			
		||||
 | 
			
		||||
        if (name == null || name.trim().isEmpty()) {
 | 
			
		||||
            String[] names = {
 | 
			
		||||
                "PixelPirat",
 | 
			
		||||
                "NoobJäger",
 | 
			
		||||
                "LagMeister",
 | 
			
		||||
                "KnopfDrücker",
 | 
			
		||||
                "SpawnCamper",
 | 
			
		||||
                "AFKHeld",
 | 
			
		||||
                "RageQuitter",
 | 
			
		||||
                "GameOverPro",
 | 
			
		||||
                "Checkpoint",
 | 
			
		||||
                "RespawnHeld",
 | 
			
		||||
                "Teebeutel",
 | 
			
		||||
                "GlitchHexer",
 | 
			
		||||
                "QuickScope",
 | 
			
		||||
                "LootSammler",
 | 
			
		||||
                "EpicLauch",
 | 
			
		||||
                "KartoffelPro",
 | 
			
		||||
                "StilleKlinge",
 | 
			
		||||
                "TastenHeld",
 | 
			
		||||
                "PixelKrieger",
 | 
			
		||||
                "HacknSlash",
 | 
			
		||||
                "JoystickJoe",
 | 
			
		||||
                "SpawnFalle",
 | 
			
		||||
                "OneHitWanda",
 | 
			
		||||
                "CamperKing",
 | 
			
		||||
                "GameGenie",
 | 
			
		||||
                "HighPing",
 | 
			
		||||
                "CheesePro",
 | 
			
		||||
                "Speedy",
 | 
			
		||||
                "GigaGamer",
 | 
			
		||||
                "LevelNoob",
 | 
			
		||||
                "SkillTobi",
 | 
			
		||||
                "HeadshotMax",
 | 
			
		||||
                "PentaPaul",
 | 
			
		||||
                "CritKarl",
 | 
			
		||||
                "ManaLeerer",
 | 
			
		||||
                "Nachlader",
 | 
			
		||||
                "ClutchKönig",
 | 
			
		||||
                "FriendlyFe",
 | 
			
		||||
                "ZonenHeld",
 | 
			
		||||
                "SchleichKatze",
 | 
			
		||||
                "ShotgunPro",
 | 
			
		||||
                "SniperUdo",
 | 
			
		||||
                "BossHunter",
 | 
			
		||||
                "HeldenNoob",
 | 
			
		||||
                "KillFranz",
 | 
			
		||||
                "FragKarl",
 | 
			
		||||
                "TeamNiete",
 | 
			
		||||
                "LootPaul",
 | 
			
		||||
                "UltraNoob",
 | 
			
		||||
                "ProfiScout",
 | 
			
		||||
                "PunkteKlaus",
 | 
			
		||||
                "KrüppelKill",
 | 
			
		||||
                "PixelNinja",
 | 
			
		||||
                "NoobCrusher",
 | 
			
		||||
                "LagBoss",
 | 
			
		||||
                "SpawnKing",
 | 
			
		||||
                "AFKSlayer",
 | 
			
		||||
                "RespawnPro",
 | 
			
		||||
                "Killjoy",
 | 
			
		||||
                "GameBreaker",
 | 
			
		||||
                "FastFingers",
 | 
			
		||||
                "LootKing",
 | 
			
		||||
                "QuickFlick",
 | 
			
		||||
                "SilentShot",
 | 
			
		||||
                "HackGod",
 | 
			
		||||
                "GlitchHero",
 | 
			
		||||
                "SpeedyBot",
 | 
			
		||||
                "AimWizard",
 | 
			
		||||
                "FragMaster",
 | 
			
		||||
                "OneTapPro",
 | 
			
		||||
                "KnifeLord",
 | 
			
		||||
                "MetaHunter",
 | 
			
		||||
                "PingWarrior",
 | 
			
		||||
                "KeyBash",
 | 
			
		||||
                "ClutchPro",
 | 
			
		||||
                "ScopeBot",
 | 
			
		||||
                "TrollMage",
 | 
			
		||||
                "PowerLooter",
 | 
			
		||||
                "TankHero",
 | 
			
		||||
                "CampLord",
 | 
			
		||||
                "SmurfSlayer",
 | 
			
		||||
                "SkillThief",
 | 
			
		||||
                "SniperGod",
 | 
			
		||||
                "LevelHack",
 | 
			
		||||
                "GhostAim",
 | 
			
		||||
                "BossTamer",
 | 
			
		||||
                "ShotgunJoe",
 | 
			
		||||
                "AimRider",
 | 
			
		||||
                "KillCount",
 | 
			
		||||
                "PixelManiac",
 | 
			
		||||
                "TrollOver",
 | 
			
		||||
                "SneakPro",
 | 
			
		||||
                "ReloadKing",
 | 
			
		||||
                "SpawnTrap",
 | 
			
		||||
                "LagLover",
 | 
			
		||||
                "MetaHater",
 | 
			
		||||
                "BoomMaker",
 | 
			
		||||
                "WipeLord",
 | 
			
		||||
                "CarryPro",
 | 
			
		||||
                "ProBaiter",
 | 
			
		||||
                "GameWarden",
 | 
			
		||||
                "KartoffelKönig",
 | 
			
		||||
                "SaufenderWolf",
 | 
			
		||||
                "WurstGriller",
 | 
			
		||||
                "Flitzekacke",
 | 
			
		||||
                "BratwurstBub",
 | 
			
		||||
                "Hoppeldoppels",
 | 
			
		||||
                "BananenMensch",
 | 
			
		||||
                "KlopapierGuru",
 | 
			
		||||
                "SchnitzelKing",
 | 
			
		||||
                "NerdNomade",
 | 
			
		||||
                "Dönertänzer",
 | 
			
		||||
                "GlitzerGurke",
 | 
			
		||||
                "SchinkenShrek",
 | 
			
		||||
                "KäseKalle",
 | 
			
		||||
                "SchokoSchnecke",
 | 
			
		||||
                "KeksKämpfer",
 | 
			
		||||
                "QuarkPiraten",
 | 
			
		||||
                "Müslimonster",
 | 
			
		||||
                "KnuddelNase",
 | 
			
		||||
                "FantaFighter",
 | 
			
		||||
                "SchnapsSaurier",
 | 
			
		||||
                "Wackelpudding",
 | 
			
		||||
                "ZitronenZock",
 | 
			
		||||
                "FettWurst",
 | 
			
		||||
                "PlüschPanda",
 | 
			
		||||
                "Zuckerschnur",
 | 
			
		||||
                "FluffiKopf",
 | 
			
		||||
                "DonutDöner",
 | 
			
		||||
                "VollpfostenX",
 | 
			
		||||
                "Schraubenschlüssel",
 | 
			
		||||
                "Witzepumper",
 | 
			
		||||
                "ToastTraum",
 | 
			
		||||
                "FroschFighter",
 | 
			
		||||
                "KrümelTiger",
 | 
			
		||||
                "RegenWolke",
 | 
			
		||||
                "PuddingPower",
 | 
			
		||||
                "KoffeinKrieger",
 | 
			
		||||
                "SpeckSchlumpf",
 | 
			
		||||
                "SuperSuppe",
 | 
			
		||||
                "BierBärchen",
 | 
			
		||||
                "FischBär",
 | 
			
		||||
                "Flauschi",
 | 
			
		||||
                "Schokomonster",
 | 
			
		||||
                "ChaosKäse",
 | 
			
		||||
                "FlitzLappen",
 | 
			
		||||
                "WurstWombat",
 | 
			
		||||
                "KrümelMensch",
 | 
			
		||||
                "PuddingBär",
 | 
			
		||||
                "ZickZack",
 | 
			
		||||
                "Schwabel",
 | 
			
		||||
                "Fluffi",
 | 
			
		||||
                "RülpsFrosch",
 | 
			
		||||
                "PommesPapa",
 | 
			
		||||
                "QuarkBär",
 | 
			
		||||
                "KnusperKönig",
 | 
			
		||||
                "ToastBrot",
 | 
			
		||||
                "Ploppster",
 | 
			
		||||
                "Schleimschwein",
 | 
			
		||||
                "Äpfelchen",
 | 
			
		||||
                "Knallbonbon",
 | 
			
		||||
                "KaffeeKopf",
 | 
			
		||||
                "WackelWurst",
 | 
			
		||||
                "RennKeks",
 | 
			
		||||
                "BröselBub",
 | 
			
		||||
                "ZockerBrot",
 | 
			
		||||
                "BierWurm",
 | 
			
		||||
                "StinkFlummi",
 | 
			
		||||
                "SchlumpfKing",
 | 
			
		||||
                "PurzelBär",
 | 
			
		||||
                "FlinkFluff",
 | 
			
		||||
                "PloppPudel",
 | 
			
		||||
                "Schnorchel",
 | 
			
		||||
                "FliegenKopf",
 | 
			
		||||
                "PixelPommes",
 | 
			
		||||
                "SchwipsWürst",
 | 
			
		||||
                "WutzBär",
 | 
			
		||||
                "KnuddelKeks",
 | 
			
		||||
                "FantaFlumm",
 | 
			
		||||
                "ZockerKäse",
 | 
			
		||||
                "LachHäufchen",
 | 
			
		||||
                "GurkenGuru",
 | 
			
		||||
                "PonySchnitzel",
 | 
			
		||||
                "NudelNinja",
 | 
			
		||||
                "VulkanKeks",
 | 
			
		||||
                "WasserToast",
 | 
			
		||||
                "MenschSalat",
 | 
			
		||||
                "KampfKohlenhydrate",
 | 
			
		||||
                "SockenZirkus",
 | 
			
		||||
                "SchwimmBärchen",
 | 
			
		||||
                "TanzenderDachgepäckträger",
 | 
			
		||||
                "PizzamarktMensch",
 | 
			
		||||
                "ZahnarztZocker",
 | 
			
		||||
                "RollerCoasterTester",
 | 
			
		||||
                "WaschmaschinenPilot",
 | 
			
		||||
                "WitzigeZwiebel",
 | 
			
		||||
                "Pillenschlucker",
 | 
			
		||||
                "ZwiebelReiter",
 | 
			
		||||
                "HüpfenderKaktus",
 | 
			
		||||
                "KochenderAsteroid",
 | 
			
		||||
                "ChaosKarotte",
 | 
			
		||||
                "WolkenFurz",
 | 
			
		||||
                "SchnitzelPartikel",
 | 
			
		||||
                "WackelBiene",
 | 
			
		||||
                    "PixelPirat",
 | 
			
		||||
                    "NoobJäger",
 | 
			
		||||
                    "LagMeister",
 | 
			
		||||
                    "KnopfDrücker",
 | 
			
		||||
                    "SpawnCamper",
 | 
			
		||||
                    "AFKHeld",
 | 
			
		||||
                    "RageQuitter",
 | 
			
		||||
                    "GameOverPro",
 | 
			
		||||
                    "Checkpoint",
 | 
			
		||||
                    "RespawnHeld",
 | 
			
		||||
                    "Teebeutel",
 | 
			
		||||
                    "GlitchHexer",
 | 
			
		||||
                    "QuickScope",
 | 
			
		||||
                    "LootSammler",
 | 
			
		||||
                    "EpicLauch",
 | 
			
		||||
                    "KartoffelPro",
 | 
			
		||||
                    "StilleKlinge",
 | 
			
		||||
                    "TastenHeld",
 | 
			
		||||
                    "PixelKrieger",
 | 
			
		||||
                    "HacknSlash",
 | 
			
		||||
                    "JoystickJoe",
 | 
			
		||||
                    "SpawnFalle",
 | 
			
		||||
                    "OneHitWanda",
 | 
			
		||||
                    "CamperKing",
 | 
			
		||||
                    "GameGenie",
 | 
			
		||||
                    "HighPing",
 | 
			
		||||
                    "CheesePro",
 | 
			
		||||
                    "Speedy",
 | 
			
		||||
                    "GigaGamer",
 | 
			
		||||
                    "LevelNoob",
 | 
			
		||||
                    "SkillTobi",
 | 
			
		||||
                    "HeadshotMax",
 | 
			
		||||
                    "PentaPaul",
 | 
			
		||||
                    "CritKarl",
 | 
			
		||||
                    "ManaLeerer",
 | 
			
		||||
                    "Nachlader",
 | 
			
		||||
                    "ClutchKönig",
 | 
			
		||||
                    "FriendlyFe",
 | 
			
		||||
                    "ZonenHeld",
 | 
			
		||||
                    "SchleichKatze",
 | 
			
		||||
                    "ShotgunPro",
 | 
			
		||||
                    "SniperUdo",
 | 
			
		||||
                    "BossHunter",
 | 
			
		||||
                    "HeldenNoob",
 | 
			
		||||
                    "KillFranz",
 | 
			
		||||
                    "FragKarl",
 | 
			
		||||
                    "TeamNiete",
 | 
			
		||||
                    "LootPaul",
 | 
			
		||||
                    "UltraNoob",
 | 
			
		||||
                    "ProfiScout",
 | 
			
		||||
                    "PunkteKlaus",
 | 
			
		||||
                    "KrüppelKill",
 | 
			
		||||
                    "PixelNinja",
 | 
			
		||||
                    "NoobCrusher",
 | 
			
		||||
                    "LagBoss",
 | 
			
		||||
                    "SpawnKing",
 | 
			
		||||
                    "AFKSlayer",
 | 
			
		||||
                    "RespawnPro",
 | 
			
		||||
                    "Killjoy",
 | 
			
		||||
                    "GameBreaker",
 | 
			
		||||
                    "FastFingers",
 | 
			
		||||
                    "LootKing",
 | 
			
		||||
                    "QuickFlick",
 | 
			
		||||
                    "SilentShot",
 | 
			
		||||
                    "HackGod",
 | 
			
		||||
                    "GlitchHero",
 | 
			
		||||
                    "SpeedyBot",
 | 
			
		||||
                    "AimWizard",
 | 
			
		||||
                    "FragMaster",
 | 
			
		||||
                    "OneTapPro",
 | 
			
		||||
                    "KnifeLord",
 | 
			
		||||
                    "MetaHunter",
 | 
			
		||||
                    "PingWarrior",
 | 
			
		||||
                    "KeyBash",
 | 
			
		||||
                    "ClutchPro",
 | 
			
		||||
                    "ScopeBot",
 | 
			
		||||
                    "TrollMage",
 | 
			
		||||
                    "PowerLooter",
 | 
			
		||||
                    "TankHero",
 | 
			
		||||
                    "CampLord",
 | 
			
		||||
                    "SmurfSlayer",
 | 
			
		||||
                    "SkillThief",
 | 
			
		||||
                    "SniperGod",
 | 
			
		||||
                    "LevelHack",
 | 
			
		||||
                    "GhostAim",
 | 
			
		||||
                    "BossTamer",
 | 
			
		||||
                    "ShotgunJoe",
 | 
			
		||||
                    "AimRider",
 | 
			
		||||
                    "KillCount",
 | 
			
		||||
                    "PixelManiac",
 | 
			
		||||
                    "TrollOver",
 | 
			
		||||
                    "SneakPro",
 | 
			
		||||
                    "ReloadKing",
 | 
			
		||||
                    "SpawnTrap",
 | 
			
		||||
                    "LagLover",
 | 
			
		||||
                    "MetaHater",
 | 
			
		||||
                    "BoomMaker",
 | 
			
		||||
                    "WipeLord",
 | 
			
		||||
                    "CarryPro",
 | 
			
		||||
                    "ProBaiter",
 | 
			
		||||
                    "GameWarden",
 | 
			
		||||
                    "KartoffelKönig",
 | 
			
		||||
                    "SaufenderWolf",
 | 
			
		||||
                    "WurstGriller",
 | 
			
		||||
                    "Flitzekacke",
 | 
			
		||||
                    "BratwurstBub",
 | 
			
		||||
                    "Hoppeldoppels",
 | 
			
		||||
                    "BananenMensch",
 | 
			
		||||
                    "KlopapierGuru",
 | 
			
		||||
                    "SchnitzelKing",
 | 
			
		||||
                    "NerdNomade",
 | 
			
		||||
                    "Dönertänzer",
 | 
			
		||||
                    "GlitzerGurke",
 | 
			
		||||
                    "SchinkenShrek",
 | 
			
		||||
                    "KäseKalle",
 | 
			
		||||
                    "SchokoSchnecke",
 | 
			
		||||
                    "KeksKämpfer",
 | 
			
		||||
                    "QuarkPiraten",
 | 
			
		||||
                    "Müslimonster",
 | 
			
		||||
                    "KnuddelNase",
 | 
			
		||||
                    "FantaFighter",
 | 
			
		||||
                    "SchnapsSaurier",
 | 
			
		||||
                    "Wackelpudding",
 | 
			
		||||
                    "ZitronenZock",
 | 
			
		||||
                    "FettWurst",
 | 
			
		||||
                    "PlüschPanda",
 | 
			
		||||
                    "Zuckerschnur",
 | 
			
		||||
                    "FluffiKopf",
 | 
			
		||||
                    "DonutDöner",
 | 
			
		||||
                    "VollpfostenX",
 | 
			
		||||
                    "Waschlappen",
 | 
			
		||||
                    "Witzepumper",
 | 
			
		||||
                    "ToastTraum",
 | 
			
		||||
                    "FroschFighter",
 | 
			
		||||
                    "KrümelTiger",
 | 
			
		||||
                    "RegenWolke",
 | 
			
		||||
                    "PuddingPower",
 | 
			
		||||
                    "KoffeinKrieger",
 | 
			
		||||
                    "SpeckSchlumpf",
 | 
			
		||||
                    "SuperSuppe",
 | 
			
		||||
                    "BierBärchen",
 | 
			
		||||
                    "FischBär",
 | 
			
		||||
                    "Flauschi",
 | 
			
		||||
                    "Schokomonster",
 | 
			
		||||
                    "ChaosKäse",
 | 
			
		||||
                    "FlitzLappen",
 | 
			
		||||
                    "WurstWombat",
 | 
			
		||||
                    "KrümelMensch",
 | 
			
		||||
                    "PuddingBär",
 | 
			
		||||
                    "ZickZack",
 | 
			
		||||
                    "Schwabel",
 | 
			
		||||
                    "Fluffi",
 | 
			
		||||
                    "RülpsFrosch",
 | 
			
		||||
                    "PommesPapa",
 | 
			
		||||
                    "QuarkBär",
 | 
			
		||||
                    "KnusperKönig",
 | 
			
		||||
                    "ToastBrot",
 | 
			
		||||
                    "Ploppster",
 | 
			
		||||
                    "Schleimschwein",
 | 
			
		||||
                    "Äpfelchen",
 | 
			
		||||
                    "Knallbonbon",
 | 
			
		||||
                    "KaffeeKopf",
 | 
			
		||||
                    "WackelWurst",
 | 
			
		||||
                    "RennKeks",
 | 
			
		||||
                    "BröselBub",
 | 
			
		||||
                    "ZockerBrot",
 | 
			
		||||
                    "BierWurm",
 | 
			
		||||
                    "StinkFlummi",
 | 
			
		||||
                    "SchlumpfKing",
 | 
			
		||||
                    "PurzelBär",
 | 
			
		||||
                    "FlinkFluff",
 | 
			
		||||
                    "PloppPudel",
 | 
			
		||||
                    "Schnorchel",
 | 
			
		||||
                    "FliegenKopf",
 | 
			
		||||
                    "PixelPommes",
 | 
			
		||||
                    "SchwipsWürst",
 | 
			
		||||
                    "WutzBär",
 | 
			
		||||
                    "KnuddelKeks",
 | 
			
		||||
                    "FantaFlumm",
 | 
			
		||||
                    "ZockerKäse",
 | 
			
		||||
                    "LachHäufchen",
 | 
			
		||||
                    "GurkenGuru",
 | 
			
		||||
                    "PonySchnitzel",
 | 
			
		||||
                    "NudelNinja",
 | 
			
		||||
                    "VulkanKeks",
 | 
			
		||||
                    "WasserToast",
 | 
			
		||||
                    "MenschSalat",
 | 
			
		||||
                    "KampfKohl",
 | 
			
		||||
                    "SockenZirkus",
 | 
			
		||||
                    "SchwimmBärchen",
 | 
			
		||||
                    "TanzenderPudel",
 | 
			
		||||
                    "PizzamarktMensch",
 | 
			
		||||
                    "ZahnarztZocker",
 | 
			
		||||
                    "RollerRudi",
 | 
			
		||||
                    "PupsPilot",
 | 
			
		||||
                    "WitzigeZwiebel",
 | 
			
		||||
                    "Pillenschlucker",
 | 
			
		||||
                    "ZwiebelReiter",
 | 
			
		||||
                    "HüpfenderKaktus",
 | 
			
		||||
                    "AsteroidenAlf",
 | 
			
		||||
                    "ChaosKarotte",
 | 
			
		||||
                    "WolkenFurz",
 | 
			
		||||
                    "Krümelmonster",
 | 
			
		||||
                    "WackelBiene",
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Random random = new Random();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.button.AbstractButton;
 | 
			
		||||
import pp.mdga.client.button.ButtonLeft;
 | 
			
		||||
import pp.mdga.client.button.ButtonRight;
 | 
			
		||||
import pp.mdga.client.button.MenuButton;
 | 
			
		||||
@@ -11,6 +10,11 @@
 | 
			
		||||
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code VideoSettingsDialog} class represents a dialog for configuring video settings,
 | 
			
		||||
 * such as resolution and fullscreen mode. It also provides an option to restart the application
 | 
			
		||||
 * when certain settings are changed.
 | 
			
		||||
 */
 | 
			
		||||
public class VideoSettingsDialog extends Dialog {
 | 
			
		||||
    private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
 | 
			
		||||
 | 
			
		||||
@@ -29,6 +33,13 @@ public class VideoSettingsDialog extends Dialog {
 | 
			
		||||
 | 
			
		||||
    private boolean active = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code VideoSettingsDialog}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The main application managing the dialog.
 | 
			
		||||
     * @param node The root node for attaching UI elements.
 | 
			
		||||
     * @param view The view managing navigation and interaction with the video settings dialog.
 | 
			
		||||
     */
 | 
			
		||||
    public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        super(app, node);
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +78,9 @@ public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
 | 
			
		||||
        backButton.setPos(new Vector2f(0, 1.8f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is shown. Displays all buttons and marks the dialog as active.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShow() {
 | 
			
		||||
        active = true;
 | 
			
		||||
@@ -83,6 +97,9 @@ protected void onShow() {
 | 
			
		||||
        backButton.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the dialog is hidden. Hides all buttons and marks the dialog as inactive.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onHide() {
 | 
			
		||||
        active = false;
 | 
			
		||||
@@ -100,14 +117,25 @@ protected void onHide() {
 | 
			
		||||
        restartButton.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the dialog's state. This method can be used for periodic updates while the dialog is active.
 | 
			
		||||
     */
 | 
			
		||||
    public void update() {
 | 
			
		||||
        if(!active) {
 | 
			
		||||
        if (!active) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the resolution settings and optionally triggers the restart button if changes are detected.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width        The width of the resolution.
 | 
			
		||||
     * @param height       The height of the resolution.
 | 
			
		||||
     * @param imageFactor  The scaling factor for the resolution.
 | 
			
		||||
     * @param isFullscreen {@code true} if fullscreen mode is enabled, {@code false} otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
 | 
			
		||||
        if(width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
 | 
			
		||||
        if (width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
 | 
			
		||||
            restartButton.show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,26 @@
 | 
			
		||||
import pp.mdga.client.animation.ZoomControl;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
 | 
			
		||||
 class ActionTextHandler {
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code ActionTextHandler} class manages the display of animated and stylized text messages in the game's UI.
 | 
			
		||||
 * It supports dynamic text creation with spacing, color, and effects, such as dice rolls, player actions, and rankings.
 | 
			
		||||
 */
 | 
			
		||||
class ActionTextHandler {
 | 
			
		||||
    private Node root;
 | 
			
		||||
    private BitmapFont font;
 | 
			
		||||
    private AppSettings appSettings;
 | 
			
		||||
    private int ranking;
 | 
			
		||||
 | 
			
		||||
     ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
 | 
			
		||||
    float paddingRanked = 100;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an {@code ActionTextHandler}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param guiNode      The GUI node where the text messages will be displayed.
 | 
			
		||||
     * @param assetManager The asset manager used to load fonts and other assets.
 | 
			
		||||
     * @param appSettings  The application settings for positioning and sizing.
 | 
			
		||||
     */
 | 
			
		||||
    ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
 | 
			
		||||
        root = new Node("actionTextRoot");
 | 
			
		||||
        guiNode.attachChild(root);
 | 
			
		||||
 | 
			
		||||
@@ -26,13 +39,23 @@ class ActionTextHandler {
 | 
			
		||||
        ranking = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a {@code Node} containing text with specified spacing, size, and colors for each segment of the text.
 | 
			
		||||
     *
 | 
			
		||||
     * @param textArr  An array of strings representing the text to be displayed.
 | 
			
		||||
     * @param spacing  The spacing between individual characters.
 | 
			
		||||
     * @param size     The size of the text.
 | 
			
		||||
     * @param colorArr An array of {@code ColorRGBA} representing the color for each string in {@code textArr}.
 | 
			
		||||
     * @return A {@code Node} containing the styled text with spacing and color applied.
 | 
			
		||||
     * @throws RuntimeException if the lengths of {@code textArr} and {@code colorArr} do not match.
 | 
			
		||||
     */
 | 
			
		||||
    private Node createTextWithSpacing(String[] textArr, float spacing, float size, ColorRGBA[] colorArr) {
 | 
			
		||||
        if(textArr.length != colorArr.length) throw new RuntimeException("text and color are not the same length");
 | 
			
		||||
        if (textArr.length != colorArr.length) throw new RuntimeException("text and color are not the same length");
 | 
			
		||||
 | 
			
		||||
        Node textNode = new Node("TextWithSpacing");
 | 
			
		||||
        Node center = new Node();
 | 
			
		||||
        float xOffset = 0;
 | 
			
		||||
        for(int i = 0; i < textArr.length; i++){
 | 
			
		||||
        for (int i = 0; i < textArr.length; i++) {
 | 
			
		||||
            String text = textArr[i];
 | 
			
		||||
            ColorRGBA color = colorArr[i];
 | 
			
		||||
            for (char c : text.toCharArray()) {
 | 
			
		||||
@@ -40,90 +63,199 @@ private Node createTextWithSpacing(String[] textArr, float spacing, float size,
 | 
			
		||||
                letter.setColor(color);
 | 
			
		||||
                letter.setSize(size);
 | 
			
		||||
                letter.setText(Character.toString(c));
 | 
			
		||||
                letter.setLocalTranslation(xOffset, letter.getHeight()/2, 0);
 | 
			
		||||
                letter.setLocalTranslation(xOffset, letter.getHeight() / 2, 0);
 | 
			
		||||
                center.attachChild(letter);
 | 
			
		||||
 | 
			
		||||
                xOffset += letter.getLineWidth() + spacing;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        center.setLocalTranslation(new Vector3f(-xOffset/2,0,0));
 | 
			
		||||
        center.setLocalTranslation(new Vector3f(-xOffset / 2, 0, 0));
 | 
			
		||||
        textNode.attachChild(center);
 | 
			
		||||
        return textNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a {@code Node} containing text with specified spacing, size, and a single color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param text    The text to be displayed.
 | 
			
		||||
     * @param spacing The spacing between individual characters.
 | 
			
		||||
     * @param size    The size of the text.
 | 
			
		||||
     * @param color   The color of the text.
 | 
			
		||||
     * @return A {@code Node} containing the styled text.
 | 
			
		||||
     */
 | 
			
		||||
    private Node createTextWithSpacing(String text, float spacing, float size, ColorRGBA color) {
 | 
			
		||||
        return createTextWithSpacing(new String[]{text}, spacing, size, new ColorRGBA[]{color});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Vector3f center(float width, float height, Vector3f pos){
 | 
			
		||||
        return new Vector3f(pos.x+width/2, pos.y+height/2,0);
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the center position of a rectangle given its width, height, and an origin position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width  The width of the rectangle.
 | 
			
		||||
     * @param height The height of the rectangle.
 | 
			
		||||
     * @param pos    The origin position of the rectangle.
 | 
			
		||||
     * @return A {@code Vector3f} representing the center position.
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f center(float width, float height, Vector3f pos) {
 | 
			
		||||
        return new Vector3f(pos.x + width / 2, pos.y + height / 2, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Node createTopText(String name, float spacing, float size, ColorRGBA color, float top){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates and positions a single-line text at the top of the screen with a specified vertical offset.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name    The text to be displayed.
 | 
			
		||||
     * @param spacing The spacing between individual characters.
 | 
			
		||||
     * @param size    The size of the text.
 | 
			
		||||
     * @param color   The color of the text.
 | 
			
		||||
     * @param top     The vertical offset from the top of the screen.
 | 
			
		||||
     * @return A {@code Node} containing the styled text positioned at the top.
 | 
			
		||||
     */
 | 
			
		||||
    private Node createTopText(String name, float spacing, float size, ColorRGBA color, float top) {
 | 
			
		||||
        return createTopText(new String[]{name}, spacing, size, new ColorRGBA[]{color}, top);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Node createTopText(String[] name, float spacing, float size, ColorRGBA color[], float top){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates and positions multi-line text at the top of the screen with specified vertical offset, spacing, and colors.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name    An array of strings representing the text to be displayed.
 | 
			
		||||
     * @param spacing The spacing between individual characters.
 | 
			
		||||
     * @param size    The size of the text.
 | 
			
		||||
     * @param color   An array of {@code ColorRGBA} representing the color for each string in {@code name}.
 | 
			
		||||
     * @param top     The vertical offset from the top of the screen.
 | 
			
		||||
     * @return A {@code Node} containing the styled text positioned at the top.
 | 
			
		||||
     */
 | 
			
		||||
    private Node createTopText(String[] name, float spacing, float size, ColorRGBA color[], float top) {
 | 
			
		||||
        Node text = createTextWithSpacing(name, spacing, size, color);
 | 
			
		||||
        text.setLocalTranslation(0, (appSettings.getHeight()/2f)*0.8f-top,0);
 | 
			
		||||
        text.setLocalTranslation(0, (appSettings.getHeight() / 2f) * 0.8f - top, 0);
 | 
			
		||||
        root.attachChild(text);
 | 
			
		||||
        return text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Vector3f centerText(float width, float height, Vector3f pos){
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the center position of a rectangle with negative width offset.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width  The negative width of the rectangle.
 | 
			
		||||
     * @param height The height of the rectangle.
 | 
			
		||||
     * @param pos    The origin position of the rectangle.
 | 
			
		||||
     * @return A {@code Vector3f} representing the center position.
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f centerText(float width, float height, Vector3f pos) {
 | 
			
		||||
        return center(-width, height, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void activePlayer(String name, Color color){
 | 
			
		||||
        createTopText(new String[]{name," ist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating the active player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name  The name of the active player.
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void activePlayer(String name, Color color) {
 | 
			
		||||
        createTopText(new String[]{name, " ist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void ownActive(Color color){
 | 
			
		||||
        createTopText(new String[]{"Du"," bist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating that the current player is active.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void ownActive(Color color) {
 | 
			
		||||
        createTopText(new String[]{"Du", " bist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void diceNum(int diceNum, String name, Color color){
 | 
			
		||||
        createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a dice roll result for a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum The number rolled on the dice.
 | 
			
		||||
     * @param name    The name of the player.
 | 
			
		||||
     * @param color   The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void diceNum(int diceNum, String name, Color color) {
 | 
			
		||||
        createTopText(new String[]{name, " würfelt:"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0);
 | 
			
		||||
 | 
			
		||||
        createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 100);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void diceNumMult(int diceNum,int mult, String name, Color color){
 | 
			
		||||
        createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a dice roll result with a multiplier for a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum The number rolled on the dice.
 | 
			
		||||
     * @param mult    The multiplier applied to the dice result.
 | 
			
		||||
     * @param name    The name of the player.
 | 
			
		||||
     * @param color   The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void diceNumMult(int diceNum, int mult, String name, Color color) {
 | 
			
		||||
        createTopText(new String[]{name, " würfelt:"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0);
 | 
			
		||||
 | 
			
		||||
        createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 100);
 | 
			
		||||
        createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void ownDice(int diceNum){
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays the dice roll result for the current player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum The number rolled on the dice.
 | 
			
		||||
     */
 | 
			
		||||
    void ownDice(int diceNum) {
 | 
			
		||||
        createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void ownDiceMult(int diceNum, int mult){
 | 
			
		||||
        createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 0);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays the dice roll result with a multiplier for the current player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum The number rolled on the dice.
 | 
			
		||||
     * @param mult    The multiplier applied to the dice result.
 | 
			
		||||
     */
 | 
			
		||||
    void ownDiceMult(int diceNum, int mult) {
 | 
			
		||||
        createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void drawCard(String name, Color color){
 | 
			
		||||
        createTopText(new String[]{name," erhält eine Bonuskarte"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating that a specified player received a bonus card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name  The name of the player who received the bonus card.
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void drawCard(String name, Color color) {
 | 
			
		||||
        createTopText(new String[]{name, " erhält eine Bonuskarte"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void drawCardOwn(Color color){
 | 
			
		||||
        createTopText(new String[]{"Du","  erhälst eine Bonuskarte"}, 5,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating that the current player received a bonus card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void drawCardOwn(Color color) {
 | 
			
		||||
        createTopText(new String[]{"Du", "  erhälst eine Bonuskarte"}, 5, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void finishText(String name, Color color){
 | 
			
		||||
        createTopText(new String[]{name," ist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating that a specified player has completed their turn or action.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name  The name of the player who finished.
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void finishText(String name, Color color) {
 | 
			
		||||
        createTopText(new String[]{name, " ist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void finishTextOwn(Color color){
 | 
			
		||||
        createTopText(new String[]{"Du", " bist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message indicating that the current player has completed their turn or action.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     */
 | 
			
		||||
    void finishTextOwn(Color color) {
 | 
			
		||||
        createTopText(new String[]{"Du", " bist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private ColorRGBA playerColorToColorRGBA(Color color){
 | 
			
		||||
        return switch (color){
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a player's team color to a corresponding {@code ColorRGBA}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The player's team color.
 | 
			
		||||
     * @return The corresponding {@code ColorRGBA}.
 | 
			
		||||
     * @throws RuntimeException if the color is invalid.
 | 
			
		||||
     */
 | 
			
		||||
    private ColorRGBA playerColorToColorRGBA(Color color) {
 | 
			
		||||
        return switch (color) {
 | 
			
		||||
            case ARMY -> ColorRGBA.Green;
 | 
			
		||||
            case NAVY -> ColorRGBA.Blue;
 | 
			
		||||
            case CYBER -> ColorRGBA.Orange;
 | 
			
		||||
@@ -132,25 +264,41 @@ private ColorRGBA playerColorToColorRGBA(Color color){
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void hide(){
 | 
			
		||||
         ranking = 0;
 | 
			
		||||
         root.detachAllChildren();
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides all text messages displayed by the handler and resets the ranking counter.
 | 
			
		||||
     */
 | 
			
		||||
    void hide() {
 | 
			
		||||
        ranking = 0;
 | 
			
		||||
        root.detachAllChildren();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float paddingRanked = 100;
 | 
			
		||||
 | 
			
		||||
    void rollRankingResult(String name, Color color, int eye){
 | 
			
		||||
        createTopText(new String[]{name,":  "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a ranked dice roll result for a specified player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name  The name of the player.
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     * @param eye   The dice roll result.
 | 
			
		||||
     */
 | 
			
		||||
    void rollRankingResult(String name, Color color, int eye) {
 | 
			
		||||
        createTopText(new String[]{name, ":  " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
 | 
			
		||||
        ranking++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     void rollRankingResultOwn(Color color, int eye){
 | 
			
		||||
         createTopText(new String[]{"Du",":  "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
 | 
			
		||||
         ranking++;
 | 
			
		||||
     }
 | 
			
		||||
 | 
			
		||||
     void diceNow(){
 | 
			
		||||
         createTopText("Klicke  zum  Würfeln", 5, 80, ColorRGBA.White, 0);
 | 
			
		||||
     }
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a ranked dice roll result for the current player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color representing the player's team.
 | 
			
		||||
     * @param eye   The dice roll result.
 | 
			
		||||
     */
 | 
			
		||||
    void rollRankingResultOwn(Color color, int eye) {
 | 
			
		||||
        createTopText(new String[]{"Du", ":  " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
 | 
			
		||||
        ranking++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a message prompting the player to roll the dice.
 | 
			
		||||
     */
 | 
			
		||||
    void diceNow() {
 | 
			
		||||
        createTopText("Klicke  zum  Würfeln", 5, 80, ColorRGBA.White, 0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,23 +7,18 @@
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import com.jme3.scene.shape.Box;
 | 
			
		||||
import com.jme3.scene.shape.Cylinder;
 | 
			
		||||
import com.jme3.scene.shape.Sphere;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.board.OutlineControl;
 | 
			
		||||
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * CardControl class extends OutlineControl to manage the visual representation
 | 
			
		||||
 * and behavior of a card in the game.
 | 
			
		||||
 */
 | 
			
		||||
public class CardControl extends OutlineControl {
 | 
			
		||||
 | 
			
		||||
    private static final ColorRGBA OUTLINE_COLOR = ColorRGBA.Yellow;
 | 
			
		||||
 | 
			
		||||
    private static final ColorRGBA HIGHLIGHT_COLOR = ColorRGBA.Yellow;
 | 
			
		||||
    private static final int HIGHLIGHT_WIDTH = 9;
 | 
			
		||||
 | 
			
		||||
@@ -33,29 +28,40 @@ public class CardControl extends OutlineControl {
 | 
			
		||||
    private static final ColorRGBA SELECT_COLOR = ColorRGBA.Blue;
 | 
			
		||||
    private static final int SELECT_WIDTH = 13;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static final int OUTLINE_THICKNESS = 9;
 | 
			
		||||
    private boolean hoverable;
 | 
			
		||||
    private boolean highlight;
 | 
			
		||||
    private boolean selectable;
 | 
			
		||||
    private boolean select;
 | 
			
		||||
    private Node root;
 | 
			
		||||
    private BitmapText num;
 | 
			
		||||
 | 
			
		||||
    public CardControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, Node root){
 | 
			
		||||
        super(app, fpp, cam, OUTLINE_THICKNESS);
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for CardControl.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  the application instance
 | 
			
		||||
     * @param fpp  the FilterPostProcessor instance
 | 
			
		||||
     * @param cam  the Camera instance
 | 
			
		||||
     * @param root the root Node
 | 
			
		||||
     */
 | 
			
		||||
    public CardControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, Node root) {
 | 
			
		||||
        super(app, fpp, cam,
 | 
			
		||||
            HIGHLIGHT_COLOR, HIGHLIGHT_WIDTH,
 | 
			
		||||
            HOVER_COLOR, HOVER_WIDTH,
 | 
			
		||||
            SELECT_COLOR, SELECT_WIDTH
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this.root = root;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Node rootNum = createNum();
 | 
			
		||||
        rootNum.setLocalTranslation(new Vector3f(0.35f,0.8f,0));
 | 
			
		||||
        rootNum.setLocalTranslation(new Vector3f(0.35f, 0.8f, 0));
 | 
			
		||||
        root.attachChild(rootNum);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Node createNum(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Node containing a number and a circle geometry.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the created Node
 | 
			
		||||
     */
 | 
			
		||||
    private Node createNum() {
 | 
			
		||||
        Node rootNum = new Node("root Num");
 | 
			
		||||
        Geometry circle = new Geometry("circle", new Sphere(20,20,1));
 | 
			
		||||
        circle.setLocalTranslation(new Vector3f(0.03f,0.01f,1));
 | 
			
		||||
        Geometry circle = new Geometry("circle", new Sphere(20, 20, 1));
 | 
			
		||||
        circle.setLocalTranslation(new Vector3f(0.03f, 0.01f, 1));
 | 
			
		||||
        circle.setLocalScale(0.2f);
 | 
			
		||||
        Material mat = new Material(getApp().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
 | 
			
		||||
        mat.setColor("Color", ColorRGBA.Black);
 | 
			
		||||
@@ -72,79 +78,30 @@ private Node createNum(){
 | 
			
		||||
        return rootNum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNumCard(int num){
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the number displayed on the card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num the number to display
 | 
			
		||||
     */
 | 
			
		||||
    public void setNumCard(int num) {
 | 
			
		||||
        this.num.setText(String.valueOf(num));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the root Node of the card.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the root Node
 | 
			
		||||
     */
 | 
			
		||||
    public Node getRoot() {
 | 
			
		||||
        return root;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the spatial properties of the card.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initSpatial(){
 | 
			
		||||
    public void initSpatial() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void outline(){
 | 
			
		||||
        super.outline(OUTLINE_COLOR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final static Vector3f HIGHLIGHT_Y = new Vector3f(0,0.4f,0);
 | 
			
		||||
 | 
			
		||||
    public void setHighlight() {
 | 
			
		||||
        this.highlight = true;
 | 
			
		||||
        root.setLocalTranslation(root.getLocalTranslation().add(HIGHLIGHT_Y));
 | 
			
		||||
        highlight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void highlight() {
 | 
			
		||||
        super.outline(HIGHLIGHT_COLOR, HIGHLIGHT_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void unHighlight(){
 | 
			
		||||
        highlight = false;
 | 
			
		||||
        root.setLocalTranslation(root.getLocalTranslation().subtract(HIGHLIGHT_Y));
 | 
			
		||||
        deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hover(){
 | 
			
		||||
        if(!hoverable) return;
 | 
			
		||||
        super.outline(HOVER_COLOR, HOVER_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hoverOff(){
 | 
			
		||||
        if(!hoverable) return;
 | 
			
		||||
 | 
			
		||||
        if(select) select();
 | 
			
		||||
        else if(highlight) highlight();
 | 
			
		||||
        else deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void select(){
 | 
			
		||||
        if(!selectable) return;
 | 
			
		||||
        select = true;
 | 
			
		||||
        super.outline(SELECT_COLOR, SELECT_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void unSelect(){
 | 
			
		||||
        if(!selectable) return;
 | 
			
		||||
        select = false;
 | 
			
		||||
        if(highlight) highlight();
 | 
			
		||||
        else deOutline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSelectable(boolean selectable){
 | 
			
		||||
        this.selectable = selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isSelected() {
 | 
			
		||||
        return select;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isSelectable() {
 | 
			
		||||
        return selectable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoverable(boolean hoverable) {
 | 
			
		||||
        this.hoverable = hoverable;
 | 
			
		||||
    }
 | 
			
		||||
    private final static Vector3f HIGHLIGHT_Y = new Vector3f(0, 0.4f, 0);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.post.filters.ComposeFilter;
 | 
			
		||||
import com.jme3.post.filters.FXAAFilter;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
@@ -18,8 +19,13 @@
 | 
			
		||||
import com.jme3.texture.Image;
 | 
			
		||||
import com.jme3.texture.Texture2D;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * CardLayer is an application state that manages the rendering and updating of card objects
 | 
			
		||||
 * in a separate viewport with post-processing effects.
 | 
			
		||||
 */
 | 
			
		||||
public class CardLayer extends AbstractAppState {
 | 
			
		||||
 | 
			
		||||
    public static final int SHADOWMAP_SIZE = 1024 * 8;
 | 
			
		||||
@@ -31,35 +37,58 @@ public class CardLayer extends AbstractAppState {
 | 
			
		||||
    private List<Spatial> cardBuffer;
 | 
			
		||||
    private final FilterPostProcessor fpp;
 | 
			
		||||
    private final Camera overlayCam;
 | 
			
		||||
    Texture2D backTexture;
 | 
			
		||||
    private Texture2D backTexture;
 | 
			
		||||
    private FXAAFilter fxaaFilter;
 | 
			
		||||
    private ViewPort view;
 | 
			
		||||
    private DirectionalLightShadowFilter dlsf;
 | 
			
		||||
    DirectionalLight sun;
 | 
			
		||||
    ComposeFilter compose;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new CardLayer with the specified post-processor, camera, and background texture.
 | 
			
		||||
     *
 | 
			
		||||
     * @param fpp the FilterPostProcessor to use for post-processing effects
 | 
			
		||||
     * @param overlayCam the Camera to use for the overlay
 | 
			
		||||
     * @param backTexture the Texture2D to use as the background texture
 | 
			
		||||
     */
 | 
			
		||||
    public CardLayer(FilterPostProcessor fpp, Camera overlayCam, Texture2D backTexture) {
 | 
			
		||||
        this.overlayCam = overlayCam;
 | 
			
		||||
        this.fpp = fpp;
 | 
			
		||||
        this.cardBuffer = new ArrayList<>();
 | 
			
		||||
        init = false;
 | 
			
		||||
        this.backTexture = backTexture;
 | 
			
		||||
        cardBuffer = new ArrayList<>();
 | 
			
		||||
        init = false;
 | 
			
		||||
        fxaaFilter = new FXAAFilter();
 | 
			
		||||
        view = null;
 | 
			
		||||
        dlsf = null;
 | 
			
		||||
 | 
			
		||||
        sun = new DirectionalLight();
 | 
			
		||||
        sun.setColor(ColorRGBA.White);
 | 
			
		||||
        sun.setDirection(new Vector3f(.5f, -.5f, -1));
 | 
			
		||||
        compose = new ComposeFilter(backTexture);
 | 
			
		||||
        root = new Node("Under gui viewport Root");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the CardLayer, setting up the viewport, filters, and lighting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stateManager the AppStateManager managing this state
 | 
			
		||||
     * @param app the Application instance
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(AppStateManager stateManager, Application app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        root = new Node("Under gui viewport Root");
 | 
			
		||||
 | 
			
		||||
        ViewPort view = app.getRenderManager().createMainView("Under gui ViewPort", overlayCam);
 | 
			
		||||
        view = app.getRenderManager().createMainView("Under gui ViewPort", overlayCam);
 | 
			
		||||
        view.setEnabled(true);
 | 
			
		||||
        view.setClearFlags(true, true, true);
 | 
			
		||||
        view.attachScene(root);
 | 
			
		||||
        fpp.setFrameBufferFormat(Image.Format.RGBA8);
 | 
			
		||||
        fpp.addFilter(new ComposeFilter(backTexture));
 | 
			
		||||
        fpp.addFilter(compose);
 | 
			
		||||
        fpp.addFilter(fxaaFilter);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        DirectionalLight sun = new DirectionalLight();
 | 
			
		||||
        sun.setColor(ColorRGBA.White);
 | 
			
		||||
        sun.setDirection(new Vector3f(.5f, -.5f, -1));
 | 
			
		||||
        root.addLight(sun);
 | 
			
		||||
 | 
			
		||||
        DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
 | 
			
		||||
        dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
 | 
			
		||||
        dlsf.setLight(sun);
 | 
			
		||||
        dlsf.setEnabled(true);
 | 
			
		||||
        dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
 | 
			
		||||
@@ -71,17 +100,35 @@ public void initialize(AppStateManager stateManager, Application app) {
 | 
			
		||||
        if (!init) init = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shuts down the CardLayer, removing filters, lights, and clearing buffers.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        fpp.removeFilter(dlsf);
 | 
			
		||||
        dlsf = null;
 | 
			
		||||
        root.removeLight(sun);
 | 
			
		||||
        fpp.removeFilter(fxaaFilter);
 | 
			
		||||
        view.detachScene(root);
 | 
			
		||||
 | 
			
		||||
        cardBuffer.clear();
 | 
			
		||||
        root.detachAllChildren();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the CardLayer, updating the geometric state of the root node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager handling the rendering
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(RenderManager rm) {
 | 
			
		||||
        root.updateGeometricState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the CardLayer, attaching buffered cards to the root node and updating its logical state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        if (init && !cardBuffer.isEmpty()) {
 | 
			
		||||
@@ -93,19 +140,39 @@ public void update(float tpf) {
 | 
			
		||||
        root.updateLogicalState(tpf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a spatial card to the CardLayer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the Spatial card to add
 | 
			
		||||
     */
 | 
			
		||||
    public void addSpatial(Spatial card) {
 | 
			
		||||
        if(root == null) cardBuffer.add(card);
 | 
			
		||||
        if (root == null) cardBuffer.add(card);
 | 
			
		||||
        else root.attachChild(card);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes a spatial card from the CardLayer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param spatial the Spatial card to delete
 | 
			
		||||
     */
 | 
			
		||||
    public void deleteSpatial(Spatial spatial) {
 | 
			
		||||
        root.detachChild(spatial);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the overlay camera used by the CardLayer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the overlay camera
 | 
			
		||||
     */
 | 
			
		||||
    public Camera getOverlayCam() {
 | 
			
		||||
        return overlayCam;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the root node of the CardLayer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the root node
 | 
			
		||||
     */
 | 
			
		||||
    public Node getRootNode() {
 | 
			
		||||
        return root;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,9 @@
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles the card layer in the GUI, including card management and dice control.
 | 
			
		||||
 */
 | 
			
		||||
public class CardLayerHandler {
 | 
			
		||||
    private static final Vector3f START = new Vector3f(-1.8f, -3.5f, 0);
 | 
			
		||||
    private static final Vector3f MARGIN = new Vector3f(1.8f, 0, 0);
 | 
			
		||||
@@ -29,17 +32,28 @@ public class CardLayerHandler {
 | 
			
		||||
    private DiceControl diceControl;
 | 
			
		||||
 | 
			
		||||
    private final Map<BonusCard, CardControl> bonusCardControlMap = new HashMap<>();
 | 
			
		||||
    private final List<BonusCard> cardOrder = new ArrayList<>();
 | 
			
		||||
    private final Map<BonusCard, Integer> bonusCardIntegerMap = new HashMap<>();
 | 
			
		||||
    private final Set<CardControl> selectableCards = new HashSet<>();
 | 
			
		||||
 | 
			
		||||
    private BonusCard cardSelect = null;
 | 
			
		||||
    private boolean show = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a CardLayerHandler.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     * @param backTexture the background texture
 | 
			
		||||
     */
 | 
			
		||||
    public CardLayerHandler(MdgaApp app, Texture2D backTexture) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.fpp = new FilterPostProcessor(app.getAssetManager());
 | 
			
		||||
        this.backTexture = backTexture;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the card layer and dice control.
 | 
			
		||||
     */
 | 
			
		||||
    public void init() {
 | 
			
		||||
        cardLayerCamera = createOverlayCam();
 | 
			
		||||
        cardLayer = new CardLayer(fpp, cardLayerCamera, backTexture);
 | 
			
		||||
@@ -49,114 +63,188 @@ public void init() {
 | 
			
		||||
        diceControl.create(new Vector3f(0, 0, 0), 1f, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shuts down the card layer and clears selectable cards.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        clearSelectableCards();
 | 
			
		||||
        if (cardLayer != null) {
 | 
			
		||||
            cardLayer.shutdown();
 | 
			
		||||
            clearSelectableCards();
 | 
			
		||||
            app.getStateManager().detach(cardLayer);
 | 
			
		||||
        }
 | 
			
		||||
        cardLayer = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rolls the dice with a specified number and action to perform after rolling.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rollNum the number to roll (must be between 1 and 6)
 | 
			
		||||
     * @param actionAfter the action to perform after rolling
 | 
			
		||||
     */
 | 
			
		||||
    public void rollDice(int rollNum, Runnable actionAfter) {
 | 
			
		||||
        if (!(1 <= rollNum && rollNum <= 6)) throw new RuntimeException("rollNum is not in the range [1,6]");
 | 
			
		||||
        diceControl.rollDice(rollNum, actionAfter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the dice on the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void showDice() {
 | 
			
		||||
        if (show) return;
 | 
			
		||||
        show = true;
 | 
			
		||||
        cardLayer.addSpatial(diceControl.getSpatial());
 | 
			
		||||
        diceControl.spin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the dice from the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void hideDice() {
 | 
			
		||||
        show = false;
 | 
			
		||||
        diceControl.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a card to the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the card to add
 | 
			
		||||
     */
 | 
			
		||||
    public void addCard(BonusCard card) {
 | 
			
		||||
        if (card == BonusCard.HIDDEN) throw new RuntimeException("Can't add hidden card to GUI");
 | 
			
		||||
 | 
			
		||||
        if (!bonusCardControlMap.containsKey(card)) {
 | 
			
		||||
            CardControl control = createCard(bonusToAsset(card), nextPos());
 | 
			
		||||
            bonusCardControlMap.put(card, control);
 | 
			
		||||
            cardLayer.addSpatial(control.getRoot());
 | 
			
		||||
            cardOrder.add(card);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int newNum = bonusCardIntegerMap.getOrDefault(card, 0) + 1;
 | 
			
		||||
        bonusCardIntegerMap.put(card, newNum);
 | 
			
		||||
        bonusCardControlMap.get(card).setNumCard(newNum);
 | 
			
		||||
 | 
			
		||||
        updateCard();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removeCard(BonusCard card){
 | 
			
		||||
        if(bonusCardControlMap.containsKey(card)){
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a card from the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the card to remove
 | 
			
		||||
     */
 | 
			
		||||
    public void removeCard(BonusCard card) {
 | 
			
		||||
        if (bonusCardControlMap.containsKey(card)) {
 | 
			
		||||
            bonusCardIntegerMap.put(card, bonusCardIntegerMap.get(card) - 1);
 | 
			
		||||
 | 
			
		||||
            if(bonusCardIntegerMap.get(card) <= 0){
 | 
			
		||||
                cardLayer.deleteSpatial(bonusCardControlMap.get(card).getRoot());
 | 
			
		||||
            if (bonusCardIntegerMap.get(card) <= 0) {
 | 
			
		||||
                bonusCardIntegerMap.remove(card);
 | 
			
		||||
                bonusCardControlMap.remove(card);
 | 
			
		||||
                cardOrder.remove(card);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
            updateCard();
 | 
			
		||||
        } else throw new RuntimeException("card is not in bonusCardControlMap");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears all selectable cards.
 | 
			
		||||
     */
 | 
			
		||||
    public void clearSelectableCards() {
 | 
			
		||||
        for (CardControl control : selectableCards) {
 | 
			
		||||
            control.setSelectable(false);
 | 
			
		||||
            control.setHoverable(false);
 | 
			
		||||
            control.unHighlight();
 | 
			
		||||
            control.unSelect();
 | 
			
		||||
            control.selectableOff();
 | 
			
		||||
        }
 | 
			
		||||
        selectableCards.clear();
 | 
			
		||||
        cardSelect = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the card layer with the current cards.
 | 
			
		||||
     */
 | 
			
		||||
    private void updateCard() {
 | 
			
		||||
        for (BonusCard card : bonusCardControlMap.keySet()) {
 | 
			
		||||
            CardControl control = bonusCardControlMap.get(card);
 | 
			
		||||
            cardLayer.deleteSpatial(control.getRoot());
 | 
			
		||||
        }
 | 
			
		||||
        bonusCardControlMap.clear();
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < cardOrder.size(); i++) {
 | 
			
		||||
            BonusCard card = cardOrder.get(i);
 | 
			
		||||
            CardControl control = createCard(bonusToAsset(card), nextPos(i));
 | 
			
		||||
            control.setNumCard(bonusCardIntegerMap.get(card));
 | 
			
		||||
            cardLayer.addSpatial(control.getRoot());
 | 
			
		||||
            bonusCardControlMap.put(card, control);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the selectable cards.
 | 
			
		||||
     *
 | 
			
		||||
     * @param select the list of cards to set as selectable
 | 
			
		||||
     */
 | 
			
		||||
    public void setSelectableCards(List<BonusCard> select) {
 | 
			
		||||
        for (BonusCard card : select) {
 | 
			
		||||
            selectableCards.add(bonusCardControlMap.get(card));
 | 
			
		||||
        }
 | 
			
		||||
        for (CardControl control : selectableCards) {
 | 
			
		||||
            control.setSelectable(true);
 | 
			
		||||
            control.setHoverable(true);
 | 
			
		||||
            control.setHighlight();
 | 
			
		||||
            control.selectableOn();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a card control.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cardControl the card control to select
 | 
			
		||||
     */
 | 
			
		||||
    public void selectCard(CardControl cardControl) {
 | 
			
		||||
        if (cardControl.isSelected()) {
 | 
			
		||||
            cardControl.unSelect();
 | 
			
		||||
            cardControl.selectOff();
 | 
			
		||||
            cardSelect = null;
 | 
			
		||||
        } else {
 | 
			
		||||
            for (CardControl control : selectableCards) {
 | 
			
		||||
                control.unSelect();
 | 
			
		||||
                control.selectOff();
 | 
			
		||||
            }
 | 
			
		||||
            cardControl.select();
 | 
			
		||||
            cardControl.selectOn();
 | 
			
		||||
            cardSelect = getKeyByValue(bonusCardControlMap, cardControl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        app.getModelSynchronize().selectCard(cardSelect);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the card layer camera.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the card layer camera
 | 
			
		||||
     */
 | 
			
		||||
    public Camera getCardLayerCamera() {
 | 
			
		||||
        return cardLayerCamera;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void shield(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a shield symbol to the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void shield() {
 | 
			
		||||
        SymbolControl control = createSymbol(Asset.shieldSymbol);
 | 
			
		||||
        cardLayer.addSpatial(control.getSpatial());
 | 
			
		||||
        control.shield();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void swap(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a swap symbol to the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void swap() {
 | 
			
		||||
        SymbolControl control = createSymbol(Asset.swapSymbol);
 | 
			
		||||
        cardLayer.addSpatial(control.getSpatial());
 | 
			
		||||
        control.swap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void turbo(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a turbo symbol to the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void turbo() {
 | 
			
		||||
        SymbolControl control = createSymbol(Asset.turboSymbol);
 | 
			
		||||
        cardLayer.addSpatial(control.getSpatial());
 | 
			
		||||
        control.turbo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a bonus card to its corresponding asset.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the bonus card
 | 
			
		||||
     * @return the corresponding asset
 | 
			
		||||
     */
 | 
			
		||||
    private Asset bonusToAsset(BonusCard card) {
 | 
			
		||||
        return switch (card) {
 | 
			
		||||
            case TURBO -> Asset.turboCard;
 | 
			
		||||
@@ -166,10 +254,21 @@ private Asset bonusToAsset(BonusCard card) {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Vector3f nextPos() {
 | 
			
		||||
        return START.add(MARGIN.mult(bonusCardControlMap.size()));
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the next position for a card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param i the index of the card
 | 
			
		||||
     * @return the next position vector
 | 
			
		||||
     */
 | 
			
		||||
    private Vector3f nextPos(int i) {
 | 
			
		||||
        return START.add(MARGIN.mult(i));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an overlay camera for the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the created overlay camera
 | 
			
		||||
     */
 | 
			
		||||
    private Camera createOverlayCam() {
 | 
			
		||||
        Camera originalCam = app.getCamera();
 | 
			
		||||
        Camera overlayCam = new Camera(originalCam.getWidth(), originalCam.getHeight());
 | 
			
		||||
@@ -182,6 +281,15 @@ private Camera createOverlayCam() {
 | 
			
		||||
        return overlayCam;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the key associated with a value in a map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map the map to search
 | 
			
		||||
     * @param value the value to find the key for
 | 
			
		||||
     * @param <K> the type of keys in the map
 | 
			
		||||
     * @param <V> the type of values in the map
 | 
			
		||||
     * @return the key associated with the value, or null if not found
 | 
			
		||||
     */
 | 
			
		||||
    private <K, V> K getKeyByValue(Map<K, V> map, V value) {
 | 
			
		||||
        for (Map.Entry<K, V> entry : map.entrySet()) {
 | 
			
		||||
            if (entry.getValue().equals(value)) {
 | 
			
		||||
@@ -191,6 +299,9 @@ private <K, V> K getKeyByValue(Map<K, V> map, V value) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Test method to add sample cards to the card layer.
 | 
			
		||||
     */
 | 
			
		||||
    public void test() {
 | 
			
		||||
        addCard(BonusCard.SHIELD);
 | 
			
		||||
        addCard(BonusCard.SHIELD);
 | 
			
		||||
@@ -198,7 +309,14 @@ public void test() {
 | 
			
		||||
        addCard(BonusCard.SWAP);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CardControl createCard(Asset card, Vector3f pos){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a card control for a given asset and position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the asset representing the card
 | 
			
		||||
     * @param pos the position to place the card
 | 
			
		||||
     * @return the created card control
 | 
			
		||||
     */
 | 
			
		||||
    private CardControl createCard(Asset card, Vector3f pos) {
 | 
			
		||||
        Node rootCard = new Node("Root Card");
 | 
			
		||||
        Spatial spatial = app.getAssetManager().loadModel(card.getModelPath());
 | 
			
		||||
        rootCard.attachChild(spatial);
 | 
			
		||||
@@ -207,26 +325,37 @@ private CardControl createCard(Asset card, Vector3f pos){
 | 
			
		||||
        spatial.setMaterial(mat);
 | 
			
		||||
        spatial.setLocalScale(1f);
 | 
			
		||||
        rootCard.setLocalTranslation(pos);
 | 
			
		||||
        spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
 | 
			
		||||
        spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
 | 
			
		||||
        spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
        CardControl control = new CardControl(app, fpp, cardLayer.getOverlayCam(), rootCard);
 | 
			
		||||
        spatial.addControl(control);
 | 
			
		||||
        return control;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SymbolControl createSymbol(Asset asset){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a symbol control for a given asset.
 | 
			
		||||
     *
 | 
			
		||||
     * @param asset the asset representing the symbol
 | 
			
		||||
     * @return the created symbol control
 | 
			
		||||
     */
 | 
			
		||||
    private SymbolControl createSymbol(Asset asset) {
 | 
			
		||||
        Spatial spatial = app.getAssetManager().loadModel(asset.getModelPath());
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
 | 
			
		||||
        mat.setTexture("ColorMap", app.getAssetManager().loadTexture(asset.getDiffPath()));
 | 
			
		||||
        spatial.setMaterial(mat);
 | 
			
		||||
        spatial.setLocalScale(1f);
 | 
			
		||||
        spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
 | 
			
		||||
        spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
 | 
			
		||||
        SymbolControl control = new SymbolControl();
 | 
			
		||||
        spatial.addControl(control);
 | 
			
		||||
        return control;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CardLayer getCardLayer(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the card layer
 | 
			
		||||
     */
 | 
			
		||||
    public CardLayer getCardLayer() {
 | 
			
		||||
        return cardLayer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,16 +11,18 @@
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.mdga.client.Asset;
 | 
			
		||||
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
import static com.jme3.material.Materials.LIGHTING;
 | 
			
		||||
import static com.jme3.material.Materials.UNSHADED;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * DiceControl class handles the rolling and spinning behavior of a dice in the game.
 | 
			
		||||
 */
 | 
			
		||||
public class DiceControl extends AbstractControl {
 | 
			
		||||
    private Quaternion targetRotation;
 | 
			
		||||
    private final Vector3f angularVelocity = new Vector3f();
 | 
			
		||||
    private float deceleration = 0.5f;
 | 
			
		||||
    private float deceleration = 1f;
 | 
			
		||||
    private float timeElapsed = 0.0f;
 | 
			
		||||
    private float rollDuration = 1f;
 | 
			
		||||
    private static final int ANGULAR_MIN = 5;
 | 
			
		||||
@@ -32,31 +34,39 @@ public class DiceControl extends AbstractControl {
 | 
			
		||||
    private final AssetManager assetManager;
 | 
			
		||||
    private Runnable actionAfter;
 | 
			
		||||
 | 
			
		||||
    public DiceControl(AssetManager assetManager){
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for DiceControl.
 | 
			
		||||
     *
 | 
			
		||||
     * @param assetManager the asset manager to load models and textures
 | 
			
		||||
     */
 | 
			
		||||
    public DiceControl(AssetManager assetManager) {
 | 
			
		||||
        this.assetManager = assetManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the control each frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        float clampedTpf = Math.min(tpf, 0.05f); // Max 50 ms per frame
 | 
			
		||||
        if (isRolling) {
 | 
			
		||||
            if(!slerp) {
 | 
			
		||||
            if (!slerp) {
 | 
			
		||||
                // Apply rotational velocity to the dice
 | 
			
		||||
                spinWithAngularVelocity(tpf);
 | 
			
		||||
                spinWithAngularVelocity(clampedTpf);
 | 
			
		||||
 | 
			
		||||
                // Gradually reduce rotational velocity (simulate deceleration)
 | 
			
		||||
                angularVelocity.subtractLocal(
 | 
			
		||||
                        angularVelocity.mult(deceleration * tpf)
 | 
			
		||||
                        angularVelocity.mult(deceleration * clampedTpf)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Stop rolling when angular velocity is close to zero
 | 
			
		||||
                if (angularVelocity.lengthSquared() < 3f) {
 | 
			
		||||
                if (angularVelocity.lengthSquared() <= 3f || MdgaApp.DEBUG_MULTIPLIER == 0) {
 | 
			
		||||
                    slerp = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                timeElapsed += tpf * rollDuration;
 | 
			
		||||
 | 
			
		||||
            } else {
 | 
			
		||||
                timeElapsed += clampedTpf * rollDuration;
 | 
			
		||||
 | 
			
		||||
                if (timeElapsed > 1.0f) timeElapsed = 1.0f;
 | 
			
		||||
                Quaternion interpolated = spatial.getLocalRotation().clone();
 | 
			
		||||
@@ -64,18 +74,23 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
                spatial.setLocalRotation(interpolated);
 | 
			
		||||
 | 
			
		||||
                // Stop rolling once duration is complete
 | 
			
		||||
                if (timeElapsed >= 1.0f) {
 | 
			
		||||
                if (timeElapsed >= 1.0f * MdgaApp.DEBUG_MULTIPLIER) {
 | 
			
		||||
                    isRolling = false;
 | 
			
		||||
                    slerp = false;
 | 
			
		||||
                    actionAfter.run();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }else if(spin){
 | 
			
		||||
            spinWithAngularVelocity(tpf);
 | 
			
		||||
        } else if (spin) {
 | 
			
		||||
            spinWithAngularVelocity(clampedTpf);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void spinWithAngularVelocity(float tpf){
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies rotational velocity to the dice.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    private void spinWithAngularVelocity(float tpf) {
 | 
			
		||||
        Quaternion currentRotation = spatial.getLocalRotation();
 | 
			
		||||
        Quaternion deltaRotation = new Quaternion();
 | 
			
		||||
        deltaRotation.fromAngles(
 | 
			
		||||
@@ -86,20 +101,32 @@ private void spinWithAngularVelocity(float tpf){
 | 
			
		||||
        spatial.setLocalRotation(currentRotation.mult(deltaRotation));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the control.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the render manager
 | 
			
		||||
     * @param vp the viewport
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiates the dice roll.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum the number on the dice to roll to
 | 
			
		||||
     * @param actionAfter the action to perform after the roll
 | 
			
		||||
     */
 | 
			
		||||
    public void rollDice(int diceNum, Runnable actionAfter) {
 | 
			
		||||
        if (isRolling) return;
 | 
			
		||||
        spin = false;
 | 
			
		||||
        slerp = false;
 | 
			
		||||
        timeElapsed = 0;
 | 
			
		||||
        this.actionAfter = actionAfter;
 | 
			
		||||
        angularVelocity.set(
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX)
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
 | 
			
		||||
                FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        targetRotation = getRotationForDiceNum(diceNum);
 | 
			
		||||
@@ -107,6 +134,12 @@ public void rollDice(int diceNum, Runnable actionAfter) {
 | 
			
		||||
        isRolling = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the target rotation for a given dice number.
 | 
			
		||||
     *
 | 
			
		||||
     * @param diceNum the number on the dice
 | 
			
		||||
     * @return the target rotation as a Quaternion
 | 
			
		||||
     */
 | 
			
		||||
    private Quaternion getRotationForDiceNum(int diceNum) {
 | 
			
		||||
        return switch (diceNum) {
 | 
			
		||||
            case 1 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_X);
 | 
			
		||||
@@ -119,10 +152,19 @@ private Quaternion getRotationForDiceNum(int diceNum) {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Linear interpolation function.
 | 
			
		||||
     *
 | 
			
		||||
     * @param t the interpolation factor
 | 
			
		||||
     * @return the interpolated value
 | 
			
		||||
     */
 | 
			
		||||
    public static float lerp(float t) {
 | 
			
		||||
        return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a random rotation for the dice.
 | 
			
		||||
     */
 | 
			
		||||
    public void randomRotation() {
 | 
			
		||||
        Quaternion randomRotation = new Quaternion();
 | 
			
		||||
        randomRotation.fromAngles(
 | 
			
		||||
@@ -133,38 +175,55 @@ public void randomRotation() {
 | 
			
		||||
        spatial.setLocalRotation(randomRotation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void spin(){
 | 
			
		||||
        angularVelocity.set(ANGULAR_SPIN,ANGULAR_SPIN,ANGULAR_SPIN);
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiates the dice spin.
 | 
			
		||||
     */
 | 
			
		||||
    public void spin() {
 | 
			
		||||
        angularVelocity.set(ANGULAR_SPIN, ANGULAR_SPIN, ANGULAR_SPIN);
 | 
			
		||||
        spin = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hide(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the dice by removing it from the parent node.
 | 
			
		||||
     */
 | 
			
		||||
    public void hide() {
 | 
			
		||||
        spatial.removeFromParent();
 | 
			
		||||
        spin = false;
 | 
			
		||||
        isRolling = false;
 | 
			
		||||
        slerp = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void create(Vector3f pos, float scale, boolean shadow){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the dice model and sets its initial properties.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position to place the dice
 | 
			
		||||
     * @param scale the scale of the dice
 | 
			
		||||
     * @param shadow whether the dice should cast and receive shadows
 | 
			
		||||
     */
 | 
			
		||||
    public void create(Vector3f pos, float scale, boolean shadow) {
 | 
			
		||||
        Spatial spatial = assetManager.loadModel(Asset.dice.getModelPath());
 | 
			
		||||
        Material mat;
 | 
			
		||||
        if(shadow){
 | 
			
		||||
        if (shadow) {
 | 
			
		||||
            mat = new Material(assetManager, LIGHTING);
 | 
			
		||||
            mat.setTexture("DiffuseMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
 | 
			
		||||
            spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
 | 
			
		||||
        }
 | 
			
		||||
        else{
 | 
			
		||||
        } else {
 | 
			
		||||
            mat = new Material(assetManager, UNSHADED);
 | 
			
		||||
            mat.setTexture("ColorMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
 | 
			
		||||
        }
 | 
			
		||||
        spatial.setMaterial(mat);
 | 
			
		||||
        spatial.setLocalScale(scale);
 | 
			
		||||
        spatial.setLocalTranslation(pos);
 | 
			
		||||
        spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
 | 
			
		||||
        spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
 | 
			
		||||
        spatial.addControl(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPos(Vector3f pos){
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the position of the dice.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position
 | 
			
		||||
     */
 | 
			
		||||
    public void setPos(Vector3f pos) {
 | 
			
		||||
        spatial.setLocalTranslation(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,14 @@
 | 
			
		||||
import com.jme3.texture.Image;
 | 
			
		||||
import com.jme3.texture.Texture2D;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.game.BonusCard;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles the GUI elements and interactions for the game.
 | 
			
		||||
 */
 | 
			
		||||
public class GuiHandler {
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
    private final CardLayerHandler cardLayerHandler;
 | 
			
		||||
@@ -20,6 +23,12 @@ public class GuiHandler {
 | 
			
		||||
 | 
			
		||||
    private FrameBuffer backFrameBuffer;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new GuiHandler.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     * @param guiNode the GUI node
 | 
			
		||||
     */
 | 
			
		||||
    public GuiHandler(MdgaApp app, Node guiNode) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.ownColor = ownColor;
 | 
			
		||||
@@ -34,6 +43,11 @@ public GuiHandler(MdgaApp app, Node guiNode) {
 | 
			
		||||
        actionTextHandler = new ActionTextHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the GUI handler with the player's color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownColor the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void init(Color ownColor) {
 | 
			
		||||
        cardLayerHandler.init();
 | 
			
		||||
        playerNameHandler.show();
 | 
			
		||||
@@ -41,59 +55,108 @@ public void init(Color ownColor) {
 | 
			
		||||
        app.getViewPort().setOutputFrameBuffer(backFrameBuffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shuts down the GUI handler.
 | 
			
		||||
     */
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        cardLayerHandler.shutdown();
 | 
			
		||||
        app.getViewPort().setOutputFrameBuffer(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rolls the dice and handles the result.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rollNum the number rolled
 | 
			
		||||
     * @param mult the multiplier
 | 
			
		||||
     */
 | 
			
		||||
    public void rollDice(int rollNum, int mult) {
 | 
			
		||||
        cardLayerHandler.rollDice(rollNum, ()->{
 | 
			
		||||
            if(mult == -1) actionTextHandler.ownDice(rollNum);
 | 
			
		||||
        cardLayerHandler.rollDice(rollNum, () -> {
 | 
			
		||||
            if (mult == -1) actionTextHandler.ownDice(rollNum);
 | 
			
		||||
            else actionTextHandler.ownDiceMult(rollNum, mult);
 | 
			
		||||
            hideDice();
 | 
			
		||||
            app.getModelSynchronize().animationEnd();
 | 
			
		||||
            app.getModelSynchronize().animationEnd();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the rolled dice with a multiplier for a specific player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rollNum the number rolled
 | 
			
		||||
     * @param mult the multiplier
 | 
			
		||||
     * @param color the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void showRolledDiceMult(int rollNum, int mult, Color color) {
 | 
			
		||||
        String name = playerNameHandler.getName(color);
 | 
			
		||||
        if(mult == -1) actionTextHandler.diceNum(rollNum, name, color);
 | 
			
		||||
        if (mult == -1) actionTextHandler.diceNum(rollNum, name, color);
 | 
			
		||||
        else actionTextHandler.diceNumMult(rollNum, mult, name, color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the rolled dice for a specific player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rollNum the number rolled
 | 
			
		||||
     * @param color the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void showRolledDice(int rollNum, Color color) {
 | 
			
		||||
        actionTextHandler.diceNum(rollNum, playerNameHandler.getName(color), color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays the dice on the screen.
 | 
			
		||||
     */
 | 
			
		||||
    public void showDice() {
 | 
			
		||||
        cardLayerHandler.showDice();
 | 
			
		||||
        actionTextHandler.diceNow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the dice from the screen.
 | 
			
		||||
     */
 | 
			
		||||
    public void hideDice() {
 | 
			
		||||
        cardLayerHandler.hideDice();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //add own handCard
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a card to the player's hand.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the card to add
 | 
			
		||||
     */
 | 
			
		||||
    public void addCardOwn(BonusCard card) {
 | 
			
		||||
        cardLayerHandler.addCard(card);
 | 
			
		||||
        playerNameHandler.addCard(ownColor);
 | 
			
		||||
        actionTextHandler.drawCardOwn(ownColor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void playCardOwn(BonusCard card){
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays a card from the player's hand.
 | 
			
		||||
     *
 | 
			
		||||
     * @param card the card to play
 | 
			
		||||
     */
 | 
			
		||||
    public void playCardOwn(BonusCard card) {
 | 
			
		||||
        getEffectByCard(card);
 | 
			
		||||
        cardLayerHandler.removeCard(card);
 | 
			
		||||
        playerNameHandler.removeCard(ownColor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays a card from an enemy player's hand.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the enemy player's color
 | 
			
		||||
     * @param card the card to play
 | 
			
		||||
     */
 | 
			
		||||
    public void playCardEnemy(Color color, BonusCard card) {
 | 
			
		||||
        getEffectByCard(card);
 | 
			
		||||
        playerNameHandler.removeCard(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void getEffectByCard(BonusCard bonus){
 | 
			
		||||
        switch(bonus){
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the effect of a card and applies it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bonus the card to get the effect from
 | 
			
		||||
     */
 | 
			
		||||
    private void getEffectByCard(BonusCard bonus) {
 | 
			
		||||
        switch (bonus) {
 | 
			
		||||
            case SWAP -> swap();
 | 
			
		||||
            case TURBO -> turbo();
 | 
			
		||||
            case SHIELD -> shield();
 | 
			
		||||
@@ -101,30 +164,64 @@ private void getEffectByCard(BonusCard bonus){
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears all selectable cards.
 | 
			
		||||
     */
 | 
			
		||||
    public void clearSelectableCards() {
 | 
			
		||||
        cardLayerHandler.clearSelectableCards();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the selectable cards.
 | 
			
		||||
     *
 | 
			
		||||
     * @param select the list of selectable cards
 | 
			
		||||
     */
 | 
			
		||||
    public void setSelectableCards(List<BonusCard> select) {
 | 
			
		||||
        cardLayerHandler.setSelectableCards(select);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects a card.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cardControl the card control to select
 | 
			
		||||
     */
 | 
			
		||||
    public void selectCard(CardControl cardControl) {
 | 
			
		||||
        cardLayerHandler.selectCard(cardControl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the camera for the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the card layer camera
 | 
			
		||||
     */
 | 
			
		||||
    public Camera getCardLayerCamera() {
 | 
			
		||||
        return cardLayerHandler.getCardLayerCamera();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Node getCardLayerRootNode(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the root node for the card layer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the card layer root node
 | 
			
		||||
     */
 | 
			
		||||
    public Node getCardLayerRootNode() {
 | 
			
		||||
        return cardLayerHandler.getCardLayer().getRootNode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a player to the game.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the player's color
 | 
			
		||||
     * @param name the player's name
 | 
			
		||||
     */
 | 
			
		||||
    public void addPlayer(Color color, String name) {
 | 
			
		||||
        playerNameHandler.addPlayer(color, name, color == ownColor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the active player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the active player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void setActivePlayer(Color color) {
 | 
			
		||||
        playerNameHandler.setActivePlayer(color);
 | 
			
		||||
 | 
			
		||||
@@ -132,40 +229,63 @@ public void setActivePlayer(Color color) {
 | 
			
		||||
        else actionTextHandler.activePlayer(playerNameHandler.getName(color), color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void shield(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies the shield effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void shield() {
 | 
			
		||||
        cardLayerHandler.shield();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void swap(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies the swap effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void swap() {
 | 
			
		||||
        cardLayerHandler.swap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void turbo(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Applies the turbo effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void turbo() {
 | 
			
		||||
        cardLayerHandler.turbo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hideText(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the action text.
 | 
			
		||||
     */
 | 
			
		||||
    public void hideText() {
 | 
			
		||||
        actionTextHandler.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //addCard Enemy (DrawCardNotification)
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws a card for an enemy player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the enemy player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void drawCard(Color color) {
 | 
			
		||||
        //Color != ownColor
 | 
			
		||||
        actionTextHandler.drawCard(playerNameHandler.getName(color), color);
 | 
			
		||||
        playerNameHandler.addCard(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void finish(Color color){
 | 
			
		||||
        if(ownColor == color) actionTextHandler.finishTextOwn(color);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays the finish text for a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void finish(Color color) {
 | 
			
		||||
        if (ownColor == color) actionTextHandler.finishTextOwn(color);
 | 
			
		||||
        else actionTextHandler.finishText(playerNameHandler.getName(color), color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void rollRankingResult(Color color, int eye){
 | 
			
		||||
        if(ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays the ranking result for a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the player's color
 | 
			
		||||
     * @param eye the ranking result
 | 
			
		||||
     */
 | 
			
		||||
    public void rollRankingResult(Color color, int eye) {
 | 
			
		||||
        if (ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
 | 
			
		||||
        else actionTextHandler.rollRankingResult(playerNameHandler.getName(color), color, eye);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,20 @@
 | 
			
		||||
import com.jme3.font.BitmapFont;
 | 
			
		||||
import com.jme3.font.BitmapText;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.system.AppSettings;
 | 
			
		||||
import com.jme3.ui.Picture;
 | 
			
		||||
import pp.mdga.game.BonusCard;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Vector;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles the display and management of player names and their associated data.
 | 
			
		||||
 */
 | 
			
		||||
public class PlayerNameHandler {
 | 
			
		||||
    private final BitmapFont playerFont;
 | 
			
		||||
    private final Node playerNameNode;
 | 
			
		||||
@@ -40,7 +40,14 @@ public class PlayerNameHandler {
 | 
			
		||||
 | 
			
		||||
    private final Node guiNode;
 | 
			
		||||
 | 
			
		||||
    public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a PlayerNameHandler.
 | 
			
		||||
     *
 | 
			
		||||
     * @param guiNode the GUI node to attach player names to
 | 
			
		||||
     * @param assetManager the asset manager to load resources
 | 
			
		||||
     * @param appSettings the application settings
 | 
			
		||||
     */
 | 
			
		||||
    public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
 | 
			
		||||
        this.guiNode = guiNode;
 | 
			
		||||
 | 
			
		||||
        playerFont = assetManager.loadFont("Fonts/Gunplay.fnt");
 | 
			
		||||
@@ -52,120 +59,187 @@ public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings ap
 | 
			
		||||
        this.assetManager = assetManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the player names on the GUI.
 | 
			
		||||
     */
 | 
			
		||||
    public void show() {
 | 
			
		||||
        guiNode.attachChild(playerNameNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the player names from the GUI.
 | 
			
		||||
     */
 | 
			
		||||
    public void hide() {
 | 
			
		||||
        guiNode.detachChild(playerNameNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void drawPlayers(){
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws the player names and their associated data on the GUI.
 | 
			
		||||
     */
 | 
			
		||||
    private void drawPlayers() {
 | 
			
		||||
        playerNameNode.detachAllChildren();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for(int i = 0; i < playerOrder.size(); i++) {
 | 
			
		||||
        for (int i = 0; i < playerOrder.size(); i++) {
 | 
			
		||||
            Color color = playerOrder.get(i);
 | 
			
		||||
            if(!colorNameMap.containsKey(color)) throw new RuntimeException(color + " isn't mapped to a name");
 | 
			
		||||
            if (!colorNameMap.containsKey(color)) throw new RuntimeException(color + " isn't mapped to a name");
 | 
			
		||||
 | 
			
		||||
            Node nameParent = new Node("nameParent");
 | 
			
		||||
            nameParent.attachChild(createColor(color));
 | 
			
		||||
            BitmapText name = createName(colorNameMap.get(color), i == 0, color == ownColor);
 | 
			
		||||
            nameParent.attachChild(name);
 | 
			
		||||
            if(colorCardMap.getOrDefault(color, 0) > 0){
 | 
			
		||||
            if (colorCardMap.getOrDefault(color, 0) > 0) {
 | 
			
		||||
                Picture pic = createHandCard(name.getLineWidth());
 | 
			
		||||
                nameParent.attachChild(pic);
 | 
			
		||||
                nameParent.attachChild(createCardNum(colorCardMap.get(color), pic.getWidth(), pic.getLocalTranslation().getX()));
 | 
			
		||||
            }
 | 
			
		||||
            nameParent.setLocalTranslation(50,appSettings.getWindowHeight()-PADDING_TOP- MARGIN_NAMES *i,0);
 | 
			
		||||
            nameParent.setLocalTranslation(50, appSettings.getWindowHeight() - PADDING_TOP - MARGIN_NAMES * i, 0);
 | 
			
		||||
            playerNameNode.attachChild(nameParent);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Spatial createCardNum(int num, float lastWidth, float lastX ) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a BitmapText object to display the number of cards a player has.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num the number of cards
 | 
			
		||||
     * @param lastWidth the width of the last element
 | 
			
		||||
     * @param lastX the x position of the last element
 | 
			
		||||
     * @return a BitmapText object displaying the number of cards
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createCardNum(int num, float lastWidth, float lastX) {
 | 
			
		||||
        BitmapText hudText = new BitmapText(playerFont);
 | 
			
		||||
        //renderedSize = 45
 | 
			
		||||
        hudText.setSize(TEXT_SIZE);
 | 
			
		||||
        hudText.setColor(NORMAL_COLOR);
 | 
			
		||||
        hudText.setText(String.valueOf(num));
 | 
			
		||||
        hudText.setLocalTranslation(lastX + lastWidth + 20,hudText.getHeight()/2, 0);
 | 
			
		||||
        hudText.setLocalTranslation(lastX + lastWidth + 20, hudText.getHeight() / 2, 0);
 | 
			
		||||
        return hudText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Picture object to display a hand card image.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width the width of the previous element
 | 
			
		||||
     * @return a Picture object displaying a hand card image
 | 
			
		||||
     */
 | 
			
		||||
    private Picture createHandCard(float width) {
 | 
			
		||||
        Picture pic = new Picture("HUD Picture");
 | 
			
		||||
        pic.setImage(assetManager, "./Images/handcard.png", true);
 | 
			
		||||
        pic.setWidth(IMAGE_SIZE);
 | 
			
		||||
        pic.setHeight(IMAGE_SIZE);
 | 
			
		||||
        pic.setPosition(-pic.getWidth()/2 + width + PADDING_LEFT * 2 ,-pic.getHeight()/2);
 | 
			
		||||
        pic.setPosition(-pic.getWidth() / 2 + width + PADDING_LEFT * 2, -pic.getHeight() / 2);
 | 
			
		||||
        return pic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String imagePath(Color color){
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the image path for a given color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color to get the image path for
 | 
			
		||||
     * @return the image path for the given color
 | 
			
		||||
     */
 | 
			
		||||
    private String imagePath(Color color) {
 | 
			
		||||
        String root = "./Images/name_pictures/";
 | 
			
		||||
        return switch(color){
 | 
			
		||||
            case ARMY -> root+"HEER_IMAGE.png";
 | 
			
		||||
            case NAVY -> root+"MARINE_IMAGE.png";
 | 
			
		||||
            case CYBER -> root+"CIR_IMAGE.png";
 | 
			
		||||
            case AIRFORCE -> root+"LW_IMAGE.png";
 | 
			
		||||
        return switch (color) {
 | 
			
		||||
            case ARMY -> root + "HEER_IMAGE.png";
 | 
			
		||||
            case NAVY -> root + "MARINE_IMAGE.png";
 | 
			
		||||
            case CYBER -> root + "CIR_IMAGE.png";
 | 
			
		||||
            case AIRFORCE -> root + "LW_IMAGE.png";
 | 
			
		||||
            default -> throw new RuntimeException("None is not valid");
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Picture object to display a color image.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color to create the image for
 | 
			
		||||
     * @return a Picture object displaying the color image
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createColor(Color color) {
 | 
			
		||||
        Picture pic = new Picture("HUD Picture");
 | 
			
		||||
        pic.setImage(assetManager, imagePath(color), true);
 | 
			
		||||
        pic.setWidth(IMAGE_SIZE);
 | 
			
		||||
        pic.setHeight(IMAGE_SIZE);
 | 
			
		||||
        pic.setPosition(-pic.getWidth()/2,-pic.getHeight()/2);
 | 
			
		||||
        pic.setPosition(-pic.getWidth() / 2, -pic.getHeight() / 2);
 | 
			
		||||
        return pic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private BitmapText createName(String name, boolean first, boolean own){
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a BitmapText object to display a player's name.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name the player's name
 | 
			
		||||
     * @param first whether the player is the first in the list
 | 
			
		||||
     * @param own whether the player is the current user
 | 
			
		||||
     * @return a BitmapText object displaying the player's name
 | 
			
		||||
     */
 | 
			
		||||
    private BitmapText createName(String name, boolean first, boolean own) {
 | 
			
		||||
        BitmapText hudText = new BitmapText(playerFont);
 | 
			
		||||
        //renderedSize = 45
 | 
			
		||||
        hudText.setSize(TEXT_SIZE);
 | 
			
		||||
        hudText.setColor(first ? ACTIVE_COLOR : own ? OWN_COLOR : NORMAL_COLOR);
 | 
			
		||||
        hudText.setText(name);
 | 
			
		||||
        hudText.setLocalTranslation(PADDING_LEFT,hudText.getHeight()/2, 0);
 | 
			
		||||
        hudText.setText(own ? name + " (Du)" : name);
 | 
			
		||||
        hudText.setLocalTranslation(PADDING_LEFT, hudText.getHeight() / 2, 0);
 | 
			
		||||
        return hudText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addPlayer(Color color, String name, boolean own){
 | 
			
		||||
        if(own) ownColor = color;
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a player to the handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color associated with the player
 | 
			
		||||
     * @param name the name of the player
 | 
			
		||||
     * @param own whether the player is the current user
 | 
			
		||||
     */
 | 
			
		||||
    public void addPlayer(Color color, String name, boolean own) {
 | 
			
		||||
        if (own) ownColor = color;
 | 
			
		||||
        colorNameMap.put(color, name);
 | 
			
		||||
        playerOrder.add(color);
 | 
			
		||||
        drawPlayers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the active player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color associated with the active player
 | 
			
		||||
     */
 | 
			
		||||
    public void setActivePlayer(Color color) {
 | 
			
		||||
        Color lastFirst = playerOrder.remove(0);
 | 
			
		||||
        if (playerOrder.get(0) == color) return;
 | 
			
		||||
        Color oldFirst = playerOrder.remove(0);
 | 
			
		||||
        playerOrder.remove(color);
 | 
			
		||||
        playerOrder.add(0, color);
 | 
			
		||||
        playerOrder.add(lastFirst);
 | 
			
		||||
        playerOrder.add(oldFirst);
 | 
			
		||||
 | 
			
		||||
        drawPlayers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getName(Color color){
 | 
			
		||||
        if(!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the name of a player by their color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color associated with the player
 | 
			
		||||
     * @return the name of the player
 | 
			
		||||
     */
 | 
			
		||||
    public String getName(Color color) {
 | 
			
		||||
        if (!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
 | 
			
		||||
        return colorNameMap.get(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addCard(Color color){
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a card to a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color associated with the player
 | 
			
		||||
     */
 | 
			
		||||
    public void addCard(Color color) {
 | 
			
		||||
        colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) + 1);
 | 
			
		||||
        drawPlayers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removeCard(Color color){
 | 
			
		||||
        if(colorCardMap.containsKey(color)){
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a card from a player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color associated with the player
 | 
			
		||||
     */
 | 
			
		||||
    public void removeCard(Color color) {
 | 
			
		||||
        if (colorCardMap.containsKey(color)) {
 | 
			
		||||
            colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) - 1);
 | 
			
		||||
            if(colorCardMap.get(color) <= 0) colorCardMap.remove(color);
 | 
			
		||||
            if (colorCardMap.get(color) <= 0) colorCardMap.remove(color);
 | 
			
		||||
        }
 | 
			
		||||
        drawPlayers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,69 +11,129 @@
 | 
			
		||||
import com.jme3.texture.FrameBuffer;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OutlineFilter is a custom filter for rendering outlines around objects.
 | 
			
		||||
 */
 | 
			
		||||
public class OutlineFilter extends Filter {
 | 
			
		||||
 | 
			
		||||
	private OutlinePreFilter outlinePreFilter;
 | 
			
		||||
	private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
 | 
			
		||||
	private float outlineWidth = 1;
 | 
			
		||||
    private OutlinePreFilter outlinePreFilter;
 | 
			
		||||
    private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
 | 
			
		||||
    private float outlineWidth = 1;
 | 
			
		||||
 | 
			
		||||
	public OutlineFilter(OutlinePreFilter outlinePreFilter) {
 | 
			
		||||
		super("OutlineFilter");
 | 
			
		||||
		this.outlinePreFilter = outlinePreFilter;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an OutlineFilter with the specified OutlinePreFilter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlinePreFilter the pre-filter used for outlining
 | 
			
		||||
     */
 | 
			
		||||
    public OutlineFilter(OutlinePreFilter outlinePreFilter) {
 | 
			
		||||
        super("OutlineFilter");
 | 
			
		||||
        this.outlinePreFilter = outlinePreFilter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
 | 
			
		||||
		MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/Outline.j3md");
 | 
			
		||||
		material = new Material(matDef);
 | 
			
		||||
		material.setVector2("Resolution", new Vector2f(w, h));
 | 
			
		||||
		material.setColor("OutlineColor", outlineColor);
 | 
			
		||||
		material.setFloat("OutlineWidth", outlineWidth);
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the filter with the given parameters.
 | 
			
		||||
     *
 | 
			
		||||
     * @param assetManager the asset manager
 | 
			
		||||
     * @param renderManager the render manager
 | 
			
		||||
     * @param vp the viewport
 | 
			
		||||
     * @param w the width of the viewport
 | 
			
		||||
     * @param h the height of the viewport
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
 | 
			
		||||
        MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/Outline.j3md");
 | 
			
		||||
        material = new Material(matDef);
 | 
			
		||||
        material.setVector2("Resolution", new Vector2f(w, h));
 | 
			
		||||
        material.setColor("OutlineColor", outlineColor);
 | 
			
		||||
        material.setFloat("OutlineWidth", outlineWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void preFrame(float tpf) {
 | 
			
		||||
		super.preFrame(tpf);
 | 
			
		||||
		material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
 | 
			
		||||
    /**
 | 
			
		||||
     * Called before each frame is rendered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void preFrame(float tpf) {
 | 
			
		||||
        super.preFrame(tpf);
 | 
			
		||||
        material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
 | 
			
		||||
//		System.out.println("OutlineFilter.preFrame()");
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
 | 
			
		||||
		super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
 | 
			
		||||
    /**
 | 
			
		||||
     * Called after each frame is rendered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param renderManager the render manager
 | 
			
		||||
     * @param viewPort the viewport
 | 
			
		||||
     * @param prevFilterBuffer the previous filter buffer
 | 
			
		||||
     * @param sceneBuffer the scene buffer
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
 | 
			
		||||
        super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
 | 
			
		||||
//		material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
 | 
			
		||||
//		System.out.println("OutlineFilter.postFrame()");
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected Material getMaterial() {
 | 
			
		||||
		return material;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the material used by this filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the material
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Material getMaterial() {
 | 
			
		||||
        return material;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public ColorRGBA getOutlineColor() {
 | 
			
		||||
		return outlineColor;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the outline color.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the outline color
 | 
			
		||||
     */
 | 
			
		||||
    public ColorRGBA getOutlineColor() {
 | 
			
		||||
        return outlineColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public void setOutlineColor(ColorRGBA outlineColor) {
 | 
			
		||||
		this.outlineColor = outlineColor;
 | 
			
		||||
		if (material != null) {
 | 
			
		||||
			material.setColor("OutlineColor", outlineColor);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlineColor the new outline color
 | 
			
		||||
     */
 | 
			
		||||
    public void setOutlineColor(ColorRGBA outlineColor) {
 | 
			
		||||
        this.outlineColor = outlineColor;
 | 
			
		||||
        if (material != null) {
 | 
			
		||||
            material.setColor("OutlineColor", outlineColor);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public float getOutlineWidth() {
 | 
			
		||||
		return outlineWidth;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the outline width.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the outline width
 | 
			
		||||
     */
 | 
			
		||||
    public float getOutlineWidth() {
 | 
			
		||||
        return outlineWidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public void setOutlineWidth(float outlineWidth) {
 | 
			
		||||
		this.outlineWidth = outlineWidth;
 | 
			
		||||
		if (material != null) {
 | 
			
		||||
			material.setFloat("OutlineWidth", outlineWidth);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline width.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlineWidth the new outline width
 | 
			
		||||
     */
 | 
			
		||||
    public void setOutlineWidth(float outlineWidth) {
 | 
			
		||||
        this.outlineWidth = outlineWidth;
 | 
			
		||||
        if (material != null) {
 | 
			
		||||
            material.setFloat("OutlineWidth", outlineWidth);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public OutlinePreFilter getOutlinePreFilter() {
 | 
			
		||||
		return outlinePreFilter;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the OutlinePreFilter used by this filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the OutlinePreFilter
 | 
			
		||||
     */
 | 
			
		||||
    public OutlinePreFilter getOutlinePreFilter() {
 | 
			
		||||
        return outlinePreFilter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,56 +12,100 @@
 | 
			
		||||
import com.jme3.texture.Texture;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OutlinePreFilter is a custom filter used to apply an outline effect to objects.
 | 
			
		||||
 */
 | 
			
		||||
public class OutlinePreFilter extends Filter {
 | 
			
		||||
 | 
			
		||||
	private Pass normalPass;
 | 
			
		||||
	private RenderManager renderManager;
 | 
			
		||||
    private Pass normalPass;
 | 
			
		||||
    private RenderManager renderManager;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Creates a OutlinePreFilter
 | 
			
		||||
	 */
 | 
			
		||||
	public OutlinePreFilter() {
 | 
			
		||||
		super("OutlinePreFilter");
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an OutlinePreFilter.
 | 
			
		||||
     */
 | 
			
		||||
    public OutlinePreFilter() {
 | 
			
		||||
        super("OutlinePreFilter");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean isRequiresDepthTexture() {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that this filter requires a depth texture.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true, indicating that a depth texture is required.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected boolean isRequiresDepthTexture() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void postQueue(RenderQueue queue) {
 | 
			
		||||
		Renderer r = renderManager.getRenderer();
 | 
			
		||||
		r.setFrameBuffer(normalPass.getRenderFrameBuffer());
 | 
			
		||||
		renderManager.getRenderer().clearBuffers(true, true, false);
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Called after the render queue is processed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param queue the render queue.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void postQueue(RenderQueue queue) {
 | 
			
		||||
        Renderer r = renderManager.getRenderer();
 | 
			
		||||
        r.setFrameBuffer(normalPass.getRenderFrameBuffer());
 | 
			
		||||
        renderManager.getRenderer().clearBuffers(true, true, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
 | 
			
		||||
		super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
 | 
			
		||||
    /**
 | 
			
		||||
     * Called after the frame is rendered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param renderManager the render manager.
 | 
			
		||||
     * @param viewPort the viewport.
 | 
			
		||||
     * @param prevFilterBuffer the previous filter buffer.
 | 
			
		||||
     * @param sceneBuffer the scene buffer.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
 | 
			
		||||
        super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the material used by this filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the material.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Material getMaterial() {
 | 
			
		||||
        return material;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected Material getMaterial() {
 | 
			
		||||
		return material;
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the texture containing the outline.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the outline texture.
 | 
			
		||||
     */
 | 
			
		||||
    public Texture getOutlineTexture() {
 | 
			
		||||
        return normalPass.getRenderedTexture();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public Texture getOutlineTexture() {
 | 
			
		||||
		return normalPass.getRenderedTexture();
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manager the asset manager.
 | 
			
		||||
     * @param renderManager the render manager.
 | 
			
		||||
     * @param vp the viewport.
 | 
			
		||||
     * @param w the width.
 | 
			
		||||
     * @param h the height.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
 | 
			
		||||
        this.renderManager = renderManager;
 | 
			
		||||
        normalPass = new Pass();
 | 
			
		||||
        normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
 | 
			
		||||
        material = new Material(manager, "MatDefs/SelectObjectOutliner/OutlinePre.j3md");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
 | 
			
		||||
		this.renderManager = renderManager;
 | 
			
		||||
		normalPass = new Pass();
 | 
			
		||||
		normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
 | 
			
		||||
		material = new Material(manager, "MatDefs/SelectObjectOutliner/OutlinePre.j3md");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void cleanUpFilter(Renderer r) {
 | 
			
		||||
		normalPass.cleanup(r);
 | 
			
		||||
	}
 | 
			
		||||
    /**
 | 
			
		||||
     * Cleans up the filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param r the renderer.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void cleanUpFilter(Renderer r) {
 | 
			
		||||
        normalPass.cleanup(r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,17 +10,34 @@
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.texture.FrameBuffer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OutlineProFilter is a custom filter for rendering outlines around objects.
 | 
			
		||||
 */
 | 
			
		||||
public class OutlineProFilter extends Filter {
 | 
			
		||||
 | 
			
		||||
    private OutlinePreFilter outlinePreFilter;
 | 
			
		||||
    private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
 | 
			
		||||
    private float outlineWidth = 1;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an OutlineProFilter with the specified OutlinePreFilter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlinePreFilter the pre-filter used for outlining
 | 
			
		||||
     */
 | 
			
		||||
    public OutlineProFilter(OutlinePreFilter outlinePreFilter) {
 | 
			
		||||
        super("OutlineFilter");
 | 
			
		||||
        this.outlinePreFilter = outlinePreFilter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the filter with the given parameters.
 | 
			
		||||
     *
 | 
			
		||||
     * @param assetManager the asset manager
 | 
			
		||||
     * @param renderManager the render manager
 | 
			
		||||
     * @param vp the viewport
 | 
			
		||||
     * @param w the width of the viewport
 | 
			
		||||
     * @param h the height of the viewport
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
 | 
			
		||||
        MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/OutlinePro.j3md");
 | 
			
		||||
@@ -30,29 +47,54 @@ protected void initFilter(AssetManager assetManager, RenderManager renderManager
 | 
			
		||||
        material.setFloat("OutlineWidth", outlineWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called before rendering each frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void preFrame(float tpf) {
 | 
			
		||||
        super.preFrame(tpf);
 | 
			
		||||
        material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
 | 
			
		||||
//		System.out.println("OutlineFilter.preFrame()");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called after rendering each frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param renderManager the render manager
 | 
			
		||||
     * @param viewPort the viewport
 | 
			
		||||
     * @param prevFilterBuffer the previous filter buffer
 | 
			
		||||
     * @param sceneBuffer the scene buffer
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
 | 
			
		||||
        super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
 | 
			
		||||
//		material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
 | 
			
		||||
//		System.out.println("OutlineFilter.postFrame()");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the material used by this filter.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the material
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Material getMaterial() {
 | 
			
		||||
        return material;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the outline color.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the outline color
 | 
			
		||||
     */
 | 
			
		||||
    public ColorRGBA getOutlineColor() {
 | 
			
		||||
        return outlineColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlineColor the new outline color
 | 
			
		||||
     */
 | 
			
		||||
    public void setOutlineColor(ColorRGBA outlineColor) {
 | 
			
		||||
        this.outlineColor = outlineColor;
 | 
			
		||||
        if (material != null) {
 | 
			
		||||
@@ -60,10 +102,20 @@ public void setOutlineColor(ColorRGBA outlineColor) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the outline width.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the outline width
 | 
			
		||||
     */
 | 
			
		||||
    public float getOutlineWidth() {
 | 
			
		||||
        return outlineWidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the outline width.
 | 
			
		||||
     *
 | 
			
		||||
     * @param outlineWidth the new outline width
 | 
			
		||||
     */
 | 
			
		||||
    public void setOutlineWidth(float outlineWidth) {
 | 
			
		||||
        this.outlineWidth = outlineWidth;
 | 
			
		||||
        if (material != null) {
 | 
			
		||||
@@ -71,9 +123,13 @@ public void setOutlineWidth(float outlineWidth) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the OutlinePreFilter.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the OutlinePreFilter
 | 
			
		||||
     */
 | 
			
		||||
    public OutlinePreFilter getOutlinePreFilter() {
 | 
			
		||||
        return outlinePreFilter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,80 +9,113 @@
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is responsible for outlining selected objects in the scene.
 | 
			
		||||
 */
 | 
			
		||||
public class SelectObjectOutliner {
 | 
			
		||||
 | 
			
		||||
    private final FilterPostProcessor fpp;
 | 
			
		||||
    private final RenderManager renderManager;
 | 
			
		||||
    private final AssetManager assetManager;
 | 
			
		||||
    private final Camera cam;
 | 
			
		||||
    private final int width;
 | 
			
		||||
    private boolean selected;
 | 
			
		||||
    private ViewPort outlineViewport = null;
 | 
			
		||||
//    private OutlineFilter outlineFilter = null;
 | 
			
		||||
    //    private OutlineFilter outlineFilter = null;
 | 
			
		||||
    private OutlineProFilter outlineFilter = null;
 | 
			
		||||
    private final MdgaApp app;
 | 
			
		||||
 | 
			
		||||
    public SelectObjectOutliner(int width, FilterPostProcessor fpp, RenderManager renderManager, AssetManager assetManager, Camera cam, MdgaApp app) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for SelectObjectOutliner.
 | 
			
		||||
     *
 | 
			
		||||
     * @param fpp the FilterPostProcessor
 | 
			
		||||
     * @param renderManager the RenderManager
 | 
			
		||||
     * @param assetManager the AssetManager
 | 
			
		||||
     * @param cam the Camera
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public SelectObjectOutliner(FilterPostProcessor fpp, RenderManager renderManager, AssetManager assetManager, Camera cam, MdgaApp app) {
 | 
			
		||||
        this.selected = false;
 | 
			
		||||
        this.fpp = fpp;
 | 
			
		||||
        this.renderManager = renderManager;
 | 
			
		||||
        this.assetManager = assetManager;
 | 
			
		||||
        this.cam = cam;
 | 
			
		||||
        this.width = width;
 | 
			
		||||
        this.app = app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deselects the currently selected object, removing the outline effect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param model the Spatial model to deselect
 | 
			
		||||
     */
 | 
			
		||||
    public void deselect(Spatial model) {
 | 
			
		||||
        if(selected){
 | 
			
		||||
        if (selected) {
 | 
			
		||||
            selected = false;
 | 
			
		||||
            hideOutlineFilterEffect(model);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void select(Spatial model, ColorRGBA color) {
 | 
			
		||||
        if(!selected){
 | 
			
		||||
            selected = true;
 | 
			
		||||
            showOutlineFilterEffect(model, width, color);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
//    /**
 | 
			
		||||
//     * Selects an object and applies an outline effect.
 | 
			
		||||
//     *
 | 
			
		||||
//     * @param model the Spatial model to select
 | 
			
		||||
//     * @param color the ColorRGBA for the outline
 | 
			
		||||
//     */
 | 
			
		||||
//    public void select(Spatial model, ColorRGBA color) {
 | 
			
		||||
//        if (!selected) {
 | 
			
		||||
//            selected = true;
 | 
			
		||||
//            showOutlineFilterEffect(model, width, color);
 | 
			
		||||
//        }
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects an object and applies an outline effect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param model the Spatial model to select
 | 
			
		||||
     * @param color the ColorRGBA for the outline
 | 
			
		||||
     * @param width the width of the outline
 | 
			
		||||
     */
 | 
			
		||||
    public void select(Spatial model, ColorRGBA color, int width) {
 | 
			
		||||
        if(!selected){
 | 
			
		||||
        if (!selected) {
 | 
			
		||||
            selected = true;
 | 
			
		||||
            showOutlineFilterEffect(model, width, color);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the outline effect from the selected object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param model the Spatial model to hide the outline effect from
 | 
			
		||||
     */
 | 
			
		||||
    private void hideOutlineFilterEffect(Spatial model) {
 | 
			
		||||
//        app.enqueue(() -> {
 | 
			
		||||
            outlineFilter.setEnabled(false);
 | 
			
		||||
            outlineFilter.getOutlinePreFilter().setEnabled(false);
 | 
			
		||||
            fpp.removeFilter(outlineFilter);
 | 
			
		||||
            outlineViewport.detachScene(model);
 | 
			
		||||
            outlineViewport.clearProcessors();
 | 
			
		||||
            renderManager.removePreView(outlineViewport);
 | 
			
		||||
            outlineViewport = null;
 | 
			
		||||
//            return null;
 | 
			
		||||
//        });
 | 
			
		||||
        outlineFilter.setEnabled(false);
 | 
			
		||||
        outlineFilter.getOutlinePreFilter().setEnabled(false);
 | 
			
		||||
        fpp.removeFilter(outlineFilter);
 | 
			
		||||
        outlineViewport.detachScene(model);
 | 
			
		||||
        outlineViewport.clearProcessors();
 | 
			
		||||
        renderManager.removePreView(outlineViewport);
 | 
			
		||||
        outlineViewport = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the outline effect on the selected object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param model the Spatial model to show the outline effect on
 | 
			
		||||
     * @param width the width of the outline
 | 
			
		||||
     * @param color the ColorRGBA for the outline
 | 
			
		||||
     */
 | 
			
		||||
    private void showOutlineFilterEffect(Spatial model, int width, ColorRGBA color) {
 | 
			
		||||
//        app.enqueue(() -> {
 | 
			
		||||
            outlineViewport = renderManager.createPreView("outlineViewport", cam);
 | 
			
		||||
            FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
 | 
			
		||||
        outlineViewport = renderManager.createPreView("outlineViewport", cam);
 | 
			
		||||
        FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
 | 
			
		||||
 | 
			
		||||
            OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
 | 
			
		||||
            outlineFpp.addFilter(outlinePreFilter);
 | 
			
		||||
        OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
 | 
			
		||||
        outlineFpp.addFilter(outlinePreFilter);
 | 
			
		||||
        outlineViewport.attachScene(model);
 | 
			
		||||
        outlineViewport.addProcessor(outlineFpp);
 | 
			
		||||
 | 
			
		||||
            outlineViewport.attachScene(model);
 | 
			
		||||
            outlineViewport.addProcessor(outlineFpp);
 | 
			
		||||
        outlineFilter = new OutlineProFilter(outlinePreFilter);
 | 
			
		||||
        outlineFilter.setOutlineColor(color);
 | 
			
		||||
        outlineFilter.setOutlineWidth(width);
 | 
			
		||||
 | 
			
		||||
            outlineFilter = new OutlineProFilter(outlinePreFilter);
 | 
			
		||||
            outlineFilter.setOutlineColor(color);
 | 
			
		||||
            outlineFilter.setOutlineWidth(width);
 | 
			
		||||
 | 
			
		||||
            fpp.addFilter(outlineFilter);
 | 
			
		||||
//            return null;
 | 
			
		||||
//        });
 | 
			
		||||
        fpp.addFilter(outlineFilter);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,9 @@
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
import java.lang.reflect.InvocationTargetException;
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.BlockingQueue;
 | 
			
		||||
@@ -31,6 +34,7 @@ public class MdgaServer implements MessageListener<HostedConnection>, Connection
 | 
			
		||||
    private static int port;
 | 
			
		||||
    private final ServerGameLogic logic;
 | 
			
		||||
    private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
 | 
			
		||||
    private volatile boolean running = true;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        // Configure logging
 | 
			
		||||
@@ -55,21 +59,23 @@ public MdgaServer(int port) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * Main method to start the server.
 | 
			
		||||
     */
 | 
			
		||||
    public void run() {
 | 
			
		||||
        startServer();
 | 
			
		||||
        while (true)
 | 
			
		||||
        while (running) {
 | 
			
		||||
            processNextMessage();
 | 
			
		||||
        }
 | 
			
		||||
        shutdownServerResources();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * Starts the server and initializes listeners.
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        try {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server...");
 | 
			
		||||
            unlockSerializers();//NON-NLS
 | 
			
		||||
            myServer = Network.createServer(port);
 | 
			
		||||
            initializeSerializables();
 | 
			
		||||
            myServer.start();
 | 
			
		||||
@@ -77,20 +83,28 @@ private void startServer() {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
 | 
			
		||||
            exit(1);
 | 
			
		||||
            exit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Process the next message in the queue.
 | 
			
		||||
     */
 | 
			
		||||
    private void processNextMessage() {
 | 
			
		||||
        try {
 | 
			
		||||
            pendingMessages.take().process(logic);
 | 
			
		||||
            ReceivedMessage message = pendingMessages.take(); // This is a blocking call
 | 
			
		||||
            message.process(logic);
 | 
			
		||||
        } catch (InterruptedException ex) {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
 | 
			
		||||
            LOGGER.log(Level.INFO, "Server thread interrupted, shutting down..."); //NON-NLS
 | 
			
		||||
            Thread.currentThread().interrupt();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register serializable classes.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeSerializables() {
 | 
			
		||||
 | 
			
		||||
        Serializer.registerClass(UUID.class, new UUIDSerializer());
 | 
			
		||||
        Serializer.registerClass(AnimationEndMessage.class);
 | 
			
		||||
        Serializer.registerClass(ClientStartGameMessage.class);
 | 
			
		||||
@@ -108,6 +122,7 @@ private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(RequestPlayCardMessage.class);
 | 
			
		||||
        Serializer.registerClass(SelectCardMessage.class);
 | 
			
		||||
        Serializer.registerClass(SelectedPiecesMessage.class);
 | 
			
		||||
        Serializer.registerClass(SelectPieceMessage.class);
 | 
			
		||||
        Serializer.registerClass(SelectTSKMessage.class);
 | 
			
		||||
        Serializer.registerClass(ActivePlayerMessage.class);
 | 
			
		||||
        Serializer.registerClass(AnyPieceMessage.class);
 | 
			
		||||
@@ -138,6 +153,7 @@ private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(UpdateTSKMessage.class);
 | 
			
		||||
        Serializer.registerClass(WaitPieceMessage.class);
 | 
			
		||||
        Serializer.registerClass(IncorrectRequestMessage.class);
 | 
			
		||||
        Serializer.registerClass(SpectatorMessage.class);
 | 
			
		||||
        Serializer.registerClass(Player.class);
 | 
			
		||||
        Serializer.registerClass(Statistic.class);
 | 
			
		||||
        Serializer.registerClass(Board.class);
 | 
			
		||||
@@ -151,6 +167,8 @@ private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(SwapCard.class);
 | 
			
		||||
        Serializer.registerClass(ShieldCard.class);
 | 
			
		||||
        Serializer.registerClass(HiddenCard.class);
 | 
			
		||||
        Serializer.registerClass(ChoosePieceStateMessage.class);
 | 
			
		||||
        Serializer.registerClass(DrawCardMessage.class);
 | 
			
		||||
 | 
			
		||||
        Serializer.registerClass(Color.class, new EnumSerializer());
 | 
			
		||||
        Serializer.registerClass(PieceState.class, new EnumSerializer());
 | 
			
		||||
@@ -158,6 +176,9 @@ private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(BonusCard.class, new EnumSerializer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register listeners for the server.
 | 
			
		||||
     */
 | 
			
		||||
    private void registerListeners() {
 | 
			
		||||
        myServer.addMessageListener(this, AnimationEndMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, ClientStartGameMessage.class);
 | 
			
		||||
@@ -204,7 +225,7 @@ public void messageReceived(HostedConnection source, Message message) {
 | 
			
		||||
     * @param message as the received message as a Message object.
 | 
			
		||||
     */
 | 
			
		||||
    private void messageReceived(HostedConnection source, ClientMessage message) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
 | 
			
		||||
        System.out.println("server received from: " + source.getId() + " " + message.getClass().getName());
 | 
			
		||||
        pendingMessages.add(new ReceivedMessage(message, source.getId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -258,12 +279,12 @@ public void handleDisconnect(int id) {
 | 
			
		||||
        this.logic.received(new DisconnectedMessage(), id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void exit(int exitValue) { //NON-NLS
 | 
			
		||||
        LOGGER.log(Level.INFO, "close request"); //NON-NLS
 | 
			
		||||
        if (myServer != null)
 | 
			
		||||
            for (HostedConnection client : myServer.getConnections()) //NON-NLS
 | 
			
		||||
                if (client != null) client.close("Game over"); //NON-NLS
 | 
			
		||||
        System.exit(exitValue);
 | 
			
		||||
    /**
 | 
			
		||||
     * Stops the server thread gracefully.
 | 
			
		||||
     */
 | 
			
		||||
    public void exit() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Requesting server shutdown"); //NON-NLS
 | 
			
		||||
        running = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -279,10 +300,10 @@ public void send(int id, ServerMessage message) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        final HostedConnection connection = myServer.getConnection(id);
 | 
			
		||||
        if (connection != null)
 | 
			
		||||
        if (connection != null) {
 | 
			
		||||
            System.out.println("server sends to: " + id + " " + message.getClass().getName());
 | 
			
		||||
            connection.send(message);
 | 
			
		||||
        else
 | 
			
		||||
            LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
 | 
			
		||||
        } else LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -305,7 +326,11 @@ public void broadcast(ServerMessage message) {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void disconnectClient(int id) {
 | 
			
		||||
        this.myServer.getConnection(id).close("");
 | 
			
		||||
        if (myServer.getConnection(id) != null) {
 | 
			
		||||
            this.myServer.getConnection(id).close("");
 | 
			
		||||
        } else {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "no connection with id={0}", id); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -315,13 +340,33 @@ public void disconnectClient(int id) {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        for (HostedConnection client : this.myServer.getConnections()) {
 | 
			
		||||
            if (client != null) {
 | 
			
		||||
                client.close("Host closed the server.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.exit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        this.myServer.close();
 | 
			
		||||
        this.exit(0);
 | 
			
		||||
    /**
 | 
			
		||||
     * Gracefully shutdown server resources like connections and sockets.
 | 
			
		||||
     */
 | 
			
		||||
    private void shutdownServerResources() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Shutting down server resources"); //NON-NLS
 | 
			
		||||
        if (myServer != null && myServer.isRunning()) {
 | 
			
		||||
            for (HostedConnection client : myServer.getConnections()) {
 | 
			
		||||
                if (client != null) client.close("Server shutting down.");
 | 
			
		||||
            }
 | 
			
		||||
            myServer.close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method will be used to unlock the Serializer registry.
 | 
			
		||||
     */
 | 
			
		||||
    private static void unlockSerializers() {
 | 
			
		||||
        try {
 | 
			
		||||
            Field lockField = Serializer.class.getDeclaredField("locked");
 | 
			
		||||
            lockField.setAccessible(true);
 | 
			
		||||
            lockField.setBoolean(null, false); // Unlock the Serializer registry
 | 
			
		||||
        } catch (NoSuchFieldException | IllegalAccessException e) {
 | 
			
		||||
            System.err.println("Failed to unlock the Serializer registry: " + e.getMessage());
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,16 @@
 | 
			
		||||
import pp.mdga.message.client.ClientInterpreter;
 | 
			
		||||
import pp.mdga.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a message received from the server.
 | 
			
		||||
 */
 | 
			
		||||
public record ReceivedMessage(ClientMessage msg, int from) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes the received message using the specified client interpreter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the client interpreter to use for processing the message
 | 
			
		||||
     */
 | 
			
		||||
    void process(ClientInterpreter interpreter) {
 | 
			
		||||
        msg.accept(interpreter, from);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,52 @@
 | 
			
		||||
package pp.mdga.client.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializer;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializer;
 | 
			
		||||
/**
 | 
			
		||||
 * Serializer for UUID objects to be used with jME3 networking.
 | 
			
		||||
 */
 | 
			
		||||
public class UUIDSerializer extends Serializer {
 | 
			
		||||
 | 
			
		||||
public class UUIDSerializer extends Serializer
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Reads a UUID object from the given ByteBuffer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data the ByteBuffer containing the serialized UUID
 | 
			
		||||
     * @param c the class type of the object to be read
 | 
			
		||||
     * @param <T> the type of the object to be read
 | 
			
		||||
     * @return the deserialized UUID object, or null if the UUID is a placeholder
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException
 | 
			
		||||
{
 | 
			
		||||
    byte[] uuid = new byte[36];
 | 
			
		||||
    data.get(uuid);
 | 
			
		||||
    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
 | 
			
		||||
        byte[] uuid = new byte[36];
 | 
			
		||||
        data.get(uuid);
 | 
			
		||||
 | 
			
		||||
    return (T) UUID.fromString(new String(uuid));
 | 
			
		||||
}
 | 
			
		||||
        if (uuid.equals(new UUID(1, 1))) {
 | 
			
		||||
            return null;
 | 
			
		||||
        } else {
 | 
			
		||||
            return (T) UUID.fromString(new String(uuid));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a UUID object to the given ByteBuffer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param buffer the ByteBuffer to write the serialized UUID to
 | 
			
		||||
     * @param object the UUID object to be serialized
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeObject(ByteBuffer buffer, Object object) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
 | 
			
		||||
        UUID uuid = (UUID) object;
 | 
			
		||||
        buffer.put(uuid.toString().getBytes());
 | 
			
		||||
 | 
			
		||||
        if (uuid != null) {
 | 
			
		||||
            buffer.put(uuid.toString().getBytes());
 | 
			
		||||
        } else {
 | 
			
		||||
            buffer.put(new UUID(1, 1).toString().getBytes());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,13 @@
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * CeremonyView class handles the display and interaction of the ceremony view in the application.
 | 
			
		||||
 */
 | 
			
		||||
public class CeremonyView extends MdgaView {
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum representing the sub-states of the CeremonyView.
 | 
			
		||||
     */
 | 
			
		||||
    private enum SubState {
 | 
			
		||||
        AWARD_CEREMONY,
 | 
			
		||||
        STATISTICS,
 | 
			
		||||
@@ -39,6 +45,11 @@ private enum SubState {
 | 
			
		||||
 | 
			
		||||
    private AmbientLight ambient = new AmbientLight();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for CeremonyView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app The application instance.
 | 
			
		||||
     */
 | 
			
		||||
    public CeremonyView(MdgaApp app) {
 | 
			
		||||
        super(app);
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +63,9 @@ public CeremonyView(MdgaApp app) {
 | 
			
		||||
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when entering the CeremonyView.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnter() {
 | 
			
		||||
        rootNode.addLight(ambient);
 | 
			
		||||
@@ -98,12 +112,15 @@ public void onEnter() {
 | 
			
		||||
        enterSub(SubState.AWARD_CEREMONY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when leaving the CeremonyView.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLeave() {
 | 
			
		||||
        backButton.hide();
 | 
			
		||||
        continueButton.hide();
 | 
			
		||||
 | 
			
		||||
        if(null != background) {
 | 
			
		||||
        if (null != background) {
 | 
			
		||||
            guiNode.detachChild(background);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -116,18 +133,33 @@ public void onLeave() {
 | 
			
		||||
        rootNode.detachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when entering an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay The overlay being entered.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onEnterOverlay(Overlay overlay) {
 | 
			
		||||
        if(rootNode.hasChild(podest)) {
 | 
			
		||||
        if (rootNode.hasChild(podest)) {
 | 
			
		||||
            rootNode.detachChild(podest);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when leaving an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay The overlay being left.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onLeaveOverlay(Overlay overlay) {
 | 
			
		||||
        enterSub(state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called to update the CeremonyView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf Time per frame.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onUpdate(float tpf) {
 | 
			
		||||
        for (CeremonyButton c : ceremonyButtons) {
 | 
			
		||||
@@ -135,6 +167,9 @@ protected void onUpdate(float tpf) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the award ceremony sub-state.
 | 
			
		||||
     */
 | 
			
		||||
    private void awardCeremony() {
 | 
			
		||||
        continueButton.show();
 | 
			
		||||
 | 
			
		||||
@@ -145,6 +180,9 @@ private void awardCeremony() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the statistics sub-state.
 | 
			
		||||
     */
 | 
			
		||||
    private void statistics() {
 | 
			
		||||
        //background = createBackground("Images/b2.png");
 | 
			
		||||
        //guiNode.attachChild(background);
 | 
			
		||||
@@ -154,10 +192,15 @@ private void statistics() {
 | 
			
		||||
        ceremonyDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enters a sub-state of the CeremonyView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param state The sub-state to enter.
 | 
			
		||||
     */
 | 
			
		||||
    private void enterSub(SubState state) {
 | 
			
		||||
        this.state = state;
 | 
			
		||||
 | 
			
		||||
        if(rootNode.hasChild(podest)) {
 | 
			
		||||
        if (rootNode.hasChild(podest)) {
 | 
			
		||||
            rootNode.detachChild(podest);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -178,17 +221,23 @@ private void enterSub(SubState state) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the forward button action.
 | 
			
		||||
     */
 | 
			
		||||
    public void forward() {
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case AWARD_CEREMONY:
 | 
			
		||||
                enterSub(SubState.STATISTICS);
 | 
			
		||||
                break;
 | 
			
		||||
            case STATISTICS:
 | 
			
		||||
                app.getModelSynchronize().enter(MdgaState.MAIN);
 | 
			
		||||
                app.getModelSynchronize().next();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the back button action.
 | 
			
		||||
     */
 | 
			
		||||
    private void back() {
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case AWARD_CEREMONY:
 | 
			
		||||
@@ -200,17 +249,35 @@ private void back() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a participant to the ceremony.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color The color of the participant.
 | 
			
		||||
     * @param pos The position of the participant.
 | 
			
		||||
     * @param name The name of the participant.
 | 
			
		||||
     */
 | 
			
		||||
    public void addCeremonyParticipant(Color color, int pos, String name) {
 | 
			
		||||
        CeremonyButton button = new CeremonyButton(app, guiNode, rootNode, color, CeremonyButton.Pos.values()[pos - 1], name);
 | 
			
		||||
 | 
			
		||||
        ceremonyButtons.add(button);
 | 
			
		||||
 | 
			
		||||
        if(state.equals(SubState.AWARD_CEREMONY)) {
 | 
			
		||||
        if (state != null && state.equals(SubState.AWARD_CEREMONY)) {
 | 
			
		||||
            button.hide();
 | 
			
		||||
            button.show();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a row of statistics.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name The name of the row.
 | 
			
		||||
     * @param v1 Value 1.
 | 
			
		||||
     * @param v2 Value 2.
 | 
			
		||||
     * @param v3 Value 3.
 | 
			
		||||
     * @param v4 Value 4.
 | 
			
		||||
     * @param v5 Value 5.
 | 
			
		||||
     * @param v6 Value 6.
 | 
			
		||||
     */
 | 
			
		||||
    public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
 | 
			
		||||
        ceremonyDialog.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
 | 
			
		||||
 | 
			
		||||
@@ -218,6 +285,9 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
 | 
			
		||||
        ceremonyDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cleans up after the game.
 | 
			
		||||
     */
 | 
			
		||||
    public void afterGameCleanup() {
 | 
			
		||||
        ceremonyDialog.prepare();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,26 @@
 | 
			
		||||
 | 
			
		||||
import com.jme3.post.FilterPostProcessor;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.board.BoardHandler;
 | 
			
		||||
import pp.mdga.client.board.CameraHandler;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.button.ButtonLeft;
 | 
			
		||||
import pp.mdga.client.button.ButtonRight;
 | 
			
		||||
import pp.mdga.client.dialog.InterruptDialog;
 | 
			
		||||
import pp.mdga.client.gui.GuiHandler;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the view for the game.
 | 
			
		||||
 */
 | 
			
		||||
public class GameView extends MdgaView {
 | 
			
		||||
    private BoardHandler boardHandler;
 | 
			
		||||
    private CameraHandler camera;
 | 
			
		||||
    private GuiHandler guiHandler;
 | 
			
		||||
 | 
			
		||||
    private ButtonLeft leaveButton;
 | 
			
		||||
    private ButtonRight confirmButton;
 | 
			
		||||
    public ButtonRight confirmButton;
 | 
			
		||||
 | 
			
		||||
    private ButtonRight noPowerButton;
 | 
			
		||||
 | 
			
		||||
@@ -28,12 +31,19 @@ public class GameView extends MdgaView {
 | 
			
		||||
 | 
			
		||||
    private FilterPostProcessor fpp;
 | 
			
		||||
 | 
			
		||||
    public boolean needConfirm = false;
 | 
			
		||||
    public boolean needNoPower = false;
 | 
			
		||||
 | 
			
		||||
    private Node guiHandlerNode = new Node();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new GameView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     */
 | 
			
		||||
    public GameView(MdgaApp app) {
 | 
			
		||||
        super(app);
 | 
			
		||||
 | 
			
		||||
        setOwnColor(Color.AIRFORCE);
 | 
			
		||||
        leaveButton = new ButtonLeft(app, settingsNode, () -> app.getModelSynchronize().leave(), "Spiel verlassen", 1);
 | 
			
		||||
 | 
			
		||||
        confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
 | 
			
		||||
@@ -51,6 +61,9 @@ public GameView(MdgaApp app) {
 | 
			
		||||
        guiNode.attachChild(guiHandlerNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when entering the view.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnter() {
 | 
			
		||||
        camera.init(ownColor);
 | 
			
		||||
@@ -62,76 +75,140 @@ public void onEnter() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.START);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when leaving the view.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLeave() {
 | 
			
		||||
        boardHandler.shutdown();
 | 
			
		||||
        guiHandler.shutdown();
 | 
			
		||||
        camera.shutdown();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        confirmButton.hide();
 | 
			
		||||
        noPowerButton.hide();
 | 
			
		||||
 | 
			
		||||
        app.getViewPort().removeProcessor(fpp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called to update the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onUpdate(float tpf) {
 | 
			
		||||
        camera.update(app.getInputSynchronize().getScroll(), app.getInputSynchronize().getRotation());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when entering an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay being entered
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onEnterOverlay(Overlay overlay) {
 | 
			
		||||
        if(overlay == Overlay.SETTINGS) {
 | 
			
		||||
        if (overlay == Overlay.SETTINGS) {
 | 
			
		||||
            leaveButton.show();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when leaving an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay being left
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onLeaveOverlay(Overlay overlay) {
 | 
			
		||||
        if(overlay == Overlay.SETTINGS) {
 | 
			
		||||
        if (overlay == Overlay.SETTINGS) {
 | 
			
		||||
            leaveButton.hide();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves the game.
 | 
			
		||||
     */
 | 
			
		||||
    private void leaveGame() {
 | 
			
		||||
        app.getModelSynchronize().leave();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the board handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the board handler
 | 
			
		||||
     */
 | 
			
		||||
    public BoardHandler getBoardHandler() {
 | 
			
		||||
        return  boardHandler;
 | 
			
		||||
        return boardHandler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the GUI handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the GUI handler
 | 
			
		||||
     */
 | 
			
		||||
    public GuiHandler getGuiHandler() {
 | 
			
		||||
        return guiHandler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the player's color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownColor the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public void setOwnColor(Color ownColor) {
 | 
			
		||||
        this.ownColor = ownColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the player's color.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's color
 | 
			
		||||
     */
 | 
			
		||||
    public Color getOwnColor() {
 | 
			
		||||
        return ownColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the confirm button and hides the no power button.
 | 
			
		||||
     */
 | 
			
		||||
    public void needConfirm() {
 | 
			
		||||
        noPowerButton.hide();
 | 
			
		||||
        confirmButton.show();
 | 
			
		||||
 | 
			
		||||
        needConfirm = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the confirm button.
 | 
			
		||||
     */
 | 
			
		||||
    public void noConfirm() {
 | 
			
		||||
        confirmButton.hide();
 | 
			
		||||
 | 
			
		||||
        needConfirm = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void needNoPower() {
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the no power button and hides the confirm button.
 | 
			
		||||
     */
 | 
			
		||||
    public void showNoPower() {
 | 
			
		||||
        confirmButton.hide();
 | 
			
		||||
        noPowerButton.show();
 | 
			
		||||
 | 
			
		||||
        needNoPower = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void noNoPower() {
 | 
			
		||||
        noPowerButton.show();
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the no power button.
 | 
			
		||||
     */
 | 
			
		||||
    public void hideNoPower() {
 | 
			
		||||
        noPowerButton.hide();
 | 
			
		||||
        needNoPower = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enters the interrupt state with the specified color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color to set
 | 
			
		||||
     */
 | 
			
		||||
    public void enterInterrupt(Color color) {
 | 
			
		||||
        enterOverlay(Overlay.INTERRUPT);
 | 
			
		||||
 | 
			
		||||
@@ -144,6 +221,9 @@ public void enterInterrupt(Color color) {
 | 
			
		||||
        interruptDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves the interrupt state.
 | 
			
		||||
     */
 | 
			
		||||
    public void leaveInterrupt() {
 | 
			
		||||
        leaveOverlay(Overlay.INTERRUPT);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,22 +5,19 @@
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Quad;
 | 
			
		||||
import com.jme3.texture.Texture;
 | 
			
		||||
import com.jme3.util.SkyFactory;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.button.ButtonLeft;
 | 
			
		||||
import pp.mdga.client.button.ButtonRight;
 | 
			
		||||
import pp.mdga.client.button.LobbyButton;
 | 
			
		||||
import pp.mdga.client.button.SettingsButton;
 | 
			
		||||
import pp.mdga.game.Color;
 | 
			
		||||
import pp.mdga.message.client.StartGameMessage;
 | 
			
		||||
import pp.mdga.notification.GameNotification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the lobby view in the game.
 | 
			
		||||
 */
 | 
			
		||||
public class LobbyView extends MdgaView {
 | 
			
		||||
    private Geometry background;
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +36,11 @@ public class LobbyView extends MdgaView {
 | 
			
		||||
 | 
			
		||||
    private Color own = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new LobbyView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     */
 | 
			
		||||
    public LobbyView(MdgaApp app) {
 | 
			
		||||
        super(app);
 | 
			
		||||
 | 
			
		||||
@@ -54,6 +56,9 @@ public LobbyView(MdgaApp app) {
 | 
			
		||||
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when entering the lobby view.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnter() {
 | 
			
		||||
        app.getCamera().setParallelProjection(true);
 | 
			
		||||
@@ -64,7 +69,7 @@ public void onEnter() {
 | 
			
		||||
        leaveButton.show();
 | 
			
		||||
        readyButton.show();
 | 
			
		||||
 | 
			
		||||
        if(app.getGameLogic().isHost()) {
 | 
			
		||||
        if (app.getGameLogic().isHost()) {
 | 
			
		||||
            startButton.show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -98,6 +103,9 @@ public void onEnter() {
 | 
			
		||||
        rootNode.attachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when leaving the lobby view.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLeave() {
 | 
			
		||||
        leaveButton.hide();
 | 
			
		||||
@@ -114,10 +122,10 @@ public void onLeave() {
 | 
			
		||||
        app.getCamera().setParallelProjection(false);
 | 
			
		||||
 | 
			
		||||
        app.getCamera().setFrustumPerspective(
 | 
			
		||||
            45.0f,
 | 
			
		||||
            (float) app.getCamera().getWidth() / app.getCamera().getHeight(),
 | 
			
		||||
            0.1f,
 | 
			
		||||
            1000.0f
 | 
			
		||||
                45.0f,
 | 
			
		||||
                (float) app.getCamera().getWidth() / app.getCamera().getHeight(),
 | 
			
		||||
                0.1f,
 | 
			
		||||
                1000.0f
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        app.getCamera().setLocation(new Vector3f(0, 0, 10));
 | 
			
		||||
@@ -136,6 +144,11 @@ public void onLeave() {
 | 
			
		||||
        rootNode.detachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called on each frame update.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onUpdate(float tpf) {
 | 
			
		||||
        airforceButton.update(tpf);
 | 
			
		||||
@@ -146,27 +159,34 @@ protected void onUpdate(float tpf) {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onEnterOverlay(Overlay overlay) {
 | 
			
		||||
 | 
			
		||||
        // No implementation needed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onLeaveOverlay(Overlay overlay)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    protected void onLeaveOverlay(Overlay overlay) {
 | 
			
		||||
        // No implementation needed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the taken status of a color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color
 | 
			
		||||
     * @param isTaken whether the color is taken
 | 
			
		||||
     * @param isSelf whether the color is taken by the player
 | 
			
		||||
     * @param name the name of the player who took the color
 | 
			
		||||
     */
 | 
			
		||||
    public void setTaken(Color color, boolean isTaken, boolean isSelf, String name) {
 | 
			
		||||
        LobbyButton.Taken taken;
 | 
			
		||||
 | 
			
		||||
        if(isTaken) {
 | 
			
		||||
            if(isSelf) {
 | 
			
		||||
        if (isTaken) {
 | 
			
		||||
            if (isSelf) {
 | 
			
		||||
                own = color;
 | 
			
		||||
                taken = LobbyButton.Taken.SELF;
 | 
			
		||||
            } else {
 | 
			
		||||
                taken = LobbyButton.Taken.OTHER;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if(isSelf) {
 | 
			
		||||
            if (isSelf) {
 | 
			
		||||
                own = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -189,6 +209,12 @@ public void setTaken(Color color, boolean isTaken, boolean isSelf, String name)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the ready status of a color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color
 | 
			
		||||
     * @param isReady whether the color is ready
 | 
			
		||||
     */
 | 
			
		||||
    public void setReady(Color color, boolean isReady) {
 | 
			
		||||
        LobbyButton button = switch (color) {
 | 
			
		||||
            case CYBER -> cyberButton;
 | 
			
		||||
@@ -204,15 +230,20 @@ public void setReady(Color color, boolean isReady) {
 | 
			
		||||
            this.isReady = isReady;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!isReady) {
 | 
			
		||||
        if (!isReady) {
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.NOT_READY);
 | 
			
		||||
        } else {
 | 
			
		||||
            if(button.getTaken() != LobbyButton.Taken.SELF) {
 | 
			
		||||
            if (button.getTaken() != LobbyButton.Taken.SELF) {
 | 
			
		||||
                app.getAcousticHandler().playSound(MdgaSound.OTHER_READY);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles the task selection for a color.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the color
 | 
			
		||||
     */
 | 
			
		||||
    private void toggleTsk(Color color) {
 | 
			
		||||
        LobbyButton.Taken taken = LobbyButton.Taken.NOT;
 | 
			
		||||
 | 
			
		||||
@@ -231,7 +262,7 @@ private void toggleTsk(Color color) {
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(isReady) {
 | 
			
		||||
        if (isReady) {
 | 
			
		||||
            setReady(own, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -248,19 +279,25 @@ private void toggleTsk(Color color) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the player as ready.
 | 
			
		||||
     */
 | 
			
		||||
    public void ready() {
 | 
			
		||||
        if(own == null) {
 | 
			
		||||
        if (own == null) {
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!isReady) {
 | 
			
		||||
        if (!isReady) {
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.SELF_READY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        app.getModelSynchronize().setReady(!isReady);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves the lobby.
 | 
			
		||||
     */
 | 
			
		||||
    private void leaveLobby() {
 | 
			
		||||
        app.getModelSynchronize().leave();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,13 @@
 | 
			
		||||
import pp.mdga.client.dialog.JoinDialog;
 | 
			
		||||
import pp.mdga.client.dialog.StartDialog;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * MainView class that extends MdgaView and manages the main view of the application.
 | 
			
		||||
 */
 | 
			
		||||
public class MainView extends MdgaView {
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum representing the different sub-states of the MainView.
 | 
			
		||||
     */
 | 
			
		||||
    private enum SubState {
 | 
			
		||||
        HOST,
 | 
			
		||||
        JOIN,
 | 
			
		||||
@@ -22,6 +28,11 @@ private enum SubState {
 | 
			
		||||
    private JoinDialog joinDialog;
 | 
			
		||||
    private HostDialog hostDialog;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for MainView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the MdgaApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public MainView(MdgaApp app) {
 | 
			
		||||
        super(app);
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +43,9 @@ public MainView(MdgaApp app) {
 | 
			
		||||
        background = createBackground("Images/startmenu.png");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the view is entered.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnter() {
 | 
			
		||||
        app.setup();
 | 
			
		||||
@@ -41,6 +55,9 @@ public void onEnter() {
 | 
			
		||||
        enterSub(SubState.MAIN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the view is left.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLeave() {
 | 
			
		||||
        startDialog.hide();
 | 
			
		||||
@@ -50,6 +67,11 @@ public void onLeave() {
 | 
			
		||||
        guiNode.detachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called to update the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onUpdate(float tpf) {
 | 
			
		||||
        startDialog.update();
 | 
			
		||||
@@ -57,18 +79,31 @@ public void onUpdate(float tpf) {
 | 
			
		||||
        hostDialog.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when an overlay is entered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay being entered
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onEnterOverlay(Overlay overlay) {
 | 
			
		||||
        guiNode.detachChild(background);
 | 
			
		||||
        settingsNode.attachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when an overlay is left.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay being left
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onLeaveOverlay(Overlay overlay) {
 | 
			
		||||
        settingsNode.detachChild(background);
 | 
			
		||||
        guiNode.attachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the join menu.
 | 
			
		||||
     */
 | 
			
		||||
    private void joinMenu() {
 | 
			
		||||
        startDialog.hide();
 | 
			
		||||
        hostDialog.hide();
 | 
			
		||||
@@ -76,6 +111,9 @@ private void joinMenu() {
 | 
			
		||||
        joinDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the host menu.
 | 
			
		||||
     */
 | 
			
		||||
    private void hostMenu() {
 | 
			
		||||
        startDialog.hide();
 | 
			
		||||
        joinDialog.hide();
 | 
			
		||||
@@ -83,6 +121,9 @@ private void hostMenu() {
 | 
			
		||||
        hostDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the main menu.
 | 
			
		||||
     */
 | 
			
		||||
    private void mainMenu() {
 | 
			
		||||
        joinDialog.hide();
 | 
			
		||||
        hostDialog.hide();
 | 
			
		||||
@@ -90,6 +131,9 @@ private void mainMenu() {
 | 
			
		||||
        startDialog.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to host a game.
 | 
			
		||||
     */
 | 
			
		||||
    private void tryHost() {
 | 
			
		||||
        int port = 0;
 | 
			
		||||
        String text = hostDialog.getPort();
 | 
			
		||||
@@ -98,7 +142,7 @@ private void tryHost() {
 | 
			
		||||
        try {
 | 
			
		||||
            port = Integer.parseInt(text);
 | 
			
		||||
 | 
			
		||||
            if(port >= 1 && port <= 65535) {
 | 
			
		||||
            if (port >= 1 && port <= 65535) {
 | 
			
		||||
                app.getModelSynchronize().setName(startDialog.getName());
 | 
			
		||||
                hostDialog.hostServer();
 | 
			
		||||
                try {
 | 
			
		||||
@@ -117,6 +161,9 @@ private void tryHost() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to join a game.
 | 
			
		||||
     */
 | 
			
		||||
    private void tryJoin() {
 | 
			
		||||
        int port = 0;
 | 
			
		||||
        String ip = joinDialog.getIpt();
 | 
			
		||||
@@ -146,20 +193,31 @@ private void tryJoin() {
 | 
			
		||||
        app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates an IP address.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip the IP address to validate
 | 
			
		||||
     * @return true if the IP address is valid, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isValidIpAddress(String ip) {
 | 
			
		||||
        String ipRegex =
 | 
			
		||||
            "^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$";
 | 
			
		||||
                "^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                        "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                        "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
 | 
			
		||||
                        "(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$";
 | 
			
		||||
 | 
			
		||||
        return ip != null && ip.matches(ipRegex);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enters a sub-state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param state the sub-state to enter
 | 
			
		||||
     */
 | 
			
		||||
    private void enterSub(SubState state) {
 | 
			
		||||
        this.state = state;
 | 
			
		||||
 | 
			
		||||
        if(null != background) {
 | 
			
		||||
        if (null != background) {
 | 
			
		||||
            rootNode.detachChild(background);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -176,6 +234,9 @@ private void enterSub(SubState state) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the state based on the current sub-state.
 | 
			
		||||
     */
 | 
			
		||||
    public void forward() {
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case HOST:
 | 
			
		||||
@@ -189,6 +250,11 @@ public void forward() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the state based on the current sub-state and a boolean flag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param host a boolean flag indicating whether to host or join
 | 
			
		||||
     */
 | 
			
		||||
    public void forward(boolean host) {
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case HOST:
 | 
			
		||||
@@ -198,7 +264,7 @@ public void forward(boolean host) {
 | 
			
		||||
                tryJoin();
 | 
			
		||||
                break;
 | 
			
		||||
            case MAIN:
 | 
			
		||||
                if(host) {
 | 
			
		||||
                if (host) {
 | 
			
		||||
                    enterSub(SubState.HOST);
 | 
			
		||||
                    //TODO playSound
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -209,6 +275,9 @@ public void forward(boolean host) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Goes back to the main menu from the current sub-state.
 | 
			
		||||
     */
 | 
			
		||||
    public void back() {
 | 
			
		||||
        switch (state) {
 | 
			
		||||
            case HOST:
 | 
			
		||||
@@ -225,12 +294,21 @@ public void back() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the JoinDialog instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the JoinDialog instance
 | 
			
		||||
     */
 | 
			
		||||
    public JoinDialog getJoinDialog() {
 | 
			
		||||
        return joinDialog;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the HostDialog instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the HostDialog instance
 | 
			
		||||
     */
 | 
			
		||||
    public HostDialog getHostDialog() {
 | 
			
		||||
        return hostDialog;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,12 +11,20 @@
 | 
			
		||||
import com.jme3.texture.Texture;
 | 
			
		||||
import pp.mdga.client.MdgaApp;
 | 
			
		||||
import pp.mdga.client.acoustic.MdgaSound;
 | 
			
		||||
import pp.mdga.client.button.*;
 | 
			
		||||
import pp.mdga.client.button.AbstractButton;
 | 
			
		||||
import pp.mdga.client.button.LabelButton;
 | 
			
		||||
import pp.mdga.client.button.SettingsButton;
 | 
			
		||||
import pp.mdga.client.dialog.AudioSettingsDialog;
 | 
			
		||||
import pp.mdga.client.dialog.SettingsDialog;
 | 
			
		||||
import pp.mdga.client.dialog.VideoSettingsDialog;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract class representing a view in the MDGA application.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class MdgaView {
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum representing different types of overlays.
 | 
			
		||||
     */
 | 
			
		||||
    public enum Overlay {
 | 
			
		||||
        INTERRUPT,
 | 
			
		||||
        SETTINGS,
 | 
			
		||||
@@ -38,6 +46,11 @@ public enum Overlay {
 | 
			
		||||
 | 
			
		||||
    private int settingsDepth = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for MdgaView.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the application instance
 | 
			
		||||
     */
 | 
			
		||||
    public MdgaView(MdgaApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        settingsButton = new SettingsButton(app, guiNode, this::enterSettings);
 | 
			
		||||
@@ -47,6 +60,9 @@ public MdgaView(MdgaApp app) {
 | 
			
		||||
        audioSettingsDialog = new AudioSettingsDialog(app, settingsNode, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to enter the view.
 | 
			
		||||
     */
 | 
			
		||||
    public void enter() {
 | 
			
		||||
        app.getRootNode().attachChild(rootNode);
 | 
			
		||||
        app.getGuiNode().attachChild(guiNode);
 | 
			
		||||
@@ -56,10 +72,12 @@ public void enter() {
 | 
			
		||||
        onEnter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave the view.
 | 
			
		||||
     */
 | 
			
		||||
    public void leave() {
 | 
			
		||||
        onLeave();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        settingsButton.hide();
 | 
			
		||||
 | 
			
		||||
        while (settingsDepth > 0) {
 | 
			
		||||
@@ -70,18 +88,33 @@ public void leave() {
 | 
			
		||||
        app.getGuiNode().detachChild(guiNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to enter an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay to enter
 | 
			
		||||
     */
 | 
			
		||||
    public void enterOverlay(Overlay overlay) {
 | 
			
		||||
        app.getGuiNode().detachChild(guiNode);
 | 
			
		||||
 | 
			
		||||
        onEnterOverlay(overlay);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay to leave
 | 
			
		||||
     */
 | 
			
		||||
    public void leaveOverlay(Overlay overlay) {
 | 
			
		||||
        app.getGuiNode().attachChild(guiNode);
 | 
			
		||||
 | 
			
		||||
        onLeaveOverlay(overlay);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to update the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        videoSettingsDialog.update();
 | 
			
		||||
        audioSettingsDialog.update();
 | 
			
		||||
@@ -94,17 +127,44 @@ public void update(float tpf) {
 | 
			
		||||
        onUpdate(tpf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Abstract method to handle entering the view.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onEnter();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Abstract method to handle leaving the view.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onLeave();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to handle updating the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame
 | 
			
		||||
     */
 | 
			
		||||
    protected void onUpdate(float tpf) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Abstract method to handle entering an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay to enter
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onEnterOverlay(Overlay overlay);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Abstract method to handle leaving an overlay.
 | 
			
		||||
     *
 | 
			
		||||
     * @param overlay the overlay to leave
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void onLeaveOverlay(Overlay overlay);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to create a background geometry with a texture.
 | 
			
		||||
     *
 | 
			
		||||
     * @param texturePath the path to the texture
 | 
			
		||||
     * @return the created background geometry
 | 
			
		||||
     */
 | 
			
		||||
    protected Geometry createBackground(String texturePath) {
 | 
			
		||||
        TextureKey key = new TextureKey(texturePath, true);
 | 
			
		||||
        Texture backgroundTexture = app.getAssetManager().loadTexture(key);
 | 
			
		||||
@@ -121,6 +181,9 @@ protected Geometry createBackground(String texturePath) {
 | 
			
		||||
        return background;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to enter the settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void enterSettings() {
 | 
			
		||||
        enterOverlay(Overlay.SETTINGS);
 | 
			
		||||
 | 
			
		||||
@@ -131,6 +194,9 @@ public void enterSettings() {
 | 
			
		||||
        settingsDepth++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave the settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void leaveSettings() {
 | 
			
		||||
        leaveOverlay(Overlay.SETTINGS);
 | 
			
		||||
 | 
			
		||||
@@ -141,6 +207,9 @@ public void leaveSettings() {
 | 
			
		||||
        settingsDepth--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to enter the video settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void enterVideoSettings() {
 | 
			
		||||
        settingsDialog.hide();
 | 
			
		||||
        videoSettingsDialog.show();
 | 
			
		||||
@@ -148,6 +217,9 @@ public void enterVideoSettings() {
 | 
			
		||||
        settingsDepth++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave the video settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void leaveVideoSettings() {
 | 
			
		||||
        settingsDialog.show();
 | 
			
		||||
        videoSettingsDialog.hide();
 | 
			
		||||
@@ -155,6 +227,9 @@ public void leaveVideoSettings() {
 | 
			
		||||
        settingsDepth--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to enter the audio settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void enterAudioSettings() {
 | 
			
		||||
        settingsDialog.hide();
 | 
			
		||||
        audioSettingsDialog.show();
 | 
			
		||||
@@ -162,6 +237,9 @@ public void enterAudioSettings() {
 | 
			
		||||
        settingsDepth++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave the audio settings view.
 | 
			
		||||
     */
 | 
			
		||||
    public void leaveAudioSettings() {
 | 
			
		||||
        settingsDialog.show();
 | 
			
		||||
        audioSettingsDialog.hide();
 | 
			
		||||
@@ -169,6 +247,9 @@ public void leaveAudioSettings() {
 | 
			
		||||
        settingsDepth--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to leave advanced settings.
 | 
			
		||||
     */
 | 
			
		||||
    private void leaveAdvanced() {
 | 
			
		||||
        settingsDialog.show();
 | 
			
		||||
        audioSettingsDialog.hide();
 | 
			
		||||
@@ -176,6 +257,9 @@ private void leaveAdvanced() {
 | 
			
		||||
        settingsDepth--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to handle pressing the escape key.
 | 
			
		||||
     */
 | 
			
		||||
    public void pressEscape() {
 | 
			
		||||
        if (settingsDepth == 0) {
 | 
			
		||||
            enterSettings();
 | 
			
		||||
@@ -186,6 +270,9 @@ public void pressEscape() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to handle pressing the forward key.
 | 
			
		||||
     */
 | 
			
		||||
    public void pressForward() {
 | 
			
		||||
        if (this instanceof MainView mainView) {
 | 
			
		||||
            mainView.forward(false);
 | 
			
		||||
@@ -197,7 +284,13 @@ public void pressForward() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this instanceof GameView gameView) {
 | 
			
		||||
            app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
 | 
			
		||||
            if (gameView.needConfirm) {
 | 
			
		||||
                app.getModelSynchronize().confirm();
 | 
			
		||||
            } else if (gameView.needNoPower) {
 | 
			
		||||
                app.getModelSynchronize().confirm();
 | 
			
		||||
            } else {
 | 
			
		||||
                app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this instanceof CeremonyView ceremonyView) {
 | 
			
		||||
@@ -205,10 +298,16 @@ public void pressForward() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to show information on the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param error the error message
 | 
			
		||||
     * @param isError flag indicating if it is an error
 | 
			
		||||
     */
 | 
			
		||||
    public void showInfo(String error, boolean isError) {
 | 
			
		||||
        infoTimer.reset();
 | 
			
		||||
 | 
			
		||||
        if(null != infoLabel) {
 | 
			
		||||
        if (null != infoLabel) {
 | 
			
		||||
            infoLabel.hide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +315,7 @@ public void showInfo(String error, boolean isError) {
 | 
			
		||||
 | 
			
		||||
        ColorRGBA color;
 | 
			
		||||
 | 
			
		||||
        if(isError) {
 | 
			
		||||
        if (isError) {
 | 
			
		||||
            color = ColorRGBA.Red.clone();
 | 
			
		||||
        } else {
 | 
			
		||||
            color = ColorRGBA.Green.clone();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Projekte/mdga/client/src/main/resources/Images/Statistics.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 MiB  | 
| 
		 Before Width: | Height: | Size: 216 KiB  | 
| 
		 Before Width: | Height: | Size: 274 KiB  | 
| 
		 After Width: | Height: | Size: 3.1 MiB  | 
| 
		 After Width: | Height: | Size: 46 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/mdga/client/src/main/resources/Images/particle/line.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 140 B  | 
| 
		 After Width: | Height: | Size: 29 KiB  | 
| 
		 After Width: | Height: | Size: 18 KiB  | 
@@ -1,4 +1,6 @@
 | 
			
		||||
world 0,0 90
 | 
			
		||||
treesBigBackground 0,0 90
 | 
			
		||||
treesSmallBackground 0,0 90
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#Marine Pos
 | 
			
		||||
@@ -56,7 +58,8 @@ big_tent -10,-9 130
 | 
			
		||||
big_tent 9,-10 225
 | 
			
		||||
radar 0,10 -20
 | 
			
		||||
tank -1,-10 135
 | 
			
		||||
tank 0,-18 180
 | 
			
		||||
#tank 0,-18 180
 | 
			
		||||
tank_shoot 0,-18 180
 | 
			
		||||
tank 3,-18 180
 | 
			
		||||
tank -3,-18 180
 | 
			
		||||
 | 
			
		||||
@@ -270,3 +273,4 @@ treeBig 12,22 360
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
// Samplers for textures
 | 
			
		||||
uniform sampler2D m_Texture;
 | 
			
		||||
uniform sampler2D m_OutlineDepthTexture;
 | 
			
		||||
uniform sampler2D m_DepthTexture;
 | 
			
		||||
 | 
			
		||||
// Input texture coordinates from the vertex shader
 | 
			
		||||
in vec2 texCoord;
 | 
			
		||||
@@ -15,26 +14,25 @@ uniform float m_OutlineWidth;
 | 
			
		||||
out vec4 fragColor;
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
    // Sample depth textures
 | 
			
		||||
    // Sample depth textures at various offsets
 | 
			
		||||
    vec4 depth = texture(m_OutlineDepthTexture, texCoord);
 | 
			
		||||
    vec4 depth1 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth2 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth3 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth4 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth5 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth6 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth7 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
    vec4 depth8 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
    vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
    vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
 | 
			
		||||
    // Sample the main texture
 | 
			
		||||
    vec4 color = texture(m_Texture, texCoord);
 | 
			
		||||
 | 
			
		||||
    // Determine whether to apply the outline color
 | 
			
		||||
    if (depth == vec4(0.0) &&
 | 
			
		||||
       (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
 | 
			
		||||
        depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
 | 
			
		||||
        fragColor = m_OutlineColor; // Apply outline color
 | 
			
		||||
    } else {
 | 
			
		||||
        fragColor = color; // Use the original texture color
 | 
			
		||||
    }
 | 
			
		||||
    // Check if an outline should be applied
 | 
			
		||||
    bool isEdge = (depth == vec4(0.0)) &&
 | 
			
		||||
                  (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
 | 
			
		||||
                   depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth);
 | 
			
		||||
 | 
			
		||||
    // Output the final color
 | 
			
		||||
    fragColor = isEdge ? m_OutlineColor : color;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
 | 
			
		||||
// Use 'in' instead of 'varying' for inputs from the vertex shader
 | 
			
		||||
// Input texture coordinates from the vertex shader
 | 
			
		||||
in vec2 texCoord;
 | 
			
		||||
 | 
			
		||||
// Declare a custom output variable for the fragment color
 | 
			
		||||
// Output variable for the fragment color
 | 
			
		||||
out vec4 fragColor;
 | 
			
		||||
 | 
			
		||||
// Uniform samplers
 | 
			
		||||
// Uniform samplers for textures
 | 
			
		||||
uniform sampler2D m_Texture;
 | 
			
		||||
uniform sampler2D m_NormalsTexture;
 | 
			
		||||
uniform sampler2D m_DepthTexture;
 | 
			
		||||
@@ -13,6 +12,7 @@ uniform sampler2D m_DepthTexture;
 | 
			
		||||
void main() {
 | 
			
		||||
    // Sample the texture at the given texture coordinates
 | 
			
		||||
    vec4 color = texture(m_Texture, texCoord);
 | 
			
		||||
 | 
			
		||||
    // Assign the color to the output variable
 | 
			
		||||
    fragColor = color;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,109 +1,78 @@
 | 
			
		||||
// Uniform samplers
 | 
			
		||||
uniform sampler2D m_Texture;
 | 
			
		||||
uniform sampler2D m_OutlineDepthTexture;
 | 
			
		||||
uniform sampler2D m_DepthTexture;
 | 
			
		||||
varying vec2 texCoord;
 | 
			
		||||
 | 
			
		||||
// Input texture coordinates from the vertex shader
 | 
			
		||||
in vec2 texCoord;
 | 
			
		||||
 | 
			
		||||
// Uniforms for resolution, outline color, and width
 | 
			
		||||
uniform vec2 m_Resolution;
 | 
			
		||||
uniform vec4 m_OutlineColor;
 | 
			
		||||
uniform float m_OutlineWidth;
 | 
			
		||||
 | 
			
		||||
// Output variable for fragment color
 | 
			
		||||
out vec4 fragColor;
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
	vec4 depth = texture2D(m_OutlineDepthTexture, texCoord);
 | 
			
		||||
	vec4 depth1 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth2 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth3 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth4 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth5 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth6 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-m_OutlineWidth))/m_Resolution);
 | 
			
		||||
	vec4 depth7 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,0.))/m_Resolution);
 | 
			
		||||
	vec4 depth8 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,0.))/m_Resolution);
 | 
			
		||||
	vec4 color = texture2D(m_Texture, texCoord);
 | 
			
		||||
	//如果是背景
 | 
			
		||||
	float ratio=0.;
 | 
			
		||||
	if(depth==vec4(0.) && (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth||depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)){
 | 
			
		||||
		float dist=m_OutlineWidth;
 | 
			
		||||
		//距离边的像素
 | 
			
		||||
		vec4 nearDepth;
 | 
			
		||||
		if(depth1 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth2 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,-i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth3 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth4 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,-i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth5 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth6 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-i))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth7 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,0.))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}else
 | 
			
		||||
		if(depth8 != depth){
 | 
			
		||||
			for(float i=0.;i<m_OutlineWidth;i++){
 | 
			
		||||
				nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,0.))/m_Resolution);
 | 
			
		||||
				if(nearDepth != depth){
 | 
			
		||||
					dist = i;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		//0:场景颜色		1:outline颜色 
 | 
			
		||||
		ratio = clamp(1.- dist/m_OutlineWidth,0.,1.);
 | 
			
		||||
		//float off = (1.-ratio*ratio)*(1.-ratio*ratio);
 | 
			
		||||
		gl_FragColor = color*(1.-ratio) +m_OutlineColor*ratio;
 | 
			
		||||
		//gl_FragColor = m_OutlineColor;
 | 
			
		||||
	}else{
 | 
			
		||||
		gl_FragColor = color;
 | 
			
		||||
	}
 | 
			
		||||
	//debug
 | 
			
		||||
	//gl_FragColor = vec4(0.,(1.-ratio),0.,1.);
 | 
			
		||||
}
 | 
			
		||||
    vec4 depth = texture(m_OutlineDepthTexture, texCoord);
 | 
			
		||||
    vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
 | 
			
		||||
    vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
    vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
 | 
			
		||||
 | 
			
		||||
    vec4 color = texture(m_Texture, texCoord);
 | 
			
		||||
 | 
			
		||||
    float ratio = 0.0;
 | 
			
		||||
    if (depth == vec4(0.0) &&
 | 
			
		||||
        (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
 | 
			
		||||
         depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
 | 
			
		||||
        float dist = m_OutlineWidth;
 | 
			
		||||
        vec4 nearDepth;
 | 
			
		||||
 | 
			
		||||
        // Iterate to find the distance to the nearest edge
 | 
			
		||||
        for (float i = 0.0; i < m_OutlineWidth; i++) {
 | 
			
		||||
            if (depth1 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth2 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, -i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth3 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth4 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, -i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth5 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth6 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -i)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth7 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, 0.0)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            } else if (depth8 != depth) {
 | 
			
		||||
                nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, 0.0)) / m_Resolution);
 | 
			
		||||
                if (nearDepth != depth) { dist = i; break; }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Calculate ratio for outline blending
 | 
			
		||||
        ratio = clamp(1.0 - dist / m_OutlineWidth, 0.0, 1.0);
 | 
			
		||||
 | 
			
		||||
        // Blend the outline color with the base color
 | 
			
		||||
        fragColor = color * (1.0 - ratio) + m_OutlineColor * ratio;
 | 
			
		||||
    } else {
 | 
			
		||||
        // No outline, use the base texture color
 | 
			
		||||
        fragColor = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Optional: Debugging outline visualization
 | 
			
		||||
    // fragColor = vec4(0.0, 1.0 - ratio, 0.0, 1.0);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
// Use 'in' for vertex attributes
 | 
			
		||||
// Vertex attributes
 | 
			
		||||
in vec4 inPosition;
 | 
			
		||||
in vec2 inTexCoord;
 | 
			
		||||
 | 
			
		||||
// Use 'out' for passing data to the fragment shader
 | 
			
		||||
// Output to fragment shader
 | 
			
		||||
out vec2 texCoord;
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Projekte/mdga/client/src/main/resources/Models/shell/shell.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 13 MiB  | 
| 
		 After Width: | Height: | Size: 10 MiB  |