merge dev into test #33

Merged
j23f0712 merged 36 commits from development into dev/test 2024-12-02 17:37:00 +01:00
40 changed files with 1099 additions and 619 deletions
Showing only changes of commit 3717e7b794 - Show all commits

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.animation; package pp.mdga.client;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;

View File

@@ -106,7 +106,7 @@ public void simpleInitApp() {
gameView = new GameView(this); gameView = new GameView(this);
ceremonyView = new CeremonyView(this); ceremonyView = new CeremonyView(this);
enter(MdgaState.MAIN); enter(MdgaState.GAME);
} }
/** /**

View File

@@ -67,8 +67,9 @@ private void handleLobby(Notification notification) {
lobbyView.setTaken(n.getColor(), false, false, null); lobbyView.setTaken(n.getColor(), false, false, null);
} else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) { } else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) {
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady()); lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
} else if (notification instanceof GameNotification) { } else if (notification instanceof GameNotification n) {
app.enter(MdgaState.GAME); app.enter(MdgaState.GAME);
((GameView) app.getView()).setOwnColor(n.getOwnColor());
} else { } else {
throw new RuntimeException("notification not expected: " + notification.toString()); throw new RuntimeException("notification not expected: " + notification.toString());
} }
@@ -84,6 +85,7 @@ private void handleGame(Notification notification) {
guiHandler.addCard(n.getBonusCard()); guiHandler.addCard(n.getBonusCard());
} else if (notification instanceof ActivePlayerNotification n) { } else if (notification instanceof ActivePlayerNotification n) {
gameView.getGuiHandler().setActivePlayer(n.getColor()); gameView.getGuiHandler().setActivePlayer(n.getColor());
boardHandler.showDice(n.getColor());
} else if (notification instanceof CeremonyNotification ceremonyNotification) { } else if (notification instanceof CeremonyNotification ceremonyNotification) {
app.enter(MdgaState.CEREMONY); app.enter(MdgaState.CEREMONY);
CeremonyView ceremonyView = (CeremonyView) app.getView(); CeremonyView ceremonyView = (CeremonyView) app.getView();
@@ -116,23 +118,22 @@ private void handleGame(Notification notification) {
} else if (notification instanceof DrawCardNotification n) { } else if (notification instanceof DrawCardNotification n) {
guiHandler.drawCard(n.getColor()); guiHandler.drawCard(n.getColor());
} else if (notification instanceof HomeMoveNotification home) { } else if (notification instanceof HomeMoveNotification home) {
boardHandler.moveHomePiece(home.getPieceId(), home.getHomeIndex()); boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText(); guiHandler.hideText();
} else if (notification instanceof InterruptNotification) { } else if (notification instanceof InterruptNotification) {
app.enter(MdgaState.LOBBY); app.enter(MdgaState.LOBBY);
} else if (notification instanceof MovePieceNotification n) { } else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) { if(n.isMoveStart()) {
//StartMove //StartMove
boardHandler.movePieceStart(n.getPiece(), n.getMoveIndex()); boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
} }
else { else {
//InfieldMove //InfieldMove
boardHandler.movePiece(n.getPiece(), n.getStartIndex(), n.getMoveIndex()); boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
// boardHandler.test(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
} }
guiHandler.hideText(); guiHandler.hideText();
} else if (notification instanceof ThrowPieceNotification n) { } else if (notification instanceof ThrowPieceNotification n) {
boardHandler.throwPiece(n.getPieceId()); boardHandler.throwPieceAnim(n.getPieceId());
} else if (notification instanceof NoShieldNotification n) { } else if (notification instanceof NoShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceId()); boardHandler.unshieldPiece(n.getPieceId());
} else if (notification instanceof PlayCardNotification n) { } else if (notification instanceof PlayCardNotification n) {
@@ -152,6 +153,7 @@ private void handleGame(Notification notification) {
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1); guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
} }
else { else {
boardHandler.hideDice();
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor()); if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
else guiHandler.showRolledDice(n.getEyes(), n.getColor()); else guiHandler.showRolledDice(n.getEyes(), n.getColor());
} }

View File

@@ -1,8 +1,7 @@
package pp.mdga.client.animation; package pp.mdga.client.animation;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import pp.mdga.client.InitControl;
import com.jme3.renderer.ViewPort;
/** /**
* A control that smoothly moves a spatial from an initial position to an end position * A control that smoothly moves a spatial from an initial position to an end position

View File

@@ -2,9 +2,7 @@
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import pp.mdga.client.InitControl;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.game.BonusCard; import pp.mdga.game.BonusCard;
/** /**

View File

@@ -1,9 +1,6 @@
package pp.mdga.client.animation; package pp.mdga.client.animation;
import com.jme3.renderer.RenderManager; import pp.mdga.client.InitControl;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/** /**
* A control that applies a zoom effect to a spatial, smoothly scaling it in and out. * A control that applies a zoom effect to a spatial, smoothly scaling it in and out.

View File

@@ -2,4 +2,7 @@
import pp.mdga.client.Asset; import pp.mdga.client.Asset;
/**
* Record for holding Asset information
*/
record AssetOnMap(Asset asset, int x, int y, float rot) {} record AssetOnMap(Asset asset, int x, int y, float rot) {}

View File

@@ -16,16 +16,24 @@
import java.util.*; import java.util.*;
/**
* BoardHandler is responsible for managing the game board in the MDGA client, including handling
* the initialization, movement, and management of game pieces and assets.
* It works closely with the MdgaApp to create and manipulate 3D models of assets, track player pieces,
* and manage movement across the game board.
*/
public class BoardHandler { public class BoardHandler {
// Constants defining the grid size and elevation of the board
private static final float GRID_SIZE = 1.72f; private static final float GRID_SIZE = 1.72f;
private static final float GRID_ELEVATION = 0.0f; private static final float GRID_ELEVATION = 0.0f;
private static final String MAP_NAME = "Maps/map.mdga"; private static final String MAP_NAME = "Maps/map.mdga";
// The main application instance for accessing game assets and logic
private final MdgaApp app; private final MdgaApp app;
// Collection of in-game assets and board elements
private ArrayList<NodeControl> infield; private ArrayList<NodeControl> infield;
private Map<UUID, PieceControl> pieces; private Map<UUID, PieceControl> pieces;
private Map<Color, List<AssetOnMap>> colorAssetsMap; private Map<Color, List<AssetOnMap>> colorAssetsMap;
private Map<Color, List<NodeControl>> homeNodesMap; private Map<Color, List<NodeControl>> homeNodesMap;
private Map<Color, List<NodeControl>> waitingNodesMap; private Map<Color, List<NodeControl>> waitingNodesMap;
@@ -35,11 +43,11 @@ public class BoardHandler {
private final Node rootNodeBoard; private final Node rootNodeBoard;
private final Node rootNode; private final Node rootNode;
private final FilterPostProcessor fpp; private final FilterPostProcessor fpp;
private boolean isInitialised; private boolean isInitialised;
// Flags and lists for handling piece selection and movement
private List<PieceControl> selectableOwnPieces; private List<PieceControl> selectableOwnPieces;
private List<PieceControl> selectableEnemyPieces; private List<PieceControl> selectableEnemyPieces;
private List<NodeControl> outlineNodes; private List<NodeControl> outlineNodes;
@@ -47,6 +55,14 @@ public class BoardHandler {
private PieceControl selectedEnemyPiece; private PieceControl selectedEnemyPiece;
private DiceControl diceControl; private DiceControl diceControl;
/**
* Creates a new BoardHandler.
*
* @param app The main application instance
* @param rootNode The root node where the board will be attached
* @param fpp The post-processor for effects like shadows or filters
* @throws RuntimeException if the app is null
*/
public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) { public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
if(app == null) throw new RuntimeException("app is null"); if(app == null) throw new RuntimeException("app is null");
@@ -57,6 +73,9 @@ public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
isInitialised = false; isInitialised = false;
} }
/**
* Initializes the game board by setting up the pieces and nodes.
*/
public void init() { public void init() {
isInitialised = true; isInitialised = true;
selectableOwnPieces = new ArrayList<>(); selectableOwnPieces = new ArrayList<>();
@@ -68,17 +87,30 @@ public void init() {
rootNode.attachChild(rootNodeBoard); rootNode.attachChild(rootNodeBoard);
} }
/**
* Shuts down the board handler by detaching all board-related nodes and clearing selected pieces.
*/
public void shutdown(){ public void shutdown(){
clearSelectable(); clearSelectable();
isInitialised = false; isInitialised = false;
rootNode.detachChild(rootNodeBoard); rootNode.detachChild(rootNodeBoard);
} }
/**
* Adds an asset to the map of player assets, ensuring that the player does not have too many assets.
*
* @param col The color of the player
* @param assetOnMap The asset to be added
* @throws RuntimeException if there are too many assets for the player
*/
private void addFigureToPlayerMap(Color col, AssetOnMap assetOnMap) { private void addFigureToPlayerMap(Color col, AssetOnMap assetOnMap) {
List<AssetOnMap> inMap = addItemToMapList(colorAssetsMap, col, assetOnMap); List<AssetOnMap> inMap = addItemToMapList(colorAssetsMap, col, assetOnMap);
if (inMap.size() > 4) throw new RuntimeException("to many assets for " + col); if (inMap.size() > 4) throw new RuntimeException("to many assets for " + col);
} }
/**
* Initializes the map with the assets loaded from the map file and corresponding nodes.
*/
private void initMap() { private void initMap() {
pieces = new HashMap<>(); pieces = new HashMap<>();
colorAssetsMap = new HashMap<>(); colorAssetsMap = new HashMap<>();
@@ -120,6 +152,13 @@ private void initMap() {
} }
} }
/**
* Converts an asset to its corresponding color.
*
* @param asset The asset to be converted
* @return The color associated with the asset
* @throws RuntimeException if the asset is invalid
*/
private Color assetToColor(Asset asset) { private Color assetToColor(Asset asset) {
return switch (asset) { return switch (asset) {
case lw -> Color.AIRFORCE; case lw -> Color.AIRFORCE;
@@ -130,6 +169,14 @@ private Color assetToColor(Asset asset) {
}; };
} }
/**
* Creates a 3D model of an asset and adds it to the board.
*
* @param asset The asset to be displayed
* @param pos The position of the asset on the board
* @param rot The rotation of the asset
* @return The Spatial representation of the asset
*/
private Spatial createModel(Asset asset, Vector3f pos, float rot) { private Spatial createModel(Asset asset, Vector3f pos, float rot) {
String modelName = asset.getModelPath(); String modelName = asset.getModelPath();
String texName = asset.getDiffPath(); String texName = asset.getDiffPath();
@@ -146,10 +193,23 @@ private Spatial createModel(Asset asset, Vector3f pos, float rot) {
return model; return model;
} }
/**
* Converts grid coordinates to world space.
*
* @param x The x-coordinate on the grid
* @param y The y-coordinate on the grid
* @return The corresponding world position
*/
private static Vector3f gridToWorld(int x, int y) { private static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION); return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
} }
/**
* Displays an asset on the map at the given position with the specified rotation.
*
* @param assetOnMap The asset to be displayed.
* @return A spatial representation of the asset at the specified location and rotation.
*/
private Spatial displayAsset(AssetOnMap assetOnMap) { private Spatial displayAsset(AssetOnMap assetOnMap) {
int x = assetOnMap.x(); int x = assetOnMap.x();
int y = assetOnMap.y(); int y = assetOnMap.y();
@@ -171,6 +231,13 @@ private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOn
if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color); if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
} }
/**
* Calculates the rotation angle required to move a piece from one position to another.
*
* @param prev The previous position.
* @param next The target position.
* @return The rotation angle in degrees.
*/
private float getRotationMove(Vector3f prev, Vector3f next) { private float getRotationMove(Vector3f prev, Vector3f next) {
Vector3f direction = next.subtract(prev).normalizeLocal(); Vector3f direction = next.subtract(prev).normalizeLocal();
//I had to reverse dir.y, because then it worked. //I had to reverse dir.y, because then it worked.
@@ -179,6 +246,14 @@ private float getRotationMove(Vector3f prev, Vector3f next) {
return newRot; return newRot;
} }
/**
* Recursively moves a piece from its current index to the destination index,
* to keep track of the piece rotation.
*
* @param uuid The UUID of the piece to move.
* @param curIndex The current index of the piece.
* @param moveIndex The target index to move the piece to.
*/
private void movePieceRek(UUID uuid, int curIndex, int moveIndex){ private void movePieceRek(UUID uuid, int curIndex, int moveIndex){
if (curIndex == moveIndex) return; if (curIndex == moveIndex) return;
@@ -211,6 +286,12 @@ private Vector3f getWaitingPos(Color color){
return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList()); return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
} }
/**
* Gets the mean position of a list of vectors.
*
* @param vectors The list of vectors.
* @return The mean position as a Vector3f.
*/
public static Vector3f getMeanPosition(List<Vector3f> vectors) { public static Vector3f getMeanPosition(List<Vector3f> vectors) {
if (vectors.isEmpty()) return new Vector3f(0, 0, 0); if (vectors.isEmpty()) return new Vector3f(0, 0, 0);
@@ -221,7 +302,13 @@ public static Vector3f getMeanPosition(List<Vector3f> vectors) {
return sum.divide(vectors.size()); return sum.divide(vectors.size());
} }
//public methods**************************************************************************************************** /**
* Adds a player to the game by associating a color and a list of UUIDs to corresponding assets and waiting nodes.
*
* @param color the color of the player
* @param uuid the list of UUIDs representing the player's assets
* @throws RuntimeException if the number of assets or waiting nodes does not match the provided UUIDs
*/
public void addPlayer(Color color, List<UUID> uuid) { public void addPlayer(Color color, List<UUID> uuid) {
List<AssetOnMap> playerAssets = colorAssetsMap.get(color); List<AssetOnMap> playerAssets = colorAssetsMap.get(color);
@@ -254,7 +341,14 @@ public void addPlayer(Color color, List<UUID> uuid) {
} }
} }
public void moveHomePiece(UUID uuid, int index){ /**
* Moves a piece to its corresponding home node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param index the index of the home node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or if the home nodes are not properly defined
*/
private void moveHomePiece(UUID uuid, int index){
Color color = pieceColor.get(uuid); Color color = pieceColor.get(uuid);
if(color == null) throw new RuntimeException("uuid is not mapped to a color"); if(color == null) throw new RuntimeException("uuid is not mapped to a color");
@@ -275,7 +369,15 @@ public void moveHomePiece(UUID uuid, int index){
app.getModelSynchronize().animationEnd(); app.getModelSynchronize().animationEnd();
} }
public void movePieceStart(UUID uuid, int nodeIndex){ /**
* Starts the movement of a piece to a target node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param nodeIndex the index of the target node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
* @throws IllegalArgumentException if the node index is invalid
*/
private void movePieceStart(UUID uuid, int nodeIndex){
// Farbe des Pieces abrufen // Farbe des Pieces abrufen
Color color = pieceColor.get(uuid); Color color = pieceColor.get(uuid);
@@ -298,13 +400,26 @@ public void movePieceStart(UUID uuid, int nodeIndex){
app.getModelSynchronize().animationEnd(); app.getModelSynchronize().animationEnd();
} }
public void movePiece(UUID uuid, int curIndex, int moveIndex){ /**
* Moves a piece from its current position to the target position based on the given indexes.
*
* @param uuid the UUID of the piece to move
* @param curIndex the current index of the piece
* @param moveIndex the target index of the move
*/
private void movePiece(UUID uuid, int curIndex, int moveIndex){
movePieceRek(uuid, curIndex, moveIndex); movePieceRek(uuid, curIndex, moveIndex);
app.getModelSynchronize().animationEnd(); app.getModelSynchronize().animationEnd();
} }
public void throwPiece(UUID uuid){ /**
* Throws a piece to the next available waiting node and updates the waiting node mapping.
*
* @param uuid the UUID of the piece to throw
* @throws RuntimeException if the UUID is not mapped to a color or if no available waiting nodes are found
*/
private void throwPiece(UUID uuid){
// Farbe des Pieces abrufen // Farbe des Pieces abrufen
Color color = pieceColor.get(uuid); Color color = pieceColor.get(uuid);
@@ -331,59 +446,65 @@ public void throwPiece(UUID uuid){
app.getModelSynchronize().animationEnd(); app.getModelSynchronize().animationEnd();
} }
/**
* Activates the shield for the specified piece.
*
* @param uuid the UUID of the piece to shield
*/
public void shieldPiece(UUID uuid){ public void shieldPiece(UUID uuid){
pieces.get(uuid).activateShield(); pieces.get(uuid).activateShield();
} }
/**
* Deactivates the shield for the specified piece.
*
* @param uuid the UUID of the piece to unshield
*/
public void unshieldPiece(UUID uuid){ public void unshieldPiece(UUID uuid){
pieces.get(uuid).deactivateShield(); pieces.get(uuid).deactivateShield();
} }
/**
* Suppresses the shield for the specified piece.
*
* @param uuid the UUID of the piece to suppress the shield
*/
public void suppressShield(UUID uuid){ public void suppressShield(UUID uuid){
pieces.get(uuid).suppressShield(); pieces.get(uuid).suppressShield();
} }
public void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2){ /**
* Swaps the positions and rotations of two pieces.
// PieceControl piece1Control = pieces.get(piece1); *
// PieceControl piece2Control = pieces.get(piece2); * @param p1 the first piece to swap
* @param p2 the second piece to swap
// if(piece1Control == null) throw new RuntimeException("piece1 UUID is not valid"); * @param loc1 the original location of the first piece
// if(piece2Control == null) throw new RuntimeException("piece2 UUID is not valid"); * @param rot1 the original rotation of the first piece
* @param loc2 the original location of the second piece
// float rot1 = piece1Control.getRotation(); * @param rot2 the original rotation of the second piece
// float rot2 = piece2Control.getRotation(); */
private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2){
// piece1Control.setRotation(rot2);
// piece2Control.setRotation(rot1);
// Vector3f pos1 = piece1Control.getLocation().clone();
// Vector3f pos2 = piece2Control.getLocation().clone();
// piece1Control.setLocation(pos2);
// piece2Control.setLocation(pos1);
p1.setLocation(loc2); p1.setLocation(loc2);
p2.setLocation(loc1); p2.setLocation(loc1);
p1.setRotation(rot2); p1.setRotation(rot2);
p2.setRotation(rot1); p2.setRotation(rot1);
app.getModelSynchronize().animationEnd(); app.getModelSynchronize().animationEnd();
} }
public void highlight(UUID uuid, boolean bool){ /**
* Outlines the possible move nodes for a list of pieces based on the move indices and whether it's a home move.
pieces.get(uuid).highlight(bool); *
pieces.get(uuid).setSelectable(bool); * @param pieces the list of UUIDs representing the pieces to outline
* @param moveIndexe the list of indices for the target move nodes
} * @param homeMoves the list indicating whether the move is a home move
* @throws RuntimeException if the sizes of the input lists do not match
//called when (dice) moveNum is received from server to display the movable pieces and corresponding moveNodes */
public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) { public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) {
if(pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size()) throw new RuntimeException("arrays are not the same size"); if(pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size()) throw new RuntimeException("arrays are not the same size");
@@ -413,7 +534,12 @@ public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolea
} }
} }
//called when swap notification is received to highlight and select own/enemy pieces /**
* Outlines the pieces that can be swapped based on the provided own and enemy pieces.
*
* @param ownPieces the list of UUIDs representing the player's pieces
* @param enemyPieces the list of UUIDs representing the enemy's pieces
*/
public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){ public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
selectableEnemyPieces.clear(); selectableEnemyPieces.clear();
@@ -437,6 +563,11 @@ public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
} }
} }
/**
* Outlines the pieces that can be shielded based on the provided list of pieces.
*
* @param pieces the list of UUIDs representing the pieces to be shielded
*/
public void outlineShield(List<UUID> pieces){ public void outlineShield(List<UUID> pieces){
selectableOwnPieces.clear(); selectableOwnPieces.clear();
selectableEnemyPieces.clear(); selectableEnemyPieces.clear();
@@ -452,7 +583,11 @@ public void outlineShield(List<UUID> pieces){
} }
} }
//called from inputSynchronizer when a piece is selectable /**
* Selects a piece from either the own or enemy pieces based on the input and deselects others if needed.
*
* @param pieceSelected the PieceControl instance representing the piece selected by the user
*/
public void pieceSelect(PieceControl pieceSelected) { public void pieceSelect(PieceControl pieceSelected) {
boolean isSelected = pieceSelected.isSelected(); boolean isSelected = pieceSelected.isSelected();
if(selectableOwnPieces.contains(pieceSelected)){ if(selectableOwnPieces.contains(pieceSelected)){
@@ -486,7 +621,9 @@ else if(selectableEnemyPieces.contains(pieceSelected)) {
app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece)); app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece));
} }
//called when view is no longer needed to select pieces /**
* Clears all highlighted, selectable, and selected pieces and nodes.
*/
public void clearSelectable(){ public void clearSelectable(){
for(PieceControl p : selectableEnemyPieces) { for(PieceControl p : selectableEnemyPieces) {
p.unSelect(); p.unSelect();
@@ -508,16 +645,20 @@ public void clearSelectable(){
selectedOwnPiece = null; selectedOwnPiece = null;
} }
public void enableHover(UUID uuid){ /**
pieces.get(uuid).setHoverable(true); * Displays the dice for the specified color at the appropriate position.
} *
* @param color the color of the player whose dice should be displayed
*/
public void showDice(Color color){ public void showDice(Color color){
rootNodeBoard.attachChild(diceControl.getSpatial()); rootNodeBoard.attachChild(diceControl.getSpatial());
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4))); diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4)));
diceControl.spin(); diceControl.spin();
} }
/**
* Hides the dice from the view.
*/
public void hideDice(){ public void hideDice(){
diceControl.hide(); diceControl.hide();
} }
@@ -531,6 +672,13 @@ private <K, V> K getKeyByValue(Map<K, V> map, V value) {
return null; return null;
} }
/**
* Animates the movement of a piece from its current index to a target index.
*
* @param uuid the UUID of the piece to animate
* @param curIndex the current index of the piece
* @param moveIndex the target index to animate the piece to
*/
public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){ public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl( pieces.get(uuid).getSpatial().addControl(new MoveControl(
infield.get(curIndex).getLocation(), infield.get(curIndex).getLocation(),
@@ -538,6 +686,12 @@ public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
()->movePiece(uuid,curIndex,moveIndex))); ()->movePiece(uuid,curIndex,moveIndex)));
} }
/**
* Animates the movement of a piece to its home position based on the given home index.
*
* @param uuid the UUID of the piece to animate
* @param homeIndex the index of the home node to move the piece to
*/
public void movePieceHomeAnim(UUID uuid, int homeIndex){ public void movePieceHomeAnim(UUID uuid, int homeIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl( pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(), pieces.get(uuid).getLocation(),
@@ -545,6 +699,12 @@ public void movePieceHomeAnim(UUID uuid, int homeIndex){
()->moveHomePiece(uuid,homeIndex))); ()->moveHomePiece(uuid,homeIndex)));
} }
/**
* Animates the start of the movement of a piece to a target index.
*
* @param uuid the UUID of the piece to animate
* @param moveIndex the target index to animate the piece to
*/
public void movePieceStartAnim(UUID uuid, int moveIndex){ public void movePieceStartAnim(UUID uuid, int moveIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl( pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(), pieces.get(uuid).getLocation(),
@@ -553,6 +713,11 @@ public void movePieceStartAnim(UUID uuid, int moveIndex){
)); ));
} }
/**
* Animates the throwing of a piece to the next available waiting node.
*
* @param uuid the UUID of the piece to animate
*/
public void throwPieceAnim(UUID uuid){ public void throwPieceAnim(UUID uuid){
pieces.get(uuid).getSpatial().addControl(new MoveControl( pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(), pieces.get(uuid).getLocation(),
@@ -561,6 +726,12 @@ public void throwPieceAnim(UUID uuid){
)); ));
} }
/**
* Animates the swapping of two pieces by swapping their positions and rotations.
*
* @param piece1 the UUID of the first piece
* @param piece2 the UUID of the second piece
*/
public void swapPieceAnim(UUID piece1, UUID piece2){ public void swapPieceAnim(UUID piece1, UUID piece2){
PieceControl piece1Control = pieces.get(piece1); PieceControl piece1Control = pieces.get(piece1);
PieceControl piece2Control = pieces.get(piece2); PieceControl piece2Control = pieces.get(piece2);
@@ -582,6 +753,13 @@ public void swapPieceAnim(UUID piece1, UUID piece2){
)); ));
} }
/**
* Retrieves the next available waiting node for the specified color.
*
* @param color the color of the player to get the next waiting node for
* @return the next available NodeControl for the specified color
* @throws IllegalStateException if no available waiting nodes are found for the color
*/
private NodeControl getNextWaitingNode(Color color) { private NodeControl getNextWaitingNode(Color color) {
List<NodeControl> nodes = waitingNodesMap.get(color); List<NodeControl> nodes = waitingNodesMap.get(color);

View File

@@ -15,6 +15,10 @@
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color; import pp.mdga.game.Color;
/**
* Handles the camera position, rotation, and lighting effects for the game.
* Provides methods for camera initialization, updates based on user input, and shutdown operations.
*/
public class CameraHandler { public class CameraHandler {
MdgaApp app; MdgaApp app;
@@ -34,6 +38,12 @@ public class CameraHandler {
private boolean init; private boolean init;
private boolean initRot; private boolean initRot;
/**
* Constructor for the CameraHandler. Initializes the camera settings and lighting.
*
* @param app The main application instance that provides the camera and root node.
* @param fpp The FilterPostProcessor used for post-processing effects.
*/
public CameraHandler(MdgaApp app, FilterPostProcessor fpp) { public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
init = false; init = false;
initRot = false; initRot = false;
@@ -61,6 +71,12 @@ public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
} }
/**
* Initializes the camera with a specific color orientation.
* Adds lights, sky, and shadow filters to the scene.
*
* @param ownColor The color that defines the initial camera view angle.
*/
public void init(Color ownColor) { public void init(Color ownColor) {
app.getRootNode().addLight(sun); app.getRootNode().addLight(sun);
app.getRootNode().addLight(ambient); app.getRootNode().addLight(ambient);
@@ -72,6 +88,10 @@ public void init(Color ownColor) {
app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor)*2); app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor)*2);
} }
/**
* Shuts down the camera handler by removing all lights, sky, and filters,
* and resets the camera position and rotation to its default state.
*/
public void shutdown() { public void shutdown() {
app.getRootNode().removeLight(sun); app.getRootNode().removeLight(sun);
app.getRootNode().removeLight(ambient); app.getRootNode().removeLight(ambient);
@@ -84,6 +104,13 @@ public void shutdown() {
fpp.removeFilter(dlsf); fpp.removeFilter(dlsf);
} }
/**
* Updates the camera position and rotation based on user input (scroll and rotation).
* Adjusts the vertical angle and radius based on zoom and rotation values.
*
* @param scroll The scroll input, determining zoom level.
* @param rotation The rotation input, determining camera orientation.
*/
public void update(float scroll, float rotation) { public void update(float scroll, float rotation) {
if(!init) return; if(!init) return;
float scrollValue = Math.max(0, Math.min(scroll, 100)); float scrollValue = Math.max(0, Math.min(scroll, 100));
@@ -117,6 +144,12 @@ public void update(float scroll, float rotation) {
app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Z); app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Z);
} }
/**
* Returns the camera angle based on the specified color.
*
* @param color The color used to determine the camera angle.
* @return The camera angle in degrees.
*/
private float getAngleByColor(Color color){ private float getAngleByColor(Color color){
return switch (color){ return switch (color){
case ARMY -> 0; case ARMY -> 0;
@@ -127,6 +160,12 @@ private float getAngleByColor(Color color){
}; };
} }
/**
* Returns the initial camera angle based on the specified color.
*
* @param color The color used to determine the camera angle.
* @return The initial camera angle in degrees.
*/
private float getInitAngleByColor(Color color){ private float getInitAngleByColor(Color color){
return (getAngleByColor(color) + 180) % 360; return (getAngleByColor(color) + 180) % 360;
} }

View File

@@ -10,11 +10,27 @@
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* A utility class for loading and parsing map data from a file.
* The map contains asset names and coordinates for objects placed on the map.
*/
class MapLoader { class MapLoader {
/**
* Private constructor to prevent instantiation.
*/
private MapLoader() { private MapLoader() {
} }
/**
* Loads a map file and parses its contents into a list of assets and their positions.
* Each line in the map file defines an asset, its coordinates, and its rotation.
*
* @param mapName The name of the map file to load. The file is expected to be located in the resources directory.
* @return A list of {@link AssetOnMap} objects representing the assets placed on the map.
* @throws IOException If an error occurs while reading the map file.
* @throws IllegalArgumentException If the map file contains invalid data.
*/
public static List<AssetOnMap> loadMap(String mapName) { public static List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>(); List<AssetOnMap> assetsOnMap = new ArrayList<>();
@@ -60,6 +76,13 @@ public static List<AssetOnMap> loadMap(String mapName) {
return assetsOnMap; return assetsOnMap;
} }
/**
* Returns the corresponding {@link Asset} for a given asset name.
*
* @param assetName The name of the asset to load.
* @return The {@link Asset} associated with the given name.
* @throws IllegalStateException If the asset name is unrecognized.
*/
private static Asset getLoadedAsset(String assetName) { private static Asset getLoadedAsset(String assetName) {
return switch (assetName) { return switch (assetName) {
case "lw" -> Asset.lw; case "lw" -> Asset.lw;

View File

@@ -8,19 +8,40 @@
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
/**
* A control that adds highlighting functionality to a node in the game.
* This class extends {@link OutlineControl} to add an outline effect when the node is highlighted.
*/
public class NodeControl extends OutlineControl { public class NodeControl extends OutlineControl {
private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White; private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 6; private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
/**
* Constructs a {@link NodeControl} with the specified application and post processor.
* This constructor sets up the necessary elements for highlighting functionality.
*
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public NodeControl(MdgaApp app, FilterPostProcessor fpp) { public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
super(app, fpp); super(app, fpp);
} }
/**
* Returns the location of the node in 3D space.
* This is the node's local translation in the scene.
*
* @return The {@link Vector3f} representing the node's location.
*/
public Vector3f getLocation(){ public Vector3f getLocation(){
return this.getSpatial().getLocalTranslation(); return this.getSpatial().getLocalTranslation();
} }
/**
* Highlights the node by applying an outline effect.
* The outline color and width are predefined as white and 6, respectively.
*/
public void highlight() { public void highlight() {
super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH); super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
} }

View File

@@ -3,16 +3,19 @@
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor; import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
import pp.mdga.client.InitControl;
import pp.mdga.client.outline.SelectObjectOutliner; import pp.mdga.client.outline.SelectObjectOutliner;
public class OutlineControl extends AbstractControl { /**
private static final int THICKNESS_DEFAULT = 6; * A control that provides outline functionality to a spatial object.
* This class is responsible for adding an outline effect to a spatial
* object, allowing it to be highlighted or deselected.
*/
public class OutlineControl extends InitControl {
/** The {@link SelectObjectOutliner} responsible for managing the outline effect. */
private final SelectObjectOutliner outlineOwn; private final SelectObjectOutliner outlineOwn;
private static final int THICKNESS_DEFAULT = 6;
private MdgaApp app; private MdgaApp app;
@@ -31,44 +34,33 @@ public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thic
outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app); outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
} }
/**
* Applies an outline to the spatial object with the given color.
*
* @param color The {@link ColorRGBA} representing the color of the outline.
*/
public void outline(ColorRGBA color){ public void outline(ColorRGBA color){
outlineOwn.select(spatial, color); outlineOwn.select(spatial, color);
} }
/**
* Applies an outline to the spatial object with the given color and width.
*
* @param color The {@link ColorRGBA} representing the color of the outline.
* @param width The width of the outline.
*/
public void outline(ColorRGBA color, int width){ public void outline(ColorRGBA color, int width){
deOutline(); deOutline();
outlineOwn.select(spatial, color, width); outlineOwn.select(spatial, color, width);
} }
/**
* Removes the outline effect from the spatial object.
*/
public void deOutline(){ public void deOutline(){
outlineOwn.deselect(spatial); outlineOwn.deselect(spatial);
} }
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public void initSpatial(){
}
@Override
public void setSpatial(Spatial spatial){
if(this.spatial == null && spatial != null){
super.setSpatial(spatial);
initSpatial();
}
else{
super.setSpatial(spatial);
}
}
public MdgaApp getApp() { public MdgaApp getApp() {
return app; return app;
} }

View File

@@ -14,6 +14,12 @@
import pp.mdga.client.Asset; import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
/**
* A control that manages the behavior and properties of a game piece, such as its rotation,
* position, shield activation, and highlighting. This class extends {@link OutlineControl}
* to provide outline functionality and includes additional features like shield effects,
* hover states, and selection states.
*/
public class PieceControl extends OutlineControl { public class PieceControl extends OutlineControl {
private final float initRotation; private final float initRotation;
private final AssetManager assetManager; private final AssetManager assetManager;
@@ -43,7 +49,15 @@ public class PieceControl extends OutlineControl {
private boolean selectable; private boolean selectable;
private boolean select; private boolean select;
/**
* Constructs a {@link PieceControl} with the specified initial rotation, asset manager,
* application, and post-processor.
*
* @param initRotation The initial rotation of the piece in degrees.
* @param assetManager The {@link AssetManager} used for loading models and materials.
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){ public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){
super(app, fpp); super(app, fpp);
this.parentNode = new Node(); this.parentNode = new Node();
@@ -59,10 +73,20 @@ public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app,
select = false; select = false;
} }
/**
* Gets the current rotation of the piece in degrees.
*
* @return The rotation of the piece in degrees.
*/
public float getRotation() { public float getRotation() {
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1))); return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1)));
} }
/**
* Sets the rotation of the piece to the specified value in degrees.
*
* @param rot The rotation in degrees to set.
*/
public void setRotation(float rot){ public void setRotation(float rot){
if(rot < 0) rot =- 360; if(rot < 0) rot =- 360;
@@ -71,10 +95,20 @@ public void setRotation(float rot){
spatial.setLocalRotation(quaternion); spatial.setLocalRotation(quaternion);
} }
/**
* Gets the current location (position) of the piece.
*
* @return The location of the piece as a {@link Vector3f}.
*/
public Vector3f getLocation(){ public Vector3f getLocation(){
return spatial.getLocalTranslation(); return spatial.getLocalTranslation();
} }
/**
* Updates the piece control every frame. If the shield is active, it will rotate.
*
* @param delta The time difference between frames (time per frame).
*/
@Override @Override
protected void controlUpdate(float delta) { protected void controlUpdate(float delta) {
if(shieldRing != null){ if(shieldRing != null){
@@ -82,10 +116,19 @@ protected void controlUpdate(float delta) {
} }
} }
/**
* Sets the location (position) of the piece.
*
* @param loc The location to set as a {@link Vector3f}.
*/
public void setLocation(Vector3f loc){ public void setLocation(Vector3f loc){
this.spatial.setLocalTranslation(loc); this.spatial.setLocalTranslation(loc);
} }
/**
* Initializes the spatial object and sets its rotation.
* This also moves the spatial to a new parent node for organizational purposes.
*/
@Override @Override
public void initSpatial(){ public void initSpatial(){
setRotation(this.initRotation); setRotation(this.initRotation);
@@ -101,6 +144,10 @@ public void rotateInit() {
// rotate(rotation - initRotation); // rotate(rotation - initRotation);
} }
/**
* Activates the shield around the piece.
* This adds a visual shield effect in the form of a rotating ring.
*/
public void activateShield(){ public void activateShield(){
shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath()); shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
shieldRing.scale(1f); shieldRing.scale(1f);
@@ -115,11 +162,18 @@ public void activateShield(){
parentNode.attachChild(shieldRing); parentNode.attachChild(shieldRing);
} }
/**
* Deactivates the shield by removing the shield ring from the scene.
*/
public void deactivateShield(){ public void deactivateShield(){
parentNode.detachChild(shieldRing); parentNode.detachChild(shieldRing);
shieldRing = null; shieldRing = null;
} }
/**
* Suppresses the shield, changing its color to a suppressed state.
*/
public void suppressShield(){ public void suppressShield(){
assert(shieldRing != null) : "PieceControl: shieldRing is not set"; assert(shieldRing != null) : "PieceControl: shieldRing is not set";
shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR); shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
@@ -133,22 +187,36 @@ public Material getMaterial(){
return ((Geometry) getSpatial()).getMaterial(); return ((Geometry) getSpatial()).getMaterial();
} }
/**
* Highlights the piece with the appropriate outline color based on whether it is an enemy or not.
*
* @param enemy True if the piece is an enemy, false if it is owned by the player.
*/
public void highlight(boolean enemy) { public void highlight(boolean enemy) {
this.enemy = enemy; this.enemy = enemy;
highlight = true; highlight = true;
super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH); super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
} }
/**
* Removes the highlight effect from the piece.
*/
public void unHighlight(){ public void unHighlight(){
highlight = false; highlight = false;
deOutline(); deOutline();
} }
/**
* Applies a hover effect on the piece if it is hoverable.
*/
public void hover(){ public void hover(){
if(!hoverable) return; if(!hoverable) return;
super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH); super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
} }
/**
* Removes the hover effect from the piece.
*/
public void hoverOff(){ public void hoverOff(){
if(!hoverable) return; if(!hoverable) return;
@@ -157,28 +225,56 @@ public void hoverOff(){
else deOutline(); else deOutline();
} }
/**
* Deselects the piece and removes the selection outline. If the piece was highlighted,
* it will be re-highlighted. Otherwise, the outline is removed.
*/
public void unSelect(){ public void unSelect(){
select = false; select = false;
if(highlight) highlight(enemy); if(highlight) highlight(enemy);
else deOutline(); else deOutline();
} }
/**
* Selects the piece and applies the selection outline. If the piece is an enemy, it will
* be outlined with the enemy selection color; otherwise, the own selection color will be used.
*/
public void select(){ public void select(){
if(!selectable) return; if(!selectable) return;
select = true; select = true;
super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH); super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
} }
/**
* Sets whether the piece is selectable.
*
* @param selectable True if the piece can be selected, false otherwise.
*/
public void setSelectable(boolean selectable){ public void setSelectable(boolean selectable){
this.selectable = selectable; this.selectable = selectable;
} }
/**
* Checks if the piece is selected.
*
* @return True if the piece is selected, false otherwise.
*/
public boolean isSelected() { return select; } public boolean isSelected() { return select; }
/**
* Checks if the piece is selectable.
*
* @return True if the piece is selectable, false otherwise.
*/
public boolean isSelectable() { public boolean isSelectable() {
return selectable; return selectable;
} }
/**
* Sets whether the piece is hoverable.
*
* @param hoverable True if the piece can be hovered over, false otherwise.
*/
public void setHoverable(boolean hoverable) { public void setHoverable(boolean hoverable) {
this.hoverable = hoverable; this.hoverable = hoverable;
} }

View File

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

View File

@@ -1,12 +0,0 @@
package pp.mdga.client.board;
public enum Rotation {
UP,
RIGHT,
DOWN,
LEFT,
UP_LEFT,
UP_RIGHT,
DOWN_RIGHT,
DOWN_LEFT
}

View File

@@ -64,6 +64,9 @@ public void onEnter() {
app.getViewPort().addProcessor(fpp); app.getViewPort().addProcessor(fpp);
app.getAcousticHandler().playSound(MdgaSound.START); app.getAcousticHandler().playSound(MdgaSound.START);
app.getNotificationSynchronizer().addTestNotification(new AcquireCardNotification(BonusCard.SHIELD));
app.getNotificationSynchronizer().addTestNotification(new SelectableCardsNotification(List.of(BonusCard.SHIELD)));
} }
@Override @Override