12 Commits

Author SHA1 Message Date
Felix Koppe
5cb0843819 Add missle sound 2024-10-13 08:02:24 +02:00
Felix Koppe
34929b40f1 Add Shell 2024-10-12 18:19:57 +02:00
Felix Koppe
5dd3df50c5 Adjust particle effects 2024-10-10 15:47:21 +02:00
Felix Koppe
a7c3632a59 Add hit/miss effects 2024-10-09 07:41:14 +02:00
Felix Koppe
05eb0f92ea Add client host capability 2024-10-08 14:14:53 +02:00
Felix Koppe
f96c63e76e Add music 2024-10-08 12:41:24 +02:00
Felix Koppe
9dbac0b226 Add ship models 2024-10-06 23:13:29 +02:00
Felix Koppe
7ef06f2440 Fix logic error 2024-10-06 22:09:45 +02:00
Felix Koppe
95fdd86100 Add clientside and serverside map check 2024-10-06 19:33:30 +02:00
Felix Koppe
1f548f5ee5 Merge commit 2024-10-02 14:58:36 +02:00
Felix Koppe
af5d1aaca6 Fix BattleState.receivedEffect 2024-10-02 14:57:10 +02:00
Felix Koppe
3a2f20e45c Fix ShipMap.remove 2024-10-02 08:21:32 +02:00
236 changed files with 2693457 additions and 925226 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$" />
<option name="externalProjectPath" value="$PROJECT_DIR$/Projekte" />
<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, 3,\
2, 4,\
2, 5,\
2, 8
robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3
#
# Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500

View File

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

View File

@@ -123,14 +123,9 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/**
* The Object which handles the background music
* Listener for handling actions triggered by the Escape key.
*/
private BackgroundMusic backgroundMusic;
/**
* The object that handles the main volume
*/
private MainVolume mainVolume;
private GameMusic music;
static {
// Configure logging
@@ -203,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.
*
@@ -236,9 +240,7 @@ public void simpleInitApp() {
setupGui();
serverConnection.connect();
mainVolume = new MainVolume(this);
backgroundMusic = new BackgroundMusic(this);
logic.addListener(backgroundMusic);
music = new GameMusic(this, "Sound/Music/battleship.ogg");
}
/**
@@ -440,22 +442,4 @@ 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

@@ -5,14 +5,9 @@
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client.server;
package pp.battleship.client;
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.*;
import com.jme3.network.serializing.Serializer;
import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player;
@@ -43,8 +38,6 @@ 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;
@@ -62,12 +55,18 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
}
}
/**
* Starts the Battleships server.
*/
public static void main(String[] args) {
new BattleshipServer().run();
}
/**
* Creates the server.
*/
public BattleshipServer(int port) {
BattleshipServer() {
config.readFromIfExists(CONFIG_FILE);
BattleshipServer.port = port;
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config);
}
@@ -81,7 +80,7 @@ public void run() {
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(port);
myServer = Network.createServer(config.getPort());
initializeSerializables();
myServer.start();
registerListeners();
@@ -104,6 +103,9 @@ 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);
@@ -112,15 +114,12 @@ 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

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

View File

@@ -14,7 +14,6 @@
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;
@@ -28,19 +27,14 @@
* An application state that plays sounds.
*/
public class GameSound extends AbstractAppState implements GameEventListener {
static final Logger LOGGER = System.getLogger(GameSound.class.getName());
private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameSound.class);
private static final 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 rocketSound;
private BattleshipApp app;
private AudioNode missleSound;
/**
* Checks if sound is enabled in the preferences.
@@ -82,13 +76,10 @@ 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
rocketSound = loadSound(app, "Sound/Effects/rocket.wav");
volume = PREFERENCES.getFloat(SOUND_VOLUME_PREF, 1.0f);
missleSound = loadSound(app, "Sound/Effects/missle.wav");
}
/**
@@ -119,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.
*/
@@ -135,65 +134,13 @@ 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();
break;
case SPLASH :
splash();
break;
case DESTROYED_SHIP:
shipDestroyed();
break;
case ROCKET_FIRED:
rocket();
break;
case EXPLOSION -> explosion();
case SPLASH -> splash();
case DESTROYED_SHIP -> shipDestroyed();
case MISSLE_FLYBY -> flyBy();
}
}
}

View File

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

View File

@@ -7,11 +7,7 @@
package pp.battleship.client;
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.*;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.style.ElementId;
import pp.dialog.Dialog;
@@ -37,13 +33,7 @@ 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.
@@ -54,32 +44,26 @@ 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")))
@@ -88,44 +72,17 @@ public Menu(BattleshipApp app) {
}
/**
* 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
* Updates the state of the load/save buttons and volume based on the game logic.
*/
@Override
public void update(float tpf){
public void update() {
loadButton.setEnabled(app.getGameLogic().mayLoadMap());
saveButton.setEnabled(app.getGameLogic().maySaveMap());
if(volumeRef.update()){
double newVolume = volumeRef.get();
adjustMusicVolume(newVolume);
adjustVolume(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);
}
/**
@@ -133,33 +90,15 @@ private void adjustMainVolume(double newVolume) {
*
* @param volume is the double value of the volume
*/
private void adjustMusicVolume(double volume) {
app.getBackgroundMusic().setVolume((float) volume);
}
/**
* this method adjusts the volume for the sound
*
* @param volume is a double value of the sound volume
*/
private void adjustSoundVolume(double volume) {
app.getStateManager().getState(GameSound.class).setSoundVolume((float) volume);
private void adjustVolume(double volume) {
app.getGameMusic().setVolume((float) volume);
}
/**
* this method toggles the background music on and off
*/
private void toggleMusic() {
app.getBackgroundMusic().toggleMusic();
}
/**
* Updates the state of the load and save buttons based on the game logic.
*/
@Override
public void update() {
loadButton.setEnabled(app.getGameLogic().mayLoadMap());
saveButton.setEnabled(app.getGameLogic().maySaveMap());
app.getGameMusic().toggleMusic();
}
/**

View File

@@ -12,7 +12,6 @@
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;
@@ -53,9 +52,10 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f);
port.setSingleLine(true);
Checkbox serverHost = new Checkbox(lookup("host.own.server"));
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());
@@ -63,12 +63,11 @@ 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 -> connect())
.setOkButton(lookup("button.connect"), d -> connectLocally())
.setNoButton(lookup("button.cancel"), app::closeApp)
.setOkClose(false)
.setNoClose(false)
@@ -79,7 +78,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 connectServer() {
private void connect() {
LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
try {
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
@@ -93,36 +92,38 @@ private void connectServer() {
}
/**
* This method will start a server or just connect to one based on the boolean hostServer
* Starts a server or if not host connects to one
*/
private void connect() {
private void connectLocally() {
if(hostServer){
startServer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
LOGGER.log(Level.WARNING, e.getMessage(), e.getMessage());
}
connectServer();
} else {
connectServer();
}
connect();
}
/**
* This method starts a server in a new thread
* Starts a host server in a new thread
*/
private void startServer() {
new Thread(() -> {
try{
BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText()));
BattleshipServer battleshipServer = new BattleshipServer();
battleshipServer.run();
} catch (Exception e) {
LOGGER.log(Level.ERROR, e.getMessage(), e);
LOGGER.log(Level.ERROR,e);
}
}).start();
}
/**
* Toggles client/host mode
*/
private void toggleServerHost(){
hostServer = !hostServer;
}

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
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;
@@ -16,7 +17,6 @@
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,8 +44,6 @@ 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.
@@ -67,7 +65,6 @@ 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);
@@ -120,26 +117,28 @@ public Spatial visit(Battleship ship) {
}
/**
* this method will create a representation of a shell in the map
* Creates a visual representation of a shell on the map.
*
* @param shell the Shell element to visit
* @return the node the representation is attached to
* @param shell the Shell object representing the shell in the model
* @return a Spatial representing the shell
*/
@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.Black));
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 ShellMapControl(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
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.
*
@@ -151,7 +150,6 @@ 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

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

View File

@@ -8,11 +8,17 @@
package pp.battleship.client.gui;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.renderer.queue.RenderQueue;
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;
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.*;
@@ -28,18 +34,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";
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 KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
private static final String COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS
private static final String SHELL = "shell";
private final EffectHandler effectHandler;
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 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.
@@ -52,10 +58,41 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root);
this.app = app;
this.map = map;
effectHandler = new EffectHandler(app);
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.
@@ -66,7 +103,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
*/
@Override
public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : effectHandler.createMissEffect(shot);
return shot.isHit() ? handleHit(shot) : handler.createMissParticles(shot);
}
/**
@@ -74,19 +111,40 @@ 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");
effectHandler.createHitEffect(shipNode, shot);
effectHandler.createFireEffect(shipNode, shot);
handler.createHitParticles(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
@@ -107,42 +165,6 @@ 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.
@@ -151,84 +173,88 @@ private Spatial createRocket() {
* @return the spatial representing the battleship
*/
private Spatial createShip(Battleship 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");
};
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);
}
}
}
/**
* Creates a detailed 3D model to represent a "King George V" battleship.
* Creates a simple box to represent a battleship that is not of the "King George V" type.
*
* @param ship the battleship to be represented
* @return the spatial representing the "King George V" battleship
* @return the geometry representing the battleship as a box
*/
private Spatial createBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
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);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(1.48f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
return geometry;
}
/**
* creates a detailed 3D model to represent an UBoat
* 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 ship the ship to be represented
* @return the spatial representing the Uboat
* @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 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;
private Material createColoredMaterial(ColorRGBA color) {
final Material material = new Material(app.getAssetManager(), UNSHADED);
if (color.getAlpha() < 1f)
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
material.setColor(COLOR, color);
return material;
}
/**
* creates a detailed 3D model to represent the modern battleship
* Creates a 3D model to represent a "battleship".
*
* @param ship the ship to be represented
* @return the spatial representing the Modern 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 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);
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(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.rotate(rotation);
model.scale(scale);
model.setLocalTranslation(translation);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;

View File

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

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

View File

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

View File

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

View File

@@ -14,9 +14,6 @@
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;
@@ -26,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).
*/
@@ -46,27 +49,15 @@ 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 to be controlled
* The ship, that the ShipControl controls
*/
private final Battleship battleship;
static final Logger LOGGER = System.getLogger(ShipControl.class.getName());
private final Battleship ship;
/**
* Constructs a new ShipControl instance for the specified Battleship.
@@ -76,7 +67,7 @@ class ShipControl extends AbstractControl {
* @param ship the Battleship object to control
*/
public ShipControl(Battleship ship) {
battleship = ship;
this.ship = ship;
// Determine the axis of rotation based on the ship's orientation
axis = switch (ship.getRot()) {
@@ -85,14 +76,13 @@ public ShipControl(Battleship ship) {
};
// Set the cycle duration and amplitude based on the ship's length
cycle = battleship.getLength() * 2f;
cycle = ship.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
*/
@@ -101,25 +91,24 @@ protected void controlUpdate(float tpf) {
// If spatial is null, do nothing
if (spatial == null) return;
if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= SHIP_SINKING_REMOVE_THRESHOLD) {
LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName());
if (ship.isDestroyed() && spatial.getLocalTranslation().getY() < SINKING_HEIGHT) { // removes the ship, if it is completely sunk
spatial.getParent().detachChild(spatial);
} else if (battleship.isDestroyed()) {
spatial.move(0, SINKING_SPEED * tpf, 0);
} else {
// Update the time within the oscillation cycle
time = (time + tpf) % cycle;
// Calculate the current angle of the oscillation
final float angle = amplitude * sin(time * TWO_PI / cycle);
// Update the pitch Quaternion with the new angle
pitch.fromAngleAxis(angle, axis);
// Apply the pitch rotation to the spatial
spatial.setLocalRotation(pitch);
}
else 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;
// 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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

View File

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

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