diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapView.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapView.java index bd16c83..81f348b 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapView.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapView.java @@ -28,7 +28,7 @@ import pp.util.Position; * and interaction between the model and the view. */ class MapView { - private static final float FIELD_SIZE = 40f; + public static final float FIELD_SIZE = 40f; private static final float GRID_LINE_WIDTH = 2f; private static final float BACKGROUND_DEPTH = -4f; private static final float GRID_DEPTH = -1f; diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapViewSynchronizer.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapViewSynchronizer.java index a6075fd..684dab9 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapViewSynchronizer.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/MapViewSynchronizer.java @@ -7,14 +7,23 @@ package pp.battleship.client.gui; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.material.RenderState.BlendMode; import com.jme3.math.ColorRGBA; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; + import pp.battleship.model.Battleship; +import pp.battleship.model.Shell; import pp.battleship.model.Shot; import pp.util.Position; +import static com.jme3.material.Materials.UNSHADED; + + /** * Synchronizes the visual representation of the ship map with the game model. * It handles the rendering of ships and shots on the map view, updating the view @@ -122,4 +131,24 @@ 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); } + + /** + * Creates and returns a Spatial representation of the given {@code Shell} object + * for 2D visualization in the game. The shell is represented as a circle. + * + * @param shell The {@code Shell} object to be visualized. + * @return A {@code Spatial} object representing the shell on the map. + */ + @Override + public Spatial visit(Shell shell) { + final ColorRGBA color = ColorRGBA.Black; + Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f)); + Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); + mat.setColor("Color", color); + ellipse.setMaterial(mat); + ellipse.addControl(new Shell2DControl(view, shell)); + return ellipse; + } } diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java index d330c74..f1736e8 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java @@ -20,6 +20,7 @@ import com.jme3.scene.shape.Cylinder; import pp.battleship.client.BattleshipApp; import pp.battleship.model.Battleship; import pp.battleship.model.Rotation; +import pp.battleship.model.Shell; import pp.battleship.model.ShipMap; import pp.battleship.model.Shot; @@ -39,6 +40,7 @@ class SeaSynchronizer extends ShipMapSynchronizer { 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 BATTLE_MODEL = "Models/Battle/Battle.j3o"; //NON-NLS + private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; private static final String COLOR = "Color"; //NON-NLS private static final String SHIP = "ship"; //NON-NLS private static final String SHOT = "shot"; //NON-NLS @@ -352,4 +354,25 @@ class SeaSynchronizer extends ShipMapSynchronizer { case UP -> PI; }; } + + /** + * Creates and returns a 3D model representation of the given {@code Shell} object + * for visualization in the game. + * + * @param shell The {@code Shell} object to be visualized. + * @return A {@code Spatial} object representing the 3D model of the shell. + */ + @Override + public Spatial visit(Shell shell) { + final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o"); + model.setLocalScale(.05f); + model.setShadowMode(ShadowMode.CastAndReceive); + Material mat = new Material(app.getAssetManager(), LIGHTING); + mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture("Models/Shell/shell_color.png")); + mat.setReceivesShadows(true); + model.setMaterial(mat); + + model.addControl(new ShellControl(shell)); + return model; + } } diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/Shell2DControl.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/Shell2DControl.java new file mode 100644 index 0000000..93ae2ad --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/Shell2DControl.java @@ -0,0 +1,49 @@ +package pp.battleship.client.gui; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.control.AbstractControl; +import pp.battleship.model.Shell; +import pp.util.Position; + +/** + * Controls the 2D representation of a {@code Shell} in the game, updating its position + * based on the shell's current state in the game model. The {@code Shell2DControl} class + * is responsible for translating the shell's 3D position to a 2D view position within + * the game's map view. + */ +public class Shell2DControl extends AbstractControl { + private final Shell shell; + private final MapView view; + + /** + * Constructs a new {@code Shell2DControl} to manage the 2D visualization of the given {@code Shell}. + * + * @param view The {@code MapView} used to get information about the map to display. + * @param shell The {@code Shell} being visualized. + */ + public Shell2DControl(MapView view, Shell shell){ + this.shell = shell; + this.view = view; + } + + /** + * Updates the position of the shell's 2D representation based on the shell's current + * 3D position in the game model. The position is mapped from model space to view space + * coordinates and translated to the appropriate location within the {@code MapView}. + * + * @param tpf Time per frame, representing the time elapsed since the last frame. + */ + @Override + protected void controlUpdate(float tpf) { + Vector3f shellPos = shell.getPosition(); + Position viewPos = view.modelToView(shellPos.x, shellPos.z); + spatial.setLocalTranslation(viewPos.getX() + MapView.FIELD_SIZE / 2, viewPos.getY() + MapView.FIELD_SIZE / 2, 0); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + // No rendering-specific behavior required for this control + } +} diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShellControl.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShellControl.java new file mode 100644 index 0000000..a551f6a --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShellControl.java @@ -0,0 +1,50 @@ +package pp.battleship.client.gui; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.control.AbstractControl; +import pp.battleship.model.Shell; + +import static pp.util.FloatMath.PI; + +/** + * Controls the 3D representation of a {@code Shell} in the game, updating its position + * and rotation based on the shell's current state in the game model. The {@code ShellControl} + * class ensures that the spatial associated with the shell is positioned and oriented correctly + * within the world. + */ +public class ShellControl extends AbstractControl { + private final Shell shell; + + /** + * Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}. + * + * @param shell The {@code Shell} being visualized and controlled. + */ + public ShellControl(Shell shell){ + super(); + this.shell = shell; + } + + /** + * Updates the 3D position and rotation of the shell based on its current state. + * Converts map coordinates to world coordinates and applies the shell's orientation. + * + * @param tpf Time per frame, representing the elapsed time since the last update. + */ + @Override + protected void controlUpdate(float tpf) { + Vector3f pos = shell.getPosition(); + Vector3f fixed = new Vector3f(pos.z + 0.5f, pos.y, pos.x + 0.5f); + fixed.setY(pos.y); + spatial.setLocalTranslation(fixed); + spatial.setLocalRotation(shell.getRotation()); + spatial.rotate(PI/2,0,0); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + // No rendering-specific behavior required for this control + } +} diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/server/BattleshipServer.java b/Projekte/battleship/client/src/main/java/pp/battleship/server/BattleshipServer.java index 1ad0a2e..d10a5d9 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/server/BattleshipServer.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/server/BattleshipServer.java @@ -18,6 +18,7 @@ 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.AnimationFinishedMessage; import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.ShootMessage; @@ -115,6 +116,7 @@ public class BattleshipServer implements MessageListener, Conn Serializer.registerClass(MapMessage.class); Serializer.registerClass(ShootMessage.class); Serializer.registerClass(EffectMessage.class); + Serializer.registerClass(AnimationFinishedMessage.class); Serializer.registerClass(Battleship.class); Serializer.registerClass(IntPoint.class); Serializer.registerClass(Shot.class); @@ -123,6 +125,7 @@ public class BattleshipServer implements MessageListener, Conn private void registerListeners() { myServer.addMessageListener(this, MapMessage.class); myServer.addMessageListener(this, ShootMessage.class); + myServer.addMessageListener(this, AnimationFinishedMessage.class); myServer.addConnectionListener(this); } diff --git a/Projekte/battleship/client/src/main/resources/Models/Shell/shell.j3o b/Projekte/battleship/client/src/main/resources/Models/Shell/shell.j3o new file mode 100644 index 0000000..66a97a1 Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Models/Shell/shell.j3o differ diff --git a/Projekte/battleship/client/src/main/resources/Models/Shell/shell_color.png b/Projekte/battleship/client/src/main/resources/Models/Shell/shell_color.png new file mode 100644 index 0000000..885f84b Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Models/Shell/shell_color.png differ diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java index 99df8d0..ec6785b 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java @@ -10,6 +10,7 @@ package pp.battleship.game.client; import pp.battleship.message.client.ShootMessage; import pp.battleship.message.server.EffectMessage; import pp.battleship.model.IntPoint; +import pp.battleship.model.Shell; import pp.battleship.model.ShipMap; import pp.battleship.notification.Sound; @@ -53,16 +54,13 @@ class BattleState extends ClientState { @Override public void receivedEffect(EffectMessage msg) { ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS - playSound(msg); + myTurn = msg.isMyTurn(); logic.setInfoText(msg.getInfoTextKey()); - affectedMap(msg).add(msg.getShot()); - if (destroyedOpponentShip(msg)) - logic.getOpponentMap().add(msg.getDestroyedShip()); - if (msg.isGameOver()) { - msg.getRemainingOpponentShips().forEach(logic.getOpponentMap()::add); - logic.setState(new GameOverState(logic)); - } + Shell shell = new Shell(msg.getShot()); + affectedMap(msg).add(shell); + logic.playSound(Sound.SHELL_FLYING); + logic.setState(new ShootingState(logic, shell, myTurn, msg)); } /** @@ -74,29 +72,4 @@ class BattleState extends ClientState { private ShipMap affectedMap(EffectMessage msg) { return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap(); } - - /** - * Checks if the opponent's ship was destroyed by the player's shot. - * - * @param msg the effect message received from the server - * @return true if the shot destroyed an opponent's ship, false otherwise - */ - private boolean destroyedOpponentShip(EffectMessage msg) { - return msg.getDestroyedShip() != null && msg.isOwnShot(); - } - - /** - * Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit, - * or destruction of a ship. - * - * @param msg the effect message containing the result of the shot - */ - private void playSound(EffectMessage msg) { - if (!msg.getShot().isHit()) - logic.playSound(Sound.SPLASH); - else if (msg.getDestroyedShip() == null) - logic.playSound(Sound.EXPLOSION); - else - logic.playSound(Sound.DESTROYED_SHIP); - } } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ShootingState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ShootingState.java new file mode 100644 index 0000000..75312b2 --- /dev/null +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ShootingState.java @@ -0,0 +1,114 @@ +package pp.battleship.game.client; + +import pp.battleship.message.client.AnimationFinishedMessage; +import pp.battleship.message.server.EffectMessage; +import pp.battleship.model.Battleship; +import pp.battleship.model.Shell; +import pp.battleship.model.ShipMap; +import pp.battleship.notification.Sound; + +/** + * Represents the shooting state of the game where a shell is fired at the opponent. + */ +public class ShootingState extends ClientState { + private float shootValue; + private final static float SHELL_SPEED = 0.3f; + private final Shell shell; + private final boolean myTurn; + private final EffectMessage msg; + + /** + * Constructs a shooting state with the specified game logic. + * + * @param logic the game logic + * @param shell the shell being shot + * @param myTurn indicates if it is the player's turn + * @param msg the effect message associated with the shooting action + */ + public ShootingState(ClientGameLogic logic, Shell shell, boolean myTurn, EffectMessage msg) { + super(logic); + this.msg = msg; + this.myTurn = myTurn; + this.shell = shell; + this.shootValue = 0; + shell.move(shootValue); + } + + @Override + public boolean showBattle() { + return true; + } + + /** + * Updates the shooting state by moving the shell based on the elapsed time. + * + * @param delta the time in seconds since the last update + */ + @Override + void update(float delta) { + super.update(delta); + if (shootValue > 1) { + endState(); + } + else { + shootValue += delta * SHELL_SPEED; + shell.move(shootValue); + } + } + + /** + * Ends the shooting state and processes the effects of the shot. + */ + private void endState() { + playSound(msg); + affectedMap(msg).add(msg.getShot()); + affectedMap(msg).remove(shell); + + if (destroyedOpponentShip(msg)) + logic.getOpponentMap().add(msg.getDestroyedShip()); + if (msg.isGameOver()) { + for (Battleship ship : msg.getRemainingOpponentShips()) { + logic.getOpponentMap().add(ship); + } + logic.setState(new GameOverState(logic)); + return; + } + logic.send(new AnimationFinishedMessage()); + logic.setState(new BattleState(logic, myTurn)); + } + + /** + * Checks if an opponent's ship was destroyed by the shot. + * + * @param msg the effect message containing the shot details + * @return true if an opponent's ship was destroyed, false otherwise + */ + private boolean destroyedOpponentShip(EffectMessage msg) { + return msg.getDestroyedShip() != null && msg.isOwnShot(); + } + + /** + * Retrieves the affected map based on whether the shot was owned by the player or the opponent. + * + * @param msg the effect message containing shot details + * @return the ShipMap that was affected by the shot + */ + private ShipMap affectedMap(EffectMessage msg) { + return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap(); + } + + /** + * Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit, + * or destruction of a ship. + * + * @param msg the effect message containing the result of the shot + */ + private void playSound(EffectMessage msg) { + if (!msg.getShot().isHit()) + logic.playSound(Sound.SPLASH); + else if (msg.getDestroyedShip() == null) + logic.playSound(Sound.EXPLOSION); + else + logic.playSound(Sound.DESTROYED_SHIP); + } +} diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerGameLogic.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerGameLogic.java index b2c6dfc..9f36179 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerGameLogic.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerGameLogic.java @@ -8,8 +8,8 @@ package pp.battleship.game.server; import pp.battleship.BattleshipConfig; +import pp.battleship.message.client.AnimationFinishedMessage; import pp.battleship.message.client.ClientInterpreter; -import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.ShootMessage; import pp.battleship.message.server.EffectMessage; @@ -39,6 +39,7 @@ public class ServerGameLogic implements ClientInterpreter { private final ServerSender serverSender; private Player activePlayer; private ServerState state = ServerState.WAIT; + private Set waitPlayers = new HashSet<>(); /** * Constructs a ServerGameLogic with the specified sender and configuration. @@ -135,7 +136,6 @@ public class ServerGameLogic implements ClientInterpreter { /** * Handles the reception of a MapMessage. - * Also tests if Map is in correct format * * @param msg the received MapMessage * @param from the ID of the sender client @@ -144,59 +144,74 @@ public class ServerGameLogic implements ClientInterpreter { public void received(MapMessage msg, int from) { if (state != ServerState.SET_UP) LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS - - Player cp = getPlayerById(from); - List ships = msg.getShips(); - - if (!validateShips(ships)) { - LOGGER.log(Level.ERROR, "Invalid ship placement by player {0}", from); - send(cp, null ); - return; + else { + if (checkMap(msg.getShips())) { + playerReady(getPlayerById(from), msg.getShips()); + } + else { + LOGGER.log(Level.WARNING, "Invalid Map sent from player {0}", from); //NON-NLS + send(players.get(from), new GameDetails(config)); + } } - - playerReady(cp, ships); } - /** - * Validates a list of ships. - * Validates the list, that no ships are out of bounds or overlap with each other - * - * @param ships list of ships to validate - * @return {@code true} if all ships positions are valid, {@code false} otherwise + * Handles the reception of a AnimationFinishedMessage. + * + * @param msg the received MapMessage + * @param from the ID of the sender client */ - private boolean validateShips(List ships) { - Set occupiedPoints = new HashSet<>(); - - for (Battleship ship : ships) { - if (!isWithinBounds(ship)) { - return false; + @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; } - - 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 (!occupiedPoints.add(point)) { - return false; - } - } + if (waitPlayers.size() == 2) { + waitPlayers = new HashSet<>(); + setState(ServerState.BATTLE); + } + } + } + + /** + * Validates the placement of battleships on the map. + * Ensures that: + *
    + *
  • The number of ships matches the configuration.
  • + *
  • Ships are within the map's boundaries.
  • + *
  • Ships do not overlap.
  • + *
+ * + * @param ships the list of {@link Battleship} objects to validate + * @return {@code true} if all ships are placed correctly; {@code false} otherwise + */ + private boolean checkMap(List ships) { + int numShips = config.getShipNums().values().stream().mapToInt(Integer::intValue).sum(); + if (numShips != ships.size()) return false; + + List occupied = new ArrayList<>(); + + for (Battleship battleship : ships) { + int x = battleship.getX(); + int y = battleship.getY(); + for (int i = 0; i < battleship.getLength(); i++) { + if (x >= 0 && x < config.getMapWidth() && y >= 0 && y < config.getMapHeight() && !occupied.contains(new IntPoint(x, y))) { + occupied.add(new IntPoint(x, y)); + x += battleship.getRot().dx(); + y += battleship.getRot().dy(); + } + else return false; } } - return true; } - - - /** - * Test if a ship is inside the map - * @param ship the ship to validate - * @return {@code true} if a ship is within bounds, {@code false} otherwise - */ - private boolean isWithinBounds(Battleship ship) { - return ship.getMinX() >= 0 && ship.getMaxX() < config.getMapWidth() && - ship.getMinY() >= 0 && ship.getMaxY() < config.getMapHeight(); - } - /** * Handles the reception of a ShootMessage. @@ -208,8 +223,11 @@ public class ServerGameLogic implements ClientInterpreter { public void received(ShootMessage msg, int from) { if (state != ServerState.BATTLE) LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS - else + else{ + setState(ServerState.ANIMATION); shoot(getPlayerById(from), msg.getPosition()); + } + } /** diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerState.java index 507aa7a..52f11e4 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerState.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/server/ServerState.java @@ -26,6 +26,11 @@ enum ServerState { */ BATTLE, + /** + * The server is waiting for all clients to finish the shoot animation. + */ + ANIMATION, + /** * The game has ended because all the ships of one player have been destroyed. */ diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/Copycat.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/Copycat.java index f6e89be..c5185f4 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/Copycat.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/Copycat.java @@ -7,6 +7,7 @@ package pp.battleship.game.singlemode; +import pp.battleship.message.client.AnimationFinishedMessage; import pp.battleship.message.client.ClientInterpreter; import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.MapMessage; @@ -63,6 +64,11 @@ class Copycat implements ClientInterpreter { copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList()); } + @Override + public void received(AnimationFinishedMessage msg, int from) { + copiedMessage = msg; + } + /** * Creates a copy of the provided {@link Battleship}. * diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/RobotClient.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/RobotClient.java index d7831a7..405a1d9 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/RobotClient.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/singlemode/RobotClient.java @@ -1,6 +1,7 @@ package pp.battleship.game.singlemode; import pp.battleship.game.client.BattleshipClient; +import pp.battleship.message.client.AnimationFinishedMessage; import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.ShootMessage; import pp.battleship.message.server.EffectMessage; @@ -71,6 +72,7 @@ class RobotClient implements ServerInterpreter { * Makes the RobotClient take a shot by sending a ShootMessage with the target position. */ private void robotShot() { + connection.sendRobotMessage(new ShootMessage(getShotPosition())); } @@ -121,6 +123,7 @@ class RobotClient implements ServerInterpreter { @Override public void received(EffectMessage msg) { LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS + connection.sendRobotMessage(new AnimationFinishedMessage()); if (msg.isMyTurn()) shoot(); } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/message/client/AnimationFinishedMessage.java b/Projekte/battleship/model/src/main/java/pp/battleship/message/client/AnimationFinishedMessage.java new file mode 100644 index 0000000..51cfb49 --- /dev/null +++ b/Projekte/battleship/model/src/main/java/pp/battleship/message/client/AnimationFinishedMessage.java @@ -0,0 +1,24 @@ +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); + } +} diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/message/client/ClientInterpreter.java b/Projekte/battleship/model/src/main/java/pp/battleship/message/client/ClientInterpreter.java index 6a50c1d..f1b332b 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/message/client/ClientInterpreter.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/message/client/ClientInterpreter.java @@ -26,4 +26,12 @@ public interface ClientInterpreter { * @param from the connection ID from which the message was received */ 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); } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/model/Shell.java b/Projekte/battleship/model/src/main/java/pp/battleship/model/Shell.java new file mode 100644 index 0000000..6888cfe --- /dev/null +++ b/Projekte/battleship/model/src/main/java/pp/battleship/model/Shell.java @@ -0,0 +1,125 @@ +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 accept(Visitor visitor) { + return visitor.visit(this); + } + + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } +} diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/model/ShipMap.java b/Projekte/battleship/model/src/main/java/pp/battleship/model/ShipMap.java index 5a16278..773e7ff 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/model/ShipMap.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/model/ShipMap.java @@ -78,6 +78,15 @@ public class ShipMap { addItem(ship); } + /** + * Registers a shot on the map and triggers an item addition event. + * + * @param shell the shell to be registered on the map + */ + public void add(Shell shell) { + addItem(shell); + } + /** * Registers a shot on the map, updates the state of the affected ship (if any), * and triggers an item addition event. diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/model/Visitor.java b/Projekte/battleship/model/src/main/java/pp/battleship/model/Visitor.java index f490451..b8e1954 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/model/Visitor.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/model/Visitor.java @@ -28,4 +28,12 @@ public interface Visitor { * @return the result of visiting the Battleship element */ 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); } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/model/VoidVisitor.java b/Projekte/battleship/model/src/main/java/pp/battleship/model/VoidVisitor.java index 871c063..24c78b4 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/model/VoidVisitor.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/model/VoidVisitor.java @@ -25,4 +25,10 @@ public interface VoidVisitor { * @param ship the Battleship element to visit */ void visit(Battleship ship); + + /** + * Visits a Shell element + * @param shell the Shell element to visit + */ + void visit(Shell shell); } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/notification/Sound.java b/Projekte/battleship/model/src/main/java/pp/battleship/notification/Sound.java index d82ab69..933c1d4 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/notification/Sound.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/notification/Sound.java @@ -22,5 +22,9 @@ public enum Sound { /** * Sound of a ship being destroyed. */ - DESTROYED_SHIP + DESTROYED_SHIP, + /** + * Sound of a shell flying + */ + SHELL_FLYING } diff --git a/Projekte/battleship/server/src/main/java/pp/battleship/server/BattleshipServer.java b/Projekte/battleship/server/src/main/java/pp/battleship/server/BattleshipServer.java index 1ad0a2e..d10a5d9 100644 --- a/Projekte/battleship/server/src/main/java/pp/battleship/server/BattleshipServer.java +++ b/Projekte/battleship/server/src/main/java/pp/battleship/server/BattleshipServer.java @@ -18,6 +18,7 @@ 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.AnimationFinishedMessage; import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.ShootMessage; @@ -115,6 +116,7 @@ public class BattleshipServer implements MessageListener, Conn Serializer.registerClass(MapMessage.class); Serializer.registerClass(ShootMessage.class); Serializer.registerClass(EffectMessage.class); + Serializer.registerClass(AnimationFinishedMessage.class); Serializer.registerClass(Battleship.class); Serializer.registerClass(IntPoint.class); Serializer.registerClass(Shot.class); @@ -123,6 +125,7 @@ public class BattleshipServer implements MessageListener, Conn private void registerListeners() { myServer.addMessageListener(this, MapMessage.class); myServer.addMessageListener(this, ShootMessage.class); + myServer.addMessageListener(this, AnimationFinishedMessage.class); myServer.addConnectionListener(this); }