17 Commits

Author SHA1 Message Date
Lukas Bauer
66798eb3e4 Improved code documentation 2024-10-11 15:22:20 +02:00
Lukas Bauer
edff9e1ad9 Improved code documentation and clarify comments for better readability and understanding 2024-10-11 15:06:09 +02:00
Lukas Bauer
bf16a18b71 Aufgabe 13
edited class BattleState in order to implement the 2D map feature
edited class GameSound to add the Missile launch sound
edited class MapViewSynchronizer in order to implement the 2D map feature
edited class ImpactEffectManager removed unused import
edited class SeaSynchronizer removed unused code
edited class Shell in order to implement the 2D map feature
edited class ShellControl in order to implement the 2D map feature
edited Sound added Missile launch enum
edited FloatMath to improve the animation for the 2D projectile
added missileLaunch.wav
2024-10-11 14:23:49 +02:00
Lukas Bauer
6100e95e76 Aufgabe 13
added class Shell for the feature Airstrike
added class ShellControl for the feature Airstrike
added new Model Bomb to implement the feature Airstrike
added README´S to the Models to show the free to use license
edited class ShipMap for the feature Airstrike
edited class ImpactEffectManager to improve the Fire and Water Particles
edited class MapViewSynchronizer for the feature Airstrike
edited class BattleState for the feature Airstrike
edited class BattleshipApp for the feature Airstrike
2024-10-09 21:10:31 +02:00
Lukas Bauer
1b7b842ca7 JavaDoc adjustments 2024-10-08 19:46:23 +02:00
Lukas Bauer
865b0fc33c Aufgabe 12
added class ImpactEffectManager to implement the funktionality with hit or miss particles
added map4 for particle testing purposes
edited class SeaSynchronizer in order to implement the particle feature
edited class ShipControl in order to implement the particle feature
edited settings.gradle in order to implement the particle feature
2024-10-08 19:43:07 +02:00
Lukas Bauer
5a9b0b1de0 fix Aufgabe 11
edited class NetworkDialog
to change the logic that the checkbox works now as intended
moved BattleshipServer and ReceivedMessage into own folders to separate server from client
2024-10-07 20:11:15 +02:00
Lukas Bauer
8650fa5b58 fix edited class EditorState
fix edited battleship.properties with the right Key to fix an exception
fix edited battleship_de.properties with the right Key to fix an exception
2024-10-05 13:09:05 +02:00
Lukas Bauer
113da79df6 fix edited class NetworkDialog changed Button to Checklist 2024-10-04 18:42:47 +02:00
Lukas Bauer
e41d21584a Aufgabe 11.
added class BattleshipServer, ReceivedMessage
to implement the feature that a client is able to start an server
edited NetworkDialog to implement the logic to start an server and overhauled the GUI to be appropriate for the new features
2024-10-04 18:33:13 +02:00
Lukas Bauer
5e773ca1ba Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:11:53 +02:00
Lukas Bauer
b2b3bc23bf Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:10:36 +02:00
Lukas Bauer
ce490fb877 Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:07:27 +02:00
Lukas Bauer
ce5e908349 edited SeaSynchronizer , EditorState, ServerGameLogic
added JavaDocs for the new and adapted functions
added a new invalid map for testing purposes
fixed implemented the logic to check for invalid maps in the method received in ServerGameLogic
2024-10-03 13:28:41 +02:00
Lukas Bauer
07e922d01e Aufgabe 9.
edited SeaSynchronizer
added 3 Ship Models Small Ship, Medium Ship and Big Ship and tweaked the scaling of all Models
2024-10-02 20:23:05 +02:00
Lukas Bauer
42b442e937 Aufgabe 8.
edited class EditorState and ServerGameLogic
 to check for invalid maps in which ships overlap or are out of the map boundries for clientside and serverside
2024-10-02 18:24:27 +02:00
Lukas Bauer
93ade34282 Aufgabe 7.
fix: in class ShipMap
function remove
hierbei wurde nach dem entfernen eines Items dass Event ItemAddedEvent ausgelöst hierbei sollte aber logischerweiße dass Event ItemRemovedEvent ausgelöst werden.

fix: in class BattleState
function receivedEffect
hierbei wurde die eigene map für die Bedingung isGameOver verglichen wobei man für isGameOver die OpponentMap auf remainingShips untersuchen muss.
2024-10-02 00:31:40 +02:00
124 changed files with 1059257 additions and 353371 deletions

View File

@@ -1,18 +1,17 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application" <configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application" singleton="false">
singleton="false"> <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp" />
<option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/> <module name="Projekte.battleship.client.main" />
<module name="Projekte.battleship.client.main"/> <option name="VM_PARAMETERS" value="-XstartOnFirstThread" />
<option name="VM_PARAMETERS" value="-XstartOnFirstThread"/> <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="pp.battleship.client.*"/> <option name="PATTERN" value="pp.battleship.client.server.*" />
<option name="ENABLED" value="true"/> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>
<method v="2"> <method v="2">
<option name="Make" enabled="true"/> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>

View File

@@ -1,18 +1,19 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false" <configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
nameIsGenerated="true"> <option name="ALTERNATIVE_JRE_PATH" value="temurin-20" />
<option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/> <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<module name="Projekte.battleship.client.main"/> <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp" />
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/> <module name="Gruppe-01" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="pp.battleship.client.*"/> <option name="PATTERN" value="pp.battleship.client.server.*" />
<option name="ENABLED" value="true"/> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>
<method v="2"> <method v="2">
<option name="Make" enabled="true"/> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>

View File

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

View File

@@ -26,13 +26,10 @@ map.own=maps/map1.json
robot.targets=2, 0,\ robot.targets=2, 0,\
2, 1,\ 2, 1,\
2, 2,\ 2, 2,\
2, 3,\ 2, 3
2, 4,\
2, 5,\
2, 6
# #
# Delay in milliseconds between each shot fired by the RobotClient. # Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=4000 robot.delay=500
# #
# The dimensions of the game map used in single mode. # The dimensions of the game map used in single mode.
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows. # 'map.width' defines the number of columns, and 'map.height' defines the number of rows.

View File

@@ -0,0 +1,66 @@
{
"width": 10,
"height": 10,
"ships": [
{
"length": 4,
"x": 2,
"y": 8,
"rot": "RIGHT"
},
{
"length": 3,
"x": 2,
"y": 5,
"rot": "DOWN"
},
{
"length": 3,
"x": 5,
"y": 6,
"rot": "RIGHT"
},
{
"length": 2,
"x": 4,
"y": 4,
"rot": "RIGHT"
},
{
"length": 2,
"x": 7,
"y": 4,
"rot": "RIGHT"
},
{
"length": 2,
"x": 7,
"y": 4,
"rot": "RIGHT"
},
{
"length": 1,
"x": 6,
"y": 2,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 2,
"rot": "RIGHT"
},
{
"length": 1,
"x": 6,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 0,
"rot": "RIGHT"
}
]
}

View File

@@ -0,0 +1,66 @@
{
"width": 10,
"height": 10,
"ships": [
{
"length": 1,
"x": 9,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 9,
"y": 1,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 1,
"rot": "RIGHT"
},
{
"length": 2,
"x": 8,
"y": 2,
"rot": "RIGHT"
},
{
"length": 2,
"x": 6,
"y": 0,
"rot": "RIGHT"
},
{
"length": 2,
"x": 6,
"y": 1,
"rot": "RIGHT"
},
{
"length": 3,
"x": 5,
"y": 2,
"rot": "RIGHT"
},
{
"length": 3,
"x": 7,
"y": 3,
"rot": "RIGHT"
},
{
"length": 4,
"x": 6,
"y": 4,
"rot": "RIGHT"
}
]
}

View File

@@ -0,0 +1,95 @@
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.audio.AudioData.DataType;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource.Status;
import java.util.prefs.Preferences;
public class BackgroundMusic {
private static final String VOLUME_PREF = "volume";
private static final String MUSIC_ENABLED_PREF = "musicEnabled";
private Preferences prefs = Preferences.userNodeForPackage(BackgroundMusic.class);
private final AudioNode backgroundMusic;
private boolean musicEnabled;
private float volume;
/**
* Constuctor for the BackgroundMusic
* @param app for synchronising and saving data with the application
* @param musicFilePath the filepath for the BackgroundMusic
*/
public BackgroundMusic(Application app, String musicFilePath) {
this.volume = prefs.getFloat(VOLUME_PREF, 1.0f);
this.musicEnabled = prefs.getBoolean(MUSIC_ENABLED_PREF, true);
backgroundMusic = new AudioNode(app.getAssetManager(), musicFilePath, DataType.Stream);
backgroundMusic.setLooping(true);
backgroundMusic.setPositional(false);
backgroundMusic.setVolume(1.0f);
if (musicEnabled) {
play();
}
}
/**
* Checks if the condition is met to play BackgroundMusic
*/
public void play() {
if (musicEnabled && (backgroundMusic.getStatus() == Status.Stopped || backgroundMusic.getStatus() == Status.Paused)) {
backgroundMusic.play();
}
}
/**
* function to stop the BackgroundMusic
*/
public void stop() {
if (backgroundMusic.getStatus() == Status.Playing) {
backgroundMusic.stop();
}
}
/**
* function to toggle the Backgroundmusic also sets the volume to the previous session
*/
public void toogleMusic() {
this.musicEnabled = !this.musicEnabled;
if (musicEnabled) {
play();
} else {
stop();
}
prefs.putFloat(VOLUME_PREF, volume);
}
/**
* Setter for the Volume also safes the volume from the previous session
* @param volume variable for the volume of the BackgroundMusic
*/
public void setVolume(float volume) {
this.volume = volume;
backgroundMusic.setVolume(volume);
prefs.putFloat(VOLUME_PREF, volume);
}
/**
* Getter for Volume
* @return a float value of the volume
*/
public float getVolume() {
return volume;
}
/**
* Getter for musicEnabled
* @return if the music is enabled return true, false otherwise
*/
public boolean isMusicEnabled() {
return musicEnabled;
}
}

View File

@@ -121,8 +121,10 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
* Listener for handling actions triggered by the Escape key. * Listener for handling actions triggered by the Escape key.
*/ */
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed); private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/**
private EffectHandler effectHandler; *
*/
private BackgroundMusic backgroundMusic;
static { static {
// Configure logging // Configure logging
@@ -143,6 +145,7 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
*/ */
public static void main(String[] args) { public static void main(String[] args) {
new BattleshipApp().start(); new BattleshipApp().start();
} }
/** /**
@@ -157,7 +160,6 @@ private BattleshipApp() {
logic.addListener(this); logic.addListener(this);
setShowSettings(config.getShowSettings()); setShowSettings(config.getShowSettings());
setSettings(makeSettings()); setSettings(makeSettings());
effectHandler = null;
} }
/** /**
@@ -227,8 +229,8 @@ public void simpleInitApp() {
setupInput(); setupInput();
setupStates(); setupStates();
setupGui(); setupGui();
effectHandler = new EffectHandler(this);
serverConnection.connect(); serverConnection.connect();
backgroundMusic = new BackgroundMusic(this, "Sound/Effects/BackgroundMusic/boss_battle_#2_metal_opening.wav");
} }
/** /**
@@ -269,20 +271,11 @@ private void setupStates() {
flyCam.setEnabled(false); flyCam.setEnabled(false);
stateManager.detach(stateManager.getState(StatsAppState.class)); stateManager.detach(stateManager.getState(StatsAppState.class));
stateManager.detach(stateManager.getState(DebugKeysAppState.class)); stateManager.detach(stateManager.getState(DebugKeysAppState.class));
attachGameMusic();
attachGameSound(); attachGameSound();
stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState());
} }
/**
* Attaches the game sound state and sets its initial enabled state.
*/
private void attachGameMusic() {
final GameMusic gameMusic = new GameMusic();
gameMusic.setEnabled(GameMusic.enabledInPreferences());
stateManager.attach(gameMusic);
}
/** /**
* Attaches the game sound state and sets its initial enabled state. * Attaches the game sound state and sets its initial enabled state.
*/ */
@@ -440,7 +433,11 @@ void errorDialog(String errorMessage) {
.open(); .open();
} }
public EffectHandler getEffectHandler() { /**
return effectHandler; * Getter for the BackgroundMusic
* @return the BackgroundMusic
*/
public BackgroundMusic getBackgroundMusic(){
return backgroundMusic;
} }
} }

View File

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

View File

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

View File

@@ -10,6 +10,9 @@
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState; import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager; import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode; import com.jme3.audio.AudioNode;
import pp.battleship.notification.GameEventListener; import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent; import pp.battleship.notification.SoundEvent;
@@ -18,7 +21,6 @@
import java.lang.System.Logger.Level; import java.lang.System.Logger.Level;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import static pp.JmeUtil.loadSound;
import static pp.util.PreferencesUtils.getPreferences; import static pp.util.PreferencesUtils.getPreferences;
/** /**
@@ -32,7 +34,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
private AudioNode splashSound; private AudioNode splashSound;
private AudioNode shipDestroyedSound; private AudioNode shipDestroyedSound;
private AudioNode explosionSound; private AudioNode explosionSound;
private AudioNode shellFlyingSound; private AudioNode missileLaunch;
/** /**
* Checks if sound is enabled in the preferences. * Checks if sound is enabled in the preferences.
@@ -43,13 +45,6 @@ public static boolean enabledInPreferences() {
return PREFERENCES.getBoolean(ENABLED_PREF, true); return PREFERENCES.getBoolean(ENABLED_PREF, true);
} }
/**
* Toggles the game sound on or off.
*/
public void toggleSound() {
setEnabled(!isEnabled());
}
/** /**
* Sets the enabled state of this AppState. * Sets the enabled state of this AppState.
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
@@ -77,16 +72,33 @@ public void initialize(AppStateManager stateManager, Application app) {
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.wav"); missileLaunch = loadSound(app, "Sound/Effects/missileLaunch.wav");
} }
/** /**
* Plays the shell flying sound effect. * Loads a sound from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/ */
public void shellFly() { private AudioNode loadSound(Application app, String name) {
if (isEnabled() && shellFlyingSound != null) { try {
shellFlyingSound.playInstance(); 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;
}
/**
* Plays the missile launch sound effect.
*/
public void missileLaunch() {
missileLaunch.playInstance();
} }
/** /**
@@ -113,13 +125,17 @@ public void shipDestroyed() {
shipDestroyedSound.playInstance(); shipDestroyedSound.playInstance();
} }
/**
* Checks the according case for the soundeffect
* @param event the received event
*/
@Override @Override
public void receivedEvent(SoundEvent event) { public void receivedEvent(SoundEvent event) {
switch (event.sound()) { switch (event.sound()) {
case EXPLOSION -> explosion(); case EXPLOSION -> explosion();
case SPLASH -> splash(); case SPLASH -> splash();
case DESTROYED_SHIP -> shipDestroyed(); case DESTROYED_SHIP -> shipDestroyed();
case SHELL_FLYING -> shellFly(); case MISSILE_LAUNCH -> missileLaunch();
} }
} }
} }

View File

@@ -14,6 +14,10 @@
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel; import pp.dialog.StateCheckboxModel;
import pp.dialog.TextInputDialog; import pp.dialog.TextInputDialog;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.core.VersionedReference;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -33,7 +37,7 @@ class Menu extends Dialog {
private final BattleshipApp app; private final BattleshipApp app;
private final Button loadButton = new Button(lookup("menu.map.load")); private final Button loadButton = new Button(lookup("menu.map.load"));
private final Button saveButton = new Button(lookup("menu.map.save")); private final Button saveButton = new Button(lookup("menu.map.save"));
private final VolumeSlider volumeSlider; private final VersionedReference<Double> volumeRef;
/** /**
* Constructs the Menu dialog for the Battleship application. * Constructs the Menu dialog for the Battleship application.
@@ -43,14 +47,23 @@ class Menu extends Dialog {
public Menu(BattleshipApp app) { public Menu(BattleshipApp app) {
super(app.getDialogManager()); super(app.getDialogManager());
this.app = app; this.app = app;
volumeSlider = new VolumeSlider(app.getStateManager().getState(GameMusic.class));
addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS
addChild(new Checkbox(lookup("menu.sound-enabled"), addChild(new Checkbox(lookup("menu.sound-enabled"),
new StateCheckboxModel(app, GameSound.class))); new StateCheckboxModel(app, GameSound.class)));
addChild(new Checkbox(lookup("menu.music-enabled"), new StateCheckboxModel(app, GameMusic.class)));
Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
musicToggle.setChecked(app.getBackgroundMusic().isMusicEnabled());
musicToggle.addClickCommands(s -> toggleMusic());
addChild(musicToggle);
Slider volumeSlider = new Slider();
volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 2.0, app.getBackgroundMusic().getVolume()));
volumeSlider.setDelta(0.1);
addChild(volumeSlider); addChild(volumeSlider);
volumeRef = volumeSlider.getModel().createReference();
addChild(loadButton) addChild(loadButton)
.addClickCommands(s -> ifTopDialog(this::loadDialog)); .addClickCommands(s -> ifTopDialog(this::loadDialog));
addChild(saveButton) addChild(saveButton)
@@ -62,6 +75,33 @@ public Menu(BattleshipApp app) {
update(); update();
} }
/**
* Updates the volume if there is a change and adjusts it accordingly.
* @param tmp unused time parameter
*/
public void update(float tmp){
if(volumeRef.update()) {
double newVolume = volumeRef.get();
adjustVolume(newVolume);
}
}
/**
* Adjusts the background music volume to the specified value.
* @param newVolume the new volume level
*/
private void adjustVolume(double newVolume) {
app.getBackgroundMusic().setVolume((float) newVolume);
}
/**
* Toggles the background music on or off.
*/
private void toggleMusic(){
app.getBackgroundMusic().toogleMusic();
}
/** /**
* Updates the state of the load and save buttons based on the game logic. * Updates the state of the load and save buttons based on the game logic.
*/ */
@@ -71,11 +111,6 @@ public void update() {
saveButton.setEnabled(app.getGameLogic().maySaveMap()); saveButton.setEnabled(app.getGameLogic().maySaveMap());
} }
@Override
public void update(float delta) {
volumeSlider.update();
}
/** /**
* As an escape action, this method closes the menu if it is the top dialog. * As an escape action, this method closes the menu if it is the top dialog.
*/ */

View File

@@ -7,12 +7,9 @@
package pp.battleship.client; package pp.battleship.client;
import com.simsilica.lemur.Checkbox; import com.simsilica.lemur.*;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.SpringGridLayout; import com.simsilica.lemur.component.SpringGridLayout;
import pp.battleship.client.clienthost.BattleshipServerClient; import pp.battleship.client.server.BattleshipServer;
import pp.dialog.Dialog; import pp.dialog.Dialog;
import pp.dialog.DialogBuilder; import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog; import pp.dialog.SimpleDialog;
@@ -38,10 +35,11 @@ class NetworkDialog extends SimpleDialog {
private String hostname; private String hostname;
private int portNumber; private int portNumber;
private Future<Object> connectionFuture; private Future<Object> connectionFuture;
private Future<Object> serverFuture;
private Dialog progressDialog; private Dialog progressDialog;
private BattleshipServerClient server; private BattleshipServer battleshipServer;
private final Checkbox clientHostCheckbox; private boolean GUI = true;
private Container menu;
private boolean serverToggle = false;
/** /**
* Constructs a new NetworkDialog. * Constructs a new NetworkDialog.
@@ -55,50 +53,118 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f); host.setPreferredWidth(400f);
port.setSingleLine(true); port.setSingleLine(true);
this.menu = new Container();
menu();
addChild(menu);
}
/**
* Main Menu creation
*/
private void menu() {
Button connectGame = new Button("Multiplayer");
connectGame.addClickCommands(source -> serverConnectGUI());
Checkbox serverHost = new Checkbox("Host own Game");
serverHost.addClickCommands(source -> toggleServer());
menu.addChild(serverHost);
menu.addChild(connectGame);
}
/**
* Logic for the ServerGUI
*/
private void serverConnectGUI() {
LOGGER.log(Level.INFO, "Hosting Server...");
if (GUI) {
host.setSingleLine(true);
host.setPreferredWidth(400f);
port.setSingleLine(true);
final BattleshipApp app = network.getApp(); final BattleshipApp app = network.getApp();
final Container input = new Container(new SpringGridLayout()); final Container input = new Container(new SpringGridLayout());
input.addChild(new Label(lookup("host.name") + ": ")); input.addChild(new Label(lookup("host.name") + ": "));
input.addChild(host, 1); input.addChild(host, 1);
input.addChild(new Label(lookup("port.number") + ": ")); input.addChild(new Label(lookup("port.number") + ": "));
input.addChild(port, 1); input.addChild(port, 1);
clientHostCheckbox = new Checkbox("Host Server");
input.addChild(clientHostCheckbox);
DialogBuilder.simple(app.getDialogManager()) DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog")) .setTitle(lookup("server.dialog"))
.setExtension(d -> d.addChild(input)) .setExtension(d -> d.addChild(input))
.setOkButton(lookup("button.connect"), d -> connect()) .setOkButton(lookup("button.connect"), d -> connect())
.setNoButton(lookup("button.cancel"), app::closeApp)
.setOkClose(false) .setOkClose(false)
.setNoClose(false)
.build(this); .build(this);
GUI = false;
}
}
/**
* Connect to server if serverToggle is false hosts otherwise
*/
private void connect(){
if (serverToggle) {
hostServer();
} else {
connectServer();
}
} }
/** /**
* Handles the action for the connect button in the connection dialog. * Handles the action for the connect button in the connection dialog.
* Tries to parse the port number and initiate connection to the server. * Tries to parse the port number and initiate connection to the server.
*/ */
private void connect() { private void connectServer() {
LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
try { try {
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText(); hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
portNumber = Integer.parseInt(port.getText()); portNumber = Integer.parseInt(port.getText());
openProgressDialog(); openProgressDialog();
if (clientHostCheckbox.isChecked()) {
serverFuture = network.getApp().getExecutor().submit(this::initServer);
while (server == null || !server.isReady()) {
Thread.sleep(100);
}
}
connectionFuture = network.getApp().getExecutor().submit(this::initNetwork); connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
network.getApp().errorDialog(lookup("port.must.be.integer")); network.getApp().errorDialog(lookup("port.must.be.integer"));
} }
catch (InterruptedException e) {
throw new RuntimeException(e);
} }
/**
* Checks if the Server is running or starts the Server and handles Exceptions *
*/
private void hostServer() {
if (battleshipServer == null) {
startServer(); // Starts the server in a new thread
try {
Thread.sleep(1000); // Wait for the server to start
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
}
// Proceed with connecting to the server
connectServer();
}
/**
* Toggle Method for Boolean
*/
private void toggleServer(){
serverToggle = !serverToggle;
}
/**
* Allows Client to Start a Server in a new Thread
*/
private void startServer() {
new Thread(() -> {
try {
// Initialize and run the server
battleshipServer = new BattleshipServer();
battleshipServer.run(Integer.parseInt(port.getText()));
} catch (Exception e) {
LOGGER.log(Level.ERROR, e);
}
}).start();
} }
/** /**
@@ -120,25 +186,7 @@ private Object initNetwork() {
try { try {
network.initNetwork(hostname, portNumber); network.initNetwork(hostname, portNumber);
return null; return null;
} } catch (Exception e) {
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Tries to initialize the server hosted by the client.
*
* @throws RuntimeException If an error occurs when starting the server.
*/
private Object initServer() {
try {
server = new BattleshipServerClient();
server.run(Integer.parseInt(port.getText()));
return null;
}
catch (Exception e) {
LOGGER.log(Level.ERROR, "Error while starting server", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -153,27 +201,12 @@ public void update(float delta) {
try { try {
connectionFuture.get(); connectionFuture.get();
success(); success();
} } catch (ExecutionException e) {
catch (ExecutionException e) {
failure(e.getCause()); failure(e.getCause());
} } catch (InterruptedException e) {
catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
if (serverFuture != null && serverFuture.isDone()) {
try {
serverFuture.get();
}
catch (ExecutionException e) {
LOGGER.log(Level.ERROR, "Failed to start server", e.getCause());
}
catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Server thread was interrupted", e);
Thread.currentThread().interrupt();
}
}
} }
/** /**

View File

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

View File

@@ -0,0 +1,155 @@
package pp.battleship.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
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.client.BattleshipApp;
import pp.battleship.model.Shot;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
/**
* Manages the visual effects for impact events such as hits and misses.
*/
public class ImpactEffectManager {
private final AssetManager assetManager;
private static final Logger LOGGER = System.getLogger(ImpactEffectManager.class.getName());
private Material particleMaterial;
private BattleshipApp app;
/**
* Constructor to initialize the asset manager via the main application.
*
* @param app The main application instance.
*/
public ImpactEffectManager(BattleshipApp app) {
this.app = app;
this.assetManager = app.getAssetManager();
this.particleMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
}
/**
* Generates a visual effect for a hit at the specified position.
*
* @param battleshipNode The node where the effect should be attached.
* @param shot The details of the shot where the hit occurred.
*/
public void triggerHitEffect(Node battleshipNode, Shot shot) {
ParticleEmitter hitEffect = createParticleEmitter("HitEffect", 50, ColorRGBA.Orange, ColorRGBA.Red, 0.45f, 0.1f, 1f, 2f);
hitEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
LOGGER.log(Level.DEBUG, "Hit effect created at position: {0}", hitEffect.getLocalTranslation().toString());
// hitEffect.emitAllParticles();
hitEffect.addControl(new EffectCleanupControl(hitEffect));
app.getRootNode().attachChild(hitEffect);
}
/**
* Creates a visual effect for a missed shot at the specified location.
*
* @param shot The details of the missed shot.
* @return The particle emitter representing the miss effect.
*/
public ParticleEmitter triggerMissEffect(Shot shot) {
ParticleEmitter missEffect = createParticleEmitter("MissEffect", 50, ColorRGBA.Blue, ColorRGBA.Cyan, 0.3f, 0.05f, 0.5f, 1.5f);
missEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
missEffect.addControl(new EffectCleanupControl(missEffect));
return missEffect;
}
/**
* Helper method to create a particle emitter with predefined parameters.
*
* @param name The name of the particle emitter.
* @param count The number of particles to emit.
* @param startColor The initial color of the particles.
* @param endColor The final color of the particles.
* @param startSize The initial size of the particles.
* @param endSize The final size of the particles.
* @param lowLife The minimum lifetime of the particles.
* @param highLife The maximum lifetime of the particles.
* @return The configured ParticleEmitter instance.
*/
private ParticleEmitter createParticleEmitter(String name, int count, ColorRGBA startColor, ColorRGBA endColor, float startSize, float endSize, float lowLife, float highLife) {
ParticleEmitter emitter = new ParticleEmitter(name, Type.Triangle, count);
emitter.setNumParticles(0);
emitter.setMaterial(particleMaterial);
emitter.setImagesX(2);
emitter.setImagesY(2);
emitter.setStartColor(startColor);
emitter.setEndColor(endColor);
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
emitter.setStartSize(startSize);
emitter.setEndSize(endSize);
emitter.setGravity(0, -0.5f, 0);
emitter.setLowLife(lowLife);
emitter.setHighLife(highLife);
return emitter;
}
/**
* Custom control class to handle the cleanup of particle effects once they are no longer visible.
*/
private static class EffectCleanupControl extends AbstractControl {
private final ParticleEmitter emitter;
private float currentTime = 0;
private final float duration = 3.5f;
/**
* Constructor for managing cleanup of a standalone particle emitter.
*
* @param emitter The particle emitter to manage.
*/
public EffectCleanupControl(ParticleEmitter emitter) {
this.emitter = emitter;
}
/**
* Removes the emitter when all particles are no longer visible.
*
* @param tpf Time per frame.
*/
@Override
protected void controlUpdate(float tpf) {
currentTime += tpf;
if (currentTime <= duration) {
}
if (currentTime >= 1f && currentTime <= 1.1) {
//Start
emitter.setNumParticles(50);
emitter.setParticlesPerSec(50);
}
if (currentTime >= duration){
emitter.setParticlesPerSec(0);
}
if (currentTime >= duration+1f) {
//Ende
spatial.removeFromParent();
emitter.emitAllParticles();
}
}
/**
* No custom rendering needed; particle behavior is handled in controlUpdate.
* @param rm handles rendering, and ViewPort
* @param vp defines the view where the scene is displayed.
*/
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
// No rendering-specific behavior needed for this control.
}
}
}

View File

@@ -28,7 +28,7 @@
* and interaction between the model and the view. * and interaction between the model and the view.
*/ */
class MapView { class MapView {
public static final float FIELD_SIZE = 40f; private static final float FIELD_SIZE = 40f;
private static final float GRID_LINE_WIDTH = 2f; private static final float GRID_LINE_WIDTH = 2f;
private static final float BACKGROUND_DEPTH = -4f; private static final float BACKGROUND_DEPTH = -4f;
private static final float GRID_DEPTH = -1f; private static final float GRID_DEPTH = -1f;

View File

@@ -7,20 +7,15 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import pp.battleship.model.Battleship; import pp.battleship.model.Battleship;
import pp.battleship.model.Shell; import pp.battleship.model.Shell;
import pp.battleship.model.Shot; import pp.battleship.model.Shot;
import pp.util.Position; import pp.util.Position;
import java.lang.System.Logger;
import static com.jme3.material.Materials.UNSHADED;
/** /**
* Synchronizes the visual representation of the ship map with the game model. * Synchronizes the visual representation of the ship map with the game model.
@@ -32,9 +27,10 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private static final float SHIP_LINE_WIDTH = 6f; private static final float SHIP_LINE_WIDTH = 6f;
private static final float SHOT_DEPTH = -2f; private static final float SHOT_DEPTH = -2f;
private static final float SHIP_DEPTH = 0f; private static final float SHIP_DEPTH = 0f;
private static final float SHELL_DEPTH = 1f;
private static final float INDENT = 4f; private static final float INDENT = 4f;
private static final float SHELL_DEPTH = 8f;
// Colors used for different visual elements // Colors used for different visual elements
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red; private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue; private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
@@ -118,23 +114,21 @@ public Spatial visit(Battleship ship) {
} }
/** /**
* Creates and returns a Spatial representation of the given {@code Shell} object * Creates a visual representation (Spatial) for the given shell, attaching a control for its behavior.
* for 2D visualization in the game. The shell is represented as a circle. * @param shell the shell to visit and visualize
* * @return the constructed shell node (Spatial)
* @param shell The {@code Shell} object to be visualized.
* @return A {@code Spatial} object representing the shell on the map.
*/ */
@Override @Override
public Spatial visit(Shell shell) { public Spatial visit(Shell shell) {
final ColorRGBA color = ColorRGBA.Black; LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell);
Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f)); final Node shellNode = new Node("shell");
Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS final Position startPosition = view.modelToView(shell.getCurrentPosition().x,shell.getCurrentPosition().z);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(),startPosition.getY(), SHELL_DEPTH, 30, 30, ColorRGBA.Black));
mat.setColor("Color", color);
ellipse.setMaterial(mat); shellNode.addControl(new ShellControl(shell, this.view, shell.getLogic()));
ellipse.addControl(new Shell2DControl(view, shell)); return shellNode;
return ellipse;
} }
/** /**

View File

@@ -17,17 +17,9 @@
import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder; import com.jme3.scene.shape.Cylinder;
import pp.battleship.client.BattleshipApp; import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Battleship; import pp.battleship.model.*;
import pp.battleship.model.Rotation;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot;
import java.util.Timer;
import java.util.TimerTask;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static pp.JmeUtil.mapToWorldCord;
import static pp.util.FloatMath.HALF_PI; import static pp.util.FloatMath.HALF_PI;
import static pp.util.FloatMath.PI; import static pp.util.FloatMath.PI;
@@ -39,7 +31,11 @@
*/ */
class SeaSynchronizer extends ShipMapSynchronizer { class SeaSynchronizer extends ShipMapSynchronizer {
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";//NON-NLS
private static final String DESTROYER = "Models/Destroyer/Destroyer.j3o";
private static final String FERRY = "Models/Ferry/13922_Staten_Island_Ferry_V1_l1.obj";
private static final String SMALL = "Models/Small/10634_SpeedBoat_v01_LOD3.obj";
private static final String BOMB = "Models/Bomb/BombGBU.j3o";
private static final String COLOR = "Color"; //NON-NLS private static final String COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS private static final String SHIP = "ship"; //NON-NLS
private static final String SHOT = "shot"; //NON-NLS private static final String SHOT = "shot"; //NON-NLS
@@ -49,6 +45,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private final ShipMap map; private final ShipMap map;
private final BattleshipApp app; private final BattleshipApp app;
private ImpactEffectManager effectHandler;
/** /**
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map. * Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
@@ -61,6 +58,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root); super(app.getGameLogic().getOwnMap(), root);
this.app = app; this.app = app;
this.map = map; this.map = map;
effectHandler = new ImpactEffectManager(app);
addExisting(); addExisting();
} }
@@ -74,11 +72,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
*/ */
@Override @Override
public Spatial visit(Shot shot) { public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : handleMiss(shot); return shot.isHit() ? handleHit(shot) : effectHandler.triggerMissEffect(shot);
}
private Spatial handleMiss(Shot shot) {
return app.getEffectHandler().waterSplash(mapToWorldCord(shot.getX(), shot.getY()));
} }
/** /**
@@ -92,24 +86,13 @@ private Spatial handleMiss(Shot shot) {
private Spatial handleHit(Shot shot) { private Spatial handleHit(Shot shot) {
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship"); final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node"); final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
shipNode.getControl(ShipControl.class).hit(shot);
if (ship.isDestroyed()) {
shipNode.getControl(ShipControl.class).destroyed();
new Timer().schedule(new TimerTask() { System.out.println(shot.getX() + " " + shot.getY());
@Override effectHandler.triggerHitEffect(shipNode, shot);
public void run() {
handleShipDestroy(shipNode);
}
}, 10000);
}
return null; return null;
} }
private void handleShipDestroy(Node shipNode) {
shipNode.getParent().detachChild(shipNode);
}
/** /**
* Creates a cylinder geometry representing the specified shot. * Creates a cylinder geometry representing the specified shot.
* The appearance of the cylinder depends on whether the shot is a hit or a miss. * The appearance of the cylinder depends on whether the shot is a hit or a miss.
@@ -126,7 +109,7 @@ private Geometry createCylinder(Shot shot) {
geometry.setMaterial(createColoredMaterial(color)); geometry.setMaterial(createColoredMaterial(color));
geometry.rotate(HALF_PI, 0f, 0f); 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); geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
return geometry; return geometry;
@@ -148,46 +131,46 @@ public Spatial visit(Battleship ship) {
final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f); final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f); final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
node.setLocalTranslation(x, 0f, z); node.setLocalTranslation(x, 0f, z);
node.addControl(new ShipControl(ship, node, app.getEffectHandler())); node.addControl(new ShipControl(ship));
return node; return node;
} }
/**
* Creates and returns a 3D model representation of the given {@code Shell} object
* for visualization in the game.
*
* @param shell The {@code Shell} object to be visualized.
* @return A {@code Spatial} object representing the 3D model of the shell.
*/
@Override @Override
public Spatial visit(Shell shell) { public Spatial visit(Shell shell) {
final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o"); final Spatial bombModel = app.getAssetManager().loadModel(BOMB);
model.setLocalScale(.05f);
model.setShadowMode(ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), LIGHTING);
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture("Models/Shell/shell_color.png"));
mat.setReceivesShadows(true);
model.setMaterial(mat);
model.addControl(new ShellControl(shell)); // Apply transformations to the bomb model (scale, rotate, etc. if needed)
return model; bombModel.scale(0.05f);
bombModel.rotate(-HALF_PI, 0f, 0f);
// Set the position of the bomb at the shell's current position
bombModel.setLocalTranslation(shell.getCurrentPosition());
// Add a control to animate the bomb's movement (similar to the previous cylinder animation)
bombModel.addControl(new ShellControl(shell,shell.getLogic()));
return bombModel;
} }
/** /**
* Creates the appropriate graphical representation of the specified battleship. * 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. * The representation is a detailed model based on the length of the ship.
* *
* @param ship the battleship to be represented * @param ship the battleship to be represented
* @return the spatial representing the battleship * @return the spatial representing the battleship
*/ */
private Spatial createShip(Battleship ship) { private Spatial createShip(Battleship ship) {
return switch (ship.getLength()) { // return ship.getLength() == 4 ? createBattleship(ship) : createBox(ship);
case 1 -> createBattleship(ship, ShipModel.SHIP1); if (ship.getLength() == 4) {
case 2 -> createBattleship(ship, ShipModel.SHIP2); return createBattleship(ship);
case 3 -> createBattleship(ship, ShipModel.SHIP3); } else if (ship.getLength() == 3) {
case 4 -> createBattleship(ship, ShipModel.SHIP4); return createBigShip(ship);
default -> createBox(ship); } else if (ship.getLength() == 2) {
}; return createMediumShip(ship);
} else if (ship.getLength() == 1) {
return createSmallShip(ship);
}
return createBattleship(ship);
} }
/** /**
@@ -232,23 +215,60 @@ private Material createColoredMaterial(ColorRGBA color) {
* @param ship the battleship to be represented * @param ship the battleship to be represented
* @return the spatial representing the "King George V" battleship * @return the spatial representing the "King George V" battleship
*/ */
private Spatial createBattleship(Battleship ship, ShipModel shipModel) { private Spatial createBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(shipModel.getPath()); final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(shipModel.getScale()); model.scale(1.48f);
model.setLocalTranslation(shipModel.getTranslation());
model.setShadowMode(ShadowMode.CastAndReceive); model.setShadowMode(ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), LIGHTING); return model;
}
String colorPath = shipModel.getColorPath(); /**
String bumpPath = shipModel.getBumpPath(); * Creates a detailed 3D model to represent a "Big Ship"
if (colorPath != null) mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(colorPath)); * @param ship The Battleship object with position and rotation info.
if (bumpPath != null) mat.setTexture("NormalMap", app.getAssetManager().loadTexture(bumpPath)); * @return the spatial representing the "Big Ship" battleship
*/
private Spatial createBigShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(DESTROYER);
model.move(0,0.4f,0);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
model.scale(0.0001f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* Creates a detailed 3D model to represent a "Medium Ship"
* @param ship The Battleship object with position and rotation info.
* @return the spatial representing the "Medium Ship" battleship
*/
private Spatial createMediumShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(FERRY);
model.move(0,0.25f,0);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.00025f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* Creates a detailed 3D model to represent a "Small Ship"
* @param ship The Battleship object with position and rotation info.
* @return the spatial representing the "Small Ship" battleship
*/
private Spatial createSmallShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(SMALL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
model.scale(0.0009f);
model.setShadowMode(ShadowMode.CastAndReceive);
mat.setReceivesShadows(true);
model.setMaterial(mat);
return model; return model;
} }

View File

@@ -1,49 +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.model.Shell;
import pp.util.Position;
/**
* Controls the 2D representation of a {@code Shell} in the game, updating its position
* based on the shell's current state in the game model. The {@code Shell2DControl} class
* is responsible for translating the shell's 3D position to a 2D view position within
* the game's map view.
*/
public class Shell2DControl extends AbstractControl {
private final Shell shell;
private final MapView view;
/**
* Constructs a new {@code Shell2DControl} to manage the 2D visualization of the given {@code Shell}.
*
* @param view The {@code MapView} used to get information about the map to display.
* @param shell The {@code Shell} being visualized.
*/
public Shell2DControl(MapView view, Shell shell){
this.shell = shell;
this.view = view;
}
/**
* Updates the position of the shell's 2D representation based on the shell's current
* 3D position in the game model. The position is mapped from model space to view space
* coordinates and translated to the appropriate location within the {@code MapView}.
*
* @param tpf Time per frame, representing the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
Vector3f shellPos = shell.getPosition();
Position viewPos = view.modelToView(shellPos.x, shellPos.z);
spatial.setLocalTranslation(viewPos.getX() + MapView.FIELD_SIZE / 2, viewPos.getY() + MapView.FIELD_SIZE / 2, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-specific behavior required for this control
}
}

View File

@@ -1,51 +1,96 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import pp.battleship.game.client.ClientGameLogic;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.model.Shell; import pp.battleship.model.Shell;
import pp.battleship.notification.Sound;
import static pp.JmeUtil.mapToWorldCord; import pp.util.Position;
import static pp.util.FloatMath.PI;
/** /**
* Controls the 3D representation of a {@code Shell} in the game, updating its position * Controls the oscillating pitch motion of a battleship model in the game.
* and rotation based on the shell's current state in the game model. The {@code ShellControl} * The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
* class ensures that the spatial associated with the shell is positioned and oriented correctly
* within the world.
*/ */
public class ShellControl extends AbstractControl { class ShellControl extends AbstractControl {
private final Shell shell; private final Shell shell;
private MapView view;
private final ClientGameLogic logic;
private final EffectMessage msg;
private boolean hasPlayedSound = false;
/** /**
* Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}. * Constructs a new ShipControl instance for the specified Battleship.
* The ship's orientation determines the axis of rotation, while its length influences
* the cycle duration and amplitude of the oscillation.
* *
* @param shell The {@code Shell} being visualized and controlled. * @param shell the Battleship object to control
*/ */
public ShellControl(Shell shell){ public ShellControl(Shell shell, ClientGameLogic clientGameLogic) {
super();
this.shell = shell; this.shell = shell;
this.logic = clientGameLogic;
this.msg = shell.getMsg();
} }
/** /**
* Updates the 3D position and rotation of the shell based on its current state. * Initializes ShellControl with a shell, map view, and game logic.
* Converts map coordinates to world coordinates and applies the shell's orientation. * @param shell the shell to be controlled
* @param view the map view to display the shell
* @param clientGameLogic the game logic instance
*/
public ShellControl(Shell shell, MapView view, ClientGameLogic clientGameLogic) {
this.shell = shell;
this.view = view;
this.logic = clientGameLogic;
this.msg = shell.getMsg();
}
/**
* 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.
* *
* @param tpf Time per frame, representing the elapsed time since the last update. * @param tpf time per frame (in seconds), used to calculate the new pitch angle
*/ */
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
Vector3f pos = shell.getPosition();
Vector3f fixed = mapToWorldCord(pos.x, pos.z); if (spatial == null)
fixed.setY(pos.y); return;
spatial.setLocalTranslation(fixed); if (shell.isFinished() && !hasPlayedSound) {
spatial.setLocalRotation(shell.getRotation()); if (!msg.getShot().isHit())
spatial.rotate(PI/2,0,0); logic.playSound(Sound.SPLASH);
else if (msg.getDestroyedShip() == null)
logic.playSound(Sound.EXPLOSION);
else
logic.playSound(Sound.DESTROYED_SHIP);
hasPlayedSound = true;
}
if (view == null) {
shell.updatePosition(tpf);
spatial.setLocalTranslation(shell.getCurrentPosition());
} else {
shell.updatePosition(tpf);
Position pos2d = view.modelToView(shell.getCurrentPosition().x - 0.3f, shell.getCurrentPosition().z - 0.3f);
spatial.setLocalTranslation(pos2d.getY(), pos2d.getX(), 0);
}
} }
/**
* Called during rendering, but no operations are needed as this control only affects spatial transformation.
* @param rm the RenderManager rendering the spatial
* @param vp the ViewPort being rendered
*/
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-specific behavior required for this control // No rendering logic is needed for this control
} }
} }

View File

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

View File

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

View File

@@ -5,20 +5,14 @@
// (c) Mark Minas (mark.minas@unibw.de) // (c) Mark Minas (mark.minas@unibw.de)
//////////////////////////////////////// ////////////////////////////////////////
package pp.battleship.client.clienthost; package pp.battleship.client.server;
import com.jme3.network.ConnectionListener; import com.jme3.network.*;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer; import com.jme3.network.serializing.Serializer;
import pp.battleship.BattleshipConfig; import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player; import pp.battleship.game.server.Player;
import pp.battleship.game.server.ServerGameLogic; import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender; import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientMessage; import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage; import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage; import pp.battleship.message.client.ShootMessage;
@@ -42,14 +36,13 @@
/** /**
* Server implementing the visitor pattern as MessageReceiver for ClientMessages * Server implementing the visitor pattern as MessageReceiver for ClientMessages
*/ */
public class BattleshipServerClient implements MessageListener<HostedConnection>, ConnectionListener, ServerSender { public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
private static final Logger LOGGER = System.getLogger(BattleshipServerClient.class.getName()); private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
private static final File CONFIG_FILE = new File("server.properties"); private static final File CONFIG_FILE = new File("server.properties");
private final BattleshipConfig config = new BattleshipConfig();
private Server myServer; private Server myServer;
private final ServerGameLogic logic; private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessageClient> pendingMessages = new LinkedBlockingQueue<>(); private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
static { static {
// Configure logging // Configure logging
@@ -63,27 +56,20 @@ public class BattleshipServerClient implements MessageListener<HostedConnection>
} }
} }
/** /**
* Creates the server and reads the configuration from the specified file. * Creates the server.
* Initializes the game logic and sets up logging.
*/ */
public BattleshipServerClient() { public BattleshipServer() {
BattleshipConfig config = new BattleshipConfig();
config.readFromIfExists(CONFIG_FILE); config.readFromIfExists(CONFIG_FILE);
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config); logic = new ServerGameLogic(this, config);
} }
/** /**
* Checks if the server is ready. * Starts the server on the given port and continuously processes incoming messages.
* * @param port the port to start the server on
* @return true if the server is running, false otherwise
*/
public boolean isReady() {
return myServer != null && myServer.isRunning();
}
/**
* Starts the server and continuously processes incoming messages.
*/ */
public void run(int port) { public void run(int port) {
startServer(port); startServer(port);
@@ -92,7 +78,8 @@ public void run(int port) {
} }
/** /**
* Starts the server by creating a network server on the specified port. * Initializes and starts the server on the specified port, handling exceptions if the server fails to start.
* @param port the port to start the server on
*/ */
private void startServer(int port) { private void startServer(int port) {
try { try {
@@ -110,7 +97,7 @@ private void startServer(int port) {
} }
/** /**
* Processes the next message in the queue. * Retrieves and processes the next message from the queue, handling interruptions during the wait.
*/ */
private void processNextMessage() { private void processNextMessage() {
try { try {
@@ -123,7 +110,7 @@ private void processNextMessage() {
} }
/** /**
* Registers all serializable message classes for network transmission. * Processes the next message from the queue, handling interruptions during message retrieval.
*/ */
private void initializeSerializables() { private void initializeSerializables() {
Serializer.registerClass(GameDetails.class); Serializer.registerClass(GameDetails.class);
@@ -131,35 +118,48 @@ private void initializeSerializables() {
Serializer.registerClass(MapMessage.class); Serializer.registerClass(MapMessage.class);
Serializer.registerClass(ShootMessage.class); Serializer.registerClass(ShootMessage.class);
Serializer.registerClass(EffectMessage.class); Serializer.registerClass(EffectMessage.class);
Serializer.registerClass(AnimationFinishedMessage.class);
Serializer.registerClass(Battleship.class); Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class); Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class); Serializer.registerClass(Shot.class);
} }
/** /**
* Registers the message and connection listeners for the server. * Registers message and connection listeners for the server.
*/ */
private void registerListeners() { private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class); myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class); myServer.addMessageListener(this, ShootMessage.class);
myServer.addMessageListener(this, AnimationFinishedMessage.class);
myServer.addConnectionListener(this); myServer.addConnectionListener(this);
} }
/**
* Handles received messages, logging the source and adding client messages to the pending queue.
* @param source the connection the message was received from
* @param message the received message
*/
@Override @Override
public void messageReceived(HostedConnection source, Message message) { public void messageReceived(HostedConnection source, Message message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
if (message instanceof ClientMessage clientMessage) if (message instanceof ClientMessage clientMessage)
pendingMessages.add(new ReceivedMessageClient(clientMessage, source.getId())); pendingMessages.add(new ReceivedMessage(clientMessage, source.getId()));
} }
/**
* Handles a new connection by logging it and adding a new player to the game logic.
* @param server the server receiving the connection
* @param hostedConnection the newly added connection
*/
@Override @Override
public void connectionAdded(Server server, HostedConnection hostedConnection) { public void connectionAdded(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
logic.addPlayer(hostedConnection.getId()); logic.addPlayer(hostedConnection.getId());
} }
/**
* Handles the removal of a connection by logging it, checking if it belongs to an active player, and exiting if necessary.
* @param server the server losing the connection
* @param hostedConnection the removed connection
*/
@Override @Override
public void connectionRemoved(Server server, HostedConnection hostedConnection) { public void connectionRemoved(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
@@ -173,9 +173,8 @@ public void connectionRemoved(Server server, HostedConnection hostedConnection)
} }
/** /**
* Shuts down the server and closes all active connections. * Closes all client connections and terminates the server with the given exit value.
* * @param exitValue the exit code to terminate the application with
* @param exitValue the exit code to terminate the program with
*/ */
private void exit(int exitValue) { //NON-NLS private void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS LOGGER.log(Level.INFO, "close request"); //NON-NLS

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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 it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,64 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 13.03.2012 15:35:28
newmtl metall
Ns 31.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2431 0.2431 0.2431
Kd 0.2431 0.2431 0.2431
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl 13922_StatenIslandFerry
Ns 50.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.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
map_Ka 13922_StatenIslandFerry_diffuse.jpg
map_Kd 13922_StatenIslandFerry_diffuse.jpg
newmtl metall2
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2118 0.2824 0.3451
Kd 0.2118 0.2824 0.3451
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl boat
Ns 34.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6588 0.3059 0.1294
Kd 0.6588 0.3059 0.1294
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl white
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.9686 0.9686 0.9686
Kd 0.9686 0.9686 0.9686
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View File

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

View File

@@ -1,15 +0,0 @@
# 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
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 ship3_color.jpg
map_Kd ship3_color.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

View File

@@ -0,0 +1,28 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 08.06.2011 15:26:00
newmtl _10634_SpeedBoat_v01_LOD310634_SpeedBoat_v01
Ns 53.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.2000 0.2000 0.2000
Ke 0.0000 0.0000 0.0000
map_Ka 10634_SpeedBoat_v01.jpg
map_Kd 10634_SpeedBoat_v01.jpg
newmtl glass
Ns 80.0000
Ni 1.5000
d 0.2000
Tr 0.8000
Tf 0.2000 0.2000 0.2000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.5000 0.5000 0.5000
Ke 0.0000 0.0000 0.0000

View File

@@ -0,0 +1,3 @@
based on:
https://free3d.com/3d-model/speedboat-v01--840133.html
License: Free Personal Use Only

Binary file not shown.

View File

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

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,64 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 13.03.2012 15:35:28
newmtl metall
Ns 31.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2431 0.2431 0.2431
Kd 0.2431 0.2431 0.2431
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl 13922_StatenIslandFerry
Ns 50.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.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
map_Ka 13922_StatenIslandFerry_diffuse.jpg
map_Kd 13922_StatenIslandFerry_diffuse.jpg
newmtl metall2
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2118 0.2824 0.3451
Kd 0.2118 0.2824 0.3451
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl boat
Ns 34.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6588 0.3059 0.1294
Kd 0.6588 0.3059 0.1294
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl white
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.9686 0.9686 0.9686
Kd 0.9686 0.9686 0.9686
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

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

View File

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

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