Merge branch 'dev/client' into 'development'

Client work of the Week

See merge request progproj/gruppen-ht24/Gruppe-01!4
This commit is contained in:
Hanno Fleischer
2024-11-17 11:24:45 +00:00
47 changed files with 1293 additions and 143 deletions

View File

@@ -1,18 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false"
nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp"/>
<module name="Projekte.mdga.client.main"/>
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
<configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp" />
<module name="Projekte.mdga.client.main" />
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties -ea" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.mdga.client.*"/>
<option name="ENABLED" value="true"/>
<option name="PATTERN" value="pp.mdga.client.Board.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true"/>
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,291 @@
package pp.mdga.client.Acoustic;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import java.util.*;
public class AcousticHandler {
private MdgaApp app;
private MdgaState state = MdgaState.NONE;
private boolean playGame = false;
private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
private NanoTimer trackTimer = new NanoTimer();
private boolean fading = false;
private NanoTimer fadeTimer = new NanoTimer();
private static final float FADE_DURATION = 3.0f;
private static final float CROSSFADE_DURATION = 1.5f;
private float mainVolume = 1.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
private GameMusic scheduled = null;
private GameMusic playing = null;
private ArrayList<GameSound> sounds = new ArrayList<>();
public AcousticHandler(MdgaApp app) {
this.app = app;
}
/**
* This method updates the acousticHandler and should be called every frame
*/
public void update() {
updateVolumeAndTrack();
if (playGame) {
updateGameTracks();
}
Iterator<GameSound> iterator = sounds.iterator();
while (iterator.hasNext()) {
GameSound s = iterator.next();
s.update(getSoundVolumeTotal());
if (!s.isPlaying()) {
iterator.remove();
}
}
}
/**
* This method instantly plays a sound
*
* @param sound the sound to be played
*/
public void playSound(MdgaSound sound) {
ArrayList<SoundAssetDelayVolume> assets = new ArrayList<SoundAssetDelayVolume>();
switch (sound) {
case LOST:
assets.add(new SoundAssetDelayVolume(SoundAsset.LOST, 1.0f, 0.0f));
break;
case VICTORY:
assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 2.0f));
break;
default:
break;
}
for (SoundAssetDelayVolume sawd : assets) {
GameSound gameSound = new GameSound(app, sawd.asset(), getSoundVolumeTotal(), sawd.subVolume(), sawd.delay());
sounds.add(gameSound);
}
}
/**
* This method fades the played music to fit the state.
*
* @param state the state of which the corresponding music should be played to be played
*/
public void playState(MdgaState state) {
if (this.state == state) {
return;
}
MusicAsset asset = null;
switch (state) {
case MAIN:
playGame = false;
asset = MusicAsset.MAIN_MENU;
break;
case LOBBY:
playGame = false;
asset = MusicAsset.LOBBY;
break;
case GAME:
addGameTracks();
playGame = true;
assert (gameTracks.size() > 0) : "no more game music available";
asset = gameTracks.remove(0);
break;
case CEREMONY:
playGame = false;
asset = MusicAsset.CEREMONY;
break;
}
assert (null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop());
}
/**
* Performs linear interpolation between two float values.
*
* @param start The starting value.
* @param end The ending value.
* @param t The interpolation factor, typically between 0 and 1.
* @return The interpolated value between start and end.
*/
private float lerp(float start, float end, float t) {
return start + t * (end - start);
}
/**
* Updates the current volume and handles track crossfading logic.
* This method is responsible for fading out the currently playing track,
* fading in the scheduled track, and handling crossfade between the two tracks.
*/
private void updateVolumeAndTrack() {
if (playing == null && scheduled != null && !fading) {
playing = scheduled;
scheduled = null;
playing.play();
return;
}
if (scheduled != null && !fading) {
fading = true;
fadeTimer.reset();
}
if (fading) {
float time = fadeTimer.getTimeInSeconds();
if (time <= FADE_DURATION) {
float t = Math.min(time / FADE_DURATION, 1.0f);
float oldVolume = lerp(1.0f, 0.0f, t);
if (playing != null) {
playing.update(getMusicVolumeTotal() * oldVolume);
}
}
if (time > FADE_DURATION && time <= FADE_DURATION + CROSSFADE_DURATION) {
float t = Math.min((time - FADE_DURATION) / CROSSFADE_DURATION, 1.0f);
float newVolume = lerp(0.0f, 1.0f, t);
if (!scheduled.isPlaying()) {
scheduled.play();
}
scheduled.update(getMusicVolumeTotal() * newVolume);
}
if (time > FADE_DURATION + CROSSFADE_DURATION) {
if (playing != null) {
playing.pause();
}
playing = scheduled;
scheduled = null;
fading = false;
}
}
else if (playing != null) {
playing.update(getMusicVolumeTotal());
}
}
/**
* Adds a list of game tracks to the gameTracks collection and shuffles them.
* This method adds predefined game tracks to the track list and shuffles the order.
*/
private void addGameTracks() {
Random random = new Random();
for (int i = 1; i <= 6; i++) {
gameTracks.add(MusicAsset.valueOf("GAME_" + i));
}
Collections.shuffle(gameTracks, random);
}
/**
* Updates the current game tracks. If the currently playing track is nearing its end,
* a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
*/
private void updateGameTracks() {
if (playing.nearEnd(10)) {
if (gameTracks.isEmpty()) {
addGameTracks();
}
}
if (playing != null && playing.nearEnd(3) && trackTimer.getTimeInSeconds() > 20) {
trackTimer.reset();
MusicAsset nextTrack = gameTracks.remove(0);
scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop());
}
}
/**
* Retrieves the main volume level.
*
* @return The current main volume level.
*/
public float getMainVolume() {
return mainVolume;
}
/**
* Retrieves the music volume level.
*
* @return The current music volume level.
*/
public float getMusicVolume() {
return musicVolume;
}
/**
* Retrieves the sound volume level.
*
* @return The current sound volume level.
*/
public float getSoundVolume() {
return soundVolume;
}
/**
* Sets the main volume level.
*
* @param mainVolume The desired main volume level.
*/
public void setMainVolume(float mainVolume) {
this.mainVolume = mainVolume;
}
/**
* Sets the music volume level.
*
* @param musicVolume The desired music volume level.
*/
public void setMusicVolume(float musicVolume) {
this.musicVolume = musicVolume;
}
/**
* Sets the sound volume level.
*
* @param soundVolume The desired sound volume level.
*/
public void setSoundVolume(float soundVolume) {
this.soundVolume = soundVolume;
}
/**
* Calculates the total music volume by multiplying the music volume by the main volume.
*
* @return The total music volume.
*/
float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume();
}
/**
* Calculates the total sound volume by multiplying the sound volume by the main volume.
*
* @return The total sound volume.
*/
float getSoundVolumeTotal() {
return getSoundVolume() * getMainVolume();
}
}

View File

@@ -0,0 +1,110 @@
package pp.mdga.client.Acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.mdga.client.MdgaApp;
/**
* Represents a game music track, including its playback controls and volume settings.
* This class manages the playback of a music track, allowing for playing, pausing,
* volume adjustment, and tracking the current status of the music.
*/
class GameMusic {
private float volume;
private final float subVolume;
private final AudioNode music;
/**
* Constructs a new GameMusic object.
*
* @param app The instance of the application, used to access the asset manager.
* @param asset The music asset to be played.
* @param volume The total volume of the music, adjusted by the main volume.
* @param subVolume A relative volume that modifies the base music volume, typically a percentage.
* @param loop A flag indicating whether the music should loop once it finishes.
*/
GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop) {
this.volume = volume;
this.subVolume = subVolume;
music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
music.setPositional(false);
music.setDirectional(false);
music.setVolume(volume * subVolume);
music.setLooping(loop);
}
/**
* Plays the current music track.
* If the music is already initialized, it starts playback.
* If the music is not available, no action is performed.
*/
void play() {
if (null == music) {
return;
}
music.play();
}
/**
* Pauses the current music track.
* If the music is not available or is not playing, no action is performed.
*/
void pause() {
if (null == music) {
return;
}
music.stop();
}
/**
* Checks if the current music track is playing.
*
* @return true if the music is playing, false otherwise.
*/
boolean isPlaying() {
return music.getStatus() == AudioSource.Status.Playing;
}
/**
* Checks if the current music track is near the end.
*
* @param thresholdSeconds The threshold in seconds. If the remaining time is less than or equal to this value,
* the track is considered near the end.
* @return true if the track is near its end (within the threshold), false otherwise.
*/
boolean nearEnd(float thresholdSeconds) {
if (music == null || !isPlaying()) {
return false;
}
float currentTime = music.getPlaybackTime(); // Current playback time in seconds
float duration = music.getAudioData().getDuration(); // Total duration in seconds
if (duration <= 0) {
return false;
}
float remainingTime = duration - currentTime;
return remainingTime <= thresholdSeconds;
}
/**
* Updates the volume of the music.
* If the volume has changed, it will adjust the music's volume accordingly.
*
* @param newVolume The new total volume for the music.
*/
void update(float newVolume) {
if (volume != newVolume) {
volume = newVolume;
music.setVolume(volume * subVolume);
}
}
}

View File

@@ -0,0 +1,83 @@
package pp.mdga.client.Acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp;
/**
* Represents a game sound effect, with control over playback, volume, and timing.
* This class manages the playback of a sound effect, including starting playback after a delay,
* adjusting volume, and tracking whether the sound has finished playing.
*/
class GameSound {
private float volume;
final private float subVolume;
private final AudioNode sound;
private boolean playing = false;
private boolean finished = false;
private float delay = 0.0f;
private NanoTimer timer = null;
/**
* Constructs a new GameSound object.
*
* @param app The instance of the application, used to access the asset manager.
* @param asset The sound asset to be played.
* @param volume The total volume of the sound, adjusted by the main volume.
* @param subVolume A relative volume that modifies the base sound volume, typically a percentage.
* @param delay The delay before the sound starts playing, in seconds.
*/
GameSound(MdgaApp app, SoundAsset asset, float volume, float subVolume, float delay) {
this.volume = volume;
this.subVolume = subVolume;
this.delay = delay;
sound = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Buffer);
sound.setPositional(false);
sound.setDirectional(false);
sound.setLooping(false);
sound.setVolume(volume * subVolume);
timer = new NanoTimer();
}
/**
* Checks if the sound is currently playing.
*
* @return true if the sound is playing, false otherwise.
*/
boolean isPlaying() {
return !finished;
}
/**
* Updates the sound playback, adjusting the volume if necessary, and starting
* the sound after the specified delay.
*
* @param newVolume The new total volume for the sound.
*/
void update(float newVolume) {
if (!playing && timer.getTimeInSeconds() > delay) {
sound.play();
playing = true;
}
if (!playing) {
return;
}
if (volume != newVolume) {
volume = newVolume;
sound.setVolume(volume * subVolume);
}
if (sound != null && sound.getStatus() == AudioSource.Status.Playing) {
finished = true;
}
}
}

View File

@@ -0,0 +1,23 @@
package pp.mdga.client.Acoustic;
/**
* Enum representing the various sound effects used in the game.
* Each sound corresponds to an event or action in the game and may consist of one or more
* audio files, potentially with time delays between them.
* <p>
* These sounds are used to play specific audio cues, such as when a dice is rolled,
* a turn starts or ends, a piece is moved or lost, and various other events in the game.
*/
public enum MdgaSound {
DICE_ROLL,
TURN_START,
TURN_END,
PIECE_END,
PIECE_MOVE,
PIECE_LOST,
SELECT,
DESELECT,
HURRY,
VICTORY,
LOST
}

View File

@@ -0,0 +1,76 @@
package pp.mdga.client.Acoustic;
/**
* Enum representing various music assets used in the game.
* Each constant corresponds to a specific music track, along with its properties such as file path,
* looping behavior, and relative volume (subVolume).
* These music assets are used to control the music that plays in different parts of the game, such as menus and in-game music.
*/
enum MusicAsset {
MAIN_MENU("Spaceship.wav", 1.0f),
LOBBY("DeadPlanet.wav", 1.0f),
CEREMONY("80s,Disco,Life.wav", 1.0f),
GAME_1("NeonRoadTrip.wav", false, 1.0f),
GAME_2("NoPressureTrance.wav", false, 1.0f),
GAME_3("TheSynthRave.wav", false, 1.0f),
GAME_4("LaserParty.wav", false, 1.0f),
GAME_5("RetroNoir.wav", false, 1.0f),
GAME_6("SpaceInvaders.wav", false, 1.0f);
private final String path;
private final boolean loop;
private final float subVolume;
/**
* Constructs a new MusicAsset object with the specified name and sub-volume.
* The track will not loop by default.
*
* @param name The name of the music file.
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
*/
MusicAsset(String name, float subVolume) {
this.path = "music/" + name;
this.loop = false;
this.subVolume = subVolume;
}
/**
* Constructs a new MusicAsset object with the specified name, loop flag, and sub-volume.
*
* @param name The name of the music file.
* @param loop If true, the track will loop; otherwise, it will play once.
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
*/
MusicAsset(String name, boolean loop, float subVolume) {
this.path = "music/" + name;
this.loop = loop;
this.subVolume = subVolume;
}
/**
* Gets the file path of the music track.
*
* @return The path to the music file (relative to the music folder).
*/
public String getPath() {
return path;
}
/**
* Gets whether the music track should loop.
*
* @return true if the track should loop, false otherwise.
*/
public boolean getLoop() {
return loop;
}
/**
* Gets the relative volume (subVolume) for the music track.
*
* @return The relative volume for the track, typically a value between 0.0 and 1.0.
*/
public float getSubVolume() {
return subVolume;
}
}

View File

@@ -0,0 +1,41 @@
package pp.mdga.client.Acoustic;
/**
* Enum representing various sound assets used in the game.
* Each constant corresponds to a specific sound effect used throughout the game.
* These sounds are associated with various events and actions, such as dice rolls,
* turn changes, piece movements, and game outcomes.
*/
enum SoundAsset {
DICE_ROLL(""),
TURN_START(""),
TURN_END(""),
PIECE_END(""),
PIECE_MOVE(""),
PIECE_LOST(""),
SELECT(""),
DESELECT(""),
HURRY(""),
VICTORY("LevelUp2.wav"),
LOST("GameOver.wav");
private final String path;
/**
* Constructs a new SoundAsset object with the specified name.
*
* @param name The name of the sound file.
*/
SoundAsset(String name) {
this.path = "sound/" + name;
}
/**
* Gets the file path of the sound effect.
*
* @return The path to the sound file (relative to the sound folder).
*/
public String getPath() {
return path;
}
}

View File

@@ -0,0 +1,7 @@
package pp.mdga.client.Acoustic;
/**
* A record that encapsulates a sound asset along with its playback settings:
* the relative volume (subVolume) and a delay before it starts playing.
*/
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {}

View File

@@ -11,19 +11,19 @@ public AnimationHandler(MdgaApp app) {
this.app = app;
}
public void playAnimation(AnimationType type) {
public void playAnimation(MdgaAnimation type) {
}
public void update() {
if(null == animation) {
if (null == animation) {
return;
}
if(animation.isOver()) {
if (animation.isOver()) {
animation = null;
//trigger next state
//trigger next state in model
}
}
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Animation;
public enum AnimationType {
public enum MdgaAnimation {
}

View File

@@ -0,0 +1,3 @@
package pp.mdga.client.Board;
record AssetOnMap(BoardAsset asset, int x, int y, float rot) {}

View File

@@ -1,6 +1,6 @@
package pp.mdga.client;
package pp.mdga.client.Board;
public enum Asset {
enum BoardAsset {
bigTent,
cardStack,
cir,
@@ -8,13 +8,13 @@ public enum Asset {
jet,
lw,
marine,
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue.png"),
node_home_black("./node_home/node_home.j3o", "./node_home/node_home_black.png"),
node_home_green("./node_home/node_home.j3o", "./node_home/node_home_green.png"),
node_home_yellow("./node_home/node_home.j3o", "./node_home/node_home_yellow.png"),
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue_diff.png"),
node_home_black("./node_home/node_home.j3o", "./node_home/node_home_black_diff.png"),
node_home_green("./node_home/node_home.j3o", "./node_home/node_home_green_diff.png"),
node_home_yellow("./node_home/node_home.j3o", "./node_home/node_home_yellow_diff.png"),
node_normal,
node_start("./node_normal/node_normal.j3o", "./node_normal/node_normal_start.png"),
node_bonus("./node_normal/node_normal.j3o", "./node_normal/node_normal_bonus.png"),
node_start("./node_normal/node_normal.j3o", "./node_normal/node_start_diff.png"),
node_bonus("./node_normal/node_normal.j3o", "./node_normal/node_bonus_diff.png"),
radar,
shieldCard,
ship,
@@ -22,26 +22,26 @@ public enum Asset {
swapCard,
tank,
turboCard,
world(1.1f);
world(1.2f);
private final String modelPath;
private final String diffPath;
private final float size;
Asset(){
BoardAsset() {
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = 1f;
}
Asset(String modelPath, String diffPath){
BoardAsset(String modelPath, String diffPath) {
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = 1f;
}
Asset(float size){
BoardAsset(float size) {
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
@@ -56,8 +56,7 @@ public String getDiffPath() {
return diffPath;
}
public float getSize(){
public float getSize() {
return size;
}
}

View File

@@ -1,30 +1,140 @@
package pp.mdga.client.Board;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
import java.util.ArrayList;
import java.util.*;
public class BoardView {
private static final float GRID_SIZE = 10.0f;
private static final float GRID_SIZE = 1.72f;
private static final float GRID_ELEVATION = 0.0f;
private static final int GRID_EXTEND = 5;
private static final String MAP_NAME = "map.mdga";
private PileControl drawPile = new PileControl();
private PileControl discardPile = new PileControl();
private final MdgaApp app;
private ArrayList<NodeControl> infield = new ArrayList<NodeControl>(40);
private ArrayList<PieceControl> pieces;
private PileControl drawPile = null;
private PileControl discardPile = null;
BoardView(int playerCount) {
assert(2 <= playerCount && playerCount <= 4);
private ArrayList<NodeControl> infield = new ArrayList<>(40);
private Map<UUID, PieceControl> pieces;
pieces = new ArrayList<PieceControl>(4 * playerCount);
private Map<Color, List<AssetOnMap>> playerMap;
public BoardView(MdgaApp app) {
assert (app != null) : "app is null";
this.app = app;
this.pieces = new HashMap<>();
this.playerMap = new HashMap<>();
initMap();
initCamera();
}
private void addFigureToPlayerMap(Color col, AssetOnMap assetOnMap) {
List<AssetOnMap> inMap = playerMap.getOrDefault(col, new ArrayList<>());
inMap.add(assetOnMap);
assert (inMap.size() <= 4) : "to many assets for one player";
playerMap.put(col, inMap);
}
private void initMap() {
List<AssetOnMap> assetOnMaps = MapLoader.loadMap(MAP_NAME);
for (AssetOnMap assetOnMap : assetOnMaps) {
switch (assetOnMap.asset()) {
case lw -> addFigureToPlayerMap(assetToColor(BoardAsset.lw), assetOnMap);
case heer -> addFigureToPlayerMap(assetToColor(BoardAsset.heer), assetOnMap);
case cir -> addFigureToPlayerMap(assetToColor(BoardAsset.cir), assetOnMap);
case marine -> addFigureToPlayerMap(assetToColor(BoardAsset.marine), assetOnMap);
case node_normal, node_bonus, node_start ->
infield.addLast(displayAndControl(assetOnMap, new NodeControl()));
default -> displayAsset(assetOnMap);
}
}
}
private void initCamera() {
app.getFlyByCamera().setEnabled(true);
int zoom = 20;
app.getCamera().setLocation(new Vector3f(-zoom, 0, zoom));
app.getCamera().lookAt(new Vector3f(0, 0, 0), new Vector3f(0, 0, 1));
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(0.3f, 0, -1));
app.getRootNode().addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
app.getRootNode().addLight(ambient);
int SHADOWMAP_SIZE = 1024 * 8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(app.getAssetManager(), SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
app.getViewPort().addProcessor(dlsr);
}
private Color assetToColor(BoardAsset asset) {
return switch (asset) {
case lw -> Color.AIRFORCE;
case heer -> Color.ARMY;
case marine -> Color.NAVY;
case cir -> Color.CYBER;
default -> throw new RuntimeException("invalid asset");
};
}
private Spatial createModel(BoardAsset asset, Vector3f pos, float rot) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
Spatial model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90 + rot));
model.setLocalTranslation(pos);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
app.getRootNode().attachChild(model);
return model;
}
private static Vector3f gridToWorld(int x, int y) {
assert(-GRID_EXTEND <= x && x <= GRID_EXTEND);
assert(-GRID_EXTEND <= y && y < GRID_EXTEND);
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
return new Vector3f(GRID_SIZE * x, GRID_ELEVATION, GRID_SIZE * y);
public void addPlayer(Color color, UUID uuid) {
List<AssetOnMap> playerAssets = playerMap.get(color);
assert (playerAssets != null) : "Assets for Player color are not defined";
for (AssetOnMap assetOnMap : playerAssets) {
pieces.put(uuid, displayAndControl(assetOnMap, new PieceControl()));
}
}
//displays an assets and return the created asset
private Spatial displayAsset(AssetOnMap assetOnMap) {
int x = assetOnMap.x();
int y = assetOnMap.y();
return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
}
private <T extends AbstractControl> T displayAndControl(AssetOnMap assetOnMap, T control) {
Spatial spatial = displayAsset(assetOnMap);
spatial.addControl(control);
return control;
}
}

View File

@@ -0,0 +1,83 @@
package pp.mdga.client.Board;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
class MapLoader {
private MapLoader() {
}
public static List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
) {
while (true) {
String entry = reader.readLine();
if (entry == null) break;
entry = entry.trim();
if (entry.isEmpty()) continue;
if (entry.charAt(0) == '#') continue;
String[] parts = entry.trim().split(" ");
assert (parts.length == 3) : "MapLoader: line has not 3 parts";
String assetName = parts[0];
String[] coordinates = parts[1].split(",");
assert (coordinates.length == 2) : "MapLoade: coordinates are wrong";
int x = Integer.parseInt(coordinates[0]);
int y = Integer.parseInt(coordinates[1]);
float rot = Float.parseFloat(parts[2]);
BoardAsset asset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
}
}
catch (IOException e) {
e.printStackTrace();
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
return assetsOnMap;
}
private static BoardAsset getLoadedAsset(String assetName) {
return switch (assetName) {
case "lw" -> BoardAsset.lw;
case "cir" -> BoardAsset.cir;
case "marine" -> BoardAsset.marine;
case "heer" -> BoardAsset.heer;
case "node" -> BoardAsset.node_normal;
case "node_start" -> BoardAsset.node_start;
case "node_bonus" -> BoardAsset.node_bonus;
case "node_home_blue" -> BoardAsset.node_home_blue;
case "node_home_yellow" -> BoardAsset.node_home_yellow;
case "node_home_black" -> BoardAsset.node_home_black;
case "node_home_green" -> BoardAsset.node_home_green;
case "world" -> BoardAsset.world;
case "jet" -> BoardAsset.jet;
case "big_tent" -> BoardAsset.bigTent;
case "small_tent" -> BoardAsset.smallTent;
case "radar" -> BoardAsset.radar;
case "ship" -> BoardAsset.ship;
case "tank" -> BoardAsset.tank;
default -> throw new IllegalStateException("Unexpected value: " + assetName);
};
}
}

View File

@@ -1,5 +1,18 @@
package pp.mdga.client.Board;
public class NodeControl {
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
public class NodeControl extends AbstractControl {
@Override
protected void controlUpdate(float v) {
}
@Override
protected void controlRender(RenderManager renderManager, ViewPort viewPort) {
}
}

View File

@@ -1,5 +1,18 @@
package pp.mdga.client.Board;
public class PieceControl {
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
public class PieceControl extends AbstractControl {
@Override
protected void controlUpdate(float v) {
}
@Override
protected void controlRender(RenderManager renderManager, ViewPort viewPort) {
}
}

View File

@@ -1,5 +1,5 @@
package pp.mdga.client.Board;
public class PileControl {
class PileControl {
}

View File

@@ -1,5 +1,26 @@
package pp.mdga.client.Dialog;
public class DialogView {
import pp.dialog.DialogManager;
import pp.mdga.client.MdgaApp;
public class DialogView {
private MdgaApp app;
private DialogManager dialogManager = new DialogManager(app);
private StartDialog dialog;
public DialogView(MdgaApp app) {
this.app = app;
}
DialogManager getDialogManager() {
return dialogManager;
}
public void mainMenu() {
//dialogManager = new DialogManager(app);
//di
//MainMenuDialog mainMenuDialog = new MainMenuDialog(app);
}
}

View File

@@ -0,0 +1,4 @@
package pp.mdga.client.Dialog;
public class InterruptDialog {
}

View File

@@ -1,5 +1,34 @@
package pp.mdga.client.Dialog;
public class StartDialog {
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog;
import pp.mdga.client.MdgaApp;
public class StartDialog extends SimpleDialog {
StartDialog(MdgaApp app) {
super(app.getDialogView().getDialogManager());
Checkbox serverHost = new Checkbox("sdgfsdg");
serverHost.setChecked(false);
//serverHost.addClickCommands(s -> toggleServerHost());
final Container input = new Container(new SpringGridLayout());
input.addChild(new Label("sdgsgsdg"));
//input.addChild(host, 1);
input.addChild(new Label("sdfdsgsdgsdg"));
//input.addChild(port, 1);
input.addChild(serverHost);
DialogBuilder.simple(app.getDialogView().getDialogManager())
.setTitle("server.dialog")
.setOkButton("button.connect")
.setNoButton("button.cancel")
.setOkClose(false)
.setNoClose(false)
.build(this);
}
}

View File

@@ -1,29 +1,31 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;
import com.jme3.system.NanoTimer;
import pp.mdga.client.Acoustic.AcousticHandler;
import pp.mdga.client.Acoustic.MdgaSound;
import pp.mdga.client.Animation.AnimationHandler;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.system.AppSettings;
import pp.mdga.client.Board.BoardView;
import pp.mdga.client.Dialog.DialogView;
public class MdgaApp extends SimpleApplication {
private AnimationHandler animationHandler;
private AcousticHandler acousticHandler;
private BoardView boardView;
private DialogView dialogView;
NanoTimer test = new NanoTimer();
private MdgaState testState = MdgaState.MAIN;
public static void main(String[] args) {
MdgaApp app = new MdgaApp();
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
settings.setCenterWindow(true);
settings.setWidth(1300);
settings.setHeight(1000);
settings.setWidth(1280);
settings.setHeight(720);
MdgaApp app = new MdgaApp();
app.setSettings(settings);
app.setShowSettings(false);
app.start();
@@ -32,60 +34,53 @@ public static void main(String[] args) {
@Override
public void simpleInitApp() {
animationHandler = new AnimationHandler(this);
acousticHandler = new AcousticHandler(this);
boardView = new BoardView(this);
dialogView = new DialogView(this);
flyCam.setEnabled(true);
int zoom = 20;
cam.setLocation(new Vector3f(zoom,0,zoom));
cam.lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
//dialogView.mainMenu();
//acousticHandler.playState(MdgaState.GAME);
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(-1,0,-1));
rootNode.addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
rootNode.addLight(ambient);
final int SHADOWMAP_SIZE=1024*8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
viewPort.addProcessor(dlsr);
createModel(Asset.lw).setLocalTranslation(new Vector3f(0,-10,0));
createModel(Asset.cir).setLocalTranslation(new Vector3f(0,-8,0));
createModel(Asset.marine).setLocalTranslation(new Vector3f(0,-6,0));
createModel(Asset.heer).setLocalTranslation(new Vector3f(0,-4,0));
createModel(Asset.node_normal).setLocalTranslation(new Vector3f(0,-2.5f,0));
createModel(Asset.node_home_blue).setLocalTranslation(new Vector3f(0,-1,0));
createModel(Asset.smallTent).setLocalTranslation(new Vector3f(0,1,0));
createModel(Asset.tank).setLocalTranslation(new Vector3f(0,5,0));
createModel(Asset.jet).setLocalTranslation(new Vector3f(0,12,0));
createModel(Asset.ship).setLocalTranslation(new Vector3f(0,17,0));
createModel(Asset.radar).setLocalTranslation(new Vector3f(0,20,0));
createModel(Asset.world);
//System.out.println(Asset.node_normal.getModelPath());
//System.out.println(Asset.node_normal.getDiffPath());
}
private Spatial createModel(Asset asset){
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
Spatial model = assetManager.loadModel(modelName);
model.scale(asset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", assetManager.loadTexture(texName));
model.setMaterial(mat);
rootNode.attachChild(model);
return model;
acousticHandler.playSound(MdgaSound.LOST);
acousticHandler.playSound(MdgaSound.VICTORY);
}
@Override
public void simpleUpdate(float tpf) {
//this method will be called every game tick and can be used to make updates
acousticHandler.update();
//test.reset();
if (test.getTimeInSeconds() > 10) {
if (testState == MdgaState.MAIN) {
testState = MdgaState.LOBBY;
acousticHandler.playState(MdgaState.MAIN);
}
else if (testState == MdgaState.LOBBY) {
testState = MdgaState.CEREMONY;
acousticHandler.playState(MdgaState.LOBBY);
}
else {
testState = MdgaState.MAIN;
acousticHandler.playState(MdgaState.CEREMONY);
}
test.reset();
}
}
public AnimationHandler getAnimationHandler() {
return animationHandler;
}
public AcousticHandler getAcousticHandler() {
return acousticHandler;
}
public BoardView getBoardView() {
return boardView;
}
public DialogView getDialogView() {
return dialogView;
}
}

View File

@@ -0,0 +1,48 @@
package pp.mdga.client;
import pp.mdga.notification.Notification;
import pp.mdga.notification.PieceInGameNotification;
import pp.mdga.notification.PlayerInGameNotification;
public enum MdgaState {
NONE {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
MAIN {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
LOBBY {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
GAME {
@Override
void handleNotification(MdgaApp app, Notification notification) {
if (notification instanceof PlayerInGameNotification) {
//TODO
}
else if (notification instanceof PieceInGameNotification) {
//TODO
}
else {
throw new RuntimeException("unexpected notification");
}
}
},
CEREMONY {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
};
abstract void handleNotification(MdgaApp app, Notification notification);
}

View File

@@ -1,5 +1,23 @@
package pp.mdga.client;
public class NotificationSynchronizer {
import pp.mdga.notification.Notification;
import java.util.ArrayList;
public class NotificationSynchronizer {
private final MdgaApp app;
private MdgaState state = MdgaState.MAIN;
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
void update() {
ArrayList<Notification> notifications = new ArrayList<>();
//TODO fetch model notifications
for (Notification n : notifications) {
state.handleNotification(app, n);
}
}
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class CeremonyState {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class GameState {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class LobbyState {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class MdgaState {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class MusicState {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.State;
public class SoundState {
}

View File

@@ -0,0 +1,66 @@
world 0,0
# jet 0,0
#Nodes für Map
node_start 8,0
node 8,1
node 8,2
node 7,3
node_bonus 6,4
node 5,5
node 4,6
node 3,7
node 2,8
node 1,8
node_start 0,8
node -1,8
node -2,8
node -3,7
node_bonus -4,6
node -5,5
node -6,4
node -7,3
node -8,2
node -8,1
node_start -8,0
node -8,-1
node -8,-2
node -7,-3
node_bonus -6,-4
node -5,-5
node -4,-6
node -3,-7
node -2,-8
node -1,-8
node_start 0,-8
node 1,-8
node 2,-8
node 3,-7
node_bonus 4,-6
node 5,-5
node 6,-4
node 7,-3
node 8,-2
node 8,-1
#Node Home
node_home_blue 7,0
node_home_blue 6,0
node_home_blue 5,0
node_home_blue 4,0
node_home_black 0,4
node_home_black 0,5
node_home_black 0,6
node_home_black 0,7
node_home_yellow 0,-4
node_home_yellow 0,-5
node_home_yellow 0,-6
node_home_yellow 0,-7
node_home_green -4,0
node_home_green -5,0
node_home_green -6,0
node_home_green -7,0

View File

@@ -0,0 +1,126 @@
world 0,0 0
#Marine Pos
marine 4,-5 180
marine 4,-4 180
marine 5,-4 180
marine 5,-5 180
#Blue (Marine) Home Node
node_home_blue 4,-5 0
node_home_blue 4,-4 0
node_home_blue 5,-4 0
node_home_blue 5,-5 0
#Lw Pos
lw -5,4 0
lw -4,4 0
lw -4,5 0
lw -5,5 0
#Black (Lw) Home Node
node_home_black -5,4 0
node_home_black -4,4 0
node_home_black -4,5 0
node_home_black -5,5 0
#Heer Pos
heer -4,-5 90
heer -4,-4 90
heer -5,-4 90
heer -5,-5 90
#Green (Heer) Home Node
node_home_green -4,-5 0
node_home_green -4,-4 0
node_home_green -5,-4 0
node_home_green -5,-5 0
#CIR Pos
cir 4,5 -90
cir 4,4 -90
cir 5,4 -90
cir 5,5 -90
#Assets
jet -10,-1 -45
ship 11,0 79
big_tent -9,-7 40
big_tent 7,-10 135
small_tent -9,7 -45
small_tent -10,5 -30
radar 0,10 -110
small_tent 6,8 100
small_tent 8,7 70
tank -1,-10 45
#Yellow (CIR) Home Node
node_home_yellow 4,5 0
node_home_yellow 4,4 0
node_home_yellow 5,4 0
node_home_yellow 5,5 0
#Nodes für Map
node_start -5,1 0
node -4,1 0
node -3,1 0
node -2,1 0
node_bonus -1,1 0
node -1,2 0
node -1,3 0
node -1,4 0
node -1,5 0
node 0,5 0
node_start 1,5 0
node 1,4 0
node 1,3 0
node 1,2 0
node_bonus 1,1 0
node 2,1 0
node 3,1 0
node 4,1 0
node 5,1 0
node 5,0 0
node_start 5,-1 0
node 4,-1 0
node 3,-1 0
node 2,-1 0
node_bonus 1,-1 0
node 1,-2 0
node 1,-3 0
node 1,-4 0
node 1,-5 0
node 0,-5 0
node_start -1,-5 0
node -1,-4 0
node -1,-3 0
node -1,-2 0
node_bonus -1,-1 0
node -2,-1 0
node -3,-1 0
node -4,-1 0
node -5,-1 0
node -5,0 0
#Node Home
node_home_green 0,-1 0
node_home_green 0,-2 0
node_home_green 0,-3 0
node_home_green 0,-4 0
node_home_yellow 0,1 0
node_home_yellow 0,2 0
node_home_yellow 0,3 0
node_home_yellow 0,4 0
node_home_blue 1,0 0
node_home_blue 2,0 0
node_home_blue 3,0 0
node_home_blue 4,0 0
node_home_black -1,0 0
node_home_black -2,0 0
node_home_black -3,0 0
node_home_black -4,0 0

View File

@@ -0,0 +1,22 @@
# Credit
## Free SynthWave Music Pack
See "Free SynthWave Music Pack License.pdf" for LICENCE information.
- 80s,Dico,Live.wav
- LaserParty.wav
- NeonRoadTrip.wav
- NoPressureTrance.wav
- RetroNoir.wav
- SpaceInvaders.wav
- TheSynthRave.SynthWave
## Short Loopable Background Music
https://joshuuu.itch.io/short-loopable-background-music
Free for personal or commercial use as long as you don't redistribute as your own. Donations also welcome! Please add jhaeka to your credits. :)
- DeadPlanet.wav
- Spaceship.wav

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

View File

@@ -32,8 +32,8 @@
}
}
}
include 'mdga:client'
findProject(':mdga:client')?.name = 'client'
include 'mdga:client'
findProject(':mdga:client')?.name = 'client'
include 'mdga:client'
include 'mdga:model'
findProject(':mdga:client')?.name = 'client'