12 Commits

Author SHA1 Message Date
Felix Koppe
5cb0843819 Add missle sound 2024-10-13 08:02:24 +02:00
Felix Koppe
34929b40f1 Add Shell 2024-10-12 18:19:57 +02:00
Felix Koppe
5dd3df50c5 Adjust particle effects 2024-10-10 15:47:21 +02:00
Felix Koppe
a7c3632a59 Add hit/miss effects 2024-10-09 07:41:14 +02:00
Felix Koppe
05eb0f92ea Add client host capability 2024-10-08 14:14:53 +02:00
Felix Koppe
f96c63e76e Add music 2024-10-08 12:41:24 +02:00
Felix Koppe
9dbac0b226 Add ship models 2024-10-06 23:13:29 +02:00
Felix Koppe
7ef06f2440 Fix logic error 2024-10-06 22:09:45 +02:00
Felix Koppe
95fdd86100 Add clientside and serverside map check 2024-10-06 19:33:30 +02:00
Felix Koppe
1f548f5ee5 Merge commit 2024-10-02 14:58:36 +02:00
Felix Koppe
af5d1aaca6 Fix BattleState.receivedEffect 2024-10-02 14:57:10 +02:00
Felix Koppe
3a2f20e45c Fix ShipMap.remove 2024-10-02 08:21:32 +02:00
197 changed files with 2693974 additions and 106 deletions

View File

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

View File

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

View File

@@ -122,6 +122,11 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
*/
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/**
* Listener for handling actions triggered by the Escape key.
*/
private GameMusic music;
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
@@ -193,6 +198,15 @@ DialogManager getDialogManager() {
return dialogManager;
}
/**
* Returns the GameMusic responsible for playing music.
*
* @return The {@link GameMusic} instance.
*/
GameMusic getGameMusic() {
return music;
}
/**
* Returns the game logic handler for the client.
*
@@ -225,6 +239,8 @@ public void simpleInitApp() {
setupStates();
setupGui();
serverConnection.connect();
music = new GameMusic(this, "Sound/Music/battleship.ogg");
}
/**

View File

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

View File

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

View File

@@ -34,6 +34,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
private AudioNode splashSound;
private AudioNode shipDestroyedSound;
private AudioNode explosionSound;
private AudioNode missleSound;
/**
* Checks if sound is enabled in the preferences.
@@ -78,6 +79,7 @@ public void initialize(AppStateManager stateManager, Application app) {
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
missleSound = loadSound(app, "Sound/Effects/missle.wav");
}
/**
@@ -108,6 +110,14 @@ public void splash() {
splashSound.playInstance();
}
/**
* Plays the missle flyby sound effect.
*/
public void flyBy() {
if (isEnabled() && missleSound != null)
missleSound.playInstance();
}
/**
* Plays the explosion sound effect.
*/
@@ -130,6 +140,7 @@ public void receivedEvent(SoundEvent event) {
case EXPLOSION -> explosion();
case SPLASH -> splash();
case DESTROYED_SHIP -> shipDestroyed();
case MISSLE_FLYBY -> flyBy();
}
}
}

View File

@@ -7,9 +7,8 @@
package pp.battleship.client;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.*;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.style.ElementId;
import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel;
@@ -34,6 +33,8 @@ class Menu extends Dialog {
private final Button loadButton = new Button(lookup("menu.map.load"));
private final Button saveButton = new Button(lookup("menu.map.save"));
private final VersionedReference<Double> volumeRef;
/**
* Constructs the Menu dialog for the Battleship application.
*
@@ -49,6 +50,20 @@ public Menu(BattleshipApp app) {
.addClickCommands(s -> ifTopDialog(this::loadDialog));
addChild(saveButton)
.addClickCommands(s -> ifTopDialog(this::saveDialog));
Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
musicToggle.setChecked(app.getGameMusic().isMusicEnabled());
musicToggle.addClickCommands(s -> toggleMusic());
addChild(musicToggle);
Slider volumeSlider = new Slider(lookup("menu.music.slider"));
volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 4.0, app.getGameMusic().getVolume()));
volumeSlider.setDelta(0.4);
addChild(volumeSlider);
volumeRef = volumeSlider.getModel().createReference();
addChild(new Button(lookup("menu.return-to-game")))
.addClickCommands(s -> ifTopDialog(this::close));
addChild(new Button(lookup("menu.quit")))
@@ -57,12 +72,33 @@ public Menu(BattleshipApp app) {
}
/**
* Updates the state of the load and save buttons based on the game logic.
* Updates the state of the load/save buttons and volume based on the game logic.
*/
@Override
public void update() {
loadButton.setEnabled(app.getGameLogic().mayLoadMap());
saveButton.setEnabled(app.getGameLogic().maySaveMap());
if(volumeRef.update()){
double newVolume = volumeRef.get();
adjustVolume(newVolume);
}
}
/**
* this method adjust the volume for the background music
*
* @param volume is the double value of the volume
*/
private void adjustVolume(double volume) {
app.getGameMusic().setVolume((float) volume);
}
/**
* this method toggles the background music on and off
*/
private void toggleMusic() {
app.getGameMusic().toggleMusic();
}
/**

View File

@@ -7,6 +7,7 @@
package pp.battleship.client;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField;
@@ -37,6 +38,7 @@ class NetworkDialog extends SimpleDialog {
private int portNumber;
private Future<Object> connectionFuture;
private Dialog progressDialog;
private boolean hostServer = false;
/**
* Constructs a new NetworkDialog.
@@ -50,6 +52,11 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f);
port.setSingleLine(true);
Checkbox serverHost = new Checkbox(lookup("menu.host"));
serverHost.setChecked(false);
serverHost.addClickCommands(s -> toggleServerHost());
addChild(serverHost);
final BattleshipApp app = network.getApp();
final Container input = new Container(new SpringGridLayout());
input.addChild(new Label(lookup("host.name") + ": "));
@@ -60,7 +67,7 @@ class NetworkDialog extends SimpleDialog {
DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog"))
.setExtension(d -> d.addChild(input))
.setOkButton(lookup("button.connect"), d -> connect())
.setOkButton(lookup("button.connect"), d -> connectLocally())
.setNoButton(lookup("button.cancel"), app::closeApp)
.setOkClose(false)
.setNoClose(false)
@@ -84,6 +91,43 @@ private void connect() {
}
}
/**
* Starts a server or if not host connects to one
*/
private void connectLocally() {
if(hostServer){
startServer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e.getMessage());
}
}
connect();
}
/**
* Starts a host server in a new thread
*/
private void startServer() {
new Thread(() -> {
try{
BattleshipServer battleshipServer = new BattleshipServer();
battleshipServer.run();
} catch (Exception e) {
LOGGER.log(Level.ERROR,e);
}
}).start();
}
/**
* Toggles client/host mode
*/
private void toggleServerHost(){
hostServer = !hostServer;
}
/**
* Creates a dialog indicating that the connection is in progress.
*/

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;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage;
record ReceivedMessage(ClientMessage message, int from) {
void process(ClientInterpreter interpreter) {
message.accept(interpreter, from);
}
}

View File

@@ -8,10 +8,13 @@
package pp.battleship.client.gui;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell;
import pp.battleship.model.Shot;
import pp.util.Position;
@@ -26,6 +29,10 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private static final float SHOT_DEPTH = -2f;
private static final float SHIP_DEPTH = 0f;
private static final float INDENT = 4f;
private static final float SHELL_DEPTH = 8f;
private static final float SHELL_SIZE = 0.75f;
private static final float SHELL_CENTERED_IN_MAP_GRID = 0.0625f;
// Colors used for different visual elements
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
@@ -109,6 +116,29 @@ public Spatial visit(Battleship ship) {
return shipNode;
}
/**
* Creates a visual representation of a shell on the map.
*
* @param shell the Shell object representing the shell in the model
* @return a Spatial representing the shell
*/
@Override
public Spatial visit(Shell shell) {
final Node shellNode = new Node("shell");
final Position p1 = view.modelToView(shell.getX(), shell.getY());
final Position p2 = view.modelToView(shell.getX() + SHELL_SIZE, shell.getY() + SHELL_SIZE);
final Position startPosition = view.modelToView(SHELL_CENTERED_IN_MAP_GRID, SHELL_CENTERED_IN_MAP_GRID);
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(), startPosition.getY(), SHELL_DEPTH, p2.getX() - p1.getX(), p2.getY() - p1.getY(), ColorRGBA.Magenta));
shellNode.setLocalTranslation(startPosition.getX(), startPosition.getY(), SHELL_DEPTH);
shellNode.addControl(new ShellControlMap(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
return shellNode;
}
/**
* Creates a line geometry representing part of the ship's border.
*

View File

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

View File

@@ -10,6 +10,9 @@
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
@@ -17,10 +20,7 @@
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation;
import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot;
import pp.battleship.model.*;
import static java.util.Objects.requireNonNull;
import static pp.util.FloatMath.HALF_PI;
@@ -41,9 +41,11 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
private static final String BOMB = "Models/Bomb/Files/Bomb GBU-43B(MOAB).obj";
private final ShipMap map;
private final BattleshipApp app;
private final ParticleHandler handler;
/**
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
@@ -56,9 +58,41 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root);
this.app = app;
this.map = map;
handler = new ParticleHandler(app);
addExisting();
}
/**
* Visits a {@link Shell} and creates a graphical representation of it.
*
* @param shell the shell to be represented
* @return the graphical representation of the shell
*/
@Override
public Spatial visit(Shell shell) {
final Node node = new Node("shell");
final Spatial model = app.getAssetManager().loadModel(BOMB);
model.rotate(-PI/2, 0, 0);
model.scale(0.09f);
model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, 0, 0);
node.attachChild(model);
final float x = shell.getY();
final float z = shell.getX();
node.setLocalTranslation(x, 8f, z);
ShellControll control = new ShellControll(shell, app);
node.addControl(control);
return node;
}
/**
* Visits a {@link Shot} and creates a graphical representation of it.
* If the shot is a hit, it attaches the representation to the ship node.
@@ -69,7 +103,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
*/
@Override
public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : createCylinder(shot);
return shot.isHit() ? handleHit(shot) : handler.createMissParticles(shot);
}
/**
@@ -77,16 +111,14 @@ public Spatial visit(Shot shot) {
* contains the ship model as a child so that it moves with the ship.
*
* @param shot a hit
* @return always null to prevent the representation from being attached
* @return always null to prevent the representation from being sattached
* to the items node as well
*/
private Spatial handleHit(Shot shot) {
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
final Geometry representation = createCylinder(shot);
representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
shipNode.attachChild(representation);
handler.createHitParticles(shipNode, shot);
return null;
}
@@ -141,7 +173,33 @@ public Spatial visit(Battleship ship) {
* @return the spatial representing the battleship
*/
private Spatial createShip(Battleship ship) {
return ship.getLength() == 4 ? createBattleship(ship) : createBox(ship);
final int len = ship.getLength();
Quaternion q0 = new Quaternion();
Quaternion q1 = new Quaternion();
q1.fromAngleAxis(PI/2, new Vector3f(1, 0, 0));
Quaternion q2 = new Quaternion();
q2.fromAngleAxis(PI/2, new Vector3f(1, 0, 0));
switch (len) {
case 1 -> {
return createBattleship(ship, "Models/Uboat/uboat.j3o", 0.2f, new Vector3f(0.0f, -0.1f, 0.0f), q0);
}
case 2 -> {
return createBattleship(ship, "Models/KingGeorgeV/KingGeorgeV.j3o", 1.0f, new Vector3f(0.0f, 0.0f, 0.0f), q0);
}
case 3 -> {
return createBattleship(ship, "Models/EssexClass/essex.j3o", 0.8f, new Vector3f(0.0f, 0.3f, 0.0f), q2);
}
case 4 -> {
return createBattleship(ship, "Models/Container/container.j3o", 0.05f, new Vector3f(0.0f, 0.0f, 0.0f), q1);
}
default -> {
return createBox(ship);
}
}
}
/**
@@ -181,16 +239,22 @@ private Material createColoredMaterial(ColorRGBA color) {
}
/**
* Creates a detailed 3D model to represent a "King George V" battleship.
* Creates a 3D model to represent a "battleship".
*
* @param ship the battleship to be represented
* @return the spatial representing the "King George V" battleship
* @param path the battleship to be represented
* @param scale the battleship to be represented
* @param translation the battleship to be represented
* @param rotation the battleship to be represented
* @return the spatial representing the battleship
*/
private Spatial createBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
private Spatial createBattleship(Battleship ship, String path, float scale, Vector3f translation, Quaternion rotation) {
final Spatial model = app.getAssetManager().loadModel(path);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(1.48f);
model.rotate(rotation);
model.scale(scale);
model.setLocalTranslation(translation);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;

View File

@@ -0,0 +1,67 @@
package pp.battleship.client.gui;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.client.BattleshipApp;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.model.IntPoint;
import pp.util.Position;
/**
* Controls the movement of a shell in a specified position on the battlefield.
* This control updates the shell's position and sends a message when it reaches
* its target location.
*/
public class ShellControlMap extends AbstractControl {
private final Position position; // Target position for the shell
private final IntPoint pos; // Target coordinates as an IntPoint
private static final Vector3f vector = new Vector3f(); // Movement vector
private final BattleshipApp app; // Reference to the main application
/**
* Constructs a ShellControlMap object with the specified target position,
* application instance, and target coordinates.
*
* @param position The target Position for the shell.
* @param app The BattleshipApp instance managing the game logic.
* @param pos The IntPoint representing the target coordinates of the shell.
*/
public ShellControlMap(Position position, BattleshipApp app, IntPoint pos) {
super();
this.position = position;
this.pos = pos;
this.app = app;
vector.set(new Vector3f(position.getX(), position.getY(), 0));
}
/**
* Updates the shell's position based on the time per frame.
* If the shell reaches or exceeds the target position, it detaches
* the shell from the scene and sends an animation end message.
*
* @param tpf Time per frame, used to calculate movement.
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(vector.mult(tpf));
if (spatial.getLocalTranslation().getX() >= position.getX() &&
spatial.getLocalTranslation().getY() >= position.getY()) {
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(pos));
}
}
/**
* Renders the shell control. This method can be overridden to implement
* custom rendering behavior, but it is currently empty.
*
* @param rm The RenderManager for rendering the control.
* @param vp The ViewPort where the control is rendered.
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic implemented for ShellControlMap
}
}

View File

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

View File

@@ -23,6 +23,12 @@
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
*/
class ShipControl extends AbstractControl {
/**
* The sinking height, after wich the ship will get removed
*/
private static final Float SINKING_HEIGHT = -0.6f;
/**
* The axis of rotation for the ship's pitch (tilting forward and backward).
*/
@@ -48,6 +54,11 @@ class ShipControl extends AbstractControl {
*/
private float time;
/**
* The ship, that the ShipControl controls
*/
private final Battleship ship;
/**
* Constructs a new ShipControl instance for the specified Battleship.
* The ship's orientation determines the axis of rotation, while its length influences
@@ -56,6 +67,8 @@ class ShipControl extends AbstractControl {
* @param ship the Battleship object to control
*/
public ShipControl(Battleship ship) {
this.ship = ship;
// Determine the axis of rotation based on the ship's orientation
axis = switch (ship.getRot()) {
case LEFT, RIGHT -> Vector3f.UNIT_X;
@@ -78,6 +91,13 @@ protected void controlUpdate(float tpf) {
// If spatial is null, do nothing
if (spatial == null) return;
if (ship.isDestroyed() && spatial.getLocalTranslation().getY() < SINKING_HEIGHT) { // removes the ship, if it is completely sunk
spatial.getParent().detachChild(spatial);
}
else if (ship.isDestroyed() && spatial.getLocalTranslation().getY() >= SINKING_HEIGHT) { // sink the ship, if it's not completely sunk
spatial.move(0, tpf * -0.03f, 0);
}
// Update the time within the oscillation cycle
time = (time + tpf) % cycle;

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

View File

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

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.

After

Width:  |  Height:  |  Size: 168 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
credit to patrickdearteaga.com

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