15 Commits

Author SHA1 Message Date
Tamino Mueller
e0bea47583 task 13 2024-10-14 08:39:59 +02:00
Tamino Mueller
38979bdf86 effect when opponent miss the ship 2024-10-14 08:15:02 +02:00
Tamino Mueller
68beedcf29 effect when opponent miss the ship 2024-10-14 08:12:56 +02:00
Tamino Mueller
7d70e8bd13 effect when opponent miss the ship 2024-10-14 08:12:37 +02:00
Tamino Mueller
0fd0555dea add effects when ships are hit 2024-10-14 08:07:57 +02:00
Tamino Mueller
b962444506 task 11 2024-10-13 20:58:18 +02:00
Tamino Mueller
c0a3e8b37a task 10 2024-10-13 20:34:01 +02:00
Tamino Mueller
c8621e02c6 insert music 2024-10-13 19:56:12 +02:00
Tamino Mueller
25d7884cad fixed sizes of the boats 2024-10-13 17:34:43 +02:00
Tamino Mueller
2192f6dbc3 fixed sizes of the boats 2024-10-11 10:22:17 +02:00
Tamino Mueller
2b8bfb82ba added last boat und finished task 9 2024-10-11 02:47:38 +02:00
Tamino Mueller
70ed981ea2 added boat with length 1 and 3 2024-10-11 02:23:59 +02:00
Tamino Mueller
d5450df77c added alienboat 2024-10-11 01:39:25 +02:00
Tamino Mueller
1f75f7bf30 Task 8 2024-10-08 20:49:19 +02:00
Tamino Mueller
1ac55a9570 Aufgabe 7 2024-10-07 00:57:00 +02:00
74 changed files with 147844 additions and 20 deletions

BIN
Projekte/Alienship.j3o Normal file

Binary file not shown.

BIN
Projekte/Boje.j3o Normal file

Binary file not shown.

BIN
Projekte/KingGeorgeV.j3o Normal file

Binary file not shown.

BIN
Projekte/Marlow66.j3o Normal file

Binary file not shown.

Binary file not shown.

BIN
Projekte/UX23.j3o Normal file

Binary file not shown.

View 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

View File

@@ -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();

View File

@@ -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.

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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();
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -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();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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));
}
}

View File

@@ -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);
}
}

View File

@@ -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.");
}
}

View File

@@ -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);
}
}
}
*/

View File

@@ -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.");
}
}
}

View File

@@ -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}.
*

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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));
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -54,6 +54,8 @@ public class ShipMapTest {
verify(mockBroker).notifyListeners(any(ItemAddedEvent.class));
}
@Test
public void testRemoveItem() {
map.add(battleship);

View File

@@ -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
}
}
}

View File

@@ -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')