38 Commits

Author SHA1 Message Date
Fleischer Hanno hanno.fleischer@unibw.de
f8e97266d5 fixed bug when playing rocket audio to not produce an AudioRender error 2024-10-13 11:53:24 +02:00
Fleischer Hanno hanno.fleischer@unibw.de
9e591e37c3 Added sound to rocket firing 2024-10-13 08:58:48 +02:00
Hanno Fleischer
487305dccc added the rocket sound wav and fixed code and check style
also includes minor fixes
2024-10-13 08:09:19 +02:00
Hanno Fleischer
22d827b074 adjusted positioning of the modern battle ship and set fullscreen mode to false 2024-10-11 11:53:13 +02:00
Hanno Fleischer
074b38540d Merge branch 'b_Fleischer_Hanno' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into b_Fleischer_Hanno 2024-10-11 11:47:05 +02:00
Hanno Fleischer
3838766504 added a fire effect for hit ships
this will now display a burning fire at the position where the ships was hit
and displays it until thge ship is removed.
2024-10-11 11:46:37 +02:00
Fleischer Hanno hanno.fleischer@unibw.de
54e5719edf added README.txt for the rocket model to credit its source 2024-10-11 10:49:04 +02:00
Hanno Fleischer
9df809ded5 adjusted the ModernBattleShip to be a j30 object and load withc its corresponding texture 2024-10-11 09:54:12 +02:00
Hanno Fleischer
93ae95ce59 adjusted size of rocket, and removed unused methods and import statements 2024-10-11 09:42:12 +02:00
Hanno Fleischer
ffd3951a78 added JavaDocs commects where they where missing and removed outcommented code 2024-10-11 01:25:10 +02:00
Hanno Fleischer
c56767d994 added rest solution for exercise 13
added the representation for the shell element in the map which will be displayed when you shoot,
2024-10-11 00:48:40 +02:00
Fleischer Hanno hanno.fleischer@unibw.de
f99b91324c part solution for exercise 13
added an animation state for the server and client and gave it the functionality to display a 3d model representing the shot of the other person
adjusted the server to serialize the new messages for handling the animation states
2024-10-10 23:10:39 +02:00
Fleischer Hanno hanno.fleischer@unibw.de
da2508395c fixed minor issues and cleaned up code to not include duplicate code. 2024-10-07 17:54:35 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
4820a76ff0 added the backgroundmusic to listeners in the initsimpleapp
and not in the getter of backgroundmusic.
2024-10-05 21:07:00 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
9dc3984f35 renamed the method in BackgroundMusic
renamed the method toogleMusic to toggleMusic.
2024-10-05 19:08:57 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
f6f87c4f5d adjusted the preferences in BackgroundMusic and MainVolume 2024-10-05 18:51:25 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
d3429bf4f0 fixed a bug at the victory music
the victory audio node was not part of the set volume method
now implemented the method to set volume for victory music.
2024-10-05 18:40:30 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
ecbe486d3b minor tweaks to make the code pass the check style 2024-10-05 18:25:25 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
52673dfbce added MainVolume slider in the menu
added the logic so that the volume of music or sound is multiplied by the main volume
created the class MainVolume to handle the logic of the main volume
adjusted methods in GameSounds and BackgroundMusic to integrate main volume
2024-10-05 17:32:14 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
586251b2ad adjusted the paths of the music
added the README.txt for each music piece to state the source of the music.
2024-10-05 14:46:08 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
ca57507b53 added the missing solution for exercise 12
ships will now sink when destroyed and will be removed when they are fully submerged
2024-10-05 14:06:45 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
44a25a2e1f added part solution for exercise 12
added the Effecthandler class to handle the effects for a shot hit or missed
added jme3-effects libary and used it to display the effects for the shots
minor tweaks the the gui and backgroundmuisc
2024-10-05 13:20:34 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
dca0875ad5 Adjusted tthe logic of the server and client
when the client sends a wrong map the server will send the client back to the editro state
2024-10-05 12:35:49 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
0f629252bc fixed the toggle button for the victory music
added the pause(gameOverMusicV) to the toggle method for the music control
so that it will be paused when the music is turned off.
2024-10-05 09:01:48 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
b18705f064 added the logic to play diffrent Background music during diffrent states
added the a Enum for the Music and the MusicEvent
added a song for the game victory
adjusted the backgroundmusic class to handle the logic of the new song
added the Backgroundmusic to the ClientLogic as a EventListener.
2024-10-04 20:43:37 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
961242bb20 added missing javadocs in the NetworkDialog.java 2024-10-04 17:30:21 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
05271beded changed the postion of the host server checkbox
moved the checkbox form below the connect button inside the input
container for the hostname and port. The checkbox is the lowest seated
element in the input container now.
2024-10-04 15:49:07 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
f6bc65471a added solution for exercise 11
added a checkbox to start a server from your client
copied the server to the client and removed its main method and changed the constructor to public
2024-10-04 15:40:38 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
0f080363f3 adjusted the in/de-crement button
adjusted the delta of the slider so the button hav smaller steps in between
2024-10-03 18:33:56 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
6e0a93b74d fixed toggleMusic in BackgroundMusic.java
changed the Preference save form volume to the boolean musicenable
2024-10-03 17:56:12 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
d471f524d0 solution for exercise 10
added BackgroundMusic which is being handled in BackgroundMusic.java
changed the menu to incoporate volume controls for the music
2024-10-03 17:39:54 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
d15f1a3f5f edited battleship.properties
added a new error message in case ships are out of bounds
2024-10-02 21:04:59 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
562a478ef8 added solution for exercise 9
added the models for the 3 remaining ship types
and implemented them in the SeaSynchronizer.java
also added the correct logic for the ships to be displayed correctly
2024-10-02 20:56:05 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
46f75188cb fixed the code for exercise 8
fixed the code so that it uses a for each loop instead of an for int i
and adjusted the part for validating ship overlaping to use the
isCollidingWith method of Battleship
2024-10-02 17:35:34 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
7b70666332 added the solution for exercise 8 for the client
added the checkMapToLoad() in EditorState.java so if the Player tries to
load a map it will be checked if the placement is correct.
2024-10-02 14:50:14 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
1bac56c92c adjusted the server Mapcheck
inverted the else if statement in ServerGameLogic.java received() so that
if the map check returns that the map is correct the programm continues on
instead of throwing an error message and removed the hard limit of an 10x10 map in the checkMap() Method
2024-10-02 14:16:46 +02:00
Hanno Fleischer hanno.fleischer@unibw.de
4dcd53a660 Added part-solution for exercise 8
added the solution for exercise 8 for the sever sided test to ServerGameLogic.java
2024-10-02 13:57:17 +02:00
Fleischer Hanno hanno.fleischer@unibw.de
f759eddda1 solution exercise 7
edited in BattleState.java the receivedMsg() method so that if the game moves to the game over state
the remaining opponent ships will be added to the list of the opponenets instead of your own list.
edited the ShipMap.java so that when the notifylisteners is called for removing an object it will be
handled as an ItemRemovedEvent instead of an ItemAddedEvent
2024-10-02 11:01:13 +02:00
236 changed files with 925247 additions and 2693478 deletions

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Projekte [test]" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/Projekte" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--continue" />
<option name="taskDescriptions">

View File

@@ -9,7 +9,7 @@
#
# Specifies the map used by the opponent in single mode.
# 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.
# The player must define their own map if this property is not set.
@@ -23,10 +23,10 @@ map.own=maps/map1.json
# 2, 3
# defines four shots, namely at the coordinates
# (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3
robot.targets=2, 3,\
2, 4,\
2, 5,\
2, 8
#
# Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500

View File

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

@@ -123,9 +123,14 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/**
* Listener for handling actions triggered by the Escape key.
* The Object which handles the background music
*/
private GameMusic music;
private BackgroundMusic backgroundMusic;
/**
* The object that handles the main volume
*/
private MainVolume mainVolume;
static {
// Configure logging
@@ -198,15 +203,6 @@ DialogManager getDialogManager() {
return dialogManager;
}
/**
* Returns the GameMusic responsible for playing music.
*
* @return The {@link GameMusic} instance.
*/
GameMusic getGameMusic() {
return music;
}
/**
* Returns the game logic handler for the client.
*
@@ -240,7 +236,9 @@ public void simpleInitApp() {
setupGui();
serverConnection.connect();
music = new GameMusic(this, "Sound/Music/battleship.ogg");
mainVolume = new MainVolume(this);
backgroundMusic = new BackgroundMusic(this);
logic.addListener(backgroundMusic);
}
/**
@@ -442,4 +440,22 @@ void errorDialog(String errorMessage) {
.build()
.open();
}
/**
* this method returns the object which handles the background music
*
* @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

@@ -1,170 +0,0 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import static pp.util.PreferencesUtils.getPreferences;
/**
* An application state that plays music.
*/
public class GameMusic extends AbstractAppState implements GameEventListener {
private static final Logger LOGGER = System.getLogger(GameMusic.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
private static final String ENABLED_PREF = "toggle"; //NON-NLS
private static final String SLIDER_PREF = "slider"; //NON-NLS
private AudioNode music;
private boolean enabled;
private float volume;
public GameMusic(Application app, String musicFilePath) {
this.enabled = PREFERENCES.getBoolean(ENABLED_PREF, true);
this.volume = PREFERENCES.getFloat(SLIDER_PREF, 2.0f);
music = new AudioNode(app.getAssetManager(), musicFilePath, AudioData.DataType.Stream);
music.setLooping(true);
music.setPositional(false);
music.setVolume(1.0f);
if(enabled) {
start();
}
}
/**
* Checks if music is enabled in the preferences.
*
* @return {@code true} if music is enabled, {@code false} otherwise.
*/
public static boolean enabledInPreferences() {
return PREFERENCES.getBoolean(ENABLED_PREF, true);
}
/**
* Returns the volume value that is set in the preferences.
*
* @return {@code true} if sound is enabled, {@code false} otherwise.
*/
public static float volumeInPreferences() {
return PREFERENCES.getFloat(SLIDER_PREF, 2.0f);
}
/**
* Toggles the game music on or off.
*/
public void toggleMusic() {
setEnabled(!isEnabled());
}
/**
* Sets the enabled state of this AppState.
* Overrides {@link AbstractAppState#setEnabled(boolean)}
*
* @param enabled {@code true} to enable the AppState, {@code false} to disable it.
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if(enabled) {
start();
} else {
stop();
}
LOGGER.log(Level.INFO, "Music enabled: {0}", enabled); //NON-NLS
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
}
/**
* Sets the volume for the music
*
* @param volume the new volume to be set
*/
public void setVolume(float volume) {
this.volume = volume;
music.setVolume(volume);
LOGGER.log(Level.INFO, "Volume set to: {0}", volume); //NON-NLS
PREFERENCES.putFloat(SLIDER_PREF, volume);
}
/**
* Loads a music from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
private AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);
sound.setPositional(false);
return sound;
}
catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
}
/**
* Starts the music.
*/
public void start() {
if (enabled && (music.getStatus() == AudioSource.Status.Stopped || music.getStatus() == AudioSource.Status.Paused)) {
music.play();
}
}
/**
* Stops the music.
*/
public void stop() {
if (music.getStatus() == AudioSource.Status.Playing) {
music.stop();
}
}
/**
* This method returns the volume
*
* @return float the current volume
*/
public float getVolume() {
return volume;
}
/**
* Returns if music should be played or not
*
* @return boolean value if music is enabled
*/
public boolean isMusicEnabled() {
return enabled;
}
}

View File

@@ -14,6 +14,7 @@
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent;
@@ -27,14 +28,19 @@
* An application state that plays sounds.
*/
public class GameSound extends AbstractAppState implements GameEventListener {
private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
static final Logger LOGGER = System.getLogger(GameSound.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameSound.class);
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 shipDestroyedSound;
private AudioNode explosionSound;
private AudioNode missleSound;
private AudioNode rocketSound;
private BattleshipApp app;
/**
* Checks if sound is enabled in the preferences.
@@ -76,10 +82,13 @@ public void setEnabled(boolean enabled) {
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = (BattleshipApp) app;
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
missleSound = loadSound(app, "Sound/Effects/missle.wav");
rocketSound = loadSound(app, "Sound/Effects/rocket.wav");
volume = PREFERENCES.getFloat(SOUND_VOLUME_PREF, 1.0f);
}
/**
@@ -110,14 +119,6 @@ public void splash() {
splashSound.playInstance();
}
/**
* Plays the missle flyby sound effect.
*/
public void flyBy() {
if (isEnabled() && missleSound != null)
missleSound.playInstance();
}
/**
* Plays the explosion sound effect.
*/
@@ -134,13 +135,65 @@ public void shipDestroyed() {
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
public void receivedEvent(SoundEvent event) {
switch (event.sound()) {
case EXPLOSION -> explosion();
case SPLASH -> splash();
case DESTROYED_SHIP -> shipDestroyed();
case MISSLE_FLYBY -> flyBy();
case EXPLOSION :
explosion();
break;
case SPLASH :
splash();
break;
case DESTROYED_SHIP:
shipDestroyed();
break;
case ROCKET_FIRED:
rocket();
break;
}
}
}

View File

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

@@ -7,7 +7,11 @@
package pp.battleship.client;
import com.simsilica.lemur.*;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.style.ElementId;
import pp.dialog.Dialog;
@@ -33,7 +37,13 @@ class Menu extends Dialog {
private final Button loadButton = new Button(lookup("menu.map.load"));
private final Button saveButton = new Button(lookup("menu.map.save"));
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.
@@ -44,26 +54,32 @@ public Menu(BattleshipApp app) {
super(app.getDialogManager());
this.app = app;
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"),
new StateCheckboxModel(app, GameSound.class)));
Slider soundSlider = createSlider(app.getStateManager().getState(GameSound.class).getVolume());
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);
volumeRef = volumeSlider.getModel().createReference();
addChild(loadButton)
.addClickCommands(s -> ifTopDialog(this::loadDialog));
addChild(saveButton)
.addClickCommands(s -> ifTopDialog(this::saveDialog));
Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
musicToggle.setChecked(app.getGameMusic().isMusicEnabled());
musicToggle.addClickCommands(s -> toggleMusic());
addChild(musicToggle);
Slider volumeSlider = new Slider(lookup("menu.music.slider"));
volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 4.0, app.getGameMusic().getVolume()));
volumeSlider.setDelta(0.4);
addChild(volumeSlider);
volumeRef = volumeSlider.getModel().createReference();
addChild(new Button(lookup("menu.return-to-game")))
.addClickCommands(s -> ifTopDialog(this::close));
addChild(new Button(lookup("menu.quit")))
@@ -72,17 +88,44 @@ public Menu(BattleshipApp app) {
}
/**
* Updates the state of the load/save buttons and volume based on the game logic.
* 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() {
loadButton.setEnabled(app.getGameLogic().mayLoadMap());
saveButton.setEnabled(app.getGameLogic().maySaveMap());
public void update(float tpf){
if(volumeRef.update()){
double newVolume = volumeRef.get();
adjustVolume(newVolume);
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);
}
/**
@@ -90,15 +133,33 @@ public void update() {
*
* @param volume is the double value of the volume
*/
private void adjustVolume(double volume) {
app.getGameMusic().setVolume((float) 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.getGameMusic().toggleMusic();
app.getBackgroundMusic().toggleMusic();
}
/**
* Updates the state of the load and save buttons based on the game logic.
*/
@Override
public void update() {
loadButton.setEnabled(app.getGameLogic().mayLoadMap());
saveButton.setEnabled(app.getGameLogic().maySaveMap());
}
/**

View File

@@ -12,6 +12,7 @@
import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.battleship.client.server.BattleshipServer;
import pp.dialog.Dialog;
import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog;
@@ -52,10 +53,9 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f);
port.setSingleLine(true);
Checkbox serverHost = new Checkbox(lookup("menu.host"));
Checkbox serverHost = new Checkbox(lookup("host.own.server"));
serverHost.setChecked(false);
serverHost.addClickCommands(s -> toggleServerHost());
addChild(serverHost);
final BattleshipApp app = network.getApp();
final Container input = new Container(new SpringGridLayout());
@@ -63,11 +63,12 @@ class NetworkDialog extends SimpleDialog {
input.addChild(host, 1);
input.addChild(new Label(lookup("port.number") + ": "));
input.addChild(port, 1);
input.addChild(serverHost);
DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog"))
.setExtension(d -> d.addChild(input))
.setOkButton(lookup("button.connect"), d -> connectLocally())
.setOkButton(lookup("button.connect"), d -> connect())
.setNoButton(lookup("button.cancel"), app::closeApp)
.setOkClose(false)
.setNoClose(false)
@@ -78,7 +79,7 @@ class NetworkDialog extends SimpleDialog {
* Handles the action for the connect button in the connection dialog.
* Tries to parse the port number and initiate connection to the server.
*/
private void connect() {
private void connectServer() {
LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
try {
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
@@ -92,38 +93,36 @@ private void connect() {
}
/**
* Starts a server or if not host connects to one
* This method will start a server or just connect to one based on the boolean hostServer
*/
private void connectLocally() {
private void connect() {
if(hostServer){
startServer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e.getMessage());
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
connectServer();
} else {
connectServer();
}
connect();
}
/**
* Starts a host server in a new thread
* This method starts a server in a new thread
*/
private void startServer() {
new Thread(() -> {
try{
BattleshipServer battleshipServer = new BattleshipServer();
BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText()));
battleshipServer.run();
} catch (Exception e) {
LOGGER.log(Level.ERROR,e);
LOGGER.log(Level.ERROR, e.getMessage(), e);
}
}).start();
}
/**
* Toggles client/host mode
*/
private void toggleServerHost(){
hostServer = !hostServer;
}

View File

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

@@ -8,7 +8,6 @@
package pp.battleship.client.gui;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
@@ -17,6 +16,7 @@
import pp.battleship.model.Shell;
import pp.battleship.model.Shot;
import pp.util.Position;
import java.lang.System.Logger;
/**
* Synchronizes the visual representation of the ship map with the game model.
@@ -30,10 +30,10 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private static final float SHIP_DEPTH = 0f;
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
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
@@ -44,6 +44,8 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
// The MapView associated with this synchronizer
private final MapView view;
static final Logger LOGGER = System.getLogger(MapViewSynchronizer.class.getName());
/**
* Constructs a new MapViewSynchronizer for the given MapView.
* Initializes the synchronizer and adds existing elements from the model to the view.
@@ -65,6 +67,7 @@ public MapViewSynchronizer(MapView view) {
*/
@Override
public Spatial visit(Shot shot) {
LOGGER.log(Logger.Level.DEBUG, "Visiting " + shot);
// Convert the shot's model coordinates to view coordinates
final Position p1 = view.modelToView(shot.getX(), shot.getY());
final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1);
@@ -117,28 +120,26 @@ public Spatial visit(Battleship ship) {
}
/**
* Creates a visual representation of a shell on the map.
* this method will create a representation of a shell in the map
*
* @param shell the Shell object representing the shell in the model
* @return a Spatial representing the shell
* @param shell the Shell element to visit
* @return the node the representation is attached to
*/
@Override
public Spatial visit(Shell shell) {
LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell);
final Node shellNode = new Node("shell");
final Position p1 = view.modelToView(shell.getX(), shell.getY());
final Position p2 = view.modelToView(shell.getX() + SHELL_SIZE, shell.getY() + SHELL_SIZE);
final Position startPosition = view.modelToView(SHELL_CENTERED_IN_MAP_GRID, SHELL_CENTERED_IN_MAP_GRID);
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(), startPosition.getY(), SHELL_DEPTH, p2.getX() - p1.getX(), p2.getY() - p1.getY(), ColorRGBA.Magenta));
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(), startPosition.getY(), SHELL_DEPTH, p2.getX() - p1.getX(), p2.getY() - p1.getY(), ColorRGBA.Black));
shellNode.setLocalTranslation(startPosition.getX(), startPosition.getY(), SHELL_DEPTH);
shellNode.addControl(new ShellControlMap(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
shellNode.addControl(new ShellMapControl(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
return shellNode;
}
/**
* Creates a line geometry representing part of the ship's border.
*
@@ -150,6 +151,7 @@ public Spatial visit(Shell shell) {
* @return a Geometry representing the line
*/
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);
}
}

View File

@@ -1,193 +0,0 @@
package pp.battleship.client.gui;
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.Node;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Shot;
public class ParticleHandler {
private final BattleshipApp app;
static final System.Logger LOGGER = System.getLogger(ParticleHandler.class.getName());
private Material material;
/**
* Constructs a new ParticleHandler
*
* @param app the main battleship application
*/
public ParticleHandler(BattleshipApp app) {
this.app = app;
material = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
}
/**
* this method is used to create a hit effect at the position of a node
*
* @param node the node of the battleship where the effect should be attached to
* @param shot the shot that hit a target
*/
public void createHitParticles(Node node, Shot shot) {
ParticleEmitter smokeParticles = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,1000);
smokeParticles.setMaterial(material);
smokeParticles.setImagesX(2);
smokeParticles.setImagesY(2);
smokeParticles.setStartColor(ColorRGBA.Orange);
smokeParticles.setEndColor(ColorRGBA.Black);
smokeParticles.getParticleInfluencer().setInitialVelocity(new Vector3f(0,0.8f,0));
smokeParticles.setStartSize(0.5f);
smokeParticles.setEndSize(0.04f);
smokeParticles.setGravity(0, -0.1f, 0);
smokeParticles.setLowLife(1f);
smokeParticles.setHighLife(4f);
smokeParticles.setParticlesPerSec(0);
smokeParticles.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
smokeParticles.emitAllParticles();
ParticleEmitter particles2 = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,300);
particles2.setMaterial(material);
particles2.setImagesX(2);
particles2.setImagesY(2);
particles2.setStartColor(ColorRGBA.Orange);
particles2.setEndColor(ColorRGBA.Yellow);
particles2.getParticleInfluencer().setInitialVelocity(new Vector3f(0,2,0));
particles2.setStartSize(0.7f);
particles2.setEndSize(0.1f);
particles2.setGravity(0, 0, 0);
particles2.setLowLife(0.5f);
particles2.setHighLife(1.5f);
particles2.setParticlesPerSec(0);
particles2.setLocalTranslation(shot.getY() + 1f, 0 , shot.getX() + 1f);
particles2.emitAllParticles();
ParticleEmitter particles3 = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,300);
particles3.setMaterial(material);
particles3.setImagesX(1);
particles3.setImagesY(1);
particles3.setStartColor(ColorRGBA.Red);
particles3.setEndColor(ColorRGBA.Gray);
particles3.getParticleInfluencer().setInitialVelocity(new Vector3f(0,0.5f,0));
particles3.setStartSize(0.8f);
particles3.setEndSize(0.1f);
particles3.setGravity(0, 0, 0);
particles3.setLowLife(0.5f);
particles3.setHighLife(0.8f);
particles3.setParticlesPerSec(0);
particles3.setLocalTranslation(shot.getY() + 1f, 0 , shot.getX() + 1f);
particles3.emitAllParticles();
ParticleEmitter flames = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,300);
flames.setMaterial(material);
flames.setImagesX(1);
flames.setImagesY(1);
flames.setStartColor(ColorRGBA.Red);
flames.setEndColor(ColorRGBA.Orange);
flames.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1.0f,0));
flames.setStartSize(0.4f);
flames.setEndSize(0.1f);
flames.setGravity(0, -0.2f, 0);
flames.setLowLife(0.7f);
flames.setHighLife(1.5f);
flames.setLocalTranslation(shot.getY(), 0 , shot.getX());
flames.setLocalTranslation(flames.getLocalTranslation().subtract(node.getLocalTranslation()));
flames.setParticlesPerSec(70);
node.attachChild(smokeParticles);
smokeParticles.addControl((Control) new ParticleControl(smokeParticles, node));
node.attachChild(particles2);
particles2.addControl((Control) new ParticleControl(particles2, node));
node.attachChild(particles3);
particles3.addControl((Control) new ParticleControl(particles3, node));
//node.attachChild(flames);
//flames.addControl((Control) new ParticleControl(flames, node));
LOGGER.log(System.Logger.Level.DEBUG, "Hit-particles at {0}", smokeParticles.getLocalTranslation().toString());
}
/**
* Creates a miss effect at a certain location
* @param shot the shot that missed
* @return A ParticleEmitter
*/
public ParticleEmitter createMissParticles(Shot shot) {
ParticleEmitter particles = new ParticleEmitter("MissEffect", ParticleMesh.Type.Triangle, 200);
particles.setMaterial(material);
particles.setImagesX(2);
particles.setImagesY(2);
particles.setStartColor(ColorRGBA.Blue);
particles.setEndColor(ColorRGBA.White);
particles.getParticleInfluencer().setInitialVelocity(new Vector3f(0.01f, 3f, 0.01f));
particles.setStartSize(0.05f);
particles.setEndSize(0.4f);
particles.setGravity(0, 2f, 0);
particles.setLowLife(1.0f);
particles.setHighLife(2.2f);
particles.setParticlesPerSec(0);
particles.setLocalTranslation(shot.getY(), -0.5f , shot.getX());
particles.emitAllParticles();
LOGGER.log(System.Logger.Level.DEBUG, "Miss-particles at {0}", particles.getLocalTranslation().toString());
return particles;
}
/**
* This inner class is used to control the effects
*/
private static class ParticleControl extends AbstractControl {
private final ParticleEmitter emitter;
private final Node parentNode;
/**
* this constructor is used to when the particle should be attached to a specific node
*
* @param emitter the Particle emitter to be controlled
* @param parentNode the node to be attached
*/
public ParticleControl(ParticleEmitter emitter, Node parentNode) {
this.emitter = emitter;
this.parentNode = parentNode;
}
/**
* This constructor is used when the particle shouldn't be attached to
* a specific node
*
* @param emitter the Particle emitter to be controlled
*/
public ParticleControl(ParticleEmitter emitter) {
this.emitter = emitter;
this.parentNode = null;
}
/**
* The method which checks if the particle 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

@@ -8,17 +8,11 @@
package pp.battleship.client.gui;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.material.RenderState;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
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.model.*;
@@ -34,18 +28,18 @@
*/
class SeaSynchronizer extends ShipMapSynchronizer {
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
private static final String COLOR = "Color"; //NON-NLS
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";
private static final String UBOAT = "Models/UBoat/14084_WWII_Ship_German_Type_II_U-boat_v2_L1.obj"; //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 SHOT = "shot"; //NON-NLS
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 static final String BOMB = "Models/Bomb/Files/Bomb GBU-43B(MOAB).obj";
private static final String SHELL = "shell";
private final EffectHandler effectHandler;
private final ShipMap map;
private final BattleshipApp app;
private final ParticleHandler handler;
/**
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
@@ -58,41 +52,10 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root);
this.app = app;
this.map = map;
handler = new ParticleHandler(app);
effectHandler = new EffectHandler(app);
addExisting();
}
/**
* Visits a {@link Shell} and creates a graphical representation of it.
*
* @param shell the shell to be represented
* @return the graphical representation of the shell
*/
@Override
public Spatial visit(Shell shell) {
final Node node = new Node("shell");
final Spatial model = app.getAssetManager().loadModel(BOMB);
model.rotate(-PI/2, 0, 0);
model.scale(0.09f);
model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, 0, 0);
node.attachChild(model);
final float x = shell.getY();
final float z = shell.getX();
node.setLocalTranslation(x, 8f, z);
ShellControll control = new ShellControll(shell, app);
node.addControl(control);
return node;
}
/**
* Visits a {@link Shot} and creates a graphical representation of it.
* If the shot is a hit, it attaches the representation to the ship node.
@@ -103,7 +66,7 @@ public Spatial visit(Shell shell) {
*/
@Override
public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : handler.createMissParticles(shot);
return shot.isHit() ? handleHit(shot) : effectHandler.createMissEffect(shot);
}
/**
@@ -111,40 +74,19 @@ public Spatial visit(Shot shot) {
* contains the ship model as a child so that it moves with the ship.
*
* @param shot a hit
* @return always null to prevent the representation from being sattached
* @return always null to prevent the representation from being attached
* to the items node as well
*/
private Spatial handleHit(Shot shot) {
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
handler.createHitParticles(shipNode, shot);
effectHandler.createHitEffect(shipNode, shot);
effectHandler.createFireEffect(shipNode, shot);
return null;
}
/**
* 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.
* The representation is either a 3D model or a simple box depending on the
@@ -165,6 +107,42 @@ public Spatial visit(Battleship ship) {
return node;
}
/**
* Visits a shell and creates a graphical representation
*
* @param shell the Shell element to visit
* @return the node containing the graphical representation
*/
@Override
public Spatial visit(Shell shell){
final Node node = new Node(SHELL);
node.attachChild(createRocket());
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.move(0, 0, 0);
return model;
}
/**
* Creates the appropriate graphical representation of the specified battleship.
* The representation is either a detailed model or a simple box based on the length of the ship.
@@ -173,88 +151,84 @@ public Spatial visit(Battleship ship) {
* @return the spatial representing the battleship
*/
private Spatial createShip(Battleship ship) {
final int len = ship.getLength();
Quaternion q0 = new Quaternion();
Quaternion q1 = new Quaternion();
q1.fromAngleAxis(PI/2, new Vector3f(1, 0, 0));
Quaternion q2 = new Quaternion();
q2.fromAngleAxis(PI/2, new Vector3f(1, 0, 0));
switch (len) {
case 1 -> {
return createBattleship(ship, "Models/Uboat/uboat.j3o", 0.2f, new Vector3f(0.0f, -0.1f, 0.0f), q0);
}
case 2 -> {
return createBattleship(ship, "Models/KingGeorgeV/KingGeorgeV.j3o", 1.0f, new Vector3f(0.0f, 0.0f, 0.0f), q0);
}
case 3 -> {
return createBattleship(ship, "Models/EssexClass/essex.j3o", 0.8f, new Vector3f(0.0f, 0.3f, 0.0f), q2);
}
case 4 -> {
return createBattleship(ship, "Models/Container/container.j3o", 0.05f, new Vector3f(0.0f, 0.0f, 0.0f), q1);
}
default -> {
return createBox(ship);
}
}
return switch (ship.getLength()) {
case 1 -> createPatrolBoat(ship);
case 2 -> createModernBattleship(ship);
case 3 -> createUBoat(ship);
case 4 -> createBattleship(ship);
default -> throw new IllegalArgumentException("Ship length must be between 1 and 4 units long");
};
}
/**
* Creates a simple box to represent a battleship that is not of the "King George V" type.
* Creates a detailed 3D model to represent a "King George V" battleship.
*
* @param ship the battleship to be represented
* @return the geometry representing the battleship as a box
* @return the spatial representing the "King George V" battleship
*/
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 3D model to represent a "battleship".
*
* @param ship the battleship to be represented
* @param path the battleship to be represented
* @param scale the battleship to be represented
* @param translation the battleship to be represented
* @param rotation the battleship to be represented
* @return the spatial representing the battleship
*/
private Spatial createBattleship(Battleship ship, String path, float scale, Vector3f translation, Quaternion rotation) {
final Spatial model = app.getAssetManager().loadModel(path);
private Spatial createBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.rotate(rotation);
model.scale(scale);
model.setLocalTranslation(translation);
model.scale(1.48f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* creates a detailed 3D model to represent an UBoat
*
* @param ship the ship to be represented
* @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);
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.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;

View File

@@ -0,0 +1,65 @@
package pp.battleship.client.gui;
import com.jme3.math.Quaternion;
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.battleship.model.Shell;
import java.lang.System.Logger;
/**
* This class controls a 3D representation of a shell
*/
public class ShellControl extends AbstractControl {
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
*
* @param shell the shell to be displayed
* @param app the main application
*/
public ShellControl(Shell shell, BattleshipApp app) {
this.shell = shell;
this.app = app;
}
/**
* this method moves the representation towards it destination
* and deletes it if it reaches its target
*
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(0, -MOVE_SPEED * tpf, 0);
spatial.rotate(0f, 0.05f, 0f);
//LOGGER.log(System.Logger.Level.DEBUG, "moved rocket {0}", spatial.getLocalTranslation().getY());
if (spatial.getLocalTranslation().getY() <= 1.5){
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(new IntPoint(shell.getX(), shell.getY())));
}
}
/**
* 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

@@ -1,67 +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;
/**
* Controls the movement of a shell in a specified position on the battlefield.
* This control updates the shell's position and sends a message when it reaches
* its target location.
*/
public class ShellControlMap extends AbstractControl {
private final Position position; // Target position for the shell
private final IntPoint pos; // Target coordinates as an IntPoint
private static final Vector3f vector = new Vector3f(); // Movement vector
private final BattleshipApp app; // Reference to the main application
/**
* Constructs a ShellControlMap object with the specified target position,
* application instance, and target coordinates.
*
* @param position The target Position for the shell.
* @param app The BattleshipApp instance managing the game logic.
* @param pos The IntPoint representing the target coordinates of the shell.
*/
public ShellControlMap(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));
}
/**
* Updates the shell's position based on the time per frame.
* If the shell reaches or exceeds the target position, it detaches
* the shell from the scene and sends an animation end message.
*
* @param tpf Time per frame, used to calculate movement.
*/
@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));
}
}
/**
* Renders the shell control. This method can be overridden to implement
* custom rendering behavior, but it is currently empty.
*
* @param rm The RenderManager for rendering the control.
* @param vp The ViewPort where the control is rendered.
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic implemented for ShellControlMap
}
}

View File

@@ -1,62 +0,0 @@
package pp.battleship.client.gui;
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.battleship.model.Shell;
import java.util.logging.Logger;
/**
* Controls the movement and behavior of a shell in the battleship game.
* This control updates the position of the shell and sends a message
* when the shell reaches the ground.
*/
public class ShellControll extends AbstractControl {
private final Shell shell;
private final BattleshipApp app;
private static final float MOVE_SPEED = 20.0f; // Speed at which the shell moves
/**
* Constructs a ShellControll object with the specified shell and application instance.
*
* @param shell The Shell instance to control.
* @param app The BattleshipApp instance managing the game logic.
*/
public ShellControll(Shell shell, BattleshipApp app) {
this.shell = shell;
this.app = app;
}
/**
* Updates the shell's position based on the time per frame.
* If the shell's position falls below a certain threshold,
* it detaches the shell from the scene and sends an animation end message.
*
* @param tpf Time per frame, used to calculate movement.
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(0, -MOVE_SPEED * tpf, 0);
if (spatial.getLocalTranslation().getY() <= 0.1f) {
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(new IntPoint(shell.getX(), shell.getY())));
}
}
/**
* Renders the shell control. This method can be overridden to implement
* custom rendering behavior, but it is currently empty.
*
* @param rm The RenderManager for rendering the control.
* @param vp The ViewPort where the control is rendered.
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic implemented for ShellControll
}
}

View File

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

@@ -14,6 +14,9 @@
import com.jme3.scene.control.AbstractControl;
import pp.battleship.model.Battleship;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import static pp.util.FloatMath.DEG_TO_RAD;
import static pp.util.FloatMath.TWO_PI;
import static pp.util.FloatMath.sin;
@@ -23,12 +26,6 @@
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
*/
class ShipControl extends AbstractControl {
/**
* The sinking height, after wich the ship will get removed
*/
private static final Float SINKING_HEIGHT = -0.6f;
/**
* The axis of rotation for the ship's pitch (tilting forward and backward).
*/
@@ -49,15 +46,27 @@ class ShipControl extends AbstractControl {
*/
private final Quaternion pitch = new Quaternion();
/**
* 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;
/**
* The ship, that the ShipControl controls
* The ship to be controlled
*/
private final Battleship ship;
private final Battleship battleship;
static final Logger LOGGER = System.getLogger(ShipControl.class.getName());
/**
* Constructs a new ShipControl instance for the specified Battleship.
@@ -67,7 +76,7 @@ class ShipControl extends AbstractControl {
* @param ship the Battleship object to control
*/
public ShipControl(Battleship ship) {
this.ship = ship;
battleship = ship;
// Determine the axis of rotation based on the ship's orientation
axis = switch (ship.getRot()) {
@@ -76,13 +85,14 @@ public ShipControl(Battleship ship) {
};
// Set the cycle duration and amplitude based on the ship's length
cycle = ship.getLength() * 2f;
cycle = battleship.getLength() * 2f;
amplitude = 5f * DEG_TO_RAD / ship.getLength();
}
/**
* Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted
* to create a continuous tilting motion, simulating the effect of waves.
* 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
*/
@@ -91,24 +101,25 @@ protected void controlUpdate(float tpf) {
// If spatial is null, do nothing
if (spatial == null) return;
if (ship.isDestroyed() && spatial.getLocalTranslation().getY() < SINKING_HEIGHT) { // removes the ship, if it is completely sunk
if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= SHIP_SINKING_REMOVE_THRESHOLD) {
LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName());
spatial.getParent().detachChild(spatial);
}
else if (ship.isDestroyed() && spatial.getLocalTranslation().getY() >= SINKING_HEIGHT) { // sink the ship, if it's not completely sunk
spatial.move(0, tpf * -0.03f, 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);
}
// 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);
}
/**

View File

@@ -5,9 +5,14 @@
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client;
package pp.battleship.client.server;
import com.jme3.network.*;
import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player;
@@ -38,6 +43,8 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
private static final File CONFIG_FILE = new File("server.properties");
private static int port;
private final BattleshipConfig config = new BattleshipConfig();
private Server myServer;
private final ServerGameLogic logic;
@@ -55,18 +62,12 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
}
}
/**
* Starts the Battleships server.
*/
public static void main(String[] args) {
new BattleshipServer().run();
}
/**
* Creates the server.
*/
BattleshipServer() {
public BattleshipServer(int port) {
config.readFromIfExists(CONFIG_FILE);
BattleshipServer.port = port;
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config);
}
@@ -80,7 +81,7 @@ public void run() {
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(config.getPort());
myServer = Network.createServer(port);
initializeSerializables();
myServer.start();
registerListeners();
@@ -103,9 +104,6 @@ private void processNextMessage() {
}
private void initializeSerializables() {
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(AnimationStartMessage.class);
Serializer.registerClass(SwitchBattleState.class);
Serializer.registerClass(GameDetails.class);
Serializer.registerClass(StartBattleMessage.class);
Serializer.registerClass(MapMessage.class);
@@ -114,12 +112,15 @@ private void initializeSerializables() {
Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class);
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(AnimationStartMessage.class);
Serializer.registerClass(SwitchBattleState.class);
}
private void registerListeners() {
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class);
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addConnectionListener(this);
}

View File

@@ -5,7 +5,7 @@
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client;
package pp.battleship.client.server;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage;

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +0,0 @@
# Blender MTL File: 'untitletttd.blend'
# Material Count: 1
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
map_Kd C:\Users\Convidado.Cliente-JMF-PC\Desktop\ghftht.png

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -1,35 +0,0 @@
# Blender 4.2.1 LTS MTL File: 'untitled.blend'
# www.blender.org
newmtl Bridge
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ni 1.500000
illum 2
map_Kd Bridge_baseColor.png
map_Ke Bridge_emissive.jpg
map_d Bridge_baseColor.png
map_Bump -bm 1.000000 Bridge_normal.jpg
newmtl Containers
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.500000
d 1.000000
illum 2
map_Kd Containers_baseColor.jpg
map_Bump -bm 1.000000 Containers_normal.jpg
newmtl Hull
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.500000
d 1.000000
illum 2
map_Kd Hull_baseColor.jpg
map_Bump -bm 1.000000 Hull_normal.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

View File

@@ -1,180 +0,0 @@
newmtl Model001_Material001
map_Kd Yorktown_paint6.png
map_bump bumpmap_flat.png
newmtl Model001_Material002
map_Kd diff_null_7.png
map_bump bumpmap_flat.png
newmtl Model001_Material003
map_Kd diff_null_14.png
map_bump bumpmap_flat.png
newmtl Model001_Material004
map_Kd diff_null_Color005.png
map_bump bumpmap_flat.png
newmtl Model001_Material005
map_Kd diff_null_8.png
map_bump bumpmap_flat.png
newmtl Model001_Material006
map_Kd diff_null_Color007.png
map_bump bumpmap_flat.png
newmtl Model001_Material007
map_Kd diff_null_17.png
map_bump bumpmap_flat.png
newmtl Model001_Material008
map_Kd diff_null_FrontColor.png
map_bump bumpmap_flat.png
newmtl Model001_Material009
map_Kd diff_null_BackColor.png
map_bump bumpmap_flat.png
newmtl Model001_Material010
map_Kd diff_null_4.png
map_bump bumpmap_flat.png
newmtl Model001_Material011
map_Kd Color_004.png
map_bump bumpmap_flat.png
newmtl Model001_Material012
map_Kd diff_null_Gray1.png
map_bump bumpmap_flat.png
newmtl Model001_Material013
map_Kd diff_null_3.png
map_bump bumpmap_flat.png
newmtl Model001_Material014
map_Kd Metal_Rough.png
map_bump bumpmap_flat.png
newmtl Model001_Material015
map_Kd diff_null_12.png
map_bump bumpmap_flat.png
newmtl Model001_Material016
map_Kd diff_null_19.png
map_bump bumpmap_flat.png
newmtl Model001_Material017
map_Kd diff_null_Color003.png
map_bump bumpmap_flat.png
newmtl Model001_Material018
map_Kd diff_null_mat1.png
map_bump bumpmap_flat.png
newmtl Model001_Material019
map_Kd diff_null_1.png
map_bump bumpmap_flat.png
newmtl Model001_Material020
map_Kd diff_null_2.png
map_bump bumpmap_flat.png
newmtl Model001_Material021
map_Kd diff_null_Black1.png
map_bump bumpmap_flat.png
newmtl Model001_Material022
map_Kd diff_null_Model001Materia.png
map_bump bumpmap_flat.png
newmtl Model001_Material023
map_Kd diff_null_Model001Mate1.png
map_bump bumpmap_flat.png
newmtl Model001_Material024
map_Kd diff_null_13.png
map_bump bumpmap_flat.png
newmtl Model001_Material025
map_Kd _6.png
map_bump bumpmap_flat.png
newmtl Model001_Material026
map_Kd Metal_Aluminum_Anodized.png
map_bump bumpmap_flat.png
newmtl Model001_Material027
map_Kd diff_null_Color006.png
map_bump bumpmap_flat.png
newmtl Model001_Material028
map_Kd Blinds_Vertical_Stripe_Gray.png
map_bump bumpmap_flat.png
newmtl Model001_Material029
map_Kd diff_null_15.png
map_bump bumpmap_flat.png
newmtl Model001_Material030
map_Kd diff_null_Color002.png
map_bump bumpmap_flat.png
newmtl Model001_Material031
map_Kd Cladding_Siding_Tan.png
map_bump bumpmap_flat.png
newmtl Model001_Material032
map_Kd diff_null_Color008.png
map_bump bumpmap_flat.png
newmtl Model001_Material033
map_Kd diff_null_16.png
map_bump bumpmap_flat.png
newmtl Model001_Material034
map_Kd _2.png
map_bump bumpmap_flat.png
newmtl Model001_Material035
map_Kd Blinds_Roman_Hobbled_Blue.png
map_bump bumpmap_flat.png
newmtl Model001_Material036
map_Kd Blinds_Wood_White.png
map_bump bumpmap_flat.png
newmtl Model001_Material037
map_Kd Metal_Seamed.png
map_bump bumpmap_flat.png
newmtl Model001_Material038
map_Kd image_16.png
map_bump bumpmap_flat.png
newmtl Model001_Material039
map_Kd diff_null_9.png
map_bump bumpmap_flat.png
newmtl Model001_Material040
map_Kd image_15.png
map_bump bumpmap_flat.png
newmtl Model001_Material041
map_Kd _Blinds_Roman_Hobbled_Blue_1.png
map_bump bumpmap_flat.png
newmtl Model001_Material042
map_Kd diff_null_10.png
map_bump bumpmap_flat.png
newmtl Model001_Material043
map_Kd diff_null_Material5.png
map_bump bumpmap_flat.png
newmtl Model001_Material044
map_Kd diff_null_Material1.png
map_bump bumpmap_flat.png
newmtl Model001_Material045
map_Kd diff_null_11.png
map_bump bumpmap_flat.png

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