fixing shell

This commit is contained in:
Luca Puderbach 2024-10-20 04:30:57 +02:00
parent 04d16a2882
commit e4a24c3070
20 changed files with 258 additions and 50 deletions

View File

@ -7,6 +7,10 @@
package pp.battleship.client; package pp.battleship.client;
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;
import com.jme3.app.state.AppStateManager; import com.jme3.app.state.AppStateManager;
@ -14,13 +18,9 @@ import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException; 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 pp.battleship.notification.GameEventListener; import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent; import pp.battleship.notification.SoundEvent;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import static pp.util.PreferencesUtils.getPreferences; import static pp.util.PreferencesUtils.getPreferences;
/** /**
@ -34,7 +34,6 @@ public class GameSound extends AbstractAppState implements GameEventListener {
private AudioNode splashSound; private AudioNode splashSound;
private AudioNode shipDestroyedSound; private AudioNode shipDestroyedSound;
private AudioNode explosionSound; private AudioNode explosionSound;
private AudioNode shellFlyingSound;
/** /**
* Checks if sound is enabled in the preferences. * Checks if sound is enabled in the preferences.
@ -79,7 +78,6 @@ public class GameSound extends AbstractAppState implements GameEventListener {
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.wav");
} }
/** /**
@ -125,20 +123,6 @@ public class GameSound extends AbstractAppState implements GameEventListener {
if (isEnabled() && shipDestroyedSound != null) if (isEnabled() && shipDestroyedSound != null)
shipDestroyedSound.playInstance(); shipDestroyedSound.playInstance();
} }
/**
* Plays the shell flying sound effect.
*/
public void shellFly() {
if (isEnabled() && shellFlyingSound != null) {S
shellFlyingSound.playInstance();
}
}
/**
* Handles a recieved {@code SoundEvent} and plays the according sound.
*
* @param event the Sound event to be processed
*/
@Override @Override
public void receivedEvent(SoundEvent event) { public void receivedEvent(SoundEvent event) {

View File

@ -7,19 +7,19 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.ActionListener;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.system.AppSettings; import com.jme3.system.AppSettings;
import com.simsilica.lemur.Button; import com.simsilica.lemur.Button;
import com.simsilica.lemur.Container; import com.simsilica.lemur.Container;
import pp.battleship.client.BattleshipAppState;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import static pp.battleship.Resources.lookup; import static pp.battleship.Resources.lookup;
import static pp.battleship.client.BattleshipApp.CLICK; import static pp.battleship.client.BattleshipApp.CLICK;
import pp.battleship.client.BattleshipAppState;
/** /**
* EditorState manages the editor mode in the Battleship game, * EditorState manages the editor mode in the Battleship game,

View File

@ -7,19 +7,20 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.material.Material;
import static com.jme3.material.Materials.UNSHADED;
import com.jme3.material.RenderState;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.material.Material; import com.jme3.scene.Spatial;
import com.jme3.material.RenderState;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.scene.shape.Sphere; import com.jme3.scene.shape.Sphere;
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;
import static com.jme3.material.Materials.UNSHADED;
/** /**
* Synchronizes the visual representation of the ship map with the game model. * Synchronizes the visual representation of the ship map with the game model.

View File

@ -232,7 +232,7 @@ public class ParticleEffectFactory {
ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300); ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300);
smokeEmitter.setGravity(0, 0, 0); smokeEmitter.setGravity(0, 0, 0);
smokeEmitter.getParticleInfluencer().setVelocityVariation(1); smokeEmitter.getParticleInfluencer().setVelocityVariation(1);
smokeEmitter.setLocalTranslation(0, 2f, 0); smokeEmitter.setLocalTranslation(0, 2f, 0); //___________________
smokeEmitter.setLowLife(1); smokeEmitter.setLowLife(1);
smokeEmitter.setHighLife(1); smokeEmitter.setHighLife(1);
smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0)); smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
@ -243,7 +243,7 @@ public class ParticleEffectFactory {
return smokeEmitter; return smokeEmitter;
} }
public ParticleEmitter createWaterSplash() { public ParticleEmitter createWaterSplash() { //wird durch explosions animation ersetzt
// Create a new particle emitter for the splash effect // Create a new particle emitter for the splash effect
ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30); ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30);

View File

@ -26,9 +26,9 @@ import com.jme3.scene.shape.Cylinder;
import pp.battleship.client.BattleshipApp; import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation; import pp.battleship.model.Rotation;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap; import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
import pp.battleship.model.Shell;
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;
@ -52,6 +52,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray; 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 SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f); private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md";
private final ShipMap map; private final ShipMap map;
private final BattleshipApp app; private final BattleshipApp app;

View File

@ -4,8 +4,8 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import pp.battleship.model.Shell;
import pp.battleship.model.Shell;
import static pp.util.FloatMath.PI; import static pp.util.FloatMath.PI;
/** /**

View File

@ -1,4 +1,4 @@
/////////////////////////////////////// ////////////////////////////////////////
// Programming project code // Programming project code
// UniBw M, 2022, 2023, 2024 // UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2 // www.unibw.de/inf2
@ -28,6 +28,7 @@ import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player; import pp.battleship.game.server.Player;
import pp.battleship.game.server.ServerGameLogic; import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender; import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
@ -64,7 +65,7 @@ public class BattleshipSelfhostServer implements MessageListener<HostedConnectio
} }
/** /**
* Starts the server. * Starts the Battleships server.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
new BattleshipSelfhostServer().run(); new BattleshipSelfhostServer().run();
@ -119,12 +120,14 @@ public class BattleshipSelfhostServer implements MessageListener<HostedConnectio
Serializer.registerClass(Battleship.class); Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class); Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class); Serializer.registerClass(Shot.class);
Serializer.registerClass(AnimationFinishedMessage.class);
} }
private void registerListeners() { private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class); myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class); myServer.addMessageListener(this, ShootMessage.class);
myServer.addConnectionListener(this); myServer.addConnectionListener(this);
myServer.addMessageListener(this, AnimationFinishedMessage.class);
} }
@Override @Override

View File

@ -1,4 +1,3 @@
//////////////////////////////////////// ////////////////////////////////////////
// Programming project code // Programming project code
// UniBw M, 2022, 2023, 2024 // UniBw M, 2022, 2023, 2024

View File

@ -12,9 +12,9 @@ import java.lang.System.Logger.Level;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.EffectMessage; import pp.battleship.message.server.EffectMessage;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap; import pp.battleship.model.ShipMap;
import pp.battleship.notification.Sound; import pp.battleship.notification.Sound;
import pp.battleship.model.Shell;
/** /**
* Represents the state of the client where players take turns to attack each other's ships. * Represents the state of the client where players take turns to attack each other's ships.

View File

@ -15,6 +15,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import pp.battleship.BattleshipConfig; import pp.battleship.BattleshipConfig;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientInterpreter; import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
@ -24,7 +25,7 @@ import pp.battleship.message.server.ServerMessage;
import pp.battleship.message.server.StartBattleMessage; import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.message.client.AnimationFinishedMessage;
/** /**
* Controls the server-side game logic for Battleship. * Controls the server-side game logic for Battleship.
@ -244,11 +245,32 @@ public class ServerGameLogic implements ClientInterpreter {
ship.getPositions().stream().allMatch(belegt::add)); ship.getPositions().stream().allMatch(belegt::add));
} }
// public void displayError(String message) { /**
// System.out.println("Fehlermeldung: " + message); * Handles the reception of a AnimationFinishedMessage.
// // Optional: GUI-Element aktualisieren, um die Nachricht anzuzeigen *
// } * @param msg the received MapMessage
* @param from the ID of the sender client
*/
@Override
public void received(AnimationFinishedMessage msg, int from) {
if (state != ServerState.ANIMATION) {
LOGGER.log(Level.ERROR, "animation finished not allowed in {0}", state);
}
else {
LOGGER.log(Level.DEBUG, "anim received from {0}", getPlayerById(from));
Player player = getPlayerById(from);
if (!waitPlayers.add(player)) {
LOGGER.log(Level.ERROR, "{0} already sent animation finished", player); //NON-NLS
return;
}
if (waitPlayers.size() == 2) {
waitPlayers = new HashSet<>();
setState(ServerState.BATTLE);
}
}
}
} }

View File

@ -29,5 +29,10 @@ enum ServerState {
/** /**
* The game has ended because all the ships of one player have been destroyed. * The game has ended because all the ships of one player have been destroyed.
*/ */
GAME_OVER GAME_OVER,
/**
* The server is waiting for all clients to finish the shoot animation.
*/
ANIMATION
} }

View File

@ -7,6 +7,7 @@
package pp.battleship.game.singlemode; package pp.battleship.game.singlemode;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientInterpreter; import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.MapMessage;
@ -72,4 +73,10 @@ class Copycat implements ClientInterpreter {
private static Battleship copy(Battleship ship) { private static Battleship copy(Battleship ship) {
return new Battleship(ship.getLength(), ship.getX(), ship.getY(), ship.getRot()); return new Battleship(ship.getLength(), ship.getX(), ship.getY(), ship.getRot());
} }
@Override
public void received(AnimationFinishedMessage msg, int from) {
copiedMessage = msg;
}
} }

View File

@ -0,0 +1,25 @@
package pp.battleship.message.client;
import com.jme3.network.serializing.Serializable;
/**
* Represents a message indicating that an animation has finished on the client side.
*/
@Serializable
public class AnimationFinishedMessage extends ClientMessage {
public AnimationFinishedMessage() {
super();
}
/**
* Accepts a visitor to process this message.
*
* @param interpreter the visitor to process this message
* @param from the connection ID from which the message was received
*/
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

View File

@ -26,4 +26,12 @@ public interface ClientInterpreter {
* @param from the connection ID from which the message was received * @param from the connection ID from which the message was received
*/ */
void received(MapMessage msg, int from); void received(MapMessage msg, int from);
/**
* Processes a received AnimationFinishedMessage.
*
* @param msg the MapMessage to be processed
* @param from the connection ID from which the message was received
*/
void received(AnimationFinishedMessage msg, int from);
} }

View File

@ -0,0 +1,126 @@
package pp.battleship.model;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
/**
* The {@code Shell} class represents a projectile fired by a ship in the Battleship game.
* It models the position and rotation of the projectile and allows for its movement along
* a Bezier curve.
*/
public class Shell implements Item {
/**
* Initial position of the shell
*/
private final static Vector3f INIT_POS = new Vector3f(-3, 7, -3);
/**
* The overshot difference vector used to get a shallower flight path
*/
private final static Vector3f OVER_SHOT_DIFF = new Vector3f(-1, -1, -1);
// Target shot position
private final Vector3f shotPosition;
// Position on top of shotPosition used for Bezier curve
private final Vector3f overShotPosition;
// Current position of the shell
private Vector3f position;
// Current rotation of the shell
private final Quaternion rotation;
/**
* Constructs a new {@code Shell} object using the given {@code Shot} target.
* The initial position, target position, and overshot position are calculated.
*
* @param shot The target {@code Shot} object containing the destination coordinates.
*/
public Shell(Shot shot) {
this.shotPosition = new Vector3f(shot.getX(), 0, shot.getY());
this.overShotPosition = new Vector3f(shotPosition.x, INIT_POS.y, shotPosition.z).add(OVER_SHOT_DIFF);
this.position = INIT_POS;
this.rotation = new Quaternion();
}
/**
* Gets the current position of the shell.
*
* @return The current position as a {@code Vector3f}.
*/
public Vector3f getPosition() {
return this.position;
}
/**
* Gets the current rotation of the shell.
*
* @return The current rotation as a {@code Quaternion}.
*/
public Quaternion getRotation() {
return this.rotation;
}
/**
* Moves the shell along a Bezier curve based on the given time factor {@code t}.
* The position and rotation of the shell are updated.
*
* @param t The time factor between 0 and 1, representing the progress of the shell's flight.
*/
public void move(float t) {
if (t > 1f) t = 1f;
Vector3f newPosition = bezInt(INIT_POS, overShotPosition, shotPosition, t);
updateRotation(position, newPosition);
this.position = newPosition;
}
/**
* Performs a quadratic Bezier interpolation between three points based on the time factor {@code t}.
*
* @param p1 The start position.
* @param p2 The overshot position.
* @param p3 The target position.
* @param t The time factor for interpolation.
* @return The interpolated position as a {@code Vector3f}.
*/
private Vector3f bezInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
Vector3f inA = linInt(p1, p2, t);
Vector3f inB = linInt(p2, p3, t);
return linInt(inA, inB, t);
}
/**
* Performs linear interpolation between two points {@code p1} and {@code p2} based on the time factor {@code t}.
*
* @param p1 The start position.
* @param p2 The end position.
* @param t The time factor for interpolation.
* @return The interpolated position as a {@code Vector3f}.
*/
private Vector3f linInt(Vector3f p1, Vector3f p2, float t) {
float x = p1.getX() + t * (p2.getX() - p1.getX());
float y = p1.getY() + t * (p2.getY() - p1.getY());
float z = p1.getZ() + t * (p2.getZ() - p1.getZ());
return new Vector3f(x, y, z);
}
/**
* Updates the rotation of the shell to face the new position along its flight path.
*
* @param oldPos The previous position of the shell.
* @param newPos The new position of the shell.
*/
private void updateRotation(Vector3f oldPos, Vector3f newPos) {
Vector3f direction = newPos.subtract(oldPos).normalize();
if (direction.lengthSquared() > 0) {
this.rotation.lookAt(direction, Vector3f.UNIT_Y);
}
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
@Override
public void accept(VoidVisitor visitor) {
visitor.visit(this);
}
}

View File

@ -249,4 +249,8 @@ public class ShipMap {
if (eventBroker != null) if (eventBroker != null)
eventBroker.notifyListeners(event); eventBroker.notifyListeners(event);
} }
public void add(Shell shell) {
addItem(shell);
}
} }

View File

@ -28,4 +28,13 @@ public interface Visitor<T> {
* @return the result of visiting the Battleship element * @return the result of visiting the Battleship element
*/ */
T visit(Battleship ship); T visit(Battleship ship);
/**
* Visits a Shell element
*
* @param shell the Shell element to visit
* @return the result of visitung the Battleship element
*/
T visit(Shell shell);
} }

View File

@ -25,4 +25,11 @@ public interface VoidVisitor {
* @param ship the Battleship element to visit * @param ship the Battleship element to visit
*/ */
void visit(Battleship ship); void visit(Battleship ship);
/**
* Visits a Shell element
* @param shell the Shell element to visit
*/
void visit(Shell shell);
} }

View File

@ -22,5 +22,9 @@ public enum Sound {
/** /**
* Sound of a ship being destroyed. * Sound of a ship being destroyed.
*/ */
DESTROYED_SHIP DESTROYED_SHIP,
/**
* Sound of flying Shell
*/
SHELL_FLYING
} }

View File

@ -28,6 +28,7 @@ import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player; import pp.battleship.game.server.Player;
import pp.battleship.game.server.ServerGameLogic; import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender; import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
@ -119,12 +120,14 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
Serializer.registerClass(Battleship.class); Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class); Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class); Serializer.registerClass(Shot.class);
Serializer.registerClass(AnimationFinishedMessage.class);
} }
private void registerListeners() { private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class); myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class); myServer.addMessageListener(this, ShootMessage.class);
myServer.addConnectionListener(this); myServer.addConnectionListener(this);
myServer.addMessageListener(this, AnimationFinishedMessage.class);
} }
@Override @Override