32 Commits

Author SHA1 Message Date
Yvonne Schmidt
6c8da2f4a0 fixed crash when starting animation in selfhost 2024-10-18 14:24:06 +02:00
Yvonne Schmidt
446cb43abb reworked the shell animation 2024-10-18 00:30:28 +02:00
Yvonne Schmidt
47610d9e11 added groundwork for shell animation 2024-10-14 05:31:20 +02:00
Yvonne Schmidt
563784dbab Merge remote-tracking branch 'origin/b_schmidt_yvonne' into b_schmidt_yvonne
# Conflicts:
#	Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java
2024-10-14 00:45:01 +02:00
Yvonne Schmidt
0e5ffcc501 added very basic splash effect 2024-10-13 18:59:18 +02:00
Yvonne Schmidt
1288d6d1ca added missing documentation 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
520b98f693 added a sinking effect for damaged ships 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
8e02c3919e added fire and explosion effect, when a ship is hit 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
4df5a1a7d1 added the option to host the server on a client 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
3bdc9c20dc added missing comments to public void setVolume and
public void update. Also removed unused import statements.
2024-10-13 13:19:01 +02:00
Yvonne Schmidt
f541f3edc0 added background music 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
c26d7b5422 removed superfluous models and cleaned up code 2024-10-13 13:19:01 +02:00
Yvonne Schmidt
2e294896d8 added new models and fixed loading of 3d interface 2024-10-13 13:19:00 +02:00
Yvonne Schmidt
f02a3107a0 added battlecruiser model 2024-10-13 13:18:59 +02:00
Yvonne Schmidt
141d817421 added u-boat model 2024-10-13 13:18:59 +02:00
Yvonne Schmidt
fe1bf7746b fixed model export for small Boat 2024-10-13 13:18:59 +02:00
Yvonne Schmidt
2c7820491a added small boat model 2024-10-13 13:18:59 +02:00
Yvonne Schmidt
2dd70c10b5 Server checks imported map
for invalid placements
2024-10-13 13:18:59 +02:00
Yvonne Schmidt
6475b63a22 fixed incorrect remove function and corrected a mistake during the initiation of a game over state 2024-10-13 13:18:58 +02:00
Yvonne Schmidt
ae41ec4007 added a sinking effect for damaged ships 2024-10-11 01:33:52 +02:00
Yvonne Schmidt
b9309b6f5f added fire and explosion effect, when a ship is hit 2024-10-10 23:31:32 +02:00
Yvonne Schmidt
ae5ba034fe added the option to host the server on a client 2024-10-10 15:48:14 +02:00
Yvonne Schmidt
682da199e8 added missing comments to public void setVolume and
public void update. Also removed unused import statements.
2024-10-08 22:45:28 +02:00
Yvonne Schmidt
8a7446d81c added background music 2024-10-08 22:17:29 +02:00
Yvonne Schmidt
cb57187f98 removed superfluous models and cleaned up code 2024-10-08 18:43:35 +02:00
Yvonne Schmidt
958f4f6a13 added new models and fixed loading of 3d interface 2024-10-08 18:37:46 +02:00
Yvonne Schmidt
af08a84dc3 added battlecruiser model 2024-10-08 00:55:35 +02:00
Yvonne Schmidt
53df6011db added u-boat model 2024-10-08 00:43:02 +02:00
Yvonne Schmidt
4bc3b3dc33 fixed model export for small Boat 2024-10-07 19:58:08 +02:00
Yvonne Schmidt
cf780f0995 added small boat model 2024-10-07 18:58:24 +02:00
Yvonne Schmidt
05618c0793 Server checks imported map
for invalid placements
2024-10-07 01:23:52 +02:00
Yvonne Schmidt
3ee095215a Assignment 7 ready 2024-10-06 14:52:33 +02:00
230 changed files with 2876308 additions and 147698 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,16 +7,18 @@ description = 'Battleship Client'
dependencies {
implementation project(":jme-common")
implementation project(":battleship:model")
implementation 'org.jmonkeyengine:jme3-core:3.6.0-stable'
implementation 'org.jmonkeyengine:jme3-effects:3.6.0-stable'
implementation libs.jme3.desktop
implementation libs.jme3.effects
implementation project(path: ':battleship:server')
implementation project(path: ':battleship:server')
runtimeOnly libs.jme3.awt.dialogs
runtimeOnly libs.jme3.plugins
runtimeOnly libs.jme3.jogg
runtimeOnly libs.jme3.testdata
}
application {

View File

@@ -23,9 +23,9 @@ import com.simsilica.lemur.style.BaseStyles;
import pp.battleship.client.gui.BattleAppState;
import pp.battleship.client.gui.EditorAppState;
import pp.battleship.client.gui.SeaAppState;
import pp.battleship.client.GameMusic;
import pp.battleship.game.client.BattleshipClient;
import pp.battleship.game.client.ClientGameLogic;
import pp.battleship.game.client.GameMusic;
import pp.battleship.game.client.ServerConnection;
import pp.battleship.game.singlemode.BattleshipClientConfig;
import pp.battleship.game.singlemode.ServerConnectionMockup;
@@ -266,24 +266,15 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
flyCam.setEnabled(false);
stateManager.detach(stateManager.getState(StatsAppState.class));
stateManager.detach(stateManager.getState(DebugKeysAppState.class));
atttachGameMusic();
attachGameSound();
attachGameMusic();
stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState());
}
private void atttachGameMusic() {
final GameMusic gameSound = new GameMusic();
gameSound.setEnabled(GameMusic.enabledInPreferences());
stateManager.attach(gameSound);
}
/**
* Attaches the game sound state and sets its initial enabled state.
*
*/
private void attachGameSound() {
final GameSound gameSound = new GameSound();
@@ -292,6 +283,15 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
stateManager.attach(gameSound);
}
/**
* Attaches the music state and sets its initial enabled state.
*/
private void attachGameMusic() {
final GameMusic gameMusic = new GameMusic();
gameMusic.setEnabled(GameMusic.enabledInPreferences());
stateManager.attach(gameMusic);
}
/**
* Updates the application state every frame.
* This method is called once per frame during the game loop.

View File

@@ -12,8 +12,6 @@ import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import pp.battleship.game.client.ClientGameLogic;
/**
* Abstract class representing a state in the Battleship game.
* Extends the AbstractAppState from jMonkeyEngine to manage state behavior.

View File

@@ -1,4 +1,5 @@
package pp.battleship.game.client;
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
@@ -8,19 +9,25 @@ import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import static pp.util.PreferencesUtils.getPreferences;
public class GameMusic extends AbstractAppState{
/**
* An application state that plays music.
*/
public class GameMusic extends AbstractAppState {
private static final Logger LOGGER = System.getLogger(GameMusic.class.getName());
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
private static final String ENABLED_PREF = "enabled"; //NON-NLS
private static final String VOLUME_PREF = "volume"; //NON-NLS
private AudioNode music;
private AudioNode battleMusic;
private AudioNode menuMusicModern;
private AudioNode menuMusicTraditional;
/**
* Checks if sound is enabled in the preferences.
@@ -30,19 +37,48 @@ public class GameMusic extends AbstractAppState{
public static boolean enabledInPreferences() {
return PREFERENCES.getBoolean(ENABLED_PREF, true);
}
/**
* Checks if sound is enabled in the preferences.
* Checks enabled volume in preferences.
*
* @return {@code float} if a volumePreference is set, the volume is set to the Value in PREFERENCES,
* {@code 0.25f} if no volumePreference is set in PREFERENCES, the Volume is set to a default of 0.25f
*
*/
public static float volumeInPreferences() {
return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
public static float volumePreference() {
return PREFERENCES.getFloat(VOLUME_PREF, 0.25f);
}
/**
* Initializes the sound effects for the game.
* Overrides {@link com.jme3.app.state.AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application)}
* Toggles the game sound on or off.
*/
public void toggleSound() {
setEnabled(!isEnabled());
}
/**
* 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 (isEnabled() == enabled) return;
else if (!isEnabled() && enabled) {
if (menuMusicModern != null) menuMusicModern.play();
} else if (isEnabled() && !enabled) {
if (menuMusicModern != null) menuMusicModern.stop();
}
super.setEnabled(enabled);
LOGGER.log(Level.INFO, "Music enabled: {0}", enabled); //NON-NLS
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
}
/**
* Initializes the music.
* Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
*
* @param stateManager The state manager
* @param app The application
@@ -50,23 +86,24 @@ public class GameMusic extends AbstractAppState{
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
music = loadSoundb(app, "Sound/Music/My_heart_will_go_on.ogg");
setVolume(volumeInPreferences());
music.setLooping(true);
if (isEnabled() && music != null) {
music.play();
menuMusicModern =loadSound(app, "Sound/BackgroundMusic/menu-music-modern.ogg");
setVolume(volumePreference());
menuMusicModern.setLooping(true);
if (isEnabled() && menuMusicModern != null) {
menuMusicModern.play();
}
}
/**
* Loads a sound from the specified file.
* Loads the music from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
private AudioNode loadSoundb(Application app, String name) {
try{
private AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);
sound.setPositional(false);
@@ -78,37 +115,15 @@ public class GameMusic extends AbstractAppState{
return null;
}
/**
* Sets the enabled state of this AppState.
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
* Sets the vol param to the level set in PREFERENCES
*
* @param vol Volume level of the music as indicated by the Volume control Slider
*
* @param enabled {@code true} to enable the AppState, {@code false} to disable it.
*/
@Override
public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) return;
else if(!isEnabled() && enabled) {
if (music != null) music.play();
} else if (isEnabled() && !enabled) {
if (music != null) music.stop();
}
super.setEnabled(enabled);
LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
}
/**
* Toggles the game sound on or off.
*/
public void toggleSound() {
setEnabled(!isEnabled());
}
public void setVolume(float vol){
music.setVolume(vol);
menuMusicModern.setVolume(vol);
PREFERENCES.putFloat(VOLUME_PREF, vol);
}
}
}

View File

@@ -87,7 +87,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
public AudioNode loadSound(Application app, String name) {
private AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);

View File

@@ -11,7 +11,7 @@ import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.style.ElementId;
import pp.battleship.game.client.GameMusic;
import pp.battleship.client.gui.VolumeControl;
import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel;
import pp.dialog.TextInputDialog;
@@ -31,25 +31,30 @@ import static pp.util.PreferencesUtils.getPreferences;
class Menu extends Dialog {
private static final Preferences PREFERENCES = getPreferences(Menu.class);
private static final String LAST_PATH = "last.file.path";
// private final VolumeSlider slider;
private final BattleshipApp app;
private final Button loadButton = new Button(lookup("menu.map.load"));
private final Button saveButton = new Button(lookup("menu.map.save"));
private final VolumeControl volumeSlider;
/**
* Constructs the Menu dialog for the Battleship application.
*
* @param app the BattleshipApp instance
*/
public Menu(BattleshipApp app) {
super(app.getDialogManager());
this.app = app;
addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS
addChild(new Checkbox(lookup("menu.sound-enabled"),
new StateCheckboxModel(app, GameSound.class)));
// slider = new VolumeSlider(app.getStateManager().getState(GameMusic.class));
addChild(new Label(lookup("battleship.name"), new ElementId("header")));//NON-NLS
addChild(new Checkbox(lookup("menu.sound-enabled"), new StateCheckboxModel(app, GameSound.class)));
addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class)));
volumeSlider = new VolumeControl(app.getStateManager().getState(GameMusic.class));
addChild(volumeSlider);
addChild(loadButton)
.addClickCommands(s -> ifTopDialog(this::loadDialog));
addChild(saveButton)
@@ -59,11 +64,6 @@ class Menu extends Dialog {
addChild(new Button(lookup("menu.quit")))
.addClickCommands(s -> ifTopDialog(app::closeApp));
update();
addChild(new Checkbox(lookup("menu.sound-enabled"), new StateCheckboxModel(app, GameSound.class)));
addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class)));
// addChild(slider);
}
/**
@@ -75,13 +75,14 @@ class Menu extends Dialog {
saveButton.setEnabled(app.getGameLogic().maySaveMap());
}
/* @Override
/**
* Updates the position of the volume control slider.
*/
@Override
public void update(float delta) {
slider.update();
volumeSlider.update();
}
*/
/**
* As an escape action, this method closes the menu if it is the top dialog.

View File

@@ -7,12 +7,12 @@
package pp.battleship.client;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.battleship.server.BattleshipServer;
import pp.battleship.client.server.BattleshipSelfhostServer;
import pp.dialog.Dialog;
import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog;
@@ -35,9 +35,9 @@ class NetworkDialog extends SimpleDialog {
private final NetworkSupport network;
private final TextField host = new TextField(LOCALHOST);
private final TextField port = new TextField(DEFAULT_PORT);
private static final Checkbox HOST = new Checkbox(lookup("server.host"));
private String hostname;
private int portNumber;
private final Button serverButton = new Button(lookup("client.server-start"));
private Future<Object> connectionFuture;
private Dialog progressDialog;
@@ -59,6 +59,7 @@ class NetworkDialog extends SimpleDialog {
input.addChild(host, 1);
input.addChild(new Label(lookup("port.number") + ": "));
input.addChild(port, 1);
input.addChild(HOST).addClickCommands(s -> ifTopDialog(this::startClientServer));
DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog"))
@@ -68,11 +69,7 @@ class NetworkDialog extends SimpleDialog {
.setOkClose(false)
.setNoClose(false)
.build(this);
addChild(serverButton).addClickCommands(s -> ifTopDialog(this::startServerInThread));
}
//Add the button to start the sever
/**
* Handles the action for the connect button in the connection dialog.
@@ -157,21 +154,22 @@ class NetworkDialog extends SimpleDialog {
network.getApp().errorDialog(lookup("server.connection.failed"));
network.getApp().setInfoText(e.getLocalizedMessage());
}
/**
* Starts the server in a separate thread.
* Starts the server of the client in a new thread and catches connectivity issues.
*/
private void startServerInThread() {
serverButton.setEnabled(false);
private void startClientServer() {
HOST.setEnabled(false);
Thread serverThread = new Thread(() -> {
try {
BattleshipServer.main(null);
BattleshipSelfhostServer.main(null);
} catch (Exception e) {
serverButton.setEnabled(true);
HOST.setEnabled(true);
LOGGER.log(Level.ERROR, "Server could not be started", e);
network.getApp().errorDialog("Could not start server: " + e.getMessage());
}
});
serverThread.start();
}
}

View File

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

View File

@@ -7,16 +7,21 @@
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.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import pp.battleship.model.Battleship;
import pp.battleship.model.Shell;
import pp.battleship.model.Shot;
import pp.util.Position;
import static com.jme3.material.Materials.UNSHADED;
/**
* Synchronizes the visual representation of the ship map with the game model.
* It handles the rendering of ships and shots on the map view, updating the view
@@ -38,7 +43,6 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
// The MapView associated with this synchronizer
private final MapView view;
private Shell shell;
/**
* Constructs a new MapViewSynchronizer for the given MapView.
@@ -125,16 +129,25 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) {
return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH);
}
public void update(float deltaTime) {
if (shell != null) {
shell.updatePosition(deltaTime);
drawShell(shell.getCurrentPosition());
}
}
private void drawShell(Vector3f position){
//TODO implement
/**
* Creates and returns a Spatial representation of the given {@code Shell} object
* for 2D visualization in the game. The shell is represented as a circle.
*
* @param shell The {@code Shell} object to be visualized.
* @return A {@code Spatial} object representing the shell on the map.
*/
@Override
public Spatial visit(Shell shell) {
final ColorRGBA color = ColorRGBA.Black;
Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f));
Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
mat.setColor("Color", color);
ellipse.setMaterial(mat);
ellipse.addControl(new Shell2DControl(view, shell));
return ellipse;
}
}

View File

@@ -1,285 +0,0 @@
package pp.battleship.client.gui;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import pp.battleship.client.BattleshipApp;
/**
* Factory class responsible for creating particle effects used in the game.
* This centralizes the creation of various types of particle emitters.
*/
public class ParticleEffectFactory {
private static final int COUNT_FACTOR = 1;
private static final float COUNT_FACTOR_F = 1f;
private static final boolean POINT_SPRITE = true;
private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle;
private final BattleshipApp app;
ParticleEffectFactory(BattleshipApp app) {
this.app = app;
}
/**
* Creates a flame particle emitter.
*
* @return a configured flame particle emitter
*/
ParticleEmitter createFlame() {
ParticleEmitter flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR);
flame.setSelectRandomImage(true);
flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F)));
flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
flame.setStartSize(0.1f);
flame.setEndSize(0.5f);
flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
flame.setParticlesPerSec(0);
flame.setGravity(0, -5, 0);
flame.setLowLife(.4f);
flame.setHighLife(.5f);
flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0));
flame.getParticleInfluencer().setVelocityVariation(1f);
flame.setImagesX(2);
flame.setImagesY(2);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
flame.setMaterial(mat);
return flame;
}
/**
* Creates a flash particle emitter.
*
* @return a configured flash particle emitter
*/
ParticleEmitter createFlash() {
ParticleEmitter flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR);
flash.setSelectRandomImage(true);
flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F));
flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
flash.setStartSize(.1f);
flash.setEndSize(0.5f);
flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
flash.setParticlesPerSec(0);
flash.setGravity(0, 0, 0);
flash.setLowLife(.2f);
flash.setHighLife(.2f);
flash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 5f, 0));
flash.getParticleInfluencer().setVelocityVariation(1);
flash.setImagesX(2);
flash.setImagesY(2);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
flash.setMaterial(mat);
return flash;
}
/**
* Creates a round spark particle emitter.
*
* @return a configured round spark particle emitter
*/
ParticleEmitter createRoundSpark() {
ParticleEmitter roundSpark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR);
roundSpark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F)));
roundSpark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F));
roundSpark.setStartSize(0.2f);
roundSpark.setEndSize(0.8f);
roundSpark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
roundSpark.setParticlesPerSec(0);
roundSpark.setGravity(0, -.5f, 0);
roundSpark.setLowLife(1.8f);
roundSpark.setHighLife(2f);
roundSpark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0));
roundSpark.getParticleInfluencer().setVelocityVariation(.5f);
roundSpark.setImagesX(1);
roundSpark.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
roundSpark.setMaterial(mat);
return roundSpark;
}
/**
* Creates a spark particle emitter.
*
* @return a configured spark particle emitter
*/
ParticleEmitter createSpark() {
ParticleEmitter spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR);
spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
spark.setStartSize(.5f);
spark.setEndSize(.5f);
spark.setFacingVelocity(true);
spark.setParticlesPerSec(0);
spark.setGravity(0, 5, 0);
spark.setLowLife(1.1f);
spark.setHighLife(1.5f);
spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0));
spark.getParticleInfluencer().setVelocityVariation(1);
spark.setImagesX(1);
spark.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png"));
spark.setMaterial(mat);
return spark;
}
/**
* Creates a smoke trail particle emitter.
*
* @return a configured smoke trail particle emitter
*/
ParticleEmitter createSmokeTrail() {
ParticleEmitter smokeTrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR);
smokeTrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
smokeTrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
smokeTrail.setStartSize(.2f);
smokeTrail.setEndSize(1f);
smokeTrail.setFacingVelocity(true);
smokeTrail.setParticlesPerSec(0);
smokeTrail.setGravity(0, 1, 0);
smokeTrail.setLowLife(.4f);
smokeTrail.setHighLife(.5f);
smokeTrail.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 12, 0));
smokeTrail.getParticleInfluencer().setVelocityVariation(1);
smokeTrail.setImagesX(1);
smokeTrail.setImagesY(3);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png"));
smokeTrail.setMaterial(mat);
return smokeTrail;
}
/**
* Creates a debris particle emitter.
*
* @return a configured debris particle emitter
*/
ParticleEmitter createDebris() {
ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR);
debris.setSelectRandomImage(true);
debris.setRandomAngle(true);
debris.setRotateSpeed(FastMath.TWO_PI * 4);
debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F));
debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f));
debris.setStartSize(.10f);
debris.setEndSize(.15f);
debris.setParticlesPerSec(0);
debris.setGravity(0, 12f, 0);
debris.setLowLife(1.4f);
debris.setHighLife(1.5f);
debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 15, 0));
debris.getParticleInfluencer().setVelocityVariation(.60f);
debris.setImagesX(3);
debris.setImagesY(3);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
debris.setMaterial(mat);
return debris;
}
/**
* Creates a shockwave particle emitter.
*
* @return a configured shockwave particle emitter
*/
ParticleEmitter createShockwave() {
ParticleEmitter shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
shockwave.setFaceNormal(Vector3f.UNIT_Y);
shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F));
shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f));
shockwave.setStartSize(0f);
shockwave.setEndSize(3f);
shockwave.setParticlesPerSec(0);
shockwave.setGravity(0, 0, 0);
shockwave.setLowLife(0.5f);
shockwave.setHighLife(0.5f);
shockwave.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
shockwave.getParticleInfluencer().setVelocityVariation(0f);
shockwave.setImagesX(1);
shockwave.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png"));
shockwave.setMaterial(mat);
return shockwave;
}
/**
* Creates a moving smoke emitter.
*
* @return a configured smoke emitter
*/
ParticleEmitter createMovingSmokeEmitter() {
ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300);
smokeEmitter.setGravity(0, 0, 0);
smokeEmitter.getParticleInfluencer().setVelocityVariation(1);
smokeEmitter.setLowLife(1);
smokeEmitter.setHighLife(1);
smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
smokeEmitter.setMaterial(mat);
return smokeEmitter;
}
/**
* Creates a one-time water splash particle emitter.
*
* @return a configured one-time water splash particle emitter
*/
public ParticleEmitter createWaterSplash() {
// Create a new particle emitter for the splash effect
ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30);
// Set the shape of the emitter, making particles emit from a point or small area
waterSplash.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.2f));
// Start and end colors for water (blue, fading out)
waterSplash.setStartColor(new ColorRGBA(0.4f, 0.4f, 1f, 1f)); // Light blue at start
waterSplash.setEndColor(new ColorRGBA(0.4f, 0.4f, 1f, 0f)); // Transparent at the end
// Particle size: small at start, larger before fading out
waterSplash.setStartSize(0.1f);
waterSplash.setEndSize(0.3f);
// Particle lifespan (how long particles live)
waterSplash.setLowLife(0.5f);
waterSplash.setHighLife(1f);
// Gravity: Pull the water particles downwards
waterSplash.setGravity(0, -9.81f, 0); // Earth's gravity simulation
// Velocity: Give particles an initial burst upward (simulates splash)
waterSplash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0));
waterSplash.getParticleInfluencer().setVelocityVariation(0.6f); // Add randomness to splash
// Set how many particles are emitted per second (0 to emit all particles at once)
waterSplash.setParticlesPerSec(0);
// Load a texture for the water splash (assuming a texture exists at this path)
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Splash/splash.png"));
waterSplash.setMaterial(mat);
return waterSplash;
}
}

View File

@@ -8,9 +8,13 @@
package pp.battleship.client.gui;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
@@ -20,27 +24,27 @@ import com.jme3.scene.shape.Cylinder;
import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot;
import static java.util.Objects.requireNonNull;
import static pp.util.FloatMath.HALF_PI;
import static pp.util.FloatMath.PI;
/**
* The {@code SeaSynchronizer} class is responsible for synchronizing the graphical
* representation of the ships and shots on the sea map with the underlying data model.
* representation of the ships and shots on the sea map with the underlying model.
* It extends the {@link ShipMapSynchronizer} to provide specific synchronization
* logic for the sea map.
*/
class SeaSynchronizer extends ShipMapSynchronizer {
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
private static final String UX23 = "Models/UX23/UX23.j3o";
private static final String BOJE = "Models/Boje/Boje.j3o";
private static final String ALIENSHIP = "Models/Alienship/Alienship.j3o";
private static final String MARLOW66 = "Models/Marlow66/Marlow66.j3o";
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";//NON-NLS
private static final String SMALL_BOAT_MODEL = "Models/BoatSmall/12219_boat_v2_L2.j3o";
private static final String BATTLE_MODEL = "Models/Battle/Battle.j3o"; //NON-NLS
private static final String CV_MODEL = "Models/CV/CV.j3o"; //NON-NLS
private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md";
private static final String COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS
private static final String SHOT = "shot"; //NON-NLS
@@ -50,7 +54,273 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private final ShipMap map;
private final BattleshipApp app;
private final ParticleEffectFactory particleFactory;
private Shell shell;
private Spatial shellModel;
private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris,
shockwave;
private static final int COUNT_FACTOR = 1;
private static final float COUNT_FACTOR_F = 1f;
private static final boolean POINT_SPRITE = true;
private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle;
/**
* Creates a Flame texture with indefinite Lifetime
* @return flame texture
*/
private ParticleEmitter createFlame(){
flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR);
flame.setSelectRandomImage(true);
flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F)));
flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
flame.setStartSize(0.1f);
flame.setEndSize(0.5f);
flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
flame.setParticlesPerSec(0);
flame.setGravity(0, -5, 0);
flame.setLowLife(.4f);
flame.setHighLife(.5f);
flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0));
flame.getParticleInfluencer().setVelocityVariation(1f);
flame.setImagesX(2);
flame.setImagesY(2);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
flame.setMaterial(mat);
return flame;
}
/**
* Creates a Flash texture
* @return flash texture
*/
private ParticleEmitter createFlash(){
flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR);
flash.setSelectRandomImage(true);
flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F));
flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
flash.setStartSize(.1f);
flash.setEndSize(0.5f);
flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
flash.setParticlesPerSec(0);
flash.setGravity(0, 0, 0);
flash.setLowLife(.2f);
flash.setHighLife(.2f);
flash.getParticleInfluencer()
.setInitialVelocity(new Vector3f(0, 5f, 0));
flash.getParticleInfluencer().setVelocityVariation(1);
flash.setImagesX(2);
flash.setImagesY(2);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
flash.setMaterial(mat);
return flash;
}
/**
* Creates small spark particles, that dissipate into the air
* @return spark texture
*/
private ParticleEmitter createRoundSpark(){
roundspark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR);
roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F)));
roundspark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F));
roundspark.setStartSize(0.2f);
roundspark.setEndSize(0.8f);
roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
roundspark.setParticlesPerSec(0);
roundspark.setGravity(0, -.5f, 0);
roundspark.setLowLife(1.8f);
roundspark.setHighLife(2f);
roundspark.getParticleInfluencer()
.setInitialVelocity(new Vector3f(0, 3, 0));
roundspark.getParticleInfluencer().setVelocityVariation(.5f);
roundspark.setImagesX(1);
roundspark.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png"));
mat.setBoolean("PointSprite", POINT_SPRITE);
roundspark.setMaterial(mat);
return roundspark;
}
/**
* Creates small, thin smoke trails that enhance the flying debris
* @return crates a thin smoke trail texture
*/
private ParticleEmitter createSpark(){
spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR);
spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
spark.setStartSize(.5f);
spark.setEndSize(.5f);
spark.setFacingVelocity(true);
spark.setParticlesPerSec(0);
spark.setGravity(0, 5, 0);
spark.setLowLife(1.1f);
spark.setHighLife(1.5f);
spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0));
spark.getParticleInfluencer().setVelocityVariation(1);
spark.setImagesX(1);
spark.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png"));
spark.setMaterial(mat);
return spark;
}
/**
* Creates a dynamic smoke trail
* @return smoke texture
*/
private ParticleEmitter createSmokeTrail(){
smoketrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR);
smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
smoketrail.setStartSize(.2f);
smoketrail.setEndSize(1f);
// smoketrail.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
smoketrail.setFacingVelocity(true);
smoketrail.setParticlesPerSec(0);
smoketrail.setGravity(0, 1, 0);
smoketrail.setLowLife(.4f);
smoketrail.setHighLife(.5f);
smoketrail.getParticleInfluencer()
.setInitialVelocity(new Vector3f(0, 12, 0));
smoketrail.getParticleInfluencer().setVelocityVariation(1);
smoketrail.setImagesX(1);
smoketrail.setImagesY(3);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png"));
smoketrail.setMaterial(mat);
return smoketrail;
}
/**
* creates flying debris particles
* @return debris texture
*/
private ParticleEmitter createDebris(){
debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR);
debris.setSelectRandomImage(true);
debris.setRandomAngle(true);
debris.setRotateSpeed(FastMath.TWO_PI * 4);
debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F));
debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f));
debris.setStartSize(.10f);
debris.setEndSize(.15f);
// debris.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
debris.setParticlesPerSec(0);
debris.setGravity(0, 12f, 0);
debris.setLowLife(1.4f);
debris.setHighLife(1.5f);
debris.getParticleInfluencer()
.setInitialVelocity(new Vector3f(0, 15, 0));
debris.getParticleInfluencer().setVelocityVariation(.60f);
debris.setImagesX(3);
debris.setImagesY(3);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
debris.setMaterial(mat);
return debris;
}
/**
* Creates an expanding circular shockwave
* @return shockwave texture
*/
private ParticleEmitter createShockwave(){
shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
// shockwave.setRandomAngle(true);
shockwave.setFaceNormal(Vector3f.UNIT_Y);
shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F));
shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f));
shockwave.setStartSize(0f);
shockwave.setEndSize(3f);
shockwave.setParticlesPerSec(0);
shockwave.setGravity(0, 0, 0);
shockwave.setLowLife(0.5f);
shockwave.setHighLife(0.5f);
shockwave.getParticleInfluencer()
.setInitialVelocity(new Vector3f(0, 0, 0));
shockwave.getParticleInfluencer().setVelocityVariation(0f);
shockwave.setImagesX(1);
shockwave.setImagesY(1);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png"));
shockwave.setMaterial(mat);
return shockwave;
}
/**
* Creates an animated smoke column.
* @return moving smoke texture
*/
private ParticleEmitter createMovingSmokeEmitter() {
ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300);
smokeEmitter.setGravity(0, 0, 0);
smokeEmitter.getParticleInfluencer().setVelocityVariation(1);
smokeEmitter.setLowLife(1);
smokeEmitter.setHighLife(1);
smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames
// Set the material for the emitter
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
smokeEmitter.setMaterial(mat);
return smokeEmitter;
}
/**
* Creates a small burst of bubbles.
*
* @return bubbling water texture.
*/
public ParticleEmitter createWaterSplash() {
ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30);
waterSplash.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.2f));
waterSplash.setStartColor(new ColorRGBA(0.4f, 0.4f, 1f, 1f)); // Light blue at start
waterSplash.setEndColor(new ColorRGBA(0.4f, 0.4f, 1f, 0f)); // Transparent at the end
waterSplash.setStartSize(0.05f);
waterSplash.setEndSize(0.1f);
waterSplash.setLowLife(0.5f);
waterSplash.setHighLife(1f);
waterSplash.setGravity(0, 0, 0); // No gravity to simulate a horizontal water splash
waterSplash.getParticleInfluencer().setInitialVelocity(new Vector3f(1f, 0f, 1f)); // Horizontal spread
waterSplash.getParticleInfluencer().setVelocityVariation(1f); // Add randomness to splash
waterSplash.setParticlesPerSec(0);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Splash/splash.png"));
waterSplash.setMaterial(mat);
return waterSplash;
}
/**
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
@@ -63,7 +333,6 @@ class SeaSynchronizer extends ShipMapSynchronizer {
super(app.getGameLogic().getOwnMap(), root);
this.app = app;
this.map = map;
this.particleFactory = new ParticleEffectFactory(app);
addExisting();
}
@@ -80,7 +349,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
return shot.isHit() ? handleHit(shot) : handleMiss(shot);
}
/**
/**
* Handles a hit by attaching its representation to the node that
* contains the ship model as a child so that it moves with the ship.
*
@@ -98,14 +367,14 @@ class SeaSynchronizer extends ShipMapSynchronizer {
Node hitEffectNode = new Node("HitEffectNode");
// Create particle effects
ParticleEmitter flame = particleFactory.createFlame();
ParticleEmitter flash = particleFactory.createFlash();
ParticleEmitter spark = particleFactory.createSpark();
ParticleEmitter roundSpark = particleFactory.createRoundSpark();
ParticleEmitter smokeTrail = particleFactory.createSmokeTrail();
ParticleEmitter debris = particleFactory.createDebris();
ParticleEmitter shockwave = particleFactory.createShockwave();
ParticleEmitter movingSmoke = particleFactory.createMovingSmokeEmitter();
ParticleEmitter flame = createFlame();
ParticleEmitter flash = createFlash();
ParticleEmitter spark = createSpark();
ParticleEmitter roundSpark = createRoundSpark();
ParticleEmitter smokeTrail = createSmokeTrail();
ParticleEmitter debris = createDebris();
ParticleEmitter shockwave = createShockwave();
ParticleEmitter movingSmoke = createMovingSmokeEmitter(); // New moving smoke emitter
// Attach all effects to the hitEffectNode
hitEffectNode.attachChild(flame);
@@ -133,14 +402,29 @@ class SeaSynchronizer extends ShipMapSynchronizer {
shockwave.emitAllParticles();
flame.emitAllParticles();
roundSpark.emitAllParticles();
if (ship.isDestroyed()) {
// Add ShipSinkingControl to the shipNode
ShipSinkingControl sinkingControl = new ShipSinkingControl(2f, 5f, -5f, 60f);
shipNode.addControl(sinkingControl);
sinkingControl.startSinking(); // Start the sinking process
}
return null;
}
/**
* Handles a miss by representing it with a blue cylinder
* and attaching a water splash effect to it.
* @param shot the shot to be processed
* @return a Spatial simulating a miss with water splash effect
*/
private Spatial handleMiss(Shot shot) {
Node shotNode = new Node("ShotNode");
Geometry shotCylinder = createCylinder(shot);
shotNode.attachChild(shotCylinder);
ParticleEmitter waterSplash = particleFactory.createWaterSplash();
ParticleEmitter waterSplash = createWaterSplash();
waterSplash.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
shotNode.attachChild(waterSplash);
waterSplash.emitAllParticles();
@@ -200,11 +484,17 @@ class SeaSynchronizer extends ShipMapSynchronizer {
*/
private Spatial createShip(Battleship ship) {
switch (ship.getLength()) {
case 4: return createBattleship(ship);
case 3: return createMarlow66(ship);
case 2: return createUX23(ship);
case 1: return createAllienship(ship);
default: return createBox(ship);
case 4:
return createBattleship(ship);
case 3:
return createCV(ship);
case 2:
return createBattle(ship);
case 1:
return createSmallShip(ship);
default:
return createBox(ship);
}
}
@@ -248,49 +538,63 @@ class SeaSynchronizer extends ShipMapSynchronizer {
* Creates a detailed 3D model to represent a "King George V" battleship.
*
* @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) {
final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(1.48f);
// model.scale(0.0007f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createAllienship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(ALIENSHIP);
/**
* Creates a detailed 3D model to represent a small tugboat.
*
* @param ship the battleship to be represented
* @return the spatial representing the small tugboat.
*/
private Spatial createSmallShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(SMALL_BOAT_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.10f);
model.scale(0.0005f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;}
private Spatial createUX23(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(UX23);
return model;
}
/**
* Creates a detailed 3D model to represent a U-Boat .
*
* @param ship the battleship to be represented
* @return the spatial representing the U-Boat.
*/
private Spatial createBattle(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BATTLE_MODEL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
// model.move(0f, -0.05f, 0f);
model.scale(0.89f);
model.scale(0.27f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createMarlow66(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(MARLOW66);
/**
* Creates a detailed 3D model to represent an aircraft carrier
*
* @param ship the battleship to be represented
* @return the spatial representing the aircraft carrier.
*/
private Spatial createCV(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(CV_MODEL);
model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
model.move(0f, 0.25f, 0f);
model.scale(0.135f);
model.scale(0.85f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
@@ -310,4 +614,26 @@ class SeaSynchronizer extends ShipMapSynchronizer {
case UP -> PI;
};
}
/**
* 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
public Spatial visit(Shell shell) {
final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o");
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));
return model;
}
}

View File

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

@@ -0,0 +1,51 @@
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 static pp.util.FloatMath.PI;
/**
* Controls the 3D representation of a {@code Shell} in the game, updating its position
* and rotation based on the shell's current state in the game model. The {@code ShellControl}
* class ensures that the spatial associated with the shell is positioned and oriented correctly
* within the world.
*/
public class ShellControl extends AbstractControl {
private final Shell shell;
/**
* Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}.
*
* @param shell The {@code Shell} being visualized and controlled.
*/
public ShellControl(Shell shell){
super();
this.shell = shell;
}
/**
* Updates the 3D position and rotation of the shell based on its current state.
* Converts map coordinates to world coordinates and applies the shell's orientation.
*
* @param tpf Time per frame, representing the elapsed time since the last update.
*/
@Override
protected void controlUpdate(float tpf) {
Vector3f pos = shell.getPosition();
Vector3f fixed = new Vector3f(pos.z + 0.5f, pos.y, pos.x + 0.5f);
fixed.setY(pos.y);
spatial.setLocalTranslation(fixed);
spatial.setLocalRotation(shell.getRotation());
spatial.rotate(PI/2,0,0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering-specific behavior required for this control
}
}

View File

@@ -0,0 +1,116 @@
package pp.battleship.client.gui;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
* Controls the burning, tilting, and sinking behavior of a battleship.
* The ship will burn and tilt for a specified duration, then sink below the water surface.
*/
public class ShipSinkingControl extends AbstractControl {
private float elapsedTime = 0f;
private final float burnTiltDuration;
private final float sinkDuration;
private final float sinkDepth;
private final float tiltAngle;
private final Vector3f initialPosition;
private boolean sinkingStarted = false;
/**
* Constructs a new ShipSinkingControl instance.
*
* @param burnTiltDuration Time in seconds for the ship to burn and tilt on the surface
* @param sinkDuration Time in seconds for the ship to fully sink
* @param sinkDepth Depth below the water to sink the ship
* @param tiltAngle Final tilt angle in degrees
*/
public ShipSinkingControl(float burnTiltDuration, float sinkDuration, float sinkDepth, float tiltAngle) {
this.burnTiltDuration = burnTiltDuration;
this.sinkDuration = sinkDuration;
this.sinkDepth = sinkDepth;
this.tiltAngle = tiltAngle;
this.initialPosition = new Vector3f(); // Placeholder; will be set in controlUpdate
}
/**
* Overrides controlUpdate in AbstractControl
* regulates the burn and tilt timeframe
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
if (spatial == null) return;
elapsedTime += tpf;
if (elapsedTime < burnTiltDuration) {
float progress = elapsedTime / burnTiltDuration;
float angleInRadians = FastMath.DEG_TO_RAD * FastMath.interpolateLinear(progress, 0f, -tiltAngle);
Quaternion tiltRotation = new Quaternion().fromAngles(angleInRadians, 0, 0);
spatial.setLocalRotation(tiltRotation);
return;
}
// Start sinking if it hasn't started yet
if (!sinkingStarted) {
sinkingStarted = true;
// Save the initial position when sinking starts
initialPosition.set(spatial.getLocalTranslation());
// Remove the hitEffectNode
Node parentNode = (Node) spatial;
Spatial hitEffects = parentNode.getChild("HitEffectNode");
if (hitEffects != null) {
parentNode.detachChild(hitEffects);
}
}
// Calculate the progress of the sinking (0 to 1)
float progress = Math.min((elapsedTime - burnTiltDuration) / sinkDuration, 1f);
// Apply the tilt angle (remains constant during sinking)
Quaternion tiltRotation = new Quaternion().fromAngles(-FastMath.DEG_TO_RAD * tiltAngle, 0, 0);
spatial.setLocalRotation(tiltRotation);
// Sink the ship by interpolating the Y position
float currentY = FastMath.interpolateLinear(progress, initialPosition.y, sinkDepth);
spatial.setLocalTranslation(initialPosition.x, currentY, initialPosition.z);
if (currentY <= sinkDepth) {
Node parentNode = (Node) spatial.getParent();
if (parentNode != null) {
parentNode.detachChild(spatial);
}
spatial.removeControl(this);
}
}
/**
* This method is called during the rendering phase, but it does not perform any
* operations in this implementation as the control only influences the spatial's
* transformation, not its rendering process.
*
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic is needed for this control
}
/**
* Starts the sinking process for the ship.
*/
public void startSinking() {
// Nothing to do here as the control update handles the timing and sequence
}
}

View File

@@ -0,0 +1,34 @@
package pp.battleship.client.gui;
import com.simsilica.lemur.Slider;
import pp.battleship.client.GameMusic;
public class VolumeControl extends Slider {
private final GameMusic menuMusicModern;
private double vol;
public VolumeControl(GameMusic music) {
super();
this.menuMusicModern = music;
vol = GameMusic.volumePreference();
getModel().setPercent(vol);
}
/**
* Updates the volume of the music to the appropriate level set by the User through the Volume control Slider
*
*/
public void update() {
if (vol != getModel().getPercent()) {
vol = getModel().getPercent();
menuMusicModern.setVolume( (float) vol);
}
}
}

View File

@@ -0,0 +1,182 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client.server;
import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import pp.battleship.BattleshipConfig;
import pp.battleship.game.server.Player;
import pp.battleship.game.server.ServerGameLogic;
import pp.battleship.game.server.ServerSender;
import pp.battleship.message.client.AnimationFinishedMessage;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerMessage;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shot;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.LogManager;
/**
* Server implementing the visitor pattern as MessageReceiver for ClientMessages
*/
public class BattleshipSelfhostServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
private static final Logger LOGGER = System.getLogger(BattleshipSelfhostServer.class.getName());
private static final File CONFIG_FILE = new File("server.properties");
private final BattleshipConfig config = new BattleshipConfig();
private Server myServer;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessageSelfhost> pendingMessages = new LinkedBlockingQueue<>();
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
try {
manager.readConfiguration(new FileInputStream("logging.properties"));
LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
}
catch (IOException e) {
LOGGER.log(Level.INFO, e.getMessage());
}
}
/**
* Starts the server.
*/
public static void main(String[] args) {
new BattleshipSelfhostServer().run();
}
/**
* Creates the server.
*/
BattleshipSelfhostServer() {
config.readFromIfExists(CONFIG_FILE);
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config);
}
public void run() {
startServer();
while (true)
processNextMessage();
}
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(config.getPort());
initializeSerializables();
myServer.start();
registerListeners();
LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
}
catch (IOException e) {
LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
exit(1);
}
}
private void processNextMessage() {
try {
pendingMessages.take().process(logic);
}
catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
Thread.currentThread().interrupt();
}
}
private void initializeSerializables() {
Serializer.registerClass(GameDetails.class);
Serializer.registerClass(StartBattleMessage.class);
Serializer.registerClass(MapMessage.class);
Serializer.registerClass(ShootMessage.class);
Serializer.registerClass(EffectMessage.class);
Serializer.registerClass(AnimationFinishedMessage.class);
Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class);
}
private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class);
myServer.addMessageListener(this, AnimationFinishedMessage.class);
myServer.addConnectionListener(this);
}
@Override
public void messageReceived(HostedConnection source, Message message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
if (message instanceof ClientMessage clientMessage)
pendingMessages.add(new ReceivedMessageSelfhost(clientMessage, source.getId()));
}
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
logic.addPlayer(hostedConnection.getId());
}
@Override
public void connectionRemoved(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
final Player player = logic.getPlayerById(hostedConnection.getId());
if (player == null)
LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
else { //NON-NLS
LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
exit(0);
}
}
private void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null)
for (HostedConnection client : myServer.getConnections()) //NON-NLS
if (client != null) client.close("Game over"); //NON-NLS
System.exit(exitValue);
}
/**
* Send the specified message to the specified connection.
*
* @param id the connection id
* @param message the message
*/
public void send(int id, ServerMessage message) {
if (myServer == null || !myServer.isRunning()) {
LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
return;
}
final HostedConnection connection = myServer.getConnection(id);
if (connection != null)
connection.send(message);
else
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
}
}

View File

@@ -0,0 +1,17 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client.server;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage;
record ReceivedMessageSelfhost(ClientMessage message, int from) {
void process(ClientInterpreter interpreter) {
message.accept(interpreter, from);
}
}

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,16 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 29.03.2012 14:25:39
newmtl default
Ns 35.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.5400 0.5400 0.5400
Ke 0.0000 0.0000 0.0000
map_Ka 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
map_Kd 14084_WWII_ship_German_Type_II_U-boat_diff.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

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