Compare commits
	
		
			15 Commits
		
	
	
		
			4a9eba255b
			...
			b_Mueller_
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e0bea47583 | ||
|  | 38979bdf86 | ||
|  | 68beedcf29 | ||
|  | 7d70e8bd13 | ||
|  | 0fd0555dea | ||
|  | b962444506 | ||
|  | c0a3e8b37a | ||
|  | c8621e02c6 | ||
|  | 25d7884cad | ||
|  | 2192f6dbc3 | ||
|  | 2b8bfb82ba | ||
|  | 70ed981ea2 | ||
|  | d5450df77c | ||
|  | 1f75f7bf30 | ||
|  | 1ac55a9570 | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/Alienship.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Projekte/Boje.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Projekte/KingGeorgeV.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Projekte/Marlow66.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Projekte/Models/Alienship.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								Projekte/UX23.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -9,6 +9,9 @@ dependencies { | ||||
|     implementation project(":battleship:model") | ||||
|  | ||||
|     implementation libs.jme3.desktop | ||||
|     implementation libs.jme3.effects | ||||
|     implementation project(path: ':battleship:server') | ||||
|     implementation project(path: ':battleship:server') | ||||
|  | ||||
|     runtimeOnly libs.jme3.awt.dialogs | ||||
|     runtimeOnly libs.jme3.plugins | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import pp.battleship.client.gui.EditorAppState; | ||||
| import pp.battleship.client.gui.SeaAppState; | ||||
| import pp.battleship.game.client.BattleshipClient; | ||||
| import pp.battleship.game.client.ClientGameLogic; | ||||
| import pp.battleship.game.client.GameMusic; | ||||
| import pp.battleship.game.client.ServerConnection; | ||||
| import pp.battleship.game.singlemode.BattleshipClientConfig; | ||||
| import pp.battleship.game.singlemode.ServerConnectionMockup; | ||||
| @@ -265,13 +266,24 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient | ||||
|         flyCam.setEnabled(false); | ||||
|         stateManager.detach(stateManager.getState(StatsAppState.class)); | ||||
|         stateManager.detach(stateManager.getState(DebugKeysAppState.class)); | ||||
|         atttachGameMusic(); | ||||
|  | ||||
|         attachGameSound(); | ||||
|         stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); | ||||
|     } | ||||
|  | ||||
|     private void atttachGameMusic() { | ||||
|         final GameMusic gameSound = new GameMusic(); | ||||
|         gameSound.setEnabled(GameMusic.enabledInPreferences()); | ||||
|         stateManager.attach(gameSound); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Attaches the game sound state and sets its initial enabled state. | ||||
|      * | ||||
|      */ | ||||
|     private void attachGameSound() { | ||||
|         final GameSound gameSound = new GameSound(); | ||||
|   | ||||
| @@ -12,6 +12,8 @@ import com.jme3.app.state.AbstractAppState; | ||||
| import com.jme3.app.state.AppStateManager; | ||||
| import pp.battleship.game.client.ClientGameLogic; | ||||
|  | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Abstract class representing a state in the Battleship game. | ||||
|  * Extends the AbstractAppState from jMonkeyEngine to manage state behavior. | ||||
|   | ||||
| @@ -87,7 +87,7 @@ public class GameSound extends AbstractAppState implements GameEventListener { | ||||
|      * @param name The name of the sound file. | ||||
|      * @return The loaded AudioNode. | ||||
|      */ | ||||
|     private AudioNode loadSound(Application app, String name) { | ||||
|     public AudioNode loadSound(Application app, String name) { | ||||
|         try { | ||||
|             final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); | ||||
|             sound.setLooping(false); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Checkbox; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.style.ElementId; | ||||
| import pp.battleship.game.client.GameMusic; | ||||
| import pp.dialog.Dialog; | ||||
| import pp.dialog.StateCheckboxModel; | ||||
| import pp.dialog.TextInputDialog; | ||||
| @@ -30,6 +31,8 @@ import static pp.util.PreferencesUtils.getPreferences; | ||||
| class Menu extends Dialog { | ||||
|     private static final Preferences PREFERENCES = getPreferences(Menu.class); | ||||
|     private static final String LAST_PATH = "last.file.path"; | ||||
|  | ||||
|   //  private final VolumeSlider slider; | ||||
|     private final BattleshipApp app; | ||||
|     private final Button loadButton = new Button(lookup("menu.map.load")); | ||||
|     private final Button saveButton = new Button(lookup("menu.map.save")); | ||||
| @@ -39,12 +42,14 @@ class Menu extends Dialog { | ||||
|      * | ||||
|      * @param app the BattleshipApp instance | ||||
|      */ | ||||
|  | ||||
|     public Menu(BattleshipApp app) { | ||||
|         super(app.getDialogManager()); | ||||
|         this.app = app; | ||||
|         addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS | ||||
|         addChild(new Checkbox(lookup("menu.sound-enabled"), | ||||
|                               new StateCheckboxModel(app, GameSound.class))); | ||||
|        // slider = new VolumeSlider(app.getStateManager().getState(GameMusic.class)); | ||||
|         addChild(loadButton) | ||||
|                 .addClickCommands(s -> ifTopDialog(this::loadDialog)); | ||||
|         addChild(saveButton) | ||||
| @@ -54,6 +59,11 @@ class Menu extends Dialog { | ||||
|         addChild(new Button(lookup("menu.quit"))) | ||||
|                 .addClickCommands(s -> ifTopDialog(app::closeApp)); | ||||
|         update(); | ||||
|         addChild(new Checkbox(lookup("menu.sound-enabled"), new StateCheckboxModel(app, GameSound.class))); | ||||
|  | ||||
|         addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class))); | ||||
|       //  addChild(slider); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -65,6 +75,14 @@ class Menu extends Dialog { | ||||
|         saveButton.setEnabled(app.getGameLogic().maySaveMap()); | ||||
|     } | ||||
|  | ||||
|   /*  @Override | ||||
|     public void update(float delta) { | ||||
|         slider.update(); | ||||
|     } | ||||
|  | ||||
|    */ | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * As an escape action, this method closes the menu if it is the top dialog. | ||||
|      */ | ||||
|   | ||||
| @@ -7,10 +7,12 @@ | ||||
|  | ||||
| package pp.battleship.client; | ||||
|  | ||||
| import com.simsilica.lemur.Button; | ||||
| import com.simsilica.lemur.Container; | ||||
| import com.simsilica.lemur.Label; | ||||
| import com.simsilica.lemur.TextField; | ||||
| import com.simsilica.lemur.component.SpringGridLayout; | ||||
| import pp.battleship.server.BattleshipServer; | ||||
| import pp.dialog.Dialog; | ||||
| import pp.dialog.DialogBuilder; | ||||
| import pp.dialog.SimpleDialog; | ||||
| @@ -35,6 +37,7 @@ class NetworkDialog extends SimpleDialog { | ||||
|     private final TextField port = new TextField(DEFAULT_PORT); | ||||
|     private String hostname; | ||||
|     private int portNumber; | ||||
|     private final Button serverButton = new Button(lookup("client.server-start")); | ||||
|     private Future<Object> connectionFuture; | ||||
|     private Dialog progressDialog; | ||||
|  | ||||
| @@ -65,7 +68,11 @@ class NetworkDialog extends SimpleDialog { | ||||
|                      .setOkClose(false) | ||||
|                      .setNoClose(false) | ||||
|                      .build(this); | ||||
|         addChild(serverButton).addClickCommands(s -> ifTopDialog(this::startServerInThread)); | ||||
|     } | ||||
|     //Add the button to start the sever | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Handles the action for the connect button in the connection dialog. | ||||
| @@ -150,4 +157,21 @@ class NetworkDialog extends SimpleDialog { | ||||
|         network.getApp().errorDialog(lookup("server.connection.failed")); | ||||
|         network.getApp().setInfoText(e.getLocalizedMessage()); | ||||
|     } | ||||
|     /** | ||||
|      * Starts the server in a separate thread. | ||||
|      */ | ||||
|     private void startServerInThread() { | ||||
|         serverButton.setEnabled(false); | ||||
|         Thread serverThread = new Thread(() -> { | ||||
|             try { | ||||
|                 BattleshipServer.main(null); | ||||
|             } catch (Exception e) { | ||||
|                 serverButton.setEnabled(true); | ||||
|                 LOGGER.log(Level.ERROR, "Server could not be started", e); | ||||
|                 network.getApp().errorDialog("Could not start server: " + e.getMessage()); | ||||
|             } | ||||
|         }); | ||||
|         serverThread.start(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,10 +8,12 @@ | ||||
| package pp.battleship.client.gui; | ||||
|  | ||||
| import com.jme3.math.ColorRGBA; | ||||
| import com.jme3.math.Vector3f; | ||||
| import com.jme3.scene.Geometry; | ||||
| import com.jme3.scene.Node; | ||||
| import com.jme3.scene.Spatial; | ||||
| import pp.battleship.model.Battleship; | ||||
| import pp.battleship.model.Shell; | ||||
| import pp.battleship.model.Shot; | ||||
| import pp.util.Position; | ||||
|  | ||||
| @@ -36,6 +38,7 @@ class MapViewSynchronizer extends ShipMapSynchronizer { | ||||
|  | ||||
|     // The MapView associated with this synchronizer | ||||
|     private final MapView view; | ||||
|     private Shell shell; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a new MapViewSynchronizer for the given MapView. | ||||
| @@ -122,4 +125,16 @@ class MapViewSynchronizer extends ShipMapSynchronizer { | ||||
|     private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) { | ||||
|         return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH); | ||||
|     } | ||||
|     public void update(float deltaTime) { | ||||
|         if (shell != null) { | ||||
|             shell.updatePosition(deltaTime); | ||||
|             drawShell(shell.getCurrentPosition()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void drawShell(Vector3f position){ | ||||
|         //TODO implement | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,285 @@ | ||||
| package pp.battleship.client.gui; | ||||
|  | ||||
| import com.jme3.effect.ParticleEmitter; | ||||
| import com.jme3.effect.ParticleMesh.Type; | ||||
| import com.jme3.effect.shapes.EmitterSphereShape; | ||||
| import com.jme3.material.Material; | ||||
| import com.jme3.math.ColorRGBA; | ||||
| import com.jme3.math.FastMath; | ||||
| import com.jme3.math.Vector3f; | ||||
| import pp.battleship.client.BattleshipApp; | ||||
|  | ||||
| /** | ||||
|  * Factory class responsible for creating particle effects used in the game. | ||||
|  * This centralizes the creation of various types of particle emitters. | ||||
|  */ | ||||
| public class ParticleEffectFactory { | ||||
|     private static final int COUNT_FACTOR = 1; | ||||
|     private static final float COUNT_FACTOR_F = 1f; | ||||
|     private static final boolean POINT_SPRITE = true; | ||||
|     private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle; | ||||
|      | ||||
|     private final BattleshipApp app; | ||||
|  | ||||
|     ParticleEffectFactory(BattleshipApp app) { | ||||
|         this.app = app; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a flame particle emitter. | ||||
|      *  | ||||
|      * @return a configured flame particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createFlame() { | ||||
|         ParticleEmitter flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR); | ||||
|         flame.setSelectRandomImage(true); | ||||
|         flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); | ||||
|         flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); | ||||
|         flame.setStartSize(0.1f); | ||||
|         flame.setEndSize(0.5f); | ||||
|         flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); | ||||
|         flame.setParticlesPerSec(0); | ||||
|         flame.setGravity(0, -5, 0); | ||||
|         flame.setLowLife(.4f); | ||||
|         flame.setHighLife(.5f); | ||||
|         flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0)); | ||||
|         flame.getParticleInfluencer().setVelocityVariation(1f); | ||||
|         flame.setImagesX(2); | ||||
|         flame.setImagesY(2); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png")); | ||||
|         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||
|         flame.setMaterial(mat); | ||||
|  | ||||
|         return flame; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a flash particle emitter. | ||||
|      *  | ||||
|      * @return a configured flash particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createFlash() { | ||||
|         ParticleEmitter flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR); | ||||
|         flash.setSelectRandomImage(true); | ||||
|         flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F)); | ||||
|         flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||
|         flash.setStartSize(.1f); | ||||
|         flash.setEndSize(0.5f); | ||||
|         flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); | ||||
|         flash.setParticlesPerSec(0); | ||||
|         flash.setGravity(0, 0, 0); | ||||
|         flash.setLowLife(.2f); | ||||
|         flash.setHighLife(.2f); | ||||
|         flash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 5f, 0)); | ||||
|         flash.getParticleInfluencer().setVelocityVariation(1); | ||||
|         flash.setImagesX(2); | ||||
|         flash.setImagesY(2); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png")); | ||||
|         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||
|         flash.setMaterial(mat); | ||||
|  | ||||
|         return flash; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a round spark particle emitter. | ||||
|      *  | ||||
|      * @return a configured round spark particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createRoundSpark() { | ||||
|         ParticleEmitter roundSpark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR); | ||||
|         roundSpark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F))); | ||||
|         roundSpark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F)); | ||||
|         roundSpark.setStartSize(0.2f); | ||||
|         roundSpark.setEndSize(0.8f); | ||||
|         roundSpark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); | ||||
|         roundSpark.setParticlesPerSec(0); | ||||
|         roundSpark.setGravity(0, -.5f, 0); | ||||
|         roundSpark.setLowLife(1.8f); | ||||
|         roundSpark.setHighLife(2f); | ||||
|         roundSpark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0)); | ||||
|         roundSpark.getParticleInfluencer().setVelocityVariation(.5f); | ||||
|         roundSpark.setImagesX(1); | ||||
|         roundSpark.setImagesY(1); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png")); | ||||
|         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||
|         roundSpark.setMaterial(mat); | ||||
|  | ||||
|         return roundSpark; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a spark particle emitter. | ||||
|      *  | ||||
|      * @return a configured spark particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createSpark() { | ||||
|         ParticleEmitter spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR); | ||||
|         spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); | ||||
|         spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||
|         spark.setStartSize(.5f); | ||||
|         spark.setEndSize(.5f); | ||||
|         spark.setFacingVelocity(true); | ||||
|         spark.setParticlesPerSec(0); | ||||
|         spark.setGravity(0, 5, 0); | ||||
|         spark.setLowLife(1.1f); | ||||
|         spark.setHighLife(1.5f); | ||||
|         spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0)); | ||||
|         spark.getParticleInfluencer().setVelocityVariation(1); | ||||
|         spark.setImagesX(1); | ||||
|         spark.setImagesY(1); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png")); | ||||
|         spark.setMaterial(mat); | ||||
|  | ||||
|         return spark; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a smoke trail particle emitter. | ||||
|      *  | ||||
|      * @return a configured smoke trail particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createSmokeTrail() { | ||||
|         ParticleEmitter smokeTrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR); | ||||
|         smokeTrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); | ||||
|         smokeTrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||
|         smokeTrail.setStartSize(.2f); | ||||
|         smokeTrail.setEndSize(1f); | ||||
|         smokeTrail.setFacingVelocity(true); | ||||
|         smokeTrail.setParticlesPerSec(0); | ||||
|         smokeTrail.setGravity(0, 1, 0); | ||||
|         smokeTrail.setLowLife(.4f); | ||||
|         smokeTrail.setHighLife(.5f); | ||||
|         smokeTrail.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 12, 0)); | ||||
|         smokeTrail.getParticleInfluencer().setVelocityVariation(1); | ||||
|         smokeTrail.setImagesX(1); | ||||
|         smokeTrail.setImagesY(3); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png")); | ||||
|         smokeTrail.setMaterial(mat); | ||||
|  | ||||
|         return smokeTrail; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a debris particle emitter. | ||||
|      *  | ||||
|      * @return a configured debris particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createDebris() { | ||||
|         ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR); | ||||
|         debris.setSelectRandomImage(true); | ||||
|         debris.setRandomAngle(true); | ||||
|         debris.setRotateSpeed(FastMath.TWO_PI * 4); | ||||
|         debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F)); | ||||
|         debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f)); | ||||
|         debris.setStartSize(.10f); | ||||
|         debris.setEndSize(.15f); | ||||
|         debris.setParticlesPerSec(0); | ||||
|         debris.setGravity(0, 12f, 0); | ||||
|         debris.setLowLife(1.4f); | ||||
|         debris.setHighLife(1.5f); | ||||
|         debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 15, 0)); | ||||
|         debris.getParticleInfluencer().setVelocityVariation(.60f); | ||||
|         debris.setImagesX(3); | ||||
|         debris.setImagesY(3); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png")); | ||||
|         debris.setMaterial(mat); | ||||
|  | ||||
|         return debris; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a shockwave particle emitter. | ||||
|      *  | ||||
|      * @return a configured shockwave particle emitter | ||||
|      */ | ||||
|     ParticleEmitter createShockwave() { | ||||
|         ParticleEmitter shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR); | ||||
|         shockwave.setFaceNormal(Vector3f.UNIT_Y); | ||||
|         shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F)); | ||||
|         shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f)); | ||||
|         shockwave.setStartSize(0f); | ||||
|         shockwave.setEndSize(3f); | ||||
|         shockwave.setParticlesPerSec(0); | ||||
|         shockwave.setGravity(0, 0, 0); | ||||
|         shockwave.setLowLife(0.5f); | ||||
|         shockwave.setHighLife(0.5f); | ||||
|         shockwave.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0)); | ||||
|         shockwave.getParticleInfluencer().setVelocityVariation(0f); | ||||
|         shockwave.setImagesX(1); | ||||
|         shockwave.setImagesY(1); | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png")); | ||||
|         shockwave.setMaterial(mat); | ||||
|  | ||||
|         return shockwave; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a moving smoke emitter. | ||||
|      *  | ||||
|      * @return a configured smoke emitter | ||||
|      */ | ||||
|     ParticleEmitter createMovingSmokeEmitter() { | ||||
|         ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300); | ||||
|         smokeEmitter.setGravity(0, 0, 0); | ||||
|         smokeEmitter.getParticleInfluencer().setVelocityVariation(1); | ||||
|         smokeEmitter.setLowLife(1); | ||||
|         smokeEmitter.setHighLife(1); | ||||
|         smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0)); | ||||
|         smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png")); | ||||
|         smokeEmitter.setMaterial(mat); | ||||
|  | ||||
|         return smokeEmitter; | ||||
|     } | ||||
|     /** | ||||
|      * Creates a one-time water splash particle emitter. | ||||
|      * | ||||
|      * @return a configured one-time water splash particle emitter | ||||
|      */ | ||||
|     public ParticleEmitter createWaterSplash() { | ||||
|         // Create a new particle emitter for the splash effect | ||||
|         ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30); | ||||
|  | ||||
|         // Set the shape of the emitter, making particles emit from a point or small area | ||||
|         waterSplash.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.2f)); | ||||
|  | ||||
|         // Start and end colors for water (blue, fading out) | ||||
|         waterSplash.setStartColor(new ColorRGBA(0.4f, 0.4f, 1f, 1f));  // Light blue at start | ||||
|         waterSplash.setEndColor(new ColorRGBA(0.4f, 0.4f, 1f, 0f));    // Transparent at the end | ||||
|  | ||||
|         // Particle size: small at start, larger before fading out | ||||
|         waterSplash.setStartSize(0.1f); | ||||
|         waterSplash.setEndSize(0.3f); | ||||
|  | ||||
|         // Particle lifespan (how long particles live) | ||||
|         waterSplash.setLowLife(0.5f); | ||||
|         waterSplash.setHighLife(1f); | ||||
|  | ||||
|         // Gravity: Pull the water particles downwards | ||||
|         waterSplash.setGravity(0, -9.81f, 0);  // Earth's gravity simulation | ||||
|  | ||||
|         // Velocity: Give particles an initial burst upward (simulates splash) | ||||
|         waterSplash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0)); | ||||
|         waterSplash.getParticleInfluencer().setVelocityVariation(0.6f);  // Add randomness to splash | ||||
|  | ||||
|         // Set how many particles are emitted per second (0 to emit all particles at once) | ||||
|         waterSplash.setParticlesPerSec(0); | ||||
|  | ||||
|         // Load a texture for the water splash (assuming a texture exists at this path) | ||||
|         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||
|         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Splash/splash.png")); | ||||
|         waterSplash.setMaterial(mat); | ||||
|  | ||||
|         return waterSplash; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -7,6 +7,7 @@ | ||||
|  | ||||
| package pp.battleship.client.gui; | ||||
|  | ||||
| import com.jme3.effect.ParticleEmitter; | ||||
| import com.jme3.material.Material; | ||||
| import com.jme3.material.RenderState.BlendMode; | ||||
| import com.jme3.math.ColorRGBA; | ||||
| @@ -21,7 +22,6 @@ import pp.battleship.model.Battleship; | ||||
| import pp.battleship.model.Rotation; | ||||
| import pp.battleship.model.ShipMap; | ||||
| import pp.battleship.model.Shot; | ||||
|  | ||||
| import static java.util.Objects.requireNonNull; | ||||
| import static pp.util.FloatMath.HALF_PI; | ||||
| import static pp.util.FloatMath.PI; | ||||
| @@ -34,7 +34,13 @@ import static pp.util.FloatMath.PI; | ||||
|  */ | ||||
| class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|     private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS | ||||
|     private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS | ||||
|     private static final String UX23 = "Models/UX23/UX23.j3o"; | ||||
|     private static final String BOJE = "Models/Boje/Boje.j3o"; | ||||
|  | ||||
|     private static final String ALIENSHIP = "Models/Alienship/Alienship.j3o"; | ||||
|     private static final String MARLOW66 = "Models/Marlow66/Marlow66.j3o"; | ||||
|     private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; | ||||
|  | ||||
|     private static final String COLOR = "Color"; //NON-NLS | ||||
|     private static final String SHIP = "ship"; //NON-NLS | ||||
|     private static final String SHOT = "shot"; //NON-NLS | ||||
| @@ -44,6 +50,7 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|  | ||||
|     private final ShipMap map; | ||||
|     private final BattleshipApp app; | ||||
|     private final ParticleEffectFactory particleFactory; | ||||
|  | ||||
|     /** | ||||
|      * Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map. | ||||
| @@ -56,6 +63,7 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|         super(app.getGameLogic().getOwnMap(), root); | ||||
|         this.app = app; | ||||
|         this.map = map; | ||||
|         this.particleFactory = new ParticleEffectFactory(app); | ||||
|         addExisting(); | ||||
|     } | ||||
|  | ||||
| @@ -69,10 +77,10 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|      */ | ||||
|     @Override | ||||
|     public Spatial visit(Shot shot) { | ||||
|         return shot.isHit() ? handleHit(shot) : createCylinder(shot); | ||||
|         return shot.isHit() ? handleHit(shot) : handleMiss(shot); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|         /** | ||||
|      * Handles a hit by attaching its representation to the node that | ||||
|      * contains the ship model as a child so that it moves with the ship. | ||||
|      * | ||||
| @@ -84,12 +92,62 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|         final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship"); | ||||
|         final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node"); | ||||
|  | ||||
|         final Geometry representation = createCylinder(shot); | ||||
|         representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation()); | ||||
|         shipNode.attachChild(representation); | ||||
|  | ||||
|  | ||||
|         // Create a new node specifically for the hit effects | ||||
|         Node hitEffectNode = new Node("HitEffectNode"); | ||||
|  | ||||
|         // Create particle effects | ||||
|         ParticleEmitter flame = particleFactory.createFlame(); | ||||
|         ParticleEmitter flash = particleFactory.createFlash(); | ||||
|         ParticleEmitter spark = particleFactory.createSpark(); | ||||
|         ParticleEmitter roundSpark = particleFactory.createRoundSpark(); | ||||
|         ParticleEmitter smokeTrail = particleFactory.createSmokeTrail(); | ||||
|         ParticleEmitter debris = particleFactory.createDebris(); | ||||
|         ParticleEmitter shockwave = particleFactory.createShockwave(); | ||||
|         ParticleEmitter movingSmoke = particleFactory.createMovingSmokeEmitter(); | ||||
|  | ||||
|         // Attach all effects to the hitEffectNode | ||||
|         hitEffectNode.attachChild(flame); | ||||
|         hitEffectNode.attachChild(flash); | ||||
|         hitEffectNode.attachChild(spark); | ||||
|         hitEffectNode.attachChild(roundSpark); | ||||
|         hitEffectNode.attachChild(smokeTrail); | ||||
|         hitEffectNode.attachChild(debris); | ||||
|         hitEffectNode.attachChild(shockwave); | ||||
|         hitEffectNode.attachChild(movingSmoke); | ||||
|  | ||||
|         // Set the local translation for the hit effect to the point of impact | ||||
|         hitEffectNode.setLocalTranslation(shot.getY() + 0.5f - shipNode.getLocalTranslation().x, | ||||
|                                           0.5f, // Adjust as needed for height above the ship | ||||
|                                           shot.getX() + 0.5f - shipNode.getLocalTranslation().z); | ||||
|  | ||||
|         // Attach the hitEffectNode to the shipNode so it moves with the ship | ||||
|         shipNode.attachChild(hitEffectNode); | ||||
|  | ||||
|         // Emit particles when the hit happens | ||||
|         flash.emitAllParticles(); | ||||
|         spark.emitAllParticles(); | ||||
|         smokeTrail.emitAllParticles(); | ||||
|         debris.emitAllParticles(); | ||||
|         shockwave.emitAllParticles(); | ||||
|         flame.emitAllParticles(); | ||||
|         roundSpark.emitAllParticles(); | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|     private Spatial handleMiss(Shot shot) { | ||||
|         Node shotNode = new Node("ShotNode"); | ||||
|         Geometry shotCylinder = createCylinder(shot); | ||||
|         shotNode.attachChild(shotCylinder); | ||||
|         ParticleEmitter waterSplash = particleFactory.createWaterSplash(); | ||||
|         waterSplash.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); | ||||
|         shotNode.attachChild(waterSplash); | ||||
|         waterSplash.emitAllParticles(); | ||||
|  | ||||
|         return shotNode; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a cylinder geometry representing the specified shot. | ||||
| @@ -141,7 +199,13 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|      * @return the spatial representing the battleship | ||||
|      */ | ||||
|     private Spatial createShip(Battleship ship) { | ||||
|         return ship.getLength() == 4 ? createBattleship(ship) : createBox(ship); | ||||
|         switch (ship.getLength()) { | ||||
|             case 4: return createBattleship(ship); | ||||
|             case 3: return createMarlow66(ship); | ||||
|             case 2: return createUX23(ship); | ||||
|             case 1: return createAllienship(ship); | ||||
|             default: return createBox(ship); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -191,6 +255,42 @@ class SeaSynchronizer extends ShipMapSynchronizer { | ||||
|  | ||||
|         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||
|         model.scale(1.48f); | ||||
|         // model.scale(0.0007f); | ||||
|         model.setShadowMode(ShadowMode.CastAndReceive); | ||||
|  | ||||
|         return model; | ||||
|     } | ||||
|  | ||||
|     private Spatial createAllienship(Battleship ship) { | ||||
|         final Spatial model = app.getAssetManager().loadModel(ALIENSHIP); | ||||
|  | ||||
|         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||
|         model.scale(0.10f); | ||||
|         model.setShadowMode(ShadowMode.CastAndReceive); | ||||
|  | ||||
|         return model;} | ||||
|     private Spatial createUX23(Battleship ship) { | ||||
|         final Spatial model = app.getAssetManager().loadModel(UX23); | ||||
|  | ||||
|         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||
|         // model.move(0f, -0.05f, 0f); | ||||
|         model.scale(0.89f); | ||||
|         model.setShadowMode(ShadowMode.CastAndReceive); | ||||
|  | ||||
|         return model; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     private Spatial createMarlow66(Battleship ship) { | ||||
|         final Spatial model = app.getAssetManager().loadModel(MARLOW66); | ||||
|  | ||||
|         model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f); | ||||
|         model.move(0f, 0.25f, 0f); | ||||
|         model.scale(0.135f); | ||||
|         model.setShadowMode(ShadowMode.CastAndReceive); | ||||
|  | ||||
|         return model; | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Effects/Debris.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Effects/Smoke.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Effects/flame.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Effects/flash.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 63 KiB | 
| After Width: | Height: | Size: 2.0 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Effects/spark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 78 KiB | 
| After Width: | Height: | Size: 235 KiB | 
| After Width: | Height: | Size: 76 KiB | 
| After Width: | Height: | Size: 127 KiB | 
| After Width: | Height: | Size: 70 KiB | 
| After Width: | Height: | Size: 142 KiB | 
| After Width: | Height: | Size: 3.1 MiB | 
| After Width: | Height: | Size: 3.2 MiB | 
| @@ -41,7 +41,11 @@ public class ModelExporter extends SimpleApplication { | ||||
|      */ | ||||
|     @Override | ||||
|     public void simpleInitApp() { | ||||
|         export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o"); //NON-NLS | ||||
|         export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o");//NON-NLS | ||||
|         export("Models/Alienship/Alienship.obj", "Alienship.j3o");//NON-NLS | ||||
|         export("Models/Marlow66/Marlow66.obj", "Marlow66.j3o");//NON-NLS | ||||
|         export("Models/UX23/UX23.obj", "UX23.j3o");//NON-NLS | ||||
|         export("Models/Boje/Boje.obj", "Boje.j3o");//NON-NLS | ||||
|  | ||||
|         stop(); | ||||
|     } | ||||
|   | ||||
| After Width: | Height: | Size: 78 KiB | 
| @@ -0,0 +1,11 @@ | ||||
| # Blender MTL File: 'water ship.blend' | ||||
| # Material Count: 1 | ||||
|  | ||||
| newmtl Material | ||||
| Ns 96.078431 | ||||
| Ka 0.000000 0.000000 0.000000 | ||||
| Kd 0.640000 0.640000 0.640000 | ||||
| Ks 0.500000 0.500000 0.500000 | ||||
| Ni 1.000000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
							
								
								
									
										20188
									
								
								Projekte/battleship/converter/src/main/resources/Models/Boje/Boje.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 235 KiB | 
| After Width: | Height: | Size: 89 KiB | 
| @@ -0,0 +1,90 @@ | ||||
| # Blender MTL File: 'Marlow66.blend' | ||||
| # Material Count: 6 | ||||
|  | ||||
| # DISCLOSURE: | ||||
| # This model obj or mtl files are NOT intended for commercial purposes. | ||||
| # Do not copy or sell in part or in full without explicit permission from original author | ||||
| # including accompanying textures, jpgs etc... | ||||
| # One or more textures bundled with this project have been created with images from Textures.com.  | ||||
| # These images may not be redistributed by default. Please visit www.textures.com for more information. | ||||
| # | ||||
| # AUTHOR: PapaySailor  Copyright 2020 | ||||
| # WEB SITE: www.archipelagosim.com  | ||||
|  | ||||
| newmtl Aluminum | ||||
| Ns 40.0 | ||||
| Ka 0.2 0.2 0.2 | ||||
| Kd 0.8 0.8 0.8 | ||||
| Ks 0.1 0.1 0.1 | ||||
|  | ||||
| newmtl Black | ||||
| Ns 517.690314 | ||||
| Ka 0.235955 0.235955 0.235955 | ||||
| Kd 0.000000 0.000000 0.000000 | ||||
| Ks 0.500000 0.500000 0.500000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 3 | ||||
|  | ||||
| newmtl Chrome | ||||
| Ns 10 | ||||
| Ka 0.05 0.05 0.1 | ||||
| Kd 0.99 0.99 0.99 | ||||
| Ks 1.0 1.0 1.0 | ||||
|  | ||||
| newmtl DarkWood | ||||
| Ns 225.000000 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 0.58 0.41 0.25 | ||||
| Ks 0.500000 0.500000 0.500000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
| map_Kd WoodPlanks.jpg | ||||
|  | ||||
| newmtl LightBlue | ||||
| Ns 440.461707 | ||||
| Ka 0.3 0.3 0.3 | ||||
| Kd 0.25 1.0 1.0 | ||||
| Ks 0.500000 0.500000 0.500000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 3 | ||||
| map_Kd HullTexture.png | ||||
|  | ||||
| newmtl None | ||||
| Ns 500 | ||||
| Ka 0.8 0.8 0.8 | ||||
| Kd 0.8 0.8 0.8 | ||||
| Ks 0.8 0.8 0.8 | ||||
| d 1 | ||||
| illum 2 | ||||
|  | ||||
| newmtl White | ||||
| Ka 0.38 0.39 0.38 | ||||
| Kd 0.9 0.9 0.9 | ||||
| Ks 0.57 0.49 0.37 | ||||
| Ke 0.0 0.0 0.0 | ||||
| Ns 579.0 | ||||
|  | ||||
| newmtl WindowShaded | ||||
| Ka 0.184744 0.184744 0.184744 | ||||
| Kd 0.166368 0.218014 0.259048 | ||||
| Ks 0.6283 0.5559 0.3661 | ||||
| Ke 0.0 0.0 0.0 | ||||
| Ns 427.451019 | ||||
| Tr 0.2 | ||||
|  | ||||
| newmtl Wood | ||||
| Ns 50 | ||||
| Ka 0.2 0.2 0.2 | ||||
| Kd 0.93 0.82 0.63 | ||||
| Ks 0.500000 0.500000 0.500000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
| map_Kd FineWood.jpg | ||||
| After Width: | Height: | Size: 76 KiB | 
| After Width: | Height: | Size: 127 KiB | 
| After Width: | Height: | Size: 70 KiB | 
| After Width: | Height: | Size: 142 KiB | 
| After Width: | Height: | Size: 3.1 MiB | 
| After Width: | Height: | Size: 3.2 MiB | 
| @@ -0,0 +1,55 @@ | ||||
| # Blender MTL File: 'None' | ||||
| # Material Count: 5 | ||||
|  | ||||
| newmtl mat_0_0 | ||||
| Ns 1.000002 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 0.686275 0.686275 0.686275 | ||||
| Ks 1.000000 1.000000 1.000000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
|  | ||||
| newmtl mat_0_1 | ||||
| Ns 1.000002 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 1.000000 1.000000 1.000000 | ||||
| Ks 1.000000 1.000000 1.000000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
| map_Kd 001.JPG | ||||
|  | ||||
| newmtl mat_0_2 | ||||
| Ns 1.000002 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 1.000000 1.000000 1.000000 | ||||
| Ks 1.000000 1.000000 1.000000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
| map_Kd 001.JPG | ||||
|  | ||||
| newmtl mat_0_3 | ||||
| Ns 1.000002 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 0.058824 0.058824 0.058824 | ||||
| Ks 1.000000 1.000000 1.000000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
|  | ||||
| newmtl mat_0_4 | ||||
| Ns 1.000002 | ||||
| Ka 1.000000 1.000000 1.000000 | ||||
| Kd 1.000000 1.000000 1.000000 | ||||
| Ks 1.000000 1.000000 1.000000 | ||||
| Ke 0.000000 0.000000 0.000000 | ||||
| Ni 1.450000 | ||||
| d 1.000000 | ||||
| illum 2 | ||||
| map_Kd 001.JPG | ||||
							
								
								
									
										17572
									
								
								Projekte/battleship/converter/src/main/resources/Models/UX23/UX23.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -60,7 +60,7 @@ class BattleState extends ClientState { | ||||
|         if (destroyedOpponentShip(msg)) | ||||
|             logic.getOpponentMap().add(msg.getDestroyedShip()); | ||||
|         if (msg.isGameOver()) { | ||||
|             msg.getRemainingOpponentShips().forEach(logic.getOwnMap()::add); | ||||
|             msg.getRemainingOpponentShips().forEach(logic.getOpponentMap()::add); | ||||
|             logic.setState(new GameOverState(logic)); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,114 @@ | ||||
| package pp.battleship.game.client; | ||||
|  | ||||
| import com.jme3.app.Application; | ||||
| import com.jme3.app.state.AbstractAppState; | ||||
| import com.jme3.app.state.AppStateManager; | ||||
| import com.jme3.asset.AssetLoadException; | ||||
| import com.jme3.asset.AssetNotFoundException; | ||||
| import com.jme3.audio.AudioData; | ||||
| import com.jme3.audio.AudioNode; | ||||
|  | ||||
| import java.lang.System.Logger; | ||||
| import java.lang.System.Logger.Level; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import static pp.util.PreferencesUtils.getPreferences; | ||||
|  | ||||
| public class GameMusic extends AbstractAppState{ | ||||
|     private static final Logger LOGGER = System.getLogger(GameMusic.class.getName()); | ||||
|     private static final Preferences PREFERENCES = getPreferences(GameMusic.class); | ||||
|     private static final String ENABLED_PREF = "enabled"; //NON-NLS | ||||
|     private static final String VOLUME_PREF = "volume"; //NON-NLS | ||||
|  | ||||
|     private AudioNode music; | ||||
|  | ||||
|     /** | ||||
|      * Checks if sound is enabled in the preferences. | ||||
|      * | ||||
|      * @return {@code true} if sound is enabled, {@code false} otherwise. | ||||
|      */ | ||||
|     public static boolean enabledInPreferences() { | ||||
|         return PREFERENCES.getBoolean(ENABLED_PREF, true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks if sound is enabled in the preferences. | ||||
|      * | ||||
|      * | ||||
|      */ | ||||
|     public static float volumeInPreferences() { | ||||
|         return PREFERENCES.getFloat(VOLUME_PREF, 0.5f); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initializes the sound effects for the game. | ||||
|      * Overrides {@link com.jme3.app.state.AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application)} | ||||
|      * | ||||
|      * @param stateManager The state manager | ||||
|      * @param app          The application | ||||
|      */ | ||||
|     @Override | ||||
|     public void initialize(AppStateManager stateManager, Application app) { | ||||
|         super.initialize(stateManager, app); | ||||
|         music = loadSoundb(app, "Sound/Music/My_heart_will_go_on.ogg"); | ||||
|         setVolume(volumeInPreferences()); | ||||
|         music.setLooping(true); | ||||
|         if (isEnabled() && music != null) { | ||||
|             music.play(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Loads a sound from the specified file. | ||||
|      * | ||||
|      * @param app  The application | ||||
|      * @param name The name of the sound file. | ||||
|      * @return The loaded AudioNode. | ||||
|      */ | ||||
|   private AudioNode loadSoundb(Application app, String name) { | ||||
|         try{ | ||||
|             final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); | ||||
|             sound.setLooping(false); | ||||
|             sound.setPositional(false); | ||||
|             return sound; | ||||
|         } | ||||
|         catch (AssetLoadException | AssetNotFoundException ex) { | ||||
|             LOGGER.log(Level.ERROR, ex.getMessage(), ex); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Sets the enabled state of this AppState. | ||||
|      * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} | ||||
|      * | ||||
|      * @param enabled {@code true} to enable the AppState, {@code false} to disable it. | ||||
|      */ | ||||
|     @Override | ||||
|     public void setEnabled(boolean enabled) { | ||||
|         if (isEnabled() == enabled) return; | ||||
|         else if(!isEnabled() && enabled) { | ||||
|             if (music != null) music.play(); | ||||
|         } else if (isEnabled() && !enabled) { | ||||
|             if (music != null) music.stop(); | ||||
|         } | ||||
|         super.setEnabled(enabled); | ||||
|         LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS | ||||
|         PREFERENCES.putBoolean(ENABLED_PREF, enabled); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Toggles the game sound on or off. | ||||
|      */ | ||||
|     public void toggleSound() { | ||||
|         setEnabled(!isEnabled()); | ||||
|     } | ||||
|  | ||||
|     public void setVolume(float vol){ | ||||
|         music.setVolume(vol); | ||||
|         PREFERENCES.putFloat(VOLUME_PREF, vol); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,93 @@ | ||||
| package pp.battleship.game.client; | ||||
|  | ||||
| import pp.battleship.message.client.ShellAnimationFinishedMessage; | ||||
| import pp.battleship.message.server.EffectMessage; | ||||
| import pp.battleship.model.Shell; | ||||
|  | ||||
| import java.lang.System.Logger.Level; | ||||
|  | ||||
| /** | ||||
|  * This class represents the client state during a shooting animation. | ||||
|  * It handles the logic for the animation of a fired shell and ensures | ||||
|  * that the client notifies the server when the animation is complete. | ||||
|  */ | ||||
| public class ShootingAnimationState extends ClientState { | ||||
|  | ||||
|     // The shell object representing the fired shell's movement | ||||
|     private final Shell shell; | ||||
|  | ||||
|     /** | ||||
|      * Constructs the ShootingAnimationState with the given game logic and shell. | ||||
|      * | ||||
|      * @param logic the game logic instance managing the state | ||||
|      * @param shell the shell object representing the fired projectile | ||||
|      */ | ||||
|     ShootingAnimationState(ClientGameLogic logic, Shell shell) { | ||||
|         super(logic); | ||||
|         this.shell = shell; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Indicates that the battle scene should be shown in this state. | ||||
|      * | ||||
|      * @return true because the battle view is active during the shooting animation | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean showBattle() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles the effect message received from the server. | ||||
|      * Logs the received effect for debugging purposes. | ||||
|      * | ||||
|      * @param msg the effect message received from the server | ||||
|      */ | ||||
|     @Override | ||||
|     void receivedEffect(EffectMessage msg) { | ||||
|         ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS | ||||
|         // Here, you could implement additional logic to display the effects of the shot,  | ||||
|         // like a visual indication of a hit or a miss. | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called once per frame to update the shell's position and check if it has reached the target. | ||||
|      * If the shell reaches the target, a message is sent to the server. | ||||
|      * | ||||
|      * @param deltaTime time in seconds since the last update call | ||||
|      */ | ||||
|     @Override | ||||
|     public void update(float deltaTime) { | ||||
|         if (shell != null) { | ||||
|             // Update the position of the shell based on the elapsed time | ||||
|             shell.updatePosition(deltaTime); | ||||
|  | ||||
|             // Check if the shell has reached its target | ||||
|             if (shell.isAtTarget()) { | ||||
|                 // If the shell has reached the target, notify the server that the animation is complete | ||||
|                 sendAnimationCompleteMessage(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sends a message to the server indicating that the shooting animation has finished. | ||||
|      */ | ||||
|     private void sendAnimationCompleteMessage() { | ||||
|         ShellAnimationFinishedMessage message = new ShellAnimationFinishedMessage(); | ||||
|         // Send the message to the server via the game's logic communication system | ||||
|         logic.send(message); | ||||
|         logic.setState(new BattleState(logic, true)); | ||||
|         ClientGameLogic.LOGGER.log(Level.INFO, "Shell animation complete, message sent to server."); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Optionally, you can add an entry method if there is any setup that needs to happen when entering the state. | ||||
|      * By default, this is empty. | ||||
|      */ | ||||
|     @Override | ||||
|     void entry() { | ||||
|         super.entry();  // Call the parent entry method | ||||
|         ClientGameLogic.LOGGER.log(Level.INFO, "Entered ShootingAnimationState."); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,25 @@ | ||||
| /* | ||||
| package pp.battleship.game.client; | ||||
|  | ||||
|  | ||||
| public class VolumeSlider extends Slider { | ||||
|  | ||||
|     private final GameMusic music; | ||||
|  | ||||
|     private double vol; | ||||
|  | ||||
|     public VolumeSlider(GameMusic music) { | ||||
|         super(); | ||||
|         this.music = music; | ||||
|         vol = GameMusic.volumeInPreferences(); | ||||
|         getModel().setPercent(vol); | ||||
|     } | ||||
|  | ||||
|     public void update() { | ||||
|         if (vol != getModel().getPercent()) { | ||||
|             vol = getModel().getPercent(); | ||||
|             music.setVolume((float) vol); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| */ | ||||
| @@ -10,6 +10,7 @@ package pp.battleship.game.server; | ||||
| import pp.battleship.BattleshipConfig; | ||||
| import pp.battleship.message.client.ClientInterpreter; | ||||
| import pp.battleship.message.client.MapMessage; | ||||
| import pp.battleship.message.client.ShellAnimationFinishedMessage; | ||||
| import pp.battleship.message.client.ShootMessage; | ||||
| import pp.battleship.message.server.EffectMessage; | ||||
| import pp.battleship.message.server.GameDetails; | ||||
| @@ -35,6 +36,7 @@ public class ServerGameLogic implements ClientInterpreter { | ||||
|     private final BattleshipConfig config; | ||||
|     private final List<Player> players = new ArrayList<>(2); | ||||
|     private final Set<Player> readyPlayers = new HashSet<>(); | ||||
|     private Set<Integer> playersFinishedShellAnimation = new HashSet<>(); | ||||
|     private final ServerSender serverSender; | ||||
|     private Player activePlayer; | ||||
|     private ServerState state = ServerState.WAIT; | ||||
| @@ -140,12 +142,22 @@ public class ServerGameLogic implements ClientInterpreter { | ||||
|      */ | ||||
|     @Override | ||||
|     public void received(MapMessage msg, int from) { | ||||
|         List<Battleship> ships = msg.getShips(); | ||||
|         if (state != ServerState.SET_UP) | ||||
|             LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS | ||||
|         else | ||||
|             playerReady(getPlayerById(from), msg.getShips()); | ||||
|  | ||||
|  | ||||
|         if (!shipsValid(ships)){ | ||||
|             LOGGER.log(Level.ERROR, "ship placement by player {0} is Invalid", from); | ||||
|             send(getPlayerById(from),null); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         playerReady(getPlayerById(from), msg.getShips()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Handles the reception of a ShootMessage. | ||||
|      * | ||||
| @@ -217,4 +229,53 @@ public class ServerGameLogic implements ClientInterpreter { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean isInBounds(Battleship ship){ | ||||
|         return ship.getMinX() >= 0 && ship.getMaxX() < config.getMapWidth() && | ||||
|                ship.getMinY() >= 0 && ship.getMaxY() < config.getMapHeight(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     private boolean shipsValid(List<Battleship> ships) { | ||||
|  | ||||
|         Set<IntPoint> occupied = new HashSet<>(); | ||||
|  | ||||
|         for (Battleship ship : ships) { | ||||
|             if (!isInBounds(ship)){ | ||||
|                 return false; | ||||
|             } | ||||
|             for (int x = ship.getMinX(); x <= ship.getMaxX(); x++) { | ||||
|                 for (int y = ship.getMinY(); y <= ship.getMaxY(); y++) { | ||||
|                     IntPoint point = new IntPoint(x,y); | ||||
|                     if (!occupied.add(point)){ | ||||
|                         return false; | ||||
|  | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|     @Override | ||||
|     public void received(ShellAnimationFinishedMessage msg, int from) { | ||||
|         // Add the player to the set of players who have finished the animation | ||||
|         playersFinishedShellAnimation.add(from); | ||||
|  | ||||
|         // Check if both players have finished the shell animation | ||||
|         if (playersFinishedShellAnimation.size() == 2) { | ||||
|             // Clear the set of players who have finished the animation for the next shot | ||||
|             playersFinishedShellAnimation.clear(); | ||||
|  | ||||
|             // Transition back to the BATTLE state | ||||
|             setState(ServerState.BATTLE); | ||||
|  | ||||
|             // Log the completion of the shell animation | ||||
|             LOGGER.log(Level.INFO, "Both players finished shell animation. Returning to BATTLE state."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ package pp.battleship.game.singlemode; | ||||
| import pp.battleship.message.client.ClientInterpreter; | ||||
| import pp.battleship.message.client.ClientMessage; | ||||
| import pp.battleship.message.client.MapMessage; | ||||
| import pp.battleship.message.client.ShellAnimationFinishedMessage; | ||||
| import pp.battleship.message.client.ShootMessage; | ||||
| import pp.battleship.model.Battleship; | ||||
|  | ||||
| @@ -63,6 +64,11 @@ class Copycat implements ClientInterpreter { | ||||
|         copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void received(ShellAnimationFinishedMessage shellAnimationFinishedMessage, int from) { | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'received'"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a copy of the provided {@link Battleship}. | ||||
|      * | ||||
|   | ||||
| @@ -26,4 +26,5 @@ public interface ClientInterpreter { | ||||
|      * @param from the connection ID from which the message was received | ||||
|      */ | ||||
|     void received(MapMessage msg, int from); | ||||
|     void received(ShellAnimationFinishedMessage shellAnimationFinishedMessage, int from); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| package pp.battleship.message.client; | ||||
|  | ||||
| public class ShellAnimationFinishedMessage  extends ClientMessage{ | ||||
|  | ||||
|     @Override | ||||
|     public void accept(ClientInterpreter interpreter, int from) { | ||||
|         interpreter.received(this, from); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,47 @@ | ||||
| package pp.battleship.model; | ||||
|  | ||||
| import com.jme3.math.Vector3f; | ||||
|  | ||||
| public class Shell { | ||||
|     private Vector3f startPosition; | ||||
|     private Vector3f targetPosition; | ||||
|     private Vector3f currentPosition; | ||||
|     private float speed; | ||||
|     private boolean isAtTarget; | ||||
|  | ||||
|     public Shell(Vector3f startPosition, Vector3f targetPosition, float speed) { | ||||
|         this.startPosition = startPosition; | ||||
|         this.targetPosition = targetPosition; | ||||
|         this.currentPosition = new Vector3f(startPosition); | ||||
|         this.speed = speed; | ||||
|         this.isAtTarget = true; | ||||
|     } | ||||
|  | ||||
|     // Aktualisiert die Position des Geschosses basierend auf der verstrichenen Zeit | ||||
|     public void updatePosition(float deltaTime) { | ||||
|         if (!isAtTarget) { | ||||
|             // Berechne die Richtung des Geschosses | ||||
|             Vector3f direction = targetPosition.subtract(currentPosition).normalize(); | ||||
|             // Berechne die Bewegung basierend auf der Geschwindigkeit und der verstrichenen Zeit | ||||
|             Vector3f movement = direction.mult(speed * deltaTime); | ||||
|             currentPosition.addLocal(movement); | ||||
|  | ||||
|             // Prüfe, ob das Geschoss das Ziel erreicht hat | ||||
|             if (currentPosition.distance(targetPosition) < speed * deltaTime) { | ||||
|                 currentPosition.set(targetPosition); | ||||
|                 isAtTarget = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Gibt die aktuelle Position des Geschosses zurück | ||||
|     public Vector3f getCurrentPosition() { | ||||
|         return currentPosition; | ||||
|     } | ||||
|  | ||||
|     // Überprüft, ob das Geschoss das Ziel erreicht hat | ||||
|     public boolean isAtTarget() { | ||||
|         return isAtTarget; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,46 @@ | ||||
| package pp.battleship.model; | ||||
|  | ||||
| import com.jme3.renderer.RenderManager; | ||||
| import com.jme3.renderer.ViewPort; | ||||
| import com.jme3.scene.control.AbstractControl; | ||||
|  | ||||
| public class ShellControl extends AbstractControl { | ||||
|  | ||||
|     private Shell shell;   // Das Shell-Objekt, das die Bewegung des Geschosses enthält | ||||
|  | ||||
|     public ShellControl(Shell shell) { | ||||
|         this.shell = shell; | ||||
|     } | ||||
|  | ||||
|     // Die Methode wird in jedem Frame aufgerufen, um die Logik zu aktualisieren | ||||
|     @Override | ||||
|     protected void controlUpdate(float deltaTime) { | ||||
|         if (shell != null) { | ||||
|             // Aktualisiere die Position des Geschosses basierend auf der verstrichenen Zeit | ||||
|             shell.updatePosition(deltaTime); | ||||
|  | ||||
|             // Setze die neue Position des Geschosses im 3D-Raum | ||||
|             spatial.setLocalTranslation(shell.getCurrentPosition()); | ||||
|  | ||||
|             // Optionale Animation oder Effekte hinzufügen (z.B. Rauch oder Funkenflug) | ||||
|             // addParticleEffects(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Setze das Shell-Objekt neu, um es während des Spiels zu ändern | ||||
|     public void setShell(Shell shell) { | ||||
|         this.shell = shell; | ||||
|     } | ||||
|  | ||||
|     // Gibt das aktuell verwendete Shell-Objekt zurück | ||||
|     public Shell getShell() { | ||||
|         return this.shell; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void controlRender(RenderManager rm, ViewPort vp) { | ||||
|         // TODO Auto-generated method stub | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -7,15 +7,16 @@ | ||||
|  | ||||
| package pp.battleship.model; | ||||
|  | ||||
| import pp.battleship.notification.GameEvent; | ||||
| import pp.battleship.notification.GameEventBroker; | ||||
| import pp.battleship.notification.ItemAddedEvent; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| import pp.battleship.notification.GameEvent; | ||||
| import pp.battleship.notification.GameEventBroker; | ||||
| import pp.battleship.notification.ItemAddedEvent; | ||||
| import pp.battleship.notification.ItemRemovedEvent; | ||||
|  | ||||
| /** | ||||
|  * Represents a rectangular map that holds ships and registers shots fired. | ||||
|  * It also supports event notification for game state changes such as item addition or removal. | ||||
| @@ -97,7 +98,7 @@ public class ShipMap { | ||||
|      */ | ||||
|     public void remove(Item item) { | ||||
|         items.remove(item); | ||||
|         notifyListeners(new ItemAddedEvent(item, this)); | ||||
|         notifyListeners(new ItemRemovedEvent(item, this)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -37,3 +37,4 @@ dialog.error=Error | ||||
| dialog.question=Question | ||||
| port.must.be.integer=Port must be an integer number | ||||
| map.doesnt.fit=The map doesn't fit to this game | ||||
| client.server-start=Start server | ||||
| @@ -4,7 +4,7 @@ | ||||
| ## www.unibw.de/inf2 | ||||
| ## (c) Mark Minas (mark.minas@unibw.de) | ||||
| ######################################## | ||||
| # | ||||
|  | ||||
| battleship.name=Schiffe versenken | ||||
| button.ready=Bereit | ||||
| button.rotate=Rotiere | ||||
| @@ -37,3 +37,4 @@ dialog.error=Fehler | ||||
| dialog.question=Frage | ||||
| port.must.be.integer=Der Port muss eine ganze Zahl sein | ||||
| map.doesnt.fit=Diese Karte passt nicht zu diesem Spiel | ||||
| client.server-start=Server starten | ||||
|   | ||||
| @@ -54,6 +54,8 @@ public class ShipMapTest { | ||||
|         verify(mockBroker).notifyListeners(any(ItemAddedEvent.class)); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     @Test | ||||
|     public void testRemoveItem() { | ||||
|         map.add(battleship); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
|  | ||||
| //////////////////////////////////////// | ||||
| // Programming project code | ||||
| // UniBw M, 2022, 2023, 2024 | ||||
| @@ -5,7 +6,7 @@ | ||||
| // (c) Mark Minas (mark.minas@unibw.de) | ||||
| //////////////////////////////////////// | ||||
|  | ||||
| package pp.battleship.server; | ||||
|         package pp.battleship.server; | ||||
|  | ||||
| import com.jme3.network.ConnectionListener; | ||||
| import com.jme3.network.HostedConnection; | ||||
| @@ -176,4 +177,4 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn | ||||
|         else | ||||
|             LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,9 @@ dependencyResolutionManagement { | ||||
|             library('jme3-plugins', 'org.jmonkeyengine', 'jme3-plugins').versionRef('jme') | ||||
|             library('jme3-jogg', 'org.jmonkeyengine', 'jme3-jogg').versionRef('jme') | ||||
|             library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme') | ||||
|             library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme') | ||||
|  | ||||
|  | ||||
|             library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme') | ||||
|             library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme') | ||||
|  | ||||
|   | ||||