19 Commits

Author SHA1 Message Date
Cedric Beck
5035aa5a96 fixed sinking-rotation, volumeslider; deleted TODO comments 2024-10-14 11:29:22 +02:00
Cedric Beck
67b99317d2 added TODOÃ's for presentation 2024-10-12 17:45:30 +02:00
Cedric Beck
bb1e3858bb fixed not working single mode, added read port from textline when client host server 2024-10-12 16:17:46 +02:00
Cedric Beck
9b85030050 finished ex. 13 + cleanup 2024-10-12 15:34:45 +02:00
Cedric Beck
4492843ca1 finished 12 2024-10-08 14:22:42 +02:00
Cedric Beck
5edd4ebe0b Added Ex. 10
added client hosts a server
added javadoc comments
2024-10-06 14:29:31 +02:00
Cedric Beck
216bd60d84 Added models as .j3o file
added color & bump texture path in 'ShipModelÃ'
added lighting texture for models
2024-10-05 13:27:12 +02:00
Cedric Beck
8275778d66 Added functionality for preferences in 'GameMusic' 2024-10-05 11:28:15 +02:00
Cedric Beck
1d114c8d24 Added Ex. 10 2024-10-04 17:42:30 +02:00
Cedric Beck
8b6a787115 Added credit for ship1 2024-10-04 14:06:00 +02:00
Cedric Beck
8a5afe0b18 Added ex. 9 2024-10-04 14:02:33 +02:00
Cedric Beck
6496a5a6b7 Added extendend client-side ship check 2024-10-02 21:09:48 +02:00
Cedric Beck
af221ad693 Added documentation to ServerGameLogic.checkMap 2024-10-02 20:48:16 +02:00
Cedric Beck
ae61e8061c Added ex. 8 2024-10-02 20:43:41 +02:00
Cedric Beck
5f596d5797 Merge branch 'b_Beck_Cedric' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into b_Beck_Cedric 2024-10-02 18:37:42 +02:00
Cedric Beck
82d86a378a Added TODO 2024-10-02 18:37:31 +02:00
Cedric Beck
02f7a6542e Fixed Ex. 7 2024-10-02 18:36:37 +02:00
Cedric Beck
1afa621bec Fixed Ex. 7 2024-10-02 17:11:52 +02:00
Cedric Beck
546872dd83 Fixed ClientGame1Player1Test 2024-10-02 14:46:16 +02:00
124 changed files with 150341 additions and 754063 deletions

View File

@@ -9,7 +9,6 @@ implementation project(":jme-common")
implementation project(":battleship:model") implementation project(":battleship:model")
implementation libs.jme3.desktop implementation libs.jme3.desktop
implementation libs.jme3.effects
runtimeOnly libs.jme3.awt.dialogs runtimeOnly libs.jme3.awt.dialogs
runtimeOnly libs.jme3.plugins runtimeOnly libs.jme3.plugins

View File

@@ -9,7 +9,7 @@
# #
# Specifies the map used by the opponent in single mode. # Specifies the map used by the opponent in single mode.
# Single mode is activated if this property is set. # Single mode is activated if this property is set.
map.opponent=maps/map2.json #map.opponent=maps/map2.json
# #
# Specifies the map used by the player in single mode. # Specifies the map used by the player in single mode.
# The player must define their own map if this property is not set. # The player must define their own map if this property is not set.
@@ -23,13 +23,16 @@ map.own=maps/map1.json
# 2, 3 # 2, 3
# defines four shots, namely at the coordinates # defines four shots, namely at the coordinates
# (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3) # (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
robot.targets=2, 3,\ robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3,\
2, 4,\ 2, 4,\
2, 5,\ 2, 5,\
2, 8 2, 6
# #
# Delay in milliseconds between each shot fired by the RobotClient. # Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500 robot.delay=4000
# #
# The dimensions of the game map used in single mode. # The dimensions of the game map used in single mode.
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows. # 'map.width' defines the number of columns, and 'map.height' defines the number of rows.

View File

@@ -1,242 +0,0 @@
package pp.battleship.client;
import com.jme3.audio.AudioData.DataType;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource.Status;
import pp.battleship.notification.Music;
import pp.battleship.notification.MusicEvent;
import pp.battleship.notification.GameEventListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
public class BackgroundMusic implements GameEventListener {
private static final String VOLUME_PREF = "volume";
private static final String MUSIC_ENABLED_PREF = "musicEnabled";
private static final Preferences PREFS = Preferences.userNodeForPackage(BackgroundMusic.class);
static final Logger LOGGER = System.getLogger(BackgroundMusic.class.getName());
private static final String MENU_MUSIC = "Music/MainMenu/Dark_Intro.ogg";
private static final String BATTLE_MUSIC = "Music/BattleTheme/boss_battle_#2_metal_loop.wav";
private static final String GAME_OVER_MUSIC_L = "Music/GameOver/Lose/Lose.ogg";
private static final String GAME_OVER_MUSIC_V = "Music/GameOver/Victory/Victory.wav";
private final AudioNode menuMusic;
private final AudioNode battleMusic;
private final AudioNode gameOverMusicL;
private final AudioNode gameOverMusicV;
private String lastNodePlayed;
private boolean musicEnabled;
private float volume;
private final BattleshipApp app;
/**
* Initializes and controls the BackgroundMusic
*
* @param app The main Application
*/
public BackgroundMusic(BattleshipApp app) {
this.volume = PREFS.getFloat(VOLUME_PREF, 1.0f);
this.musicEnabled = PREFS.getBoolean(MUSIC_ENABLED_PREF, true);
this.app = app;
menuMusic = createAudioNode(MENU_MUSIC);
battleMusic = createAudioNode(BATTLE_MUSIC);
gameOverMusicL = createAudioNode(GAME_OVER_MUSIC_L);
gameOverMusicV = createAudioNode(GAME_OVER_MUSIC_V);
stop(battleMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
lastNodePlayed = menuMusic.getName();
if(musicEnabled) {
play(menuMusic);
}
}
/**
* This method will be used to create the audio node containing the music
*
* @param musicFilePath the file path to the music
* @return the created audio node
*/
private AudioNode createAudioNode(String musicFilePath) {
AudioNode audioNode = new AudioNode(app.getAssetManager(), musicFilePath, DataType.Stream);
audioNode.setVolume(volume * app.getMainVolumeControl().getMainVolume());
audioNode.setPositional(false);
audioNode.setLooping(true);
audioNode.setName(musicFilePath);
return audioNode;
}
/**
* sets the give audio node to play
*
* @param audioNode the audio node which should start to play
*/
public void play(AudioNode audioNode) {
if (musicEnabled && (audioNode.getStatus() == Status.Stopped || audioNode.getStatus() == Status.Paused)) {
audioNode.play();
lastNodePlayed = audioNode.getName();
}
}
/**
* stops the given audio node from playing
*
* @param audioNode the audio node to be stopped
*/
public void stop(AudioNode audioNode) {
if (audioNode.getStatus() == Status.Playing) {
audioNode.stop();
}
}
/**
* pauses the given audi node
*
* @param audioNode the audio node to be paused
*/
public void pause(AudioNode audioNode) {
if (audioNode.getStatus() == Status.Playing) {
audioNode.pause();
}
}
/**
* Toggle Method to control the music to switch it on or off
*/
public void toggleMusic() {
this.musicEnabled = !this.musicEnabled;
if (musicEnabled) {
switch (lastNodePlayed){
case MENU_MUSIC:
play(menuMusic);
break;
case BATTLE_MUSIC:
play(battleMusic);
break;
case GAME_OVER_MUSIC_L:
play(gameOverMusicL);
break;
case GAME_OVER_MUSIC_V:
play(gameOverMusicV);
break;
}
} else {
pause(menuMusic);
pause(battleMusic);
pause(gameOverMusicL);
pause(gameOverMusicV);
}
PREFS.putBoolean(MUSIC_ENABLED_PREF, musicEnabled);
}
/**
* this method is used when the main volume changes
*/
public void setVolume(){
setVolume(PREFS.getFloat(VOLUME_PREF, 1.0f));
}
/**
* Method to set the volume for the music
*
* @param volume float to transfer the new volume
*/
public void setVolume(float volume) {
this.volume = volume;
float mainVolume = app.getMainVolumeControl().getMainVolume();
menuMusic.setVolume(volume * mainVolume);
battleMusic.setVolume(volume * mainVolume);
gameOverMusicL.setVolume(volume * mainVolume);
gameOverMusicV.setVolume(volume * mainVolume);
PREFS.putFloat(VOLUME_PREF, volume);
}
/**
* This method retuns the volume
*
* @return the current volume as a float
*/
public float getVolume() {
return volume;
}
/**
* Returns if music should be played or not
*
* @return boolean value in music should be played
*/
public boolean isMusicEnabled() {
return musicEnabled;
}
/**
* changes the music to the specified music if it isn't already playing
*
* @param music the music to play
*/
public void changeMusic(Music music) {
if(music == Music.MENU_THEME && !lastNodePlayed.equals(MENU_MUSIC)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(battleMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
play(menuMusic);
lastNodePlayed = menuMusic.getName();
} else if (music == Music.BATTLE_THEME && !lastNodePlayed.equals(BATTLE_MUSIC)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
play(battleMusic);
lastNodePlayed = battleMusic.getName();
} else if (music == Music.GAME_OVER_THEME_L && !lastNodePlayed.equals(GAME_OVER_MUSIC_L)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(battleMusic);
stop(gameOverMusicV);
play(gameOverMusicL);
lastNodePlayed = gameOverMusicL.getName();
} else if (music == Music.GAME_OVER_THEME_V && !lastNodePlayed.equals(GAME_OVER_MUSIC_V)){
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(battleMusic);
stop(gameOverMusicL);
play(gameOverMusicV);
lastNodePlayed = gameOverMusicV.getName();
}
}
/**
* the method which receives the Event
*
* @param music the received Event
*/
@Override
public void receivedEvent (MusicEvent music){
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
switch (music.music()){
case MENU_THEME:
changeMusic(Music.MENU_THEME);
break;
case BATTLE_THEME:
changeMusic(Music.BATTLE_THEME);
break;
case GAME_OVER_THEME_L:
changeMusic(Music.GAME_OVER_THEME_L);
break;
case GAME_OVER_THEME_V:
changeMusic(Music.GAME_OVER_THEME_V);
break;
}
}
}

View File

@@ -122,15 +122,7 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
*/ */
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed); private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/** private EffectHandler effectHandler;
* The Object which handles the background music
*/
private BackgroundMusic backgroundMusic;
/**
* The object that handles the main volume
*/
private MainVolume mainVolume;
static { static {
// Configure logging // Configure logging
@@ -165,6 +157,7 @@ private BattleshipApp() {
logic.addListener(this); logic.addListener(this);
setShowSettings(config.getShowSettings()); setShowSettings(config.getShowSettings());
setSettings(makeSettings()); setSettings(makeSettings());
effectHandler = null;
} }
/** /**
@@ -234,11 +227,8 @@ public void simpleInitApp() {
setupInput(); setupInput();
setupStates(); setupStates();
setupGui(); setupGui();
effectHandler = new EffectHandler(this);
serverConnection.connect(); serverConnection.connect();
mainVolume = new MainVolume(this);
backgroundMusic = new BackgroundMusic(this);
logic.addListener(backgroundMusic);
} }
/** /**
@@ -279,11 +269,20 @@ private void setupStates() {
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));
attachGameMusic();
attachGameSound(); attachGameSound();
stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState());
} }
/**
* Attaches the game sound state and sets its initial enabled state.
*/
private void attachGameMusic() {
final GameMusic gameMusic = new GameMusic();
gameMusic.setEnabled(GameMusic.enabledInPreferences());
stateManager.attach(gameMusic);
}
/** /**
* Attaches the game sound state and sets its initial enabled state. * Attaches the game sound state and sets its initial enabled state.
*/ */
@@ -441,21 +440,7 @@ void errorDialog(String errorMessage) {
.open(); .open();
} }
/** public EffectHandler getEffectHandler() {
* this method returns the object which handles the background music return effectHandler;
*
* @return BackgroundMusic
*/
public BackgroundMusic getBackgroundMusic(){
return backgroundMusic;
}
/**
* this method returns the object which handles the main volume
*
* @return an object of MainVolume
*/
public MainVolume getMainVolumeControl(){
return mainVolume;
} }
} }

View File

@@ -0,0 +1,197 @@
package pp.battleship.client;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import pp.battleship.model.Battleship;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* EffectHandler manages the creation and manipulation of particle effects in the Battleship application.
*/
public class EffectHandler {
private final BattleshipApp app;
private final Map<Battleship, List<ParticleEmitter>> effects;
/**
* Constructs an EffectHandler with the specified BattleshipApp instance.
*
* @param app the BattleshipApp instance
*/
public EffectHandler(BattleshipApp app) {
this.app = app;
effects = new HashMap<>();
}
/**
* Creates a fire effect at the specified position for the given Battleship.
*
* @param point the position where the fire effect will be created
* @param ship the Battleship associated with the fire effect
* @return a Node containing the fire effect
*/
public Node createFire(Vector3f point, Battleship ship) {
Node parent = new Node();
parent.setLocalTranslation(point);
ParticleEmitter fire = initializeParticleEmitter(
"Effects/Explosion/flame.png",
2,2,
new ColorRGBA(1f, 0f, 0f, 1f),
new ColorRGBA(1f, 1f, 0f, 0.5f),
new Vector3f(0, 1.5f, 0),
50,
.4f,
0.05f,
1f,
2f,
0.2f,
new Vector3f(0, 0, 0)
);
ParticleEmitter smoke = initializeParticleEmitter(
"Effects/Smoke/Smoke.png",
15,
1,
new ColorRGBA(1f, 1f, 1f, 0f),
new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
new Vector3f(0, 1f, 0),
600,
.2f,
0.1f,
1f,
5f,
0.25f,
new Vector3f(0, 0, 0)
);
parent.attachChild(fire);
parent.attachChild(smoke);
List<ParticleEmitter> oldEffects = new ArrayList<>(effects.getOrDefault(ship, new ArrayList<>()));
oldEffects.add(fire);
oldEffects.add(smoke);
effects.put(ship, oldEffects);
return parent;
}
/**
* Creates a water splash effect at the specified position.
*
* @param pos the position where the water splash effect will be created
* @return a Geometry representing the water splash effect
*/
public Geometry waterSplash(Vector3f pos) {
ParticleEmitter water = initializeParticleEmitter(
"Effects/Explosion/flash.png",
2,2,
new ColorRGBA(0.3f, 0.8f, 1f, 0f),
new ColorRGBA(0f, 0f, 1f, 1f),
new Vector3f(0, 3, 0),
100,
.6f,
0.05f,
1f,
1.5f,
0.3f,
new Vector3f(0, 4f, 0)
);
water.setLocalTranslation(pos);
water.emitAllParticles();
water.setParticlesPerSec(0);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
deleteSplash(water);
}
}, 2000);
return water;
}
/**
* Creates a debris splash effect at the specified position.
*
* @param pos the position where the debris splash effect will be created
* @return a Geometry representing the debris splash effect
*/
public Geometry debrisSplash(Vector3f pos) {
ParticleEmitter debris = initializeParticleEmitter(
"Effects/Explosion/Debris.png",
3,3,
new ColorRGBA(0.1f, 0.1f, 0.1f, 0f),
new ColorRGBA(0.5f, 0.5f, 0.5f, .8f),
new Vector3f(0, 2f, 0),
50,
0.1f,
0.5f,
1f,
1.5f,
0.5f,
new Vector3f(0, 0, 0)
);
debris.setLocalTranslation(pos);
debris.emitAllParticles();
debris.setParticlesPerSec(0);
return debris;
}
/**
* Deletes the specified splash effect from the scene.
*
* @param splash the Geometry representing the splash effect to be deleted
*/
private void deleteSplash(Geometry splash) {
splash.getParent().detachChild(splash);
}
/**
* Stops all particle effects associated with the specified Battleship.
*
* @param ship the Battleship whose effects are to be destroyed
*/
public void destroyShip(Battleship ship) {
for (ParticleEmitter emitter : effects.get(ship)) {
emitter.setParticlesPerSec(0);
}
}
private ParticleEmitter initializeParticleEmitter(
String texturePath, int imagesX, int imagesY, ColorRGBA endColor, ColorRGBA startColor, Vector3f initialVelocity,
int particleCount, float startSize, float endSize, float lowLife, float highLife,
float velocityVariation, Vector3f gravity
) {
ParticleEmitter emitter = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, particleCount);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture(texturePath));
emitter.setMaterial(mat);
emitter.setImagesX(imagesX);
emitter.setImagesY(imagesY);
emitter.setEndColor(endColor);
emitter.setStartColor(startColor);
emitter.getParticleInfluencer().setInitialVelocity(initialVelocity);
emitter.setStartSize(startSize);
emitter.setEndSize(endSize);
emitter.setLowLife(lowLife);
emitter.setHighLife(highLife);
emitter.getParticleInfluencer().setVelocityVariation(velocityVariation);
emitter.setGravity(gravity);
return emitter;
}
}

View File

@@ -0,0 +1,114 @@
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.audio.AudioNode;
import java.util.prefs.Preferences;
import static pp.JmeUtil.loadSound;
import static pp.util.PreferencesUtils.getPreferences;
/**
* An application state that plays sounds.
*/
public class GameMusic extends AbstractAppState {
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
private static final String ENABLED_PREF = "enabled";
private static final String VOLUME_PREF = "volume";
private static final String MUSIC_PATH = "Sound/background.wav";
private AudioNode backgroundMusic;
private float volume;
/**
* Returns whether the music is enabled in the user preferences.
*
* @return true if music is enabled, false otherwise
*/
public static boolean enabledInPreferences() {
return PREFERENCES.getBoolean(ENABLED_PREF, true);
}
/**
* Returns the music volume level stored in the user preferences.
*
* @return the volume level as a float (default is 0.5f)
*/
public static float volumeInPreferences() {
return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
}
/**
* Initializes the game music system
*
* @param stateManager the state manager of the game
* @param app the main application
*/
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
backgroundMusic = loadSound(app, MUSIC_PATH);
setMusicVolume(volumeInPreferences());
if (isEnabled()) playMusic();
}
/**
* Sets the enabled state of this AppState.
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
*
* @param enabled {@code true} to enable the AppState, {@code false} to disable it.
*/
@Override
public void setEnabled(boolean enabled) {
if (enabled && !isEnabled()) {
playMusic();
}
else if (!enabled && isEnabled()) {
stopMusic();
}
super.setEnabled(enabled);
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
}
/**
* Plays the background music.
*/
public void playMusic() {
if (backgroundMusic != null) {
backgroundMusic.play();
}
}
/**
* Stops background music.
*/
public void stopMusic() {
if (backgroundMusic != null) {
backgroundMusic.stop();
}
}
/**
* Sets the volume of the background music and saves the volume setting in user preferences.
*
* @param volume the volume level to set (0.0f to 1.0f)
*/
public void setMusicVolume(float volume) {
if (backgroundMusic != null) {
backgroundMusic.setVolume(volume);
this.volume = volume;
PREFERENCES.putFloat(VOLUME_PREF, volume);
}
}
/**
* Returns volume stored in class
* @return volume
*/
public float getVolume() {
return this.volume;
}
}

View File

@@ -10,11 +10,7 @@
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;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode; import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.battleship.notification.GameEventListener; import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent; import pp.battleship.notification.SoundEvent;
@@ -22,25 +18,21 @@
import java.lang.System.Logger.Level; import java.lang.System.Logger.Level;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import static pp.JmeUtil.loadSound;
import static pp.util.PreferencesUtils.getPreferences; import static pp.util.PreferencesUtils.getPreferences;
/** /**
* An application state that plays sounds. * An application state that plays sounds.
*/ */
public class GameSound extends AbstractAppState implements GameEventListener { public class GameSound extends AbstractAppState implements GameEventListener {
static final Logger LOGGER = System.getLogger(GameSound.class.getName()); private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameSound.class); private static final Preferences PREFERENCES = getPreferences(GameSound.class);
private static final String ENABLED_PREF = "enabled"; //NON-NLS private static final String ENABLED_PREF = "enabled"; //NON-NLS
private static final String SOUND_VOLUME_PREF = "volume";
private float volume;
private AudioNode splashSound; private AudioNode splashSound;
private AudioNode shipDestroyedSound; private AudioNode shipDestroyedSound;
private AudioNode explosionSound; private AudioNode explosionSound;
private AudioNode rocketSound; private AudioNode shellFlyingSound;
private BattleshipApp app;
/** /**
* Checks if sound is enabled in the preferences. * Checks if sound is enabled in the preferences.
@@ -82,33 +74,19 @@ public void setEnabled(boolean enabled) {
@Override @Override
public void initialize(AppStateManager stateManager, Application app) { public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app); super.initialize(stateManager, app);
this.app = (BattleshipApp) app;
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
rocketSound = loadSound(app, "Sound/Effects/rocket.wav"); shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.wav");
volume = PREFERENCES.getFloat(SOUND_VOLUME_PREF, 1.0f);
} }
/** /**
* Loads a sound from the specified file. * Plays the shell flying sound effect.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/ */
private AudioNode loadSound(Application app, String name) { public void shellFly() {
try { if (isEnabled() && shellFlyingSound != null) {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); shellFlyingSound.playInstance();
sound.setLooping(false);
sound.setPositional(false);
return sound;
} }
catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
} }
/** /**
@@ -135,65 +113,13 @@ public void shipDestroyed() {
shipDestroyedSound.playInstance(); shipDestroyedSound.playInstance();
} }
/**
* Plays sound effect when a rocket starts
*/
public void rocket() {
if (isEnabled() && rocketSound != null)
rocketSound.playInstance();
}
/**
* this method sets the sound volume of the sounds
*
* @param volume the volume to be set to
*/
public void setSoundVolume(float volume) {
float mainVolume = app.getMainVolumeControl().getMainVolume();
float calculatedVolume = volume * mainVolume;
shipDestroyedSound.setVolume(calculatedVolume);
splashSound.setVolume(calculatedVolume);
explosionSound.setVolume(calculatedVolume);
this.volume = volume;
PREFERENCES.putFloat(SOUND_VOLUME_PREF, volume);
}
/**
* this method will be used if the main volume changes
*/
public void setSoundVolume() {
float mainVolume = app.getMainVolumeControl().getMainVolume();
shipDestroyedSound.setVolume(volume * mainVolume);
splashSound.setVolume(volume * mainVolume);
explosionSound.setVolume(volume * mainVolume);
rocketSound.setVolume(volume * mainVolume);
PREFERENCES.putFloat(SOUND_VOLUME_PREF, volume);
}
/**
* this method returns the sound
*
* @return
*/
public float getVolume(){
return volume;
}
@Override @Override
public void receivedEvent(SoundEvent event) { public void receivedEvent(SoundEvent event) {
switch (event.sound()) { switch (event.sound()) {
case EXPLOSION : case EXPLOSION -> explosion();
explosion(); case SPLASH -> splash();
break; case DESTROYED_SHIP -> shipDestroyed();
case SPLASH : case SHELL_FLYING -> shellFly();
splash();
break;
case DESTROYED_SHIP:
shipDestroyed();
break;
case ROCKET_FIRED:
rocket();
break;
} }
} }
} }

View File

@@ -1,32 +0,0 @@
package pp.battleship.client;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
public class MainVolume {
private static final Preferences PREFS = Preferences.userNodeForPackage(MainVolume.class);
private static final String MAIN_VOLUME_PREFS = "MainVolume";
static final Logger LOGGER = System.getLogger(MainVolume.class.getName());
private float mainVolume;
private BattleshipApp app;
public MainVolume(BattleshipApp app) {
this.mainVolume = PREFS.getFloat(MAIN_VOLUME_PREFS, 1.0f);
this.app = app;
}
public void setMainVolume(float mainVolume) {
LOGGER.log(Level.DEBUG, "setMainVolume: mainVolume = {0}", mainVolume);
app.getBackgroundMusic().setVolume();
app.getStateManager().getState(GameSound.class).setSoundVolume();
this.mainVolume = mainVolume;
PREFS.putFloat(MAIN_VOLUME_PREFS, mainVolume);
}
public float getMainVolume() {
return mainVolume;
}
}

View File

@@ -9,10 +9,7 @@
import com.simsilica.lemur.Button; import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox; import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.Label; import com.simsilica.lemur.Label;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.style.ElementId; import com.simsilica.lemur.style.ElementId;
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel; import pp.dialog.StateCheckboxModel;
@@ -36,14 +33,7 @@ class Menu extends Dialog {
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 volumeSlider;
private static final double SLIDER_DELTA = 0.1;
private static final double SLIDER_MIN_VALUE = 0.0;
private static final double SLIDER_MAX_VALUE = 2.0;
private final VersionedReference<Double> volumeRef;
private final VersionedReference<Double> soundVolumeRef;
private final VersionedReference<Double> mainVolumeRef;
/** /**
* Constructs the Menu dialog for the Battleship application. * Constructs the Menu dialog for the Battleship application.
@@ -53,28 +43,13 @@ class Menu extends Dialog {
public Menu(BattleshipApp app) { public Menu(BattleshipApp app) {
super(app.getDialogManager()); super(app.getDialogManager());
this.app = app; this.app = app;
volumeSlider = 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 Label(lookup("menu.main.volume"), new ElementId("label")));
Slider mainVolumeSlider = createSlider(app.getMainVolumeControl().getMainVolume());
addChild(mainVolumeSlider);
mainVolumeRef = mainVolumeSlider.getModel().createReference();
addChild(new Label(lookup("menu.sound.volume"), new ElementId("label")));
addChild(new Checkbox(lookup("menu.sound-enabled"), addChild(new Checkbox(lookup("menu.sound-enabled"),
new StateCheckboxModel(app, GameSound.class))); new StateCheckboxModel(app, GameSound.class)));
Slider soundSlider = createSlider(app.getStateManager().getState(GameSound.class).getVolume()); addChild(new Checkbox(lookup("menu.music-enabled"), new StateCheckboxModel(app, GameMusic.class)));
addChild(soundSlider);
soundVolumeRef = soundSlider.getModel().createReference();
addChild(new Label(lookup("menu.volume"), new ElementId("label")));
Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
musicToggle.setChecked(app.getBackgroundMusic().isMusicEnabled());
musicToggle.addClickCommands(s -> toggleMusic());
addChild(musicToggle);
Slider volumeSlider = createSlider(app.getBackgroundMusic().getVolume());
addChild(volumeSlider); addChild(volumeSlider);
volumeRef = volumeSlider.getModel().createReference();
addChild(loadButton) addChild(loadButton)
.addClickCommands(s -> ifTopDialog(this::loadDialog)); .addClickCommands(s -> ifTopDialog(this::loadDialog));
@@ -87,72 +62,6 @@ public Menu(BattleshipApp app) {
update(); update();
} }
/**
* this method creates a slider to be used in the menu
*
* @param relativePosition the position of the regulator on the slider
* @return the creates slider
*/
private Slider createSlider(double relativePosition){
Slider slider = new Slider();
slider.setModel(new DefaultRangedValueModel(SLIDER_MIN_VALUE, SLIDER_MAX_VALUE, relativePosition));
slider.setDelta(SLIDER_DELTA);
return slider;
}
/**
* this method is used update the volume when there is a change in the slider
* @param tpf time per frame
*/
@Override
public void update(float tpf){
if(volumeRef.update()){
double newVolume = volumeRef.get();
adjustMusicVolume(newVolume);
}
else if (soundVolumeRef.update()) {
double newSoundVolume = soundVolumeRef.get();
adjustSoundVolume(newSoundVolume);
} else if (mainVolumeRef.update()) {
double newMainVolume = mainVolumeRef.get();
adjustMainVolume(newMainVolume);
}
}
/**
* this method adjusts the main volume
*
* @param newVolume the volume to be set as main volume
*/
private void adjustMainVolume(double newVolume) {
app.getMainVolumeControl().setMainVolume((float) newVolume);
}
/**
* this method adjust the volume for the background music
*
* @param volume is the double value of the volume
*/
private void adjustMusicVolume(double volume) {
app.getBackgroundMusic().setVolume((float) volume);
}
/**
* this method adjusts the volume for the sound
*
* @param volume is a double value of the sound volume
*/
private void adjustSoundVolume(double volume) {
app.getStateManager().getState(GameSound.class).setSoundVolume((float) volume);
}
/**
* this method toggles the background music on and off
*/
private void toggleMusic() {
app.getBackgroundMusic().toggleMusic();
}
/** /**
* Updates the state of the load and save buttons based on the game logic. * Updates the state of the load and save buttons based on the game logic.
*/ */
@@ -162,6 +71,11 @@ public void update() {
saveButton.setEnabled(app.getGameLogic().maySaveMap()); saveButton.setEnabled(app.getGameLogic().maySaveMap());
} }
@Override
public void update(float delta) {
volumeSlider.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,7 +12,7 @@
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.client.server.BattleshipServer; import pp.battleship.client.clienthost.BattleshipServerClient;
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.DialogBuilder; import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog; import pp.dialog.SimpleDialog;
@@ -38,8 +38,10 @@ class NetworkDialog extends SimpleDialog {
private String hostname; private String hostname;
private int portNumber; private int portNumber;
private Future<Object> connectionFuture; private Future<Object> connectionFuture;
private Future<Object> serverFuture;
private Dialog progressDialog; private Dialog progressDialog;
private boolean hostServer = false; private BattleshipServerClient server;
private final Checkbox clientHostCheckbox;
/** /**
* Constructs a new NetworkDialog. * Constructs a new NetworkDialog.
@@ -53,18 +55,14 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f); host.setPreferredWidth(400f);
port.setSingleLine(true); port.setSingleLine(true);
Checkbox serverHost = new Checkbox(lookup("host.own.server"));
serverHost.setChecked(false);
serverHost.addClickCommands(s -> toggleServerHost());
final BattleshipApp app = network.getApp(); final BattleshipApp app = network.getApp();
final Container input = new Container(new SpringGridLayout()); final Container input = new Container(new SpringGridLayout());
input.addChild(new Label(lookup("host.name") + ": ")); input.addChild(new Label(lookup("host.name") + ": "));
input.addChild(host, 1); input.addChild(host, 1);
input.addChild(new Label(lookup("port.number") + ": ")); input.addChild(new Label(lookup("port.number") + ": "));
input.addChild(port, 1); input.addChild(port, 1);
input.addChild(serverHost); clientHostCheckbox = new Checkbox("Host Server");
input.addChild(clientHostCheckbox);
DialogBuilder.simple(app.getDialogManager()) DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog")) .setTitle(lookup("server.dialog"))
.setExtension(d -> d.addChild(input)) .setExtension(d -> d.addChild(input))
@@ -79,54 +77,30 @@ class NetworkDialog extends SimpleDialog {
* Handles the action for the connect button in the connection dialog. * Handles the action for the connect button in the connection dialog.
* Tries to parse the port number and initiate connection to the server. * Tries to parse the port number and initiate connection to the server.
*/ */
private void connectServer() { private void connect() {
LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
try { try {
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText(); hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
portNumber = Integer.parseInt(port.getText()); portNumber = Integer.parseInt(port.getText());
openProgressDialog(); openProgressDialog();
if (clientHostCheckbox.isChecked()) {
serverFuture = network.getApp().getExecutor().submit(this::initServer);
while (server == null || !server.isReady()) {
Thread.sleep(100);
}
}
connectionFuture = network.getApp().getExecutor().submit(this::initNetwork); connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
network.getApp().errorDialog(lookup("port.must.be.integer")); network.getApp().errorDialog(lookup("port.must.be.integer"));
} }
} catch (InterruptedException e) {
throw new RuntimeException(e);
/**
* This method will start a server or just connect to one based on the boolean hostServer
*/
private void connect() {
if(hostServer){
startServer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
connectServer();
} else {
connectServer();
} }
} }
/**
* This method starts a server in a new thread
*/
private void startServer() {
new Thread(() -> {
try{
BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText()));
battleshipServer.run();
} catch (Exception e) {
LOGGER.log(Level.ERROR, e.getMessage(), e);
}
}).start();
}
private void toggleServerHost(){
hostServer = !hostServer;
}
/** /**
* Creates a dialog indicating that the connection is in progress. * Creates a dialog indicating that the connection is in progress.
*/ */
@@ -152,6 +126,23 @@ private Object initNetwork() {
} }
} }
/**
* Tries to initialize the server hosted by the client.
*
* @throws RuntimeException If an error occurs when starting the server.
*/
private Object initServer() {
try {
server = new BattleshipServerClient();
server.run(Integer.parseInt(port.getText()));
return null;
}
catch (Exception e) {
LOGGER.log(Level.ERROR, "Error while starting server", e);
throw new RuntimeException(e);
}
}
/** /**
* This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically * This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically
* updating this dialog. T * updating this dialog. T
@@ -170,6 +161,19 @@ public void update(float delta) {
LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
if (serverFuture != null && serverFuture.isDone()) {
try {
serverFuture.get();
}
catch (ExecutionException e) {
LOGGER.log(Level.ERROR, "Failed to start server", e.getCause());
}
catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Server thread was interrupted", e);
Thread.currentThread().interrupt();
}
}
} }
/** /**

View File

@@ -0,0 +1,38 @@
package pp.battleship.client;
import com.simsilica.lemur.Slider;
/**
* Represents a volume slider for controlling the background music volume in the Battleship game.
* This class extends the {@link Slider} class and interfaces with the {@link GameMusic} instance
* to adjust the volume settings based on user input.
*/
public class VolumeSlider extends Slider {
private final GameMusic gameMusic;
private float volume;
/**
* Constructs a new VolumeSlider instance and initializes it with the current volume level
* from the game music preferences.
*
* @param gameMusic the instance of {@link GameMusic} to control music volume
*/
public VolumeSlider(GameMusic gameMusic) {
super();
this.gameMusic = gameMusic;
volume = gameMusic.getVolume();
getModel().setPercent(volume);
}
/**
* Updates the volume setting based on the current slider position.
* If the slider's percent value has changed, it updates the music volume
* in the associated {@link GameMusic} instance.
*/
public void update() {
if (getModel().getPercent() != volume) {
this.volume = (float) getModel().getPercent();
gameMusic.setMusicVolume(volume);
}
}
}

View File

@@ -5,7 +5,7 @@
// (c) Mark Minas (mark.minas@unibw.de) // (c) Mark Minas (mark.minas@unibw.de)
//////////////////////////////////////// ////////////////////////////////////////
package pp.battleship.client.server; package pp.battleship.client.clienthost;
import com.jme3.network.ConnectionListener; import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection; import com.jme3.network.HostedConnection;
@@ -18,11 +18,14 @@
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.AnimationEndMessage; 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;
import pp.battleship.message.server.*; 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.Battleship;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
@@ -39,16 +42,14 @@
/** /**
* Server implementing the visitor pattern as MessageReceiver for ClientMessages * Server implementing the visitor pattern as MessageReceiver for ClientMessages
*/ */
public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender { public class BattleshipServerClient implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName()); private static final Logger LOGGER = System.getLogger(BattleshipServerClient.class.getName());
private static final File CONFIG_FILE = new File("server.properties"); private static final File CONFIG_FILE = new File("server.properties");
private static int port;
private final BattleshipConfig config = new BattleshipConfig(); private final BattleshipConfig config = new BattleshipConfig();
private Server myServer; private Server myServer;
private final ServerGameLogic logic; private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>(); private final BlockingQueue<ReceivedMessageClient> pendingMessages = new LinkedBlockingQueue<>();
static { static {
// Configure logging // Configure logging
@@ -63,22 +64,37 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
} }
/** /**
* Creates the server. * Creates the server and reads the configuration from the specified file.
* Initializes the game logic and sets up logging.
*/ */
public BattleshipServer(int port) { public BattleshipServerClient() {
config.readFromIfExists(CONFIG_FILE); config.readFromIfExists(CONFIG_FILE);
BattleshipServer.port = port;
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config); logic = new ServerGameLogic(this, config);
} }
public void run() { /**
startServer(); * Checks if the server is ready.
*
* @return true if the server is running, false otherwise
*/
public boolean isReady() {
return myServer != null && myServer.isRunning();
}
/**
* Starts the server and continuously processes incoming messages.
*/
public void run(int port) {
startServer(port);
while (true) while (true)
processNextMessage(); processNextMessage();
} }
private void startServer() { /**
* Starts the server by creating a network server on the specified port.
*/
private void startServer(int port) {
try { try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(port); myServer = Network.createServer(port);
@@ -93,6 +109,9 @@ private void startServer() {
} }
} }
/**
* Processes the next message in the queue.
*/
private void processNextMessage() { private void processNextMessage() {
try { try {
pendingMessages.take().process(logic); pendingMessages.take().process(logic);
@@ -103,24 +122,28 @@ private void processNextMessage() {
} }
} }
/**
* Registers all serializable message classes for network transmission.
*/
private void initializeSerializables() { private void initializeSerializables() {
Serializer.registerClass(GameDetails.class); Serializer.registerClass(GameDetails.class);
Serializer.registerClass(StartBattleMessage.class); Serializer.registerClass(StartBattleMessage.class);
Serializer.registerClass(MapMessage.class); Serializer.registerClass(MapMessage.class);
Serializer.registerClass(ShootMessage.class); Serializer.registerClass(ShootMessage.class);
Serializer.registerClass(EffectMessage.class); Serializer.registerClass(EffectMessage.class);
Serializer.registerClass(AnimationFinishedMessage.class);
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(AnimationEndMessage.class);
Serializer.registerClass(AnimationStartMessage.class);
Serializer.registerClass(SwitchBattleState.class);
} }
/**
* Registers the message and connection listeners for the server.
*/
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.addMessageListener(this, AnimationEndMessage.class); myServer.addMessageListener(this, AnimationFinishedMessage.class);
myServer.addConnectionListener(this); myServer.addConnectionListener(this);
} }
@@ -128,7 +151,7 @@ private void registerListeners() {
public void messageReceived(HostedConnection source, Message message) { public void messageReceived(HostedConnection source, Message message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
if (message instanceof ClientMessage clientMessage) if (message instanceof ClientMessage clientMessage)
pendingMessages.add(new ReceivedMessage(clientMessage, source.getId())); pendingMessages.add(new ReceivedMessageClient(clientMessage, source.getId()));
} }
@Override @Override
@@ -149,6 +172,11 @@ public void connectionRemoved(Server server, HostedConnection hostedConnection)
} }
} }
/**
* Shuts down the server and closes all active connections.
*
* @param exitValue the exit code to terminate the program with
*/
private void exit(int exitValue) { //NON-NLS private void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null) if (myServer != null)

View File

@@ -5,12 +5,12 @@
// (c) Mark Minas (mark.minas@unibw.de) // (c) Mark Minas (mark.minas@unibw.de)
//////////////////////////////////////// ////////////////////////////////////////
package pp.battleship.client.server; package pp.battleship.client.clienthost;
import pp.battleship.message.client.ClientInterpreter; import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
record ReceivedMessage(ClientMessage message, int from) { record ReceivedMessageClient(ClientMessage message, int from) {
void process(ClientInterpreter interpreter) { void process(ClientInterpreter interpreter) {
message.accept(interpreter, from); message.accept(interpreter, from);
} }

View File

@@ -1,179 +0,0 @@
package pp.battleship.client.gui;
import com.jme3.app.Application;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.model.Shot;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Timer;
import java.util.TimerTask;
/**
* This class is used to handle the effects for impacts
*/
public class EffectHandler {
private final AssetManager assetManager;
static final Logger LOGGER = System.getLogger(EffectHandler.class.getName());
private Material particleMat;
/**
* the constructor is used to get the asset manager from the app
*
* @param app the main application
*/
public EffectHandler(Application app) {
assetManager = app.getAssetManager();
particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
}
/**
* creates a new HitEffect
*
* @param battleshipNode the node of the ship
* @param shot the shot which triggered the effect
*/
public void createHitEffect(Node battleshipNode, Shot shot) {
createFieryEffect(battleshipNode,shot, "HitEffect", 30, 0.45f, 0.1f, -0.5f, 1f , 2f, false);
}
/**
* creates a new FireEffect
*
* @param battleshipNode the node of the ship
* @param shot the shot which triggered the effect
*/
public void createFireEffect(Node battleshipNode, Shot shot) {
createFieryEffect(battleshipNode, shot, "FireEffect", 30, 0.1f, 0.05f, -0.9f, 1f , 2f, true);
}
/**
* creates a fiery type hit effect
*
* @param battleshipNode the ship to which the effect should be attached
* @param shot the shot that triggered the effect
* @param name the name of the particle emitter
* @param numOfParticle the overall numberOfParticles
* @param startSize the start size of the particles
* @param endSize the end size of the particles
* @param gravity the gravity of the particles
* @param lowLife the lowest lifetime of a particle
* @param highLife the maximum lifetime of a particle
* @param loop if the effect should be looped
*/
public void createFieryEffect(Node battleshipNode, Shot shot, String name, int numOfParticle, float startSize, float endSize, float gravity,
float lowLife, float highLife, boolean loop) {
ParticleEmitter fieryEffect = new ParticleEmitter(name, Type.Triangle, numOfParticle);
fieryEffect.setMaterial(particleMat);
fieryEffect.setImagesX(2);
fieryEffect.setImagesY(2);
fieryEffect.setStartColor(ColorRGBA.Orange);
fieryEffect.setEndColor(ColorRGBA.Red);
fieryEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1,0));
fieryEffect.setStartSize(startSize);
fieryEffect.setEndSize(endSize);
fieryEffect.setGravity(0, gravity, 0);
fieryEffect.setLowLife(lowLife);
fieryEffect.setHighLife(highLife);
if(!loop) {
fieryEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
fieryEffect.setParticlesPerSec(0);
fieryEffect.emitAllParticles();
} else {
fieryEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
fieryEffect.getLocalTranslation().subtractLocal(battleshipNode.getLocalTranslation());
fieryEffect.setParticlesPerSec(10);
}
battleshipNode.attachChild(fieryEffect);
LOGGER.log(Level.DEBUG, "Created {0} at {1}", name ,fieryEffect.getLocalTranslation().toString());
fieryEffect.addControl(new EffectControl(fieryEffect, battleshipNode));
}
/**
* This method is used to create a miss effect at a certain location
*/
public ParticleEmitter createMissEffect(Shot shot) {
ParticleEmitter missEffect = new ParticleEmitter("MissEffect", Type.Triangle, 15);
missEffect.setMaterial(particleMat);
missEffect.setImagesX(2);
missEffect.setImagesY(2);
missEffect.setStartColor(ColorRGBA.Blue); // Water color
missEffect.setEndColor(ColorRGBA.Cyan);
missEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
missEffect.setStartSize(0.3f);
missEffect.setEndSize(0.05f);
missEffect.setGravity(0, -0.1f, 0);
missEffect.setLowLife(0.5f);
missEffect.setHighLife(1.5f);
missEffect.setParticlesPerSec(0);
missEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
missEffect.emitAllParticles();
missEffect.addControl(new EffectControl(missEffect));
return missEffect;
}
/**
* This inner class is used to control the effects
*/
private static class EffectControl extends AbstractControl {
private final ParticleEmitter emitter;
private final Node parentNode;
/**
* this constructor is used to when the effect should be attached to a specific node
*
* @param emitter the Particle emitter to be controlled
* @param parentNode the node to be attached
*/
public EffectControl(ParticleEmitter emitter, Node parentNode) {
this.emitter = emitter;
this.parentNode = parentNode;
}
/**
* This constructor is used when the effect shouldn't be attached to
* a specific node
*
* @param emitter the Particle emitter to be controlled
*/
public EffectControl(ParticleEmitter emitter){
this.emitter = emitter;
this.parentNode = null;
}
/**
* The method which checks if the Effect is not rendered anymore so it can be removed
*
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) {
if (parentNode != null)
parentNode.detachChild(emitter);
}
}
/**
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
}
}

View File

@@ -28,7 +28,7 @@
* and interaction between the model and the view. * and interaction between the model and the view.
*/ */
class MapView { 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 GRID_LINE_WIDTH = 2f;
private static final float BACKGROUND_DEPTH = -4f; private static final float BACKGROUND_DEPTH = -4f;
private static final float GRID_DEPTH = -1f; private static final float GRID_DEPTH = -1f;

View File

@@ -7,16 +7,20 @@
package pp.battleship.client.gui; 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.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.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell; import pp.battleship.model.Shell;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
import pp.util.Position; import pp.util.Position;
import java.lang.System.Logger;
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.
@@ -28,11 +32,8 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private static final float SHIP_LINE_WIDTH = 6f; private static final float SHIP_LINE_WIDTH = 6f;
private static final float SHOT_DEPTH = -2f; private static final float SHOT_DEPTH = -2f;
private static final float SHIP_DEPTH = 0f; private static final float SHIP_DEPTH = 0f;
private static final float SHELL_DEPTH = 1f;
private static final float INDENT = 4f; private static final float INDENT = 4f;
private static final float SHELL_DEPTH = 8f;
private static final float SHELL_SIZE = 0.75f;
private static final float SHELL_CENTERED_IN_MAP_GRID = 0.0625f;
// Colors used for different visual elements // Colors used for different visual elements
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red; private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
@@ -44,8 +45,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;
static final Logger LOGGER = System.getLogger(MapViewSynchronizer.class.getName());
/** /**
* Constructs a new MapViewSynchronizer for the given MapView. * Constructs a new MapViewSynchronizer for the given MapView.
* Initializes the synchronizer and adds existing elements from the model to the view. * Initializes the synchronizer and adds existing elements from the model to the view.
@@ -67,7 +66,6 @@ public MapViewSynchronizer(MapView view) {
*/ */
@Override @Override
public Spatial visit(Shot shot) { public Spatial visit(Shot shot) {
LOGGER.log(Logger.Level.DEBUG, "Visiting " + shot);
// Convert the shot's model coordinates to view coordinates // Convert the shot's model coordinates to view coordinates
final Position p1 = view.modelToView(shot.getX(), shot.getY()); final Position p1 = view.modelToView(shot.getX(), shot.getY());
final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1); final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1);
@@ -120,24 +118,23 @@ public Spatial visit(Battleship ship) {
} }
/** /**
* this method will create a representation of a shell in the map * 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 Shell element to visit * @param shell The {@code Shell} object to be visualized.
* @return the node the representation is attached to * @return A {@code Spatial} object representing the shell on the map.
*/ */
@Override @Override
public Spatial visit(Shell shell) { public Spatial visit(Shell shell) {
LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell); final ColorRGBA color = ColorRGBA.Black;
final Node shellNode = new Node("shell"); Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f));
final Position p1 = view.modelToView(shell.getX(), shell.getY()); Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS
final Position p2 = view.modelToView(shell.getX() + SHELL_SIZE, shell.getY() + SHELL_SIZE); mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
final Position startPosition = view.modelToView(SHELL_CENTERED_IN_MAP_GRID, SHELL_CENTERED_IN_MAP_GRID); mat.setColor("Color", color);
ellipse.setMaterial(mat);
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(), startPosition.getY(), SHELL_DEPTH, p2.getX() - p1.getX(), p2.getY() - p1.getY(), ColorRGBA.Black)); ellipse.addControl(new Shell2DControl(view, shell));
shellNode.setLocalTranslation(startPosition.getX(), startPosition.getY(), SHELL_DEPTH); return ellipse;
shellNode.addControl(new ShellMapControl(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
return shellNode;
} }
/** /**
@@ -151,7 +148,6 @@ public Spatial visit(Shell shell) {
* @return a Geometry representing the line * @return a Geometry representing the line
*/ */
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) {
LOGGER.log(Logger.Level.DEBUG, "created ship line");
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);
} }
} }

View File

@@ -8,15 +8,26 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.material.RenderState; import com.jme3.material.RenderState.BlendMode;
import com.jme3.renderer.queue.RenderQueue; import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.renderer.queue.RenderQueue.ShadowMode;
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 com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import pp.battleship.client.BattleshipApp; import pp.battleship.client.BattleshipApp;
import pp.battleship.model.*; 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;
import java.util.Timer;
import java.util.TimerTask;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static pp.JmeUtil.mapToWorldCord;
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;
@@ -28,15 +39,13 @@
*/ */
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 KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md";
private static final String UBOAT = "Models/UBoat/14084_WWII_Ship_German_Type_II_U-boat_v2_L1.obj"; //NON-NLS private static final String COLOR = "Color"; //NON-NLS
private static final String BATTLE_SHIP_MODERN = "Models/BattleShipModern/Destroyer.j3o";
private static final String BATTLE_SHIP_MODERN_TEXTURE = "Models/BattleShipModern/BattleshipC.jpg";
private static final String PATROL_BOAT = "Models/PatrolBoat/12219_boat_v2_L2.obj";
private static final String SHELL_ROCKET = "Models/Rocket/Rocket.obj";
private static final String SHIP = "ship"; //NON-NLS private static final String SHIP = "ship"; //NON-NLS
private static final String SHELL = "shell"; private static final String SHOT = "shot"; //NON-NLS
private final EffectHandler effectHandler; private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
private final ShipMap map; private final ShipMap map;
private final BattleshipApp app; private final BattleshipApp app;
@@ -52,7 +61,6 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root); super(app.getGameLogic().getOwnMap(), root);
this.app = app; this.app = app;
this.map = map; this.map = map;
effectHandler = new EffectHandler(app);
addExisting(); addExisting();
} }
@@ -66,7 +74,11 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
*/ */
@Override @Override
public Spatial visit(Shot shot) { public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : effectHandler.createMissEffect(shot); return shot.isHit() ? handleHit(shot) : handleMiss(shot);
}
private Spatial handleMiss(Shot shot) {
return app.getEffectHandler().waterSplash(mapToWorldCord(shot.getX(), shot.getY()));
} }
/** /**
@@ -80,13 +92,46 @@ public Spatial visit(Shot shot) {
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");
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node"); final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
shipNode.getControl(ShipControl.class).hit(shot);
if (ship.isDestroyed()) {
shipNode.getControl(ShipControl.class).destroyed();
effectHandler.createHitEffect(shipNode, shot); new Timer().schedule(new TimerTask() {
effectHandler.createFireEffect(shipNode, shot); @Override
public void run() {
handleShipDestroy(shipNode);
}
}, 10000);
}
return null; return null;
} }
private void handleShipDestroy(Node shipNode) {
shipNode.getParent().detachChild(shipNode);
}
/**
* Creates a cylinder geometry representing the specified shot.
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
*
* @param shot the shot to be represented
* @return the geometry representing the shot
*/
private Geometry createCylinder(Shot shot) {
final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR;
final float height = shot.isHit() ? 1.2f : 0.1f;
final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true);
final Geometry geometry = new Geometry(SHOT, cylinder);
geometry.setMaterial(createColoredMaterial(color));
geometry.rotate(HALF_PI, 0f, 0f);
// compute the center of the shot in world coordinates
geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
return geometry;
}
/** /**
* Visits a {@link Battleship} and creates a graphical representation of it. * Visits a {@link Battleship} and creates a graphical representation of it.
* The representation is either a 3D model or a simple box depending on the * The representation is either a 3D model or a simple box depending on the
@@ -103,43 +148,28 @@ public Spatial visit(Battleship ship) {
final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f); final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f); final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
node.setLocalTranslation(x, 0f, z); node.setLocalTranslation(x, 0f, z);
node.addControl(new ShipControl(ship)); node.addControl(new ShipControl(ship, node, app.getEffectHandler()));
return node; return node;
} }
/** /**
* Visits a shell and creates a graphical representation * Creates and returns a 3D model representation of the given {@code Shell} object
* for visualization in the game.
* *
* @param shell the Shell element to visit * @param shell The {@code Shell} object to be visualized.
* @return the node containing the graphical representation * @return A {@code Spatial} object representing the 3D model of the shell.
*/ */
@Override @Override
public Spatial visit(Shell shell){ public Spatial visit(Shell shell) {
final Node node = new Node(SHELL); final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o");
node.attachChild(createRocket()); model.setLocalScale(.05f);
final float x = shell.getY();
final float z = shell.getX();
node.setLocalTranslation(x + 0.5f, 10f, z + 0.5f);
ShellControl shellControl = new ShellControl(shell, app);
node.addControl(shellControl);
return node;
}
/**
* creates the spatial representation of a rocket
*
* @return a spatial the rocket
*/
private Spatial createRocket() {
final Spatial model = app.getAssetManager().loadModel(SHELL_ROCKET);
model.rotate(PI, 0f, 0f);
model.scale(0.002f);
model.setShadowMode(ShadowMode.CastAndReceive); model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, 0, 0); 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; return model;
} }
@@ -152,85 +182,73 @@ private Spatial createRocket() {
*/ */
private Spatial createShip(Battleship ship) { private Spatial createShip(Battleship ship) {
return switch (ship.getLength()) { return switch (ship.getLength()) {
case 1 -> createPatrolBoat(ship); case 1 -> createBattleship(ship, ShipModel.SHIP1);
case 2 -> createModernBattleship(ship); case 2 -> createBattleship(ship, ShipModel.SHIP2);
case 3 -> createUBoat(ship); case 3 -> createBattleship(ship, ShipModel.SHIP3);
case 4 -> createBattleship(ship); case 4 -> createBattleship(ship, ShipModel.SHIP4);
default -> throw new IllegalArgumentException("Ship length must be between 1 and 4 units long"); default -> createBox(ship);
}; };
} }
/**
* Creates a simple box to represent a battleship that is not of the "King George V" type.
*
* @param ship the battleship to be represented
* @return the geometry representing the battleship as a box
*/
private Spatial createBox(Battleship ship) {
final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f,
0.3f,
0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f);
final Geometry geometry = new Geometry(SHIP, box);
geometry.setMaterial(createColoredMaterial(BOX_COLOR));
geometry.setShadowMode(ShadowMode.CastAndReceive);
return geometry;
}
/**
* Creates a new {@link Material} with the specified color.
* If the color includes transparency (i.e., alpha value less than 1),
* the material's render state is set to use alpha blending, allowing for
* semi-transparent rendering.
*
* @param color the {@link ColorRGBA} to be applied to the material. If the alpha value
* of the color is less than 1, the material will support transparency.
* @return a {@link Material} instance configured with the specified color and,
* if necessary, alpha blending enabled.
*/
private Material createColoredMaterial(ColorRGBA color) {
final Material material = new Material(app.getAssetManager(), UNSHADED);
if (color.getAlpha() < 1f)
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
material.setColor(COLOR, color);
return material;
}
/** /**
* Creates a detailed 3D model to represent a "King George V" battleship. * Creates a detailed 3D model to represent a "King George V" battleship.
* *
* @param ship the battleship to be represented * @param ship the battleship to be represented
* @return the spatial representing the "King George V" battleship * @return the spatial representing the "King George V" battleship
*/ */
private Spatial createBattleship(Battleship ship) { private Spatial createBattleship(Battleship ship, ShipModel shipModel) {
final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL); final Spatial model = app.getAssetManager().loadModel(shipModel.getPath());
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(1.48f); model.scale(shipModel.getScale());
model.setLocalTranslation(shipModel.getTranslation());
model.setShadowMode(ShadowMode.CastAndReceive); model.setShadowMode(ShadowMode.CastAndReceive);
return model; Material mat = new Material(app.getAssetManager(), LIGHTING);
}
/** String colorPath = shipModel.getColorPath();
* creates a detailed 3D model to represent an UBoat String bumpPath = shipModel.getBumpPath();
* if (colorPath != null) mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(colorPath));
* @param ship the ship to be represented if (bumpPath != null) mat.setTexture("NormalMap", app.getAssetManager().loadTexture(bumpPath));
* @return the spatial representing the Uboat
*/
private Spatial createUBoat(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(UBOAT);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); mat.setReceivesShadows(true);
model.scale(0.5f);
model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, -0.3f, 0);
return model;
}
/**
* creates a detailed 3D model to represent the modern battleship
*
* @param ship the ship to be represented
* @return the spatial representing the Modern Battleship
*/
private Spatial createModernBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BATTLE_SHIP_MODERN);
Material mat = new Material(app.getAssetManager(), UNSHADED);
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(BATTLE_SHIP_MODERN_TEXTURE));
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Off);
model.setMaterial(mat); model.setMaterial(mat);
model.setQueueBucket(RenderQueue.Bucket.Opaque);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.08f);
model.setLocalTranslation(0f, 0.2f, 0f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* creates a detailed 3D model to represent the patrol boat
*
* @param ship the ship to be represented
* @return the spatial representing the patrol boat
*/
private Spatial createPatrolBoat(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(PATROL_BOAT);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.0005f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model; return model;
} }

View File

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

View File

@@ -1,65 +1,51 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.math.Quaternion; 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.client.BattleshipApp;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell; import pp.battleship.model.Shell;
import java.lang.System.Logger; import static pp.JmeUtil.mapToWorldCord;
import static pp.util.FloatMath.PI;
/** /**
* This class controls a 3D representation of a shell * 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 { public class ShellControl extends AbstractControl {
private final Shell shell; private final Shell shell;
private final BattleshipApp app;
private static final float MOVE_SPEED = 8.0f;
static final Logger LOGGER = System.getLogger(ShellControl.class.getName());
/** /**
* Constructor to create a new ShellControl object * Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}.
* *
* @param shell the shell to be displayed * @param shell The {@code Shell} being visualized and controlled.
* @param app the main application
*/ */
public ShellControl(Shell shell, BattleshipApp app) { public ShellControl(Shell shell){
super();
this.shell = shell; this.shell = shell;
this.app = app;
} }
/** /**
* this method moves the representation towards it destination * Updates the 3D position and rotation of the shell based on its current state.
* and deletes it if it reaches its target * Converts map coordinates to world coordinates and applies the shell's orientation.
* *
* @param tpf time per frame (in seconds) * @param tpf Time per frame, representing the elapsed time since the last update.
*/ */
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
spatial.move(0, -MOVE_SPEED * tpf, 0); Vector3f pos = shell.getPosition();
spatial.rotate(0f, 0.05f, 0f); Vector3f fixed = mapToWorldCord(pos.x, pos.z);
//LOGGER.log(System.Logger.Level.DEBUG, "moved rocket {0}", spatial.getLocalTranslation().getY()); fixed.setY(pos.y);
if (spatial.getLocalTranslation().getY() <= 1.5){ spatial.setLocalTranslation(fixed);
spatial.getParent().detachChild(spatial); spatial.setLocalRotation(shell.getRotation());
app.getGameLogic().send(new AnimationEndMessage(new IntPoint(shell.getX(), shell.getY()))); spatial.rotate(PI/2,0,0);
}
} }
/**
* This method is called during the rendering phase, but it does not perform any
* operations in this implementation as the control only influences the spatial's
* transformation, not its rendering process.
*
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-specific behavior required for this control
} }
} }

View File

@@ -1,63 +0,0 @@
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.client.BattleshipApp;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.model.IntPoint;
import pp.util.Position;
/**
* This class controls a ShellMap element
*/
public class ShellMapControl extends AbstractControl {
private final Position position;
private final IntPoint pos;
private static final Vector3f VECTOR = new Vector3f();
private final BattleshipApp app;
/**
* constructs a new ShellMapControl object
*
* @param position the position where the shell should move to on the map
* @param app the main application
* @param pos the position the then to render shot goes to
*/
public ShellMapControl(Position position, BattleshipApp app, IntPoint pos) {
super();
this.position = position;
this.pos = pos;
this.app = app;
VECTOR.set(new Vector3f(position.getX(), position.getY(), 0));
}
/**
* this method moves the shell representation to its correct spot and removes it after
* it arrived at its destination
*
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(VECTOR.mult(tpf));
if (spatial.getLocalTranslation().getX() >= position.getX() && spatial.getLocalTranslation().getY() >= position.getY()) {
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(pos));
}
}
/**
* This method is called during the rendering phase, but it does not perform any
* operations in this implementation as the control only influences the spatial's
* transformation, not its rendering process.
*
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
}

View File

@@ -11,127 +11,157 @@
import com.jme3.math.Vector3f; 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.Node;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import pp.battleship.client.EffectHandler;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation;
import pp.battleship.model.Shot;
import java.lang.System.Logger; import java.util.Timer;
import java.lang.System.Logger.Level; import java.util.TimerTask;
import static pp.JmeUtil.mapToWorldCord;
import static pp.util.FloatMath.DEG_TO_RAD; import static pp.util.FloatMath.DEG_TO_RAD;
import static pp.util.FloatMath.TWO_PI; import static pp.util.FloatMath.TWO_PI;
import static pp.util.FloatMath.sin; import static pp.util.FloatMath.sin;
/** /**
* Controls the oscillating pitch motion of a battleship model in the game. * Controls the pitch oscillation and sinking behavior of a {@code Battleship} model in the game.
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length. * The ship tilts back and forth to simulate movement on water, and can also be animated to sink
* when destroyed.
*/ */
class ShipControl extends AbstractControl { class ShipControl extends AbstractControl {
/** private static final float SINK_SPEED = 0.04f;
* The axis of rotation for the ship's pitch (tilting forward and backward). private static final float SINK_ROT_SPEED = 0.1f;
*/
// The axis of rotation for the ship's pitch (tilting).
private final Vector3f axis; private final Vector3f axis;
/** // The duration of one oscillation cycle in seconds.
* The duration of one complete oscillation cycle in seconds.
*/
private final float cycle; private final float cycle;
/** // The amplitude of the pitch oscillation in radians.
* The amplitude of the pitch oscillation in radians, determining how much the ship tilts.
*/
private final float amplitude; private final float amplitude;
/** // Quaternion representing the ship's pitch rotation.
* A quaternion representing the ship's current pitch rotation.
*/
private final Quaternion pitch = new Quaternion(); private final Quaternion pitch = new Quaternion();
/** // The current time within the oscillation cycle.
* the speed at which ships sink
*/
private static final float SINKING_SPEED = -0.05f;
/**
* the threshold when ships should be removed from the scene if they sink below the value
*/
private static final float SHIP_SINKING_REMOVE_THRESHOLD = -0.6f;
/**
* The current time within the oscillation cycle, used to calculate the ship's pitch angle.
*/
private float time; private float time;
/** // Flag indicating if the ship is sinking.
* The ship to be controlled private boolean sinking;
*/
private final Battleship battleship;
static final Logger LOGGER = System.getLogger(ShipControl.class.getName()); // The battleship being controlled.
private final Battleship battleship;
// Node representing the ship in the scene graph.
private final Node shipNode;
// Handles visual effects for the ship.
private final EffectHandler effectHandler;
/** /**
* Constructs a new ShipControl instance for the specified Battleship. * Constructs a new {@code ShipControl} instance to manage the effects for a specified {@code Battleship}.
* The ship's orientation determines the axis of rotation, while its length influences * The ship's orientation determines the axis of rotation, and its length affects the oscillation cycle
* the cycle duration and amplitude of the oscillation. * and amplitude.
* *
* @param ship the Battleship object to control * @param battleship The {@code Battleship} being controlled.
* @param shipNode The scene graph node representing the ship.
* @param effectHandler The {@code EffectHandler} for creating visual effects.
*/ */
public ShipControl(Battleship ship) { public ShipControl(Battleship battleship, Node shipNode, EffectHandler effectHandler) {
battleship = ship; this.battleship = battleship;
this.shipNode = shipNode;
this.effectHandler = effectHandler;
// Determine the axis of rotation based on the ship's orientation sinking = false;
axis = switch (ship.getRot()) { // Determine the axis of rotation based on the ship's orientation.
axis = switch (battleship.getRot()) {
case LEFT, RIGHT -> Vector3f.UNIT_X; case LEFT, RIGHT -> Vector3f.UNIT_X;
case UP, DOWN -> Vector3f.UNIT_Z; case UP, DOWN -> Vector3f.UNIT_Z;
}; };
// Set the cycle duration and amplitude based on the ship's length // Set the cycle duration and amplitude based on the ship's length.
cycle = battleship.getLength() * 2f; cycle = battleship.getLength() * 2f;
amplitude = 5f * DEG_TO_RAD / ship.getLength(); amplitude = 5f * DEG_TO_RAD / battleship.getLength();
} }
/** /**
* Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted * Updates the ship's motion. If the ship is sinking, it animates the sinking process.
* to create a continuous tilting motion, simulating the effect of waves. * Otherwise, it oscillates the ship to simulate wave motion.
* And lets the ship sink if it is destroyed and removes it from the scene when it has completely sunk
* *
* @param tpf time per frame (in seconds), used to calculate the new pitch angle * @param tpf Time per frame, used to update the ship's motion.
*/ */
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
// If spatial is null, do nothing if (sinking) {
handleSinking(tpf);
}
else {
handlePitch(tpf);
}
}
// Handles the sinking animation.
private void handleSinking(float tpf) {
if (spatial == null) return; if (spatial == null) return;
if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= SHIP_SINKING_REMOVE_THRESHOLD) { spatial.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, -1, 0).mult(tpf * SINK_SPEED)));
LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName()); if (battleship.getRot() == Rotation.UP || battleship.getRot() == Rotation.DOWN) {
spatial.getParent().detachChild(spatial); spatial.rotate(tpf * SINK_ROT_SPEED, 0, 0);
} else if (battleship.isDestroyed()) {
spatial.move(0, SINKING_SPEED * tpf, 0);
} else {
// Update the time within the oscillation cycle
time = (time + tpf) % cycle;
// Calculate the current angle of the oscillation
final float angle = amplitude * sin(time * TWO_PI / cycle);
// Update the pitch Quaternion with the new angle
pitch.fromAngleAxis(angle, axis);
// Apply the pitch rotation to the spatial
spatial.setLocalRotation(pitch);
} }
else {
spatial.rotate(0, 0, tpf * SINK_ROT_SPEED);
}
}
// Handles the pitch oscillation to simulate wave movement.
private void handlePitch(float tpf) {
if (spatial == null) return;
// Update time in the oscillation cycle.
time = (time + tpf) % cycle;
// Calculate the pitch angle.
float angle = amplitude * sin(time * TWO_PI / cycle);
// Update pitch rotation.
pitch.fromAngleAxis(angle, axis);
// Apply rotation to the spatial.
spatial.setLocalRotation(pitch);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-specific behavior required.
} }
/** /**
* This method is called during the rendering phase, but it does not perform any * Initiates the ship's sinking animation and schedules its destruction.
* operations in this implementation as the control only influences the spatial's
* transformation, not its rendering process.
*
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/ */
@Override public void destroyed() {
protected void controlRender(RenderManager rm, ViewPort vp) { sinking = true;
// No rendering logic is needed for this control shipNode.attachChild(effectHandler.debrisSplash(shipNode.getLocalTranslation()));
new Timer().schedule(new TimerTask() {
@Override
public void run() {
effectHandler.destroyShip(battleship);
}
}, 4000);
}
/**
* Triggers an effect when the ship is hit by a shot, creating a fire effect at the impact location.
*
* @param shot The shot that hit the ship.
*/
public void hit(Shot shot) {
Vector3f shipNodePos = shipNode.getLocalTranslation();
Vector3f shotWorld = mapToWorldCord(shot.getX(), shot.getY());
Vector3f firePos = shotWorld.subtract(shipNodePos);
shipNode.attachChild(effectHandler.createFire(firePos, battleship));
} }
} }

View File

@@ -0,0 +1,82 @@
package pp.battleship.client.gui;
import com.jme3.math.Vector3f;
/**
* Enum representing different ship models for the Battleship game.
* Each ship model has a corresponding 3D model path, scale, translation, color texture, and optional bump texture.
*/
public enum ShipModel {
SHIP1("Models/Ships/1/ship1.j3o", 0.15f, new Vector3f(0f, 0f, 0f), "Models/Ships/1/ship1_color.png", null),
SHIP2("Models/Ships/2/ship2.j3o", 0.03f, new Vector3f(0f, 0.2f, 0f), "Models/Ships/2/ship2.jpg", null),
SHIP3("Models/Ships/3/ship3.j3o", 0.47f, new Vector3f(0f, -0.2f, 0f), "Models/Ships/3/ship3_color.jpg", null),
SHIP4("Models/Ships/4/ship4.j3o", 1.48f, new Vector3f(0f, 0f, 0f), "Models/Ships/4/ship4_color.jpg", "Models/Ships/4/ship4_bump.jpg");
private final String modelPath;
private final float modelScale;
private final Vector3f translation;
private final String colorPath;
private final String bumpPath;
/**
* Constructs a new ShipModel with the specified parameters.
*
* @param modelPath the path to the 3D model of the ship
* @param modelScale the scale factor to be applied to the model
* @param translation the translation to be applied to the model
* @param colorPath the path to the color texture of the model
* @param bumpPath the optional path to the bump texture of the model (may be null)
*/
ShipModel(String modelPath, float modelScale, Vector3f translation, String colorPath, String bumpPath) {
this.modelPath = modelPath;
this.modelScale = modelScale;
this.translation = translation;
this.colorPath = colorPath;
this.bumpPath = bumpPath;
}
/**
* Returns the path to the bump texture of the ship model.
*
* @return the bump texture path, or null if no bump texture is defined
*/
public String getBumpPath() {
return bumpPath;
}
/**
* Returns the path to the color texture of the ship model.
*
* @return the color texture path
*/
public String getColorPath() {
return colorPath;
}
/**
* Returns the scale factor of the ship model.
*
* @return the scale factor
*/
public float getScale() {
return modelScale;
}
/**
* Returns the path to the 3D model of the ship.
*
* @return the model path
*/
public String getPath() {
return modelPath;
}
/**
* Returns the translation to be applied to the ship model.
*
* @return the translation vector
*/
public Vector3f getTranslation() {
return translation;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,73 +0,0 @@
newmtl Battleship
illum 4
Kd 0.00 0.00 0.00
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
map_Kd BattleshipC.jpg
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn1SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn2SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn3SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn4SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn5SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn6SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn7SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn8SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/battleship-v1--611736.html
License: Free Personal Use Only

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
License: Free Personal Use Only

View File

@@ -1,104 +0,0 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 16.12.2011 14:18:52
newmtl white
Ns 53.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6667 0.6667 0.6667
Kd 0.6667 0.6667 0.6667
Ks 0.1800 0.1800 0.1800
Ke 0.0000 0.0000 0.0000
newmtl boat_elements_black
Ns 55.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0000 0.0000 0.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl boat_glass
Ns 60.0000
Ni 7.0000
d 0.4000
Tr 0.6000
Tf 0.4000 0.4000 0.4000
illum 2
Ka 0.1059 0.1569 0.1451
Kd 0.1059 0.1569 0.1451
Ks 0.6750 0.6750 0.6750
Ke 0.0000 0.0000 0.0000
newmtl boat_screw_hooks_bronze
Ns 80.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2941 0.2157 0.0510
Kd 0.2941 0.2157 0.0510
Ks 0.7200 0.7200 0.7200
Ke 0.0000 0.0000 0.0000
newmtl boat_silver
Ns 80.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.3333 0.3333 0.3333
Kd 0.3333 0.3333 0.3333
Ks 0.7200 0.7200 0.7200
Ke 0.0000 0.0000 0.0000
newmtl boat_buffer
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.2700 0.2700 0.2700
Ke 0.0000 0.0000 0.0000
map_Ka boat_buffer_diffuse.jpg
map_Kd boat_buffer_diffuse.jpg
newmtl boat_roof_accessory
Ns 15.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
map_Ka boat_roof_accessory_diffuse.jpg
map_Kd boat_roof_accessory_diffuse.jpg
newmtl boat_body
Ns 55.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
map_Ka boat_body_diffuse.jpg
map_Kd boat_body_diffuse.jpg

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/boat-v2--225787.html
License: Free Personal Use Only

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1,250 +0,0 @@
#
# Generated by Sweet Home 3D - ven. janv. 02 20:37:08 CET 2015
# http://www.sweethome3d.com/
#
newmtl FrontColorNoCulling
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 0.0
newmtl ForegroundColor
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white
illum 1
Ka 0.48235294 0.5019608 0.5803922
Kd 0.48235294 0.5019608 0.5803922
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_5
illum 1
Ka 0.47843137 0.49803922 0.5764706
Kd 0.47843137 0.49803922 0.5764706
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_10
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl FrontColorNoCulling_11
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl ForegroundColor_12
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Mesh_13
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 0.0
newmtl Cube_1_1_1
illum 1
Ka 0.0 0.0 0.0
Kd 0.0 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_7_7
illum 1
Ka 0.4 0.4 0.4
Kd 0.4 0.4 0.4
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_10_10
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_11_11
illum 2
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl 12_12
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_1_1_Cube_1_1_1_38
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 1.0
newmtl white_Cylinder_58
illum 1
Ka 0.1882353 0.27058825 0.58431375
Kd 0.1882353 0.27058825 0.58431375
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_59
illum 1
Ka 0.3137255 0.14901961 0.011764706
Kd 0.3137255 0.14901961 0.011764706
Ks 0.0 0.0 0.0
Ns 0.0
newmtl 1_1
illum 2
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.5 0.5 0.5
Ns 64.0
Ni 1.0
d 0.48000002
map_Kd Missile_AIM-120_D_[AMRAAM]_1_1.png
newmtl Cube_1_2_2
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_4_4
illum 2
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_5_5
illum 2
Ka 0.8 0.8 0.0
Kd 0.8 0.8 0.0
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_6_6
illum 2
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_10_10_Cylinder_10_10_73
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl 11_11
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_1_1_Cube_1_1_1_76
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
Ni 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_1_1_Cube_1_1_1_76.png
newmtl Cylinder_2_2
illum 2
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_3_3
illum 1
Ka 0.4 0.4 0.0
Kd 0.4 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_4_4_Cylinder_4_4_79
illum 1
Ka 0.0 0.0 0.0
Kd 0.0 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_5_5
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_5_5.png
newmtl Cube_1_6_6
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
Ni 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_6_6.png
newmtl Cylinder_1_1
illum 1
Ka 0.4 0.4 0.4
Kd 0.4 0.4 0.4
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_5_5_Cube_1_5_5_86
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_6_6_Cube_1_6_6_87
illum 1
Ka 0.8 0.0 0.0
Kd 0.8 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_7_7_Cylinder_7_7_88
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_8_8
illum 1
Ka 0.4 0.6 0.0
Kd 0.4 0.6 0.0
Ks 0.0 0.0 0.0
Ns 1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/de/3d-model/aim-120d-missile-51025.html
License: Free Personal Use Only

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1 @@
"Fishing Boat" (https://skfb.ly/6UGtr) by JasperTobias is licensed under Creative Commons Attribution-NonCommercial (http://creativecommons.org/licenses/by-nc/4.0/).

View File

@@ -0,0 +1,22 @@
# Blender 3.6.5 MTL File: 'None'
# www.blender.org
newmtl Boat
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd ship1_color.png
newmtl Boat_2
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd ship1_color.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -0,0 +1,82 @@
# Blender 3.6.5 MTL File: 'None'
# www.blender.org
newmtl Battleship
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd ship2.jpg
newmtl blinn2SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.356400 0.356400 0.366253
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn3SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.346704 0.346704 0.356400
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn4SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.351533 0.346704 0.361307
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn5SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.346704 0.346704 0.356400
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn6SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.349118 0.346704 0.358854
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn7SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.346704 0.346704 0.356400
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl blinn8SG
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.351533 0.346704 0.361307
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,10 @@ newmtl default
Ni 1.5000 Ni 1.5000
d 1.0000 d 1.0000
Tr 0.0000 Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2 illum 2
Ka 1.0000 1.0000 1.0000 Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000 Kd 1.0000 1.0000 1.0000
Ks 0.5400 0.5400 0.5400 Ks 0.5400 0.5400 0.5400
Ke 0.0000 0.0000 0.0000 Ke 0.0000 0.0000 0.0000
map_Ka 14084_WWII_ship_German_Type_II_U-boat_diff.jpg map_Ka ship3_color.jpg
map_Kd 14084_WWII_ship_German_Type_II_U-boat_diff.jpg map_Kd ship3_color.jpg

View File

@@ -1,7 +1,7 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 29.03.2012 14:25:39 # File Created: 29.03.2012 14:25:39
mtllib 14084_WWII_Ship_German_Type_II_U-boat_v2_L1.mtl mtllib ship3.mtl
# #
# object 14084_WWII_ship_German_Type_II_U_boat # object 14084_WWII_ship_German_Type_II_U_boat
@@ -148958,7 +148958,6 @@ vt 0.0198 0.7313 0.0000
g 14084_WWII_ship_German_Type_II_U_boat g 14084_WWII_ship_German_Type_II_U_boat
usemtl default usemtl default
s 32
f 1/1/1 2/2/2 3/3/3 4/4/4 f 1/1/1 2/2/2 3/3/3 4/4/4
f 5/5/5 6/6/6 3/3/3 2/2/2 f 5/5/5 6/6/6 3/3/3 2/2/2
f 7/7/7 8/8/8 3/3/3 6/6/6 f 7/7/7 8/8/8 3/3/3 6/6/6

View File

@@ -0,0 +1,13 @@
# Blender 3.6.5 MTL File: 'None'
# www.blender.org
newmtl _King_George_V
Ns 60.000008
Ka 1.000000 1.000000 1.000000
Ks 0.450000 0.450000 0.450000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd ship4_color.jpg
map_Bump -bm 1.000000 ship4_bump.jpg

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/wwii-ship-german-type-ii-uboat-v2--700733.html
License: Free Personal Use Only

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/boss-battle-2-symphonic-metal
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/game-over-instrumental
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/victory-fanfare-short
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/dark-intro
License: CC0 (public domain)

View File

@@ -41,7 +41,7 @@ public static void main(String[] args) {
*/ */
@Override @Override
public void simpleInitApp() { public void simpleInitApp() {
export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o"); //NON-NLS export("shell.obj", "shell.j3o"); //NON-NLS
stop(); stop();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

View File

@@ -1,18 +0,0 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 16.03.2012 14:15:53
newmtl _King_George_V
Ns 60.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.4500 0.4500 0.4500
Ke 0.0000 0.0000 0.0000
map_Ka King_George_V.jpg
map_Kd King_George_V.jpg
map_bump King_George_V_bump.jpg
bump King_George_V_bump.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
License: Free Personal Use Only

View File

@@ -0,0 +1,42 @@
# Blender 3.6.5 MTL File: 'untitled.blend'
# www.blender.org
newmtl base
Ns 467.358765
Ka 0.636364 0.636364 0.636364
Kd 0.000000 0.000000 0.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 3
newmtl ring
Ns 467.358765
Ka 0.636364 0.636364 0.636364
Kd 0.031430 0.012811 0.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 3
newmtl tip
Ns 467.358765
Ka 0.636364 0.636364 0.636364
Kd 0.032954 0.004269 0.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 3
newmtl top
Ns 467.358765
Ka 0.636364 0.636364 0.636364
Kd 0.000489 0.006614 0.000950
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 3

File diff suppressed because it is too large Load Diff

View File

@@ -1,108 +0,0 @@
package pp.battleship.game.client;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.SwitchBattleState;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.notification.Music;
import pp.battleship.notification.Sound;
public class AnimationState extends ClientState {
private boolean myTurn;
/**
* creates an object of AnimationState
*
* @param logic the client logic
* @param myTurn a boolean containing if it is the clients turn
* @param position the position a shell should be created
*/
public AnimationState(ClientGameLogic logic, boolean myTurn, IntPoint position) {
super(logic);
logic.playMusic(Music.BATTLE_THEME);
this.myTurn = myTurn;
if(myTurn) {
logic.getOpponentMap().add(new Shell(position));
}else {
logic.getOwnMap().add(new Shell(position));
logic.playSound(Sound.ROCKET_FIRED);
}
}
/**
* This method makes sure the client renders the correct view
*
* @return true
*/
@Override
boolean showBattle() {
return true;
}
/**
* Reports the effect of a shot based on the server message.
*
* @param msg the message containing the effect of the shot
*/
@Override
public void receivedEffect(EffectMessage msg) {
ClientGameLogic.LOGGER.log(System.Logger.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, msg.isGameLost()));
}
}
/**
* this method is used to change the client to the battle state again
*
* @param msg the message to process
*/
@Override
public void receivedSwitchBattleState(SwitchBattleState msg) {
logic.setState(new BattleState(logic, msg.isTurn()));
}
/**
* Determines which map (own or opponent's) should be affected by the shot based on the message.
*
* @param msg the effect message received from the server
* @return the map (either the opponent's or player's own map) that is affected by the shot
*/
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);
}
}

View File

@@ -8,9 +8,13 @@
package pp.battleship.game.client; package pp.battleship.game.client;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.AnimationStartMessage; import pp.battleship.message.server.EffectMessage;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.notification.Music; import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.notification.Sound;
import java.lang.System.Logger.Level;
/** /**
* 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.
@@ -26,15 +30,9 @@ class BattleState extends ClientState {
*/ */
public BattleState(ClientGameLogic logic, boolean myTurn) { public BattleState(ClientGameLogic logic, boolean myTurn) {
super(logic); super(logic);
logic.playMusic(Music.BATTLE_THEME);
this.myTurn = myTurn; this.myTurn = myTurn;
} }
/**
* This method makes sure the client renders the correct view
*
* @return true
*/
@Override @Override
public boolean showBattle() { public boolean showBattle() {
return true; return true;
@@ -48,8 +46,30 @@ else if (logic.getOpponentMap().isValid(pos))
logic.send(new ShootMessage(pos)); logic.send(new ShootMessage(pos));
} }
/**
* Reports the effect of a shot based on the server message.
*
* @param msg the message containing the effect of the shot
*/
@Override @Override
public void receivedAnimationStart(AnimationStartMessage msg){ public void receivedEffect(EffectMessage msg) {
logic.setState(new AnimationState(logic, msg.isMyTurn(), msg.getPosition())); ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
myTurn = msg.isMyTurn();
logic.setInfoText(msg.getInfoTextKey());
Shell shell = new Shell(msg.getShot());
affectedMap(msg).add(shell);
logic.playSound(Sound.SHELL_FLYING);
logic.setState(new ShootingState(logic, shell, myTurn, msg));
}
/**
* Determines which map (own or opponent's) should be affected by the shot based on the message.
*
* @param msg the effect message received from the server
* @return the map (either the opponent's or player's own map) that is affected by the shot
*/
private ShipMap affectedMap(EffectMessage msg) {
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
} }
} }

View File

@@ -8,7 +8,10 @@
package pp.battleship.game.client; package pp.battleship.game.client;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.server.*; import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.model.ShipMap; import pp.battleship.model.ShipMap;
import pp.battleship.model.dto.ShipMapDTO; import pp.battleship.model.dto.ShipMapDTO;
@@ -17,8 +20,6 @@
import pp.battleship.notification.GameEventBroker; import pp.battleship.notification.GameEventBroker;
import pp.battleship.notification.GameEventListener; import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.InfoTextEvent; import pp.battleship.notification.InfoTextEvent;
import pp.battleship.notification.Music;
import pp.battleship.notification.MusicEvent;
import pp.battleship.notification.Sound; import pp.battleship.notification.Sound;
import pp.battleship.notification.SoundEvent; import pp.battleship.notification.SoundEvent;
@@ -225,26 +226,6 @@ public void received(EffectMessage msg) {
state.receivedEffect(msg); state.receivedEffect(msg);
} }
/**
* Reports that the client should start an animation
*
* @param msg the AnimationStartMessage received
*/
@Override
public void received(AnimationStartMessage msg) {
state.receivedAnimationStart(msg);
}
/**
* Reports that the client should move to the battle state
*
* @param msg the SwitchBattleState received
*/
@Override
public void received(SwitchBattleState msg) {
state.receivedSwitchBattleState(msg);
}
/** /**
* Initializes the player's own map, opponent's map, and harbor based on the game details. * Initializes the player's own map, opponent's map, and harbor based on the game details.
* *
@@ -277,15 +258,6 @@ public void playSound(Sound sound) {
notifyListeners(new SoundEvent(sound)); notifyListeners(new SoundEvent(sound));
} }
/**
* Emits an event to play the specified music
*
* @param music the music to be played
*/
public void playMusic(Music music) {
notifyListeners(new MusicEvent(music));
}
/** /**
* Loads a map from the specified file. * Loads a map from the specified file.
* *
@@ -332,7 +304,7 @@ public void saveMap(File file) throws IOException {
* *
* @param msg the message to be sent * @param msg the message to be sent
*/ */
public void send(ClientMessage msg) { void send(ClientMessage msg) {
if (clientSender == null) if (clientSender == null)
LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
else else

View File

@@ -7,7 +7,9 @@
package pp.battleship.game.client; package pp.battleship.game.client;
import pp.battleship.message.server.*; import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import java.io.File; import java.io.File;
@@ -163,24 +165,6 @@ void receivedEffect(EffectMessage msg) {
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName()); //NON-NLS ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName()); //NON-NLS
} }
/**
* Reports that the client should start an animation
*
* @param msg the AnimationStartMessage received
*/
void receivedAnimationStart(AnimationStartMessage msg){
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName());
}
/**
* Reports that the client should move to the battle state
*
* @param msg the SwitchBattleState received
*/
void receivedSwitchBattleState(SwitchBattleState msg){
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedSwitchBattleState not allowed in {0}", getName());
}
/** /**
* Loads a map from the specified file. * Loads a map from the specified file.
* *

View File

@@ -16,11 +16,8 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.System.Logger.Level; import java.lang.System.Logger.Level;
import java.util.Arrays;
import java.util.List;
import static pp.battleship.Resources.lookup; import static pp.battleship.Resources.lookup;
import static pp.battleship.game.client.ClientGameLogic.LOGGER;
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW; import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
import static pp.battleship.model.Battleship.Status.NORMAL; import static pp.battleship.model.Battleship.Status.NORMAL;
import static pp.battleship.model.Battleship.Status.VALID_PREVIEW; import static pp.battleship.model.Battleship.Status.VALID_PREVIEW;
@@ -59,7 +56,7 @@ public boolean showEditor() {
*/ */
@Override @Override
public void movePreview(IntPoint pos) { public void movePreview(IntPoint pos) {
LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS ClientGameLogic.LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
if (preview == null || !ownMap().isValid(pos)) return; if (preview == null || !ownMap().isValid(pos)) return;
preview.moveTo(pos); preview.moveTo(pos);
setPreviewStatus(preview); setPreviewStatus(preview);
@@ -74,7 +71,7 @@ public void movePreview(IntPoint pos) {
*/ */
@Override @Override
public void clickOwnMap(IntPoint pos) { public void clickOwnMap(IntPoint pos) {
LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
if (!ownMap().isValid(pos)) return; if (!ownMap().isValid(pos)) return;
if (preview == null) if (preview == null)
modifyShip(pos); modifyShip(pos);
@@ -128,7 +125,7 @@ private void placeShip(IntPoint cursor) {
*/ */
@Override @Override
public void clickHarbor(IntPoint pos) { public void clickHarbor(IntPoint pos) {
LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
if (!harbor().isValid(pos)) return; if (!harbor().isValid(pos)) return;
final Battleship shipAtCursor = harbor().findShipAt(pos); final Battleship shipAtCursor = harbor().findShipAt(pos);
if (preview != null) { if (preview != null) {
@@ -155,7 +152,7 @@ else if (shipAtCursor != null) {
*/ */
@Override @Override
public void rotateShip() { public void rotateShip() {
LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS ClientGameLogic.LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
if (preview == null) return; if (preview == null) return;
preview.rotated(); preview.rotated();
ownMap().remove(preview); ownMap().remove(preview);
@@ -241,9 +238,6 @@ public void loadMap(File file) throws IOException {
final ShipMapDTO dto = ShipMapDTO.loadFrom(file); final ShipMapDTO dto = ShipMapDTO.loadFrom(file);
if (!dto.fits(logic.getDetails())) if (!dto.fits(logic.getDetails()))
throw new IOException(lookup("map.doesnt.fit")); throw new IOException(lookup("map.doesnt.fit"));
else if (!checkMapToLoad(dto)) {
throw new IOException(lookup("ships.dont.fit.the.map"));
}
ownMap().clear(); ownMap().clear();
dto.getShips().forEach(ownMap()::add); dto.getShips().forEach(ownMap()::add);
harbor().clear(); harbor().clear();
@@ -251,40 +245,6 @@ else if (!checkMapToLoad(dto)) {
selectedInHarbor = null; selectedInHarbor = null;
} }
/**
* This method is used to check if the loaded map is correct
*
* @param dto the data transfer object to check
* @return boolean if map is correct or not
*/
private boolean checkMapToLoad(ShipMapDTO dto) {
int mapWidth = dto.getWidth();
int mapHeight = dto.getHeight();
// check if ship is out of bounds
for (int i = 0; i < dto.getShips().size(); i++) {
Battleship battleship = dto.getShips().get(i);
if (battleship.getMaxX() >= mapWidth || battleship.getMinX() < 0 || battleship.getMaxY() >= mapHeight || battleship.getMinY() < 0) {
LOGGER.log(Level.ERROR, "Ship is out of bounds ({0})", battleship.toString());
return false;
}
}
// check if ships overlap
List<Battleship> ships = dto.getShips();
for(Battleship ship:ships){
for(Battleship compareShip:ships){
if(!(ship==compareShip)){
if(ship.collidesWith(compareShip)){
return false;
}
}
}
}
return true;
}
/** /**
* Checks if the player's own map may be loaded from a file. * Checks if the player's own map may be loaded from a file.
* *

View File

@@ -7,8 +7,6 @@
package pp.battleship.game.client; package pp.battleship.game.client;
import pp.battleship.notification.Music;
/** /**
* Represents the state of the client when the game is over. * Represents the state of the client when the game is over.
*/ */
@@ -18,13 +16,8 @@ class GameOverState extends ClientState {
* *
* @param logic the client game logic * @param logic the client game logic
*/ */
GameOverState(ClientGameLogic logic, boolean lost) { GameOverState(ClientGameLogic logic) {
super(logic); super(logic);
if (lost){
logic.playMusic(Music.GAME_OVER_THEME_L);
} else {
logic.playMusic(Music.GAME_OVER_THEME_V);
}
} }
/** /**

View File

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

View File

@@ -40,14 +40,9 @@ public void receivedStartBattle(StartBattleMessage msg) {
logic.setState(new BattleState(logic, msg.isMyTurn())); logic.setState(new BattleState(logic, msg.isMyTurn()));
} }
/**
* This method will revert the client from wait state to editor state
* in case a wrong map was submitted
*
* @param details the game details including map size and ships
*/
@Override @Override
public void receivedGameDetails(GameDetails details){ public void receivedGameDetails(GameDetails details) {
ClientGameLogic.LOGGER.log(Level.WARNING, "Invalid Map"); //NON-NLS
logic.setInfoText("invalid.map"); logic.setInfoText("invalid.map");
logic.setState(new EditorState(logic)); logic.setState(new EditorState(logic));
} }

View File

@@ -8,21 +8,20 @@
package pp.battleship.game.server; package pp.battleship.game.server;
import pp.battleship.BattleshipConfig; import pp.battleship.BattleshipConfig;
import pp.battleship.message.client.AnimationEndMessage; 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;
import pp.battleship.message.server.*; 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.Battleship;
import pp.battleship.model.IntPoint; import pp.battleship.model.IntPoint;
import pp.battleship.model.Rotation;
import pp.util.Position;
import java.lang.System.Logger; import java.lang.System.Logger;
import java.lang.System.Logger.Level; import java.lang.System.Logger.Level;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -40,9 +39,7 @@ public class ServerGameLogic implements ClientInterpreter {
private final ServerSender serverSender; private final ServerSender serverSender;
private Player activePlayer; private Player activePlayer;
private ServerState state = ServerState.WAIT; private ServerState state = ServerState.WAIT;
private Set<Player> waitPlayers = new HashSet<>();
private boolean player1AnimationReady = false;
private boolean player2AnimationReady = false;
/** /**
* Constructs a ServerGameLogic with the specified sender and configuration. * Constructs a ServerGameLogic with the specified sender and configuration.
@@ -147,42 +144,70 @@ public Player addPlayer(int id) {
public void received(MapMessage msg, int from) { public void received(MapMessage msg, int from) {
if (state != ServerState.SET_UP) if (state != ServerState.SET_UP)
LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS
else if (!checkMap(msg, from)) { else {
LOGGER.log(Level.ERROR, "player submitted not allowed Map"); if (checkMap(msg.getShips())) {
send(getPlayerById(from), new GameDetails(config)); 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));
}
} }
else
playerReady(getPlayerById(from), msg.getShips());
} }
/** /**
* Returns true if the map contains correct ship placement and is of the correct size * Handles the reception of a AnimationFinishedMessage.
* *
* @param msg the received MapMessage of the player * @param msg the received MapMessage
* @param from the ID of the Player * @param from the ID of the sender client
* @return a boolean based on if the transmitted map ist correct
*/ */
private boolean checkMap(MapMessage msg, int from) { @Override
int mapWidth = getPlayerById(from).getMap().getWidth(); public void received(AnimationFinishedMessage msg, int from) {
int mapHeight = getPlayerById(from).getMap().getHeight(); if (state != ServerState.ANIMATION) {
LOGGER.log(Level.ERROR, "animation finished not allowed in {0}", state);
// check if ship is out of bounds }
for (Battleship battleship : msg.getShips()){ else {
if (battleship.getMaxX() >= mapWidth || battleship.getMinX() < 0 || battleship.getMaxY() >= mapHeight || battleship.getMinY() < 0) { LOGGER.log(Level.DEBUG, "anim received from {0}", getPlayerById(from));
LOGGER.log(Level.ERROR, "Ship is out of bounds ({0})", battleship.toString()); Player player = getPlayerById(from);
return false; 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);
} }
} }
}
// check if ships overlap /**
List<Battleship> ships = msg.getShips(); * Validates the placement of battleships on the map.
for(Battleship ship:ships){ * Ensures that:
for(Battleship compareShip:ships){ * <ul>
if(!(ship==compareShip)){ * <li>The number of ships matches the configuration.</li>
if(ship.collidesWith(compareShip)){ * <li>Ships are within the map's boundaries.</li>
return false; * <li>Ships do not overlap.</li>
} * </ul>
*
* @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<Battleship> ships) {
int numShips = config.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
if (numShips != ships.size()) return false;
List<IntPoint> 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; return true;
@@ -198,41 +223,11 @@ private boolean checkMap(MapMessage msg, int from) {
public void received(ShootMessage msg, int from) { public void received(ShootMessage msg, int from) {
if (state != ServerState.BATTLE) if (state != ServerState.BATTLE)
LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
else else{
for (Player player : players){ setState(ServerState.ANIMATION);
send(player, new AnimationStartMessage(msg.getPosition(), player == activePlayer)); shoot(getPlayerById(from), msg.getPosition());
setState(ServerState.ANIMATION_WAIT); }
}
}
/**
* Handles a clients message that it is done with the animation
*
* @param msg the AnimationEndMessage to be processed
* @param from the connection ID from which the message was received
*/
@Override
public void received(AnimationEndMessage msg, int from){
if(state != ServerState.ANIMATION_WAIT) {
LOGGER.log(Level.ERROR, "animation not allowed in {0}", state);
return;
}
if(getPlayerById(from) == players.get(0)){
LOGGER.log(Level.DEBUG, "{0} set to true", getPlayerById(from));
player1AnimationReady = true;
shoot(getPlayerById(from), msg.getPosition());
} else if (getPlayerById(from) == players.get(1)){
LOGGER.log(Level.DEBUG, "{0} set to true {1}", getPlayerById(from), getPlayerById(from).toString());
player2AnimationReady = true;
shoot(getPlayerById(from), msg.getPosition());
}
if(player1AnimationReady && player2AnimationReady){
setState(ServerState.BATTLE);
for (Player player : players)
send(player, new SwitchBattleState(player == activePlayer));
player1AnimationReady = false;
player2AnimationReady = false;
}
} }
/** /**
@@ -256,56 +251,40 @@ void playerReady(Player player, List<Battleship> ships) {
} }
/** /**
* This method decides what effectMessage the client should get based on the shot made * Handles the shooting action by the player.
* and switches the active player if a shot was missed
* *
* @param p the player to be sent the message * @param p the player who shot
* @param position the position where the shot would hit in the 2d map model * @param pos the position of the shot
*/ */
void shoot(Player p, IntPoint position) { void shoot(Player p, IntPoint pos) {
final Battleship selectedShip; if (p != activePlayer) return;
if(p != activePlayer){ final Player otherPlayer = getOpponent(activePlayer);
selectedShip = p.getMap().findShipAt(position); final Battleship selectedShip = otherPlayer.getMap().findShipAt(pos);
} else {
selectedShip = getOpponent(p).getMap().findShipAt(position);
}
if (selectedShip == null) { if (selectedShip == null) {
if (p != activePlayer) { // shot missed
send(p, EffectMessage.miss(false, position)); send(activePlayer, EffectMessage.miss(true, pos));
} else { send(otherPlayer, EffectMessage.miss(false, pos));
send(activePlayer, EffectMessage.miss(true, position)); activePlayer = otherPlayer;
}
else {
// shot hit a ship
selectedShip.hit(pos);
if (otherPlayer.getMap().getRemainingShips().isEmpty()) {
// game is over
send(activePlayer, EffectMessage.won(pos, selectedShip));
send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
setState(ServerState.GAME_OVER);
return;
} }
if(player1AnimationReady && player2AnimationReady){ else if (selectedShip.isDestroyed()) {
LOGGER.log(Level.DEBUG, "switched active player"); // ship has been destroyed, but game is not yet over
if(p != activePlayer){ send(activePlayer, EffectMessage.shipDestroyed(true, pos, selectedShip));
activePlayer = p; send(otherPlayer, EffectMessage.shipDestroyed(false, pos, selectedShip));
} else {
activePlayer = getOpponent(p);
}
} }
} else { else {
selectedShip.hit(position); // ship has been hit, but it hasn't been destroyed
if(getOpponent(activePlayer).getMap().getRemainingShips().isEmpty()){ send(activePlayer, EffectMessage.hit(true, pos));
if(p != activePlayer){ send(otherPlayer, EffectMessage.hit(false, pos));
send(p, EffectMessage.lost(position, selectedShip, activePlayer.getMap().getRemainingShips()));
} else {
send(activePlayer, EffectMessage.won(position, selectedShip));
}
if(player1AnimationReady && player2AnimationReady){
setState(ServerState.GAME_OVER);
}
} else if (selectedShip.isDestroyed()){
if(p != activePlayer){
send(p, EffectMessage.shipDestroyed(false, position, selectedShip));
} else {
send(activePlayer, EffectMessage.shipDestroyed(true, position, selectedShip));
}
} else {
if(p != activePlayer){
send(p, EffectMessage.hit(false, position));
} else {
send(activePlayer, EffectMessage.hit(true, position));
}
} }
} }
} }

View File

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

View File

@@ -7,7 +7,11 @@
package pp.battleship.game.singlemode; package pp.battleship.game.singlemode;
import pp.battleship.message.client.*; 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.model.Battleship; import pp.battleship.model.Battleship;
/** /**
@@ -60,16 +64,9 @@ public void received(MapMessage msg, int from) {
copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList()); copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
} }
/**
* Handles the reception of a AnimationEndMessage
* Creates a copy of the AnimationEndMessage
*
* @param msg the AnimationEndMessage to be processed
* @param from the connection ID from which the message was received
*/
@Override @Override
public void received(AnimationEndMessage msg, int from) { public void received(AnimationFinishedMessage msg, int from) {
copiedMessage = new AnimationEndMessage(msg.getPosition()); copiedMessage = msg;
} }
/** /**

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