Compare commits
	
		
			11 Commits
		
	
	
		
			dev/client
			...
			b_koppe_fe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5cb0843819 | ||
| 
						 | 
					34929b40f1 | ||
| 
						 | 
					5dd3df50c5 | ||
| 
						 | 
					a7c3632a59 | ||
| 
						 | 
					05eb0f92ea | ||
| 
						 | 
					f96c63e76e | ||
| 
						 | 
					9dbac0b226 | ||
| 
						 | 
					7ef06f2440 | ||
| 
						 | 
					95fdd86100 | ||
| 
						 | 
					1f548f5ee5 | ||
| 
						 | 
					af5d1aaca6 | 
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="Projekte [test]" type="GradleRunConfiguration" factoryName="Gradle">
 | 
			
		||||
    <ExternalSystemSettings>
 | 
			
		||||
      <option name="executionName" />
 | 
			
		||||
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
			
		||||
      <option name="externalProjectPath" value="$PROJECT_DIR$/Projekte" />
 | 
			
		||||
      <option name="externalSystemIdString" value="GRADLE" />
 | 
			
		||||
      <option name="scriptParameters" value="--continue" />
 | 
			
		||||
      <option name="taskDescriptions">
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ implementation project(":jme-common")
 | 
			
		||||
    implementation project(":battleship:model")
 | 
			
		||||
 | 
			
		||||
    implementation libs.jme3.desktop
 | 
			
		||||
    implementation libs.jme3.effects
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.awt.dialogs
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
 
 | 
			
		||||
@@ -122,6 +122,11 @@ 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.
 | 
			
		||||
     */
 | 
			
		||||
    private GameMusic music;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        // Configure logging
 | 
			
		||||
        LogManager manager = LogManager.getLogManager();
 | 
			
		||||
@@ -193,6 +198,15 @@ 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.
 | 
			
		||||
     *
 | 
			
		||||
@@ -225,6 +239,8 @@ public void simpleInitApp() {
 | 
			
		||||
        setupStates();
 | 
			
		||||
        setupGui();
 | 
			
		||||
        serverConnection.connect();
 | 
			
		||||
 | 
			
		||||
        music = new GameMusic(this, "Sound/Music/battleship.ogg");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.network.*;
 | 
			
		||||
import com.jme3.network.serializing.Serializer;
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.game.server.Player;
 | 
			
		||||
import pp.battleship.game.server.ServerGameLogic;
 | 
			
		||||
import pp.battleship.game.server.ServerSender;
 | 
			
		||||
import pp.battleship.message.client.AnimationEndMessage;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.*;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.concurrent.BlockingQueue;
 | 
			
		||||
import java.util.concurrent.LinkedBlockingQueue;
 | 
			
		||||
import java.util.logging.LogManager;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Server implementing the visitor pattern as MessageReceiver for ClientMessages
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
 | 
			
		||||
    private static final File CONFIG_FILE = new File("server.properties");
 | 
			
		||||
 | 
			
		||||
    private final BattleshipConfig config = new BattleshipConfig();
 | 
			
		||||
    private Server myServer;
 | 
			
		||||
    private final ServerGameLogic logic;
 | 
			
		||||
    private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        // Configure logging
 | 
			
		||||
        LogManager manager = LogManager.getLogManager();
 | 
			
		||||
        try {
 | 
			
		||||
            manager.readConfiguration(new FileInputStream("logging.properties"));
 | 
			
		||||
            LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.INFO, e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the Battleships server.
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        new BattleshipServer().run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the server.
 | 
			
		||||
     */
 | 
			
		||||
    BattleshipServer() {
 | 
			
		||||
        config.readFromIfExists(CONFIG_FILE);
 | 
			
		||||
        LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
 | 
			
		||||
        logic = new ServerGameLogic(this, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void run() {
 | 
			
		||||
        startServer();
 | 
			
		||||
        while (true)
 | 
			
		||||
            processNextMessage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        try {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
            myServer = Network.createServer(config.getPort());
 | 
			
		||||
            initializeSerializables();
 | 
			
		||||
            myServer.start();
 | 
			
		||||
            registerListeners();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
 | 
			
		||||
            exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void processNextMessage() {
 | 
			
		||||
        try {
 | 
			
		||||
            pendingMessages.take().process(logic);
 | 
			
		||||
        }
 | 
			
		||||
        catch (InterruptedException ex) {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
 | 
			
		||||
            Thread.currentThread().interrupt();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(AnimationEndMessage.class);
 | 
			
		||||
        Serializer.registerClass(AnimationStartMessage.class);
 | 
			
		||||
        Serializer.registerClass(SwitchBattleState.class);
 | 
			
		||||
        Serializer.registerClass(GameDetails.class);
 | 
			
		||||
        Serializer.registerClass(StartBattleMessage.class);
 | 
			
		||||
        Serializer.registerClass(MapMessage.class);
 | 
			
		||||
        Serializer.registerClass(ShootMessage.class);
 | 
			
		||||
        Serializer.registerClass(EffectMessage.class);
 | 
			
		||||
        Serializer.registerClass(Battleship.class);
 | 
			
		||||
        Serializer.registerClass(IntPoint.class);
 | 
			
		||||
        Serializer.registerClass(Shot.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void registerListeners() {
 | 
			
		||||
        myServer.addMessageListener(this, AnimationEndMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, MapMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, ShootMessage.class);
 | 
			
		||||
        myServer.addConnectionListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void messageReceived(HostedConnection source, Message message) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
 | 
			
		||||
        if (message instanceof ClientMessage clientMessage)
 | 
			
		||||
            pendingMessages.add(new ReceivedMessage(clientMessage, source.getId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connectionAdded(Server server, HostedConnection hostedConnection) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
 | 
			
		||||
        logic.addPlayer(hostedConnection.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connectionRemoved(Server server, HostedConnection hostedConnection) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
 | 
			
		||||
        final Player player = logic.getPlayerById(hostedConnection.getId());
 | 
			
		||||
        if (player == null)
 | 
			
		||||
            LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
 | 
			
		||||
        else { //NON-NLS
 | 
			
		||||
            LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
 | 
			
		||||
            exit(0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void exit(int exitValue) { //NON-NLS
 | 
			
		||||
        LOGGER.log(Level.INFO, "close request"); //NON-NLS
 | 
			
		||||
        if (myServer != null)
 | 
			
		||||
            for (HostedConnection client : myServer.getConnections()) //NON-NLS
 | 
			
		||||
                if (client != null) client.close("Game over"); //NON-NLS
 | 
			
		||||
        System.exit(exitValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the specified message to the specified connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id      the connection id
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    public void send(int id, ServerMessage message) {
 | 
			
		||||
        if (myServer == null || !myServer.isRunning()) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        final HostedConnection connection = myServer.getConnection(id);
 | 
			
		||||
        if (connection != null)
 | 
			
		||||
            connection.send(message);
 | 
			
		||||
        else
 | 
			
		||||
            LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,170 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -34,6 +34,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
 | 
			
		||||
    private AudioNode splashSound;
 | 
			
		||||
    private AudioNode shipDestroyedSound;
 | 
			
		||||
    private AudioNode explosionSound;
 | 
			
		||||
    private AudioNode missleSound;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sound is enabled in the preferences.
 | 
			
		||||
@@ -78,6 +79,7 @@ public void initialize(AppStateManager stateManager, Application 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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -108,6 +110,14 @@ 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.
 | 
			
		||||
     */
 | 
			
		||||
@@ -130,6 +140,7 @@ public void receivedEvent(SoundEvent event) {
 | 
			
		||||
            case EXPLOSION -> explosion();
 | 
			
		||||
            case SPLASH -> splash();
 | 
			
		||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
			
		||||
            case MISSLE_FLYBY -> flyBy();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,8 @@
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.simsilica.lemur.Button;
 | 
			
		||||
import com.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.*;
 | 
			
		||||
import com.simsilica.lemur.core.VersionedReference;
 | 
			
		||||
import com.simsilica.lemur.style.ElementId;
 | 
			
		||||
import pp.dialog.Dialog;
 | 
			
		||||
import pp.dialog.StateCheckboxModel;
 | 
			
		||||
@@ -34,6 +33,8 @@ 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 final VersionedReference<Double> volumeRef;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs the Menu dialog for the Battleship application.
 | 
			
		||||
     *
 | 
			
		||||
@@ -49,6 +50,20 @@ public Menu(BattleshipApp app) {
 | 
			
		||||
                .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")))
 | 
			
		||||
@@ -57,12 +72,33 @@ public Menu(BattleshipApp app) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the load and save buttons based on the game logic.
 | 
			
		||||
     * Updates the state of the load/save buttons and volume based on the game logic.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update() {
 | 
			
		||||
        loadButton.setEnabled(app.getGameLogic().mayLoadMap());
 | 
			
		||||
        saveButton.setEnabled(app.getGameLogic().maySaveMap());
 | 
			
		||||
 | 
			
		||||
        if(volumeRef.update()){
 | 
			
		||||
            double newVolume = volumeRef.get();
 | 
			
		||||
            adjustVolume(newVolume);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * this method adjust the volume for the background music
 | 
			
		||||
     *
 | 
			
		||||
     * @param volume is the double value of the volume
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustVolume(double volume) {
 | 
			
		||||
        app.getGameMusic().setVolume((float) volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * this method toggles the background music on and off
 | 
			
		||||
     */
 | 
			
		||||
    private void toggleMusic() {
 | 
			
		||||
        app.getGameMusic().toggleMusic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.TextField;
 | 
			
		||||
@@ -37,6 +38,7 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
    private int portNumber;
 | 
			
		||||
    private Future<Object> connectionFuture;
 | 
			
		||||
    private Dialog progressDialog;
 | 
			
		||||
    private boolean hostServer = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new NetworkDialog.
 | 
			
		||||
@@ -50,6 +52,11 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
        host.setPreferredWidth(400f);
 | 
			
		||||
        port.setSingleLine(true);
 | 
			
		||||
 | 
			
		||||
        Checkbox serverHost = new Checkbox(lookup("menu.host"));
 | 
			
		||||
        serverHost.setChecked(false);
 | 
			
		||||
        serverHost.addClickCommands(s -> toggleServerHost());
 | 
			
		||||
        addChild(serverHost);
 | 
			
		||||
 | 
			
		||||
        final BattleshipApp app = network.getApp();
 | 
			
		||||
        final Container input = new Container(new SpringGridLayout());
 | 
			
		||||
        input.addChild(new Label(lookup("host.name") + ":  "));
 | 
			
		||||
@@ -60,7 +67,7 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
        DialogBuilder.simple(app.getDialogManager())
 | 
			
		||||
                     .setTitle(lookup("server.dialog"))
 | 
			
		||||
                     .setExtension(d -> d.addChild(input))
 | 
			
		||||
                     .setOkButton(lookup("button.connect"), d -> connect())
 | 
			
		||||
                     .setOkButton(lookup("button.connect"), d -> connectLocally())
 | 
			
		||||
                     .setNoButton(lookup("button.cancel"), app::closeApp)
 | 
			
		||||
                     .setOkClose(false)
 | 
			
		||||
                     .setNoClose(false)
 | 
			
		||||
@@ -84,6 +91,43 @@ private void connect() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts a server or if not host connects to one
 | 
			
		||||
     */
 | 
			
		||||
    private void connectLocally() {
 | 
			
		||||
        if(hostServer){
 | 
			
		||||
            startServer();
 | 
			
		||||
            try {
 | 
			
		||||
                Thread.sleep(1000);
 | 
			
		||||
            } catch (InterruptedException e) {
 | 
			
		||||
                LOGGER.log(Level.WARNING, e.getMessage(), e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts a host server in a new thread
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try{
 | 
			
		||||
                BattleshipServer battleshipServer = new BattleshipServer();
 | 
			
		||||
                battleshipServer.run();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR,e);
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles client/host mode
 | 
			
		||||
     */
 | 
			
		||||
    private void toggleServerHost(){
 | 
			
		||||
        hostServer = !hostServer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a dialog indicating that the connection is in progress.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
record ReceivedMessage(ClientMessage message, int from) {
 | 
			
		||||
    void process(ClientInterpreter interpreter) {
 | 
			
		||||
        message.accept(interpreter, from);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,10 +8,13 @@
 | 
			
		||||
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;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +29,10 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    private static final float SHOT_DEPTH = -2f;
 | 
			
		||||
    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;
 | 
			
		||||
@@ -109,6 +116,29 @@ public Spatial visit(Battleship ship) {
 | 
			
		||||
        return shipNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual representation of a shell on the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the Shell object representing the shell in the model
 | 
			
		||||
     * @return a Spatial representing the shell
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell 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.setLocalTranslation(startPosition.getX(), startPosition.getY(), SHELL_DEPTH);
 | 
			
		||||
        shellNode.addControl(new ShellControlMap(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
 | 
			
		||||
        return shellNode;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a line geometry representing part of the ship's border.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,193 @@
 | 
			
		||||
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) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,9 @@
 | 
			
		||||
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.renderer.queue.RenderQueue.ShadowMode;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
@@ -17,10 +20,7 @@
 | 
			
		||||
import com.jme3.scene.shape.Box;
 | 
			
		||||
import com.jme3.scene.shape.Cylinder;
 | 
			
		||||
import pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.battleship.model.*;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
import static pp.util.FloatMath.HALF_PI;
 | 
			
		||||
@@ -41,9 +41,11 @@ class SeaSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    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 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.
 | 
			
		||||
@@ -56,9 +58,41 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
 | 
			
		||||
        super(app.getGameLogic().getOwnMap(), root);
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.map = map;
 | 
			
		||||
 | 
			
		||||
        handler = new ParticleHandler(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.
 | 
			
		||||
@@ -69,7 +103,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shot shot) {
 | 
			
		||||
        return shot.isHit() ? handleHit(shot) : createCylinder(shot);
 | 
			
		||||
        return shot.isHit() ? handleHit(shot) : handler.createMissParticles(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -77,16 +111,14 @@ 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 attached
 | 
			
		||||
     * @return always null to prevent the representation from being sattached
 | 
			
		||||
     * 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");
 | 
			
		||||
 | 
			
		||||
        final Geometry representation = createCylinder(shot);
 | 
			
		||||
        representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
 | 
			
		||||
        shipNode.attachChild(representation);
 | 
			
		||||
        handler.createHitParticles(shipNode, shot);
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -141,7 +173,33 @@ public Spatial visit(Battleship ship) {
 | 
			
		||||
     * @return the spatial representing the battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createShip(Battleship ship) {
 | 
			
		||||
        return ship.getLength() == 4 ? createBattleship(ship) : createBox(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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -181,16 +239,22 @@ private Material createColoredMaterial(ColorRGBA color) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a "King George V" battleship.
 | 
			
		||||
     * Creates a 3D model to represent a "battleship".
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the "King George V" battleship
 | 
			
		||||
     * @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) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
 | 
			
		||||
    private Spatial createBattleship(Battleship ship, String path, float scale, Vector3f translation, Quaternion rotation) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(path);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(1.48f);
 | 
			
		||||
        model.rotate(rotation);
 | 
			
		||||
        model.scale(scale);
 | 
			
		||||
        model.setLocalTranslation(translation);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,6 +23,12 @@
 | 
			
		||||
 * 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).
 | 
			
		||||
     */
 | 
			
		||||
@@ -48,6 +54,11 @@ class ShipControl extends AbstractControl {
 | 
			
		||||
     */
 | 
			
		||||
    private float time;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ship, that the ShipControl controls
 | 
			
		||||
     */
 | 
			
		||||
    private final Battleship ship;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ShipControl instance for the specified Battleship.
 | 
			
		||||
     * The ship's orientation determines the axis of rotation, while its length influences
 | 
			
		||||
@@ -56,6 +67,8 @@ class ShipControl extends AbstractControl {
 | 
			
		||||
     * @param ship the Battleship object to control
 | 
			
		||||
     */
 | 
			
		||||
    public ShipControl(Battleship ship) {
 | 
			
		||||
        this.ship = ship;
 | 
			
		||||
 | 
			
		||||
        // Determine the axis of rotation based on the ship's orientation
 | 
			
		||||
        axis = switch (ship.getRot()) {
 | 
			
		||||
            case LEFT, RIGHT -> Vector3f.UNIT_X;
 | 
			
		||||
@@ -78,6 +91,13 @@ 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
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update the time within the oscillation cycle
 | 
			
		||||
        time = (time + tpf) % cycle;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
| 
		 After Width: | Height: | Size: 55 KiB  | 
| 
		 After Width: | Height: | Size: 55 KiB  | 
| 
		 After Width: | Height: | Size: 59 KiB  | 
| 
		 After Width: | Height: | Size: 31 KiB  | 
@@ -0,0 +1,11 @@
 | 
			
		||||
# 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
 | 
			
		||||
| 
		 After Width: | Height: | Size: 54 KiB  | 
| 
		 After Width: | Height: | Size: 332 KiB  | 
| 
		 After Width: | Height: | Size: 30 KiB  | 
| 
		 After Width: | Height: | Size: 39 KiB  | 
| 
		 After Width: | Height: | Size: 51 KiB  | 
| 
		 After Width: | Height: | Size: 78 KiB  | 
| 
		 After Width: | Height: | Size: 74 KiB  | 
| 
		 After Width: | Height: | Size: 28 KiB  | 
| 
		 After Width: | Height: | Size: 21 KiB  | 
| 
		 After Width: | Height: | Size: 629 KiB  | 
| 
		 After Width: | Height: | Size: 95 KiB  | 
| 
		 After Width: | Height: | Size: 24 KiB  | 
| 
		 After Width: | Height: | Size: 49 KiB  | 
| 
		 After Width: | Height: | Size: 122 KiB  | 
@@ -0,0 +1,35 @@
 | 
			
		||||
# 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
 | 
			
		||||
							
								
								
									
										571196
									
								
								Projekte/battleship/client/src/main/resources/Models/Container/container.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 35 KiB  | 
| 
		 After Width: | Height: | Size: 54 KiB  | 
| 
		 After Width: | Height: | Size: 9.3 KiB  | 
| 
		 After Width: | Height: | Size: 66 KiB  | 
| 
		 After Width: | Height: | Size: 3.2 KiB  | 
| 
		 After Width: | Height: | Size: 30 KiB  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 9.4 KiB  | 
| 
		 After Width: | Height: | Size: 264 KiB  | 
| 
		 After Width: | Height: | Size: 80 KiB  | 
| 
		 After Width: | Height: | Size: 91 KiB  | 
| 
		 After Width: | Height: | Size: 33 KiB  | 
| 
		 After Width: | Height: | Size: 98 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 78 B  | 
| 
		 After Width: | Height: | Size: 79 B  | 
@@ -0,0 +1,180 @@
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										564702
									
								
								Projekte/battleship/client/src/main/resources/Models/EssexClass/essex.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 96 KiB  | 
| 
		 After Width: | Height: | Size: 82 KiB  | 
| 
		 After Width: | Height: | Size: 670 KiB  | 
| 
		 After Width: | Height: | Size: 168 KiB  | 
@@ -0,0 +1,16 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 29.03.2012 14:25:39
 | 
			
		||||
 | 
			
		||||
newmtl default
 | 
			
		||||
	Ns 35.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.5400 0.5400 0.5400
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
 | 
			
		||||
	map_Kd 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
 | 
			
		||||
							
								
								
									
										203099
									
								
								Projekte/battleship/client/src/main/resources/Models/Uboat/uboat.obj
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
credit to patrickdearteaga.com
 | 
			
		||||