diff --git a/Projekte/battleship/client/build.gradle b/Projekte/battleship/client/build.gradle index a8aeff3c..b4e41920 100644 --- a/Projekte/battleship/client/build.gradle +++ b/Projekte/battleship/client/build.gradle @@ -9,7 +9,6 @@ implementation project(":jme-common") implementation project(":battleship:model") implementation libs.jme3.desktop - runtimeOnly libs.jme3.awt.dialogs runtimeOnly libs.jme3.plugins runtimeOnly libs.jme3.jogg diff --git a/Projekte/battleship/client/client.properties b/Projekte/battleship/client/client.properties index 4403fa3b..89d59df4 100644 --- a/Projekte/battleship/client/client.properties +++ b/Projekte/battleship/client/client.properties @@ -9,7 +9,7 @@ # # Specifies the map used by the opponent in single mode. # Single mode is activated if this property is set. -#map.opponent=maps/map2.json +map.opponent=maps/map2.json # # Specifies the map used by the player in single mode. # The player must define their own map if this property is not set. diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java index b505e4d2..6e18db31 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java @@ -53,11 +53,12 @@ class NetworkDialog extends SimpleDialog { host.setPreferredWidth(400f); port.setSingleLine(true); - final BattleshipApp app = network.getApp(); - final Container input = new Container(new SpringGridLayout()); Checkbox hostServer = new Checkbox(lookup("start.own.server")); hostServer.setChecked(false); hostServer.addClickCommands(s->toggleOwnServer()); + + final BattleshipApp app = network.getApp(); + final Container input = new Container(new SpringGridLayout()); input.addChild(new Label(lookup("host.name") + ": ")); input.addChild(host, 1); input.addChild(new Label(lookup("port.number") + ": ")); @@ -182,7 +183,7 @@ private void connect() { private void startServer() { new Thread(() -> { try{ - BattleshipServer battleshipServer = new BattleshipServer(); + BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText())); battleshipServer.run(); } catch (Exception e) { LOGGER.log(Level.ERROR,e); diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/HitEffectHandler.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/HitEffectHandler.java new file mode 100644 index 00000000..f0b81e6e --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/HitEffectHandler.java @@ -0,0 +1,181 @@ +package pp.battleship.client.gui; + +import com.jme3.app.Application; +import com.jme3.asset.AssetManager; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.control.AbstractControl; +import pp.battleship.model.Shot; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + + +public class HitEffectHandler { + private final AssetManager assetManager; + private static final Logger LOGGER = System.getLogger(HitEffectHandler.class.getName()); + + /** + * Constructor for the HitEffectHandler class + * @param app the main application + */ + public HitEffectHandler(Application app){ + assetManager = app.getAssetManager(); + } + + /** + * Creates an explosion effect when ship gets hit + * @param battleshipNode the node of the ship that gets hit and the effect should be attached to + * @param shot The shot taken on a field + */ + public void hitEffect(Node battleshipNode, Shot shot){ + //Explosion + ParticleEmitter explosion = new ParticleEmitter("Explosion", Type.Triangle, 30); + explosion.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md")); + explosion.setImagesX(2); + explosion.setImagesY(2); + explosion.setStartColor(new ColorRGBA(0.96f, 0.82f, 0.6f, 1f)); + explosion.setEndColor(new ColorRGBA(0.88f, 0.32f, 0.025f, 1f)); + explosion.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0)); + explosion.setStartSize(0.45f); + explosion.setEndSize(0.1f); + explosion.setGravity(0, -0.5f, 0); + explosion.setLowLife(1f); + explosion.setHighLife(3.5f); + explosion.setParticlesPerSec(0); + explosion.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f); + explosion.emitAllParticles(); + + //Debris + ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 6); + Material debrisMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + debrisMaterial.setTexture("Texture", assetManager.loadTexture("Textures/Debris/debris.png")); + debris.setMaterial(debrisMaterial); + debris.setImagesX(2); + debris.setImagesY(2); + debris.setStartColor(ColorRGBA.DarkGray); + debris.setEndColor(ColorRGBA.Gray); + debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.25f, 2f, 0.25f)); + debris.setStartSize(1.2f); + debris.setEndSize(0.8f); + debris.setGravity(0, 1.5f, 0); + debris.getParticleInfluencer().setVelocityVariation(0.3f); + debris.setLowLife(1f); + debris.setHighLife(3.5f); + debris.setParticlesPerSec(0); + debris.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f); + debris.emitAllParticles(); + + //Fire //TODO Fix creation of fire on the edge of the map + ParticleEmitter fire = new ParticleEmitter("Fire", Type.Triangle, 30); + Material fireMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + fireMaterial.setTexture("Texture", assetManager.loadTexture("Textures/Fire/fire.png")); + fire.setMaterial(fireMaterial); + fire.setImagesX(2); + fire.setImagesY(2); + fire.setStartColor(ColorRGBA.Orange); + fire.setEndColor(ColorRGBA.Red); + fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0)); + fire.setStartSize(0.2f); + fire.setEndSize(0.05f); + fire.setLowLife(1f); + fire.setHighLife(2f); + fire.getParticleInfluencer().setVelocityVariation(0.2f); + fire.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); + fire.getLocalTranslation().subtractLocal(battleshipNode.getLocalTranslation()); + fire.emitAllParticles(); + +// LOGGER.log(Level.DEBUG, "Created HitEffect at {0}", explosion.getLocalTranslation().toString()); +// LOGGER.log(Level.DEBUG, "Created HitEffect at {0}", debris.getLocalTranslation().toString()); + LOGGER.log(Level.INFO, "Created HitEffect at {0}", fire.getLocalTranslation().toString()); + + + battleshipNode.attachChild(explosion); + explosion.addControl(new EffectControl(explosion, battleshipNode)); + battleshipNode.attachChild(fire); + fire.addControl(new EffectControl(fire, battleshipNode)); + battleshipNode.attachChild(debris); + debris.addControl(new EffectControl(debris, battleshipNode)); + } + + /** + * Creates a splash effect if shot hits the water + * @param shot The shot taken on a field + */ + public ParticleEmitter missEffect(Shot shot){ + ParticleEmitter missEffect = new ParticleEmitter("HitEffect", Type.Triangle, 45); + missEffect.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md")); + missEffect.setImagesX(2); + missEffect.setImagesY(2); + missEffect.setStartColor(new ColorRGBA(0.067f, 0.06f, 0.37f, 0.87f)); + missEffect.setEndColor(new ColorRGBA(0.32f, 0.55f, 0.87f, 0.79f)); + missEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0)); + missEffect.setStartSize(0.1f); + missEffect.setEndSize(0.08f); + missEffect.setGravity(0, 0.36f, 0); + missEffect.setLowLife(0.7f); + missEffect.setHighLife(1.8f); + missEffect.setParticlesPerSec(0); + missEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f); + + LOGGER.log(Level.DEBUG, "Created MissEffect at {0}", missEffect.getLocalTranslation().toString()); + + missEffect.emitAllParticles(); + missEffect.addControl(new EffectControl(missEffect)); + return missEffect; + } + + /** + * Inner class to control effects + */ + private static class EffectControl extends AbstractControl { + private final ParticleEmitter emitter; + private final Node parentNode; + + /** + * Constructor used to attach effect to a node + * + * @param emitter the particle emitter to be controlled + * @param parentNode the node to be attached + */ + public EffectControl(ParticleEmitter emitter, Node parentNode) { + this.emitter = emitter; + this.parentNode = parentNode; + } + + /** + * Constructor used if the effect shouldn't be attached to a node + * + * @param emitter the particle emitter to be controlled + */ + public EffectControl(ParticleEmitter emitter){ + this.emitter = emitter; + this.parentNode = null; + } + + /** + * Checks if the Effect is not rendered anymore so it can be removed + * + * @param tpf time per frame (in seconds) + */ + @Override + protected void controlUpdate(float tpf) { + if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) { + if (parentNode != null) + parentNode.detachChild(emitter); + } + } + + /** + * @param rm the RenderManager rendering the controlled Spatial (not null) + * @param vp the ViewPort being rendered (not null) + */ + @Override + protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {} + } + +} diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java index 05a60aa5..d6f4aaf0 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java @@ -44,6 +44,7 @@ class SeaSynchronizer extends ShipMapSynchronizer { private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray; private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f); private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f); + private HitEffectHandler hitEffectHandler; private final ShipMap map; private final BattleshipApp app; @@ -59,6 +60,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) { super(app.getGameLogic().getOwnMap(), root); this.app = app; this.map = map; + hitEffectHandler = new HitEffectHandler(app); addExisting(); } @@ -72,7 +74,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) { */ @Override public Spatial visit(Shot shot) { - return shot.isHit() ? handleHit(shot) : createCylinder(shot); + return shot.isHit() ? handleHit(shot) : hitEffectHandler.missEffect(shot); } /** @@ -87,9 +89,7 @@ private Spatial handleHit(Shot shot) { 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); + hitEffectHandler.hitEffect(shipNode, shot); return null; } diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java index 81acfbb1..0c0ad845 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java @@ -14,6 +14,9 @@ import com.jme3.scene.control.AbstractControl; import pp.battleship.model.Battleship; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + import static pp.util.FloatMath.DEG_TO_RAD; import static pp.util.FloatMath.TWO_PI; import static pp.util.FloatMath.sin; @@ -47,6 +50,12 @@ class ShipControl extends AbstractControl { * The current time within the oscillation cycle, used to calculate the ship's pitch angle. */ private float time; + /** + * Ship to be controlled + */ + private final Battleship battleship; + + private static final Logger LOGGER = System.getLogger(ShipControl.class.getName()); /** * Constructs a new ShipControl instance for the specified Battleship. @@ -56,6 +65,7 @@ class ShipControl extends AbstractControl { * @param ship the Battleship object to control */ public ShipControl(Battleship ship) { + this.battleship = ship; // Determine the axis of rotation based on the ship's orientation axis = switch (ship.getRot()) { case LEFT, RIGHT -> Vector3f.UNIT_X; @@ -63,7 +73,7 @@ public ShipControl(Battleship ship) { }; // Set the cycle duration and amplitude based on the ship's length - cycle = ship.getLength() * 2f; + cycle = battleship.getLength() * 2f; amplitude = 5f * DEG_TO_RAD / ship.getLength(); } @@ -78,14 +88,23 @@ protected void controlUpdate(float tpf) { // If spatial is null, do nothing if (spatial == null) return; - // Update the time within the oscillation cycle - time = (time + tpf) % cycle; + if(battleship.isDestroyed() && spatial.getLocalTranslation().getY() < -0.6f){ + LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName()); + spatial.getParent().detachChild(spatial); + } + else if (battleship.isDestroyed()) { + spatial.move(0,-0.2f*tpf,0); + } + else { + // Update the time within the oscillation cycle + time = (time + tpf) % cycle; - // Calculate the current angle of the oscillation - final float angle = amplitude * sin(time * TWO_PI / cycle); + // Calculate the current angle of the oscillation + final float angle = amplitude * sin(time * TWO_PI / cycle); - // Update the pitch Quaternion with the new angle - pitch.fromAngleAxis(angle, axis); + // Update the pitch Quaternion with the new angle + pitch.fromAngleAxis(angle, axis); + } // Apply the pitch rotation to the spatial spatial.setLocalRotation(pitch); diff --git a/Projekte/battleship/client/src/main/java/server/BattleshipServer.java b/Projekte/battleship/client/src/main/java/server/BattleshipServer.java index d384640e..95b66bda 100644 --- a/Projekte/battleship/client/src/main/java/server/BattleshipServer.java +++ b/Projekte/battleship/client/src/main/java/server/BattleshipServer.java @@ -45,6 +45,8 @@ public class BattleshipServer implements MessageListener, Conn private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName()); private static final File CONFIG_FILE = new File("server.properties"); + private static int port; + private final BattleshipConfig config = new BattleshipConfig(); private Server myServer; private final ServerGameLogic logic; @@ -62,18 +64,12 @@ public class BattleshipServer implements MessageListener, Conn } } - /** - * Starts the Battleships server. - */ - public static void main(String[] args) { - new BattleshipServer().run(); - } - /** * Creates the server. */ - public BattleshipServer() { + public BattleshipServer(int port) { config.readFromIfExists(CONFIG_FILE); + BattleshipServer.port = port; LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS logic = new ServerGameLogic(this, config); } @@ -87,7 +83,7 @@ public void run() { private void startServer() { try { LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS - myServer = Network.createServer(config.getPort()); + myServer = Network.createServer(port); initializeSerializables(); myServer.start(); registerListeners(); diff --git a/Projekte/battleship/client/src/main/resources/Textures/Debris/debris.png b/Projekte/battleship/client/src/main/resources/Textures/Debris/debris.png new file mode 100644 index 00000000..3f6698a0 Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Textures/Debris/debris.png differ diff --git a/Projekte/battleship/client/src/main/resources/Textures/Fire/fire.png b/Projekte/battleship/client/src/main/resources/Textures/Fire/fire.png new file mode 100644 index 00000000..42027e53 Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Textures/Fire/fire.png differ diff --git a/Projekte/settings.gradle b/Projekte/settings.gradle index 7440607c..bb7e8c72 100644 --- a/Projekte/settings.gradle +++ b/Projekte/settings.gradle @@ -19,6 +19,7 @@ library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme') library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme') library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme') + library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme') library('lemur', 'com.simsilica:lemur:1.16.0') library('lemur-proto', 'com.simsilica:lemur-proto:1.13.0')