finished ex. 13 + cleanup
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
#
|
#
|
||||||
# Specifies the map used by the opponent in single mode.
|
# Specifies the map used by the opponent in single mode.
|
||||||
# Single mode is activated if this property is set.
|
# Single mode is activated if this property is set.
|
||||||
map.opponent=maps/map2.json
|
#map.opponent=maps/map2.json
|
||||||
#
|
#
|
||||||
# Specifies the map used by the player in single mode.
|
# Specifies the map used by the player in single mode.
|
||||||
# The player must define their own map if this property is not set.
|
# The player must define their own map if this property is not set.
|
||||||
|
|||||||
@@ -40,9 +40,6 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.System.Logger;
|
import java.lang.System.Logger;
|
||||||
import java.lang.System.Logger.Level;
|
import java.lang.System.Logger.Level;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.LogManager;
|
import java.util.logging.LogManager;
|
||||||
@@ -281,9 +278,7 @@ private void setupStates() {
|
|||||||
* Attaches the game sound state and sets its initial enabled state.
|
* Attaches the game sound state and sets its initial enabled state.
|
||||||
*/
|
*/
|
||||||
private void attachGameMusic() {
|
private void attachGameMusic() {
|
||||||
//TODO: start volume
|
|
||||||
final GameMusic gameMusic = new GameMusic();
|
final GameMusic gameMusic = new GameMusic();
|
||||||
logic.addListener(gameMusic);
|
|
||||||
gameMusic.setEnabled(GameMusic.enabledInPreferences());
|
gameMusic.setEnabled(GameMusic.enabledInPreferences());
|
||||||
stateManager.attach(gameMusic);
|
stateManager.attach(gameMusic);
|
||||||
}
|
}
|
||||||
@@ -309,19 +304,6 @@ public void simpleUpdate(float tpf) {
|
|||||||
super.simpleUpdate(tpf);
|
super.simpleUpdate(tpf);
|
||||||
dialogManager.update(tpf);
|
dialogManager.update(tpf);
|
||||||
logic.update(tpf);
|
logic.update(tpf);
|
||||||
handleTimers(tpf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleTimers(float tpf) {
|
|
||||||
Iterator<Timer> iter = timerList.iterator();
|
|
||||||
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
Timer next = iter.next();
|
|
||||||
next.update(tpf);
|
|
||||||
if (next.isFinished()){
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -461,10 +443,4 @@ void errorDialog(String errorMessage) {
|
|||||||
public EffectHandler getEffectHandler() {
|
public EffectHandler getEffectHandler() {
|
||||||
return effectHandler;
|
return effectHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<Timer> timerList = new ArrayList<>();
|
|
||||||
|
|
||||||
public void setTimer(float time, Runnable runnable){
|
|
||||||
timerList.add(new Timer(time, runnable));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,83 @@
|
|||||||
package pp.battleship.client;
|
package pp.battleship.client;
|
||||||
|
|
||||||
import com.jme3.app.Application;
|
|
||||||
import com.jme3.effect.ParticleEmitter;
|
import com.jme3.effect.ParticleEmitter;
|
||||||
import com.jme3.effect.ParticleMesh;
|
import com.jme3.effect.ParticleMesh;
|
||||||
import com.jme3.effect.influencers.RadialParticleInfluencer;
|
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
|
||||||
import pp.battleship.model.Battleship;
|
import pp.battleship.model.Battleship;
|
||||||
import pp.battleship.model.IntPoint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EffectHandler manages the creation and manipulation of particle effects in the Battleship application.
|
||||||
|
*/
|
||||||
public class EffectHandler {
|
public class EffectHandler {
|
||||||
private BattleshipApp app;
|
private final BattleshipApp app;
|
||||||
private Map<Battleship, List<ParticleEmitter>> effects;
|
private final Map<Battleship, List<ParticleEmitter>> effects;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an EffectHandler with the specified BattleshipApp instance.
|
||||||
|
*
|
||||||
|
* @param app the BattleshipApp instance
|
||||||
|
*/
|
||||||
public EffectHandler(BattleshipApp app) {
|
public EffectHandler(BattleshipApp app) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
effects = new HashMap<>();
|
effects = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a fire effect at the specified position for the given Battleship.
|
||||||
|
*
|
||||||
|
* @param point the position where the fire effect will be created
|
||||||
|
* @param ship the Battleship associated with the fire effect
|
||||||
|
* @return a Node containing the fire effect
|
||||||
|
*/
|
||||||
public Node createFire(Vector3f point, Battleship ship) {
|
public Node createFire(Vector3f point, Battleship ship) {
|
||||||
Node parent = new Node();
|
Node parent = new Node();
|
||||||
parent.setLocalTranslation(point);
|
parent.setLocalTranslation(point);
|
||||||
|
|
||||||
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
ParticleEmitter fire = initializeParticleEmitter(
|
||||||
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
"Effects/Explosion/flame.png",
|
||||||
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
|
2,2,
|
||||||
fire.setMaterial(matRed);
|
new ColorRGBA(1f, 0f, 0f, 1f),
|
||||||
fire.setImagesX(2);
|
new ColorRGBA(1f, 1f, 0f, 0.5f),
|
||||||
fire.setImagesY(2);
|
new Vector3f(0, 1.5f, 0),
|
||||||
fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f));
|
50,
|
||||||
fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f));
|
.4f,
|
||||||
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0));
|
0.05f,
|
||||||
fire.setStartSize(.4f);
|
1f,
|
||||||
fire.setEndSize(0.05f);
|
2f,
|
||||||
fire.setGravity(0, 0, 0);
|
0.2f,
|
||||||
fire.setLowLife(1f);
|
new Vector3f(0, 0, 0)
|
||||||
fire.setHighLife(2f);
|
);
|
||||||
fire.getParticleInfluencer().setVelocityVariation(0.2f);
|
|
||||||
|
ParticleEmitter smoke = initializeParticleEmitter(
|
||||||
|
"Effects/Smoke/Smoke.png",
|
||||||
|
15,
|
||||||
|
1,
|
||||||
|
new ColorRGBA(1f, 1f, 1f, 0f),
|
||||||
|
new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
|
||||||
|
new Vector3f(0, 1f, 0),
|
||||||
|
600,
|
||||||
|
.2f,
|
||||||
|
0.1f,
|
||||||
|
1f,
|
||||||
|
5f,
|
||||||
|
0.25f,
|
||||||
|
new Vector3f(0, 0, 0)
|
||||||
|
);
|
||||||
|
|
||||||
parent.attachChild(fire);
|
parent.attachChild(fire);
|
||||||
|
|
||||||
ParticleEmitter smoke = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 600);
|
|
||||||
Material matBlack = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
|
||||||
matBlack.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
|
|
||||||
smoke.setMaterial(matBlack);
|
|
||||||
smoke.setImagesX(15);
|
|
||||||
smoke.setImagesY(1);
|
|
||||||
smoke.setEndColor(new ColorRGBA(1f,1f,1f,0f));
|
|
||||||
smoke.setStartColor(new ColorRGBA(0.5f,0.5f,0.5f,0.5f));
|
|
||||||
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1f, 0));
|
|
||||||
smoke.setStartSize(.2f);
|
|
||||||
smoke.setEndSize(0.1f);
|
|
||||||
smoke.setGravity(0, 0, 0);
|
|
||||||
smoke.setLowLife(1f);
|
|
||||||
smoke.setHighLife(5f);
|
|
||||||
smoke.getParticleInfluencer().setVelocityVariation(0.25f);
|
|
||||||
parent.attachChild(smoke);
|
parent.attachChild(smoke);
|
||||||
|
|
||||||
|
|
||||||
List<ParticleEmitter> oldEffects = new ArrayList<>(effects.getOrDefault(ship, new ArrayList<>()));
|
List<ParticleEmitter> oldEffects = new ArrayList<>(effects.getOrDefault(ship, new ArrayList<>()));
|
||||||
oldEffects.add(fire);
|
oldEffects.add(fire);
|
||||||
oldEffects.add(smoke);
|
oldEffects.add(smoke);
|
||||||
@@ -75,66 +86,112 @@ public Node createFire(Vector3f point, Battleship ship){
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a water splash effect at the specified position.
|
||||||
|
*
|
||||||
|
* @param pos the position where the water splash effect will be created
|
||||||
|
* @return a Geometry representing the water splash effect
|
||||||
|
*/
|
||||||
|
public Geometry waterSplash(Vector3f pos) {
|
||||||
|
ParticleEmitter water = initializeParticleEmitter(
|
||||||
|
"Effects/Explosion/flash.png",
|
||||||
|
2,2,
|
||||||
|
new ColorRGBA(0.3f, 0.8f, 1f, 0f),
|
||||||
|
new ColorRGBA(0f, 0f, 1f, 1f),
|
||||||
|
new Vector3f(0, 3, 0),
|
||||||
|
100,
|
||||||
|
.6f,
|
||||||
|
0.05f,
|
||||||
|
1f,
|
||||||
|
1.5f,
|
||||||
|
0.3f,
|
||||||
|
new Vector3f(0, 4f, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
water.setLocalTranslation(pos);
|
||||||
|
water.emitAllParticles();
|
||||||
|
water.setParticlesPerSec(0);
|
||||||
|
|
||||||
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
deleteSplash(water);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return water;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a debris splash effect at the specified position.
|
||||||
|
*
|
||||||
|
* @param pos the position where the debris splash effect will be created
|
||||||
|
* @return a Geometry representing the debris splash effect
|
||||||
|
*/
|
||||||
|
public Geometry debrisSplash(Vector3f pos) {
|
||||||
|
ParticleEmitter debris = initializeParticleEmitter(
|
||||||
|
"Effects/Explosion/Debris.png",
|
||||||
|
3,3,
|
||||||
|
new ColorRGBA(0.1f, 0.1f, 0.1f, 0f),
|
||||||
|
new ColorRGBA(0.5f, 0.5f, 0.5f, .8f),
|
||||||
|
new Vector3f(0, 2f, 0),
|
||||||
|
50,
|
||||||
|
0.1f,
|
||||||
|
0.5f,
|
||||||
|
1f,
|
||||||
|
1.5f,
|
||||||
|
0.5f,
|
||||||
|
new Vector3f(0, 0, 0)
|
||||||
|
);
|
||||||
|
debris.setLocalTranslation(pos);
|
||||||
|
|
||||||
|
debris.emitAllParticles();
|
||||||
|
debris.setParticlesPerSec(0);
|
||||||
|
|
||||||
|
return debris;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the specified splash effect from the scene.
|
||||||
|
*
|
||||||
|
* @param splash the Geometry representing the splash effect to be deleted
|
||||||
|
*/
|
||||||
|
private void deleteSplash(Geometry splash) {
|
||||||
|
splash.getParent().detachChild(splash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops all particle effects associated with the specified Battleship.
|
||||||
|
*
|
||||||
|
* @param ship the Battleship whose effects are to be destroyed
|
||||||
|
*/
|
||||||
public void destroyShip(Battleship ship) {
|
public void destroyShip(Battleship ship) {
|
||||||
for (ParticleEmitter emitter : effects.get(ship)) {
|
for (ParticleEmitter emitter : effects.get(ship)) {
|
||||||
emitter.setParticlesPerSec(0);
|
emitter.setParticlesPerSec(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Geometry waterSplash(Vector3f pos){
|
private ParticleEmitter initializeParticleEmitter(
|
||||||
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 100);
|
String texturePath, int imagesX, int imagesY, ColorRGBA endColor, ColorRGBA startColor, Vector3f initialVelocity,
|
||||||
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
int particleCount, float startSize, float endSize, float lowLife, float highLife,
|
||||||
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
|
float velocityVariation, Vector3f gravity
|
||||||
fire.setMaterial(matRed);
|
) {
|
||||||
fire.setImagesX(2);
|
ParticleEmitter emitter = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, particleCount);
|
||||||
fire.setImagesY(2); // 2x2 texture animation
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
fire.setEndColor(new ColorRGBA(0.3f, 0.8f, 1f, 0f));
|
mat.setTexture("Texture", app.getAssetManager().loadTexture(texturePath));
|
||||||
fire.setStartColor(new ColorRGBA(0f, 0f, 1f, 1f));
|
emitter.setMaterial(mat);
|
||||||
RadialParticleInfluencer inf = new RadialParticleInfluencer();
|
emitter.setImagesX(imagesX);
|
||||||
inf.setRadialVelocity(5);
|
emitter.setImagesY(imagesY);
|
||||||
inf.setVelocityVariation(0.3f);
|
emitter.setEndColor(endColor);
|
||||||
inf.setInitialVelocity(new Vector3f(0,3,0));
|
emitter.setStartColor(startColor);
|
||||||
fire.setParticleInfluencer(inf);
|
emitter.getParticleInfluencer().setInitialVelocity(initialVelocity);
|
||||||
fire.setStartSize(.6f);
|
emitter.setStartSize(startSize);
|
||||||
fire.setEndSize(0.05f);
|
emitter.setEndSize(endSize);
|
||||||
fire.setGravity(0, 4f, 0);
|
emitter.setLowLife(lowLife);
|
||||||
fire.setLowLife(1f);
|
emitter.setHighLife(highLife);
|
||||||
fire.setHighLife(1.5f);
|
emitter.getParticleInfluencer().setVelocityVariation(velocityVariation);
|
||||||
fire.setLocalTranslation(pos);
|
emitter.setGravity(gravity);
|
||||||
fire.emitAllParticles();
|
|
||||||
fire.setParticlesPerSec(0);
|
|
||||||
app.setTimer(2,()->deleteSplash(fire));
|
|
||||||
return fire;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteSplash(Geometry splash){
|
return emitter;
|
||||||
splash.getParent().detachChild(splash);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Geometry debrisSplash(Vector3f pos){
|
|
||||||
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
|
|
||||||
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
|
||||||
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
|
|
||||||
fire.setMaterial(matRed);
|
|
||||||
fire.setImagesX(3);
|
|
||||||
fire.setImagesY(3); // 2x2 texture animation
|
|
||||||
fire.setEndColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 0f));
|
|
||||||
fire.setStartColor(new ColorRGBA(0.5f, 0.5f, 0.5f, .8f));
|
|
||||||
fire.setStartSize(0.1f);
|
|
||||||
fire.setEndSize(0.5f);
|
|
||||||
fire.setGravity(0, 2f, 0);
|
|
||||||
fire.setLowLife(1f);
|
|
||||||
fire.setHighLife(1.5f);
|
|
||||||
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0,2f,0));
|
|
||||||
fire.getParticleInfluencer().setVelocityVariation(.5f);
|
|
||||||
|
|
||||||
|
|
||||||
fire.setLocalTranslation(pos);
|
|
||||||
|
|
||||||
fire.emitAllParticles();
|
|
||||||
fire.setParticlesPerSec(0);
|
|
||||||
|
|
||||||
return fire;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import com.jme3.app.state.AbstractAppState;
|
import com.jme3.app.state.AbstractAppState;
|
||||||
import com.jme3.app.state.AppStateManager;
|
import com.jme3.app.state.AppStateManager;
|
||||||
import com.jme3.audio.AudioNode;
|
import com.jme3.audio.AudioNode;
|
||||||
import pp.battleship.notification.GameEventListener;
|
|
||||||
|
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@
|
|||||||
/**
|
/**
|
||||||
* An application state that plays sounds.
|
* An application state that plays sounds.
|
||||||
*/
|
*/
|
||||||
public class GameMusic extends AbstractAppState implements GameEventListener {
|
public class GameMusic extends AbstractAppState {
|
||||||
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
|
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
|
||||||
private static final String ENABLED_PREF = "enabled";
|
private static final String ENABLED_PREF = "enabled";
|
||||||
private static final String VOLUME_PREF = "volume";
|
private static final String VOLUME_PREF = "volume";
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
|
|||||||
private AudioNode splashSound;
|
private AudioNode splashSound;
|
||||||
private AudioNode shipDestroyedSound;
|
private AudioNode shipDestroyedSound;
|
||||||
private AudioNode explosionSound;
|
private AudioNode explosionSound;
|
||||||
|
private AudioNode shellFlyingSound;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if sound is enabled in the preferences.
|
* Checks if sound is enabled in the preferences.
|
||||||
@@ -77,6 +77,18 @@ public void initialize(AppStateManager stateManager, Application app) {
|
|||||||
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
|
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
|
||||||
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
|
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
|
||||||
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
|
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
|
||||||
|
shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the shell flying sound effect.
|
||||||
|
*/
|
||||||
|
public void shellFly() {
|
||||||
|
System.out.println("shellFly");
|
||||||
|
if (isEnabled() && shellFlyingSound != null) {
|
||||||
|
System.out.println("play shell");
|
||||||
|
shellFlyingSound.playInstance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,6 +121,7 @@ public void receivedEvent(SoundEvent event) {
|
|||||||
case EXPLOSION -> explosion();
|
case EXPLOSION -> explosion();
|
||||||
case SPLASH -> splash();
|
case SPLASH -> splash();
|
||||||
case DESTROYED_SHIP -> shipDestroyed();
|
case DESTROYED_SHIP -> shipDestroyed();
|
||||||
|
case SHELL_FLYING -> shellFly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
import com.simsilica.lemur.Button;
|
import com.simsilica.lemur.Button;
|
||||||
import com.simsilica.lemur.Checkbox;
|
import com.simsilica.lemur.Checkbox;
|
||||||
import com.simsilica.lemur.Label;
|
import com.simsilica.lemur.Label;
|
||||||
import com.simsilica.lemur.RangedValueModel;
|
|
||||||
import com.simsilica.lemur.Slider;
|
|
||||||
import com.simsilica.lemur.style.ElementId;
|
import com.simsilica.lemur.style.ElementId;
|
||||||
import pp.dialog.Dialog;
|
import pp.dialog.Dialog;
|
||||||
import pp.dialog.StateCheckboxModel;
|
import pp.dialog.StateCheckboxModel;
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ private Object initServer(){
|
|||||||
server = new BattleshipServerClient();
|
server = new BattleshipServerClient();
|
||||||
server.run();
|
server.run();
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
LOGGER.log(Level.ERROR, "Error while starting server", e);
|
LOGGER.log(Level.ERROR, "Error while starting server", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -164,9 +165,11 @@ public void update(float delta) {
|
|||||||
if (serverFuture != null && serverFuture.isDone()) {
|
if (serverFuture != null && serverFuture.isDone()) {
|
||||||
try {
|
try {
|
||||||
serverFuture.get();
|
serverFuture.get();
|
||||||
} catch (ExecutionException e) {
|
}
|
||||||
|
catch (ExecutionException e) {
|
||||||
LOGGER.log(Level.ERROR, "Failed to start server", e.getCause());
|
LOGGER.log(Level.ERROR, "Failed to start server", e.getCause());
|
||||||
} catch (InterruptedException e) {
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
LOGGER.log(Level.WARNING, "Server thread was interrupted", e);
|
LOGGER.log(Level.WARNING, "Server thread was interrupted", e);
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package pp.battleship.client;
|
|
||||||
|
|
||||||
public class Timer {
|
|
||||||
private float time;
|
|
||||||
private final Runnable runnable;
|
|
||||||
private boolean finished;
|
|
||||||
|
|
||||||
public Timer(float time, Runnable runnable){
|
|
||||||
this.time = time;
|
|
||||||
this.runnable = runnable;
|
|
||||||
this.finished = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(float delta){
|
|
||||||
if(!finished){
|
|
||||||
time -= delta;
|
|
||||||
if(time < 0){
|
|
||||||
finished = true;
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFinished(){
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
import pp.battleship.game.server.Player;
|
import pp.battleship.game.server.Player;
|
||||||
import pp.battleship.game.server.ServerGameLogic;
|
import pp.battleship.game.server.ServerGameLogic;
|
||||||
import pp.battleship.game.server.ServerSender;
|
import pp.battleship.game.server.ServerSender;
|
||||||
|
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||||
import pp.battleship.message.client.ClientMessage;
|
import pp.battleship.message.client.ClientMessage;
|
||||||
import pp.battleship.message.client.MapMessage;
|
import pp.battleship.message.client.MapMessage;
|
||||||
import pp.battleship.message.client.ShootMessage;
|
import pp.battleship.message.client.ShootMessage;
|
||||||
@@ -130,6 +131,7 @@ private void initializeSerializables() {
|
|||||||
Serializer.registerClass(MapMessage.class);
|
Serializer.registerClass(MapMessage.class);
|
||||||
Serializer.registerClass(ShootMessage.class);
|
Serializer.registerClass(ShootMessage.class);
|
||||||
Serializer.registerClass(EffectMessage.class);
|
Serializer.registerClass(EffectMessage.class);
|
||||||
|
Serializer.registerClass(AnimationFinishedMessage.class);
|
||||||
Serializer.registerClass(Battleship.class);
|
Serializer.registerClass(Battleship.class);
|
||||||
Serializer.registerClass(IntPoint.class);
|
Serializer.registerClass(IntPoint.class);
|
||||||
Serializer.registerClass(Shot.class);
|
Serializer.registerClass(Shot.class);
|
||||||
@@ -141,6 +143,7 @@ private void initializeSerializables() {
|
|||||||
private void registerListeners() {
|
private void registerListeners() {
|
||||||
myServer.addMessageListener(this, MapMessage.class);
|
myServer.addMessageListener(this, MapMessage.class);
|
||||||
myServer.addMessageListener(this, ShootMessage.class);
|
myServer.addMessageListener(this, ShootMessage.class);
|
||||||
|
myServer.addMessageListener(this, AnimationFinishedMessage.class);
|
||||||
myServer.addConnectionListener(this);
|
myServer.addConnectionListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
* and interaction between the model and the view.
|
* and interaction between the model and the view.
|
||||||
*/
|
*/
|
||||||
class MapView {
|
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 GRID_LINE_WIDTH = 2f;
|
||||||
private static final float BACKGROUND_DEPTH = -4f;
|
private static final float BACKGROUND_DEPTH = -4f;
|
||||||
private static final float GRID_DEPTH = -1f;
|
private static final float GRID_DEPTH = -1f;
|
||||||
|
|||||||
@@ -7,14 +7,21 @@
|
|||||||
|
|
||||||
package pp.battleship.client.gui;
|
package pp.battleship.client.gui;
|
||||||
|
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.material.RenderState;
|
||||||
|
import com.jme3.material.RenderState.BlendMode;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.shape.Sphere;
|
||||||
import pp.battleship.model.Battleship;
|
import pp.battleship.model.Battleship;
|
||||||
|
import pp.battleship.model.Shell;
|
||||||
import pp.battleship.model.Shot;
|
import pp.battleship.model.Shot;
|
||||||
import pp.util.Position;
|
import pp.util.Position;
|
||||||
|
|
||||||
|
import static com.jme3.material.Materials.UNSHADED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes the visual representation of the ship map with the game model.
|
* Synchronizes the visual representation of the ship map with the game model.
|
||||||
* It handles the rendering of ships and shots on the map view, updating the view
|
* It handles the rendering of ships and shots on the map view, updating the view
|
||||||
@@ -25,6 +32,7 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
|
|||||||
private static final float SHIP_LINE_WIDTH = 6f;
|
private static final float SHIP_LINE_WIDTH = 6f;
|
||||||
private static final float SHOT_DEPTH = -2f;
|
private static final float SHOT_DEPTH = -2f;
|
||||||
private static final float SHIP_DEPTH = 0f;
|
private static final float SHIP_DEPTH = 0f;
|
||||||
|
private static final float SHELL_DEPTH = 1f;
|
||||||
private static final float INDENT = 4f;
|
private static final float INDENT = 4f;
|
||||||
|
|
||||||
// Colors used for different visual elements
|
// Colors used for different visual elements
|
||||||
@@ -109,6 +117,26 @@ public Spatial visit(Battleship ship) {
|
|||||||
return shipNode;
|
return shipNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a line geometry representing part of the ship's border.
|
* Creates a line geometry representing part of the ship's border.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState.BlendMode;
|
import com.jme3.material.RenderState.BlendMode;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.math.Vector3f;
|
|
||||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
|
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
@@ -20,10 +19,15 @@
|
|||||||
import pp.battleship.client.BattleshipApp;
|
import pp.battleship.client.BattleshipApp;
|
||||||
import pp.battleship.model.Battleship;
|
import pp.battleship.model.Battleship;
|
||||||
import pp.battleship.model.Rotation;
|
import pp.battleship.model.Rotation;
|
||||||
|
import pp.battleship.model.Shell;
|
||||||
import pp.battleship.model.ShipMap;
|
import pp.battleship.model.ShipMap;
|
||||||
import pp.battleship.model.Shot;
|
import pp.battleship.model.Shot;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static pp.JmeUtil.mapToWorldCord;
|
||||||
import static pp.util.FloatMath.HALF_PI;
|
import static pp.util.FloatMath.HALF_PI;
|
||||||
import static pp.util.FloatMath.PI;
|
import static pp.util.FloatMath.PI;
|
||||||
|
|
||||||
@@ -89,11 +93,16 @@ private Spatial handleHit(Shot shot) {
|
|||||||
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
|
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
|
||||||
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
|
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
|
||||||
|
|
||||||
shipNode.getControl(ShipEffectControl.class).hit(shot);
|
shipNode.getControl(ShipControl.class).hit(shot);
|
||||||
if (ship.isDestroyed()) {
|
if (ship.isDestroyed()) {
|
||||||
shipNode.getControl(ShipEffectControl.class).destroyed();
|
shipNode.getControl(ShipControl.class).destroyed();
|
||||||
shipNode.getControl(ShipMovementControl.class).destroyed();
|
|
||||||
app.setTimer(9,()->handleShipDestroy(shipNode));
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
handleShipDestroy(shipNode);
|
||||||
|
}
|
||||||
|
}, 9000);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -102,11 +111,6 @@ private void handleShipDestroy(Node shipNode) {
|
|||||||
shipNode.getParent().detachChild(shipNode);
|
shipNode.getParent().detachChild(shipNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3f mapToWorldCord(int x, int y){
|
|
||||||
return new Vector3f(y+0.5f, 0, x+0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a cylinder geometry representing the specified shot.
|
* Creates a cylinder geometry representing the specified shot.
|
||||||
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
|
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
|
||||||
@@ -145,11 +149,31 @@ public Spatial visit(Battleship ship) {
|
|||||||
final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
|
final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
|
||||||
final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
|
final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
|
||||||
node.setLocalTranslation(x, 0f, z);
|
node.setLocalTranslation(x, 0f, z);
|
||||||
node.addControl(new ShipMovementControl(ship));
|
node.addControl(new ShipControl(ship, node, app.getEffectHandler()));
|
||||||
node.addControl(new ShipEffectControl(node, ship, app));
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a 3D model representation of the given {@code Shell} object
|
||||||
|
* for visualization in the game.
|
||||||
|
*
|
||||||
|
* @param shell The {@code Shell} object to be visualized.
|
||||||
|
* @return A {@code Spatial} object representing the 3D model of the shell.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the appropriate graphical representation of the specified battleship.
|
* Creates the appropriate graphical representation of the specified battleship.
|
||||||
* The representation is either a detailed model or a simple box based on the length of the ship.
|
* The representation is either a detailed model or a simple box based on the length of the ship.
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.JmeUtil.mapToWorldCord;
|
||||||
|
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 = mapToWorldCord(pos.x, pos.z);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
////////////////////////////////////////
|
||||||
|
// Programming project code
|
||||||
|
// UniBw M, 2022, 2023, 2024
|
||||||
|
// www.unibw.de/inf2
|
||||||
|
// (c) Mark Minas (mark.minas@unibw.de)
|
||||||
|
////////////////////////////////////////
|
||||||
|
|
||||||
|
package pp.battleship.client.gui;
|
||||||
|
|
||||||
|
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.control.AbstractControl;
|
||||||
|
import pp.battleship.client.EffectHandler;
|
||||||
|
import pp.battleship.model.Battleship;
|
||||||
|
import pp.battleship.model.Shot;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
import static pp.JmeUtil.mapToWorldCord;
|
||||||
|
import static pp.util.FloatMath.DEG_TO_RAD;
|
||||||
|
import static pp.util.FloatMath.TWO_PI;
|
||||||
|
import static pp.util.FloatMath.sin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls the pitch oscillation and sinking behavior of a {@code Battleship} model in the game.
|
||||||
|
* The ship tilts back and forth to simulate movement on water, and can also be animated to sink
|
||||||
|
* when destroyed.
|
||||||
|
*/
|
||||||
|
class ShipControl extends AbstractControl {
|
||||||
|
private static final float SINK_SPEED = 0.04f;
|
||||||
|
private static final float SINK_ROT_SPEED = 0.1f;
|
||||||
|
|
||||||
|
// The axis of rotation for the ship's pitch (tilting).
|
||||||
|
private final Vector3f axis;
|
||||||
|
|
||||||
|
// The duration of one oscillation cycle in seconds.
|
||||||
|
private final float cycle;
|
||||||
|
|
||||||
|
// The amplitude of the pitch oscillation in radians.
|
||||||
|
private final float amplitude;
|
||||||
|
|
||||||
|
// Quaternion representing the ship's pitch rotation.
|
||||||
|
private final Quaternion pitch = new Quaternion();
|
||||||
|
|
||||||
|
// The current time within the oscillation cycle.
|
||||||
|
private float time;
|
||||||
|
|
||||||
|
// Flag indicating if the ship is sinking.
|
||||||
|
private boolean sinking;
|
||||||
|
|
||||||
|
// The battleship being controlled.
|
||||||
|
private final Battleship battleship;
|
||||||
|
|
||||||
|
// Node representing the ship in the scene graph.
|
||||||
|
private final Node shipNode;
|
||||||
|
|
||||||
|
// Handles visual effects for the ship.
|
||||||
|
private final EffectHandler effectHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code ShipControl} instance to manage the effects for a specified {@code Battleship}.
|
||||||
|
* The ship's orientation determines the axis of rotation, and its length affects the oscillation cycle
|
||||||
|
* and amplitude.
|
||||||
|
*
|
||||||
|
* @param battleship The {@code Battleship} being controlled.
|
||||||
|
* @param shipNode The scene graph node representing the ship.
|
||||||
|
* @param effectHandler The {@code EffectHandler} for creating visual effects.
|
||||||
|
*/
|
||||||
|
public ShipControl(Battleship battleship, Node shipNode, EffectHandler effectHandler) {
|
||||||
|
this.battleship = battleship;
|
||||||
|
this.shipNode = shipNode;
|
||||||
|
this.effectHandler = effectHandler;
|
||||||
|
|
||||||
|
sinking = false;
|
||||||
|
// Determine the axis of rotation based on the ship's orientation.
|
||||||
|
axis = switch (battleship.getRot()) {
|
||||||
|
case LEFT, RIGHT -> Vector3f.UNIT_X;
|
||||||
|
case UP, DOWN -> Vector3f.UNIT_Z;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the cycle duration and amplitude based on the ship's length.
|
||||||
|
cycle = battleship.getLength() * 2f;
|
||||||
|
amplitude = 5f * DEG_TO_RAD / battleship.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the ship's motion. If the ship is sinking, it animates the sinking process.
|
||||||
|
* Otherwise, it oscillates the ship to simulate wave motion.
|
||||||
|
*
|
||||||
|
* @param tpf Time per frame, used to update the ship's motion.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void controlUpdate(float tpf) {
|
||||||
|
if (sinking) {
|
||||||
|
handleSinking(tpf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handlePitch(tpf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the sinking animation.
|
||||||
|
private void handleSinking(float tpf) {
|
||||||
|
if (spatial == null) return;
|
||||||
|
|
||||||
|
spatial.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, -1, 0).mult(tpf * SINK_SPEED)));
|
||||||
|
spatial.rotate(tpf * SINK_ROT_SPEED, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the pitch oscillation to simulate wave movement.
|
||||||
|
private void handlePitch(float tpf) {
|
||||||
|
if (spatial == null) return;
|
||||||
|
|
||||||
|
// Update time in the oscillation cycle.
|
||||||
|
time = (time + tpf) % cycle;
|
||||||
|
|
||||||
|
// Calculate the pitch angle.
|
||||||
|
float angle = amplitude * sin(time * TWO_PI / cycle);
|
||||||
|
|
||||||
|
// Update pitch rotation.
|
||||||
|
pitch.fromAngleAxis(angle, axis);
|
||||||
|
|
||||||
|
// Apply rotation to the spatial.
|
||||||
|
spatial.setLocalRotation(pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||||
|
// No rendering-specific behavior required.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the ship's sinking animation and schedules its destruction.
|
||||||
|
*/
|
||||||
|
public void destroyed() {
|
||||||
|
sinking = true;
|
||||||
|
shipNode.attachChild(effectHandler.debrisSplash(shipNode.getLocalTranslation()));
|
||||||
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
effectHandler.destroyShip(battleship);
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers an effect when the ship is hit by a shot, creating a fire effect at the impact location.
|
||||||
|
*
|
||||||
|
* @param shot The shot that hit the ship.
|
||||||
|
*/
|
||||||
|
public void hit(Shot shot) {
|
||||||
|
Vector3f shipNodePos = shipNode.getLocalTranslation();
|
||||||
|
Vector3f shotWorld = mapToWorldCord(shot.getX(), shot.getY());
|
||||||
|
Vector3f firePos = shotWorld.subtract(shipNodePos);
|
||||||
|
shipNode.attachChild(effectHandler.createFire(firePos, battleship));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package pp.battleship.client.gui;
|
|
||||||
|
|
||||||
import com.jme3.math.Vector3f;
|
|
||||||
import com.jme3.renderer.RenderManager;
|
|
||||||
import com.jme3.renderer.ViewPort;
|
|
||||||
import com.jme3.scene.Node;
|
|
||||||
import com.jme3.scene.control.AbstractControl;
|
|
||||||
import pp.battleship.client.BattleshipApp;
|
|
||||||
import pp.battleship.client.EffectHandler;
|
|
||||||
import pp.battleship.model.Battleship;
|
|
||||||
import pp.battleship.model.Shot;
|
|
||||||
|
|
||||||
public class ShipEffectControl extends AbstractControl {
|
|
||||||
private final Node shipNode;
|
|
||||||
private final Battleship battleship;
|
|
||||||
private final BattleshipApp app;
|
|
||||||
|
|
||||||
public ShipEffectControl(Node node, Battleship battleship, BattleshipApp app){
|
|
||||||
this.shipNode = node;
|
|
||||||
this.battleship = battleship;
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void controlUpdate(float tpf) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hit(Shot shot){
|
|
||||||
Vector3f shipNodePos = shipNode.getLocalTranslation();
|
|
||||||
Vector3f shotWorld = mapToWorldCord(shot.getX(),shot.getY());
|
|
||||||
Vector3f firePos = shotWorld.subtract(shipNodePos);
|
|
||||||
shipNode.attachChild(app.getEffectHandler().createFire(firePos, battleship));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3f mapToWorldCord(int x, int y){
|
|
||||||
return new Vector3f(y+0.5f, 0, x+0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyed() {
|
|
||||||
shipNode.attachChild(app.getEffectHandler().debrisSplash(shipNode.getLocalTranslation()));
|
|
||||||
app.setTimer(4,this::stopEffects);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopEffects(){
|
|
||||||
app.getEffectHandler().destroyShip(battleship);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
////////////////////////////////////////
|
|
||||||
// Programming project code
|
|
||||||
// UniBw M, 2022, 2023, 2024
|
|
||||||
// www.unibw.de/inf2
|
|
||||||
// (c) Mark Minas (mark.minas@unibw.de)
|
|
||||||
////////////////////////////////////////
|
|
||||||
|
|
||||||
package pp.battleship.client.gui;
|
|
||||||
|
|
||||||
import com.jme3.math.Quaternion;
|
|
||||||
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.Battleship;
|
|
||||||
|
|
||||||
import static pp.util.FloatMath.DEG_TO_RAD;
|
|
||||||
import static pp.util.FloatMath.TWO_PI;
|
|
||||||
import static pp.util.FloatMath.sin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls the oscillating pitch motion of a battleship model in the game.
|
|
||||||
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
|
|
||||||
*/
|
|
||||||
class ShipMovementControl extends AbstractControl {
|
|
||||||
private final float sinkingSpeed = 0.04f;
|
|
||||||
private final float sinkRotSpeed = 0.1f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The axis of rotation for the ship's pitch (tilting forward and backward).
|
|
||||||
*/
|
|
||||||
private final Vector3f axis;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The duration of one complete oscillation cycle in seconds.
|
|
||||||
*/
|
|
||||||
private final float cycle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amplitude of the pitch oscillation in radians, determining how much the ship tilts.
|
|
||||||
*/
|
|
||||||
private final float amplitude;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A quaternion representing the ship's current pitch rotation.
|
|
||||||
*/
|
|
||||||
private final Quaternion pitch = new Quaternion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current time within the oscillation cycle, used to calculate the ship's pitch angle.
|
|
||||||
*/
|
|
||||||
private float time;
|
|
||||||
|
|
||||||
private boolean sinking;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new ShipMovementControl instance for the specified Battleship.
|
|
||||||
* The ship's orientation determines the axis of rotation, while its length influences
|
|
||||||
* the cycle duration and amplitude of the oscillation.
|
|
||||||
*
|
|
||||||
* @param ship the Battleship object to control
|
|
||||||
*/
|
|
||||||
public ShipMovementControl(Battleship ship) {
|
|
||||||
sinking = false;
|
|
||||||
// Determine the axis of rotation based on the ship's orientation
|
|
||||||
axis = switch (ship.getRot()) {
|
|
||||||
case LEFT, RIGHT -> Vector3f.UNIT_X;
|
|
||||||
case UP, DOWN -> Vector3f.UNIT_Z;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the cycle duration and amplitude based on the ship's length
|
|
||||||
cycle = ship.getLength() * 2f;
|
|
||||||
amplitude = 5f * DEG_TO_RAD / ship.getLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted
|
|
||||||
* to create a continuous tilting motion, simulating the effect of waves.
|
|
||||||
*
|
|
||||||
* @param tpf time per frame (in seconds), used to calculate the new pitch angle
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void controlUpdate(float tpf) {
|
|
||||||
if(sinking) handleSinking(tpf);
|
|
||||||
else handlePitch(tpf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSinking(float tpf) {
|
|
||||||
if (spatial == null) return;
|
|
||||||
|
|
||||||
spatial.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,-1,0).mult(tpf * sinkingSpeed)));
|
|
||||||
spatial.rotate(tpf * sinkRotSpeed, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePitch(float tpf){
|
|
||||||
// If spatial is null, do nothing
|
|
||||||
if (spatial == null) return;
|
|
||||||
|
|
||||||
// Update the time within the oscillation cycle
|
|
||||||
time = (time + tpf) % cycle;
|
|
||||||
|
|
||||||
// Calculate the current angle of the oscillation
|
|
||||||
final float angle = amplitude * sin(time * TWO_PI / cycle);
|
|
||||||
|
|
||||||
// Update the pitch Quaternion with the new angle
|
|
||||||
pitch.fromAngleAxis(angle, axis);
|
|
||||||
|
|
||||||
// Apply the pitch rotation to the spatial
|
|
||||||
spatial.setLocalRotation(pitch);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyed() {
|
|
||||||
sinking = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
@@ -41,7 +41,7 @@ public static void main(String[] args) {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void simpleInitApp() {
|
public void simpleInitApp() {
|
||||||
export("Models/4/ship4.obj", "ship4.j3o"); //NON-NLS
|
export("shell.obj", "shell.j3o"); //NON-NLS
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|||||||
42
Projekte/battleship/converter/src/main/resources/shell.mtl
Normal file
42
Projekte/battleship/converter/src/main/resources/shell.mtl
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Blender 3.6.5 MTL File: 'untitled.blend'
|
||||||
|
# www.blender.org
|
||||||
|
|
||||||
|
newmtl base
|
||||||
|
Ns 467.358765
|
||||||
|
Ka 0.636364 0.636364 0.636364
|
||||||
|
Kd 0.000000 0.000000 0.000000
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
|
|
||||||
|
newmtl ring
|
||||||
|
Ns 467.358765
|
||||||
|
Ka 0.636364 0.636364 0.636364
|
||||||
|
Kd 0.031430 0.012811 0.000000
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
|
|
||||||
|
newmtl tip
|
||||||
|
Ns 467.358765
|
||||||
|
Ka 0.636364 0.636364 0.636364
|
||||||
|
Kd 0.032954 0.004269 0.000000
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
|
|
||||||
|
newmtl top
|
||||||
|
Ns 467.358765
|
||||||
|
Ka 0.636364 0.636364 0.636364
|
||||||
|
Kd 0.000489 0.006614 0.000950
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
8628
Projekte/battleship/converter/src/main/resources/shell.obj
Normal file
8628
Projekte/battleship/converter/src/main/resources/shell.obj
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
import pp.battleship.message.client.ShootMessage;
|
import pp.battleship.message.client.ShootMessage;
|
||||||
import pp.battleship.message.server.EffectMessage;
|
import pp.battleship.message.server.EffectMessage;
|
||||||
import pp.battleship.model.Battleship;
|
|
||||||
import pp.battleship.model.IntPoint;
|
import pp.battleship.model.IntPoint;
|
||||||
|
import pp.battleship.model.Shell;
|
||||||
import pp.battleship.model.ShipMap;
|
import pp.battleship.model.ShipMap;
|
||||||
import pp.battleship.notification.Sound;
|
import pp.battleship.notification.Sound;
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ class BattleState extends ClientState {
|
|||||||
*/
|
*/
|
||||||
public BattleState(ClientGameLogic logic, boolean myTurn) {
|
public BattleState(ClientGameLogic logic, boolean myTurn) {
|
||||||
super(logic);
|
super(logic);
|
||||||
|
System.out.println("battle state");
|
||||||
this.myTurn = myTurn;
|
this.myTurn = myTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,18 +55,14 @@ else if (logic.getOpponentMap().isValid(pos))
|
|||||||
@Override
|
@Override
|
||||||
public void receivedEffect(EffectMessage msg) {
|
public void receivedEffect(EffectMessage msg) {
|
||||||
ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
|
ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
|
||||||
playSound(msg);
|
|
||||||
myTurn = msg.isMyTurn();
|
myTurn = msg.isMyTurn();
|
||||||
logic.setInfoText(msg.getInfoTextKey());
|
logic.setInfoText(msg.getInfoTextKey());
|
||||||
affectedMap(msg).add(msg.getShot());
|
|
||||||
if (destroyedOpponentShip(msg))
|
Shell shell = new Shell(msg.getShot());
|
||||||
logic.getOpponentMap().add(msg.getDestroyedShip());
|
affectedMap(msg).add(shell);
|
||||||
if (msg.isGameOver()) {
|
logic.playSound(Sound.SHELL_FLYING);
|
||||||
for(Battleship ship: msg.getRemainingOpponentShips()){
|
logic.setState(new ShootingState(logic, shell, myTurn, msg));
|
||||||
logic.getOpponentMap().add(ship);
|
|
||||||
}
|
|
||||||
logic.setState(new GameOverState(logic));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,29 +74,4 @@ public void receivedEffect(EffectMessage msg) {
|
|||||||
private ShipMap affectedMap(EffectMessage msg) {
|
private ShipMap affectedMap(EffectMessage msg) {
|
||||||
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
|
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the opponent's ship was destroyed by the player's shot.
|
|
||||||
*
|
|
||||||
* @param msg the effect message received from the server
|
|
||||||
* @return true if the shot destroyed an opponent's ship, false otherwise
|
|
||||||
*/
|
|
||||||
private boolean destroyedOpponentShip(EffectMessage msg) {
|
|
||||||
return msg.getDestroyedShip() != null && msg.isOwnShot();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
|
|
||||||
* or destruction of a ship.
|
|
||||||
*
|
|
||||||
* @param msg the effect message containing the result of the shot
|
|
||||||
*/
|
|
||||||
private void playSound(EffectMessage msg) {
|
|
||||||
if (!msg.getShot().isHit())
|
|
||||||
logic.playSound(Sound.SPLASH);
|
|
||||||
else if (msg.getDestroyedShip() == null)
|
|
||||||
logic.playSound(Sound.EXPLOSION);
|
|
||||||
else
|
|
||||||
logic.playSound(Sound.DESTROYED_SHIP);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package pp.battleship.game.client;
|
||||||
|
|
||||||
|
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||||
|
import pp.battleship.message.server.EffectMessage;
|
||||||
|
import pp.battleship.model.Battleship;
|
||||||
|
import pp.battleship.model.Shell;
|
||||||
|
import pp.battleship.model.ShipMap;
|
||||||
|
import pp.battleship.notification.Sound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the shooting state of the game where a shell is fired at the opponent.
|
||||||
|
*/
|
||||||
|
public class ShootingState extends ClientState {
|
||||||
|
private float shootValue;
|
||||||
|
private final static float SHELL_SPEED = 0.3f;
|
||||||
|
private final Shell shell;
|
||||||
|
private final boolean myTurn;
|
||||||
|
private final EffectMessage msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a shooting state with the specified game logic.
|
||||||
|
*
|
||||||
|
* @param logic the game logic
|
||||||
|
* @param shell the shell being shot
|
||||||
|
* @param myTurn indicates if it is the player's turn
|
||||||
|
* @param msg the effect message associated with the shooting action
|
||||||
|
*/
|
||||||
|
public ShootingState(ClientGameLogic logic, Shell shell, boolean myTurn, EffectMessage msg) {
|
||||||
|
super(logic);
|
||||||
|
System.out.println("shooting state");
|
||||||
|
this.msg = msg;
|
||||||
|
this.myTurn = myTurn;
|
||||||
|
this.shell = shell;
|
||||||
|
this.shootValue = 0;
|
||||||
|
shell.move(shootValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showBattle() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the shooting state by moving the shell based on the elapsed time.
|
||||||
|
*
|
||||||
|
* @param delta the time in seconds since the last update
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void update(float delta) {
|
||||||
|
super.update(delta);
|
||||||
|
if (shootValue > 1) {
|
||||||
|
endState();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
shootValue += delta * SHELL_SPEED;
|
||||||
|
shell.move(shootValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends the shooting state and processes the effects of the shot.
|
||||||
|
*/
|
||||||
|
private void endState() {
|
||||||
|
playSound(msg);
|
||||||
|
affectedMap(msg).add(msg.getShot());
|
||||||
|
affectedMap(msg).remove(shell);
|
||||||
|
|
||||||
|
if (destroyedOpponentShip(msg))
|
||||||
|
logic.getOpponentMap().add(msg.getDestroyedShip());
|
||||||
|
if (msg.isGameOver()) {
|
||||||
|
for (Battleship ship : msg.getRemainingOpponentShips()) {
|
||||||
|
logic.getOpponentMap().add(ship);
|
||||||
|
}
|
||||||
|
logic.setState(new GameOverState(logic));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logic.send(new AnimationFinishedMessage());
|
||||||
|
logic.setState(new BattleState(logic, myTurn));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an opponent's ship was destroyed by the shot.
|
||||||
|
*
|
||||||
|
* @param msg the effect message containing the shot details
|
||||||
|
* @return true if an opponent's ship was destroyed, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean destroyedOpponentShip(EffectMessage msg) {
|
||||||
|
return msg.getDestroyedShip() != null && msg.isOwnShot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the affected map based on whether the shot was owned by the player or the opponent.
|
||||||
|
*
|
||||||
|
* @param msg the effect message containing shot details
|
||||||
|
* @return the ShipMap that was affected by the shot
|
||||||
|
*/
|
||||||
|
private ShipMap affectedMap(EffectMessage msg) {
|
||||||
|
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
|
||||||
|
* or destruction of a ship.
|
||||||
|
*
|
||||||
|
* @param msg the effect message containing the result of the shot
|
||||||
|
*/
|
||||||
|
private void playSound(EffectMessage msg) {
|
||||||
|
if (!msg.getShot().isHit())
|
||||||
|
logic.playSound(Sound.SPLASH);
|
||||||
|
else if (msg.getDestroyedShip() == null)
|
||||||
|
logic.playSound(Sound.EXPLOSION);
|
||||||
|
else
|
||||||
|
logic.playSound(Sound.DESTROYED_SHIP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
package pp.battleship.game.server;
|
package pp.battleship.game.server;
|
||||||
|
|
||||||
import pp.battleship.BattleshipConfig;
|
import pp.battleship.BattleshipConfig;
|
||||||
|
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||||
import pp.battleship.message.client.ClientInterpreter;
|
import pp.battleship.message.client.ClientInterpreter;
|
||||||
import pp.battleship.message.client.MapMessage;
|
import pp.battleship.message.client.MapMessage;
|
||||||
import pp.battleship.message.client.ShootMessage;
|
import pp.battleship.message.client.ShootMessage;
|
||||||
@@ -38,6 +39,7 @@ public class ServerGameLogic implements ClientInterpreter {
|
|||||||
private final ServerSender serverSender;
|
private final ServerSender serverSender;
|
||||||
private Player activePlayer;
|
private Player activePlayer;
|
||||||
private ServerState state = ServerState.WAIT;
|
private ServerState state = ServerState.WAIT;
|
||||||
|
private Set<Player> waitPlayers = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a ServerGameLogic with the specified sender and configuration.
|
* Constructs a ServerGameLogic with the specified sender and configuration.
|
||||||
@@ -153,6 +155,30 @@ public void received(MapMessage msg, int from) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the reception of a AnimationFinishedMessage.
|
||||||
|
*
|
||||||
|
* @param msg the received MapMessage
|
||||||
|
* @param from the ID of the sender client
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void received(AnimationFinishedMessage msg, int from) {
|
||||||
|
if (state != ServerState.ANIMATION) {
|
||||||
|
LOGGER.log(Level.ERROR, "animation finished not allowed in {0}", state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Player player = getPlayerById(from);
|
||||||
|
if (!waitPlayers.add(player)) {
|
||||||
|
LOGGER.log(Level.ERROR, "{0} already sent animation finished", player); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (waitPlayers.size() == 2) {
|
||||||
|
waitPlayers = new HashSet<>();
|
||||||
|
setState(ServerState.BATTLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the placement of battleships on the map.
|
* Validates the placement of battleships on the map.
|
||||||
* Ensures that:
|
* Ensures that:
|
||||||
@@ -244,6 +270,7 @@ void shoot(Player p, IntPoint pos) {
|
|||||||
send(activePlayer, EffectMessage.won(pos, selectedShip));
|
send(activePlayer, EffectMessage.won(pos, selectedShip));
|
||||||
send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
|
send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
|
||||||
setState(ServerState.GAME_OVER);
|
setState(ServerState.GAME_OVER);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (selectedShip.isDestroyed()) {
|
else if (selectedShip.isDestroyed()) {
|
||||||
// ship has been destroyed, but game is not yet over
|
// ship has been destroyed, but game is not yet over
|
||||||
@@ -256,5 +283,6 @@ else if (selectedShip.isDestroyed()) {
|
|||||||
send(otherPlayer, EffectMessage.hit(false, pos));
|
send(otherPlayer, EffectMessage.hit(false, pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setState(ServerState.ANIMATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ enum ServerState {
|
|||||||
*/
|
*/
|
||||||
BATTLE,
|
BATTLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server is waiting for all clients to finish the shoot animation.
|
||||||
|
*/
|
||||||
|
ANIMATION,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The game has ended because all the ships of one player have been destroyed.
|
* The game has ended because all the ships of one player have been destroyed.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
package pp.battleship.game.singlemode;
|
package pp.battleship.game.singlemode;
|
||||||
|
|
||||||
|
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||||
import pp.battleship.message.client.ClientInterpreter;
|
import pp.battleship.message.client.ClientInterpreter;
|
||||||
import pp.battleship.message.client.ClientMessage;
|
import pp.battleship.message.client.ClientMessage;
|
||||||
import pp.battleship.message.client.MapMessage;
|
import pp.battleship.message.client.MapMessage;
|
||||||
@@ -63,6 +64,11 @@ public void received(MapMessage msg, int from) {
|
|||||||
copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
|
copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void received(AnimationFinishedMessage msg, int from) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the provided {@link Battleship}.
|
* Creates a copy of the provided {@link Battleship}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package pp.battleship.message.client;
|
||||||
|
|
||||||
|
import com.jme3.network.serializing.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a message indicating that an animation has finished on the client side.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public class AnimationFinishedMessage extends ClientMessage {
|
||||||
|
public AnimationFinishedMessage() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a visitor to process this message.
|
||||||
|
*
|
||||||
|
* @param interpreter the visitor to process this message
|
||||||
|
* @param from the connection ID from which the message was received
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void accept(ClientInterpreter interpreter, int from) {
|
||||||
|
interpreter.received(this, from);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,4 +26,12 @@ public interface ClientInterpreter {
|
|||||||
* @param from the connection ID from which the message was received
|
* @param from the connection ID from which the message was received
|
||||||
*/
|
*/
|
||||||
void received(MapMessage msg, int from);
|
void received(MapMessage msg, int from);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a received AnimationFinishedMessage.
|
||||||
|
*
|
||||||
|
* @param msg the MapMessage to be processed
|
||||||
|
* @param from the connection ID from which the message was received
|
||||||
|
*/
|
||||||
|
void received(AnimationFinishedMessage msg, int from);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package pp.battleship.model;
|
||||||
|
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code Shell} class represents a projectile fired by a ship in the Battleship game.
|
||||||
|
* It models the position and rotation of the projectile and allows for its movement along
|
||||||
|
* a Bezier curve.
|
||||||
|
*/
|
||||||
|
public class Shell implements Item {
|
||||||
|
/**
|
||||||
|
* Initial position of the shell
|
||||||
|
*/
|
||||||
|
private final static Vector3f INIT_POS = new Vector3f(-3, 7, -3);
|
||||||
|
/**
|
||||||
|
* The overshot difference vector used to get a shallower flight path
|
||||||
|
*/
|
||||||
|
private final static Vector3f OVER_SHOT_DIFF = new Vector3f(-1, -1, -1);
|
||||||
|
// Target shot position
|
||||||
|
private final Vector3f shotPosition;
|
||||||
|
// Position on top of shotPosition used for Bezier curve
|
||||||
|
private final Vector3f overShotPosition;
|
||||||
|
// Current position of the shell
|
||||||
|
private Vector3f position;
|
||||||
|
// Current rotation of the shell
|
||||||
|
private final Quaternion rotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code Shell} object using the given {@code Shot} target.
|
||||||
|
* The initial position, target position, and overshot position are calculated.
|
||||||
|
*
|
||||||
|
* @param shot The target {@code Shot} object containing the destination coordinates.
|
||||||
|
*/
|
||||||
|
public Shell(Shot shot) {
|
||||||
|
this.shotPosition = new Vector3f(shot.getX(), 0, shot.getY());
|
||||||
|
this.overShotPosition = new Vector3f(shotPosition.x, INIT_POS.y, shotPosition.z).add(OVER_SHOT_DIFF);
|
||||||
|
this.position = INIT_POS;
|
||||||
|
this.rotation = new Quaternion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current position of the shell.
|
||||||
|
*
|
||||||
|
* @return The current position as a {@code Vector3f}.
|
||||||
|
*/
|
||||||
|
public Vector3f getPosition() {
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current rotation of the shell.
|
||||||
|
*
|
||||||
|
* @return The current rotation as a {@code Quaternion}.
|
||||||
|
*/
|
||||||
|
public Quaternion getRotation() {
|
||||||
|
return this.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the shell along a Bezier curve based on the given time factor {@code t}.
|
||||||
|
* The position and rotation of the shell are updated.
|
||||||
|
*
|
||||||
|
* @param t The time factor between 0 and 1, representing the progress of the shell's flight.
|
||||||
|
*/
|
||||||
|
public void move(float t) {
|
||||||
|
if (t > 1f) t = 1f;
|
||||||
|
Vector3f newPosition = bezInt(INIT_POS, overShotPosition, shotPosition, t);
|
||||||
|
updateRotation(position, newPosition);
|
||||||
|
this.position = newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a quadratic Bezier interpolation between three points based on the time factor {@code t}.
|
||||||
|
*
|
||||||
|
* @param p1 The start position.
|
||||||
|
* @param p2 The overshot position.
|
||||||
|
* @param p3 The target position.
|
||||||
|
* @param t The time factor for interpolation.
|
||||||
|
* @return The interpolated position as a {@code Vector3f}.
|
||||||
|
*/
|
||||||
|
private Vector3f bezInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
|
||||||
|
Vector3f inA = linInt(p1, p2, t);
|
||||||
|
Vector3f inB = linInt(p2, p3, t);
|
||||||
|
return linInt(inA, inB, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs linear interpolation between two points {@code p1} and {@code p2} based on the time factor {@code t}.
|
||||||
|
*
|
||||||
|
* @param p1 The start position.
|
||||||
|
* @param p2 The end position.
|
||||||
|
* @param t The time factor for interpolation.
|
||||||
|
* @return The interpolated position as a {@code Vector3f}.
|
||||||
|
*/
|
||||||
|
private Vector3f linInt(Vector3f p1, Vector3f p2, float t) {
|
||||||
|
float x = p1.getX() + t * (p2.getX() - p1.getX());
|
||||||
|
float y = p1.getY() + t * (p2.getY() - p1.getY());
|
||||||
|
float z = p1.getZ() + t * (p2.getZ() - p1.getZ());
|
||||||
|
return new Vector3f(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the rotation of the shell to face the new position along its flight path.
|
||||||
|
*
|
||||||
|
* @param oldPos The previous position of the shell.
|
||||||
|
* @param newPos The new position of the shell.
|
||||||
|
*/
|
||||||
|
private void updateRotation(Vector3f oldPos, Vector3f newPos) {
|
||||||
|
Vector3f direction = newPos.subtract(oldPos).normalize();
|
||||||
|
if (direction.lengthSquared() > 0) {
|
||||||
|
this.rotation.lookAt(direction, Vector3f.UNIT_Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T accept(Visitor<T> visitor) {
|
||||||
|
return visitor.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(VoidVisitor visitor) {
|
||||||
|
visitor.visit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,15 @@ public void add(Shot shot) {
|
|||||||
addItem(shot);
|
addItem(shot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a shot on the map and triggers an item addition event.
|
||||||
|
*
|
||||||
|
* @param shell the shell to be registered on the map
|
||||||
|
*/
|
||||||
|
public void add(Shell shell) {
|
||||||
|
addItem(shell);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an item from the map and triggers an item removal event.
|
* Removes an item from the map and triggers an item removal event.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -28,4 +28,12 @@ public interface Visitor<T> {
|
|||||||
* @return the result of visiting the Battleship element
|
* @return the result of visiting the Battleship element
|
||||||
*/
|
*/
|
||||||
T visit(Battleship ship);
|
T visit(Battleship ship);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a Shell element.
|
||||||
|
*
|
||||||
|
* @param shell the Battleship element to visit
|
||||||
|
* @return the result of visiting the Battleship element
|
||||||
|
*/
|
||||||
|
T visit(Shell shell);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,11 @@ public interface VoidVisitor {
|
|||||||
* @param ship the Battleship element to visit
|
* @param ship the Battleship element to visit
|
||||||
*/
|
*/
|
||||||
void visit(Battleship ship);
|
void visit(Battleship ship);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a Shell element.
|
||||||
|
*
|
||||||
|
* @param shell the Battleship element to visit
|
||||||
|
*/
|
||||||
|
void visit(Shell shell);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,5 +22,9 @@ public enum Sound {
|
|||||||
/**
|
/**
|
||||||
* Sound of a ship being destroyed.
|
* Sound of a ship being destroyed.
|
||||||
*/
|
*/
|
||||||
DESTROYED_SHIP
|
DESTROYED_SHIP,
|
||||||
|
/**
|
||||||
|
* Sound of a shell in flight.
|
||||||
|
*/
|
||||||
|
SHELL_FLYING
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
import pp.battleship.game.server.Player;
|
import pp.battleship.game.server.Player;
|
||||||
import pp.battleship.game.server.ServerGameLogic;
|
import pp.battleship.game.server.ServerGameLogic;
|
||||||
import pp.battleship.game.server.ServerSender;
|
import pp.battleship.game.server.ServerSender;
|
||||||
|
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||||
import pp.battleship.message.client.ClientMessage;
|
import pp.battleship.message.client.ClientMessage;
|
||||||
import pp.battleship.message.client.MapMessage;
|
import pp.battleship.message.client.MapMessage;
|
||||||
import pp.battleship.message.client.ShootMessage;
|
import pp.battleship.message.client.ShootMessage;
|
||||||
@@ -115,6 +116,7 @@ private void initializeSerializables() {
|
|||||||
Serializer.registerClass(MapMessage.class);
|
Serializer.registerClass(MapMessage.class);
|
||||||
Serializer.registerClass(ShootMessage.class);
|
Serializer.registerClass(ShootMessage.class);
|
||||||
Serializer.registerClass(EffectMessage.class);
|
Serializer.registerClass(EffectMessage.class);
|
||||||
|
Serializer.registerClass(AnimationFinishedMessage.class);
|
||||||
Serializer.registerClass(Battleship.class);
|
Serializer.registerClass(Battleship.class);
|
||||||
Serializer.registerClass(IntPoint.class);
|
Serializer.registerClass(IntPoint.class);
|
||||||
Serializer.registerClass(Shot.class);
|
Serializer.registerClass(Shot.class);
|
||||||
@@ -123,6 +125,7 @@ private void initializeSerializables() {
|
|||||||
private void registerListeners() {
|
private void registerListeners() {
|
||||||
myServer.addMessageListener(this, MapMessage.class);
|
myServer.addMessageListener(this, MapMessage.class);
|
||||||
myServer.addMessageListener(this, ShootMessage.class);
|
myServer.addMessageListener(this, ShootMessage.class);
|
||||||
|
myServer.addMessageListener(this, AnimationFinishedMessage.class);
|
||||||
myServer.addConnectionListener(this);
|
myServer.addConnectionListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,6 @@
|
|||||||
|
|
||||||
package pp.util;
|
package pp.util;
|
||||||
|
|
||||||
//import com.jme3.app.Application;
|
|
||||||
//import com.jme3.asset.AssetLoadException;
|
|
||||||
//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.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import com.jme3.asset.AssetNotFoundException;
|
import com.jme3.asset.AssetNotFoundException;
|
||||||
import com.jme3.audio.AudioData;
|
import com.jme3.audio.AudioData;
|
||||||
import com.jme3.audio.AudioNode;
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
import pp.util.Util;
|
import pp.util.Util;
|
||||||
|
|
||||||
import java.lang.System.Logger;
|
import java.lang.System.Logger;
|
||||||
@@ -34,4 +35,8 @@ public static AudioNode loadSound(Application app, String name) {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Vector3f mapToWorldCord(float x, float y) {
|
||||||
|
return new Vector3f(y + 0.5f, 0, x + 0.5f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user