22 Commits

Author SHA1 Message Date
Johannes Schmelz
0f44d37d4a Revert "states for shell logic implemented"
This reverts commit f248a77c81
2024-10-14 12:34:49 +00:00
Johannes Schmelz
f248a77c81 states for shell logic implemented 2024-10-14 03:38:04 +02:00
Johannes Schmelz
aa9c073931 added sinking animation for destroyed ships 2024-10-13 20:52:33 +02:00
Johannes Schmelz
1740988629 added a miss effect 2024-10-11 16:00:15 +02:00
Johannes Schmelz
dcc7cf9c20 added rudimentary effects when ships are hit 2024-10-11 00:43:22 +02:00
Johannes Schmelz
5501c0716d clients can now start server in thread 2024-10-09 16:29:59 +02:00
Johannes Schmelz
8261e1b3b2 clen up 2024-10-09 16:05:37 +02:00
Johannes Schmelz
5cca8f5c05 refactor and documentation 2024-10-08 19:57:37 +02:00
Johannes Schmelz
133921cfbb added background music 2024-10-08 17:32:37 +02:00
Johannes Schmelz
4cf14d02ee reverted commit 35f154aa 2024-10-06 18:33:20 +02:00
Johannes Schmelz
50bee91775 tweaked ship positions 2024-10-06 18:29:09 +02:00
Johannes Schmelz
a656ab5062 clean up 2024-10-06 18:21:55 +02:00
Johannes Schmelz
b2a6f86fc2 added 3d model for ship with length 2 2024-10-06 18:21:40 +02:00
Johannes Schmelz
9f90d92198 added 3d model for ship with length 3 2024-10-06 16:52:43 +02:00
Johannes Schmelz
3e913f636c added small boat 2024-10-06 15:39:06 +02:00
Johannes Schmelz
806c00c94a server now shuts down with invalid ship placement 2024-10-06 00:37:20 +02:00
Johannes Schmelz
4fff32c13e added server side map loading verification 2024-10-06 00:18:36 +02:00
Johannes Schmelz
1c99117ca0 reviced() did not update the OpponentPlayer ships list corretly 2024-10-05 23:46:08 +02:00
Johannes Schmelz
62421e87cc fixed ConcurrentModificationException in clear() 2024-10-05 20:53:07 +02:00
Johannes Schmelz
e343074240 fixed remove 2024-10-05 13:14:20 +02:00
Johannes Schmelz
35f154aa0f fixed clear 2024-10-05 13:13:48 +02:00
Johannes Schmelz
4488911e82 Fixing branch setup to match main 2024-10-05 11:58:12 +02:00
188 changed files with 1243988 additions and 147388 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -10,8 +10,6 @@ dependencies {
implementation libs.jme3.desktop implementation libs.jme3.desktop
implementation libs.jme3.effects implementation libs.jme3.effects
implementation project(path: ':battleship:server')
implementation project(path: ':battleship:server')
runtimeOnly libs.jme3.awt.dialogs runtimeOnly libs.jme3.awt.dialogs
runtimeOnly libs.jme3.plugins runtimeOnly libs.jme3.plugins

View File

@@ -22,10 +22,10 @@ import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.style.BaseStyles; import com.simsilica.lemur.style.BaseStyles;
import pp.battleship.client.gui.BattleAppState; import pp.battleship.client.gui.BattleAppState;
import pp.battleship.client.gui.EditorAppState; import pp.battleship.client.gui.EditorAppState;
import pp.battleship.client.gui.GameMusic;
import pp.battleship.client.gui.SeaAppState; import pp.battleship.client.gui.SeaAppState;
import pp.battleship.game.client.BattleshipClient; import pp.battleship.game.client.BattleshipClient;
import pp.battleship.game.client.ClientGameLogic; import pp.battleship.game.client.ClientGameLogic;
import pp.battleship.game.client.GameMusic;
import pp.battleship.game.client.ServerConnection; import pp.battleship.game.client.ServerConnection;
import pp.battleship.game.singlemode.BattleshipClientConfig; import pp.battleship.game.singlemode.BattleshipClientConfig;
import pp.battleship.game.singlemode.ServerConnectionMockup; import pp.battleship.game.singlemode.ServerConnectionMockup;
@@ -266,24 +266,14 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
flyCam.setEnabled(false); flyCam.setEnabled(false);
stateManager.detach(stateManager.getState(StatsAppState.class)); stateManager.detach(stateManager.getState(StatsAppState.class));
stateManager.detach(stateManager.getState(DebugKeysAppState.class)); stateManager.detach(stateManager.getState(DebugKeysAppState.class));
atttachGameMusic();
attachGameSound(); attachGameSound();
attachGameMusic();
stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); 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. * Attaches the game sound state and sets its initial enabled state.
*
*/ */
private void attachGameSound() { private void attachGameSound() {
final GameSound gameSound = new GameSound(); final GameSound gameSound = new GameSound();
@@ -292,6 +282,15 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
stateManager.attach(gameSound); stateManager.attach(gameSound);
} }
/**
* Attaches the background music state and sets its initial enabled state.
*/
private void attachGameMusic() {
final GameMusic gameSound = new GameMusic();
gameSound.setEnabled(GameMusic.enabledInPreferences());
stateManager.attach(gameSound);
}
/** /**
* Updates the application state every frame. * Updates the application state every frame.
* This method is called once per frame during the game loop. * This method is called once per frame during the game loop.

View File

@@ -12,8 +12,6 @@ import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager; import com.jme3.app.state.AppStateManager;
import pp.battleship.game.client.ClientGameLogic; import pp.battleship.game.client.ClientGameLogic;
/** /**
* Abstract class representing a state in the Battleship game. * Abstract class representing a state in the Battleship game.
* Extends the AbstractAppState from jMonkeyEngine to manage state behavior. * 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. * @param name The name of the sound file.
* @return The loaded AudioNode. * @return The loaded AudioNode.
*/ */
public AudioNode loadSound(Application app, String name) { private AudioNode loadSound(Application app, String name) {
try { try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false); sound.setLooping(false);

View File

@@ -11,7 +11,9 @@ import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox; import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Label; import com.simsilica.lemur.Label;
import com.simsilica.lemur.style.ElementId; import com.simsilica.lemur.style.ElementId;
import pp.battleship.game.client.GameMusic;
import pp.battleship.client.gui.GameMusic;
import pp.battleship.client.gui.VolumeSlider;
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel; import pp.dialog.StateCheckboxModel;
import pp.dialog.TextInputDialog; import pp.dialog.TextInputDialog;
@@ -31,39 +33,33 @@ import static pp.util.PreferencesUtils.getPreferences;
class Menu extends Dialog { class Menu extends Dialog {
private static final Preferences PREFERENCES = getPreferences(Menu.class); private static final Preferences PREFERENCES = getPreferences(Menu.class);
private static final String LAST_PATH = "last.file.path"; private static final String LAST_PATH = "last.file.path";
// private final VolumeSlider slider;
private final BattleshipApp app; private final BattleshipApp app;
private final Button loadButton = new Button(lookup("menu.map.load")); private final Button loadButton = new Button(lookup("menu.map.load"));
private final Button saveButton = new Button(lookup("menu.map.save")); private final Button saveButton = new Button(lookup("menu.map.save"));
private final VolumeSlider slider;
/** /**
* Constructs the Menu dialog for the Battleship application. * Constructs the Menu dialog for the Battleship application.
* *
* @param app the BattleshipApp instance * @param app the BattleshipApp instance
*/ */
public Menu(BattleshipApp app) { public Menu(BattleshipApp app) {
super(app.getDialogManager()); super(app.getDialogManager());
this.app = app; this.app = app;
slider = new VolumeSlider(app.getStateManager().getState(GameMusic.class));
addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS 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)
.addClickCommands(s -> ifTopDialog(this::saveDialog));
addChild(new Button(lookup("menu.return-to-game")))
.addClickCommands(s -> ifTopDialog(this::close));
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.sound-enabled"), new StateCheckboxModel(app, GameSound.class)));
addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class))); addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class)));
// addChild(slider);
addChild(slider);
addChild(loadButton).addClickCommands(s -> ifTopDialog(this::loadDialog));
addChild(saveButton).addClickCommands(s -> ifTopDialog(this::saveDialog));
addChild(new Button(lookup("menu.return-to-game"))).addClickCommands(s -> ifTopDialog(this::close));
addChild(new Button(lookup("menu.quit"))).addClickCommands(s -> ifTopDialog(app::closeApp));
update();
} }
/** /**
@@ -75,14 +71,11 @@ class Menu extends Dialog {
saveButton.setEnabled(app.getGameLogic().maySaveMap()); saveButton.setEnabled(app.getGameLogic().maySaveMap());
} }
/* @Override @Override
public void update(float delta) { public void update(float delta) {
slider.update(); slider.update();
} }
*/
/** /**
* As an escape action, this method closes the menu if it is the top dialog. * As an escape action, this method closes the menu if it is the top dialog.
*/ */

View File

@@ -12,6 +12,7 @@ import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label; import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField; import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.SpringGridLayout; import com.simsilica.lemur.component.SpringGridLayout;
import pp.battleship.server.BattleshipServer; import pp.battleship.server.BattleshipServer;
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.DialogBuilder; import pp.dialog.DialogBuilder;
@@ -35,9 +36,10 @@ class NetworkDialog extends SimpleDialog {
private final NetworkSupport network; private final NetworkSupport network;
private final TextField host = new TextField(LOCALHOST); private final TextField host = new TextField(LOCALHOST);
private final TextField port = new TextField(DEFAULT_PORT); private final TextField port = new TextField(DEFAULT_PORT);
// private final Button serverButton = new Button(lookup("client.server-star"));
private final Button serverButton = new Button(lookup("client.server-start"));
private String hostname; private String hostname;
private int portNumber; private int portNumber;
private final Button serverButton = new Button(lookup("client.server-start"));
private Future<Object> connectionFuture; private Future<Object> connectionFuture;
private Dialog progressDialog; private Dialog progressDialog;
@@ -68,11 +70,10 @@ class NetworkDialog extends SimpleDialog {
.setOkClose(false) .setOkClose(false)
.setNoClose(false) .setNoClose(false)
.build(this); .build(this);
//Add the button to start the sever
addChild(serverButton).addClickCommands(s -> ifTopDialog(this::startServerInThread)); 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. * Handles the action for the connect button in the connection dialog.
@@ -157,6 +158,7 @@ class NetworkDialog extends SimpleDialog {
network.getApp().errorDialog(lookup("server.connection.failed")); network.getApp().errorDialog(lookup("server.connection.failed"));
network.getApp().setInfoText(e.getLocalizedMessage()); network.getApp().setInfoText(e.getLocalizedMessage());
} }
/** /**
* Starts the server in a separate thread. * Starts the server in a separate thread.
*/ */
@@ -173,5 +175,4 @@ class NetworkDialog extends SimpleDialog {
}); });
serverThread.start(); serverThread.start();
} }
} }

View File

@@ -1,4 +1,10 @@
package pp.battleship.game.client; package pp.battleship.client.gui;
import static pp.util.PreferencesUtils.getPreferences;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState; import com.jme3.app.state.AbstractAppState;
@@ -8,12 +14,6 @@ import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData; import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode; 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{ public class GameMusic extends AbstractAppState{
private static final Logger LOGGER = System.getLogger(GameMusic.class.getName()); private static final Logger LOGGER = System.getLogger(GameMusic.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameMusic.class); private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
@@ -34,7 +34,7 @@ public class GameMusic extends AbstractAppState{
/** /**
* Checks if sound is enabled in the preferences. * Checks if sound is enabled in the preferences.
* *
* * @return float to which the volume is set
*/ */
public static float volumeInPreferences() { public static float volumeInPreferences() {
return PREFERENCES.getFloat(VOLUME_PREF, 0.5f); return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
@@ -42,7 +42,7 @@ public class GameMusic extends AbstractAppState{
/** /**
* Initializes the sound effects for the game. * Initializes the sound effects for the game.
* Overrides {@link com.jme3.app.state.AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application)} * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
* *
* @param stateManager The state manager * @param stateManager The state manager
* @param app The application * @param app The application
@@ -50,7 +50,7 @@ public class GameMusic extends AbstractAppState{
@Override @Override
public void initialize(AppStateManager stateManager, Application app) { public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app); super.initialize(stateManager, app);
music = loadSoundb(app, "Sound/Music/My_heart_will_go_on.ogg"); music = loadSound(app, "Sound/background.ogg");
setVolume(volumeInPreferences()); setVolume(volumeInPreferences());
music.setLooping(true); music.setLooping(true);
if (isEnabled() && music != null) { if (isEnabled() && music != null) {
@@ -65,7 +65,7 @@ public class GameMusic extends AbstractAppState{
* @param name The name of the sound file. * @param name The name of the sound file.
* @return The loaded AudioNode. * @return The loaded AudioNode.
*/ */
private AudioNode loadSoundb(Application app, String name) { private AudioNode loadSound(Application app, String name) {
try { try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false); sound.setLooping(false);
@@ -78,8 +78,6 @@ public class GameMusic extends AbstractAppState{
return null; return null;
} }
/** /**
* Sets the enabled state of this AppState. * Sets the enabled state of this AppState.
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
@@ -89,11 +87,15 @@ public class GameMusic extends AbstractAppState{
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) return; if (isEnabled() == enabled) return;
else if(!isEnabled() && enabled) {
if (music != null) music.play(); if (music != null) {
} else if (isEnabled() && !enabled) { if (enabled) {
if (music != null) music.stop(); music.play();
} else {
music.stop();
} }
}
super.setEnabled(enabled); super.setEnabled(enabled);
LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
PREFERENCES.putBoolean(ENABLED_PREF, enabled); PREFERENCES.putBoolean(ENABLED_PREF, enabled);
@@ -106,9 +108,12 @@ public class GameMusic extends AbstractAppState{
setEnabled(!isEnabled()); setEnabled(!isEnabled());
} }
/**
* Sets the volume of music
* @param vol the volume to which the music should be set
*/
public void setVolume(float vol){ public void setVolume(float vol){
music.setVolume(vol); music.setVolume(vol);
PREFERENCES.putFloat(VOLUME_PREF, vol); PREFERENCES.putFloat(VOLUME_PREF, vol);
} }
} }

View File

@@ -8,12 +8,10 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.Shell;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
import pp.util.Position; import pp.util.Position;
@@ -38,7 +36,6 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
// The MapView associated with this synchronizer // The MapView associated with this synchronizer
private final MapView view; private final MapView view;
private Shell shell;
/** /**
* Constructs a new MapViewSynchronizer for the given MapView. * Constructs a new MapViewSynchronizer for the given MapView.
@@ -125,16 +122,4 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) { 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); 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

@@ -240,6 +240,7 @@ public class ParticleEffectFactory {
return smokeEmitter; return smokeEmitter;
} }
/** /**
* Creates a one-time water splash particle emitter. * Creates a one-time water splash particle emitter.
* *
@@ -281,5 +282,4 @@ public class ParticleEffectFactory {
return waterSplash; return waterSplash;
} }
} }

View File

@@ -22,6 +22,7 @@ import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation; import pp.battleship.model.Rotation;
import pp.battleship.model.ShipMap; import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static pp.util.FloatMath.HALF_PI; import static pp.util.FloatMath.HALF_PI;
import static pp.util.FloatMath.PI; import static pp.util.FloatMath.PI;
@@ -34,13 +35,10 @@ import static pp.util.FloatMath.PI;
*/ */
class SeaSynchronizer extends ShipMapSynchronizer { class SeaSynchronizer extends ShipMapSynchronizer {
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
private static final String UX23 = "Models/UX23/UX23.j3o"; private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
private static final String BOJE = "Models/Boje/Boje.j3o"; private static final String BOAT_SMALL_MODEL = "Models/BoatSmall/12219_boat_v2_L2.j3o"; //NON-NLS
private static final String CV_MODEL = "Models/CV/CV.j3o"; //NON-NLS
private static final String ALIENSHIP = "Models/Alienship/Alienship.j3o"; private static final String BATTLE_MODEL = "Models/Battle/Battle.j3o"; //NON-NLS
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 COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS private static final String SHIP = "ship"; //NON-NLS
private static final String SHOT = "shot"; //NON-NLS private static final String SHOT = "shot"; //NON-NLS
@@ -80,13 +78,49 @@ class SeaSynchronizer extends ShipMapSynchronizer {
return shot.isHit() ? handleHit(shot) : handleMiss(shot); return shot.isHit() ? handleHit(shot) : handleMiss(shot);
} }
/**
* Handles a miss by representing it with a blue cylinder
* and attaching a water splash effect to it.
* @param shot the shot to be processed
* @return a Spatial simulating a miss with water splash effect
*/
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;
}
/**
* Handles the sinking animation and removal of ship if destroyed
* @param ship the ship to be sunk
*/
private void sinkAndRemoveShip(Battleship ship) {
Battleship wilkeningklaunichtmeinencode = ship;
final Node shipNode = (Node) getSpatial(wilkeningklaunichtmeinencode);
if (shipNode == null) return;
// Add sinking control to animate the sinking
shipNode.addControl(new SinkingControl(shipNode));
// Add particle effects
ParticleEmitter bubbles = particleFactory.createWaterSplash();
bubbles.setLocalTranslation(shipNode.getLocalTranslation());
shipNode.attachChild(bubbles);
bubbles.emitAllParticles();
}
/** /**
* Handles a hit by attaching its representation to the node that * 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. * contains the ship model as a child so that it moves with the ship.
* *
* @param shot a hit * @param shot a hit
* @return always null to prevent the representation from being attached * @return always null to prevent the representation from being attached to the items node as well
* to the items node as well
*/ */
private Spatial handleHit(Shot shot) { private Spatial handleHit(Shot shot) {
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship"); final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
@@ -134,20 +168,13 @@ class SeaSynchronizer extends ShipMapSynchronizer {
flame.emitAllParticles(); flame.emitAllParticles();
roundSpark.emitAllParticles(); roundSpark.emitAllParticles();
//Checks if ship is destroyed and triggers animation accordingly
if (ship.isDestroyed()) {
sinkAndRemoveShip(ship);
}
return null; 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. * Creates a cylinder geometry representing the specified shot.
@@ -201,9 +228,9 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private Spatial createShip(Battleship ship) { private Spatial createShip(Battleship ship) {
switch (ship.getLength()) { switch (ship.getLength()) {
case 4: return createBattleship(ship); case 4: return createBattleship(ship);
case 3: return createMarlow66(ship); case 3: return createCV(ship);
case 2: return createUX23(ship); case 2: return createBattle(ship);
case 1: return createAllienship(ship); case 1: return createSmallship(ship);
default: return createBox(ship); default: return createBox(ship);
} }
} }
@@ -261,36 +288,51 @@ class SeaSynchronizer extends ShipMapSynchronizer {
return model; return model;
} }
private Spatial createAllienship(Battleship ship) { /**
final Spatial model = app.getAssetManager().loadModel(ALIENSHIP); * Creates a detailed 3D model to represent a small tug boat.
*
* @param ship the battleship to be represented
* @return the spatial representing a small tug boat
*/
private Spatial createSmallship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BOAT_SMALL_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.10f); model.scale(0.0005f);
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); model.setShadowMode(ShadowMode.CastAndReceive);
return model; return model;
} }
/**
* Creates a detailed 3D model to represent a "German WWII UBoat".
*
* @param ship the battleship to be represented
* @return the spatial representing the "German WWII UBoat"
private Spatial createMarlow66(Battleship ship) { */
final Spatial model = app.getAssetManager().loadModel(MARLOW66); private Spatial createCV(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(CV_MODEL);
model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f); model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
model.move(0f, 0.25f, 0f); model.move(0f, 0.25f, 0f);
model.scale(0.135f); model.scale(0.85f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* Creates a detailed 3D model to represent a battleship.
*
* @param ship the battleship to be represented
* @return the spatial representing a battleship
*/
private Spatial createBattle(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BATTLE_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.move(0f, -0.06f, 0f);
model.scale(0.27f);
model.setShadowMode(ShadowMode.CastAndReceive); model.setShadowMode(ShadowMode.CastAndReceive);
return model; return model;

View File

@@ -0,0 +1,53 @@
package pp.battleship.client.gui;
import com.jme3.scene.control.AbstractControl;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
/**
* Control that handles the sinking effect for destroyed ships.
* It will gradually move the ship downwards and then remove it from the scene.
*/
class SinkingControl extends AbstractControl {
private static final float SINK_DURATION = 5f; // Duration of the sinking animation
private static final float SINK_SPEED = 0.1f; // Speed at which the ship sinks
private float elapsedTime = 0;
private final Node shipNode;
/**
* Constructs a {@code SinkingControl} object with the shipNode to be to be sunk
* @param shipNode the node to handeld
*/
public SinkingControl(Node shipNode) {
this.shipNode = shipNode;
}
/**
* Updated the Map to sink the ship
*
* @param tpf time per frame
*/
@Override
protected void controlUpdate(float tpf) {
// Update the sinking effect
elapsedTime += tpf;
// Move the ship down over time
Vector3f currentPos = shipNode.getLocalTranslation();
shipNode.setLocalTranslation(currentPos.x, currentPos.y - SINK_SPEED * tpf, currentPos.z);
// Check if sinking duration has passed
if (elapsedTime >= SINK_DURATION) {
// Remove the ship from the scene
shipNode.removeFromParent();
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-related code needed
}
}

View File

@@ -0,0 +1,35 @@
package pp.battleship.client.gui;
import com.simsilica.lemur.Slider;
/**
* The VolumeSlider class represents the Volume Slider in the Menu.
* It extends the Slider class and provides functionalities for setting the music volume,
* with the help of the Slider in the GUI
*/
public class VolumeSlider extends Slider {
private final GameMusic music;
private double vol;
/**
* Constructs the Volume Slider for the Menu dialog
* @param music the music instance
*/
public VolumeSlider(GameMusic music) {
super();
this.music = music;
vol = GameMusic.volumeInPreferences();
getModel().setPercent(vol);
}
/**
* when triggered it updates the volume to the value set with the slider
*/
public void update() {
if (vol != getModel().getPercent()) {
vol = getModel().getPercent();
music.setVolume( (float) vol);
}
}
}

View File

@@ -0,0 +1,179 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.server;
import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player;
import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerMessage;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shot;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.LogManager;
/**
* Server implementing the visitor pattern as MessageReceiver for ClientMessages
*/
public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
private static final File CONFIG_FILE = new File("server.properties");
private final BattleshipConfig config = new BattleshipConfig();
private Server myServer;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
try {
manager.readConfiguration(new FileInputStream("logging.properties"));
LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
}
catch (IOException e) {
LOGGER.log(Level.INFO, e.getMessage());
}
}
/**
* Starts the Battleships server.
*/
public static void main(String[] args) {
new BattleshipServer().run();
}
/**
* Creates the server.
*/
BattleshipServer() {
config.readFromIfExists(CONFIG_FILE);
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config);
}
public void run() {
startServer();
while (true)
processNextMessage();
}
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(config.getPort());
initializeSerializables();
myServer.start();
registerListeners();
LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
}
catch (IOException e) {
LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
exit(1);
}
}
private void processNextMessage() {
try {
pendingMessages.take().process(logic);
}
catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
Thread.currentThread().interrupt();
}
}
private void initializeSerializables() {
Serializer.registerClass(GameDetails.class);
Serializer.registerClass(StartBattleMessage.class);
Serializer.registerClass(MapMessage.class);
Serializer.registerClass(ShootMessage.class);
Serializer.registerClass(EffectMessage.class);
Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class);
}
private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class);
myServer.addConnectionListener(this);
}
@Override
public void messageReceived(HostedConnection source, Message message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
if (message instanceof ClientMessage clientMessage)
pendingMessages.add(new ReceivedMessage(clientMessage, source.getId()));
}
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
logic.addPlayer(hostedConnection.getId());
}
@Override
public void connectionRemoved(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
final Player player = logic.getPlayerById(hostedConnection.getId());
if (player == null)
LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
else { //NON-NLS
LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
exit(0);
}
}
private void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null)
for (HostedConnection client : myServer.getConnections()) //NON-NLS
if (client != null) client.close("Game over"); //NON-NLS
System.exit(exitValue);
}
/**
* Send the specified message to the specified connection.
*
* @param id the connection id
* @param message the message
*/
public void send(int id, ServerMessage message) {
if (myServer == null || !myServer.isRunning()) {
LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
return;
}
final HostedConnection connection = myServer.getConnection(id);
if (connection != null)
connection.send(message);
else
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
}
}

View File

@@ -0,0 +1,17 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.server;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage;
record ReceivedMessage(ClientMessage message, int from) {
void process(ClientInterpreter interpreter) {
message.accept(interpreter, from);
}
}

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -42,10 +42,9 @@ public class ModelExporter extends SimpleApplication {
@Override @Override
public void simpleInitApp() { 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/BoatSmall/12219_boat_v2_L2.obj", "BoatSmall.j3o"); //NON-NLS
export("Models/Marlow66/Marlow66.obj", "Marlow66.j3o");//NON-NLS export("Models/Battle/14084_WWII_Ship_German_Type_II_U-boat_v2_L1.obj", "Battle.j3o"); //NON-NLS
export("Models/UX23/UX23.obj", "UX23.j3o");//NON-NLS export("Models/CV/essex_scb-125_generic.obj", "CV.j3o"); //NON-NLS
export("Models/Boje/Boje.obj", "Boje.j3o");//NON-NLS
stop(); stop();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,11 +0,0 @@
# 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

Some files were not shown because too many files have changed in this diff Show More