47 Commits

Author SHA1 Message Date
Felix Koppe
c4e5b7f7c8 Merge main 2024-11-28 15:02:41 +01:00
Felix
35cf092d5c Merge commit 2024-11-27 08:44:29 +01:00
Felix
06e2d831ef Fix errors 2024-11-27 08:42:45 +01:00
Felix
75b53f8309 Fix model compile errors 2024-11-27 08:39:50 +01:00
Felix
58f94e73db Merge commit 2024-11-26 20:23:23 +01:00
Fleischer Hanno
46a6552bca refactored the whole client package structure 2024-11-26 11:12:58 +01:00
Fleischer Hanno
1d95146272 refactored the model to incoporate a correct folder structure 2024-11-26 11:12:56 +01:00
Benjamin Feyer
9859d52e02 edted some tests with null tests 2024-11-26 11:12:55 +01:00
Benjamin Feyer
e18ea15efa added javadocs 2024-11-26 11:12:54 +01:00
Benjamin Feyer
461a497353 added some more testmethods 2024-11-26 11:12:54 +01:00
Benjamin Feyer
4c3099ddf2 corrected testmethods in clientStateTest 2024-11-26 11:12:54 +01:00
Benjamin Feyer
b0ab870451 added tests in the serverStateTest
added the testmethods for rolldice and movepiece
2024-11-26 11:12:54 +01:00
Benjamin Feyer
993c94c306 edited some tests in ServerStateTest 2024-11-26 11:12:54 +01:00
Benjamin Feyer
e52af59cac minor changes 2024-11-26 11:12:54 +01:00
Benjamin Feyer
40f1bdb51f added some testmethods and corrected other in the clientStatemachineTests 2024-11-26 11:12:54 +01:00
Benjamin Feyer
436dae4ebc added some testmethods for the client testing the statechanges in the dialogs 2024-11-26 11:12:54 +01:00
Benjamin Feyer
1b2d4df96f added some mor testcases for the clientstatemachine 2024-11-26 11:12:54 +01:00
Benjamin Feyer
f3ca9f01c0 added the empty testmethods in serverstateTest and edited the testmethods for substates of choocePiece in Client into MovePiece 2024-11-26 11:12:54 +01:00
Benjamin Feyer
3c97cdae38 edited a test in the clientStateTest 2024-11-26 11:12:54 +01:00
Benjamin Feyer
227d4286e5 editet tests for the server and client statemachines 2024-11-26 11:12:54 +01:00
Benjamin Feyer
9939ec2861 added empty serverstatetests 2024-11-26 11:12:54 +01:00
Benjamin Feyer
1a6a460f9f initial test commit,
added all testclasses except Playertest, Viewtest, Cameratest, SettingsTest, SoundTest, ReactionTest and ClientStateTest. And filled all created testclasses with empty testmethods, except ServerStateTest.
2024-11-26 11:12:54 +01:00
Daniel Grigencha
ec58e9c85f added import statement with the refactored server messages 2024-11-26 11:12:54 +01:00
Daniel Grigencha
cf6777023f added mdga server controller 2024-11-26 11:12:54 +01:00
Daniel Grigencha
739279d3df added new server state chart 2024-11-26 11:12:52 +01:00
Daniel Grigencha
b6bf25671f reverted server messages 2024-11-26 11:12:50 +01:00
Daniel Grigencha
5a9fd2a939 deleted server state automaton 2024-11-26 11:12:49 +01:00
Hanno Fleischer
e1b21de718 added two Pieces in RequestPlayCard in order to differentiate between own and enemy pieces 2024-11-26 11:12:39 +01:00
Hanno Fleischer
f321608132 added two lists in PossiblePiece in order to differentiate between own and enemy pieces 2024-11-26 11:12:39 +01:00
Hanno Fleischer
dd7a27629b added static methods to construct a PlayCard message for each card type 2024-11-26 11:12:39 +01:00
Hanno Fleischer
eba681c350 added getter for ArrayList Player in Game and created a flag for ready status in Player 2024-11-26 11:12:39 +01:00
Daniel Grigencha
f97eea3e5e added default constructor for serialization purposes 2024-11-26 11:12:39 +01:00
Hanno Fleischer
1582038dfe added an ArrayList of Player in game and added the received methods in clientgamelogic 2024-11-26 11:12:39 +01:00
Daniel Grigencha
798e996a8d fixed sonarlint errors and deleted map playerConnectionID 2024-11-26 11:12:39 +01:00
Fleischer Hanno
472d87b0c9 refactored ceremony message 2024-11-26 11:12:39 +01:00
Fleischer Hanno
7cfb863e5c corrected refactoring mistake, ich which RankingResponce was renamed to RankingResponse 2024-11-26 11:12:35 +01:00
Fleischer Hanno
7e1d2e833e added getter for dialogstatemachine in dialogs 2024-11-26 11:12:30 +01:00
Fleischer Hanno
67a87ffa81 made all con structors of clients states public 2024-11-26 11:12:30 +01:00
Daniel Grigencha
12fbf4e77e added javadocs to all server messages 2024-11-26 11:12:30 +01:00
Daniel Grigencha
f6d16a81bf added javadocs to all client messages 2024-11-26 11:12:30 +01:00
Fleischer Hanno
6938ce16b7 added the constructors for all client states and their statemachines 2024-11-26 11:12:30 +01:00
Daniel Grigencha
85ea4d340c added more logic for the server state diagram 2024-11-26 11:12:30 +01:00
Fleischer Hanno
e3d5d8e2e9 added message contents to the messages
addedn the conentents for all messages regarding the BPMN diagramm and own interpretation.
also created an identifier for pieces to be used for network communication between server and client so that they talk about the same piece.
2024-11-26 11:12:30 +01:00
Daniel Grigencha
6dfb2980fa added more logic for the server state diagram 2024-11-26 11:12:26 +01:00
Fleischer Hanno
f3894a5058 added a method to check if a player has pieces in his waiting area 2024-11-26 11:11:51 +01:00
Hanno Fleischer
787d8b558c added the method tryMove and the methods used by it into serverstate 2024-11-26 11:11:51 +01:00
Daniel Grigencha
3949a00932 added more logic for the server state diagram 2024-11-26 11:11:51 +01:00
514 changed files with 49717 additions and 35092 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,3 @@
.run/
.gradle
build/
#!gradle/wrapper/gradle-wrapper.jar

View File

@@ -26,14 +26,3 @@ implementation project(":mdga:model")
mainClass = 'pp.mdga.client.MdgaApp'
applicationName = 'MDGA'
}
tasks.register('fatJar', Jar) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes 'Main-Class': 'pp.mdga.client.MdgaApp'
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}

View File

@@ -1,140 +1,76 @@
package pp.mdga.client;
/**
* Represents different assets in the application. Each asset may have an associated model path,
* diffuse texture path, and a size factor. The enum provides multiple constructors to handle
* varying levels of detail for different assets.
*/
public enum Asset {
bigTent,
cardStack,
cir,
heer,
jet,
jet_noGear("Models/jet/jet_noGear.j3o", "Models/jet/jet_diff.png"),
lw,
marine,
node_home_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
node_wait_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
node_home_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
node_wait_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
node_home_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
node_wait_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
node_home_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
node_wait_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue_diff.png"),
node_wait_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_wait_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_wait_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_orange_diff.png"),
node_wait_yellow("./node_home/node_home.j3o", "./node_home/node_home_orange_diff.png"),
node_normal,
node_start("Models/node_normal/node_normal.j3o", "Models/node_normal/node_start_diff.png"),
node_bonus("Models/node_normal/node_normal.j3o", "Models/node_normal/node_bonus_diff.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,
ship(0.8f),
smallTent,
tank,
world(1.2f),
shieldRing("Models/shieldRing/shieldRing.j3o", null),
treeSmall(1.2f),
treeBig(1.2f),
// world(1.2f),
world("./world_new/world_export_new.obj", "./world_new/world_new_diff.png", 1.2f),
shield_ring("./shield_ring/shield_ring.obj", null),
tree_small("./tree_small/tree_small.obj", "./tree_small/tree_small_diff.png"),
tree_big("./tree_big/tree_big.obj", "./tree_big/tree_big_diff.png"),
turboCard,
turboSymbol("Models/turboCard/turboSymbol.j3o", "Models/turboCard/turboCard_diff.png"),
swapCard,
swapSymbol("Models/swapCard/swapSymbol.j3o", "Models/swapCard/swapCard_diff.png"),
shieldCard,
shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
dice,
missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
tankShoot("Models/tank/tankShoot_bot.j3o", "Models/tank/tank_diff.png"),
tankShootTop("Models/tank/tankShoot_top.j3o", "Models/tank/tank_diff.png"),
treesSmallBackground("Models/treeSmall/treesSmallBackground.j3o", "Models/treeSmall/treeSmall_diff.png", 1.2f),
treesBigBackground("Models/treeBig/treesBigBackground.j3o", "Models/treeBig/treeBig_diff.png", 1.2f),
shell;
shieldCard
;
private final String modelPath;
private final String diffPath;
private final float size;
private static final String ROOT = "Models/";
/**
* Default constructor. Initializes modelPath and diffPath based on the enum name and sets default size to 1.0.
*/
Asset() {
String folderFileName = "./" + ROOT + name() + "/" + name();
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = 1f;
}
/**
* Constructor with specific model path and diffuse texture path.
*
* @param modelPath Path to the 3D model file.
* @param diffPath Path to the diffuse texture file.
*/
Asset(String modelPath, String diffPath) {
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = 1f;
}
/**
* Constructor with specific model path. Diffuse texture path is derived based on enum name.
*
* @param modelPath Path to the 3D model file.
*/
Asset(String modelPath) {
String folderFileName = "./" + ROOT + name() + "/" + name();
this.modelPath = modelPath;
this.diffPath = folderFileName + "_diff.png";
;
this.size = 1f;
}
/**
* Constructor with specific size. Model and texture paths are derived based on enum name.
*
* @param size Scaling factor for the asset.
*/
Asset(float size) {
String folderFileName = "./" + ROOT + name() + "/" + name();
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = size;
}
/**
* Constructor with specific model path, diffuse texture path, and size.
*
* @param modelPath Path to the 3D model file.
* @param diffPath Path to the diffuse texture file.
* @param size Scaling factor for the asset.
*/
Asset(String modelPath, String diffPath, float size) {
Asset(String modelPath, String diffPath, float size){
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = size;
}
/**
* Gets the model path for the asset.
*
* @return Path to the 3D model file.
*/
public String getModelPath() {
return modelPath;
}
/**
* Gets the diffuse texture path for the asset.
*
* @return Path to the diffuse texture file, or null if not applicable.
*/
public String getDiffPath() {
return diffPath;
}
/**
* Gets the scaling factor for the asset.
*
* @return The size of the asset.
*/
public float getSize() {
return size;
}

View File

@@ -1,48 +0,0 @@
package pp.mdga.client;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
* An abstract control class that serves as a base for initializing spatial objects
* in jMonkeyEngine. This class overrides the controlUpdate and controlRender methods
* from the AbstractControl class, providing default empty implementations,
* and adds the ability to initialize spatial objects when they are set.
*/
public abstract class InitControl extends AbstractControl {
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
/**
* Sets the spatial object to be controlled. This method also initializes the spatial
* if it is being set for the first time.
*
* @param spatial The spatial object to control.
*/
@Override
public void setSpatial(Spatial spatial) {
if (this.spatial == null && spatial != null) {
super.setSpatial(spatial);
initSpatial();
}
}
/**
* Initializes the spatial object. This method can be overridden by subclasses
* to define custom initialization logic for the spatial.
* This method is called automatically when the spatial is set for the first time.
*/
protected void initSpatial() {
// Default empty implementation. Override to add initialization logic.
}
}

View File

@@ -1,354 +0,0 @@
package pp.mdga.client;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.board.NodeControl;
import pp.mdga.client.board.OutlineControl;
import pp.mdga.client.board.OutlineOEControl;
import pp.mdga.client.board.PieceControl;
import pp.mdga.client.gui.CardControl;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.client.view.GameView;
import pp.mdga.game.Color;
import java.util.List;
import java.util.UUID;
public class InputSynchronizer {
private MdgaApp app;
private InputManager inputManager;
protected boolean rightMousePressed = false;
private float rotationAngle = 180f;
private int scrollValue = 0;
private CardControl hoverCard;
private OutlineOEControl hoverPiece;
private boolean clickAllowed = true;
private boolean isRotateLeft = false;
private boolean isRotateRight = false;
/**
* Constructor initializes the InputSynchronizer with the application context.
* Sets up input mappings and listeners for user interactions.
*
* @param app The application instance
*/
InputSynchronizer(MdgaApp app) {
this.app = app;
this.inputManager = app.getInputManager();
hoverCard = null;
hoverPiece = null;
setupInput();
}
/**
* Updates the rotation angle based on user input.
*
* @param tpf The time per frame.
*/
public void update(float tpf) {
if (isRotateLeft && isRotateRight) {
return;
}
if (isRotateLeft) {
rotationAngle += 180 * tpf;
}
if (isRotateRight) {
rotationAngle -= 180 * tpf;
}
}
/**
* Configures input mappings for various actions and binds them to listeners.
*/
private void setupInput() {
inputManager.addMapping("Settings", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_Q));
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_E));
inputManager.addMapping("RotateRightMouse", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping("MouseLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); // Left movement
inputManager.addMapping("MouseRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); // Right movement
inputManager.addMapping("MouseScrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); // Scroll up
inputManager.addMapping("MouseScrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); // Scroll down
inputManager.addMapping("Test", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
UUID p = null;
/**
* Handles action-based input events such as key presses and mouse clicks.
*/
private final ActionListener actionListener = new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("Settings") && isPressed) {
app.getView().pressEscape();
}
if (name.equals("Forward") && isPressed) {
app.getView().pressForward();
}
if (name.equals("RotateRightMouse")) {
rightMousePressed = isPressed;
}
if (name.equals("Click") && isPressed) {
if (!clickAllowed) {
return;
}
if (app.getView() instanceof GameView gameView) {
DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
OutlineOEControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
if (diceSelect != null) {
app.getModelSynchronize().rolledDice();
} else if (cardLayerSelect != null) {
//cardSelect
if (cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
} else if (boardSelect != null) {
//boardSelect
if (boardSelect.isSelectable()) gameView.getBoardHandler().pieceSelect(boardSelect);
} else {
//both null
}
}
}
if (name.equals("Left")) {
isRotateLeft = !isRotateLeft;
}
if (name.equals("Right")) {
isRotateRight = !isRotateRight;
}
if (name.equals("Test2") && isPressed) {
if (app.getView() instanceof GameView gameView) {
if (p == null) {
p = UUID.randomUUID();
gameView.getBoardHandler().addPlayer(Color.AIRFORCE, List.of(p, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()));
gameView.getBoardHandler().movePieceStartAnim(p, 0);
gameView.getBoardHandler().outlineMove(List.of(p), List.of(2), List.of(false));
//gameView.getBoardHandler().movePieceAnim(p,0, 8);
} else {
gameView.getBoardHandler().throwPiece(p, Color.ARMY);
//gameView.getBoardHandler().movePieceStartAnim(p,0);
}
// gameView.getGuiHandler().rollRankingResult(Color.AIRFORCE, 1);
// gameView.getGuiHandler().rollRankingResult(Color.ARMY, 2);
// gameView.getGuiHandler().rollRankingResult(Color.NAVY, 3);
// gameView.getGuiHandler().rollRankingResult(Color.CYBER, 4);
// gameView.getGuiHandler().showDice();
// UUID p1 = UUID.randomUUID();
// gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p1,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
// gameView.getBoardHandler().movePieceStartAnim(p1,0);
//gameView.getGuiHandler().drawCard(Color.ARMY);
//gameView.getGuiHandler().addCardOwn(BonusCard.SHIELD);
//gameView.getGuiHandler().playCardOwn(BonusCard.SHIELD);
}
}
}
};
/**
* Handles analog-based input events such as mouse movement and scrolling.
*/
private final AnalogListener analogListener = new AnalogListener() {
@Override
public void onAnalog(String name, float value, float tpf) {
if (name.equals("MouseLeft") && rightMousePressed) {
rotationAngle -= value * 360f;
} else if (name.equals("MouseRight") && rightMousePressed) {
rotationAngle += value * 360f;
} else if (name.equals("MouseScrollUp")) {
scrollValue = Math.max(1, scrollValue - 5);
} else if (name.equals("MouseScrollDown")) {
scrollValue = Math.min(100, scrollValue + 5);
} else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")) {
hoverPiece();
hoverCard();
}
}
};
/**
* Detects the hovered piece and updates its hover state.
*/
private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T> controlType) {
if (cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Ray ray = new Ray(cam.getLocation(), getMousePos(cam).subtract(cam.getLocation()).normalize());
root.collideWith(ray, results);
for (CollisionResult collisionResult : results) {
if (collisionResult.getGeometry().getControl(controlType) != null)
return collisionResult.getGeometry().getControl(controlType);
}
return null;
}
/**
* Detects the hovered card and updates its hover state.
*/
private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Class<T> controlType) {
if (cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Vector3f mousePos = getMousePos(cam);
mousePos.setZ(cam.getLocation().getZ());
Ray ray = new Ray(mousePos, getMousePos(cam).subtract(mousePos).normalize());
root.collideWith(ray, results);
if (results.size() > 0) {
for (CollisionResult res : results) {
T control = res.getGeometry().getControl(controlType);
if (control != null) return control;
}
}
return null;
}
/**
* Handles the hover state for a piece in the game.
* Checks if a piece is being hovered over, updates the hover state, and triggers hover effects.
*/
private void hoverPiece() {
if (app.getView() instanceof GameView gameView) {
OutlineOEControl control = checkPiece();
if (control != null) {
if (control != hoverPiece) {
pieceOff(gameView);
hoverPiece = control;
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOn(hoverPiece);
}
} else {
pieceOff(gameView);
}
}
}
/**
* Handles the hover state for a card in the game.
* Checks if a card is being hovered over, updates the hover state, and triggers hover effects.
*/
private void hoverCard() {
if (app.getView() instanceof GameView gameView) {
CardControl control = checkCard(gameView);
if (control != null) {
if (control != hoverCard) {
cardOff();
hoverCard = control;
hoverCard.hoverOn();
}
} else {
cardOff();
}
}
}
/**
* Checks if a piece is being hovered over in the 3D game world.
*
* @return The PieceControl of the hovered piece, or null if no piece is hovered.
*/
private OutlineOEControl checkPiece() {
return checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
}
/**
* Checks if a card is being hovered over in the 2D card layer.
*
* @param gameView The current game view.
* @return The CardControl of the hovered card, or null if no card is hovered.
*/
private CardControl checkCard(GameView gameView) {
return checkHoverOrtho(
gameView.getGuiHandler().getCardLayerCamera(),
gameView.getGuiHandler().getCardLayerRootNode(),
CardControl.class
);
}
/**
* Disables the hover effect on the currently hovered piece, if any.
*/
private void pieceOff(GameView gameView) {
if (hoverPiece != null) {
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOff(hoverPiece);
}
hoverPiece = null;
}
/**
* Disables the hover effect on the currently hovered card, if any.
*/
private void cardOff() {
if (hoverCard != null) hoverCard.hoverOff();
hoverCard = null;
}
/**
* Retrieves the current mouse position in the 3D world using the specified camera.
*
* @param cam The camera used for determining the mouse position.
* @return A Vector3f representing the mouse position in the 3D world.
*/
private Vector3f getMousePos(Camera cam) {
Vector2f mousePositionScreen = inputManager.getCursorPosition();
Vector3f world = cam.getWorldCoordinates(mousePositionScreen, 0);
if (cam.isParallelProjection()) world.setZ(0);
return world;
}
/**
* Gets the current rotation angle of the game element.
*
* @return The rotation angle in degrees, normalized to 360 degrees.
*/
public float getRotation() {
return (rotationAngle / 2) % 360;
}
public void setRotation(float rotationAngle) {
this.rotationAngle = rotationAngle;
}
/**
* Gets the current scroll value.
*
* @return The scroll value as an integer.
*/
public int getScroll() {
return scrollValue;
}
public void setClickAllowed(boolean allowed) {
clickAllowed = allowed;
}
public boolean isClickAllowed() {
return clickAllowed;
}
}

View File

@@ -0,0 +1,72 @@
package pp.mdga.client;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
public class InputSyncronizer {
private MdgaApp app;
private InputManager inputManager;
protected boolean rightMousePressed = false;
private float rotationAngle = 180f;
private int scrollValue = 0;
InputSyncronizer(MdgaApp app) {
this.app = app;
this.inputManager = app.getInputManager();
setupInput();
}
private void setupInput() {
inputManager.addMapping("Settings", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addMapping("RotateRightMouse", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping("MouseLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); // Left movement
inputManager.addMapping("MouseRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); // Right movement
inputManager.addMapping("MouseScrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); // Scroll up
inputManager.addMapping("MouseScrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); // Scroll down
inputManager.addListener(actionListener, "Settings", "RotateRightMouse");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
private final ActionListener actionListener = new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("Settings") && isPressed) {
app.getView().pressEscape();
}
if (name.equals("RotateRightMouse")) {
rightMousePressed = isPressed;
}
}
};
private final AnalogListener analogListener = new AnalogListener() {
@Override
public void onAnalog(String name, float value, float tpf) {
if (name.equals("MouseLeft") && rightMousePressed) {
rotationAngle -= value * 360f;
} else if (name.equals("MouseRight") && rightMousePressed) {
rotationAngle += value * 360f;
} else if (name.equals("MouseScrollUp")) {
scrollValue = Math.max(1, scrollValue - 5);
} else if (name.equals("MouseScrollDown")) {
scrollValue = Math.min(100, scrollValue + 5);
}
}
};
public float getRotation() {
return (rotationAngle / 2) % 360;
}
public int getScroll() {
return scrollValue;
}
}

View File

@@ -1,186 +1,70 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.GuiGlobals;
import pp.mdga.client.acoustic.AcousticHandler;
import pp.mdga.client.animation.TimerManager;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.animation.AnimationHandler;
import com.jme3.system.AppSettings;
import pp.mdga.client.view.*;
import java.awt.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;
/**
* Main application class for the MdgaApp game.
* This class extends {@link SimpleApplication} and manages the game's lifecycle, states, and main components.
*/
public class MdgaApp extends SimpleApplication {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/**
* Handles acoustic effects and state-based sounds.
*/
private AnimationHandler animationHandler;
private AcousticHandler acousticHandler;
/**
* Synchronizes notifications throughout the application.
*/
private NotificationSynchronizer notificationSynchronizer;
private InputSyncronizer inputSyncronizer;
private ModelSyncronizer modelSyncronizer;
/**
* Manages input events and synchronization.
*/
private InputSynchronizer inputSynchronizer;
MdgaView view = null;
private MdgaState state = MdgaState.MAIN;
/**
* Synchronizes game models.
*/
private ModelSynchronizer modelSynchronizer;
private static float resolutionFactor = 1.8f;
/**
* The currently active view in the application.
*/
private MdgaView view = null;
/**
* The current state of the application.
*/
private MdgaState state = null;
/**
* Scale for rendering images.
*/
private final float imageScale = prefs.getInt("scale", 1);
/**
* The main menu view.
*/
private MainView mainView;
/**
* The lobby view.
*/
private LobbyView lobbyView;
/**
* The game view.
*/
private GameView gameView;
/**
* The ceremony view.
*/
private CeremonyView ceremonyView;
/**
* The client game logic.
*/
private final ClientGameLogic clientGameLogic;
private ExecutorService executor;
private ServerConnection networkConnection;
private final TimerManager timerManager = new TimerManager();
public static final int DEBUG_MULTIPLIER = 1;
/**
* Constructs a new MdgaApp instance.
* Initializes the network connection and client game logic.
*/
public MdgaApp() {
networkConnection = new NetworkSupport(this);
this.clientGameLogic = new ClientGameLogic(networkConnection);
}
/**
* Main entry point for the application.
* Configures settings and starts the application.
*
* @param args command-line arguments (not used)
*/
public static void main(String[] args) {
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
if (prefs.getBoolean("fullscreen", false)) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
settings.setResolution(screenWidth, screenHeight);
settings.setFullscreen(true);
} else {
settings.setWidth(prefs.getInt("width", 1280));
settings.setHeight(prefs.getInt("height", 720));
}
settings.setCenterWindow(true);
int width = (int)(1280 * resolutionFactor);
int height = (int)(720 * resolutionFactor);
settings.setWidth(width);
settings.setHeight(height);
settings.setVSync(false);
settings.setTitle("MDGA");
settings.setVSync(true);
MdgaApp app = new MdgaApp();
app.setSettings(settings);
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.setDisplayStatView(false);
app.start();
}
/**
* Initializes the application by setting up handlers, views, and entering the default state.
*/
@Override
public void simpleInitApp() {
GuiGlobals.initialize(this);
inputManager.deleteMapping("SIMPLEAPP_Exit");
flyCam.setEnabled(false);
animationHandler = new AnimationHandler(this);
acousticHandler = new AcousticHandler(this);
notificationSynchronizer = new NotificationSynchronizer(this);
inputSynchronizer = new InputSynchronizer(this);
modelSynchronizer = new ModelSynchronizer(this);
inputSyncronizer = new InputSyncronizer(this);
modelSyncronizer = new ModelSyncronizer(this);
mainView = new MainView(this);
lobbyView = new LobbyView(this);
gameView = new GameView(this);
ceremonyView = new CeremonyView(this);
inputManager.deleteMapping("SIMPLEAPP_Exit");
inputManager.deleteMapping("FLYCAM_ZoomIn");
inputManager.deleteMapping("FLYCAM_ZoomOut");
inputManager.deleteMapping("FLYCAM_RotateDrag");
flyCam.setEnabled(false);
GuiGlobals.initialize(this);
enter(MdgaState.MAIN);
enter(state);
}
/**
* Updates the application on each frame. Updates the view, acoustic handler, and notifications.
*
* @param tpf time per frame, used for smooth updating
*/
@Override
public void simpleUpdate(float tpf) {
view.update(tpf);
view.update();
acousticHandler.update();
notificationSynchronizer.update();
inputSynchronizer.update(tpf);
timerManager.update(tpf);
}
/**
* Transitions the application to a new state.
*
* @param state the new state to enter
* @throws RuntimeException if attempting to enter the {@link MdgaState#NONE} state
*/
public void enter(MdgaState state) {
if (null != view) {
if(null != view) {
view.leave();
}
@@ -188,19 +72,19 @@ public void enter(MdgaState state) {
switch (state) {
case MAIN:
view = mainView;
view = new MainView(this);
break;
case LOBBY:
view = lobbyView;
view = new LobbyView(this);
break;
case GAME:
view = gameView;
view = new GameView(this);
break;
case CEREMONY:
view = ceremonyView;
view = new CeremonyView(this);
break;
case NONE:
throw new RuntimeException("Cannot enter state NONE");
throw new RuntimeException("cant enter state NONE");
}
acousticHandler.playState(state);
@@ -208,209 +92,31 @@ public void enter(MdgaState state) {
view.enter();
}
public void afteGameCleanup() {
//TODO
}
public AnimationHandler getAnimationHandler() {
return animationHandler;
}
/**
* Gets the acoustic handler.
*
* @return the {@link AcousticHandler} instance
*/
public AcousticHandler getAcousticHandler() {
return acousticHandler;
}
/**
* Gets the current state of the application.
*
* @return the current {@link MdgaState}
*/
public MdgaState getState() {
return state;
public MdgaState getState() {return state; }
public float getResolutionFactor() {
return resolutionFactor;
}
/**
* Gets the image scaling factor.
*
* @return the image scale as a float
*/
public float getImageScale() {
return imageScale;
}
/**
* Gets the currently active view.
*
* @return the active {@link MdgaView}
*/
public MdgaView getView() {
return view;
}
/**
* Gets the model synchronizer.
*
* @return the {@link ModelSynchronizer} instance
*/
public ModelSynchronizer getModelSynchronize() {
return modelSynchronizer;
public ModelSyncronizer getModelSyncronizer() {
return modelSyncronizer;
}
/**
* Gets the input synchronizer.
*
* @return the {@link InputSynchronizer} instance
*/
public InputSynchronizer getInputSynchronize() {
return inputSynchronizer;
}
/**
* Gets the notification synchronizer.
*
* @return the {@link NotificationSynchronizer} instance
*/
public NotificationSynchronizer getNotificationSynchronizer() {
return notificationSynchronizer;
}
/**
* Prepares the app for a new game cycle.
*/
public void setup() {
}
/**
* Gets the client game logic.
*
* @return the {@link ClientGameLogic} instance
*/
public ClientGameLogic getGameLogic() {
return clientGameLogic;
}
/**
* Gets the executor service.
*
* @return the {@link ExecutorService} instance
*/
public ExecutorService getExecutor() {
if (this.executor == null) {
this.executor = Executors.newCachedThreadPool();
}
return this.executor;
}
/**
* Gets the network connection.
*
* @return the {@link ServerConnection} instance
*/
public ServerConnection getNetworkSupport() {
return networkConnection;
}
/**
* Updates the resolution settings.
*
* @param width the new width
* @param height the new height
* @param imageFactor the new image factor
* @param isFullscreen whether the game is in fullscreen mode
*/
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if (isFullscreen) {
int baseWidth = 1280;
int baseHeight = 720;
float baseAspectRatio = (float) baseWidth / baseHeight;
float newAspectRatio = (float) width / height;
float scaleFactor = Math.max((float) width / baseWidth, (float) height / baseHeight);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
settings.setResolution(screenWidth, screenHeight);
settings.setFullscreen(true);
prefs.putFloat("scale", scaleFactor);
prefs.putBoolean("fullscreen", true);
} else {
prefs.putInt("width", width);
prefs.putInt("height", height);
prefs.putFloat("scale", imageFactor);
prefs.putBoolean("fullscreen", false);
}
}
/**
* Restarts the application.
*/
public static void restartApp() {
try {
String javaBin = System.getProperty("java.home") + "/bin/java";
String classPath = System.getProperty("java.class.path");
String className = System.getProperty("sun.java.command");
ProcessBuilder builder = new ProcessBuilder(
javaBin, "-cp", classPath, className
);
builder.start();
System.exit(0);
} catch (Exception e) {
throw new RuntimeException("restart failed");
}
}
/**
* Cleans up the application after a game.
*/
public void afterGameCleanup() {
MainView main = (MainView) mainView;
main.getJoinDialog().disconnect();
if (clientGameLogic.isHost()) {
main.getHostDialog().shutdownServer();
}
ceremonyView.afterGameCleanup();
}
/**
* Gets the game view.
*
* @return the {@link GameView} instance
*/
public GameView getGameView() {
return gameView;
}
/**
* Gets the timer manager.
*
* @return the {@link TimerManager} instance
*/
public TimerManager getTimerManager() {
return timerManager;
}
/**
* Gets the ceremony view.
*
* @return the {@link CeremonyView} instance
*/
public CeremonyView getCeremonyView() {
return ceremonyView;
}
@Override
public void destroy() {
afterGameCleanup();
if (executor != null) {
executor.shutdown();
}
super.destroy();
}
public InputSyncronizer getInputSyncronizer() { return inputSyncronizer; }
}

View File

@@ -1,35 +1,9 @@
package pp.mdga.client;
/**
* Enum representing the various states of the MdgaApp application.
* Each state corresponds to a distinct phase or mode of the application.
*/
public enum MdgaState {
/**
* Represents an undefined or uninitialized state.
* This state should not be entered during normal application execution.
*/
NONE,
/**
* Represents the main menu state.
* This is typically the first state entered when the application starts.
*/
MAIN,
/**
* Represents the lobby state where players can prepare or wait before starting a game.
*/
LOBBY,
/**
* Represents the main gameplay state where the core game mechanics take place.
*/
GAME,
/**
* Represents the ceremony state, typically used for post-game events or celebrations.
*/
CEREMONY;
}

View File

@@ -1,243 +0,0 @@
package pp.mdga.client;
import pp.mdga.client.view.GameView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The ModelSynchronizer class is responsible for synchronizing the model state with the view and game logic.
*/
public class ModelSynchronizer {
private static final Logger LOGGER = Logger.getLogger(ModelSynchronizer.class.getName());
private MdgaApp app;
private UUID a;
private UUID b;
private BonusCard card;
private boolean swap;
/**
* Constructor for ModelSynchronizer.
*
* @param app the MdgaApp instance
*/
ModelSynchronizer(MdgaApp app) {
this.app = app;
swap = false;
}
/**
* Handles the end of an animation.
*/
public void animationEnd() {
if (app.getNotificationSynchronizer().waitForAnimation) {
app.getNotificationSynchronizer().waitForAnimation = false;
} else {
app.getGameLogic().selectAnimationEnd();
}
}
/**
* Selects a piece or swap based on the current state.
*
* @param a the first UUID
* @param b the second UUID
*/
public void select(UUID a, UUID b) {
if (swap) selectSwap(a, b);
else selectPiece(a);
}
/**
* Selects a swap between two pieces.
*
* @param a the first UUID
* @param b the second UUID
*/
public void selectSwap(UUID a, UUID b) {
LOGGER.log(Level.INFO, "selectPiece");
this.a = a;
this.b = b;
GameView gameView = (GameView) app.getView();
if (a != null && b != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
/**
* Selects a single piece.
*
* @param piece the UUID of the piece
*/
public void selectPiece(UUID piece) {
LOGGER.log(Level.INFO, "selectPiece");
this.a = piece;
GameView gameView = (GameView) app.getView();
if (piece != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
/**
* Selects a bonus card.
*
* @param card the BonusCard instance
*/
public void selectCard(BonusCard card) {
LOGGER.log(Level.INFO, "selectCard");
this.card = card;
GameView gameView = (GameView) app.getView();
if (card != null) {
gameView.needConfirm();
} else {
gameView.showNoPower();
}
}
/**
* Confirms the current selection.
*/
public void confirm() {
LOGGER.log(Level.INFO, "confirm");
GameView gameView = (GameView) app.getView();
gameView.getGuiHandler().hideText();
if (a != null && b != null) {
app.getGameLogic().selectPiece(a);
app.getGameLogic().selectPiece(b);
gameView.getBoardHandler().clearSelectable();
} else if (a != null) {
app.getGameLogic().selectPiece(a);
gameView.getBoardHandler().clearSelectable();
} else {
app.getGameLogic().selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
}
a = null;
b = null;
card = null;
gameView.noConfirm();
gameView.hideNoPower();
}
/**
* Selects a TSK color.
*
* @param color the Color instance
*/
public void selectTsk(Color color) {
app.getGameLogic().selectTsk(color);
}
/**
* Unselects a TSK color.
*
* @param color the Color instance
*/
public void unselectTsk(Color color) {
app.getGameLogic().deselectTSK(color);
}
/**
* Handles the event of rolling dice.
*/
public void rolledDice() {
app.getGameLogic().selectDice();
}
/**
* Sets the player's name.
*
* @param name the player's name
*/
public void setName(String name) {
LOGGER.log(Level.INFO, "setName: {0}", name);
app.getGameLogic().selectName(name);
}
/**
* Sets the player's ready status.
*
* @param ready the ready status
*/
public void setReady(boolean ready) {
app.getGameLogic().selectReady(ready);
}
/**
* Sets the host port.
*
* @param port the host port
*/
public void setHost(int port) {
app.getGameLogic().selectJoin("");
}
/**
* Sets the join IP and port.
*
* @param ip the IP address
* @param port the port number
*/
public void setJoin(String ip, int port) {
app.getGameLogic().selectJoin(ip);
}
/**
* Handles the event of leaving the game.
*/
public void leave() {
app.getGameLogic().selectLeave();
}
/**
* Enters a specific game state.
*
* @param state the MdgaState instance
*/
public void enter(MdgaState state) {
LOGGER.log(Level.INFO, "enter: {0}", state);
//app.enter(state);
}
/**
* Proceeds to the next game state.
*/
public void next() {
app.getGameLogic().selectNext();
}
/**
* Sets the swap state.
*
* @param swap the swap state
*/
public void setSwap(boolean swap) {
this.swap = swap;
}
/**
* Forces an action.
*/
public void force() {
// Implementation needed
}
}

View File

@@ -0,0 +1,72 @@
package pp.mdga.client;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.Color;
public class ModelSyncronizer {
private MdgaApp app;
ModelSyncronizer(MdgaApp app) {
this.app = app;
}
public void selectPiece() {
//TODO call from somewhere
System.out.println("selectPiece");
}
public void selectCard() {
//TODO call from somewhere
System.out.println("selectCard");
}
public void selectTsk(Color color) {
//TODO call from somewhere
System.out.println("selectTsk: " + color);
LobbyView view = (LobbyView) app.getView();
view.setTaken(color, true, true, "OwnPlayerName");
}
public void unselectTsk() {
//TODO call from somewhere
System.out.println("unselectTsk");
}
public void rolledDice() {
//TODO call from somewhere
System.out.println("rolledDice");
}
public void setName(String name) {
//TODO call from somewhere
System.out.println("setName:" + name);
}
public void setReady() {
//TODO call from somewhere
System.out.println("setReady");
app.enter(MdgaState.GAME);
}
public void setHost(int port) {
//TODO call from somewhere
System.out.println("setHost: " + port);
app.enter(MdgaState.LOBBY);
}
public void setJoin(String ip, int port) {
//TODO call from somewhere
System.out.println("setJoin");
app.enter(MdgaState.LOBBY);
}
public void leave() {
System.out.println("leave");
app.enter(MdgaState.MAIN);
}
public void enter(MdgaState state) {
System.out.println("enter:" + state);
app.enter(state);
}
}

View File

@@ -1,147 +0,0 @@
package pp.mdga.client;
import com.jme3.network.*;
import pp.mdga.message.client.ClientMessage;
import pp.mdga.message.server.ServerMessage;
import java.io.IOException;
/**
* The NetworkSupport class provides support for network communication between the client and server.
* It implements the MessageListener and ClientStateListener interfaces to handle incoming messages
* and client state changes, respectively.
*/
public class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
private static final System.Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
private final MdgaApp app;
private Client client;
/**
* Constructor for NetworkSupport.
*
* @param app the MdgaApp instance
*/
public NetworkSupport(MdgaApp app) {
this.app = app;
}
/**
* Returns the MdgaApp instance.
*
* @return the MdgaApp instance
*/
public MdgaApp getApp() {
return this.app;
}
/**
* Returns whether the client is connected to the server.
*
* @return true if the client is connected, false otherwise
*/
public boolean isConnected() {
return this.client != null && this.client.isConnected();
}
/**
* Connects the client to the server.
*/
public void connect() {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
} else {
try {
this.initNetwork("localhost", 2345);
} catch (IOException e) {
LOGGER.log(System.Logger.Level.ERROR, "could not connect to server", e);
}
}
}
/**
* Disconnects the client from the server.
*/
public void disconnect() {
if (this.client != null) {
this.client.close();
this.client = null;
LOGGER.log(System.Logger.Level.INFO, "client closed");
}
}
/**
* Initializes the network connection to the server.
*
* @param host the server host
* @param port the server port
* @throws IOException if an I/O error occurs
*/
public void initNetwork(String host, int port) throws IOException {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
} else {
this.client = Network.connectToServer(host, port);
this.client.start();
this.client.addMessageListener(this);
this.client.addClientStateListener(this);
}
}
/**
* Handles incoming messages from the server.
*
* @param client the client
* @param message the message
*/
public void messageReceived(Client client, Message message) {
LOGGER.log(System.Logger.Level.INFO, "message received from server: {0}", new Object[]{message});
if (message instanceof ServerMessage serverMessage) {
this.app.enqueue(() -> serverMessage.accept(this.app.getGameLogic()));
}
}
/**
* Handles client connection to the server.
*
* @param client the client
*/
public void clientConnected(Client client) {
LOGGER.log(System.Logger.Level.INFO, "Client connected: {0}", new Object[]{client});
}
/**
* Handles client disconnection from the server.
*
* @param client the client
* @param disconnectInfo the disconnect information
*/
public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo disconnectInfo) {
LOGGER.log(System.Logger.Level.INFO, "Client {0} disconnected: {1}", new Object[]{client, disconnectInfo});
if (this.client != client) {
throw new IllegalArgumentException("parameter value must be client");
} else {
LOGGER.log(System.Logger.Level.INFO, "client still connected: {0}", new Object[]{client.isConnected()});
this.client = null;
this.disconnect();
}
}
/**
* Sends a message to the server.
*
* @param message the message
*/
@Override
public void send(ClientMessage message) {
LOGGER.log(System.Logger.Level.INFO, "sending {0}", new Object[]{message});
if (this.client == null) {
LOGGER.log(System.Logger.Level.WARNING, "client not connected");
} else {
this.client.send(message);
}
}
}

View File

@@ -1,300 +1,136 @@
package pp.mdga.client;
import com.jme3.system.NanoTimer;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.notification.*;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
/**
* The NotificationSynchronizer class is responsible for handling and synchronizing notifications
* received from the game logic and updating the application state accordingly.
*/
public class NotificationSynchronizer {
private final MdgaApp app;
private ArrayList<Notification> notifications = new ArrayList<>();
private NanoTimer timer = new NanoTimer();
private float delay = 0;
private static final float STANDARD_DELAY = 2.5f;
public boolean waitForAnimation = false;
/**
* Constructs a NotificationSynchronizer with the specified MdgaApp instance.
*
* @param app the MdgaApp instance
*/
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
/**
* Updates the notification synchronizer by processing notifications from the game logic.
* Handles different types of notifications based on the current application state.
*/
public void addTestNotification(Notification n) {
notifications.add(n);
}
public void update() {
while (timer.getTimeInSeconds() >= delay) {
if (waitForAnimation) {
return;
}
//TODO fetch model notifications
Notification n = app.getGameLogic().getNotification();
if (n == null) {
return;
}
System.out.println("receive notification:" + n.getClass().getName());
timer.reset();
delay = 0;
if (n instanceof InfoNotification infoNotification) {
app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
return;
}
if (n != null) {
switch (app.getState()) {
case MAIN:
handleMain(n);
break;
case LOBBY:
handleLobby(n);
break;
case GAME:
handleGame(n);
break;
case CEREMONY:
handleCeremony(n);
break;
case NONE:
throw new RuntimeException("no notification expected: " + n.getClass().getName());
}
if (0 == MdgaApp.DEBUG_MULTIPLIER) {
delay = 0;
}
for (Notification n : notifications) {
switch (app.getState()) {
case MAIN:
handleMain(n);
break;
case LOBBY:
handleLobby(n);
break;
case GAME:
handleGame(n);
break;
case CEREMONY:
handleCeremony(n);
break;
case NONE:
throw new RuntimeException("no notification expected: " + n.toString());
}
}
}
/**
* Handles notifications when the application is in the MAIN state.
*
* @param notification the notification to handle
*/
private void handleMain(Notification notification) {
if (notification instanceof LobbyDialogNotification) {
app.enter(MdgaState.LOBBY);
} else if (notification instanceof StartDialogNotification) {
//nothing
} else {
throw new RuntimeException("notification not expected in main: " + notification.getClass().getName());
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
/**
* Handles notifications when the application is in the LOBBY state.
*
* @param notification the notification to handle
*/
private void handleLobby(Notification notification) {
LobbyView lobbyView = (LobbyView) app.getView();
if (notification instanceof TskSelectNotification n) {
if (notification instanceof TskSelectNotification) {
TskSelectNotification n = (TskSelectNotification)notification;
lobbyView.setTaken(n.getColor(), true, n.isSelf(), n.getName());
} else if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof TskUnselectNotification n) {
} else if (notification instanceof TskUnselectNotification) {
TskUnselectNotification n = (TskUnselectNotification)notification;
lobbyView.setTaken(n.getColor(), false, false, null);
} else if (notification instanceof LobbyReadyNotification lobbyReadyNotification) {
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
} else if (notification instanceof GameNotification n) {
app.getGameView().setOwnColor(n.getOwnColor());
} else if (notification instanceof GameNotification) {
app.enter(MdgaState.GAME);
} else {
throw new RuntimeException("notification not expected in lobby: " + notification.getClass().getName());
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
/**
* Handles notifications when the application is in the GAME state.
*
* @param notification the notification to handle
*/
private void handleGame(Notification notification) {
GameView gameView = (GameView) app.getView();
GuiHandler guiHandler = gameView.getGuiHandler();
BoardHandler boardHandler = gameView.getBoardHandler();
ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
Color ownColor = gameView.getOwnColor();
if (notification instanceof AcquireCardNotification n) {
guiHandler.addCardOwn(n.getBonusCard());
app.getAcousticHandler().playSound(MdgaSound.BONUS);
delay = STANDARD_DELAY;
} else if (notification instanceof RankingResponceNotification n) {
guiHandler.hideText();
n.getRankingResults().forEach((c, i) -> {
guiHandler.rollRankingResult(c, i);
});
delay = STANDARD_DELAY;
} else if (notification instanceof ActivePlayerNotification n) {
guiHandler.hideText();
boardHandler.hideDice();
gameView.getGuiHandler().setActivePlayer(n.getColor());
if (n.getColor() != ownColor) boardHandler.showDice(n.getColor());
app.getAcousticHandler().playSound(MdgaSound.UI90);
delay = STANDARD_DELAY;
} else if (notification instanceof CeremonyNotification ceremonyNotification) {
CeremonyView ceremonyView = app.getCeremonyView();
int size = ceremonyNotification.getNames().size();
if (ceremonyNotification.getPiecesThrown().size() != size ||
ceremonyNotification.getPiecesLost().size() != size ||
ceremonyNotification.getBonusCardsPlayed().size() != size ||
ceremonyNotification.getSixes().size() != size ||
ceremonyNotification.getNodesMoved().size() != size ||
ceremonyNotification.getBonusNodes().size() != size) {
throw new IllegalArgumentException("All data lists in CeremonyNotification must have the same size.");
}
for (int i = 0; i < size; i++) {
Color color = ceremonyNotification.getColors().get(i);
String name = ceremonyNotification.getNames().get(i);
int v1 = ceremonyNotification.getPiecesThrown().get(i);
int v2 = ceremonyNotification.getPiecesLost().get(i);
int v3 = ceremonyNotification.getBonusCardsPlayed().get(i);
int v4 = ceremonyNotification.getSixes().get(i);
int v5 = ceremonyNotification.getNodesMoved().get(i);
int v6 = ceremonyNotification.getBonusNodes().get(i);
if(i < size - 1) {
ceremonyView.addCeremonyParticipant(color, i + 1, name);
}
ceremonyView.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
}
if (notification instanceof AcquireCardNotification) {
// Handle AcquireCardNotification
} else if (notification instanceof ActivePlayerNotification) {
// Handle ActivePlayerNotification
} else if (notification instanceof CeremonyNotification) {
app.enter(MdgaState.CEREMONY);
} else if (notification instanceof DiceNowNotification) {
guiHandler.hideText();
guiHandler.showDice();
} else if (notification instanceof DrawCardNotification n) {
app.getAcousticHandler().playSound(MdgaSound.BONUS);
guiHandler.drawCard(n.getColor());
delay = STANDARD_DELAY;
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
waitForAnimation = true;
} else if (notification instanceof InterruptNotification notification1) {
gameView.enterInterrupt(notification1.getColor());
} else if (notification instanceof MovePieceNotification n) {
if (n.isMoveStart()) {
//StartMove
boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
waitForAnimation = true;
} else {
//InfieldMove
boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
waitForAnimation = true;
}
guiHandler.hideText();
} else if (notification instanceof ThrowPieceNotification n) {
boardHandler.throwPiece(n.getPieceId(), n.getThrowColor());
waitForAnimation = true;
} else if (notification instanceof RemoveShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceUuid());
} else if (notification instanceof PlayCardNotification n) {
if (n.getCard() == BonusCard.TURBO) {
app.getAcousticHandler().playSound(MdgaSound.TURBO);
guiHandler.turbo();
} else if (n.getCard() == BonusCard.SHIELD) {
app.getAcousticHandler().playSound(MdgaSound.SHIELD);
} else if (n.getCard() == BonusCard.SWAP) {
app.getAcousticHandler().playSound(MdgaSound.SWAP);
}
if (n.getColor() == ownColor) guiHandler.playCardOwn(n.getCard());
else guiHandler.playCardEnemy(n.getColor(), n.getCard());
new Timer().schedule(new TimerTask() {
@Override
public void run() {
app.getModelSynchronize().animationEnd();
}
}, 2200 * MdgaApp.DEBUG_MULTIPLIER);
} else if (notification instanceof PlayerInGameNotification n) {
boardHandler.addPlayer(n.getColor(), n.getPiecesList());
guiHandler.addPlayer(n.getColor(), n.getName());
// Handle DiceNowNotification
} else if (notification instanceof DicingNotification) {
// Handle DicingNotification
} else if (notification instanceof DrawCardNotification) {
// Handle DrawCardNotification
} else if (notification instanceof HomeMoveNotification) {
HomeMoveNotification n = (HomeMoveNotification)notification;
gameView.getBoardHandler().moveHomePiece(n.getPieceId(), n.getHomeIndex());
} else if (notification instanceof InterruptNotification) {
// Handle InterruptNotification
} else if (notification instanceof MovePieceNotification) {
MovePieceNotification n = (MovePieceNotification)notification;
//gameView.getBoardHandler().movePiece(n.get); //TODO
} else if (notification instanceof MoveThrowPieceNotification) {
MoveThrowPieceNotification n = (MoveThrowPieceNotification)notification;
//gameView.getBoardHandler().throwPiece(n.); //TODO
} else if (notification instanceof NoShieldNotification) {
NoShieldNotification n = (NoShieldNotification)notification;
gameView.getBoardHandler().unshieldPiece(n.getPieceId());
} else if (notification instanceof PieceInGameNotification) {
// Handle PieceInGameNotification
} else if (notification instanceof PlayCardNotification) {
// Handle PlayCardNotification
} else if (notification instanceof PlayerInGameNotification) {
// Handle PlayerInGameNotification
} else if (notification instanceof ResumeNotification) {
gameView.leaveInterrupt();
} else if (notification instanceof RollDiceNotification n) {
gameView.getGuiHandler().hideText();
if (n.getColor() == ownColor) {
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
waitForAnimation = true;
} else {
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
else guiHandler.showRolledDice(n.getEyes(), n.getColor());
}
} else if (notification instanceof SelectableCardsNotification n) {
guiHandler.setSelectableCards(n.getCards());
gameView.showNoPower();
} else if (notification instanceof ShieldActiveNotification n) {
boardHandler.shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification n) {
boardHandler.suppressShield(n.getPieceId());
// Handle ResumeNotification
} else if (notification instanceof RollDiceNotification) {
// Handle RollDiceNotification
} else if (notification instanceof SelectableCardsNotification) {
// Handle SelectableCardsNotification
} else if (notification instanceof SelectablePiecesNotification) {
// Handle SelectablePiecesNotification
} else if (notification instanceof ShieldActiveNotification) {
ShieldActiveNotification n = (ShieldActiveNotification)notification;
gameView.getBoardHandler().shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification) {
ShieldSuppressedNotification n = (ShieldSuppressedNotification)notification;
gameView.getBoardHandler().suppressShield(n.getPieceId());
} else if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof SwapPieceNotification n) {
boardHandler.swapPieceAnim(n.getFirstPiece(), n.getSecondPiece());
guiHandler.swap();
} else if (notification instanceof SwapPieceNotification) {
// Handle SwapPieceNotification
} else if (notification instanceof WaitMoveNotification) {
//nothing
} else if (notification instanceof SelectableMoveNotification n) {
boardHandler.outlineMove(n.getPieces(), n.getMoveIndices(), n.getHomeMoves());
modelSynchronizer.setSwap(false);
} else if (notification instanceof SelectableSwapNotification n) {
boardHandler.outlineSwap(n.getOwnPieces(), n.getEnemyPieces());
modelSynchronizer.setSwap(true);
} else if (notification instanceof SelectableShieldNotification n) {
boardHandler.outlineShield(n.getPieces());
modelSynchronizer.setSwap(false);
} else if (notification instanceof TurboActiveNotification) {
//nothing
} else if (notification instanceof FinishNotification n) {
guiHandler.finish(n.getColorFinished());
// Handle WaitMoveNotification
} else {
throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
/**
* Handles notifications when the application is in the CEREMONY state.
*
* @param notification the notification to handle
*/
private void handleCeremony(Notification notification) {
if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else {
throw new RuntimeException("notification not expected in ceremony: " + notification.getClass().getName());
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
}

View File

@@ -1,48 +0,0 @@
package pp.mdga.client;
import com.jme3.math.Vector3f;
public class Util {
private Util() {
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The ending value.
* @param t A parameter between 0 and 1 representing the interpolation progress.
* @return The interpolated value.
*/
public static float linInt(float start, float end, float t) {
return start + t * (end - start);
}
/**
* Performs quadratic interpolation between three points.
*
* @param p1 The initial point.
* @param p2 The middle point.
* @param p3 The final point.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated point.
*/
public static Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
// Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
float oneMinusT = 1 - t;
return p1.mult(oneMinusT * oneMinusT)
.add(p2.mult(2 * oneMinusT * t))
.add(p3.mult(t * t));
}
/**
* A smooth ease-in-out function for interpolation.
* It accelerates and decelerates the interpolation for a smoother effect.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The adjusted interpolation value.
*/
public static float easeInOut(float x) {
return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
}
}

View File

@@ -4,11 +4,7 @@
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import java.util.prefs.Preferences;
import java.util.*;
public class AcousticHandler {
private MdgaApp app;
@@ -21,30 +17,20 @@ public class AcousticHandler {
private boolean fading = false; // Indicates if a fade is in progress
private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
private static final float FADE_DURATION = 2.0f; // Duration for outfade
private static final float FADE_DURATION = 3.0f; // Duration for outfade
private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade
private GameMusic playing = null; // Currently playing track
private GameMusic scheduled = null; // Scheduled track to play next
private GameMusic old = null; // Old track being faded out
private GameMusic birds;
private float mainVolume = 0.0f;
private float mainVolume = 1.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
private ArrayList<GameSound> sounds = new ArrayList<>();
private Preferences prefs = Preferences.userNodeForPackage(AcousticHandler.class);
public AcousticHandler(MdgaApp app) {
this.app = app;
mainVolume = prefs.getFloat("mainVolume", 1.0f);
musicVolume = prefs.getFloat("musicVolume", 1.0f);
soundVolume = prefs.getFloat("soundVolume", 1.0f);
birds = new GameMusic(app, MusicAsset.BIRDS, getSoundVolumeTotal(), MusicAsset.BIRDS.getSubVolume(), MusicAsset.BIRDS.getLoop(), 0.0f);
}
/**
@@ -67,8 +53,6 @@ public void update() {
iterator.remove();
}
}
birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
}
/**
@@ -83,84 +67,10 @@ public void playSound(MdgaSound sound) {
assets.add(new SoundAssetDelayVolume(SoundAsset.LOST, 1.0f, 0.0f));
break;
case VICTORY:
assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 0.0f));
break;
case BUTTON_PRESSED:
assets.add(new SoundAssetDelayVolume(SoundAsset.BUTTON_PRESS, 0.7f, 0.0f));
break;
case WRONG_INPUT:
assets.add(new SoundAssetDelayVolume(SoundAsset.ERROR, 1.0f, 0.0f));
break;
case UI_CLICK:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_CLICK, 0.8f, 0.0f));
break;
case START:
assets.add(new SoundAssetDelayVolume(SoundAsset.START, 0.8f, 0.5f));
break;
case THROW:
assets.add(new SoundAssetDelayVolume(SoundAsset.LAUGHT, 1.0f, 0.2f));
break;
case POWERUP:
assets.add(new SoundAssetDelayVolume(SoundAsset.POWERUP, 1.0f, 0.2f));
break;
case SELF_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.ROBOT_READY, 1.0f, 0.0f));
break;
case OTHER_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.UNIT_READY, 1.0f, 0.0f));
break;
case OTHER_CONNECTED:
assets.add(new SoundAssetDelayVolume(SoundAsset.CONNECTED, 1.0f, 0.0f));
break;
case NOT_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND, 1.0f, 0.0f));
break;
case LEAVE:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND2, 0.6f, 0.0f));
break;
case JET:
assets.add(new SoundAssetDelayVolume(SoundAsset.JET, 1.0f, 0.0f));
break;
case EXPLOSION:
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 0f));
break;
case LOSE:
assets.add(new SoundAssetDelayVolume(SoundAsset.LOSE, 1.0f, 0.0f));
break;
case BONUS:
assets.add(new SoundAssetDelayVolume(SoundAsset.BONUS, 1.0f, 0.0f));
break;
case UI90:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI90, 1.0f, 0.0f));
break;
case MISSILE:
assets.add(new SoundAssetDelayVolume(SoundAsset.MISSILE, 1.0f, 0.0f));
break;
case MATRIX:
assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
break;
case TURRET_ROTATE:
assets.add(new SoundAssetDelayVolume(SoundAsset.TURRET_ROTATE, 0.7f, 0f));
break;
case TANK_SHOOT:
assets.add(new SoundAssetDelayVolume(SoundAsset.TANK_SHOOT, 0.7f, 0f));
break;
case TANK_EXPLOSION:
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
break;
case SHIELD:
assets.add(new SoundAssetDelayVolume(SoundAsset.SHIELD, 1.0f, 0f));
break;
case TURBO:
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 0.1f));
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 1.3f));
break;
case SWAP:
assets.add(new SoundAssetDelayVolume(SoundAsset.SWAP, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 2.0f));
break;
default:
break;
}
@@ -181,10 +91,6 @@ public void playState(MdgaState state) {
}
MusicAsset asset = null;
birds.pause();
float pause = 0.0f;
switch (state) {
case MAIN:
playGame = false;
@@ -195,12 +101,10 @@ public void playState(MdgaState state) {
asset = MusicAsset.LOBBY;
break;
case GAME:
birds.play();
addGameTracks();
playGame = true;
assert (!gameTracks.isEmpty()) : "no more game music available";
asset = gameTracks.remove(0);
pause = 2.0f;
break;
case CEREMONY:
playGame = false;
@@ -212,7 +116,7 @@ public void playState(MdgaState state) {
assert (null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), pause);
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f);
}
/**
@@ -229,20 +133,20 @@ private float lerp(float start, float end, float t) {
/**
* Updates the state of audio playback, handling track transitions and volume adjustments.
* <p>
*
* This method ensures smooth transitions between tracks using fade-in and fade-out effects.
* It also handles cases where no track is playing, starting a scheduled track immediately at full volume.
* The method prioritizes the latest scheduled track if multiple scheduling occurs quickly.
* <p>
*
* Behavior:
* 1. If nothing is scheduled and no track is playing, it exits early.
* 2. If a scheduled track exists and no track is playing, the scheduled track starts immediately at full volume.
* 3. If a scheduled track exists while a track is playing, it initiates a fade-out for the currently playing track
* and prepares for the new track to fade in.
* and prepares for the new track to fade in.
* 4. If a track transition is in progress (fading), it processes the fade-out and fade-in states.
* If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
* If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
* 5. If no fading is needed and a track is playing, it ensures the track's volume is updated.
* <p>
*
* Special cases:
* - If no track is playing and a new track is scheduled, it starts the track immediately without fading.
* - If a new track is scheduled during fading, it resets the transition to prioritize the new track.
@@ -294,23 +198,23 @@ private void updateVolumeAndTrack() {
/**
* Manages the fading process during audio track transitions.
* <p>
*
* This method handles the fade-out of the currently playing (old) track, manages any pause between the fade-out
* and fade-in, and initiates the fade-in for the new track if applicable. It ensures smooth transitions between
* tracks while maintaining the correct volume adjustments.
* <p>
*
* Behavior:
* 1. **Outfade:** Gradually decreases the volume of the `old` track over the duration of `FADE_DURATION`.
* Once the outfade completes, the `old` track is paused and cleared.
* Once the outfade completes, the `old` track is paused and cleared.
* 2. **Pause Handling:** Waits for a defined pause (if applicable) before initiating the infade for the next track.
* 3. **Infade:** If a `scheduled` track exists and the outfade and pause are complete, it begins playing
* the new track (`playing`) and initiates the infade process.
* <p>
* the new track (`playing`) and initiates the infade process.
*
* Key Details:
* - The outfade volume adjustment is interpolated linearly from full volume to zero using the `lerp` function.
* - The pause duration is retrieved from the scheduled track if it is specified.
* - If a new track is scheduled during the fade process, it is handled by external logic to prioritize transitions.
* <p>
*
* Preconditions:
* - `fading` is expected to be `true` when this method is called.
* - The method is invoked as part of the `updateVolumeAndTrack` process.
@@ -346,23 +250,23 @@ private void handleFadeProcess() {
/**
* Manages the fade-in process for the currently playing track.
* <p>
*
* This method gradually increases the volume of the `playing` track from zero to full volume
* over the duration of `CROSSFADE_DURATION`. It ensures a smooth transition into the new track.
* <p>
*
* Behavior:
* 1. If no track is set as `playing`, the method exits early, as there is nothing to fade in.
* 2. Linearly interpolates the volume of the `playing` track from 0.0 to 1.0 based on the elapsed
* `infadeTime` and the specified `CROSSFADE_DURATION`.
* `infadeTime` and the specified `CROSSFADE_DURATION`.
* 3. Once the fade-in is complete (when `infadeTime` exceeds `CROSSFADE_DURATION`), the method:
* - Marks the fade process (`fading`) as complete.
* - Ensures the `playing` track is updated to its full volume.
* <p>
* - Marks the fade process (`fading`) as complete.
* - Ensures the `playing` track is updated to its full volume.
*
* Key Details:
* - Uses the `lerp` function to calculate the volume level for the `playing` track during the fade-in.
* - Ensures the volume is always a value between 0.0 and 1.0.
* - The `infadeTime` parameter should be relative to the start of the fade-in process.
* <p>
*
* Preconditions:
* - The `playing` track must be initialized and actively fading in for this method to have an effect.
* - The method is invoked as part of the `updateVolumeAndTrack` process.
@@ -405,7 +309,7 @@ private void addGameTracks() {
* a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
*/
private void updateGameTracks() {
if (null == playing) {
if(null == playing) {
return;
}
@@ -458,7 +362,6 @@ public float getSoundVolume() {
*/
public void setMainVolume(float mainVolume) {
this.mainVolume = mainVolume;
prefs.putFloat("mainVolume", mainVolume);
}
/**
@@ -468,7 +371,6 @@ public void setMainVolume(float mainVolume) {
*/
public void setMusicVolume(float musicVolume) {
this.musicVolume = musicVolume;
prefs.putFloat("musicVolume", musicVolume);
}
/**
@@ -478,7 +380,6 @@ public void setMusicVolume(float musicVolume) {
*/
public void setSoundVolume(float soundVolume) {
this.soundVolume = soundVolume;
prefs.putFloat("soundVolume", soundVolume);
}
/**
@@ -488,7 +389,7 @@ public void setSoundVolume(float soundVolume) {
*/
float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume() / 2;
return getMusicVolume() * getMainVolume();
}
/**

View File

@@ -19,29 +19,5 @@ public enum MdgaSound {
DESELECT,
HURRY,
VICTORY,
LOST,
BUTTON_PRESSED,
WRONG_INPUT,
UI_CLICK,
START,
THROW,
POWERUP,
SELF_READY,
OTHER_READY,
OTHER_CONNECTED,
NOT_READY,
LEAVE,
JET,
EXPLOSION,
LOSE,
BONUS,
UI90,
MISSILE,
MATRIX,
TURRET_ROTATE,
TANK_SHOOT,
TANK_EXPLOSION,
SHIELD,
TURBO,
SWAP,
LOST
}

View File

@@ -10,18 +10,16 @@ enum MusicAsset {
MAIN_MENU("Spaceship.wav", true, 1.0f),
LOBBY("DeadPlanet.wav", true, 1.0f),
CEREMONY("80s,Disco,Life.wav", true, 1.0f),
GAME_1("NeonRoadTrip.wav", 0.5f),
GAME_2("NoPressureTrance.wav", 0.5f),
GAME_3("TheSynthRave.wav", 0.5f),
GAME_4("LaserParty.wav", 0.5f),
GAME_5("RetroNoir.wav", 0.5f),
GAME_6("SpaceInvaders.wav", 0.5f),
BIRDS("nature-ambience.ogg", true, 1.0f);
GAME_1("NeonRoadTrip.wav", 1.0f),
GAME_2("NoPressureTrance.wav", 1.0f),
GAME_3("TheSynthRave.wav", 1.0f),
GAME_4("LaserParty.wav", 1.0f),
GAME_5("RetroNoir.wav", 1.0f),
GAME_6("SpaceInvaders.wav", 1.0f);
private final String path;
private final boolean loop;
private final float subVolume;
private static final String ROOT = "Music/";
/**
* Constructs a new MusicAsset object with the specified name and sub-volume.
@@ -31,7 +29,7 @@ enum MusicAsset {
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
*/
MusicAsset(String name, float subVolume) {
this.path = ROOT + name;
this.path = "music/" + name;
this.loop = false;
this.subVolume = subVolume;
}
@@ -44,7 +42,7 @@ enum MusicAsset {
* @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 = ROOT + name;
this.path = "music/" + name;
this.loop = loop;
this.subVolume = subVolume;
}

View File

@@ -17,34 +17,7 @@ enum SoundAsset {
DESELECT(""),
HURRY(""),
VICTORY("LevelUp2.wav"),
LOST("GameOver.wav"),
BUTTON_PRESS("menu_button.ogg"),
ERROR("buzzer.wav"),
UI_SOUND("ui_sound.ogg"),
UI_SOUND2("ui_swoosch.wav"),
UI_CLICK("uiclick.ogg"),
START("gamestart.ogg"),
LAUGHT("laughter.wav"),
POWERUP("powerup.wav"),
ROBOT_READY("robotReady.wav"),
UNIT_READY("unitReady.wav"),
JET("jet-overhead.wav"),
EXPLOSION_1("exp.ogg"),
EXPLOSION_2("exp2.ogg"),
THUNDER("thunder.ogg"),
UI90("ui90.ogg"),
BONUS("bonus.ogg"),
LOSE("lose.ogg"),
MISSILE("missile.ogg"),
MATRIX("matrix.wav"),
CONNECTED("connected.wav"),
TURRET_ROTATE("turret_rotate.ogg"),
TANK_SHOOT("tank_shoot.ogg"),
SHIELD("shield.ogg"),
SPEED("speed.ogg"),
SWAP("swap.ogg"),
;
LOST("GameOver.wav");
private final String path;
@@ -54,7 +27,7 @@ enum SoundAsset {
* @param name The name of the sound file.
*/
SoundAsset(String name) {
this.path = "Sounds/" + name;
this.path = "sound/" + name;
}
/**

View File

@@ -4,5 +4,4 @@
* 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) {
}
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {}

View File

@@ -1,25 +0,0 @@
package pp.mdga.client.animation;
import pp.mdga.client.InitControl;
public class ActionControl extends InitControl {
private final Runnable runnable;
/**
* Constructs a new ActionControl object with the specified action.
*
* @param runnable The action to be performed.
*/
public ActionControl(Runnable runnable) {
this.runnable = runnable;
}
/**
* Performs the action associated with this control.
*/
protected void action() {
if (null != runnable) {
runnable.run();
}
}
}

View File

@@ -0,0 +1,10 @@
package pp.mdga.client.animation;
abstract class Animation {
abstract void play();
abstract void stop();
abstract boolean isOver();
}

View File

@@ -0,0 +1,29 @@
package pp.mdga.client.animation;
import pp.mdga.client.MdgaApp;
public class AnimationHandler {
private MdgaApp app;
private Animation animation = null;
public AnimationHandler(MdgaApp app) {
this.app = app;
}
public void playAnimation(MdgaAnimation type) {
}
public void update() {
if (null == animation) {
return;
}
if (animation.isOver()) {
animation = null;
//trigger next state in model
}
}
}

View File

@@ -0,0 +1,18 @@
package pp.mdga.client.animation;
class EmptyAnimation extends Animation {
@Override
void play() {
//nothing
}
@Override
void stop() {
//nothing
}
@Override
boolean isOver() {
return true;
}
}

View File

@@ -1,136 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* The {@code Explosion} class represents an explosion effect in a 3D environment.
* It manages the creation, configuration, and triggering of particle emitters for fire and smoke effects.
*/
public class Explosion {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f location;
private ParticleEmitter fire;
private ParticleEmitter smoke;
private boolean triggered = false;
private final Material mat;
/**
* Constructor for the {@code Explosion} class.
*
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
this.rootNode = rootNode;
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.png"));
}
/**
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", Type.Triangle, 50);
fire.setMaterial(mat);
fire.setImagesX(2);
fire.setImagesY(2);
fire.setStartColor(ColorRGBA.Yellow);
fire.setEndColor(ColorRGBA.Red);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.7f);
fire.setEndSize(1.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(2.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", Type.Triangle, 40);
smoke.setMaterial(mat);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.7f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(5.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {
triggered = true;
initializeEmitter();
}
rootNode.attachChild(fire);
fire.emitAllParticles();
fire.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(fire);
fire.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
rootNode.attachChild(smoke);
smoke.emitAllParticles();
smoke.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(smoke);
smoke.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
}
}

View File

@@ -1,69 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import static pp.mdga.client.Util.linInt;
public class FadeControl extends ActionControl {
private float duration; // Duration of the fade effect
private float timeElapsed = 0;
private boolean init = false;
private float startAlpha;
private float endAlpha;
public FadeControl(float duration, float startAlpha, float endAlpha, Runnable actionAfter) {
super(actionAfter);
this.duration = duration;
this.startAlpha = startAlpha;
this.endAlpha = endAlpha;
}
public FadeControl(float duration, float startAlpha, float endAlpha) {
this(duration, startAlpha, endAlpha, null);
}
@Override
protected void initSpatial() {
init = true;
}
@Override
protected void controlUpdate(float tpf) {
if (!init) return;
timeElapsed += tpf;
float t = timeElapsed / duration; // Calculate progress (0 to 1)
if (t >= 1) {
// Fade complete
t = 1;
init = false;
spatial.removeControl(this);
action();
}
float alpha = linInt(startAlpha, endAlpha, t); // Interpolate alpha
// Update the material's alpha
if (spatial instanceof Geometry geometry) {
Material mat = geometry.getMaterial();
if (mat != null) {
ColorRGBA diffuse = (ColorRGBA) mat.getParam("Diffuse").getValue();
mat.setColor("Diffuse", new ColorRGBA(diffuse.r, diffuse.g, diffuse.b, alpha));
ColorRGBA ambient = (ColorRGBA) mat.getParam("Ambient").getValue();
mat.setColor("Ambient", new ColorRGBA(ambient.r, ambient.g, ambient.b, alpha));
// Disable shadows when the object is nearly invisible
if (alpha <= 0.1f) {
geometry.setShadowMode(RenderQueue.ShadowMode.Off);
} else {
geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
}
} else throw new RuntimeException("Material is null");
} else throw new RuntimeException("Spatial is not instance of Geometry");
}
}

View File

@@ -1,195 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* The {@code JetAnimation} class handles the animation of a jet model in a 3D environment.
* It creates a jet model, animates its movement along a curved path, triggers an explosion at a target point,
* and performs additional actions upon animation completion.
*/
public class JetAnimation {
private final MdgaApp app;
private final Node rootNode;
private Spatial jetModel;
private final Vector3f spawnPoint;
private final Vector3f nodePoint;
private final Vector3f despawnPoint;
private final float curveHeight;
private final float animationDuration;
private Explosion explosion;
private Runnable actionAfter;
/**
* Constructor for the {@code JetAnimation} class.
*
* @param app The main application managing the jet animation.
* @param rootNode The root node to which the jet model will be attached.
* @param targetPoint The target point where the explosion will occur.
* @param curveHeight The height of the curve for the jet's flight path.
* @param animationDuration The total duration of the jet animation.
*/
public JetAnimation(MdgaApp app, Node rootNode, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
Vector3f spawnPoint = targetPoint.add(170, 50, 50);
Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
Vector3f despawnPoint = targetPoint.add(-100, -100, 40);
this.app = app;
this.rootNode = rootNode;
this.spawnPoint = spawnPoint;
this.nodePoint = controlPoint;
this.despawnPoint = despawnPoint;
this.curveHeight = curveHeight;
this.animationDuration = animationDuration;
explosion = new Explosion(app, rootNode, targetPoint);
this.actionAfter = actionAfter;
}
/**
* Starts the jet animation by spawning the jet model and initiating its movement along the predefined path.
*/
public void start() {
app.getAcousticHandler().playSound(MdgaSound.JET);
spawnJet();
animateJet();
}
/**
* Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
*/
private void spawnJet() {
jetModel = app.getAssetManager().loadModel(Asset.jet_noGear.getModelPath());
jetModel.setLocalTranslation(spawnPoint);
jetModel.scale(Asset.jet_noGear.getSize());
jetModel.rotate(FastMath.HALF_PI, 0, 0);
jetModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet_noGear.getDiffPath()));
jetModel.setMaterial(mat);
rootNode.attachChild(jetModel);
}
/**
* actionAfter
* Animates the jet along a Bezier curve path, triggers the explosion effect at the appropriate time,
* and performs cleanup operations after the animation completes.
*/
private void animateJet() {
Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
Vector3f controlPoint2 = nodePoint.add(0, curveHeight, 0);
BezierCurve3f curve = new BezierCurve3f(spawnPoint, controlPoint1, controlPoint2, despawnPoint);
app.getRootNode().addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
float progress = elapsedTime / animationDuration;
if (elapsedTime > 4.2f) {
explosion.trigger();
}
if (progress > 1) {
rootNode.detachChild(jetModel);
this.spatial.removeControl(this);
} else {
Vector3f currentPos = curve.interpolate(progress);
Vector3f direction = curve.interpolateDerivative(progress).normalizeLocal();
jetModel.setLocalTranslation(currentPos);
jetModel.lookAt(currentPos.add(direction), Vector3f.UNIT_Z);
jetModel.rotate(-FastMath.HALF_PI, 0, (float) Math.toRadians(-25));
}
if (elapsedTime > 6.0f) {
endAnim();
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
});
}
private void endAnim() {
actionAfter.run();
}
/**
* The {@code BezierCurve3f} class represents a 3D cubic Bezier curve.
* It provides methods to interpolate positions and derivatives along the curve.
*/
private static class BezierCurve3f {
private final Vector3f p0, p1, p2, p3;
/**
* Constructor for the {@code BezierCurve3f} class.
*
* @param p0 The starting point of the curve.
* @param p1 The first control point influencing the curve's shape.
* @param p2 The second control point influencing the curve's shape.
* @param p3 The endpoint of the curve.
*/
public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p0 = p0;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
/**
* Interpolates a position along the curve at a given progress value {@code t}.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The interpolated position on the curve.
*/
public Vector3f interpolate(float t) {
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3f point = p0.mult(uuu);
point = point.add(p1.mult(3 * uu * t));
point = point.add(p2.mult(3 * u * tt));
point = point.add(p3.mult(ttt));
return point;
}
/**
* Computes the derivative at a given progress value {@code t}, representing the direction along the curve.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The derivative (direction vector) at the specified progress.
*/
public Vector3f interpolateDerivative(float t) {
float u = 1 - t;
float tt = t * t;
Vector3f derivative = p0.mult(-3 * u * u);
derivative = derivative.add(p1.mult(3 * u * u - 6 * u * t));
derivative = derivative.add(p2.mult(6 * u * t - 3 * tt));
derivative = derivative.add(p3.mult(3 * tt));
return derivative;
}
}
}

View File

@@ -1,253 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import pp.mdga.client.MdgaApp;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* MatrixAnimation class handles the animation of radar and matrix particle effects.
*/
public class MatrixAnimation extends ActionControl {
private MdgaApp app;
private static final Random RANDOM = new Random();
private Vector3f radarPos;
private Runnable runnable;
private boolean init = false;
private List<ParticleEmitter> activeEmitter = new ArrayList<>();
private ParticleEmitter radarEmitter = null;
private float timeElapsed = 0f;
/**
* Enum representing the states of the matrix animation.
*/
private enum MatrixState {
RADAR_ON,
RADAR_OFF,
MATRIX_ON,
MATRIX_OFF
}
private MatrixState state;
/**
* Constructor for MatrixAnimation.
*
* @param app the application instance
* @param radarPos the position of the radar
* @param runnable the runnable action to be executed
*/
public MatrixAnimation(MdgaApp app, Vector3f radarPos, Runnable runnable) {
super(runnable);
this.app = app;
this.radarPos = radarPos;
}
/**
* Initializes the spatial and sets the initial state to RADAR_ON.
*/
@Override
protected void initSpatial() {
state = MatrixState.RADAR_ON;
timeElapsed = 0;
init = true;
radar();
}
/**
* Updates the control based on the time per frame (tpf).
*
* @param tpf the time per frame
*/
@Override
protected void controlUpdate(float tpf) {
if (!init) return;
timeElapsed += tpf;
switch (state) {
case RADAR_ON -> {
if (timeElapsed >= 2f) {
state = MatrixState.RADAR_OFF;
timeElapsed = 0;
radarEmitter.setParticlesPerSec(0);
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
app.getRootNode().detachChild(radarEmitter);
System.out.println("delete radar");
return null;
}));
}
}
case RADAR_OFF -> {
if (timeElapsed >= 0.1f) {
state = MatrixState.MATRIX_ON;
timeElapsed = 0;
matrix();
}
}
case MATRIX_ON -> {
if (timeElapsed >= 3f) {
state = MatrixState.MATRIX_OFF;
timeElapsed = 0;
turnOff();
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
for (ParticleEmitter particleEmitter : activeEmitter) {
app.getRootNode().detachChild(particleEmitter);
}
System.out.println("delete particle");
return null;
}));
}
}
case MATRIX_OFF -> {
if (timeElapsed >= 0.5f) {
init = false;
spatial.removeControl(this);
action();
}
}
}
}
/**
* Turns off all active particle emitters.
*/
private void turnOff() {
for (ParticleEmitter particleEmitter : activeEmitter) {
particleEmitter.setParticlesPerSec(0f);
}
}
/**
* Initializes the radar particle emitter.
*/
private void radar() {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/radar_beam.png"));
ParticleEmitter emitter = new ParticleEmitter("Effect", Type.Triangle, 50);
emitter.setMaterial(mat);
emitter.setImagesX(1); // columns
emitter.setImagesY(1); // rows
emitter.setSelectRandomImage(true);
emitter.setStartColor(ColorRGBA.White);
emitter.setEndColor(ColorRGBA.Black);
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, 2));
emitter.getParticleInfluencer().setVelocityVariation(0f);
emitter.setStartSize(0.1f);
emitter.setEndSize(10);
emitter.setGravity(0, 0, 0);
float life = 2.6f;
emitter.setLowLife(life);
emitter.setHighLife(life);
emitter.setLocalTranslation(radarPos.add(new Vector3f(0, 0, 5)));
emitter.setParticlesPerSec(1.8f);
app.getRootNode().attachChild(emitter);
radarEmitter = emitter;
}
/**
* Initializes multiple matrix particle streams.
*/
private void matrix() {
for (int i = 0; i < 5; i++) {
particleStream(
generateMatrixColor(),
generateMatrixColor(),
getRandomFloat(0, 1f),
getRandomPosition(),
getRandomFloat(1, 2)
);
}
}
/**
* Creates a particle stream with the specified parameters.
*
* @param start the start color of the particles
* @param end the end color of the particles
* @param speedVar the speed variation of the particles
* @param pos the position of the particles
* @param spawnVar the spawn rate variation of the particles
*/
private void particleStream(ColorRGBA start, ColorRGBA end, float speedVar, Vector3f pos, float spawnVar) {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/particle_cir.png"));
ParticleEmitter matrix = new ParticleEmitter("Effect", Type.Triangle, 50);
matrix.setMaterial(mat);
matrix.setImagesX(2); // columns
matrix.setImagesY(1); // rows
matrix.setSelectRandomImage(true);
matrix.setStartColor(start);
matrix.setEndColor(end);
matrix.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, -6f - speedVar));
matrix.getParticleInfluencer().setVelocityVariation(0f);
matrix.setStartSize(0.4f);
matrix.setEndSize(0.6f);
matrix.setGravity(0, 0, 2f);
matrix.setLowLife(3f);
matrix.setHighLife(3f);
matrix.setLocalTranslation(spatial.getLocalTranslation().add(pos).add(new Vector3f(0, 0, 15)));
matrix.setParticlesPerSec(spawnVar);
app.getRootNode().attachChild(matrix);
activeEmitter.add(matrix);
}
/**
* Generates a random position vector.
*
* @return a random position vector
*/
public static Vector3f getRandomPosition() {
// Generate a random angle in radians (0 to )
float angle = (float) (2 * Math.PI * RANDOM.nextDouble());
// Generate a random radius with uniform distribution
float radius = (float) Math.sqrt(RANDOM.nextDouble());
radius *= 1f;
// Convert polar coordinates to Cartesian
float x = radius * (float) Math.cos(angle);
float y = radius * (float) Math.sin(angle);
return new Vector3f(x, y, 0);
}
/**
* Generates a random float between the specified start and end values.
*
* @param start the start value
* @param end the end value
* @return a random float between start and end
*/
public static float getRandomFloat(float start, float end) {
if (start > end) {
throw new IllegalArgumentException("Start must be less than or equal to end.");
}
return start + RANDOM.nextFloat() * (end - start);
}
/**
* Generates a random color for the matrix particles.
*
* @return a random ColorRGBA object
*/
public static ColorRGBA generateMatrixColor() {
// Red is dominant
float red = 0.8f + RANDOM.nextFloat() * 0.2f; // Red channel: 0.8 to 1.0
// Green is moderately high
float green = 0.4f + RANDOM.nextFloat() * 0.3f; // Green channel: 0.4 to 0.7
// Blue is minimal
float blue = RANDOM.nextFloat() * 0.2f; // Blue channel: 0.0 to 0.2
float alpha = 1.0f; // Fully opaque
return new ColorRGBA(red, green, blue, alpha);
}
}

View File

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

View File

@@ -1,183 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
/**
* The {@code MissileAnimation} class handles the animation of a missile moving along a parabolic path
* towards a target point in a 3D environment. It also triggers an explosion at the target upon impact.
*/
public class MissileAnimation {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f start;
private final Vector3f target;
private final float flightTime;
private Explosion explosion;
private Spatial missileModel;
private Runnable actionAfter;
private ParticleEmitter smoke;
private Node missileNode = new Node();
private final Material mat;
/**
* Constructor for the {@code MissileAnimation} class.
*
* @param app The main application managing the missile animation.
* @param rootNode The root node to which the missile model will be attached.
* @param target The target point where the missile will explode.
* @param flightTime The total flight time of the missile.
*/
public MissileAnimation(MdgaApp app, Node rootNode, Vector3f target, float flightTime, Runnable actionAfter) {
this.app = app;
this.rootNode = rootNode;
this.flightTime = flightTime;
this.actionAfter = actionAfter;
explosion = new Explosion(app, rootNode, target);
this.target = target.add(new Vector3f(1.5f, -1, 0));
start = BoardHandler.gridToWorld(12, 0);
start.add(new Vector3f(0, 0, 0));
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 400);
smoke.setMaterial(mat);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
smoke.getParticleInfluencer().setVelocityVariation(0.1f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(3.5f);
smoke.setParticlesPerSec(100);
missileNode.attachChild(smoke);
smoke.move(1, 0.85f, 1.0f);
}
/**
* Starts the missile animation by loading the missile model and initiating its parabolic movement.
*/
public void start() {
Smoke s = new Smoke(app, rootNode, start);
s.trigger();
loadMissile();
app.getAcousticHandler().playSound(MdgaSound.MISSILE);
animateMissile();
}
/**
* Loads the missile model into the scene, applies scaling, material, and sets its initial position.
*/
private void loadMissile() {
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath());
missileModel.scale(Asset.missile.getSize());
missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
missileModel.setMaterial(mat);
missileNode.setLocalTranslation(start);
missileNode.attachChild(missileModel);
rootNode.attachChild(missileNode);
}
/**
* Animates the missile along a parabolic path, triggers the explosion near the target,
* and removes the missile model after the animation completes.
*/
private void animateMissile() {
missileNode.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
if (elapsedTime > 6) {
endAnim();
rootNode.detachChild(missileNode);
this.spatial.removeControl(this);
}
elapsedTime += tpf;
float progress = elapsedTime / flightTime;
if (progress >= 0.55) {
smoke.setParticlesPerSec(30);
}
if (progress >= 0.7) {
smoke.setParticlesPerSec(0);
}
if (progress >= 0.95f) {
explosion.trigger();
}
if (progress >= 1) {
explosion.trigger();
missileNode.detachChild(missileModel);
}
Vector3f currentPosition = computeParabolicPath(start, target, progress);
missileNode.setLocalTranslation(currentPosition);
Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
.subtract(currentPosition)
.normalizeLocal();
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y);
missileModel.rotate(0, FastMath.HALF_PI, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
});
}
private void endAnim() {
actionAfter.run();
}
/**
* Computes a position along a parabolic path at a given progress value {@code t}.
*
* @param start The starting point of the missile's flight.
* @param target The target point of the missile's flight.
* @param t The progress value (0.0 to 1.0) along the flight path.
* @return The interpolated position along the parabolic path.
*/
private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
Vector3f midPoint = start.add(target).multLocal(0.5f);
midPoint.addLocal(0, 0, 20);
Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
return FastMath.interpolateLinear(t, startToMid, midToTarget);
}
}

View File

@@ -1,98 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.math.Vector3f;
import static pp.mdga.client.Util.easeInOut;
import static pp.mdga.client.Util.quadInt;
/**
* A control that smoothly moves a spatial from an initial position to an end position
* using a quadratic interpolation, with the option to perform an action after the movement is complete.
* The movement path includes an intermediate "middle" position at a specified height.
*
* <p>Movement speed can be adjusted by modifying the MOVE_SPEED constant. The movement easing follows
* an ease-in-out curve to create a smooth start and stop effect.
* </p>
*/
public class MoveControl extends ActionControl {
private boolean moving;
private final Vector3f initPos;
private final Vector3f endPos;
private final Vector3f middlePos;
private final float height;
private final float duration;
private float timer = 0;
private boolean easing;
/**
* Creates a new MoveControl with specified initial and end positions, and an action to run after the movement.
* The movement follows a path with a midpoint at a fixed height.
*
* @param initPos The starting position of the spatial.
* @param endPos The target position of the spatial.
* @param actionAfter A Runnable that will be executed after the movement finishes.
*/
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter) {
this(initPos, endPos, actionAfter, 2, 1, true);
}
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter, float height, float duration, boolean easing) {
super(actionAfter);
moving = false;
this.initPos = initPos;
this.endPos = endPos;
this.height = height;
this.duration = duration;
this.easing = easing;
middlePos = new Vector3f(
(initPos.x + endPos.x) / 2,
(initPos.y + endPos.y) / 2,
height
);
}
/**
* Initializes the movement by resetting the progress and setting the moving flag to true.
* This is called automatically when the spatial is set.
*/
@Override
protected void initSpatial() {
moving = true;
timer = 0;
}
/**
* Updates the movement of the spatial by interpolating its position along the defined path.
* The movement is smoothed using an easing function.
* Once the movement reaches the target, the {@link #end()} method is called to finish the movement.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if (!moving) return;
timer += tpf;
float t = timer / duration;
if (t >= 1) t = 1;
float interpolated = easing ? easeInOut(t) : t;
spatial.setLocalTranslation(quadInt(initPos, middlePos, endPos, interpolated));
if (t >= 1) end();
}
/**
* Ends the movement by stopping the interpolation, running the action after the movement,
* and removing this control from the spatial.
*/
private void end() {
moving = false;
spatial.removeControl(this);
action();
}
}

View File

@@ -1,199 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.TankTopControl;
import java.util.Timer;
import java.util.TimerTask;
import static com.jme3.material.Materials.LIGHTING;
/**
* ShellAnimation class handles the animation of a shell being fired from a tank.
*/
public class ShellAnimation extends ActionControl {
private static final float FLYING_DURATION = 1.25f;
private static final float FLYING_HEIGHT = 12f;
private TankTopControl tankTopControl;
private MdgaApp app;
/**
* Constructor for ShellAnimation.
*
* @param tankTopControl the control for the tank top
* @param app the application instance
* @param actionAfter the action to perform after the animation
*/
public ShellAnimation(TankTopControl tankTopControl, MdgaApp app, Runnable actionAfter) {
super(actionAfter);
this.tankTopControl = tankTopControl;
this.app = app;
}
/**
* Initializes the spatial for the animation.
*/
@Override
protected void initSpatial() {
tankTopControl.rotate(spatial.getLocalTranslation(), this::shoot);
app.getAcousticHandler().playSound(MdgaSound.TURRET_ROTATE);
//app.getRootNode().attachChild(createShell());
}
/**
* Calculates the shooting position based on the tank's turret rotation.
*
* @return the shooting position as a Vector3f
*/
private Vector3f getShootPos() {
Vector3f localOffset = new Vector3f(0, -5.4f, 2.9f);
Quaternion turretRotation = tankTopControl.getSpatial().getLocalRotation();
Vector3f transformedOffset = turretRotation.mult(localOffset);
return tankTopControl.getSpatial().getLocalTranslation().add(transformedOffset);
}
/**
* Handles the shooting action, including sound and visual effects.
*/
private void shoot() {
app.getAcousticHandler().playSound(MdgaSound.TANK_SHOOT);
Vector3f shootPos = getShootPos();
createEffect(
shootPos,
"Images/particle/flame.png",
2, 2,
1, 3,
1f,
0.3f, 0.7f,
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
new ColorRGBA(1f, 0f, 0f, 0f)
);
createEffect(
shootPos,
"Images/particle/vapor_cloud.png",
3, 3,
0.3f, 0.8f,
10,
0.1f, 0.35f,
new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
ColorRGBA.Black
);
Spatial shell = createShell();
app.getRootNode().attachChild(shell);
shell.addControl(new ShellControl(this::hitExplosion, shootPos, spatial.getLocalTranslation(), FLYING_HEIGHT, FLYING_DURATION, app.getAssetManager()));
}
/**
* Creates the shell model and sets its initial properties.
*
* @return the created shell as a Spatial
*/
private Spatial createShell() {
Spatial model = app.getAssetManager().loadModel(Asset.shell.getModelPath());
model.scale(.16f);
model.setLocalTranslation(tankTopControl.getSpatial().getLocalTranslation());
Vector3f shootPos = tankTopControl.getSpatial().getLocalTranslation();
Vector3f targetPos = spatial.getLocalTranslation();
Vector3f direction = targetPos.subtract(shootPos).normalize();
Quaternion rotation = new Quaternion();
rotation.lookAt(direction, new Vector3f(1, 0, 0)); // Assuming UNIT_Y is the up vector
model.setLocalRotation(rotation);
model.rotate(FastMath.HALF_PI, 0, 0);
Material mat = new Material(app.getAssetManager(), LIGHTING);
mat.setBoolean("UseMaterialColors", true);
ColorRGBA color = ColorRGBA.fromRGBA255(143, 117, 0, 255);
mat.setColor("Diffuse", color);
mat.setColor("Ambient", color);
model.setMaterial(mat);
return model;
}
/**
* Handles the explosion effect when the shell hits a target.
*/
private void hitExplosion() {
app.getAcousticHandler().playSound(MdgaSound.TANK_EXPLOSION);
createEffect(
spatial.getLocalTranslation().setZ(1),
"Images/particle/flame.png",
2, 2,
1, 5,
2f,
0.3f, 0.7f,
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
new ColorRGBA(1f, 0f, 0f, 0f)
);
app.getTimerManager().addTask(0.8f, super::action);
}
/**
* Creates a particle effect at the specified position.
*
* @param shootPos the position to create the effect
* @param image the image to use for the particles
* @param x the number of columns in the texture
* @param y the number of rows in the texture
* @param startSize the initial size of the particles
* @param endSize the final size of the particles
* @param velocity the initial velocity of the particles
* @param lowLife the minimum lifetime of the particles
* @param highLife the maximum lifetime of the particles
* @param start the starting color of the particles
* @param end the ending color of the particles
*/
private void createEffect(Vector3f shootPos,
String image,
int x, int y,
float startSize, float endSize,
float velocity,
float lowLife, float highLife,
ColorRGBA start, ColorRGBA end) {
// Create a particle emitter for the explosion
ParticleEmitter explosionEmitter = new ParticleEmitter("Explosion", ParticleMesh.Type.Triangle, 100);
Material explosionMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
explosionMat.setTexture("Texture", app.getAssetManager().loadTexture(image));
explosionEmitter.setMaterial(explosionMat);
// Particle properties
explosionEmitter.setImagesX(x); // Columns in the texture
explosionEmitter.setImagesY(y); // Rows in the texture
explosionEmitter.setSelectRandomImage(true); // Randomize images for variety
explosionEmitter.setStartColor(start); // Bright yellowish orange
explosionEmitter.setEndColor(end); // Fade to transparent red
explosionEmitter.setStartSize(startSize); // Initial size
explosionEmitter.setEndSize(endSize); // Final size
explosionEmitter.setLowLife(lowLife); // Minimum lifetime of particles
explosionEmitter.setHighLife(highLife); // Maximum lifetime of particles
explosionEmitter.setGravity(0, 0, 1); // Gravity to pull particles down
explosionEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, velocity));
explosionEmitter.getParticleInfluencer().setVelocityVariation(1f); // Adds randomness to the initial velocity
explosionEmitter.setFacingVelocity(true); // Particles face their velocity direction
explosionEmitter.setLocalTranslation(shootPos);
explosionEmitter.setParticlesPerSec(0);
explosionEmitter.emitAllParticles();
app.getRootNode().attachChild(explosionEmitter);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
app.getRootNode().detachChild(explosionEmitter);
}
}, 1000);
}
}

View File

@@ -1,112 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
/**
* ShellControl is responsible for controlling the movement and visual effects of a shell.
*/
public class ShellControl extends ActionControl {
private final Vector3f shootPos;
private final Vector3f endPos;
private final float height;
private final float duration;
private Vector3f oldPos;
private ParticleEmitter emitter;
private AssetManager assetManager;
/**
* Constructs a new ShellControl.
*
* @param runnable the action to perform when the shell reaches its destination
* @param shootPos the starting position of the shell
* @param endPos the ending position of the shell
* @param height the height of the shell's trajectory
* @param duration the duration of the shell's flight
* @param assetManager the asset manager to load resources
*/
public ShellControl(Runnable runnable, Vector3f shootPos, Vector3f endPos, float height, float duration, AssetManager assetManager) {
super(runnable);
this.shootPos = shootPos;
this.endPos = endPos;
this.height = height;
this.duration = duration;
this.assetManager = assetManager;
}
/**
* Initializes the spatial with the necessary controls and particle emitter.
*/
@Override
protected void initSpatial() {
spatial.addControl(new MoveControl(
shootPos,
endPos,
() -> {
emitter.killAllParticles();
emitter.setParticlesPerSec(0);
emitter.removeFromParent();
spatial.removeControl(this);
spatial.removeFromParent();
action();
},
height,
duration,
false
));
oldPos = spatial.getLocalTranslation().clone();
createEmitter();
}
/**
* Creates and configures the particle emitter for the shell trail.
*/
private void createEmitter() {
emitter = new ParticleEmitter("ShellTrail", ParticleMesh.Type.Triangle, 200);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", assetManager.loadTexture("Images/particle/line.png")); // Nutze eine schmale, linienartige Textur
emitter.setMaterial(mat);
// Comic-Style Farben
emitter.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); // Reinweiß
emitter.setEndColor(new ColorRGBA(1f, 1f, 1f, 0f)); // Transparent
// Partikelgröße und Lebensdauer
emitter.setStartSize(0.15f); // Startgröße
emitter.setEndSize(0.1f); // Endgröße
emitter.setLowLife(0.14f); // Sehr kurze Lebensdauer
emitter.setHighLife(0.14f);
emitter.setGravity(0, 0, 0); // Keine Gravitation
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
emitter.getParticleInfluencer().setVelocityVariation(0f); // Kein Variationsspielraum
// Hohe Dichte für eine glatte Spur
emitter.setParticlesPerSec(500);
// Zur Shell hinzufügen
spatial.getParent().attachChild(emitter);
}
/**
* Updates the control, adjusting the shell's rotation and emitter position.
*
* @param tpf time per frame
*/
@Override
protected void controlUpdate(float tpf) {
Vector3f direction = spatial.getLocalTranslation().subtract(oldPos).normalize();
if (direction.lengthSquared() > 0) {
spatial.getLocalRotation().lookAt(direction, Vector3f.UNIT_X);
spatial.rotate(FastMath.HALF_PI, 0, 0);
}
oldPos = spatial.getLocalTranslation().clone();
emitter.setLocalTranslation(spatial.getLocalTranslation().clone());
}
}

View File

@@ -1,129 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
public class Smoke {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f location;
private ParticleEmitter fire;
private ParticleEmitter smoke;
private boolean triggered = false;
private final Material mat;
/**
* Constructor for the {@code Explosion} class.
*
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Smoke(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
this.rootNode = rootNode;
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
}
/**
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle, 50);
fire.setMaterial(mat);
fire.setStartColor(ColorRGBA.DarkGray);
fire.setEndColor(ColorRGBA.DarkGray);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.7f);
fire.setEndSize(3.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(1.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 2f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.5f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(2.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {
triggered = true;
initializeEmitter();
}
rootNode.attachChild(fire);
fire.emitAllParticles();
fire.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(fire);
fire.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
rootNode.attachChild(smoke);
smoke.emitAllParticles();
smoke.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(smoke);
smoke.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
}
});
}
}

View File

@@ -1,213 +0,0 @@
package pp.mdga.client.animation;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
import pp.mdga.game.BonusCard;
/**
* A control that manages the animation of symbols representing different bonus card states.
* The symbol can animate with zoom, rotation, and translation effects based on the state of the bonus card.
*
* <p>The control supports three main states: SHIELD, SWAP, and TURBO. Each state has its own specific animation logic:
* <ul>
* <li>SHIELD: Zooms in and out, with a scaling effect.</li>
* <li>SWAP: Rotates the symbol 360 degrees.</li>
* <li>TURBO: Moves the symbol along the Y-axis with a zoom effect.</li>
* </ul>
* </p>
*/
public class SymbolControl extends InitControl {
private boolean zoomingIn = false;
private boolean zoomingOut = false;
private float zoomSpeed = 1f;
private float zoomFactor = 3f;
private float progress = 0;
private BonusCard state;
private float rotationSpeed = 0.8f;
private Quaternion initialRotation = null;
private float y = 5;
/**
* Updates the symbol animation based on the current bonus card state.
* The method calls the corresponding update method for each state (SHIELD, SWAP, TURBO).
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if (state == null) return;
switch (state) {
case SHIELD -> shieldUpdate(tpf);
case SWAP -> swapUpdate(tpf);
case TURBO -> turboUpdate(tpf);
case HIDDEN -> throw new RuntimeException("forbidden state");
}
}
/**
* Updates the symbol when the state is SHIELD. The symbol zooms in and then zooms out.
* When the zooming out finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void shieldUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
if (progress > 1) {
zoomingIn = false;
spatial.removeFromParent();
state = null;
progress = 0;
}
}
}
/**
* Updates the symbol when the state is SWAP. The symbol rotates 360 degrees.
* After the rotation finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void swapUpdate(float tpf) {
if (initialRotation == null) {
initialRotation = spatial.getLocalRotation().clone();
}
progress += tpf * rotationSpeed;
if (progress < 0) return;
float angle = lerp(0, 360, easeInOut(progress));
Quaternion newRotation = new Quaternion();
newRotation.fromAngleAxis((float) Math.toRadians(angle), new Vector3f(0, 1, 0));
spatial.setLocalRotation(initialRotation.mult(newRotation));
if (progress >= 1.2f) {
state = null;
initialRotation = null;
progress = 0;
spatial.removeFromParent();
}
}
/**
* Updates the symbol when the state is TURBO. The symbol moves along the Y-axis with a zoom effect.
* After the movement finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void turboUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
float y = lerp(-this.y, 0, easeOut(progress));
spatial.setLocalTranslation(0, y, 0);
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
float y = lerp(0, this.y, easeIn(progress));
spatial.setLocalTranslation(0, y, 0);
if (progress > 1) {
zoomingIn = false;
spatial.removeFromParent();
state = null;
}
}
}
/**
* Starts the SHIELD animation by zooming the symbol in and out.
* The symbol will first zoom in and then zoom out, and will be removed from the parent spatial once done.
*/
public void shield() {
if (state != null) throw new RuntimeException("another state is avtive");
state = BonusCard.SHIELD;
zoomingIn = true;
zoomingOut = false;
progress = 0;
spatial.setLocalScale(1f);
}
/**
* Starts the SWAP animation by rotating the symbol 360 degrees.
* The symbol will rotate once and then be removed from the parent spatial.
*/
public void swap() {
if (state != null) throw new RuntimeException("another state is avtive");
spatial.setLocalScale(3);
state = BonusCard.SWAP;
progress = -0.2f;
}
/**
* Starts the TURBO animation by moving the symbol along the Y-axis.
* The symbol will move upwards and then return to its initial position.
*/
public void turbo() {
if (state != null) throw new RuntimeException("another state is avtive");
spatial.setLocalScale(2);
state = BonusCard.TURBO;
zoomingIn = true;
zoomingOut = false;
progress = 0;
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
/**
* Ease-out function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private static float easeOut(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
/**
* Ease-in-out function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private float easeInOut(float t) {
if (t > 1) t = 1;
return (float) -(Math.cos(Math.PI * t) - 1) / 2;
}
/**
* Ease-in function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private float easeIn(float t) {
return t * t * t * t;
}
}

View File

@@ -1,79 +0,0 @@
package pp.mdga.client.animation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TimerManager {
private final List<TimedTask> tasks = new ArrayList<>();
/**
* Add a timed task that will execute after the specified delay.
*
* @param delaySeconds The delay in seconds.
* @param task The Runnable task to execute after the delay.
*/
public void addTask(float delaySeconds, Runnable task) {
tasks.add(new TimedTask(delaySeconds, task));
}
/**
* Update the timer manager to process and execute tasks when their delay has elapsed.
* This should be called in the `controlUpdate` method or a similar update loop.
*
* @param tpf Time per frame (delta time) provided by the update loop.
*/
public void update(float tpf) {
Iterator<TimedTask> iterator = tasks.iterator();
while (iterator.hasNext()) {
TimedTask task = iterator.next();
task.update(tpf);
if (task.isReady()) {
task.run();
iterator.remove();
}
}
}
/**
* Clears all pending tasks from the manager.
*/
public void clearTasks() {
tasks.clear();
}
/**
* Checks if the manager has any pending tasks.
*
* @return True if there are pending tasks, otherwise false.
*/
public boolean hasPendingTasks() {
return !tasks.isEmpty();
}
/**
* Internal class representing a single timed task.
*/
private static class TimedTask {
private float remainingTime;
private final Runnable task;
public TimedTask(float delaySeconds, Runnable task) {
this.remainingTime = delaySeconds;
this.task = task;
}
public void update(float tpf) {
remainingTime -= tpf;
}
public boolean isReady() {
return remainingTime <= 0;
}
public void run() {
task.run();
}
}
}

View File

@@ -1,110 +0,0 @@
package pp.mdga.client.animation;
import pp.mdga.client.InitControl;
/**
* A control that applies a zoom effect to a spatial, smoothly scaling it in and out.
* The zoom effect can be customized with speed and scaling factor.
*
* <p>The control supports zooming in and out with ease-in and ease-out transitions.
* It starts by zooming in, and once complete, it zooms out, eventually removing the spatial from its parent when the animation ends.</p>
*/
public class ZoomControl extends InitControl {
private boolean zoomingIn = false;
private boolean zoomingOut = false;
private float progress = 0;
private float zoomSpeed = 1f;
private float zoomFactor = 1f;
/**
* Constructs a new ZoomControl with the default zoom speed.
*/
public ZoomControl() {
}
/**
* Constructs a new ZoomControl with a specified zoom speed.
*
* @param speed The speed at which the zoom effect occurs.
*/
public ZoomControl(float speed) {
zoomSpeed = speed;
}
/**
* Initializes the spatial for the zoom effect. This method is called when the control is added to the spatial.
* It sets the zooming state to zooming in.
*/
@Override
protected void initSpatial() {
zoomingIn = true;
}
/**
* Updates the zoom effect over time, either zooming in or zooming out.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
if (progress > 1) {
zoomingOut = false;
end();
}
}
}
/**
* Ends the zoom animation by removing the spatial from its parent and the control from the spatial.
*/
private void end() {
spatial.removeFromParent();
spatial.removeControl(this);
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
/**
* Ease-out function for smoothing the zoom-in transition.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The eased value.
*/
private float easeOut(float x) {
return x == 1 ? 1 : (float) (1 - Math.pow(2, -10 * x));
}
/**
* Ease-in function for smoothing the zoom-out transition.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The eased value.
*/
private float easeIn(float x) {
return x == 0 ? 0 : (float) Math.pow(2, 10 * x - 10);
}
}

View File

@@ -2,8 +2,4 @@
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

@@ -4,23 +4,11 @@
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.util.SkyFactory;
import com.jme3.util.SkyFactory.EnvMapType;
import pp.mdga.client.MdgaApp;
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 {
MdgaApp app;
@@ -29,33 +17,8 @@ public class CameraHandler {
private static final int SHADOWMAP_SIZE = 1024 * 8;
private Vector3f defaultCameraPosition;
private Quaternion defaultCameraRotation;
FilterPostProcessor fpp;
DirectionalLightShadowFilter dlsf;
Spatial sky;
private Color ownColor;
private boolean init;
private boolean initRot;
private SSAOFilter ssaoFilter;
private FXAAFilter fxaaFilter;
/**
* 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) {
init = false;
initRot = false;
public CameraHandler(MdgaApp app, FilterPostProcessor fpp){
this.app = app;
this.fpp = fpp;
// Save the default camera state
this.defaultCameraPosition = app.getCamera().getLocation().clone();
this.defaultCameraRotation = app.getCamera().getRotation().clone();
sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
@@ -64,67 +27,22 @@ public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 1);
DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 4);
dlsf.setLight(sun);
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
dlsf.setShadowIntensity(0.7f);
ssaoFilter = new SSAOFilter(6, 10f, 0.33f, 0.61f);
// ssaoFilter = new SSAOFilter();
fxaaFilter = new FXAAFilter();
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI * 1, 0, FastMath.HALF_PI * 0.2f);
fpp.addFilter(dlsf);
}
/**
* 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() {
app.getRootNode().addLight(sun);
app.getRootNode().addLight(ambient);
app.getRootNode().attachChild(sky);
fpp.addFilter(dlsf);
fpp.addFilter(ssaoFilter);
fpp.addFilter(fxaaFilter);
init = true;
initRot = true;
this.ownColor = ownColor;
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() {
init = false;
fpp.removeFilter(fxaaFilter);
fpp.removeFilter(ssaoFilter);
fpp.removeFilter(dlsf);
app.getRootNode().detachChild(sky);
app.getRootNode().removeLight(ambient);
app.getRootNode().removeLight(sun);
// Reset the camera to its default state
app.getCamera().setLocation(defaultCameraPosition);
app.getCamera().setRotation(defaultCameraRotation);
app.getRootNode().removeLight(ambient);
}
/**
* 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) {
if (!init) return;
float scrollValue = Math.max(0, Math.min(scroll, 100));
float rotationValue = rotation % 360;
@@ -132,7 +50,6 @@ public void update(float scroll, float rotation) {
rotationValue += 360;
}
float radius;
float verticalAngle;
@@ -141,9 +58,10 @@ public void update(float scroll, float rotation) {
radius = 30f;
} else {
verticalAngle = 90f;
rotationValue = getAngleByColor(ownColor);
rotationValue = 270f;
radius = 50f;
}
float verticalAngleRadians = FastMath.DEG_TO_RAD * verticalAngle;
float z = radius * FastMath.sin(verticalAngleRadians);
@@ -156,30 +74,4 @@ public void update(float scroll, float rotation) {
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) {
return switch (color) {
case ARMY -> 0;
case AIRFORCE -> 90;
case NAVY -> 270;
case CYBER -> 180;
default -> throw new RuntimeException("None is not allowed");
};
}
/**
* 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) {
return (getAngleByColor(color) + 180) % 360;
}
}

View File

@@ -10,33 +10,17 @@
import java.util.ArrayList;
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 {
/**
* Private constructor to prevent instantiation.
*/
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) {
List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
) {
while (true) {
@@ -65,22 +49,17 @@ public static List<AssetOnMap> loadMap(String mapName) {
Asset asset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
}
} catch (IOException e) {
}
catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
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) {
return switch (assetName) {
case "lw" -> Asset.lw;
@@ -105,11 +84,8 @@ private static Asset getLoadedAsset(String assetName) {
case "radar" -> Asset.radar;
case "ship" -> Asset.ship;
case "tank" -> Asset.tank;
case "treeSmall" -> Asset.treeSmall;
case "treeBig" -> Asset.treeBig;
case "tank_shoot" -> Asset.tankShoot;
case "treesBigBackground" -> Asset.treesBigBackground;
case "treesSmallBackground" -> Asset.treesSmallBackground;
case "tree_small" -> Asset.tree_small;
case "tree_big" -> Asset.tree_big;
default -> throw new IllegalStateException("Unexpected value: " + assetName);
};
}

View File

@@ -1,34 +1,22 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import pp.mdga.client.MdgaApp;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
/**
* 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 OutlineOEControl {
public class NodeControl extends AbstractControl {
/**
* 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) {
super(app, fpp, app.getCamera());
@Override
protected void controlUpdate(float v) {
}
/**
* 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() {
@Override
protected void controlRender(RenderManager renderManager, ViewPort viewPort) {
}
public Vector3f getLocation(){
return this.getSpatial().getLocalTranslation();
}
}

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.board.Outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
public class OutlineFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
public OutlineFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/Outline.j3md");
material = new Material(matDef);
material.setVector2("Resolution", new Vector2f(w, h));
material.setColor("OutlineColor", outlineColor);
material.setFloat("OutlineWidth", outlineWidth);
}
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
// System.out.println("OutlineFilter.preFrame()");
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
// material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
// System.out.println("OutlineFilter.postFrame()");
}
@Override
protected Material getMaterial() {
return material;
}
public ColorRGBA getOutlineColor() {
return outlineColor;
}
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
public float getOutlineWidth() {
return outlineWidth;
}
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -0,0 +1,67 @@
package pp.mdga.client.board.Outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
public class OutlinePreFilter extends Filter {
private Pass normalPass;
private RenderManager renderManager;
/**
* Creates a OutlinePreFilter
*/
public OutlinePreFilter() {
super("OutlinePreFilter");
}
@Override
protected boolean isRequiresDepthTexture() {
return true;
}
@Override
protected void postQueue(RenderQueue queue) {
Renderer r = renderManager.getRenderer();
r.setFrameBuffer(normalPass.getRenderFrameBuffer());
renderManager.getRenderer().clearBuffers(true, true, false);
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
}
@Override
protected Material getMaterial() {
return material;
}
public Texture getOutlineTexture() {
return normalPass.getRenderedTexture();
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
this.renderManager = renderManager;
normalPass = new Pass();
normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
material = new Material(manager, "MatDefs/SelectObjectOutliner/OutlinePre.j3md");
}
@Override
protected void cleanUpFilter(Renderer r) {
normalPass.cleanup(r);
}
}

View File

@@ -0,0 +1,71 @@
package pp.mdga.client.board.Outline;
import com.jme3.asset.AssetManager;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
public class SelectObjectOutliner {
private final FilterPostProcessor fpp;
private final RenderManager renderManager;
private final AssetManager assetManager;
private final Camera cam;
private final int width;
private boolean selected;
private ViewPort outlineViewport = null;
private OutlineFilter outlineFilter = null;
public SelectObjectOutliner(int width, FilterPostProcessor fpp, RenderManager renderManager, AssetManager assetManager, Camera cam) {
this.selected = false;
this.fpp = fpp;
this.renderManager = renderManager;
this.assetManager = assetManager;
this.cam = cam;
this.width = width;
}
public void deselect(Spatial model) {
if(selected){
selected = false;
hideOutlineFilterEffect(model);
}
}
public void select(Spatial model, ColorRGBA color) {
if(!selected){
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
private void hideOutlineFilterEffect(Spatial model) {
outlineFilter.setEnabled(false);
outlineFilter.getOutlinePreFilter().setEnabled(false);
fpp.removeFilter(outlineFilter);
outlineViewport.detachScene(model);
renderManager.removePreView(outlineViewport);
outlineViewport = null;
}
private void showOutlineFilterEffect(Spatial model, int width, ColorRGBA color) {
outlineViewport = renderManager.createPreView("outlineViewport", cam);
FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
outlineFpp.addFilter(outlinePreFilter);
outlineViewport.attachScene(model);
outlineViewport.addProcessor(outlineFpp);
outlineFilter = new OutlineFilter(outlinePreFilter);
outlineFilter.setOutlineColor(color);
outlineFilter.setOutlineWidth(width);
fpp.addFilter(outlineFilter);
}
}

View File

@@ -1,182 +0,0 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import pp.mdga.client.InitControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.outline.SelectObjectOutliner;
/**
* 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 selectObjectOutliner;
private final MdgaApp app;
private boolean hoverable = false;
private boolean highlight = false;
private boolean selectable = false;
private boolean select = false;
private ColorRGBA highlightColor;
private int highlightWidth;
private ColorRGBA hoverColor;
private int hoverWidth;
private ColorRGBA selectColor;
private int selectWidth;
/**
* Constructs an {@code OutlineControl} with default thickness for the object outline.
*
* @param app The main application managing the outline control.
* @param fpp The {@code FilterPostProcessor} used for post-processing effects.
*/
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam,
ColorRGBA highlightColor, int highlightWidth,
ColorRGBA hoverColor, int hoverWidth,
ColorRGBA selectColor, int selectWidth
) {
this.app = app;
this.highlightColor = highlightColor;
this.highlightWidth = highlightWidth;
this.hoverColor = hoverColor;
this.hoverWidth = hoverWidth;
this.selectColor = selectColor;
this.selectWidth = selectWidth;
selectObjectOutliner = new SelectObjectOutliner(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) {
// selectObjectOutliner.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) {
outlineOff();
selectObjectOutliner.select(spatial, color, width);
}
/**
* Removes the outline effect from the spatial object.
*/
public void outlineOff() {
selectObjectOutliner.deselect(spatial);
}
/**
* Retrieves the instance of the {@code MdgaApp} associated with this control.
*
* @return The {@code MdgaApp} instance.
*/
public MdgaApp getApp() {
return app;
}
public void highlightOn() {
highlight = true;
outline(highlightColor, highlightWidth);
}
public void highlightOff() {
highlight = false;
outlineOff();
}
public void hoverOn() {
if (!hoverable) return;
outline(hoverColor, hoverWidth);
}
public void hoverOff() {
if (!hoverable) return;
if (select) selectOn();
else if (highlight) highlightOn();
else outlineOff();
}
public void selectOn() {
if (!selectable) return;
select = true;
outline(selectColor, selectWidth);
}
public void selectOff() {
select = false;
if (highlight) highlightOn();
else outlineOff();
}
public void selectableOn(){
setSelectable(true);
setHoverable(true);
highlightOn();
select = false;
}
public void selectableOff(){
setSelectable(false);
setHoverable(false);
highlightOff();
select = false;
}
private void setSelectable(boolean selectable) {
this.selectable = selectable;
}
public boolean isSelected() {
return select;
}
public boolean isSelectable() {
return selectable;
}
public boolean isHoverable() {
return hoverable;
}
private void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
public void setHighlightColor(ColorRGBA color){
highlightColor = color;
}
public void setHighlightWidth(int width){
highlightWidth = width;
}
public void setHoverColor(ColorRGBA color){
hoverColor = color;
}
public void setHoverWidth(int width){
hoverWidth = width;
}
public void setSelectColor(ColorRGBA color){
selectColor = color;
}
public void setSelectWidth(int width){
selectWidth = width;
}
}

View File

@@ -1,57 +0,0 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import pp.mdga.client.MdgaApp;
/**
* OutlineOEControl class extends OutlineControl to manage outline colors and widths
* for own and enemy objects, including hover and select states.
*/
public class OutlineOEControl extends OutlineControl{
private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
private static final ColorRGBA OUTLINE_OWN_HOVER_COLOR = ColorRGBA.Yellow;
private static final ColorRGBA OUTLINE_ENEMY_HOVER_COLOR = ColorRGBA.Green;
private static final ColorRGBA OUTLINE_OWN_SELECT_COLOR = ColorRGBA.Cyan;
private static final ColorRGBA OUTLINE_ENEMY_SELECT_COLOR = ColorRGBA.Orange;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 8;
private static final int OUTLINE_HOVER_WIDTH = 8;
private static final int OUTLINE_SELECT_WIDTH = 10;
/**
* Constructor for OutlineOEControl.
*
* @param app the MdgaApp instance
* @param fpp the FilterPostProcessor instance
* @param cam the Camera instance
*/
public OutlineOEControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
super(app, fpp, cam,
OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH,
OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH,
OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH
);
}
/**
* Sets the outline colors and enables selection for own objects.
*/
public void selectableOwn(){
setHighlightColor(OUTLINE_OWN_COLOR);
setHoverColor(OUTLINE_OWN_HOVER_COLOR);
setSelectColor(OUTLINE_OWN_SELECT_COLOR);
selectableOn();
}
/**
* Sets the outline colors and enables selection for enemy objects.
*/
public void selectableEnemy(){
setHighlightColor(OUTLINE_ENEMY_COLOR);
setHoverColor(OUTLINE_ENEMY_HOVER_COLOR);
setSelectColor(OUTLINE_ENEMY_SELECT_COLOR);
selectableOn();
}
}

View File

@@ -7,20 +7,18 @@
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.board.Outline.SelectObjectOutliner;
/**
* 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 OutlineOEControl {
public class PieceControl extends AbstractControl {
private final float initRotation;
private final AssetManager assetManager;
private Spatial shieldRing;
@@ -32,116 +30,88 @@ public class PieceControl extends OutlineOEControl {
private static final ColorRGBA SHIELD_SUPPRESSED_COLOR = new ColorRGBA(1f, 0.5f, 0, SHIELD_TRANSPARENCY);
private static final float SHIELD_Z = 0f;
private final Node parentNode;
private boolean enemy;
private boolean hoverable;
private boolean highlight;
private boolean selectable;
private boolean select;
SelectObjectOutliner outlineOwn;
/**
* 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) {
super(app, fpp, app.getCamera());
private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
private static final int OUTLINE_THICKNESS = 4;
private final Node parentNode;
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){
super();
this.parentNode = new Node();
this.initRotation = initRotation;
this.assetManager = assetManager;
this.shieldRing = null;
this.shieldMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
this.shieldMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
outlineOwn = new SelectObjectOutliner(OUTLINE_THICKNESS, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera());
}
/**
* Gets the current rotation of the piece in degrees.
*
* @return The rotation of the piece in degrees.
*/
public float getRotation() {
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0, 0, 1)));
return (float) Math.toDegrees(this.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) {
if (rot < 0) rot = -360;
public void setRotation(float rot){
Quaternion quaternion = new Quaternion();
quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0, 0, 1));
spatial.setLocalRotation(quaternion);
quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0,0,1));
this.spatial.setLocalRotation(quaternion);
}
/**
* Gets the current location (position) of the piece.
*
* @return The location of the piece as a {@link Vector3f}.
*/
public Vector3f getLocation() {
return spatial.getLocalTranslation();
public Vector3f getLocation(){
return this.getSpatial().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
protected void controlUpdate(float delta) {
if (shieldRing != null) {
if(shieldRing != null){
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
}
}
/**
* Sets the location (position) of the piece.
*
* @param loc The location to set as a {@link Vector3f}.
*/
public void setLocation(Vector3f loc) {
this.spatial.setLocalTranslation(loc);
@Override
protected void controlRender(RenderManager renderManager, ViewPort viewPort) {
}
public void setLocation(Vector3f loc){
this.getSpatial().setLocalTranslation(loc);
}
/**
* Initializes the spatial object and sets its rotation.
* This also moves the spatial to a new parent node for organizational purposes.
*/
@Override
public void initSpatial() {
public void setSpatial(Spatial spatial){
if(this.getSpatial() == null && spatial != null){
super.setSpatial(spatial);
initSpatial();
}
else{
super.setSpatial(spatial);
}
}
public void initSpatial(){
setRotation(this.initRotation);
Node oldParent = spatial.getParent();
this.parentNode.setName(spatial.getName() + " Parent");
Node oldParent = this.spatial.getParent();
this.parentNode.setName(this.spatial.getName() + " Parent");
oldParent.detachChild(this.getSpatial());
this.parentNode.attachChild(this.getSpatial());
oldParent.attachChild(this.parentNode);
}
public void rotateInit() {
setRotation(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() {
if (shieldRing != null) {
deactivateShield();
}
shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
public void activateShield(){
shieldRing = assetManager.loadModel(Asset.shield_ring.getModelPath());
shieldRing.scale(1f);
shieldRing.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(0));
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,0,SHIELD_Z)));
shieldRing.setQueueBucket(RenderQueue.Bucket.Transparent); // Render in the transparent bucket
@@ -151,28 +121,30 @@ public void activateShield() {
parentNode.attachChild(shieldRing);
}
/**
* Deactivates the shield by removing the shield ring from the scene.
*/
public void deactivateShield() {
public void deactivateShield(){
parentNode.detachChild(shieldRing);
shieldRing = null;
}
/**
* Suppresses the shield, changing its color to a suppressed state.
*/
public void suppressShield() {
assert (shieldRing != null) : "PieceControl: shieldRing is not set";
public void suppressShield(){
assert(shieldRing != null) : "PieceControl: shieldRing is not set";
shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
}
public void setMaterial(Material mat) {
spatial.setMaterial(mat);
public void setMaterial(Material mat){
this.spatial.setMaterial(mat);
}
public Material getMaterial() {
return ((Geometry) getSpatial()).getMaterial();
public Material getMaterial(){
return ((Geometry) this.spatial).getMaterial();
}
public void outline(boolean enemy) {
ColorRGBA color = enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR;
outlineOwn.select(this.getSpatial(), color);
}
public void deOutline() {
outlineOwn.deselect(this.getSpatial());
}
}

View File

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

View File

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

View File

@@ -1,130 +0,0 @@
package pp.mdga.client.board;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
import static pp.mdga.client.Util.linInt;
/**
* Controls the rotation of the tank's top part to face an enemy position.
*/
public class TankTopControl extends InitControl {
private float timer = 0; // Time elapsed
private final static float DURATION = 1.5f; // Total rotation duration in seconds
private boolean rotating = false; // Flag to track if rotation is active
private float startAngle = 0;
private float endAngle = 0;
private Runnable actionAfter = null;
/**
* Updates the control each frame.
*
* @param tpf Time per frame
*/
@Override
protected void controlUpdate(float tpf) {
if (!rotating) return;
// Update the timer
timer += tpf;
// Calculate interpolation factor (0 to 1)
float t = timer / DURATION;
if (t >= 1) t = 1;
float curAngle = linInt(startAngle, endAngle, t);
// Interpolate the rotation
Quaternion interpolatedRotation = new Quaternion();
interpolatedRotation.fromAngleAxis((float) Math.toRadians(curAngle), Vector3f.UNIT_Z);
// Apply the interpolated rotation to the spatial
spatial.setLocalRotation(interpolatedRotation);
if (t >= 1) {
rotating = false;
if (actionAfter != null) actionAfter.run();
}
}
/**
* Initiates the rotation of the tank's top part to face the enemy position.
*
* @param enemyPos The position of the enemy
* @param actionAfter The action to execute after the rotation is complete
*/
public void rotate(Vector3f enemyPos, Runnable actionAfter) {
if (spatial == null) throw new RuntimeException("spatial is null");
startAngle = getOwnAngle();
endAngle = getEnemyAngle(enemyPos);
// Adjust endAngle to ensure the shortest path
float deltaAngle = endAngle - startAngle;
if (deltaAngle > 180) {
endAngle -= 360; // Rotate counterclockwise
} else if (deltaAngle < -180) {
endAngle += 360; // Rotate clockwise
}
timer = 0;
rotating = true;
this.actionAfter = actionAfter; // Store the action to execute after rotation
}
/**
* Calculates the angle to the enemy position.
*
* @param enemyPos The position of the enemy
* @return The angle to the enemy in degrees
*/
private float getEnemyAngle(Vector3f enemyPos) {
// Direction to the enemy in the XY plane
Vector3f direction = enemyPos.subtract(spatial.getLocalTranslation());
direction.z = 0; // Project to XY plane
direction.normalizeLocal();
Vector3f reference = Vector3f.UNIT_Y.mult(-1);
// Calculate the angle between the direction vector and the reference vector
float angle = FastMath.acos(reference.dot(direction));
// Determine rotation direction using the cross product
Vector3f cross = reference.cross(direction);
if (cross.z < 0) {
angle = -angle;
}
return (float) Math.toDegrees(angle); // Return the absolute angle in degrees
}
/**
* Calculates the tank's current angle.
*
* @return The tank's current angle in degrees
*/
private float getOwnAngle() {
// Tank's forward direction in the XY plane
Vector3f forward = spatial.getLocalRotation().mult(Vector3f.UNIT_Y);
forward.z = 0; // Project to XY plane
forward.normalizeLocal();
// Reference vector: Positive X-axis
Vector3f reference = Vector3f.UNIT_Y;
// Calculate the angle between the forward vector and the reference vector
float angle = FastMath.acos(reference.dot(forward));
// Determine rotation direction using the cross product
Vector3f cross = reference.cross(forward);
if (cross.z < 0) { // For Z-up, check the Z component of the cross product
angle = -angle;
}
return (float) Math.toDegrees(angle); // Return the absolute angle in radians
}
}

View File

@@ -1,165 +0,0 @@
package pp.mdga.client.button;
import com.jme3.font.BitmapFont;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.jme3.ui.Picture;
import pp.mdga.client.MdgaApp;
/**
* Represents an abstract base class for creating customizable button components in a graphical user interface.
* This class provides the framework for rendering buttons with different visual states, such as normal and pressed,
* and supports position adjustments and font customization.
*
* <p>Subclasses must implement the {@link #show()} and {@link #hide()} methods to define how the button
* is displayed and hidden in the application.</p>
*/
public abstract class AbstractButton {
/**
* Color representing the normal state of the button.
*/
public static final ColorRGBA BUTTON_NORMAL = ColorRGBA.fromRGBA255(233, 236, 239, 255);
/**
* Color representing the pressed state of the button.
*/
public static final ColorRGBA BUTTON_PRESSED = ColorRGBA.fromRGBA255(105, 117, 89, 255);
/**
* Color representing the normal state of the button text.
*/
public static final ColorRGBA TEXT_NORMAL = ColorRGBA.Black;
/**
* Color representing the pressed state of the button text.
*/
public static final ColorRGBA TEXT_PRESSED = ColorRGBA.fromRGBA255(180, 195, 191, 255);
/**
* The image representing the normal state of the button.
*/
protected Picture pictureNormal = new Picture("normalButton");
/**
* The image representing the hover state of the button.
*/
protected Picture pictureHover = new Picture("normalButton");
/**
* The number of horizontal divisions for calculating relative sizes.
*/
public static final float HORIZONTAL = 16;
/**
* The number of vertical divisions for calculating relative sizes.
*/
public static final float VERTICAL = 9;
/**
* The font used for rendering text on the button.
*/
protected BitmapFont font;
/**
* Reference to the application instance for accessing assets and settings.
*/
protected final MdgaApp app;
/**
* Node in the scene graph to which the button belongs.
*/
protected final Node node;
/**
* The position of the button in 2D space.
*/
protected Vector2f pos;
/**
* Factor for scaling the font size.
*/
protected float fontSizeFactor = 1.0f;
/**
* Computed font size based on scaling factor and screen dimensions.
*/
protected float fontSize;
/**
* Computed horizontal step size based on screen dimensions.
*/
protected float horizontalStep;
/**
* Computed vertical step size based on screen dimensions.
*/
protected float verticalStep;
/**
* Computed height step size based on vertical steps.
*/
protected float heightStep;
/**
* Computed width step size based on horizontal steps.
*/
protected float widthStep;
/**
* Flag indicating whether adjustments are applied to the button.
*/
protected boolean adjust = false;
/**
* Constructs an AbstractButton instance with the specified application context and scene node.
* Initializes the button's visual elements and font.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button is attached
*/
public AbstractButton(MdgaApp app, Node node) {
this.app = app;
this.node = node;
pictureNormal.setImage(app.getAssetManager(), "Images/General_Button_normal.png", true);
pictureHover.setImage(app.getAssetManager(), "Images/General_Button_hover.png", true);
font = app.getAssetManager().loadFont("Fonts/Gunplay.fnt");
}
/**
* Displays the button. Implementation must define how the button is rendered on the screen.
*/
public abstract void show();
/**
* Hides the button. Implementation must define how the button is removed from the screen.
*/
public abstract void hide();
/**
* Sets the position of the button in 2D space.
*
* @param pos the position to set
*/
public void setPos(Vector2f pos) {
this.pos = pos;
}
/**
* Calculates relative sizes and dimensions for the button based on the screen resolution.
*/
protected void calculateRelative() {
fontSize = fontSizeFactor * 15 * (float) app.getCamera().getWidth() / 720;
horizontalStep = (float) app.getCamera().getWidth() / HORIZONTAL;
verticalStep = (float) app.getCamera().getHeight() / VERTICAL;
heightStep = verticalStep / 2;
widthStep = horizontalStep / 2;
}
public Vector2f getPos() {
return pos;
}
}

View File

@@ -1,45 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
/**
* Represents a specific implementation of a clickable button positioned on the left side.
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
* It also includes placeholder methods for handling hover events, which can be customized as needed.
*/
public class ButtonLeft extends ClickButton {
/**
* Constructs a ButtonLeft instance with the specified properties.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label to display on the button
* @param narrowFactor a factor to adjust position of the button
*/
public ButtonLeft(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(0.5f * narrowFactor, 1.8f));
}
/**
* Called when the button is hovered over by the pointer.
* Subclasses can override this method to define specific hover behavior.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button.
* Subclasses can override this method to define specific unhover behavior.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -1,48 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
/**
* Represents a specific implementation of a clickable button positioned on the right side.
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
* It includes placeholder methods for handling hover events, which can be customized as needed.
*/
public class ButtonRight extends ClickButton {
/**
* Constructs a ButtonRight instance with the specified properties.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label to display on the button
* @param narrowFactor a factor to adjust the position of the button
*/
public ButtonRight(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(HORIZONTAL - 0.5f * narrowFactor, 1.8f));
// Enable adjustments specific to this button
adjust = true;
}
/**
* Called when the button is hovered over by the pointer.
* Subclasses can override this method to define specific hover behavior.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button.
* Subclasses can override this method to define specific unhover behavior.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -1,252 +0,0 @@
package pp.mdga.client.button;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
/**
* Represents a button used in a ceremony screen, with 3D model integration, customizable
* appearance based on type, and interactive behavior. The button can rotate and display
* different positions such as FIRST, SECOND, THIRD, and LOST.
*/
public class CeremonyButton extends ClickButton {
/**
* Enum representing the possible positions of the button in the ceremony screen.
*/
public enum Pos {
FIRST,
SECOND,
THIRD,
LOST,
}
/**
* Fixed width of the button in the UI layout.
*/
static final float WIDTH = 4.0f;
/**
* Node to which the 3D model associated with this button is attached.
*/
private final Node node3d;
/**
* Flag to determine if the button's 3D model should rotate.
*/
private boolean rotate = false;
/**
* The 3D model associated with the button.
*/
private Spatial model;
/**
* Current rotation angle of the button's 3D model.
*/
private float rot = 180;
/**
* The taken state of the button (default is NOT taken).
*/
private LobbyButton.Taken taken = LobbyButton.Taken.NOT;
/**
* A label associated with the button for displaying additional information.
*/
private LabelButton label;
/**
* Constructs a CeremonyButton with specified attributes such as type, position, and label.
* The button supports both 2D and 3D components for UI and visual effects.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param node3d the node for 3D scene components associated with this button
* @param tsk the type/color associated with the button
* @param pos the position of the button in the ceremony layout
* @param name the label or name displayed on the button
*/
public CeremonyButton(MdgaApp app, Node node, Node node3d, Color tsk, Pos pos, String name) {
super(app, node, () -> {
}, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
this.node3d = node3d;
label = new LabelButton(app, node, name, new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
final float mid = HORIZONTAL / 2;
final float uiSpacing = 1.4f;
final float figSpacingX = 0.9f;
final float figSpacingY = 0.25f;
float uiX = mid;
float uiY = 6;
float figX = 0;
float figY = -0.32f;
Asset asset = switch (tsk) {
case CYBER -> {
instance.setText("CIR");
yield Asset.cir;
}
case AIRFORCE -> {
instance.setText("Luftwaffe");
yield Asset.lw;
}
case ARMY -> {
instance.setText("Heer");
yield Asset.heer;
}
case NAVY -> {
instance.setText("Marine");
yield Asset.marine;
}
default -> throw new RuntimeException("None is not valid");
};
switch (pos) {
case FIRST:
rotate = true;
uiX = 0;
figY -= 1 * figSpacingY;
break;
case SECOND:
adjust = true;
label.adjust = true;
uiX -= uiSpacing;
uiY -= 1;
figX -= figSpacingX;
figY -= 2 * figSpacingY;
figY -= 0.1f;
break;
case THIRD:
uiX += uiSpacing;
uiY -= 1.5f;
figX += figSpacingX;
figY -= 3 * figSpacingY;
figY -= 0.07f;
break;
case LOST:
adjust = true;
label.adjust = true;
uiX -= 2 * uiSpacing + 0.4f;
uiX -= WIDTH / 2;
uiY -= 2.0f;
figX -= 2.5f * figSpacingX + 0.05f;
figY -= 4.5f * figSpacingY;
break;
}
setPos(new Vector2f(uiX, uiY));
label.setPos(new Vector2f(uiX, uiY + 1));
createModel(asset, new Vector3f(figX, figY, 6));
}
/**
* Handles hover behavior by changing the button's background appearance.
*/
@Override
public void onHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
buttonNormal.a = 0.1f;
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
}
/**
* Handles unhover behavior by resetting the button's background appearance.
*/
@Override
public void onUnHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
buttonNormal.a = 0.1f;
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
}
/**
* Displays the button along with its 3D model and associated label.
*/
@Override
public void show() {
release();
calculateRelative();
setRelative();
node.attachChild(instance);
node3d.attachChild(model);
label.show();
}
/**
* Hides the button along with its 3D model and associated label.
*/
@Override
public void hide() {
node.detachChild(instance);
node3d.detachChild(model);
label.hide();
}
/**
* Updates the rotation of the button's 3D model over time.
*
* @param tpf time per frame, used for smooth rotation calculations
*/
public void update(float tpf) {
if (rotate) {
rot += 140.0f * tpf;
rot %= 360;
} else {
rot = 180;
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
/**
* Creates a 3D model associated with the button and applies its materials and position.
*
* @param asset the asset representing the 3D model and texture
* @param pos the initial position of the model in 3D space
*/
private void createModel(Asset asset, Vector3f pos) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
model.setLocalTranslation(pos);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
}
}

View File

@@ -1,212 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.ui.Picture;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* Abstract base class for creating interactive buttons with click functionality.
* This class extends {@link AbstractButton} and provides additional behavior such as
* click handling, hover effects, and alignment management.
*/
public abstract class ClickButton extends AbstractButton {
/**
* The action to be executed when the button is clicked.
*/
protected final Runnable action;
/**
* The label or text displayed on the button.
*/
protected String label;
/**
* The size of the button in relative units.
*/
protected Vector2f size;
/**
* The instance of the button being managed.
*/
protected Button instance;
/**
* Constructs a ClickButton with the specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute on button click
* @param label the text label displayed on the button
* @param size the size of the button
* @param pos the position of the button in relative units
*/
ClickButton(MdgaApp app, Node node, Runnable action, String label, Vector2f size, Vector2f pos) {
super(app, node);
this.action = action;
this.label = label;
this.pos = pos;
this.size = size;
instance = new Button(label);
// Add click behavior
instance.addClickCommands((button) -> {
app.getAcousticHandler().playSound(MdgaSound.BUTTON_PRESSED);
action.run();
});
// Set text alignment
instance.setTextHAlignment(HAlignment.Center);
instance.setTextVAlignment(VAlignment.Center);
// Add hover commands
instance.addCommands(Button.ButtonAction.HighlightOn, (button) -> click());
instance.addCommands(Button.ButtonAction.HighlightOff, (button) -> release());
// Set font and colors
instance.setFont(font);
instance.setFocusColor(TEXT_NORMAL);
calculateRelative();
setRelative();
}
/**
* Displays the button by attaching it and its background image to the node.
*/
@Override
public void show() {
node.attachChild(pictureNormal);
release();
calculateRelative();
setRelative();
setImageRelative(pictureNormal);
node.attachChild(instance);
}
/**
* Hides the button by detaching it and its background images from the node.
*/
@Override
public void hide() {
node.detachChild(instance);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
}
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
}
}
/**
* Abstract method to define hover behavior. Must be implemented by subclasses.
*/
protected abstract void onHover();
/**
* Abstract method to define unhover behavior. Must be implemented by subclasses.
*/
protected abstract void onUnHover();
/**
* Handles the button click behavior, including visual feedback and sound effects.
*/
protected void click() {
instance.setColor(TEXT_PRESSED);
instance.setHighlightColor(TEXT_PRESSED);
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_PRESSED);
instance.setBackground(background);
app.getAcousticHandler().playSound(MdgaSound.UI_CLICK);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
setImageRelative(pictureHover);
node.attachChild(pictureHover);
}
onHover();
}
/**
* Resets the button to its normal state after a click or hover event.
*/
protected void release() {
instance.setColor(TEXT_NORMAL);
instance.setHighlightColor(TEXT_NORMAL);
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
instance.setBackground(background);
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
setImageRelative(pictureNormal);
node.attachChild(pictureNormal);
}
onUnHover();
}
/**
* Sets the relative size and position of the button based on screen dimensions.
*/
protected void setRelative() {
instance.setFontSize(fontSize);
instance.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = instance.getPreferredSize().x;
}
instance.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (instance.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - instance.getPreferredSize().y / 2;
if (0 == pos.x) {
instance.setLocalTranslation(horizontalMid, instance.getLocalTranslation().y, instance.getLocalTranslation().z);
}
if (0 == pos.y) {
instance.setLocalTranslation(instance.getLocalTranslation().x, verticalMid, instance.getLocalTranslation().z);
}
}
/**
* Sets the relative size and position of the button's background image.
*
* @param picture the background image to set
*/
protected void setImageRelative(Picture picture) {
if (null == picture) {
return;
}
final float larger = 10;
picture.setWidth(instance.getPreferredSize().x + larger);
picture.setHeight(instance.getPreferredSize().y + larger);
picture.setLocalTranslation(
instance.getLocalTranslation().x - larger / 2,
(instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
instance.getLocalTranslation().z + 0.01f
);
}
}

View File

@@ -1,166 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents an input button with a label and a text field, allowing users to input text.
* The button is designed for graphical user interfaces and supports configurable
* size, position, and character limit.
*/
public class InputButton extends AbstractButton {
/**
* The label associated with the input field, displayed above or beside the text field.
*/
private Label label;
/**
* The text field where users input their text.
*/
private TextField field;
/**
* A container to hold the label and the text field for layout management.
*/
private Container container = new Container();
/**
* The maximum allowed length of the input text.
*/
private final int maxLenght;
/**
* The size of the input button in relative units.
*/
protected Vector2f size;
/**
* Constructs an InputButton with the specified label, character limit, and other properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the input button belongs
* @param label the label displayed with the input field
* @param maxLenght the maximum number of characters allowed in the input field
*/
public InputButton(MdgaApp app, Node node, String label, int maxLenght) {
super(app, node);
this.label = new Label(label);
this.maxLenght = maxLenght;
// Configure label properties
this.label.setColor(TEXT_NORMAL);
// Configure text field properties
field = new TextField("");
field.setColor(TEXT_NORMAL);
field.setTextHAlignment(HAlignment.Left);
field.setTextVAlignment(VAlignment.Center);
// Set background for the text field
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
field.setBackground(grayBackground);
// Set fonts for label and text field
this.label.setFont(font);
field.setFont(font);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(5.5f, 1);
// Add components to the container
container.addChild(this.label);
container.addChild(field);
}
/**
* Displays the input button by attaching it to the scene graph node.
*/
@Override
public void show() {
calculateRelative();
setRelative();
node.attachChild(container);
}
/**
* Hides the input button by detaching it from the scene graph node.
*/
@Override
public void hide() {
node.detachChild(container);
}
/**
* Updates the input field, enforcing the character limit.
* Trims the text if it exceeds the maximum allowed length.
*/
public void update() {
String text = field.getText();
int length = text.length();
if (length > maxLenght) {
field.setText(text.substring(0, maxLenght));
}
}
/**
* Adjusts the relative size and position of the input button based on the screen resolution.
*/
protected void setRelative() {
this.label.setFontSize(fontSize);
field.setFontSize(fontSize);
field.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = container.getPreferredSize().x;
}
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
if (0 == pos.x) {
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
}
if (0 == pos.y) {
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
}
}
/**
* Retrieves the text currently entered in the input field.
*
* @return the current text in the input field
*/
public String getString() {
return field.getText();
}
/**
* Sets the text of the input field to the specified string.
*
* @param string the text to set in the input field
*/
public void setString(String string) {
field.setText(string);
}
/**
* Resets the input field by clearing its text.
*/
public void reset() {
field.setText("");
}
}

View File

@@ -1,132 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* A specialized button that can function as a label or a clickable button.
* It inherits from {@link ClickButton} and allows for flexible usage with or without button-like behavior.
*/
public class LabelButton extends ClickButton {
/**
* The color of the text displayed on the label or button.
*/
private ColorRGBA text = TEXT_NORMAL;
/**
* The color of the button's background.
*/
private ColorRGBA button = BUTTON_NORMAL;
/**
* Flag indicating whether this component functions as a button.
*/
private boolean isButton;
/**
* Constructs a LabelButton with specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param label the text displayed on the label or button
* @param size the size of the label or button
* @param pos the position of the label or button in relative units
* @param isButton whether this component acts as a button or a simple label
*/
public LabelButton(MdgaApp app, Node node, String label, Vector2f size, Vector2f pos, boolean isButton) {
super(app, node, () -> {
}, label, size, pos);
this.isButton = isButton;
// Use the same image for hover and normal states
pictureHover = pictureNormal;
}
/**
* Displays the label or button, attaching it to the scene graph.
* If the component is a button, it also attaches the background image.
*/
@Override
public void show() {
if (isButton) {
node.attachChild(pictureNormal);
}
release();
calculateRelative();
setRelative();
setImageRelative(pictureNormal);
instance.setFontSize(fontSize / 2);
node.attachChild(instance);
}
/**
* Hides the label or button, detaching it from the scene graph.
*/
@Override
public void hide() {
node.detachChild(instance);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
}
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
}
}
/**
* Handles hover behavior, updating the colors of the text and background.
*/
@Override
public void onHover() {
instance.setColor(text);
instance.setHighlightColor(text);
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
instance.setBackground(background);
}
/**
* Handles unhover behavior, restoring the colors of the text and background.
*/
@Override
public void onUnHover() {
instance.setColor(text);
instance.setHighlightColor(text);
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
instance.setBackground(background);
}
/**
* Sets the text displayed on the label or button.
*
* @param text the text to display
*/
public void setText(String text) {
instance.setText(text);
}
/**
* Sets the colors of the text and background, and refreshes the label or button.
*
* @param text the color of the text
* @param button the color of the button's background
*/
public void setColor(ColorRGBA text, ColorRGBA button) {
this.text = text;
this.button = button;
hide();
show();
}
}

View File

@@ -1,329 +0,0 @@
package pp.mdga.client.button;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
/**
* Represents a button in a multiplayer lobby screen. The button supports multiple states
* (not taken, self, other) and displays a 3D model alongside its label. It can also indicate readiness
* and interactively respond to hover and click events.
*/
public class LobbyButton extends ClickButton {
/**
* Enum representing the possible ownership states of the lobby button.
*/
public enum Taken {
NOT, // The button is not taken
SELF, // The button is taken by the user
OTHER // The button is taken by another user
}
/**
* Color for a lobby button that is taken by another user.
*/
static final ColorRGBA LOBBY_TAKEN = ColorRGBA.fromRGBA255(193, 58, 59, 100);
/**
* Color for a lobby button that is ready but not hovered.
*/
static final ColorRGBA LOBBY_READY = ColorRGBA.fromRGBA255(55, 172, 190, 100);
/**
* Color for a lobby button that is ready and hovered.
*/
static final ColorRGBA LOBBY_READY_HOVER = ColorRGBA.fromRGBA255(17, 211, 218, 100);
/**
* Color for a lobby button owned by the user in normal state.
*/
static final ColorRGBA LOBBY_SELF_NORMAL = ColorRGBA.fromRGBA255(0, 151, 19, 100);
/**
* Color for a lobby button owned by the user when hovered.
*/
static final ColorRGBA LOBBY_SELF_HOVER = ColorRGBA.fromRGBA255(0, 230, 19, 100);
/**
* Fixed width for the lobby button.
*/
static final float WIDTH = 4.0f;
/**
* Node to which the 3D model associated with this button is attached.
*/
private final Node node3d;
/**
* Indicates whether the 3D model should rotate.
*/
private boolean rotate = false;
/**
* The 3D model displayed alongside the button.
*/
private Spatial model;
/**
* The rotation angle of the 3D model.
*/
private float rot = 180;
/**
* The current ownership state of the lobby button.
*/
private Taken taken = Taken.NOT;
/**
* Label displayed on the lobby button.
*/
private LabelButton label;
/**
* Indicates whether the button represents a ready state.
*/
private boolean isReady = false;
/**
* Constructs a LobbyButton with specified properties, including a 3D model and label.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param node3d the node for 3D scene components associated with this button
* @param action the action to execute when the button is clicked
* @param tsk the type or category of the button (e.g., CYBER, AIRFORCE)
*/
public LobbyButton(MdgaApp app, Node node, Node node3d, Runnable action, Color tsk) {
super(app, node, action, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
this.node3d = node3d;
label = new LabelButton(app, node, "- leer -", new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
final float mid = HORIZONTAL / 2;
final float uiSpacing = 0.4f;
final float figSpacing = 0.51f;
float uiX = mid;
float figX = 0;
Asset asset = null;
// Configure the button based on its type
switch (tsk) {
case CYBER:
adjust = true;
label.adjust = true;
uiX -= 3 * uiSpacing;
uiX -= WIDTH / 2;
asset = Asset.cir;
figX -= 3 * figSpacing;
instance.setText("CIR");
break;
case AIRFORCE:
adjust = true;
label.adjust = true;
uiX -= uiSpacing;
asset = Asset.lw;
figX -= figSpacing;
instance.setText("Luftwaffe");
break;
case ARMY:
uiX += uiSpacing;
asset = Asset.heer;
figX += figSpacing;
instance.setText("Heer");
break;
case NAVY:
uiX += 3 * uiSpacing;
uiX += WIDTH / 2;
asset = Asset.marine;
figX += 3 * figSpacing;
instance.setText("Marine");
break;
}
setPos(new Vector2f(uiX, 6));
label.setPos(new Vector2f(uiX, 7));
createModel(asset, new Vector3f(figX, -0.55f, 6));
}
/**
* Handles hover behavior, updating the button's color and enabling rotation.
*/
@Override
public void onHover() {
ColorRGBA buttonPressed = BUTTON_PRESSED.clone();
switch (taken) {
case NOT:
buttonPressed.a = 0.3f;
break;
case SELF:
buttonPressed = LOBBY_SELF_HOVER;
break;
case OTHER:
buttonPressed = LOBBY_TAKEN;
break;
}
if (isReady) {
buttonPressed = LOBBY_READY_HOVER;
}
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonPressed);
instance.setBackground(background);
rotate = true;
}
/**
* Handles unhover behavior, restoring the button's color and disabling rotation.
*/
@Override
public void onUnHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
switch (taken) {
case NOT:
buttonNormal.a = 0.3f;
break;
case SELF:
buttonNormal = LOBBY_SELF_NORMAL;
break;
case OTHER:
buttonNormal = LOBBY_TAKEN;
break;
}
if (isReady) {
buttonNormal = LOBBY_READY;
}
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
rotate = false;
}
/**
* Displays the lobby button and its associated components.
*/
@Override
public void show() {
release();
calculateRelative();
setRelative();
node.attachChild(instance);
node3d.attachChild(model);
label.show();
}
/**
* Hides the lobby button and its associated components.
*/
@Override
public void hide() {
node.detachChild(instance);
node3d.detachChild(model);
label.hide();
}
/**
* Updates the 3D model's rotation if the button is being hovered.
*
* @param tpf time per frame, used for smooth rotation calculations
*/
public void update(float tpf) {
if (rotate) {
rot += 140.0f * tpf;
rot %= 360;
} else {
rot = 180;
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
/**
* Creates the 3D model associated with the lobby button and applies textures and positioning.
*
* @param asset the asset representing the 3D model
* @param pos the initial position of the 3D model
*/
private void createModel(Asset asset, Vector3f pos) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
model.setLocalTranslation(pos);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
}
/**
* Gets the current ownership state of the lobby button.
*
* @return the current state of the button
*/
public Taken getTaken() {
return taken;
}
/**
* Sets the ownership state of the lobby button and updates its label accordingly.
*
* @param taken the new ownership state
* @param name the name to display on the button
*/
public void setTaken(Taken taken, String name) {
this.taken = taken;
if (taken == Taken.NOT) {
label.setText("- leer -");
isReady = false;
} else {
label.setText(name);
}
onUnHover();
}
/**
* Sets the ready state of the lobby button and updates its appearance.
*
* @param isReady whether the button represents a ready state
*/
public void setReady(boolean isReady) {
this.isReady = isReady;
onUnHover();
}
}

View File

@@ -1,43 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
/**
* Represents a button used in menu screens for navigation or executing actions.
* Inherits from {@link ClickButton} and provides customizable text and actions.
*/
public class MenuButton extends ClickButton {
/**
* Constructs a MenuButton with specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label displayed on the button
*/
public MenuButton(MdgaApp app, Node node, Runnable action, String label) {
super(app, node, action, label, new Vector2f(5.5f, 2), new Vector2f(0, 0));
}
/**
* Called when the button is hovered over. Can be overridden to define hover-specific behavior.
* Currently, no additional behavior is implemented.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button. Can be overridden to define unhover-specific behavior.
* Currently, no additional behavior is implemented.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -1,50 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.simsilica.lemur.component.IconComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents a button in the settings menu, designed to display an icon and handle hover effects.
* Inherits from {@link ClickButton} and customizes its behavior for settings functionality.
*/
public class SettingsButton extends ClickButton {
/**
* The icon displayed on the button, which changes based on hover state.
*/
private IconComponent icon;
/**
* Constructs a SettingsButton with a predefined size and position.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
*/
public SettingsButton(MdgaApp app, Node node, Runnable action) {
super(app, node, action, "", new Vector2f(2, 2), new Vector2f(HORIZONTAL - 0.5f, VERTICAL - 0.5f));
// Enable adjustment for positioning
adjust = true;
pictureNormal.setImage(app.getAssetManager(), "Images/Settings_Button_normal.png", true);
pictureHover.setImage(app.getAssetManager(), "Images/Settings_Button_hover.png", true);
}
/**
* Handles hover behavior by changing the icon to the hover state.
*/
@Override
public void onHover() {
}
/**
* Handles unhover behavior by restoring the icon to the normal state.
*/
@Override
public void onUnHover() {
}
}

View File

@@ -1,168 +0,0 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents a slider button component with a label, providing functionality for
* adjusting values in a graphical user interface. It includes increment, decrement,
* and thumb buttons, allowing for interactive adjustments.
*/
public class SliderButton extends AbstractButton {
/**
* The label displayed next to the slider.
*/
private Label label;
/**
* The slider component for adjusting values.
*/
private Slider slider;
/**
* A container to hold the label and slider for layout management.
*/
private Container container = new Container();
/**
* The size of the slider button in relative units.
*/
protected Vector2f size;
/**
* Constructs a SliderButton with the specified label.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the slider button belongs
* @param label the text label displayed alongside the slider
*/
public SliderButton(MdgaApp app, Node node, String label) {
super(app, node);
this.label = new Label(label);
this.label.setColor(TEXT_NORMAL);
// Configure the slider
slider = new Slider("slider");
// Configure decrement button
slider.getDecrementButton().setText(" - ");
slider.getDecrementButton().setFont(font);
slider.getDecrementButton().setFocusColor(TEXT_NORMAL);
slider.getDecrementButton().setTextVAlignment(VAlignment.Bottom);
slider.getDecrementButton().setColor(TEXT_NORMAL);
slider.getDecrementButton().setHighlightColor(TEXT_NORMAL);
// Configure increment button
slider.getIncrementButton().setText(" + ");
slider.getIncrementButton().setFont(font);
slider.getIncrementButton().setFocusColor(TEXT_NORMAL);
slider.getIncrementButton().setTextVAlignment(VAlignment.Bottom);
slider.getIncrementButton().setColor(TEXT_NORMAL);
slider.getIncrementButton().setHighlightColor(TEXT_NORMAL);
// Configure thumb button
slider.getThumbButton().setText("X");
slider.getThumbButton().setFont(font);
slider.getThumbButton().setFocusColor(TEXT_NORMAL);
slider.getThumbButton().setColor(TEXT_NORMAL);
slider.getThumbButton().setHighlightColor(TEXT_NORMAL);
// Set slider background
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
slider.setBackground(background);
// Set label background
QuadBackgroundComponent labelBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
this.label.setBackground(labelBackground);
// Configure the label font
this.label.setFont(font);
this.label.setTextHAlignment(HAlignment.Center);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(6f, 1);
// Add label and slider to container
container.addChild(this.label);
container.addChild(slider);
}
/**
* Displays the slider button by attaching its container to the scene graph.
*/
@Override
public void show() {
calculateRelative();
setRelative();
node.attachChild(container);
}
/**
* Hides the slider button by detaching its container from the scene graph.
*/
@Override
public void hide() {
node.detachChild(container);
}
/**
* Sets the relative size and position of the slider button based on screen resolution.
*/
protected void setRelative() {
this.label.setFontSize(fontSize);
// Set font sizes for slider components
slider.getDecrementButton().setFontSize(fontSize);
slider.getIncrementButton().setFontSize(fontSize);
slider.getThumbButton().setFontSize(fontSize);
// Set slider size
slider.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = container.getPreferredSize().x;
}
// Set container position
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
if (0 == pos.x) {
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
}
if (0 == pos.y) {
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
}
}
/**
* Retrieves the current percentage value of the slider.
*
* @return the current percentage value as a float
*/
public float getPercent() {
return (float) slider.getModel().getPercent();
}
/**
* Sets the slider to the specified percentage value.
*
* @param percent the percentage value to set
*/
public void setPercent(float percent) {
slider.getModel().setPercent(percent);
}
}

View File

@@ -1,102 +0,0 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.button.SliderButton;
import pp.mdga.client.view.MdgaView;
/**
* The {@code AudioSettingsDialog} class represents a dialog for adjusting audio settings in the application.
* It provides controls for managing main volume, music volume, and sound effect volume, and includes
* a button to return to the previous menu.
*/
public class AudioSettingsDialog extends Dialog {
private final MdgaView view;
private SliderButton mainVolume;
private SliderButton musicVolume;
private SliderButton soundVolume;
private MenuButton backButton;
private boolean active = false;
/**
* Constructs an {@code AudioSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The current view, used for navigation and interaction with the dialog.
*/
public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
this.view = view;
mainVolume = new SliderButton(app, node, "Gesamt Lautstärke");
musicVolume = new SliderButton(app, node, "Musik Lautstärke");
soundVolume = new SliderButton(app, node, "Effekt Lautstärke");
backButton = new MenuButton(app, node, view::leaveAudioSettings, "Zurück");
float offset = 2.8f;
mainVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.0f;
musicVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.0f;
soundVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Initializes and displays the volume controls and back button.
*/
@Override
protected void onShow() {
active = true;
mainVolume.setPercent(app.getAcousticHandler().getMainVolume());
musicVolume.setPercent(app.getAcousticHandler().getMusicVolume());
soundVolume.setPercent(app.getAcousticHandler().getSoundVolume());
backButton.show();
mainVolume.show();
musicVolume.show();
soundVolume.show();
}
/**
* Called when the dialog is hidden. Hides all volume controls and the back button.
*/
@Override
protected void onHide() {
active = false;
backButton.hide();
mainVolume.hide();
musicVolume.hide();
soundVolume.hide();
}
/**
* Updates the application audio settings based on the current values of the sliders.
* This method is called continuously while the dialog is active.
*/
public void update() {
if (!active) {
return;
}
app.getAcousticHandler().setMainVolume(mainVolume.getPercent());
app.getAcousticHandler().setMusicVolume(musicVolume.getPercent());
app.getAcousticHandler().setSoundVolume(soundVolume.getPercent());
}
}

View File

@@ -1,135 +0,0 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import java.util.ArrayList;
/**
* The {@code CeremonyDialog} class displays a dialog containing statistical data in a tabular format.
* It allows adding rows of statistics and manages their visibility when shown or hidden.
*/
public class CeremonyDialog extends Dialog {
private ArrayList<ArrayList<LabelButton>> labels;
float offsetX;
/**
* Constructs a {@code CeremonyDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public CeremonyDialog(MdgaApp app, Node node) {
super(app, node);
prepare();
}
/**
* Called when the dialog is shown. Makes all label buttons in the table visible.
*/
@Override
protected void onShow() {
for (ArrayList<LabelButton> row : labels) {
for (LabelButton b : row) {
b.show();
}
}
}
/**
* Called when the dialog is hidden. Hides all label buttons in the table.
*/
@Override
protected void onHide() {
for (ArrayList<LabelButton> row : labels) {
for (LabelButton b : row) {
b.hide();
}
}
}
/**
* Adds a row of statistical data to the dialog.
*
* @param name The name of the player or category for the row.
* @param v1 The value for the first column.
* @param v2 The value for the second column.
* @param v3 The value for the third column.
* @param v4 The value for the fourth column.
* @param v5 The value for the fifth column.
* @param v6 The value for the sixth column.
*/
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
float offsetYSmall = 0.5f;
ArrayList<LabelButton> row = new ArrayList<>();
Vector2f sizeSmall = new Vector2f(4, 1.2f);
row.add(new LabelButton(app, node, name, sizeSmall, new Vector2f(), true));
row.add(new LabelButton(app, node, "" + v1, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v2, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v3, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v4, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v5, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v6, sizeSmall, new Vector2f(), false));
ColorRGBA colorText = AbstractButton.TEXT_NORMAL.clone();
colorText.a = 0.2f;
ColorRGBA colorButton = AbstractButton.BUTTON_NORMAL.clone();
colorButton.a = 0.2f;
int j = 0;
for (LabelButton b : row) {
if (j > 0) {
b.setColor(colorText, colorButton);
}
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetYSmall));
offsetYSmall += 0.8f;
j++;
}
offsetX += 2.3f;
labels.add(row);
}
/**
* Prepares the initial layout of the dialog, including header labels.
*/
public void prepare() {
offsetX = 0.5f;
labels = new ArrayList<>();
ArrayList<LabelButton> first = new ArrayList<>();
Vector2f size = new Vector2f(4, 1.2f);
first.add(new LabelButton(app, node, "", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Figuren geworfen", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Figuren verloren", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gespielte Bonuskarten", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gewürfelte 6en", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gelaufene Felder", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Bonusfeldern erreicht", size, new Vector2f(), true));
float offsetY = 0.5f;
for (LabelButton b : first) {
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetY));
offsetY += 0.8f;
}
offsetX += 2.3f;
labels.add(first);
}
}

View File

@@ -1,55 +1,82 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* The {@code Dialog} class serves as an abstract base class for dialogs in the application.
* It provides functionality for showing and hiding the dialog and defines abstract methods
* for custom behavior when the dialog is shown or hidden.
*/
import static com.jme3.math.FastMath.floor;
public abstract class Dialog {
protected final ColorRGBA COLOR_DEFAULT = ColorRGBA.Gray;
protected final ColorRGBA COLOR_HOVER = ColorRGBA.DarkGray;
protected Container container;
protected final MdgaApp app;
protected final Node node = new Node();
private final Node node;
private final Node root;
protected final float vertical_step;
protected final float horitontal_step;
/**
* Constructs a {@code Dialog}.
*
* @param app The main application managing the dialog.
* @param node The root node to which the dialog's node will be attached.
*/
Dialog(MdgaApp app, Node node) {
protected float fontSize = 35;
public Dialog(MdgaApp app, Node node) {
this.app = app;
this.root = node;
this.node = node;
this.container = new Container();
this.horitontal_step = app.getCamera().getWidth() / 16;
this.vertical_step = app.getCamera().getHeight() / 9;
int val = (int) (32 * Math.min(app.getResolutionFactor() * 0.9f, 1));
fontSize = val;
}
/**
* Shows the dialog by attaching its node to the root node and invoking the {@code onShow} method.
*/
public void show() {
root.attachChild(node);
onShow();
node.attachChild(container);
}
/**
* Hides the dialog by detaching its node from the root node and invoking the {@code onHide} method.
*/
public void hide() {
root.detachChild(node);
onHide();
public void hide () {
node.detachChild(container);
}
/**
* Called when the dialog is shown. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onShow();
protected void createButton(String label, Runnable action, Vector3f size) {
Button button = new Button(label);
button.addClickCommands(source -> action.run());
button.setFontSize(fontSize);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
/**
* Called when the dialog is hidden. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onHide();
QuadBackgroundComponent background = new QuadBackgroundComponent(COLOR_DEFAULT);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(COLOR_HOVER);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(COLOR_DEFAULT);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
container.addChild(button);
}
}

View File

@@ -0,0 +1,15 @@
package pp.mdga.client.dialog;
import java.util.function.Supplier;
public class GetPercentRunnable {
private final Supplier<Float> action;
public GetPercentRunnable(Supplier<Float> action) {
this.action = action;
}
public float get() {
return action.get();
}
}

View File

@@ -1,113 +1,143 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.prefs.Preferences;
public class HostDialog extends Dialog {
private TextField portInput;
/**
* The {@code HostDialog} class represents a dialog for hosting a network game session.
* It allows users to input a port number, start hosting a server, and navigate back to the previous view.
*/
public class HostDialog extends NetworkDialog {
private InputButton portInput;
public HostDialog(MdgaApp app, Node node, Runnable backAction) {
super(app, node);
private ButtonRight hostButton;
private ButtonLeft backButton;
QuadBackgroundComponent quad1 = new QuadBackgroundComponent(ColorRGBA.Gray);
quad1.setMargin(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor());
container.setBackground(quad1);
private final MainView view;
Texture texture = app.getAssetManager().loadTexture("mdga_logo.png");
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
QuadBackgroundComponent b = new QuadBackgroundComponent(texture);
/**
* Constructs a {@code HostDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public HostDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
Panel imagePanel = new Panel();
imagePanel.setBackground(b);
this.view = view;
container.addChild(imagePanel).setPreferredSize(new Vector3f(texture.getImage().getWidth() / 4 * app.getResolutionFactor(), texture.getImage().getHeight() / 4 * app.getResolutionFactor(), 0));
portInput = new InputButton(app, node, "Port: ", 5);
portInput.setString(prefs.get("hostPort", "11111"));
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
hostButton = new ButtonRight(app, node, view::forward, "Spiel hosten", 10);
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
createTextField();
float offset = 2.8f;
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
Container sub = new Container(new SpringGridLayout(Axis.X, Axis.Y));
createButton(sub, "Abbrechen", backAction, new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
sub.addChild(new Panel(40 * app.getResolutionFactor(), 0 * app.getResolutionFactor(), ColorRGBA.Gray));
createButton(sub, "Starten", () -> tryStart(), new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
container.addChild(sub);
}
void tryStart() {
if (null == portInput.getText()) {
portInput.setText("");
return;
}
int port = 0;
try {
port = Integer.parseInt(portInput.getText());
} catch (NumberFormatException e) {
portInput.setText("");
return;
}
if(port >= 1 && port <= 65535) {
app.getModelSyncronizer().setHost(port);
}
portInput.setText("");
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
portInput.show();
hostButton.show();
backButton.show();
public void show() {
super.show();
container.setLocalTranslation(
app.getCamera().getWidth() / 2 - container.getPreferredSize().x / 2,
app.getCamera().getHeight() / 2 + container.getPreferredSize().y / 2,
0
);
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
portInput.hide();
hostButton.hide();
backButton.hide();
public void hide() {
super.hide();
}
/**
* Updates the state of the port input field.
* This method is called periodically to synchronize the dialog state.
*/
public void update() {
portInput.update();
private void createTextField() {
Container subContainer = new Container(new SpringGridLayout(Axis.X, Axis.Y));
Label nameLabel = new Label("Port:\t");
nameLabel.setFontSize(fontSize);
nameLabel.setColor(ColorRGBA.Black);
portInput = new TextField("");
portInput.setColor(ColorRGBA.Black);
portInput.setTextHAlignment(HAlignment.Left);
portInput.setFontSize(fontSize);
portInput.setSingleLine(true);
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(ColorRGBA.DarkGray);
portInput.setBackground(grayBackground);
subContainer.addChild(nameLabel);
subContainer.addChild(portInput);
container.addChild(subContainer);
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("hostPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
protected void createButton(Container c, String label, Runnable action, Vector3f size) {
Button button = new Button(label);
button.addClickCommands(source -> action.run());
button.setFontSize(fontSize);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("hostPort", "11111");
}
QuadBackgroundComponent background = new QuadBackgroundComponent(COLOR_DEFAULT);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
/**
* Starts the server to host a network game.
*/
public void hostServer() {
startServer();
}
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(COLOR_HOVER);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
/**
* Connects to the server as a client.
*/
public void connectServerAsClient() {
connectServer();
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(COLOR_DEFAULT);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
c.addChild(button);
}
}

View File

@@ -1,82 +0,0 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.game.Color;
/**
* The {@code InterruptDialog} class represents a dialog that interrupts the game flow,
* providing a message and the option to force an action if the user is a host.
*/
public class InterruptDialog extends Dialog {
private ButtonRight forceButton;
private LabelButton label;
private String text = "";
/**
* Constructs an {@code InterruptDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public InterruptDialog(MdgaApp app, Node node) {
super(app, node);
forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
}
/**
* Called when the dialog is shown. Displays the label and optionally the force button if the user is the host.
*/
@Override
protected void onShow() {
if (app.getGameLogic().isHost()) {
forceButton.show();
}
label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
float offset = 2.8f;
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
label.show();
}
/**
* Called when the dialog is hidden. Hides the label and the force button.
*/
@Override
protected void onHide() {
forceButton.hide();
label.hide();
}
/**
* Sets the displayed text based on the specified color.
*
* @param color The color used to determine the text (e.g., "Luftwaffe" for AIRFORCE).
*/
public void setColor(Color color) {
switch (color) {
case AIRFORCE:
text = "Luftwaffe";
break;
case ARMY:
text = "Heer";
break;
case NAVY:
text = "Marine";
break;
case CYBER:
text = "CIR";
break;
}
}
}

View File

@@ -1,147 +1,192 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
/**
* The {@code JoinDialog} class represents a dialog for joining a network game.
* It allows users to input an IP address and port number, connect to a server, or navigate back to the previous view.
*/
public class JoinDialog extends NetworkDialog {
private InputButton ipInput;
private InputButton portInput;
public class JoinDialog extends Dialog {
private TextField portInput;
private TextField ipInput;
private ButtonRight joinButton;
private ButtonLeft backButton;
public JoinDialog(MdgaApp app, Node node, Runnable backAction) {
super(app, node);
private final MainView view;
QuadBackgroundComponent quad1 = new QuadBackgroundComponent(ColorRGBA.Gray);
quad1.setMargin(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor());
container.setBackground(quad1);
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
Texture texture = app.getAssetManager().loadTexture("mdga_logo.png");
/**
* Constructs a {@code JoinDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public JoinDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
QuadBackgroundComponent b = new QuadBackgroundComponent(texture);
this.view = view;
Panel imagePanel = new Panel();
imagePanel.setBackground(b);
ipInput = new InputButton(app, node, "Ip: ", 15);
portInput = new InputButton(app, node, "Port: ", 5);
portInput.setString(prefs.get("joinPort", "11111"));
ipInput.setString(prefs.get("joinIp", ""));
container.addChild(imagePanel).setPreferredSize(new Vector3f(texture.getImage().getWidth() / 4 * app.getResolutionFactor(), texture.getImage().getHeight() / 4 * app.getResolutionFactor(), 0));
joinButton = new ButtonRight(app, node, view::forward, "Spiel beitreten", 10);
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
float offset = 2.8f;
createIpField();
ipInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
createPortField();
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
Container sub = new Container(new SpringGridLayout(Axis.X, Axis.Y));
createButton(sub, "Abbrechen", backAction, new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
sub.addChild(new Panel(40 * app.getResolutionFactor(), 0 * app.getResolutionFactor(), ColorRGBA.Gray));
createButton(sub, "Beitreten", () -> tryJoin(), new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
container.addChild(sub);
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
ipInput.show();
portInput.show();
joinButton.show();
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
ipInput.hide();
portInput.hide();
joinButton.hide();
backButton.hide();
}
/**
* Updates the state of the input fields. This method is called periodically to synchronize the dialog state.
*/
public void update() {
ipInput.update();
portInput.update();
}
/**
* Retrieves the currently entered IP address, saves it to preferences, and sets it as the hostname.
*
* @return The IP address as a string.
*/
public String getIpt() {
prefs.put("joinIp", ipInput.getString());
setHostname(ipInput.getString());
return ipInput.getString();
}
/**
* Resets the IP input field to its default value and updates preferences accordingly.
*/
public void resetIp() {
ipInput.reset();
prefs.put("joinIp", "");
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("joinPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("joinPort", "11111");
}
/**
* Connects to the server using the current IP address and port number.
*/
public void connectToServer() {
connectServer();
}
/**
* Disconnects from the server if a network connection exists.
*/
public void disconnect() {
NetworkSupport network = getNetwork();
if (network != null) {
try {
network.disconnect();
} catch (Exception e) {
System.err.println("Error while disconnecting: " + e.getMessage());
}
void tryJoin() {
if (null == portInput.getText()) {
portInput.setText("");
ipInput.setText("");
return;
}
int port = 0;
try {
port = Integer.parseInt(portInput.getText());
} catch (NumberFormatException e) {
portInput.setText("");
ipInput.setText("");
return;
}
if(!(port >= 1 && port <= 65535)) {
portInput.setText("");
ipInput.setText("");
return;
}
if (null == ipInput.getText()) {
portInput.setText("");
ipInput.setText("");
return;
}
String ipv4Pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
if(!Pattern.matches(ipv4Pattern, ipInput.getText())) {
portInput.setText("");
ipInput.setText("");
return;
}
app.getModelSyncronizer().setJoin(ipInput.getText(), port);
}
@Override
public void show() {
super.show();
container.setLocalTranslation(
app.getCamera().getWidth() / 2 - container.getPreferredSize().x / 2,
app.getCamera().getHeight() / 2 + container.getPreferredSize().y / 2,
0
);
}
@Override
public void hide() {
super.hide();
}
private void createPortField() {
Container subContainer = new Container(new SpringGridLayout(Axis.X, Axis.Y));
Label nameLabel = new Label("Port:\t");
nameLabel.setFontSize(fontSize);
nameLabel.setColor(ColorRGBA.Black);
portInput = new TextField("");
portInput.setColor(ColorRGBA.Black);
portInput.setTextHAlignment(HAlignment.Left);
portInput.setFontSize(fontSize);
portInput.setSingleLine(true);
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(ColorRGBA.DarkGray);
portInput.setBackground(grayBackground);
subContainer.addChild(nameLabel);
subContainer.addChild(portInput);
container.addChild(subContainer);
}
private void createIpField() {
Container subContainer = new Container(new SpringGridLayout(Axis.X, Axis.Y));
Label nameLabel = new Label("Ip:\t");
nameLabel.setFontSize(fontSize);
nameLabel.setColor(ColorRGBA.Black);
ipInput = new TextField("");
ipInput.setColor(ColorRGBA.Black);
ipInput.setTextHAlignment(HAlignment.Left);
ipInput.setFontSize(fontSize);
ipInput.setSingleLine(true);
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(ColorRGBA.DarkGray);
ipInput.setBackground(grayBackground);
subContainer.addChild(nameLabel);
subContainer.addChild(ipInput);
container.addChild(subContainer);
}
protected void createButton(Container c, String label, Runnable action, Vector3f size) {
Button button = new Button(label);
button.addClickCommands(source -> action.run());
button.setFontSize(fontSize);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
QuadBackgroundComponent background = new QuadBackgroundComponent(COLOR_DEFAULT);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(COLOR_HOVER);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(COLOR_DEFAULT);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
c.addChild(button);
}
}

View File

@@ -0,0 +1,165 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Command;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
public class LobbyButtonDialog extends Dialog {
private final int pos;
private Button button;
private Command<Button> clickCommand;
private boolean taken = false;
private final String label;
private int val;
public LobbyButtonDialog(MdgaApp app, Node node, String label, int pos) {
super(app, node);
clickCommand = (Button source) -> {
toggleButton();
};
this.pos = pos;
this.label = label;
this.button = new Button(label);
this.button.setFontSize(fontSize);
val = pos;
createNotTakenButton(new Vector3f(170 * app.getResolutionFactor(), 250 * app.getResolutionFactor(), 0));
}
@Override
public void show() {
super.show();
float x = ((2 + (4 * pos)) * horitontal_step) - ((0.33333333f * pos) * container.getPreferredSize().x);
float y = 6 * vertical_step;
container.setLocalTranslation(x, y, 0);
}
@Override
public void hide() {
super.hide();
}
public void setTaken(boolean isTaken, boolean self, String name) {
taken = isTaken;
if(isTaken) {
createTakenButton(new Vector3f(170 * app.getResolutionFactor(), 250 * app.getResolutionFactor(), 0), self);
button.setText(label + "\n\n" + name);
} else {
createNotTakenButton(new Vector3f(170 * app.getResolutionFactor(), 250 * app.getResolutionFactor(), 0));
button.setText(label);
}
}
protected void createNotTakenButton(Vector3f size) {
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
if(button.getClickCommands() != null) {
button.removeClickCommands(clickCommand);
}
button.addClickCommands(clickCommand);
QuadBackgroundComponent background = new QuadBackgroundComponent(COLOR_DEFAULT);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(COLOR_HOVER);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(COLOR_DEFAULT);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
container.addChild(button);
}
private void toggleButton() {
if(taken) {
app.getModelSyncronizer().unselectTsk();
}
else {
app.getModelSyncronizer().selectTsk(Color.values()[val]);
}
}
protected void createTakenButton(Vector3f size, boolean self) {
ColorRGBA color_a;
ColorRGBA color_b;
if(button.getClickCommands() != null) {
button.removeClickCommands(clickCommand);
}
if(!self) {
color_a = ColorRGBA.Red;
color_b = ColorRGBA.Red;
} else {
color_a = ColorRGBA.Green;
color_b = ColorRGBA.Yellow;
button.addClickCommands(clickCommand);
}
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
QuadBackgroundComponent background = new QuadBackgroundComponent(color_a);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(color_b);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(color_a);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
});
container.addChild(button);
}
}

View File

@@ -1,138 +0,0 @@
package pp.mdga.client.dialog;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.server.MdgaServer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* The {@code NetworkDialog} class serves as an abstract base class for dialogs
* that involve network-related functionalities, such as connecting to a server or hosting a game.
* It provides methods for initializing, connecting to, and managing a network server.
*/
public abstract class NetworkDialog extends Dialog {
private NetworkSupport network;
private String hostname;
private int portNumber;
private Future<Object> connectionFuture;
private MdgaServer serverInstance;
private Thread serverThread;
/**
* Constructs a {@code NetworkDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param network The network support instance for managing network interactions.
*/
public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
super(app, node);
this.network = network;
}
/**
* Sets the hostname for the network connection.
*
* @param hostname The hostname or IP address of the server.
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* Sets the port number for the network connection.
*
* @param portNumber The port number to use for the connection.
*/
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
/**
* Initializes the network connection using the current hostname and port number.
*
* @return {@code null} if successful, otherwise throws an exception.
*/
protected Object initNetwork() {
try {
this.network.initNetwork(this.hostname, this.portNumber);
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Starts the process of connecting to a server asynchronously.
*/
protected void connectServer() {
try {
connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
} catch (NumberFormatException var2) {
throw new NumberFormatException("Port must be a number");
}
}
/**
* Starts hosting a server in a separate thread.
*/
protected void startServer() {
serverThread = new Thread(() -> {
try {
serverInstance = new MdgaServer(portNumber);
serverInstance.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
serverThread.start();
}
/**
* Shuts down the hosted server and cleans up resources.
*/
public void shutdownServer() {
serverInstance.shutdown();
// Wait for the server to shut down
try {
serverThread.join(); // Wait for the server thread to finish
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Server shutdown successfully.");
}
/**
* Updates the state of the connection process.
*
* @param delta The time elapsed since the last update call.
*/
public void update(float delta) {
if (this.connectionFuture != null && this.connectionFuture.isDone()) {
try {
this.connectionFuture.get();
} catch (ExecutionException ignored) {
// TODO: implement
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* Retrieves the {@code NetworkSupport} instance associated with this dialog.
*
* @return The {@code NetworkSupport} instance.
*/
public NetworkSupport getNetwork() {
return network;
}
}

View File

@@ -0,0 +1,15 @@
package pp.mdga.client.dialog;
import java.util.function.Consumer;
public class PercentRunnable {
private final Consumer<Float> action;
public PercentRunnable(Consumer<Float> action) {
this.action = action;
}
public void run(float percent) {
action.accept(percent);
}
}

View File

@@ -0,0 +1,78 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.IconComponent;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
public class SettingsButtonDialog extends Dialog {
private IconComponent icon;
public SettingsButtonDialog(MdgaApp app, Node node, String label, Runnable action) {
super(app, node);
icon = new IconComponent("zahnrad.png");
icon.setIconScale(0.1f * app.getResolutionFactor());
createButton(label, action, new Vector3f(60 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
}
@Override
public void show() {
super.show();
float x = (15.5f * horitontal_step) - container.getPreferredSize().x;
float y = 8.5f * vertical_step;
container.setLocalTranslation(x, y, 0);
}
@Override
public void hide() {
super.hide();
}
@Override
protected void createButton(String label, Runnable action, Vector3f size) {
Button button = new Button(label);
button.addClickCommands(source -> action.run());
button.setFontSize(fontSize);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setPreferredSize(size);
button.setTextHAlignment(HAlignment.Center);
button.setTextVAlignment(VAlignment.Center);
QuadBackgroundComponent background = new QuadBackgroundComponent(COLOR_DEFAULT);
background.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
button.setBackground(background);
button.setIcon(icon);
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOn, (source) -> {
QuadBackgroundComponent hoverBackground = new QuadBackgroundComponent(COLOR_HOVER);
hoverBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(hoverBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setIcon(icon);
});
button.addCommands(com.simsilica.lemur.Button.ButtonAction.HighlightOff, (source) -> {
QuadBackgroundComponent normalBackground = new QuadBackgroundComponent(COLOR_DEFAULT);
normalBackground.setMargin(5 * app.getResolutionFactor(), 5 * app.getResolutionFactor());
source.setBackground(normalBackground);
button.setHighlightColor(ColorRGBA.White);
button.setColor(ColorRGBA.Black);
button.setIcon(icon);
});
container.addChild(button);
}
}

View File

@@ -1,65 +1,112 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.Texture;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.component.SpringGridLayout;
import com.simsilica.lemur.event.CursorListener;
import com.simsilica.lemur.event.CursorMotionEvent;
import com.simsilica.lemur.event.MouseEventControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import com.simsilica.lemur.*;
import com.simsilica.lemur.style.*;
import java.util.HashMap;
/**
* The {@code SettingsDialog} class represents a dialog for navigating to various settings sections,
* such as video and audio settings, or returning to the previous view.
*/
public class SettingsDialog extends Dialog {
private MenuButton videoButton;
private MenuButton audioButton;
private MenuButton backButton;
private TextField nameInput;
private final MdgaView view;
private HashMap<Slider, PercentRunnable> map = new HashMap<Slider, PercentRunnable>();
private HashMap<Slider, GetPercentRunnable> map2 = new HashMap<Slider, GetPercentRunnable>();
/**
* Constructs a {@code SettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the settings dialog.
*/
public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
public SettingsDialog(MdgaApp app, Node node, String path) {
super(app, node);
this.view = view;
QuadBackgroundComponent quad1 = new QuadBackgroundComponent(ColorRGBA.Gray);
quad1.setMargin(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor());
container.setBackground(quad1);
videoButton = new MenuButton(app, node, view::enterVideoSettings, "Video");
audioButton = new MenuButton(app, node, view::enterAudioSettings, "Audio");
backButton = new MenuButton(app, node, view::leaveSettings, "Zurück");
Texture texture = app.getAssetManager().loadTexture(path);
float offset = 2.8f;
QuadBackgroundComponent b = new QuadBackgroundComponent(texture);
videoButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
Panel imagePanel = new Panel();
imagePanel.setBackground(b);
audioButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
container.addChild(imagePanel).setPreferredSize(new Vector3f((texture.getImage().getWidth() / 2) * app.getResolutionFactor(), (texture.getImage().getHeight() / 2) * app.getResolutionFactor(), 0));
backButton.setPos(new Vector2f(0, 1.8f));
//abstandshalter
container.addChild(new Panel(80 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
}
/**
* Called when the dialog is shown. Displays all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onShow() {
videoButton.show();
audioButton.show();
backButton.show();
public void show() {
super.show();
container.setLocalTranslation(
app.getCamera().getWidth() / 2 - container.getPreferredSize().x / 2,
app.getCamera().getHeight() / 2 + container.getPreferredSize().y / 2,
0
);
}
/**
* Called when the dialog is hidden. Hides all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onHide() {
videoButton.hide();
audioButton.hide();
backButton.hide();
public void hide() {
super.hide();
}
public void addButton(String label, Runnable action, Vector3f size) {
createButton(label, action, size);
}
public void addSlider(String label, PercentRunnable action, GetPercentRunnable action2, Vector3f size, int start) {
Container subContainer = new Container(new SpringGridLayout(Axis.X, Axis.Y));
Slider slider = new Slider("slider");
QuadBackgroundComponent background = new QuadBackgroundComponent(ColorRGBA.DarkGray);
slider.setBackground(background);
slider.setPreferredSize(size);
slider.setModel(new DefaultRangedValueModel(0, 100, start));
slider.setPreferredSize(new Vector3f(150 * app.getResolutionFactor(), 30 * app.getResolutionFactor(), 0));
slider.getDecrementButton().setText(" - ");
slider.getIncrementButton().setText(" + ");
slider.getDecrementButton().setFontSize(25 * app.getResolutionFactor());
slider.getIncrementButton().setFontSize(25 * app.getResolutionFactor());
Label nameLabel = new Label(label);
nameLabel.setFontSize(fontSize);
nameLabel.setColor(ColorRGBA.Black);
nameLabel.setPreferredSize(new Vector3f(150 * app.getResolutionFactor(), 10 * app.getResolutionFactor(), 0));
subContainer.addChild(nameLabel);
subContainer.addChild(slider);
container.addChild(subContainer);
map.put(slider, action);
map2.put(slider, action2);
//abstandshalter
container.addChild(new Panel(20 * app.getResolutionFactor(), 10 * app.getResolutionFactor(), ColorRGBA.Gray));
}
public void initVolume() {
map2.forEach((slider, runnable) -> {
double val = (double) runnable.get();
slider.getModel().setPercent(val);
});
}
public void update() {
map.forEach((slider, runnable) -> {
float val = (float) slider.getModel().getPercent();
runnable.run(val);
});
}
}

View File

@@ -0,0 +1,28 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
public class SingleButtonLeftDialog extends Dialog {
public SingleButtonLeftDialog(MdgaApp app, Node node, String label, Runnable action) {
super(app, node);
createButton(label, action, new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
}
@Override
public void show() {
super.show();
float x = 2 * horitontal_step;
float y = 1.8f * vertical_step;
container.setLocalTranslation(x, y, 0);
}
@Override
public void hide() {
super.hide();
}
}

View File

@@ -0,0 +1,28 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
public class SingleButtonRightDialog extends Dialog {
public SingleButtonRightDialog(MdgaApp app, Node node, String label, Runnable action) {
super(app, node);
createButton(label, action, new Vector3f(170 * app.getResolutionFactor(), 60 * app.getResolutionFactor(), 0));
}
@Override
public void show() {
super.show();
float x = (14 * horitontal_step) - container.getPreferredSize().x;
float y = 1.8f * vertical_step;
container.setLocalTranslation(x, y, 0);
}
@Override
public void hide() {
super.hide();
}
}

View File

@@ -1,310 +1,86 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.Random;
/**
* The {@code StartDialog} class represents the initial dialog in the application,
* allowing the user to input their name, host or join a game, or exit the application.
*/
public class StartDialog extends Dialog {
private InputButton nameInput;
private TextField nameInput;
private MenuButton hostButton;
private MenuButton joinButton;
private MenuButton endButton;
private final MainView view;
/**
* Constructs a {@code StartDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public StartDialog(MdgaApp app, Node node, MainView view) {
public StartDialog(MdgaApp app, Node node) {
super(app, node);
this.view = view;
QuadBackgroundComponent quad1 = new QuadBackgroundComponent(ColorRGBA.Gray);
quad1.setMargin(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor());
container.setBackground(quad1);
nameInput = new InputButton(app, node, "Name: ", 16);
Texture texture = app.getAssetManager().loadTexture("mdga_logo.png");
hostButton = new MenuButton(app, node, () -> view.forward(true), "Spiel hosten");
joinButton = new MenuButton(app, node, () -> view.forward(false), "Spiel beitreten");
endButton = new MenuButton(app, node, app::stop, "Spiel beenden");
QuadBackgroundComponent b = new QuadBackgroundComponent(texture);
float offset = 2.8f;
Panel imagePanel = new Panel();
imagePanel.setBackground(b);
nameInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.15f;
container.addChild(imagePanel).setPreferredSize(new Vector3f(texture.getImage().getWidth() / 4, texture.getImage().getHeight() / 4, 0));
hostButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
joinButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
createTextField();
endButton.setPos(new Vector2f(0, 1.8f));
//abstandshalter
container.addChild(new Panel(100 * app.getResolutionFactor(), 50 * app.getResolutionFactor(), ColorRGBA.Gray));
}
/**
* Called when the dialog is shown. Displays the name input field and all buttons.
*/
@Override
protected void onShow() {
nameInput.show();
public void show() {
super.show();
hostButton.show();
joinButton.show();
endButton.show();
container.setLocalTranslation(
app.getCamera().getWidth() / 2 - container.getPreferredSize().x / 2,
app.getCamera().getHeight() / 2 + container.getPreferredSize().y / 2,
0
);
}
/**
* Called when the dialog is hidden. Hides the name input field and all buttons.
*/
@Override
protected void onHide() {
nameInput.hide();
hostButton.hide();
joinButton.hide();
endButton.hide();
public void hide() {
super.hide();
}
/**
* Updates the state of the name input field. This method is called periodically to synchronize the dialog state.
*/
public void update() {
nameInput.update();
public void addButton(String label, Runnable action, Vector3f size) {
createButton(label, action, size);
}
private void createTextField() {
Container subContainer = new Container(new SpringGridLayout(Axis.X, Axis.Y));
Label nameLabel = new Label("Name:\t");
nameLabel.setFontSize(fontSize);
nameLabel.setColor(ColorRGBA.Black);
nameInput = new TextField("");
nameInput.setColor(ColorRGBA.Black);
nameInput.setTextHAlignment(HAlignment.Left);
nameInput.setFontSize(fontSize);
nameInput.setSingleLine(true);
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(ColorRGBA.DarkGray);
nameInput.setBackground(grayBackground);
subContainer.addChild(nameLabel);
subContainer.addChild(nameInput);
container.addChild(subContainer);
}
/**
* Retrieves the name entered by the user. If no name is provided, a random name is generated.
*
* @return The user's name or a randomly generated name.
*/
public String getName() {
String name = nameInput.getString();
if (name == null || name.trim().isEmpty()) {
String[] names = {
"PixelPirat",
"NoobJäger",
"LagMeister",
"KnopfDrücker",
"SpawnCamper",
"AFKHeld",
"RageQuitter",
"GameOverPro",
"Checkpoint",
"RespawnHeld",
"Teebeutel",
"GlitchHexer",
"QuickScope",
"LootSammler",
"EpicLauch",
"KartoffelPro",
"StilleKlinge",
"TastenHeld",
"PixelKrieger",
"HacknSlash",
"JoystickJoe",
"SpawnFalle",
"OneHitWanda",
"CamperKing",
"GameGenie",
"HighPing",
"CheesePro",
"Speedy",
"GigaGamer",
"LevelNoob",
"SkillTobi",
"HeadshotMax",
"PentaPaul",
"CritKarl",
"ManaLeerer",
"Nachlader",
"ClutchKönig",
"FriendlyFe",
"ZonenHeld",
"SchleichKatze",
"ShotgunPro",
"SniperUdo",
"BossHunter",
"HeldenNoob",
"KillFranz",
"FragKarl",
"TeamNiete",
"LootPaul",
"UltraNoob",
"ProfiScout",
"PunkteKlaus",
"KrüppelKill",
"PixelNinja",
"NoobCrusher",
"LagBoss",
"SpawnKing",
"AFKSlayer",
"RespawnPro",
"Killjoy",
"GameBreaker",
"FastFingers",
"LootKing",
"QuickFlick",
"SilentShot",
"HackGod",
"GlitchHero",
"SpeedyBot",
"AimWizard",
"FragMaster",
"OneTapPro",
"KnifeLord",
"MetaHunter",
"PingWarrior",
"KeyBash",
"ClutchPro",
"ScopeBot",
"TrollMage",
"PowerLooter",
"TankHero",
"CampLord",
"SmurfSlayer",
"SkillThief",
"SniperGod",
"LevelHack",
"GhostAim",
"BossTamer",
"ShotgunJoe",
"AimRider",
"KillCount",
"PixelManiac",
"TrollOver",
"SneakPro",
"ReloadKing",
"SpawnTrap",
"LagLover",
"MetaHater",
"BoomMaker",
"WipeLord",
"CarryPro",
"ProBaiter",
"GameWarden",
"KartoffelKönig",
"SaufenderWolf",
"WurstGriller",
"Flitzekacke",
"BratwurstBub",
"Hoppeldoppels",
"BananenMensch",
"KlopapierGuru",
"SchnitzelKing",
"NerdNomade",
"Dönertänzer",
"GlitzerGurke",
"SchinkenShrek",
"KäseKalle",
"SchokoSchnecke",
"KeksKämpfer",
"QuarkPiraten",
"Müslimonster",
"KnuddelNase",
"FantaFighter",
"SchnapsSaurier",
"Wackelpudding",
"ZitronenZock",
"FettWurst",
"PlüschPanda",
"Zuckerschnur",
"FluffiKopf",
"DonutDöner",
"VollpfostenX",
"Waschlappen",
"Witzepumper",
"ToastTraum",
"FroschFighter",
"KrümelTiger",
"RegenWolke",
"PuddingPower",
"KoffeinKrieger",
"SpeckSchlumpf",
"SuperSuppe",
"BierBärchen",
"FischBär",
"Flauschi",
"Schokomonster",
"ChaosKäse",
"FlitzLappen",
"WurstWombat",
"KrümelMensch",
"PuddingBär",
"ZickZack",
"Schwabel",
"Fluffi",
"RülpsFrosch",
"PommesPapa",
"QuarkBär",
"KnusperKönig",
"ToastBrot",
"Ploppster",
"Schleimschwein",
"Äpfelchen",
"Knallbonbon",
"KaffeeKopf",
"WackelWurst",
"RennKeks",
"BröselBub",
"ZockerBrot",
"BierWurm",
"StinkFlummi",
"SchlumpfKing",
"PurzelBär",
"FlinkFluff",
"PloppPudel",
"Schnorchel",
"FliegenKopf",
"PixelPommes",
"SchwipsWürst",
"WutzBär",
"KnuddelKeks",
"FantaFlumm",
"ZockerKäse",
"LachHäufchen",
"GurkenGuru",
"PonySchnitzel",
"NudelNinja",
"VulkanKeks",
"WasserToast",
"MenschSalat",
"KampfKohl",
"SockenZirkus",
"SchwimmBärchen",
"TanzenderPudel",
"PizzamarktMensch",
"ZahnarztZocker",
"RollerRudi",
"PupsPilot",
"WitzigeZwiebel",
"Pillenschlucker",
"ZwiebelReiter",
"HüpfenderKaktus",
"AsteroidenAlf",
"ChaosKarotte",
"WolkenFurz",
"Krümelmonster",
"WackelBiene",
};
Random random = new Random();
name = names[random.nextInt(names.length)];
}
return name;
return nameInput.getText();
}
}

View File

@@ -1,144 +0,0 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import java.util.prefs.Preferences;
/**
* The {@code VideoSettingsDialog} class represents a dialog for configuring video settings,
* such as resolution and fullscreen mode. It also provides an option to restart the application
* when certain settings are changed.
*/
public class VideoSettingsDialog extends Dialog {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
private ButtonRight fullscreenButton;
private MenuButton backButton;
private ButtonRight restartButton;
private ButtonLeft hdButton9;
private ButtonLeft fullHdButton9;
private ButtonLeft wqhdButton9;
private ButtonRight hdButton10;
private ButtonRight fullHdButton10;
private ButtonRight wqhdButton10;
private final MdgaView view;
private boolean active = false;
/**
* Constructs a {@code VideoSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the video settings dialog.
*/
public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
this.view = view;
backButton = new MenuButton(app, node, view::leaveVideoSettings, "Zurück");
restartButton = new ButtonRight(app, node, MdgaApp::restartApp, "Neustart", 1);
fullscreenButton = new ButtonRight(app, node, () -> updateResolution(0, 0, 0, true), "Vollbild", 1);
hdButton9 = new ButtonLeft(app, node, () -> updateResolution(1280, 720, 1.0f, false), "hd 16:9", 10);
fullHdButton9 = new ButtonLeft(app, node, () -> updateResolution(1920, 1080, 2.25f, false), "full hd 16:9", 10);
wqhdButton9 = new ButtonLeft(app, node, () -> updateResolution(2560, 1440, 4.0f, false), "wqhd 16:9", 10);
hdButton10 = new ButtonRight(app, node, () -> updateResolution(1280, 800, 1.0f, false), "hd 16:10", 10);
fullHdButton10 = new ButtonRight(app, node, () -> updateResolution(1920, 1200, 2.25f, false), "full hd 16:10", 10);
wqhdButton10 = new ButtonRight(app, node, () -> updateResolution(2560, 1600, 4.0f, false), "wqhd 16:10", 10);
float offset = 2.8f;
hdButton9.setPos(new Vector2f(hdButton9.getPos().x, MenuButton.VERTICAL - offset));
hdButton10.setPos(new Vector2f(hdButton10.getPos().x, MenuButton.VERTICAL - offset));
fullscreenButton.setPos(new Vector2f(fullscreenButton.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
fullHdButton9.setPos(new Vector2f(fullHdButton9.getPos().x, MenuButton.VERTICAL - offset));
fullHdButton10.setPos(new Vector2f(fullHdButton10.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
wqhdButton9.setPos(new Vector2f(wqhdButton9.getPos().x, MenuButton.VERTICAL - offset));
wqhdButton10.setPos(new Vector2f(wqhdButton10.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays all buttons and marks the dialog as active.
*/
@Override
protected void onShow() {
active = true;
hdButton9.show();
fullHdButton9.show();
wqhdButton9.show();
hdButton10.show();
fullHdButton10.show();
wqhdButton10.show();
fullscreenButton.show();
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all buttons and marks the dialog as inactive.
*/
@Override
protected void onHide() {
active = false;
hdButton9.hide();
fullHdButton9.hide();
wqhdButton9.hide();
hdButton10.hide();
fullHdButton10.hide();
wqhdButton10.hide();
fullscreenButton.hide();
backButton.hide();
restartButton.hide();
}
/**
* Updates the dialog's state. This method can be used for periodic updates while the dialog is active.
*/
public void update() {
if (!active) {
return;
}
}
/**
* Updates the resolution settings and optionally triggers the restart button if changes are detected.
*
* @param width The width of the resolution.
* @param height The height of the resolution.
* @param imageFactor The scaling factor for the resolution.
* @param isFullscreen {@code true} if fullscreen mode is enabled, {@code false} otherwise.
*/
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if (width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
restartButton.show();
}
app.updateResolution(width, height, imageFactor, isFullscreen);
}
}

View File

@@ -1,304 +0,0 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import pp.mdga.client.animation.ZoomControl;
import pp.mdga.game.Color;
/**
* The {@code ActionTextHandler} class manages the display of animated and stylized text messages in the game's UI.
* It supports dynamic text creation with spacing, color, and effects, such as dice rolls, player actions, and rankings.
*/
class ActionTextHandler {
private Node root;
private BitmapFont font;
private AppSettings appSettings;
private int ranking;
float paddingRanked = 100;
/**
* Constructs an {@code ActionTextHandler}.
*
* @param guiNode The GUI node where the text messages will be displayed.
* @param assetManager The asset manager used to load fonts and other assets.
* @param appSettings The application settings for positioning and sizing.
*/
ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
root = new Node("actionTextRoot");
guiNode.attachChild(root);
root.setLocalTranslation(center(appSettings.getWidth(), appSettings.getHeight(), Vector3f.ZERO));
font = assetManager.loadFont("Fonts/Gunplay.fnt");
this.appSettings = appSettings;
ranking = 0;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and colors for each segment of the text.
*
* @param textArr An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param colorArr An array of {@code ColorRGBA} representing the color for each string in {@code textArr}.
* @return A {@code Node} containing the styled text with spacing and color applied.
* @throws RuntimeException if the lengths of {@code textArr} and {@code colorArr} do not match.
*/
private Node createTextWithSpacing(String[] textArr, float spacing, float size, ColorRGBA[] colorArr) {
if (textArr.length != colorArr.length) throw new RuntimeException("text and color are not the same length");
Node textNode = new Node("TextWithSpacing");
Node center = new Node();
float xOffset = 0;
for (int i = 0; i < textArr.length; i++) {
String text = textArr[i];
ColorRGBA color = colorArr[i];
for (char c : text.toCharArray()) {
BitmapText letter = new BitmapText(font);
letter.setColor(color);
letter.setSize(size);
letter.setText(Character.toString(c));
letter.setLocalTranslation(xOffset, letter.getHeight() / 2, 0);
center.attachChild(letter);
xOffset += letter.getLineWidth() + spacing;
}
}
center.setLocalTranslation(new Vector3f(-xOffset / 2, 0, 0));
textNode.attachChild(center);
return textNode;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and a single color.
*
* @param text The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @return A {@code Node} containing the styled text.
*/
private Node createTextWithSpacing(String text, float spacing, float size, ColorRGBA color) {
return createTextWithSpacing(new String[]{text}, spacing, size, new ColorRGBA[]{color});
}
/**
* Calculates the center position of a rectangle given its width, height, and an origin position.
*
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
private Vector3f center(float width, float height, Vector3f pos) {
return new Vector3f(pos.x + width / 2, pos.y + height / 2, 0);
}
/**
* Creates and positions a single-line text at the top of the screen with a specified vertical offset.
*
* @param name The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the top.
*/
private Node createTopText(String name, float spacing, float size, ColorRGBA color, float top) {
return createTopText(new String[]{name}, spacing, size, new ColorRGBA[]{color}, top);
}
/**
* Creates and positions multi-line text at the top of the screen with specified vertical offset, spacing, and colors.
*
* @param name An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color An array of {@code ColorRGBA} representing the color for each string in {@code name}.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the top.
*/
private Node createTopText(String[] name, float spacing, float size, ColorRGBA color[], float top) {
Node text = createTextWithSpacing(name, spacing, size, color);
text.setLocalTranslation(0, (appSettings.getHeight() / 2f) * 0.8f - top, 0);
root.attachChild(text);
return text;
}
/**
* Calculates the center position of a rectangle with negative width offset.
*
* @param width The negative width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
private Vector3f centerText(float width, float height, Vector3f pos) {
return center(-width, height, pos);
}
/**
* Displays a message indicating the active player.
*
* @param name The name of the active player.
* @param color The color representing the player's team.
*/
void activePlayer(String name, Color color) {
createTopText(new String[]{name, " ist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player is active.
*
* @param color The color representing the player's team.
*/
void ownActive(Color color) {
createTopText(new String[]{"Du", " bist dran"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a dice roll result for a player.
*
* @param diceNum The number rolled on the dice.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
void diceNum(int diceNum, String name, Color color) {
createTopText(new String[]{name, " würfelt:"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0);
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 100);
}
/**
* Displays a dice roll result with a multiplier for a player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
void diceNumMult(int diceNum, int mult, String name, Color color) {
createTopText(new String[]{name, " würfelt:"}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0);
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 100);
}
/**
* Displays the dice roll result for the current player.
*
* @param diceNum The number rolled on the dice.
*/
void ownDice(int diceNum) {
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
}
/**
* Displays the dice roll result with a multiplier for the current player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
*/
void ownDiceMult(int diceNum, int mult) {
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum * mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White, ColorRGBA.Red}, 0);
}
/**
* Displays a message indicating that a specified player received a bonus card.
*
* @param name The name of the player who received the bonus card.
* @param color The color representing the player's team.
*/
void drawCard(String name, Color color) {
createTopText(new String[]{name, " erhält eine Bonuskarte"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player received a bonus card.
*
* @param color The color representing the player's team.
*/
void drawCardOwn(Color color) {
createTopText(new String[]{"Du", " erhälst eine Bonuskarte"}, 5, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that a specified player has completed their turn or action.
*
* @param name The name of the player who finished.
* @param color The color representing the player's team.
*/
void finishText(String name, Color color) {
createTopText(new String[]{name, " ist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player has completed their turn or action.
*
* @param color The color representing the player's team.
*/
void finishTextOwn(Color color) {
createTopText(new String[]{"Du", " bist fertig!"}, 7, 70, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Converts a player's team color to a corresponding {@code ColorRGBA}.
*
* @param color The player's team color.
* @return The corresponding {@code ColorRGBA}.
* @throws RuntimeException if the color is invalid.
*/
private ColorRGBA playerColorToColorRGBA(Color color) {
return switch (color) {
case ARMY -> ColorRGBA.Green;
case NAVY -> ColorRGBA.Blue;
case CYBER -> ColorRGBA.Orange;
case AIRFORCE -> ColorRGBA.Black;
default -> throw new RuntimeException("None is not valid");
};
}
/**
* Hides all text messages displayed by the handler and resets the ranking counter.
*/
void hide() {
ranking = 0;
root.detachAllChildren();
}
/**
* Displays a ranked dice roll result for a specified player.
*
* @param name The name of the player.
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResult(String name, Color color, int eye) {
createTopText(new String[]{name, ": " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
ranking++;
}
/**
* Displays a ranked dice roll result for the current player.
*
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResultOwn(Color color, int eye) {
createTopText(new String[]{"Du", ": " + eye}, 10, 90, new ColorRGBA[]{playerColorToColorRGBA(color), ColorRGBA.White}, paddingRanked * ranking);
ranking++;
}
/**
* Displays a message prompting the player to roll the dice.
*/
void diceNow() {
createTopText("Klicke zum Würfeln", 5, 80, ColorRGBA.White, 0);
}
}

View File

@@ -1,107 +1,23 @@
package pp.mdga.client.gui;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Sphere;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.board.OutlineControl;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
/**
* CardControl class extends OutlineControl to manage the visual representation
* and behavior of a card in the game.
*/
public class CardControl extends OutlineControl {
public class CardControl extends AbstractControl {
private static final ColorRGBA HIGHLIGHT_COLOR = ColorRGBA.Yellow;
private static final int HIGHLIGHT_WIDTH = 9;
public CardControl(){
private static final ColorRGBA HOVER_COLOR = ColorRGBA.Green;
private static final int HOVER_WIDTH = 12;
private static final ColorRGBA SELECT_COLOR = ColorRGBA.Blue;
private static final int SELECT_WIDTH = 13;
private Node root;
private BitmapText num;
/**
* Constructor for CardControl.
*
* @param app the application instance
* @param fpp the FilterPostProcessor instance
* @param cam the Camera instance
* @param root the root Node
*/
public CardControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, Node root) {
super(app, fpp, cam,
HIGHLIGHT_COLOR, HIGHLIGHT_WIDTH,
HOVER_COLOR, HOVER_WIDTH,
SELECT_COLOR, SELECT_WIDTH
);
this.root = root;
Node rootNum = createNum();
rootNum.setLocalTranslation(new Vector3f(0.35f, 0.8f, 0));
root.attachChild(rootNum);
}
/**
* Creates a Node containing a number and a circle geometry.
*
* @return the created Node
*/
private Node createNum() {
Node rootNum = new Node("root Num");
Geometry circle = new Geometry("circle", new Sphere(20, 20, 1));
circle.setLocalTranslation(new Vector3f(0.03f, 0.01f, 1));
circle.setLocalScale(0.2f);
Material mat = new Material(getApp().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
circle.setMaterial(mat);
// root.attachChild(circle);
BitmapFont guiFont = getApp().getAssetManager().loadFont("Fonts/Gunplay.fnt");
num = new BitmapText(guiFont);
num.setSize(0.3f);
num.setText("1");
num.setColor(ColorRGBA.White);
num.setLocalTranslation(-num.getLineWidth() / 2, num.getHeight() / 2, 2);
rootNum.attachChild(circle);
rootNum.attachChild(num);
return rootNum;
}
/**
* Sets the number displayed on the card.
*
* @param num the number to display
*/
public void setNumCard(int num) {
this.num.setText(String.valueOf(num));
}
/**
* Gets the root Node of the card.
*
* @return the root Node
*/
public Node getRoot() {
return root;
}
/**
* Initializes the spatial properties of the card.
*/
@Override
public void initSpatial() {
protected void controlUpdate(float tpf) {
}
private final static Vector3f HIGHLIGHT_Y = new Vector3f(0, 0.4f, 0);
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
}

View File

@@ -3,177 +3,75 @@
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.ComposeFilter;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import pp.mdga.client.Asset;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
/**
* CardLayer is an application state that manages the rendering and updating of card objects
* in a separate viewport with post-processing effects.
*/
public class CardLayer extends AbstractAppState {
public static final int SHADOWMAP_SIZE = 1024 * 8;
private Node root;
private Application app;
private boolean init;
private List<Spatial> cardBuffer;
private final FilterPostProcessor fpp;
private final Camera overlayCam;
private Texture2D backTexture;
private FXAAFilter fxaaFilter;
private ViewPort view;
private DirectionalLightShadowFilter dlsf;
DirectionalLight sun;
ComposeFilter compose;
/**
* Constructs a new CardLayer with the specified post-processor, camera, and background texture.
*
* @param fpp the FilterPostProcessor to use for post-processing effects
* @param overlayCam the Camera to use for the overlay
* @param backTexture the Texture2D to use as the background texture
*/
public CardLayer(FilterPostProcessor fpp, Camera overlayCam, Texture2D backTexture) {
this.overlayCam = overlayCam;
this.fpp = fpp;
this.backTexture = backTexture;
cardBuffer = new ArrayList<>();
public CardLayer() {
this.cardBuffer = new ArrayList<>();
init = false;
fxaaFilter = new FXAAFilter();
view = null;
dlsf = null;
sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(.5f, -.5f, -1));
compose = new ComposeFilter(backTexture);
root = new Node("Under gui viewport Root");
}
/**
* Initializes the CardLayer, setting up the viewport, filters, and lighting.
*
* @param stateManager the AppStateManager managing this state
* @param app the Application instance
*/
@Override
public void initialize(AppStateManager stateManager, Application app) {
public void initialize(AppStateManager stateManager, Application app ) {
this.app = app;
root = new Node("Under gui viewport Root");
view = app.getRenderManager().createMainView("Under gui ViewPort", overlayCam);
Camera originalCam = app.getCamera();
Camera cam = new Camera(originalCam.getWidth(), originalCam.getHeight());
cam.setParallelProjection(false);
cam.setFrustum(originalCam.getFrustumNear(), originalCam.getFrustumFar(), originalCam.getFrustumLeft(), originalCam.getFrustumRight(),originalCam.getFrustumTop(), originalCam.getFrustumBottom());
cam.setFov(originalCam.getFov());
cam.setLocation(new Vector3f(0, 0, 10));
cam.lookAt(new Vector3f(0,0,0), Vector3f.UNIT_Y);
ViewPort view = app.getRenderManager().createMainView("Under gui ViewPort", cam);
view.setEnabled(true);
view.setClearFlags(true, true, true);
view.setClearFlags(false, true, false);
view.attachScene(root);
fpp.setFrameBufferFormat(Image.Format.RGBA8);
fpp.addFilter(compose);
fpp.addFilter(fxaaFilter);
root.addLight(sun);
if(!init) init = true;
dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
dlsf.setLight(sun);
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
dlsf.setShadowIntensity(.5f);
fpp.addFilter(dlsf);
view.addProcessor(fpp);
if (!init) init = true;
}
/**
* Shuts down the CardLayer, removing filters, lights, and clearing buffers.
*/
public void shutdown() {
fpp.removeFilter(dlsf);
dlsf = null;
root.removeLight(sun);
fpp.removeFilter(fxaaFilter);
view.detachScene(root);
cardBuffer.clear();
root.detachAllChildren();
}
/**
* Renders the CardLayer, updating the geometric state of the root node.
*
* @param rm the RenderManager handling the rendering
*/
@Override
public void render(RenderManager rm) {
root.updateGeometricState();
}
/**
* Updates the CardLayer, attaching buffered cards to the root node and updating its logical state.
*
* @param tpf time per frame
*/
@Override
public void update(float tpf) {
public void update( float tpf ) {
root.updateLogicalState(tpf);
if (init && !cardBuffer.isEmpty()) {
for (Spatial spatial : cardBuffer) {
for(Spatial spatial : cardBuffer){
root.attachChild(spatial);
}
cardBuffer.clear();
}
root.updateLogicalState(tpf);
}
/**
* Adds a spatial card to the CardLayer.
*
* @param card the Spatial card to add
*/
public void addSpatial(Spatial card) {
if (root == null) cardBuffer.add(card);
else root.attachChild(card);
public void addCard(Spatial card){
cardBuffer.add(card);
}
/**
* Deletes a spatial card from the CardLayer.
*
* @param spatial the Spatial card to delete
*/
public void deleteSpatial(Spatial spatial) {
root.detachChild(spatial);
}
/**
* Gets the overlay camera used by the CardLayer.
*
* @return the overlay camera
*/
public Camera getOverlayCam() {
return overlayCam;
}
/**
* Gets the root node of the CardLayer.
*
* @return the root node
*/
public Node getRootNode() {
return root;
}
}
}

View File

@@ -1,361 +0,0 @@
package pp.mdga.client.gui;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.Texture2D;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.animation.SymbolControl;
import pp.mdga.game.BonusCard;
import java.util.*;
/**
* Handles the card layer in the GUI, including card management and dice control.
*/
public class CardLayerHandler {
private static final Vector3f START = new Vector3f(-1.8f, -3.5f, 0);
private static final Vector3f MARGIN = new Vector3f(1.8f, 0, 0);
private static final float CARDLAYER_CAMERA_ZOOM = 4;
private final MdgaApp app;
private final FilterPostProcessor fpp;
private final Texture2D backTexture;
private Camera cardLayerCamera;
private CardLayer cardLayer;
private DiceControl diceControl;
private final Map<BonusCard, CardControl> bonusCardControlMap = new HashMap<>();
private final List<BonusCard> cardOrder = new ArrayList<>();
private final Map<BonusCard, Integer> bonusCardIntegerMap = new HashMap<>();
private final Set<CardControl> selectableCards = new HashSet<>();
private BonusCard cardSelect = null;
private boolean show = false;
/**
* Constructs a CardLayerHandler.
*
* @param app the application instance
* @param backTexture the background texture
*/
public CardLayerHandler(MdgaApp app, Texture2D backTexture) {
this.app = app;
this.fpp = new FilterPostProcessor(app.getAssetManager());
this.backTexture = backTexture;
}
/**
* Initializes the card layer and dice control.
*/
public void init() {
cardLayerCamera = createOverlayCam();
cardLayer = new CardLayer(fpp, cardLayerCamera, backTexture);
app.getStateManager().attach(cardLayer);
diceControl = new DiceControl(app.getAssetManager());
diceControl.create(new Vector3f(0, 0, 0), 1f, false);
}
/**
* Shuts down the card layer and clears selectable cards.
*/
public void shutdown() {
clearSelectableCards();
if (cardLayer != null) {
cardLayer.shutdown();
app.getStateManager().detach(cardLayer);
}
cardLayer = null;
}
/**
* Rolls the dice with a specified number and action to perform after rolling.
*
* @param rollNum the number to roll (must be between 1 and 6)
* @param actionAfter the action to perform after rolling
*/
public void rollDice(int rollNum, Runnable actionAfter) {
if (!(1 <= rollNum && rollNum <= 6)) throw new RuntimeException("rollNum is not in the range [1,6]");
diceControl.rollDice(rollNum, actionAfter);
}
/**
* Shows the dice on the card layer.
*/
public void showDice() {
if (show) return;
show = true;
cardLayer.addSpatial(diceControl.getSpatial());
diceControl.spin();
}
/**
* Hides the dice from the card layer.
*/
public void hideDice() {
show = false;
diceControl.hide();
}
/**
* Adds a card to the card layer.
*
* @param card the card to add
*/
public void addCard(BonusCard card) {
if (card == BonusCard.HIDDEN) throw new RuntimeException("Can't add hidden card to GUI");
if (!bonusCardControlMap.containsKey(card)) {
cardOrder.add(card);
}
int newNum = bonusCardIntegerMap.getOrDefault(card, 0) + 1;
bonusCardIntegerMap.put(card, newNum);
updateCard();
}
/**
* Removes a card from the card layer.
*
* @param card the card to remove
*/
public void removeCard(BonusCard card) {
if (bonusCardControlMap.containsKey(card)) {
bonusCardIntegerMap.put(card, bonusCardIntegerMap.get(card) - 1);
if (bonusCardIntegerMap.get(card) <= 0) {
bonusCardIntegerMap.remove(card);
cardOrder.remove(card);
}
updateCard();
} else throw new RuntimeException("card is not in bonusCardControlMap");
}
/**
* Clears all selectable cards.
*/
public void clearSelectableCards() {
for (CardControl control : selectableCards) {
control.selectableOff();
}
selectableCards.clear();
cardSelect = null;
}
/**
* Updates the card layer with the current cards.
*/
private void updateCard() {
for (BonusCard card : bonusCardControlMap.keySet()) {
CardControl control = bonusCardControlMap.get(card);
cardLayer.deleteSpatial(control.getRoot());
}
bonusCardControlMap.clear();
for (int i = 0; i < cardOrder.size(); i++) {
BonusCard card = cardOrder.get(i);
CardControl control = createCard(bonusToAsset(card), nextPos(i));
control.setNumCard(bonusCardIntegerMap.get(card));
cardLayer.addSpatial(control.getRoot());
bonusCardControlMap.put(card, control);
}
}
/**
* Sets the selectable cards.
*
* @param select the list of cards to set as selectable
*/
public void setSelectableCards(List<BonusCard> select) {
for (BonusCard card : select) {
selectableCards.add(bonusCardControlMap.get(card));
}
for (CardControl control : selectableCards) {
control.selectableOn();
}
}
/**
* Selects a card control.
*
* @param cardControl the card control to select
*/
public void selectCard(CardControl cardControl) {
if (cardControl.isSelected()) {
cardControl.selectOff();
cardSelect = null;
} else {
for (CardControl control : selectableCards) {
control.selectOff();
}
cardControl.selectOn();
cardSelect = getKeyByValue(bonusCardControlMap, cardControl);
}
app.getModelSynchronize().selectCard(cardSelect);
}
/**
* Gets the card layer camera.
*
* @return the card layer camera
*/
public Camera getCardLayerCamera() {
return cardLayerCamera;
}
/**
* Adds a shield symbol to the card layer.
*/
public void shield() {
SymbolControl control = createSymbol(Asset.shieldSymbol);
cardLayer.addSpatial(control.getSpatial());
control.shield();
}
/**
* Adds a swap symbol to the card layer.
*/
public void swap() {
SymbolControl control = createSymbol(Asset.swapSymbol);
cardLayer.addSpatial(control.getSpatial());
control.swap();
}
/**
* Adds a turbo symbol to the card layer.
*/
public void turbo() {
SymbolControl control = createSymbol(Asset.turboSymbol);
cardLayer.addSpatial(control.getSpatial());
control.turbo();
}
/**
* Converts a bonus card to its corresponding asset.
*
* @param card the bonus card
* @return the corresponding asset
*/
private Asset bonusToAsset(BonusCard card) {
return switch (card) {
case TURBO -> Asset.turboCard;
case SHIELD -> Asset.shieldCard;
case SWAP -> Asset.swapCard;
case HIDDEN -> throw new RuntimeException("HIDDEN is not allowed in GUI");
};
}
/**
* Calculates the next position for a card.
*
* @param i the index of the card
* @return the next position vector
*/
private Vector3f nextPos(int i) {
return START.add(MARGIN.mult(i));
}
/**
* Creates an overlay camera for the card layer.
*
* @return the created overlay camera
*/
private Camera createOverlayCam() {
Camera originalCam = app.getCamera();
Camera overlayCam = new Camera(originalCam.getWidth(), originalCam.getHeight());
overlayCam.setParallelProjection(true);
float aspect = (float) originalCam.getWidth() / originalCam.getHeight();
float size = CARDLAYER_CAMERA_ZOOM;
overlayCam.setFrustum(-1000, 1000, -aspect * size, aspect * size, size, -size);
overlayCam.setLocation(new Vector3f(0, 0, 10));
overlayCam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
return overlayCam;
}
/**
* Gets the key associated with a value in a map.
*
* @param map the map to search
* @param value the value to find the key for
* @param <K> the type of keys in the map
* @param <V> the type of values in the map
* @return the key associated with the value, or null if not found
*/
private <K, V> K getKeyByValue(Map<K, V> map, V value) {
for (Map.Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
return entry.getKey();
}
}
return null;
}
/**
* Test method to add sample cards to the card layer.
*/
public void test() {
addCard(BonusCard.SHIELD);
addCard(BonusCard.SHIELD);
addCard(BonusCard.TURBO);
addCard(BonusCard.SWAP);
}
/**
* Creates a card control for a given asset and position.
*
* @param card the asset representing the card
* @param pos the position to place the card
* @return the created card control
*/
private CardControl createCard(Asset card, Vector3f pos) {
Node rootCard = new Node("Root Card");
Spatial spatial = app.getAssetManager().loadModel(card.getModelPath());
rootCard.attachChild(spatial);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(card.getDiffPath()));
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
rootCard.setLocalTranslation(pos);
spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
CardControl control = new CardControl(app, fpp, cardLayer.getOverlayCam(), rootCard);
spatial.addControl(control);
return control;
}
/**
* Creates a symbol control for a given asset.
*
* @param asset the asset representing the symbol
* @return the created symbol control
*/
private SymbolControl createSymbol(Asset asset) {
Spatial spatial = app.getAssetManager().loadModel(asset.getModelPath());
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(asset.getDiffPath()));
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
SymbolControl control = new SymbolControl();
spatial.addControl(control);
return control;
}
/**
* Gets the card layer.
*
* @return the card layer
*/
public CardLayer getCardLayer() {
return cardLayer;
}
}

View File

@@ -1,230 +0,0 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import static com.jme3.material.Materials.LIGHTING;
import static com.jme3.material.Materials.UNSHADED;
/**
* DiceControl class handles the rolling and spinning behavior of a dice in the game.
*/
public class DiceControl extends AbstractControl {
private Quaternion targetRotation;
private final Vector3f angularVelocity = new Vector3f();
private float deceleration = 1f;
private float timeElapsed = 0.0f;
private float rollDuration = 1f;
private static final int ANGULAR_MIN = 5;
private static final int ANGULAR_MAX = 15;
private static final int ANGULAR_SPIN = 10;
private boolean isRolling = false;
private boolean slerp = false;
private boolean spin = false;
private final AssetManager assetManager;
private Runnable actionAfter;
/**
* Constructor for DiceControl.
*
* @param assetManager the asset manager to load models and textures
*/
public DiceControl(AssetManager assetManager) {
this.assetManager = assetManager;
}
/**
* Updates the control each frame.
*
* @param tpf time per frame
*/
@Override
protected void controlUpdate(float tpf) {
float clampedTpf = Math.min(tpf, 0.05f); // Max 50 ms per frame
if (isRolling) {
if (!slerp) {
// Apply rotational velocity to the dice
spinWithAngularVelocity(clampedTpf);
// Gradually reduce rotational velocity (simulate deceleration)
angularVelocity.subtractLocal(
angularVelocity.mult(deceleration * clampedTpf)
);
// Stop rolling when angular velocity is close to zero
if (angularVelocity.lengthSquared() <= 3f || MdgaApp.DEBUG_MULTIPLIER == 0) {
slerp = true;
}
} else {
timeElapsed += clampedTpf * rollDuration;
if (timeElapsed > 1.0f) timeElapsed = 1.0f;
Quaternion interpolated = spatial.getLocalRotation().clone();
interpolated.slerp(targetRotation, timeElapsed);
spatial.setLocalRotation(interpolated);
// Stop rolling once duration is complete
if (timeElapsed >= 1.0f * MdgaApp.DEBUG_MULTIPLIER) {
isRolling = false;
slerp = false;
actionAfter.run();
}
}
} else if (spin) {
spinWithAngularVelocity(clampedTpf);
}
}
/**
* Applies rotational velocity to the dice.
*
* @param tpf time per frame
*/
private void spinWithAngularVelocity(float tpf) {
Quaternion currentRotation = spatial.getLocalRotation();
Quaternion deltaRotation = new Quaternion();
deltaRotation.fromAngles(
angularVelocity.x * tpf,
angularVelocity.y * tpf,
angularVelocity.z * tpf
);
spatial.setLocalRotation(currentRotation.mult(deltaRotation));
}
/**
* Renders the control.
*
* @param rm the render manager
* @param vp the viewport
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
/**
* Initiates the dice roll.
*
* @param diceNum the number on the dice to roll to
* @param actionAfter the action to perform after the roll
*/
public void rollDice(int diceNum, Runnable actionAfter) {
if (isRolling) return;
spin = false;
slerp = false;
timeElapsed = 0;
this.actionAfter = actionAfter;
angularVelocity.set(
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN, ANGULAR_MAX)
);
targetRotation = getRotationForDiceNum(diceNum);
isRolling = true;
}
/**
* Gets the target rotation for a given dice number.
*
* @param diceNum the number on the dice
* @return the target rotation as a Quaternion
*/
private Quaternion getRotationForDiceNum(int diceNum) {
return switch (diceNum) {
case 1 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_X);
case 2 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 3 -> new Quaternion().fromAngleAxis((float) (0 * (Math.PI / 2)), Vector3f.UNIT_X);
case 4 -> new Quaternion().fromAngleAxis((float) (2 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 5 -> new Quaternion().fromAngleAxis((float) (3 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 6 -> new Quaternion().fromAngleAxis((float) (3 * (Math.PI / 2)), Vector3f.UNIT_X);
default -> throw new IllegalArgumentException("Invalid dice number: " + diceNum);
};
}
/**
* Linear interpolation function.
*
* @param t the interpolation factor
* @return the interpolated value
*/
public static float lerp(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
/**
* Sets a random rotation for the dice.
*/
public void randomRotation() {
Quaternion randomRotation = new Quaternion();
randomRotation.fromAngles(
FastMath.nextRandomFloat() * FastMath.TWO_PI, // Random X rotation
FastMath.nextRandomFloat() * FastMath.TWO_PI, // Random Y rotation
FastMath.nextRandomFloat() * FastMath.TWO_PI // Random Z rotation
);
spatial.setLocalRotation(randomRotation);
}
/**
* Initiates the dice spin.
*/
public void spin() {
angularVelocity.set(ANGULAR_SPIN, ANGULAR_SPIN, ANGULAR_SPIN);
spin = true;
}
/**
* Hides the dice by removing it from the parent node.
*/
public void hide() {
spatial.removeFromParent();
spin = false;
isRolling = false;
slerp = false;
}
/**
* Creates the dice model and sets its initial properties.
*
* @param pos the position to place the dice
* @param scale the scale of the dice
* @param shadow whether the dice should cast and receive shadows
*/
public void create(Vector3f pos, float scale, boolean shadow) {
Spatial spatial = assetManager.loadModel(Asset.dice.getModelPath());
Material mat;
if (shadow) {
mat = new Material(assetManager, LIGHTING);
mat.setTexture("DiffuseMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
} else {
mat = new Material(assetManager, UNSHADED);
mat.setTexture("ColorMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
}
spatial.setMaterial(mat);
spatial.setLocalScale(scale);
spatial.setLocalTranslation(pos);
spatial.rotate((float) Math.toRadians(90), (float) Math.toRadians(180), (float) Math.toRadians(180));
spatial.addControl(this);
}
/**
* Sets the position of the dice.
*
* @param pos the new position
*/
public void setPos(Vector3f pos) {
spatial.setLocalTranslation(pos);
}
}

View File

@@ -1,291 +1,70 @@
package pp.mdga.client.gui;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Handles the GUI elements and interactions for the game.
*/
public class GuiHandler {
private final MdgaApp app;
private final CardLayerHandler cardLayerHandler;
private final PlayerNameHandler playerNameHandler;
private final ActionTextHandler actionTextHandler;
private Color ownColor;
private FrameBuffer backFrameBuffer;
private MdgaApp app;
private CardLayer cardLayer;
private Map<UUID, CardControl> ownCardsMap;
/**
* Constructs a new GuiHandler.
*
* @param app the application instance
* @param guiNode the GUI node
*/
public GuiHandler(MdgaApp app, Node guiNode) {
private static final Vector3f START = new Vector3f(-3,-3,0);
private static final Vector3f MARGIN = new Vector3f(2.5f,0,0);
public GuiHandler(MdgaApp app) {
this.app = app;
this.ownColor = ownColor;
backFrameBuffer = new FrameBuffer(app.getCamera().getWidth(), app.getCamera().getHeight(), 1);
Texture2D backTexture = new Texture2D(app.getCamera().getWidth(), app.getCamera().getHeight(), Image.Format.RGBA8);
backFrameBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.Depth));
backFrameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(backTexture));
cardLayerHandler = new CardLayerHandler(app, backTexture);
playerNameHandler = new PlayerNameHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
actionTextHandler = new ActionTextHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
}
/**
* Initializes the GUI handler with the player's color.
*
* @param ownColor the player's color
*/
public void init(Color ownColor) {
cardLayerHandler.init();
playerNameHandler.show();
this.ownColor = ownColor;
app.getViewPort().setOutputFrameBuffer(backFrameBuffer);
public void init(){
cardLayer = new CardLayer();
app.getStateManager().attach(cardLayer);
ownCardsMap = new HashMap<>();
addCard(BonusCard.SHIELD, UUID.randomUUID());
addCard(BonusCard.TURBO, UUID.randomUUID());
addCard(BonusCard.SWAP, UUID.randomUUID());
}
/**
* Shuts down the GUI handler.
*/
public void shutdown() {
cardLayerHandler.shutdown();
app.getViewPort().setOutputFrameBuffer(null);
private Asset bonusToAsset(BonusCard card){
return switch (card){
case TURBO -> Asset.turboCard;
case SHIELD -> Asset.shieldCard;
case SWAP -> Asset.swapCard;
case HIDDEN -> throw new RuntimeException("HIDDEN is not allowed in GUI");
};
}
/**
* Rolls the dice and handles the result.
*
* @param rollNum the number rolled
* @param mult the multiplier
*/
public void rollDice(int rollNum, int mult) {
cardLayerHandler.rollDice(rollNum, () -> {
if (mult == -1) actionTextHandler.ownDice(rollNum);
else actionTextHandler.ownDiceMult(rollNum, mult);
hideDice();
app.getModelSynchronize().animationEnd();
app.getModelSynchronize().animationEnd();
});
public void addCard(BonusCard card, UUID uuid) {
CardControl control = createCard(bonusToAsset(card), nextPos());
ownCardsMap.put(uuid, control);
cardLayer.addCard(control.getSpatial());
}
/**
* Shows the rolled dice with a multiplier for a specific player.
*
* @param rollNum the number rolled
* @param mult the multiplier
* @param color the player's color
*/
public void showRolledDiceMult(int rollNum, int mult, Color color) {
String name = playerNameHandler.getName(color);
if (mult == -1) actionTextHandler.diceNum(rollNum, name, color);
else actionTextHandler.diceNumMult(rollNum, mult, name, color);
private Vector3f nextPos() {
return START.add(MARGIN.mult(ownCardsMap.size()));
}
/**
* Shows the rolled dice for a specific player.
*
* @param rollNum the number rolled
* @param color the player's color
*/
public void showRolledDice(int rollNum, Color color) {
actionTextHandler.diceNum(rollNum, playerNameHandler.getName(color), color);
}
/**
* Displays the dice on the screen.
*/
public void showDice() {
cardLayerHandler.showDice();
actionTextHandler.diceNow();
}
/**
* Hides the dice from the screen.
*/
public void hideDice() {
cardLayerHandler.hideDice();
}
/**
* Adds a card to the player's hand.
*
* @param card the card to add
*/
public void addCardOwn(BonusCard card) {
cardLayerHandler.addCard(card);
playerNameHandler.addCard(ownColor);
actionTextHandler.drawCardOwn(ownColor);
}
/**
* Plays a card from the player's hand.
*
* @param card the card to play
*/
public void playCardOwn(BonusCard card) {
getEffectByCard(card);
cardLayerHandler.removeCard(card);
playerNameHandler.removeCard(ownColor);
}
/**
* Plays a card from an enemy player's hand.
*
* @param color the enemy player's color
* @param card the card to play
*/
public void playCardEnemy(Color color, BonusCard card) {
getEffectByCard(card);
playerNameHandler.removeCard(color);
}
/**
* Gets the effect of a card and applies it.
*
* @param bonus the card to get the effect from
*/
private void getEffectByCard(BonusCard bonus) {
switch (bonus) {
case SWAP -> swap();
case TURBO -> turbo();
case SHIELD -> shield();
default -> throw new RuntimeException("invalid card");
}
}
/**
* Clears all selectable cards.
*/
public void clearSelectableCards() {
cardLayerHandler.clearSelectableCards();
}
/**
* Sets the selectable cards.
*
* @param select the list of selectable cards
*/
public void setSelectableCards(List<BonusCard> select) {
cardLayerHandler.setSelectableCards(select);
}
/**
* Selects a card.
*
* @param cardControl the card control to select
*/
public void selectCard(CardControl cardControl) {
cardLayerHandler.selectCard(cardControl);
}
/**
* Gets the camera for the card layer.
*
* @return the card layer camera
*/
public Camera getCardLayerCamera() {
return cardLayerHandler.getCardLayerCamera();
}
/**
* Gets the root node for the card layer.
*
* @return the card layer root node
*/
public Node getCardLayerRootNode() {
return cardLayerHandler.getCardLayer().getRootNode();
}
/**
* Adds a player to the game.
*
* @param color the player's color
* @param name the player's name
*/
public void addPlayer(Color color, String name) {
playerNameHandler.addPlayer(color, name, color == ownColor);
}
/**
* Sets the active player.
*
* @param color the active player's color
*/
public void setActivePlayer(Color color) {
playerNameHandler.setActivePlayer(color);
if (ownColor == color) actionTextHandler.ownActive(color);
else actionTextHandler.activePlayer(playerNameHandler.getName(color), color);
}
/**
* Applies the shield effect.
*/
public void shield() {
cardLayerHandler.shield();
}
/**
* Applies the swap effect.
*/
public void swap() {
cardLayerHandler.swap();
}
/**
* Applies the turbo effect.
*/
public void turbo() {
cardLayerHandler.turbo();
}
/**
* Hides the action text.
*/
public void hideText() {
actionTextHandler.hide();
}
/**
* Draws a card for an enemy player.
*
* @param color the enemy player's color
*/
public void drawCard(Color color) {
//Color != ownColor
actionTextHandler.drawCard(playerNameHandler.getName(color), color);
playerNameHandler.addCard(color);
}
/**
* Displays the finish text for a player.
*
* @param color the player's color
*/
public void finish(Color color) {
if (ownColor == color) actionTextHandler.finishTextOwn(color);
else actionTextHandler.finishText(playerNameHandler.getName(color), color);
}
/**
* Displays the ranking result for a player.
*
* @param color the player's color
* @param eye the ranking result
*/
public void rollRankingResult(Color color, int eye) {
if (ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
else actionTextHandler.rollRankingResult(playerNameHandler.getName(color), color, eye);
private CardControl createCard(Asset card, Vector3f pos){
Spatial spatial = app.getAssetManager().loadModel(card.getModelPath());
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(card.getDiffPath()));
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
spatial.setLocalTranslation(pos);
spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
CardControl control = new CardControl();
spatial.addControl(control);
return control;
}
}

View File

@@ -1,245 +0,0 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.ui.Picture;
import pp.mdga.game.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Handles the display and management of player names and their associated data.
*/
public class PlayerNameHandler {
private final BitmapFont playerFont;
private final Node playerNameNode;
private final List<Color> playerOrder;
private final Map<Color, String> colorNameMap;
private final Map<Color, Integer> colorCardMap;
private final AppSettings appSettings;
private final AssetManager assetManager;
private Color ownColor;
private static final float PADDING_TOP = 35;
private static final float PADDING_LEFT = 50;
private static final float MARGIN_NAMES = 50;
private static final float IMAGE_SIZE = 50;
private static final float TEXT_SIZE = 28;
private static final ColorRGBA NORMAL_COLOR = ColorRGBA.White;
private static final ColorRGBA ACTIVE_COLOR = ColorRGBA.Blue;
private static final ColorRGBA OWN_COLOR = ColorRGBA.Cyan;
private final Node guiNode;
/**
* Constructs a PlayerNameHandler.
*
* @param guiNode the GUI node to attach player names to
* @param assetManager the asset manager to load resources
* @param appSettings the application settings
*/
public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings) {
this.guiNode = guiNode;
playerFont = assetManager.loadFont("Fonts/Gunplay.fnt");
playerNameNode = new Node("player name node");
playerOrder = new ArrayList<>();
colorNameMap = new HashMap<>();
colorCardMap = new HashMap<>();
this.appSettings = appSettings;
this.assetManager = assetManager;
}
/**
* Shows the player names on the GUI.
*/
public void show() {
guiNode.attachChild(playerNameNode);
}
/**
* Hides the player names from the GUI.
*/
public void hide() {
guiNode.detachChild(playerNameNode);
}
/**
* Draws the player names and their associated data on the GUI.
*/
private void drawPlayers() {
playerNameNode.detachAllChildren();
for (int i = 0; i < playerOrder.size(); i++) {
Color color = playerOrder.get(i);
if (!colorNameMap.containsKey(color)) throw new RuntimeException(color + " isn't mapped to a name");
Node nameParent = new Node("nameParent");
nameParent.attachChild(createColor(color));
BitmapText name = createName(colorNameMap.get(color), i == 0, color == ownColor);
nameParent.attachChild(name);
if (colorCardMap.getOrDefault(color, 0) > 0) {
Picture pic = createHandCard(name.getLineWidth());
nameParent.attachChild(pic);
nameParent.attachChild(createCardNum(colorCardMap.get(color), pic.getWidth(), pic.getLocalTranslation().getX()));
}
nameParent.setLocalTranslation(50, appSettings.getWindowHeight() - PADDING_TOP - MARGIN_NAMES * i, 0);
playerNameNode.attachChild(nameParent);
}
}
/**
* Creates a BitmapText object to display the number of cards a player has.
*
* @param num the number of cards
* @param lastWidth the width of the last element
* @param lastX the x position of the last element
* @return a BitmapText object displaying the number of cards
*/
private Spatial createCardNum(int num, float lastWidth, float lastX) {
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(NORMAL_COLOR);
hudText.setText(String.valueOf(num));
hudText.setLocalTranslation(lastX + lastWidth + 20, hudText.getHeight() / 2, 0);
return hudText;
}
/**
* Creates a Picture object to display a hand card image.
*
* @param width the width of the previous element
* @return a Picture object displaying a hand card image
*/
private Picture createHandCard(float width) {
Picture pic = new Picture("HUD Picture");
pic.setImage(assetManager, "./Images/handcard.png", true);
pic.setWidth(IMAGE_SIZE);
pic.setHeight(IMAGE_SIZE);
pic.setPosition(-pic.getWidth() / 2 + width + PADDING_LEFT * 2, -pic.getHeight() / 2);
return pic;
}
/**
* Returns the image path for a given color.
*
* @param color the color to get the image path for
* @return the image path for the given color
*/
private String imagePath(Color color) {
String root = "./Images/name_pictures/";
return switch (color) {
case ARMY -> root + "HEER_IMAGE.png";
case NAVY -> root + "MARINE_IMAGE.png";
case CYBER -> root + "CIR_IMAGE.png";
case AIRFORCE -> root + "LW_IMAGE.png";
default -> throw new RuntimeException("None is not valid");
};
}
/**
* Creates a Picture object to display a color image.
*
* @param color the color to create the image for
* @return a Picture object displaying the color image
*/
private Spatial createColor(Color color) {
Picture pic = new Picture("HUD Picture");
pic.setImage(assetManager, imagePath(color), true);
pic.setWidth(IMAGE_SIZE);
pic.setHeight(IMAGE_SIZE);
pic.setPosition(-pic.getWidth() / 2, -pic.getHeight() / 2);
return pic;
}
/**
* Creates a BitmapText object to display a player's name.
*
* @param name the player's name
* @param first whether the player is the first in the list
* @param own whether the player is the current user
* @return a BitmapText object displaying the player's name
*/
private BitmapText createName(String name, boolean first, boolean own) {
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(first ? ACTIVE_COLOR : own ? OWN_COLOR : NORMAL_COLOR);
hudText.setText(own ? name + " (Du)" : name);
hudText.setLocalTranslation(PADDING_LEFT, hudText.getHeight() / 2, 0);
return hudText;
}
/**
* Adds a player to the handler.
*
* @param color the color associated with the player
* @param name the name of the player
* @param own whether the player is the current user
*/
public void addPlayer(Color color, String name, boolean own) {
if (own) ownColor = color;
colorNameMap.put(color, name);
playerOrder.add(color);
drawPlayers();
}
/**
* Sets the active player.
*
* @param color the color associated with the active player
*/
public void setActivePlayer(Color color) {
if (playerOrder.get(0) == color) return;
Color oldFirst = playerOrder.remove(0);
playerOrder.remove(color);
playerOrder.add(0, color);
playerOrder.add(oldFirst);
drawPlayers();
}
/**
* Gets the name of a player by their color.
*
* @param color the color associated with the player
* @return the name of the player
*/
public String getName(Color color) {
if (!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
return colorNameMap.get(color);
}
/**
* Adds a card to a player.
*
* @param color the color associated with the player
*/
public void addCard(Color color) {
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) + 1);
drawPlayers();
}
/**
* Removes a card from a player.
*
* @param color the color associated with the player
*/
public void removeCard(Color color) {
if (colorCardMap.containsKey(color)) {
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) - 1);
if (colorCardMap.get(color) <= 0) colorCardMap.remove(color);
}
drawPlayers();
}
}

View File

@@ -1,139 +0,0 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
/**
* OutlineFilter is a custom filter for rendering outlines around objects.
*/
public class OutlineFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
/**
* Constructs an OutlineFilter with the specified OutlinePreFilter.
*
* @param outlinePreFilter the pre-filter used for outlining
*/
public OutlineFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
/**
* Initializes the filter with the given parameters.
*
* @param assetManager the asset manager
* @param renderManager the render manager
* @param vp the viewport
* @param w the width of the viewport
* @param h the height of the viewport
*/
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/Outline.j3md");
material = new Material(matDef);
material.setVector2("Resolution", new Vector2f(w, h));
material.setColor("OutlineColor", outlineColor);
material.setFloat("OutlineWidth", outlineWidth);
}
/**
* Called before each frame is rendered.
*
* @param tpf time per frame
*/
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
// System.out.println("OutlineFilter.preFrame()");
}
/**
* Called after each frame is rendered.
*
* @param renderManager the render manager
* @param viewPort the viewport
* @param prevFilterBuffer the previous filter buffer
* @param sceneBuffer the scene buffer
*/
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
// material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
// System.out.println("OutlineFilter.postFrame()");
}
/**
* Returns the material used by this filter.
*
* @return the material
*/
@Override
protected Material getMaterial() {
return material;
}
/**
* Returns the outline color.
*
* @return the outline color
*/
public ColorRGBA getOutlineColor() {
return outlineColor;
}
/**
* Sets the outline color.
*
* @param outlineColor the new outline color
*/
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
/**
* Returns the outline width.
*
* @return the outline width
*/
public float getOutlineWidth() {
return outlineWidth;
}
/**
* Sets the outline width.
*
* @param outlineWidth the new outline width
*/
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
/**
* Returns the OutlinePreFilter used by this filter.
*
* @return the OutlinePreFilter
*/
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -1,111 +0,0 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
/**
* OutlinePreFilter is a custom filter used to apply an outline effect to objects.
*/
public class OutlinePreFilter extends Filter {
private Pass normalPass;
private RenderManager renderManager;
/**
* Creates an OutlinePreFilter.
*/
public OutlinePreFilter() {
super("OutlinePreFilter");
}
/**
* Indicates that this filter requires a depth texture.
*
* @return true, indicating that a depth texture is required.
*/
@Override
protected boolean isRequiresDepthTexture() {
return true;
}
/**
* Called after the render queue is processed.
*
* @param queue the render queue.
*/
@Override
protected void postQueue(RenderQueue queue) {
Renderer r = renderManager.getRenderer();
r.setFrameBuffer(normalPass.getRenderFrameBuffer());
renderManager.getRenderer().clearBuffers(true, true, false);
}
/**
* Called after the frame is rendered.
*
* @param renderManager the render manager.
* @param viewPort the viewport.
* @param prevFilterBuffer the previous filter buffer.
* @param sceneBuffer the scene buffer.
*/
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
}
/**
* Returns the material used by this filter.
*
* @return the material.
*/
@Override
protected Material getMaterial() {
return material;
}
/**
* Returns the texture containing the outline.
*
* @return the outline texture.
*/
public Texture getOutlineTexture() {
return normalPass.getRenderedTexture();
}
/**
* Initializes the filter.
*
* @param manager the asset manager.
* @param renderManager the render manager.
* @param vp the viewport.
* @param w the width.
* @param h the height.
*/
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
this.renderManager = renderManager;
normalPass = new Pass();
normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
material = new Material(manager, "MatDefs/SelectObjectOutliner/OutlinePre.j3md");
}
/**
* Cleans up the filter.
*
* @param r the renderer.
*/
@Override
protected void cleanUpFilter(Renderer r) {
normalPass.cleanup(r);
}
}

View File

@@ -1,135 +0,0 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
/**
* OutlineProFilter is a custom filter for rendering outlines around objects.
*/
public class OutlineProFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
/**
* Constructs an OutlineProFilter with the specified OutlinePreFilter.
*
* @param outlinePreFilter the pre-filter used for outlining
*/
public OutlineProFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
/**
* Initializes the filter with the given parameters.
*
* @param assetManager the asset manager
* @param renderManager the render manager
* @param vp the viewport
* @param w the width of the viewport
* @param h the height of the viewport
*/
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/OutlinePro.j3md");
material = new Material(matDef);
material.setVector2("Resolution", new Vector2f(w, h));
material.setColor("OutlineColor", outlineColor);
material.setFloat("OutlineWidth", outlineWidth);
}
/**
* Called before rendering each frame.
*
* @param tpf time per frame
*/
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
}
/**
* Called after rendering each frame.
*
* @param renderManager the render manager
* @param viewPort the viewport
* @param prevFilterBuffer the previous filter buffer
* @param sceneBuffer the scene buffer
*/
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
}
/**
* Returns the material used by this filter.
*
* @return the material
*/
@Override
protected Material getMaterial() {
return material;
}
/**
* Returns the outline color.
*
* @return the outline color
*/
public ColorRGBA getOutlineColor() {
return outlineColor;
}
/**
* Sets the outline color.
*
* @param outlineColor the new outline color
*/
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
/**
* Returns the outline width.
*
* @return the outline width
*/
public float getOutlineWidth() {
return outlineWidth;
}
/**
* Sets the outline width.
*
* @param outlineWidth the new outline width
*/
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
/**
* Returns the OutlinePreFilter.
*
* @return the OutlinePreFilter
*/
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -1,121 +0,0 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import pp.mdga.client.MdgaApp;
/**
* This class is responsible for outlining selected objects in the scene.
*/
public class SelectObjectOutliner {
private final FilterPostProcessor fpp;
private final RenderManager renderManager;
private final AssetManager assetManager;
private final Camera cam;
private boolean selected;
private ViewPort outlineViewport = null;
// private OutlineFilter outlineFilter = null;
private OutlineProFilter outlineFilter = null;
private final MdgaApp app;
/**
* Constructor for SelectObjectOutliner.
*
* @param fpp the FilterPostProcessor
* @param renderManager the RenderManager
* @param assetManager the AssetManager
* @param cam the Camera
* @param app the MdgaApp instance
*/
public SelectObjectOutliner(FilterPostProcessor fpp, RenderManager renderManager, AssetManager assetManager, Camera cam, MdgaApp app) {
this.selected = false;
this.fpp = fpp;
this.renderManager = renderManager;
this.assetManager = assetManager;
this.cam = cam;
this.app = app;
}
/**
* Deselects the currently selected object, removing the outline effect.
*
* @param model the Spatial model to deselect
*/
public void deselect(Spatial model) {
if (selected) {
selected = false;
hideOutlineFilterEffect(model);
}
}
// /**
// * Selects an object and applies an outline effect.
// *
// * @param model the Spatial model to select
// * @param color the ColorRGBA for the outline
// */
// public void select(Spatial model, ColorRGBA color) {
// if (!selected) {
// selected = true;
// showOutlineFilterEffect(model, width, color);
// }
// }
/**
* Selects an object and applies an outline effect.
*
* @param model the Spatial model to select
* @param color the ColorRGBA for the outline
* @param width the width of the outline
*/
public void select(Spatial model, ColorRGBA color, int width) {
if (!selected) {
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
/**
* Hides the outline effect from the selected object.
*
* @param model the Spatial model to hide the outline effect from
*/
private void hideOutlineFilterEffect(Spatial model) {
outlineFilter.setEnabled(false);
outlineFilter.getOutlinePreFilter().setEnabled(false);
fpp.removeFilter(outlineFilter);
outlineViewport.detachScene(model);
outlineViewport.clearProcessors();
renderManager.removePreView(outlineViewport);
outlineViewport = null;
}
/**
* Shows the outline effect on the selected object.
*
* @param model the Spatial model to show the outline effect on
* @param width the width of the outline
* @param color the ColorRGBA for the outline
*/
private void showOutlineFilterEffect(Spatial model, int width, ColorRGBA color) {
outlineViewport = renderManager.createPreView("outlineViewport", cam);
FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
outlineFpp.addFilter(outlinePreFilter);
outlineViewport.attachScene(model);
outlineViewport.addProcessor(outlineFpp);
outlineFilter = new OutlineProFilter(outlinePreFilter);
outlineFilter.setOutlineColor(color);
outlineFilter.setOutlineWidth(width);
fpp.addFilter(outlineFilter);
}
}

View File

@@ -2,10 +2,8 @@
import com.jme3.network.*;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.serializing.serializers.EnumSerializer;
import pp.mdga.Resources;
import pp.mdga.game.*;
import pp.mdga.game.card.*;
import pp.mdga.game.Game;
import pp.mdga.game.Player;
import pp.mdga.message.client.*;
import pp.mdga.message.server.*;
import pp.mdga.server.ServerGameLogic;
@@ -15,11 +13,7 @@
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.LogManager;
@@ -31,10 +25,8 @@ public class MdgaServer implements MessageListener<HostedConnection>, Connection
private static final Logger LOGGER = System.getLogger(MdgaServer.class.getName());
private Server myServer;
private static int port;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
private volatile boolean running = true;
static {
// Configure logging
@@ -48,213 +40,131 @@ public class MdgaServer implements MessageListener<HostedConnection>, Connection
}
/**
* Constructor.
*
* @param port as the port for this server
* Starts the Battleships server.
*/
public MdgaServer(int port) {
MdgaServer.port = port;
public static void main(String[] args) {
new MdgaServer().run();
}
/**
* Creates a new MdgaServer.
*/
public MdgaServer() {
LOGGER.log(Level.INFO, "Creating MdgaServer"); //NON-NLS
logic = new ServerGameLogic(this, new Game());
}
/**
* Main method to start the server.
*/
public void run() {
startServer();
while (running) {
this.connectionAdded(myServer, myServer.getConnection(0));
while (true)
processNextMessage();
}
shutdownServerResources();
}
/**
* Starts the server and initializes listeners.
*/
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server...");
unlockSerializers();//NON-NLS
myServer = Network.createServer(port);
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(1234);
initializeSerializables();
myServer.start();
registerListeners();
LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
} catch (IOException e) {
LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
exit();
exit(1);
}
}
/**
* Process the next message in the queue.
*/
private void processNextMessage() {
try {
ReceivedMessage message = pendingMessages.take(); // This is a blocking call
message.process(logic);
pendingMessages.take().process(logic);
} catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Server thread interrupted, shutting down..."); //NON-NLS
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
Thread.currentThread().interrupt();
}
}
/**
* Register serializable classes.
*/
private void initializeSerializables() {
Serializer.registerClass(AnimationEnd.class);
Serializer.registerClass(ClientStartGame.class);
Serializer.registerClass(DeselectTSK.class);
Serializer.registerClass(ForceContinueGame.class);
Serializer.registerClass(StartGame.class);
Serializer.registerClass(JoinServer.class);
Serializer.registerClass(LeaveGame.class);
Serializer.registerClass(LobbyNotReady.class);
Serializer.registerClass(LobbyReady.class);
Serializer.registerClass(NoPowerCard.class);
Serializer.registerClass(RequestBriefing.class);
Serializer.registerClass(RequestDie.class);
Serializer.registerClass(RequestMove.class);
Serializer.registerClass(RequestPlayCard.class);
Serializer.registerClass(SelectCard.class);
Serializer.registerClass(SelectedPieces.class);
Serializer.registerClass(SelectTSK.class);
Serializer.registerClass(UUID.class, new UUIDSerializer());
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(ClientStartGameMessage.class);
Serializer.registerClass(DeselectTSKMessage.class);
Serializer.registerClass(ForceContinueGameMessage.class);
Serializer.registerClass(StartGameMessage.class);
Serializer.registerClass(JoinedLobbyMessage.class);
Serializer.registerClass(LeaveGameMessage.class);
Serializer.registerClass(LobbyNotReadyMessage.class);
Serializer.registerClass(LobbyReadyMessage.class);
Serializer.registerClass(NoPowerCardMessage.class);
Serializer.registerClass(RequestBriefingMessage.class);
Serializer.registerClass(RequestDieMessage.class);
Serializer.registerClass(RequestMoveMessage.class);
Serializer.registerClass(RequestPlayCardMessage.class);
Serializer.registerClass(SelectCardMessage.class);
Serializer.registerClass(SelectedPiecesMessage.class);
Serializer.registerClass(SelectPieceMessage.class);
Serializer.registerClass(SelectTSKMessage.class);
Serializer.registerClass(ActivePlayerMessage.class);
Serializer.registerClass(AnyPieceMessage.class);
Serializer.registerClass(BriefingMessage.class);
Serializer.registerClass(ActivePlayer.class);
Serializer.registerClass(AnyPiece.class);
Serializer.registerClass(Briefing.class);
Serializer.registerClass(CeremonyMessage.class);
Serializer.registerClass(DieMessage.class);
Serializer.registerClass(DiceAgainMessage.class);
Serializer.registerClass(DiceNowMessage.class);
Serializer.registerClass(EndOfTurnMessage.class);
Serializer.registerClass(LobbyAcceptMessage.class);
Serializer.registerClass(LobbyDenyMessage.class);
Serializer.registerClass(LobbyPlayerJoinedMessage.class);
Serializer.registerClass(LobbyPlayerLeaveMessage.class);
Serializer.registerClass(Die.class);
Serializer.registerClass(DiceAgain.class);
Serializer.registerClass(DiceNow.class);
Serializer.registerClass(EndOfTurn.class);
Serializer.registerClass(LobbyAccept.class);
Serializer.registerClass(LobbyDeny.class);
Serializer.registerClass(LobbyPlayerJoin.class);
Serializer.registerClass(LobbyPlayerLeave.class);
Serializer.registerClass(MoveMessage.class);
Serializer.registerClass(NoTurnMessage.class);
Serializer.registerClass(PauseGameMessage.class);
Serializer.registerClass(PlayCardMessage.class);
Serializer.registerClass(PossibleCardsMessage.class);
Serializer.registerClass(PossiblePieceMessage.class);
Serializer.registerClass(RankingResponseMessage.class);
Serializer.registerClass(RankingRollAgainMessage.class);
Serializer.registerClass(ReconnectBriefingMessage.class);
Serializer.registerClass(ResumeGameMessage.class);
Serializer.registerClass(ServerStartGameMessage.class);
Serializer.registerClass(ShutdownMessage.class);
Serializer.registerClass(StartPieceMessage.class);
Serializer.registerClass(UpdateReadyMessage.class);
Serializer.registerClass(UpdateTSKMessage.class);
Serializer.registerClass(WaitPieceMessage.class);
Serializer.registerClass(IncorrectRequestMessage.class);
Serializer.registerClass(SpectatorMessage.class);
Serializer.registerClass(Player.class);
Serializer.registerClass(Statistic.class);
Serializer.registerClass(Board.class);
Serializer.registerClass(Node.class);
Serializer.registerClass(Piece.class);
Serializer.registerClass(BonusNode.class);
Serializer.registerClass(StartNode.class);
Serializer.registerClass(HomeNode.class);
Serializer.registerClass(PowerCard.class);
Serializer.registerClass(TurboCard.class);
Serializer.registerClass(SwapCard.class);
Serializer.registerClass(ShieldCard.class);
Serializer.registerClass(HiddenCard.class);
Serializer.registerClass(ChoosePieceStateMessage.class);
Serializer.registerClass(DrawCardMessage.class);
Serializer.registerClass(Color.class, new EnumSerializer());
Serializer.registerClass(PieceState.class, new EnumSerializer());
Serializer.registerClass(ShieldState.class, new EnumSerializer());
Serializer.registerClass(BonusCard.class, new EnumSerializer());
Serializer.registerClass(NoTurn.class);
Serializer.registerClass(PauseGame.class);
Serializer.registerClass(PlayCard.class);
Serializer.registerClass(PossibleCard.class);
Serializer.registerClass(PossiblePiece.class);
Serializer.registerClass(RankingResponse.class);
Serializer.registerClass(RankingRollAgain.class);
Serializer.registerClass(ReconnectBriefing.class);
Serializer.registerClass(ResumeGame.class);
Serializer.registerClass(ServerStartGame.class);
Serializer.registerClass(StartPiece.class);
Serializer.registerClass(UpdateReady.class);
Serializer.registerClass(UpdateTSK.class);
Serializer.registerClass(WaitPiece.class);
}
/**
* Register listeners for the server.
*/
private void registerListeners() {
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addMessageListener(this, ClientStartGameMessage.class);
myServer.addMessageListener(this, DeselectTSKMessage.class);
myServer.addMessageListener(this, DisconnectedMessage.class);
myServer.addMessageListener(this, ForceContinueGameMessage.class);
myServer.addMessageListener(this, StartGameMessage.class);
myServer.addMessageListener(this, JoinedLobbyMessage.class);
myServer.addMessageListener(this, LeaveGameMessage.class);
myServer.addMessageListener(this, LobbyNotReadyMessage.class);
myServer.addMessageListener(this, LobbyReadyMessage.class);
myServer.addMessageListener(this, NoPowerCardMessage.class);
myServer.addMessageListener(this, RequestBriefingMessage.class);
myServer.addMessageListener(this, RequestDieMessage.class);
myServer.addMessageListener(this, RequestMoveMessage.class);
myServer.addMessageListener(this, RequestPlayCardMessage.class);
myServer.addMessageListener(this, SelectCardMessage.class);
myServer.addMessageListener(this, SelectedPiecesMessage.class);
myServer.addMessageListener(this, SelectTSKMessage.class);
myServer.addMessageListener(this, AnimationEnd.class);
myServer.addMessageListener(this, ClientStartGame.class);
myServer.addMessageListener(this, DeselectTSK.class);
myServer.addMessageListener(this, ForceContinueGame.class);
myServer.addMessageListener(this, StartGame.class);
myServer.addMessageListener(this, JoinServer.class);
myServer.addMessageListener(this, LeaveGame.class);
myServer.addMessageListener(this, LobbyNotReady.class);
myServer.addMessageListener(this, LobbyReady.class);
myServer.addMessageListener(this, NoPowerCard.class);
myServer.addMessageListener(this, RequestBriefing.class);
myServer.addMessageListener(this, RequestDie.class);
myServer.addMessageListener(this, RequestMove.class);
myServer.addMessageListener(this, RequestPlayCard.class);
myServer.addMessageListener(this, SelectCard.class);
myServer.addMessageListener(this, SelectedPieces.class);
myServer.addMessageListener(this, SelectTSK.class);
myServer.addConnectionListener(this);
}
/**
* This method will be used to receive network messages from the given source parameter.
* It will check if the given message parameter is a ClientMessage object. If yes it will call the messageReceived
* method with the casted ClientMessage object.
*
* @param source as the connection which sends the message as a HostedConnection object.
* @param message as the received message as a Message object.
*/
@Override
public void messageReceived(HostedConnection source, Message message) {
if (message instanceof ClientMessage) {
this.messageReceived(source, (ClientMessage) message);
}
}
/**
* This method will be used to received network messages from the given source parameter.
* It will add the given message parameter to the pendingMessage attribute of MdgaServer after creating
* a ReceivedMessage object with it and its id.
*
* @param source as the connection which sends the message as a HostedConnection object.
* @param message as the received message as a Message object.
*/
private void messageReceived(HostedConnection source, ClientMessage message) {
System.out.println("server received from: " + source.getId() + " " + message.getClass().getName());
public void messageReceived(HostedConnection source, ClientMessage message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
pendingMessages.add(new ReceivedMessage(message, source.getId()));
}
/**
* This method will be used to handle all connections which are connected to the server.
* It will check if the maximum number of connected clients are already reached. If yes it will send a
* LobbyDenyMessage to the given hostedConnection parameter and close it, otherwise it will send a
* LobbyAcceptMessage to the given hostedConnection parameter. In Addition, if the number of connected clients is
* equal to 1 it will set the host of the game to the id of the given hostedConnection parameter.
*
* @param server as the server which is contains all connections as a Server object.
* @param hostedConnection as the connection which is added to the server as a HostedConnection object.
*/
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
System.out.println("new connection " + hostedConnection); //NON-NLS
LOGGER.log(Level.DEBUG, "new connection {0}", hostedConnection); //NON-NLS
if (this.myServer.getConnections().size() > Resources.MAX_PLAYERS) {
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyDenyMessage());
hostedConnection.close("");
} else {
if (hostedConnection.getAddress().contains("127.0.0.1") && this.logic.getGame().getHost() == -1) {
this.logic.getGame().setHost(hostedConnection.getId());
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyAcceptMessage(hostedConnection.getId()));
} else {
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyAcceptMessage());
}
}
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
// ToDo: Synchronize data between server and client.
logic.getGame().addPlayer(hostedConnection.getId(), new Player());
}
@Override
@@ -276,15 +186,15 @@ public void connectionRemoved(Server server, HostedConnection hostedConnection)
* @param id as the id of the disconnected player.
*/
public void handleDisconnect(int id) {
this.logic.received(new DisconnectedMessage(), id);
this.logic.received(new Disconnected(), id);
}
/**
* Stops the server thread gracefully.
*/
public void exit() {
LOGGER.log(Level.INFO, "Requesting server shutdown"); //NON-NLS
running = false;
public void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null)
for (HostedConnection client : myServer.getConnections()) //NON-NLS
if (client != null) client.close("Game over"); //NON-NLS
System.exit(exitValue);
}
/**
@@ -293,17 +203,16 @@ public void exit() {
* @param id the connection id
* @param message the message
*/
@Override
public void send(int id, ServerMessage message) {
if (myServer == null || !myServer.isRunning()) {
LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
return;
}
final HostedConnection connection = myServer.getConnection(id);
if (connection != null) {
System.out.println("server sends to: " + id + " " + message.getClass().getName());
if (connection != null)
connection.send(message);
} else LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
else
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
}
/**
@@ -312,61 +221,15 @@ public void send(int id, ServerMessage message) {
*
* @param message as the message which will be sent to all players as a ServerMessage.
*/
@Override
public void broadcast(ServerMessage message) {
for (Map.Entry<Integer, Player> entry : this.logic.getGame().getPlayers().entrySet()) {
for (Map.Entry<Integer, Player> entry: this.logic.getGame().getPlayers().entrySet()) {
this.send(entry.getKey(), message);
}
}
/**
* This method will be used to diconenect the client depending on the given id parameter.
*
* @param id as the connection id of the client as an Integer.
*/
//TODO:
@Override
public void disconnectClient(int id) {
if (myServer.getConnection(id) != null) {
this.myServer.getConnection(id).close("");
} else {
LOGGER.log(Level.ERROR, "no connection with id={0}", id); //NON-NLS
}
}
public void messageReceived(HostedConnection source, Message m) {
/**
* This method will be used to shut down the server.
* It will iterate threw all connections of myServer attribute and check if they are equal to null. If not they will
* be closed. After that the myServer attribute will be closed and this program will be exited with the exit code 0.
*/
@Override
public void shutdown() {
this.exit();
}
/**
* Gracefully shutdown server resources like connections and sockets.
*/
private void shutdownServerResources() {
LOGGER.log(Level.INFO, "Shutting down server resources"); //NON-NLS
if (myServer != null && myServer.isRunning()) {
for (HostedConnection client : myServer.getConnections()) {
if (client != null) client.close("Server shutting down.");
}
myServer.close();
}
}
/**
* This method will be used to unlock the Serializer registry.
*/
private static void unlockSerializers() {
try {
Field lockField = Serializer.class.getDeclaredField("locked");
lockField.setAccessible(true);
lockField.setBoolean(null, false); // Unlock the Serializer registry
} catch (NoSuchFieldException | IllegalAccessException e) {
System.err.println("Failed to unlock the Serializer registry: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -3,16 +3,7 @@
import pp.mdga.message.client.ClientInterpreter;
import pp.mdga.message.client.ClientMessage;
/**
* Represents a message received from the server.
*/
public record ReceivedMessage(ClientMessage msg, int from) {
/**
* Processes the received message using the specified client interpreter.
*
* @param interpreter the client interpreter to use for processing the message
*/
void process(ClientInterpreter interpreter) {
msg.accept(interpreter, from);
}

View File

@@ -1,52 +0,0 @@
package pp.mdga.client.server;
import com.jme3.network.serializing.Serializer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
* Serializer for UUID objects to be used with jME3 networking.
*/
public class UUIDSerializer extends Serializer {
/**
* Reads a UUID object from the given ByteBuffer.
*
* @param data the ByteBuffer containing the serialized UUID
* @param c the class type of the object to be read
* @param <T> the type of the object to be read
* @return the deserialized UUID object, or null if the UUID is a placeholder
* @throws IOException if an I/O error occurs
*/
@Override
public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
byte[] uuid = new byte[36];
data.get(uuid);
if (uuid.equals(new UUID(1, 1))) {
return null;
} else {
return (T) UUID.fromString(new String(uuid));
}
}
/**
* Writes a UUID object to the given ByteBuffer.
*
* @param buffer the ByteBuffer to write the serialized UUID to
* @param object the UUID object to be serialized
* @throws IOException if an I/O error occurs
*/
@Override
public void writeObject(ByteBuffer buffer, Object object) throws IOException {
UUID uuid = (UUID) object;
if (uuid != null) {
buffer.put(uuid.toString().getBytes());
} else {
buffer.put(new UUID(1, 1).toString().getBytes());
}
}
}

View File

@@ -1,31 +1,13 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import pp.mdga.client.dialog.SingleButtonLeftDialog;
import pp.mdga.client.dialog.SingleButtonRightDialog;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.CeremonyButton;
import pp.mdga.client.dialog.CeremonyDialog;
import pp.mdga.game.Color;
import java.util.ArrayList;
/**
* CeremonyView class handles the display and interaction of the ceremony view in the application.
*/
public class CeremonyView extends MdgaView {
/**
* Enum representing the sub-states of the CeremonyView.
*/
private enum SubState {
AWARD_CEREMONY,
STATISTICS,
@@ -33,183 +15,54 @@ private enum SubState {
private SubState state;
private SingleButtonRightDialog continueButton;
private SingleButtonLeftDialog backButton;
private Geometry background;
private Geometry podest;
private ButtonLeft backButton;
private ButtonRight continueButton;
private ArrayList<CeremonyButton> ceremonyButtons;
private CeremonyDialog ceremonyDialog;
private AmbientLight ambient = new AmbientLight();
/**
* Constructor for CeremonyView.
*
* @param app The application instance.
*/
public CeremonyView(MdgaApp app) {
super(app);
backButton = new ButtonLeft(app, guiNode, this::back, "Zurück", 1);
continueButton = new ButtonRight(app, guiNode, this::forward, "Weiter", 1);
ceremonyButtons = new ArrayList<>(4);
ceremonyDialog = new CeremonyDialog(app, guiNode);
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
continueButton = new SingleButtonRightDialog(app, node, "Weiter", () -> forward());
backButton = new SingleButtonLeftDialog(app, node, "Zurück", () -> back());
}
/**
* Method called when entering the CeremonyView.
*/
@Override
public void onEnter() {
rootNode.addLight(ambient);
app.getAcousticHandler().playSound(MdgaSound.VICTORY);
float screenWidth = app.getCamera().getWidth();
float screenHeight = app.getCamera().getHeight();
float aspectRatio = screenWidth / screenHeight;
float scale = 3.5f;
float distanceFromCamera = 5f;
float verticalSize = (float) (2 * Math.tan(Math.toRadians(app.getCamera().getFov() / 2)) * distanceFromCamera * scale);
float horizontalSize = verticalSize * aspectRatio;
Quad backgroundQuad = new Quad(horizontalSize, verticalSize);
background = new Geometry("LobbyBackground", backgroundQuad);
TextureKey backgroundKey = new TextureKey("Images/lobby.png", true);
Texture backgroundTexture = app.getAssetManager().loadTexture(backgroundKey);
Material backgroundMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
backgroundMaterial.setTexture("ColorMap", backgroundTexture);
background.setMaterial(backgroundMaterial);
background.setLocalTranslation(-horizontalSize / 2, -verticalSize / 2, -distanceFromCamera);
rootNode.attachChild(background);
verticalSize *= 0.99f;
Quad overlayQuad = new Quad(horizontalSize, verticalSize * 0.8f);
podest = new Geometry("TransparentOverlay", overlayQuad);
TextureKey overlayKey = new TextureKey("Images/Ceremony.png", true);
Texture overlayTexture = app.getAssetManager().loadTexture(overlayKey);
Material overlayMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
overlayMaterial.setTexture("ColorMap", overlayTexture);
overlayMaterial.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
podest.setMaterial(overlayMaterial);
float overlayDistance = distanceFromCamera - 0.1f;
podest.setLocalTranslation(-horizontalSize / 2, -verticalSize * 0.415f, -overlayDistance);
enterSub(SubState.AWARD_CEREMONY);
}
/**
* Method called when leaving the CeremonyView.
*/
@Override
public void onLeave() {
backButton.hide();
continueButton.hide();
if (null != background) {
guiNode.detachChild(background);
}
ceremonyButtons.clear();
rootNode.removeLight(ambient);
ceremonyDialog.prepare();
rootNode.detachChild(background);
backButton.hide();
}
/**
* Method called when entering an overlay.
*
* @param overlay The overlay being entered.
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
if (rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
}
}
/**
* Method called when leaving an overlay.
*
* @param overlay The overlay being left.
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
enterSub(state);
}
/**
* Method called to update the CeremonyView.
*
* @param tpf Time per frame.
*/
@Override
protected void onUpdate(float tpf) {
for (CeremonyButton c : ceremonyButtons) {
c.update(tpf);
}
}
/**
* Handles the award ceremony sub-state.
*/
private void awardCeremony() {
background = createBackground("b1.png");
node.attachChild(background);
continueButton.show();
rootNode.attachChild(podest);
for (CeremonyButton c : ceremonyButtons) {
c.show();
}
backButton.hide();
}
/**
* Handles the statistics sub-state.
*/
private void statistics() {
//background = createBackground("Images/b2.png");
//guiNode.attachChild(background);
background = createBackground("b2.png");
node.attachChild(background);
backButton.show();
continueButton.show();
ceremonyDialog.show();
backButton.show();
}
/**
* Enters a sub-state of the CeremonyView.
*
* @param state The sub-state to enter.
*/
private void enterSub(SubState state) {
this.state = state;
if (rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
if(null != background) {
node.detachChild(background);
}
backButton.hide();
continueButton.hide();
for (CeremonyButton c : ceremonyButtons) {
c.hide();
}
ceremonyDialog.hide();
switch (state) {
case AWARD_CEREMONY:
@@ -221,23 +74,17 @@ private void enterSub(SubState state) {
}
}
/**
* Handles the forward button action.
*/
public void forward() {
private void forward() {
switch (state) {
case AWARD_CEREMONY:
enterSub(SubState.STATISTICS);
break;
case STATISTICS:
app.getModelSynchronize().next();
app.getModelSyncronizer().enter(MdgaState.MAIN);
break;
}
}
/**
* Handles the back button action.
*/
private void back() {
switch (state) {
case AWARD_CEREMONY:
@@ -248,47 +95,4 @@ private void back() {
break;
}
}
/**
* Adds a participant to the ceremony.
*
* @param color The color of the participant.
* @param pos The position of the participant.
* @param name The name of the participant.
*/
public void addCeremonyParticipant(Color color, int pos, String name) {
CeremonyButton button = new CeremonyButton(app, guiNode, rootNode, color, CeremonyButton.Pos.values()[pos - 1], name);
ceremonyButtons.add(button);
if (state != null && state.equals(SubState.AWARD_CEREMONY)) {
button.hide();
button.show();
}
}
/**
* Adds a row of statistics.
*
* @param name The name of the row.
* @param v1 Value 1.
* @param v2 Value 2.
* @param v3 Value 3.
* @param v4 Value 4.
* @param v5 Value 5.
* @param v6 Value 6.
*/
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
ceremonyDialog.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
ceremonyDialog.hide();
ceremonyDialog.show();
}
/**
* Cleans up after the game.
*/
public void afterGameCleanup() {
ceremonyDialog.prepare();
}
}

View File

@@ -1,239 +1,79 @@
package pp.mdga.client.view;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.board.CameraHandler;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.dialog.InterruptDialog;
import pp.mdga.client.dialog.SingleButtonLeftDialog;
import pp.mdga.client.dialog.SingleButtonRightDialog;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.game.Color;
/**
* Represents the view for the game.
*/
public class GameView extends MdgaView {
private BoardHandler boardHandler;
private CameraHandler camera;
private GuiHandler guiHandler;
private ButtonLeft leaveButton;
public ButtonRight confirmButton;
private SingleButtonLeftDialog leaveButton;
private SingleButtonRightDialog continueButton;
private ButtonRight noPowerButton;
private Color ownColor = null;
private InterruptDialog interruptDialog;
private FilterPostProcessor fpp;
public boolean needConfirm = false;
public boolean needNoPower = false;
private Node guiHandlerNode = new Node();
/**
* Constructs a new GameView.
*
* @param app the application instance
*/
public GameView(MdgaApp app) {
super(app);
leaveButton = new ButtonLeft(app, settingsNode, () -> app.getModelSynchronize().leave(), "Spiel verlassen", 1);
confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
noPowerButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Verzichten", 1);
interruptDialog = new InterruptDialog(app, guiNode);
fpp = new FilterPostProcessor(app.getAssetManager());
//Filter für Outline: Reihenfolge CameraHandler(dlsf) -> BoardHandler -> viewPort.addProcessor einhalten!
FilterPostProcessor fpp = new FilterPostProcessor(app.getAssetManager());
this.camera = new CameraHandler(app, fpp);
this.boardHandler = new BoardHandler(app, rootNode, fpp);
guiHandler = new GuiHandler(app, guiHandlerNode);
guiNode.attachChild(guiHandlerNode);
}
/**
* Called when entering the view.
*/
@Override
public void onEnter() {
camera.init(ownColor);
boardHandler.init();
guiHandler.init(ownColor);
this.boardHandler = new BoardHandler(app, fpp);
app.getViewPort().addProcessor(fpp);
app.getAcousticHandler().playSound(MdgaSound.START);
this.guiHandler = new GuiHandler(app);
leaveButton = new SingleButtonLeftDialog(app, settingsNode, "Verlassen", () -> leaveGame());
continueButton = new SingleButtonRightDialog(app, node, "Weiter", () -> app.getModelSyncronizer().enter(MdgaState.CEREMONY));
}
@Override
public void onEnter() {
camera.init();
boardHandler.init();
guiHandler.init();
continueButton.show();
}
/**
* Called when leaving the view.
*/
@Override
public void onLeave() {
boardHandler.shutdown();
guiHandler.shutdown();
continueButton.hide();
camera.shutdown();
confirmButton.hide();
noPowerButton.hide();
app.getViewPort().removeProcessor(fpp);
boardHandler.shutdown();
}
/**
* Called to update the view.
*
* @param tpf time per frame
*/
@Override
public void onUpdate(float tpf) {
camera.update(app.getInputSynchronize().getScroll(), app.getInputSynchronize().getRotation());
public void onUpdate() {
camera.update(app.getInputSyncronizer().getScroll(), app.getInputSyncronizer().getRotation());
}
/**
* Called when entering an overlay.
*
* @param overlay the overlay being entered
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
if (overlay == Overlay.SETTINGS) {
leaveButton.show();
}
protected void enterExtendedSettings() {
leaveButton.show();
}
/**
* Called when leaving an overlay.
*
* @param overlay the overlay being left
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
if (overlay == Overlay.SETTINGS) {
leaveButton.hide();
}
protected void leaveExtendedSettings() {
leaveButton.hide();
}
/**
* Leaves the game.
*/
private void leaveGame() {
app.getModelSynchronize().leave();
leaveSettings(false);
app.getModelSyncronizer().leave();
app.afteGameCleanup();
}
/**
* Gets the board handler.
*
* @return the board handler
*/
public BoardHandler getBoardHandler() {
return boardHandler;
}
/**
* Gets the GUI handler.
*
* @return the GUI handler
*/
public GuiHandler getGuiHandler() {
return guiHandler;
}
/**
* Sets the player's color.
*
* @param ownColor the player's color
*/
public void setOwnColor(Color ownColor) {
this.ownColor = ownColor;
}
/**
* Gets the player's color.
*
* @return the player's color
*/
public Color getOwnColor() {
return ownColor;
}
/**
* Shows the confirm button and hides the no power button.
*/
public void needConfirm() {
noPowerButton.hide();
confirmButton.show();
needConfirm = true;
}
/**
* Hides the confirm button.
*/
public void noConfirm() {
confirmButton.hide();
needConfirm = false;
}
/**
* Shows the no power button and hides the confirm button.
*/
public void showNoPower() {
confirmButton.hide();
noPowerButton.show();
needNoPower = true;
}
/**
* Hides the no power button.
*/
public void hideNoPower() {
noPowerButton.hide();
needNoPower = false;
}
/**
* Enters the interrupt state with the specified color.
*
* @param color the color to set
*/
public void enterInterrupt(Color color) {
enterOverlay(Overlay.INTERRUPT);
guiNode.detachChild(guiHandlerNode);
app.getGuiNode().attachChild(guiNode);
app.getInputSynchronize().setClickAllowed(false);
interruptDialog.setColor(color);
interruptDialog.show();
}
/**
* Leaves the interrupt state.
*/
public void leaveInterrupt() {
leaveOverlay(Overlay.INTERRUPT);
app.getGuiNode().detachChild(guiNode);
guiNode.attachChild(guiHandlerNode);
app.getInputSynchronize().setClickAllowed(true);
app.getAcousticHandler().playSound(MdgaSound.START);
interruptDialog.hide();
return boardHandler;
}
}

View File

@@ -1,304 +1,58 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import pp.mdga.client.dialog.LobbyButtonDialog;
import pp.mdga.client.dialog.SingleButtonLeftDialog;
import pp.mdga.client.dialog.SingleButtonRightDialog;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LobbyButton;
import pp.mdga.game.Color;
/**
* Represents the lobby view in the game.
*/
import java.util.ArrayList;
public class LobbyView extends MdgaView {
private Geometry background;
private ButtonLeft leaveButton;
private ButtonRight readyButton;
private ButtonRight startButton;
private SingleButtonRightDialog readyButton;
private SingleButtonLeftDialog leaveButton;
private LobbyButton cyberButton;
private LobbyButton airforceButton;
private LobbyButton armyButton;
private LobbyButton navyButton;
private ArrayList<LobbyButtonDialog> lobbyButtons = new ArrayList<>();
private AmbientLight ambient = new AmbientLight();
private boolean isReady = false;
private Color own = null;
/**
* Constructs a new LobbyView.
*
* @param app the application instance
*/
public LobbyView(MdgaApp app) {
super(app);
leaveButton = new ButtonLeft(app, guiNode, this::leaveLobby, "Verlassen", 1);
readyButton = new ButtonRight(app, guiNode, this::ready, "Bereit", 1);
startButton = new ButtonRight(app, guiNode, () -> app.getGameLogic().selectStart(), "Starten", 7);
background = createBackground("lobby.png");
node.attachChild(background);
cyberButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.CYBER), Color.CYBER);
airforceButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.AIRFORCE), Color.AIRFORCE);
armyButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.ARMY), Color.ARMY);
navyButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.NAVY), Color.NAVY);
readyButton = new SingleButtonRightDialog(app, node, "Fertig", () -> app.getModelSyncronizer().setReady());
leaveButton = new SingleButtonLeftDialog(app, node, "Verlassen", () -> app.getModelSyncronizer().leave());
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
lobbyButtons.add(new LobbyButtonDialog(app, node, "HEER", 0));
lobbyButtons.add(new LobbyButtonDialog(app, node, "MARINE", 1));
lobbyButtons.add(new LobbyButtonDialog(app, node, "CIR", 2));
lobbyButtons.add(new LobbyButtonDialog(app, node, "LUFTWAFFE", 3));
}
/**
* Called when entering the lobby view.
*/
@Override
public void onEnter() {
app.getCamera().setParallelProjection(true);
float aspect = (float) app.getCamera().getWidth() / app.getCamera().getHeight();
float size = 1.65f;
app.getCamera().setFrustum(-1000, 1000, -aspect * size, aspect * size, size, -size);
leaveButton.show();
readyButton.show();
leaveButton.show();
if (app.getGameLogic().isHost()) {
startButton.show();
for (LobbyButtonDialog b : lobbyButtons) {
b.show();
}
cyberButton.show();
airforceButton.show();
armyButton.show();
navyButton.show();
rootNode.addLight(ambient);
float screenWidth = app.getCamera().getWidth();
float screenHeight = app.getCamera().getHeight();
float aspectRatio = screenWidth / screenHeight;
float scale = 3.5f;
float quadWidth = scale * aspectRatio;
float quadHeight = scale;
Quad quad = new Quad(quadWidth, quadHeight);
background = new Geometry("LobbyBackground", quad);
TextureKey key = new TextureKey("Images/lobby.png", true);
Texture texture = app.getAssetManager().loadTexture(key);
Material material = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
material.setTexture("ColorMap", texture);
background.setMaterial(material);
background.setLocalTranslation(-quadWidth / 2, -quadHeight / 2, -5);
rootNode.attachChild(background);
}
/**
* Called when leaving the lobby view.
*/
@Override
public void onLeave() {
leaveButton.hide();
for (LobbyButtonDialog b : lobbyButtons) {
b.hide();
}
readyButton.hide();
startButton.hide();
airforceButton.hide();
armyButton.hide();
navyButton.hide();
cyberButton.hide();
rootNode.removeLight(ambient);
app.getCamera().setParallelProjection(false);
app.getCamera().setFrustumPerspective(
45.0f,
(float) app.getCamera().getWidth() / app.getCamera().getHeight(),
0.1f,
1000.0f
);
app.getCamera().setLocation(new Vector3f(0, 0, 10));
app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
airforceButton.setReady(false);
armyButton.setReady(false);
navyButton.setReady(false);
cyberButton.setReady(false);
airforceButton.setTaken(LobbyButton.Taken.NOT, null);
armyButton.setTaken(LobbyButton.Taken.NOT, null);
navyButton.setTaken(LobbyButton.Taken.NOT, null);
cyberButton.setTaken(LobbyButton.Taken.NOT, null);
rootNode.detachChild(background);
leaveButton.hide();
}
/**
* Called on each frame update.
*
* @param tpf time per frame
*/
@Override
protected void onUpdate(float tpf) {
airforceButton.update(tpf);
armyButton.update(tpf);
navyButton.update(tpf);
cyberButton.update(tpf);
}
@Override
protected void onEnterOverlay(Overlay overlay) {
// No implementation needed
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
// No implementation needed
}
/**
* Sets the taken status of a color.
*
* @param color the color
* @param isTaken whether the color is taken
* @param isSelf whether the color is taken by the player
* @param name the name of the player who took the color
*/
public void setTaken(Color color, boolean isTaken, boolean isSelf, String name) {
LobbyButton.Taken taken;
if (isTaken) {
if (isSelf) {
own = color;
taken = LobbyButton.Taken.SELF;
} else {
taken = LobbyButton.Taken.OTHER;
}
} else {
if (isSelf) {
own = null;
}
taken = LobbyButton.Taken.NOT;
}
switch (color) {
case CYBER:
cyberButton.setTaken(taken, name);
break;
case AIRFORCE:
airforceButton.setTaken(taken, name);
break;
case ARMY:
armyButton.setTaken(taken, name);
break;
case NAVY:
navyButton.setTaken(taken, name);
break;
}
}
/**
* Sets the ready status of a color.
*
* @param color the color
* @param isReady whether the color is ready
*/
public void setReady(Color color, boolean isReady) {
LobbyButton button = switch (color) {
case CYBER -> cyberButton;
case AIRFORCE -> airforceButton;
case ARMY -> armyButton;
case NAVY -> navyButton;
default -> throw new RuntimeException("None is not valid");
};
button.setReady(isReady);
if (button.getTaken() == LobbyButton.Taken.SELF) {
this.isReady = isReady;
}
if (!isReady) {
app.getAcousticHandler().playSound(MdgaSound.NOT_READY);
} else {
if (button.getTaken() != LobbyButton.Taken.SELF) {
app.getAcousticHandler().playSound(MdgaSound.OTHER_READY);
}
}
}
/**
* Toggles the task selection for a color.
*
* @param color the color
*/
private void toggleTsk(Color color) {
LobbyButton.Taken taken = LobbyButton.Taken.NOT;
switch (color) {
case CYBER:
taken = cyberButton.getTaken();
break;
case AIRFORCE:
taken = airforceButton.getTaken();
break;
case ARMY:
taken = armyButton.getTaken();
break;
case NAVY:
taken = navyButton.getTaken();
break;
}
if (isReady) {
setReady(own, false);
}
switch (taken) {
case NOT:
app.getModelSynchronize().selectTsk(color);
break;
case SELF:
app.getModelSynchronize().unselectTsk(color);
break;
case OTHER:
//nothing
break;
}
}
/**
* Sets the player as ready.
*/
public void ready() {
if (own == null) {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
if (!isReady) {
app.getAcousticHandler().playSound(MdgaSound.SELF_READY);
}
app.getModelSynchronize().setReady(!isReady);
}
/**
* Leaves the lobby.
*/
private void leaveLobby() {
app.getModelSynchronize().leave();
lobbyButtons.get(color.ordinal()).setTaken(isTaken, isSelf, name);
}
}

View File

@@ -1,314 +1,79 @@
package pp.mdga.client.view;
import com.jme3.scene.Geometry;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.dialog.Dialog;
import pp.mdga.client.dialog.HostDialog;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.dialog.StartDialog;
import pp.mdga.client.MdgaApp;
import com.jme3.math.Vector3f;
/**
* MainView class that extends MdgaView and manages the main view of the application.
*/
public class MainView extends MdgaView {
/**
* Enum representing the different sub-states of the MainView.
*/
private enum SubState {
HOST,
JOIN,
MAIN,
}
private SubState state;
private Geometry background;
private StartDialog startDialog;
private JoinDialog joinDialog;
private HostDialog hostDialog;
private StartDialog dialog;
private Dialog subDialog;
/**
* Constructor for MainView.
*
* @param app the MdgaApp instance
*/
public MainView(MdgaApp app) {
super(app);
startDialog = new StartDialog(app, guiNode, this);
joinDialog = new JoinDialog(app, guiNode, this);
hostDialog = new HostDialog(app, guiNode, this);
background = createBackground("powercards.png");
node.attachChild(background);
background = createBackground("Images/startmenu.png");
Vector3f size = new Vector3f(280, 60, 0);
dialog = new StartDialog(app, node);
dialog.addButton("Spiel beitreten", () -> enterJoin(), size);
dialog.addButton("Spiel hosten", () -> enterHost(), size);
dialog.addButton("Einstellungen", () -> enterSettings(false), size);
dialog.addButton("Spiel beenden", () -> app.stop(), size);
}
/**
* Called when the view is entered.
*/
@Override
public void onEnter() {
app.setup();
guiNode.attachChild(background);
enterSub(SubState.MAIN);
dialog.show();
}
/**
* Called when the view is left.
*/
@Override
public void onLeave() {
startDialog.hide();
joinDialog.hide();
hostDialog.hide();
dialog.hide();
guiNode.detachChild(background);
}
/**
* Called to update the view.
*
* @param tpf time per frame
*/
@Override
public void onUpdate(float tpf) {
startDialog.update();
joinDialog.update();
hostDialog.update();
}
/**
* Called when an overlay is entered.
*
* @param overlay the overlay being entered
*/
@Override
protected void onEnterOverlay(Overlay overlay) {
guiNode.detachChild(background);
settingsNode.attachChild(background);
}
/**
* Called when an overlay is left.
*
* @param overlay the overlay being left
*/
@Override
protected void onLeaveOverlay(Overlay overlay) {
settingsNode.detachChild(background);
guiNode.attachChild(background);
}
/**
* Shows the join menu.
*/
private void joinMenu() {
startDialog.hide();
hostDialog.hide();
joinDialog.show();
}
/**
* Shows the host menu.
*/
private void hostMenu() {
startDialog.hide();
joinDialog.hide();
hostDialog.show();
}
/**
* Shows the main menu.
*/
private void mainMenu() {
joinDialog.hide();
hostDialog.hide();
startDialog.show();
}
/**
* Attempts to host a game.
*/
private void tryHost() {
int port = 0;
String text = hostDialog.getPort();
app.getGameLogic().selectHost("");
try {
port = Integer.parseInt(text);
if (port >= 1 && port <= 65535) {
app.getModelSynchronize().setName(startDialog.getName());
hostDialog.hostServer();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
hostDialog.connectServerAsClient();
app.getModelSynchronize().setHost(port);
//app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
} catch (NumberFormatException ignored) {
}
hostDialog.resetPort();
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
/**
* Attempts to join a game.
*/
private void tryJoin() {
int port = 0;
String ip = joinDialog.getIpt();
String portText = joinDialog.getPort();
app.getGameLogic().selectJoin("");
try {
// Validate the port
port = Integer.parseInt(portText);
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Invalid port");
}
joinDialog.setPortNumber(port);
// Validate the IP address
if (isValidIpAddress(ip)) {
app.getModelSynchronize().setName(startDialog.getName());
joinDialog.setHostname(ip);
joinDialog.connectToServer();
return;
}
} catch (IllegalArgumentException e) {
// Invalid input, fall through to reset
}
joinDialog.resetPort();
joinDialog.resetIp();
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
/**
* Validates an IP address.
*
* @param ip the IP address to validate
* @return true if the IP address is valid, false otherwise
*/
private boolean isValidIpAddress(String ip) {
String ipRegex =
"^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$";
return ip != null && ip.matches(ipRegex);
}
/**
* Enters a sub-state.
*
* @param state the sub-state to enter
*/
private void enterSub(SubState state) {
this.state = state;
if (null != background) {
rootNode.detachChild(background);
}
switch (state) {
case HOST:
hostMenu();
break;
case JOIN:
joinMenu();
break;
case MAIN:
mainMenu();
break;
if(subDialog != null) {
subDialog.hide();
subDialog = null;
}
}
/**
* Forwards the state based on the current sub-state.
*/
public void forward() {
switch (state) {
case HOST:
tryHost();
break;
case JOIN:
tryJoin();
break;
case MAIN:
throw new RuntimeException("call forward(boolean host) insted of forward()");
}
private void enterJoin() {
app.getModelSyncronizer().setName(dialog.getName());
subDialog = new JoinDialog(app, node, () -> leaveJoin());
dialog.hide();
subDialog.show();
}
/**
* Forwards the state based on the current sub-state and a boolean flag.
*
* @param host a boolean flag indicating whether to host or join
*/
public void forward(boolean host) {
switch (state) {
case HOST:
tryHost();
break;
case JOIN:
tryJoin();
break;
case MAIN:
if (host) {
enterSub(SubState.HOST);
//TODO playSound
} else {
enterSub(SubState.JOIN);
//TODO: playSound
}
break;
}
private void leaveJoin() {
subDialog.hide();
dialog.show();
subDialog = null;
}
/**
* Goes back to the main menu from the current sub-state.
*/
public void back() {
switch (state) {
case HOST:
enterSub(SubState.MAIN);
//TODO: playSound
break;
case JOIN:
enterSub(SubState.MAIN);
//TODO: playSound
break;
case MAIN:
//nothing
break;
}
private void enterHost() {
app.getModelSyncronizer().setName(dialog.getName());
subDialog = new HostDialog(app, node, () -> leaveHost());
dialog.hide();
subDialog.show();
}
/**
* Gets the JoinDialog instance.
*
* @return the JoinDialog instance
*/
public JoinDialog getJoinDialog() {
return joinDialog;
}
private void leaveHost() {
subDialog.hide();
dialog.show();
/**
* Gets the HostDialog instance.
*
* @return the HostDialog instance
*/
public HostDialog getHostDialog() {
return hostDialog;
subDialog = null;
}
}

View File

@@ -2,325 +2,205 @@
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.system.NanoTimer;
import com.jme3.texture.Texture;
import pp.mdga.client.dialog.GetPercentRunnable;
import pp.mdga.client.dialog.PercentRunnable;
import pp.mdga.client.dialog.SettingsButtonDialog;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.SettingsButton;
import pp.mdga.client.dialog.AudioSettingsDialog;
import pp.mdga.client.dialog.SettingsDialog;
import pp.mdga.client.dialog.VideoSettingsDialog;
/**
* Abstract class representing a view in the MDGA application.
*/
public abstract class MdgaView {
/**
* Enum representing different types of overlays.
*/
public enum Overlay {
INTERRUPT,
SETTINGS,
}
protected MdgaApp app;
protected Node rootNode = new Node("View Root");
protected Node guiNode = new Node("View Root GUI");
protected Node settingsNode = new Node("View Root Overlay");
protected Node node;
private SettingsButton settingsButton;
protected int depth = 0;
private boolean isVideo = false;
private boolean isAudio = false;
private SettingsDialog settingsDialog;
private VideoSettingsDialog videoSettingsDialog;
private AudioSettingsDialog audioSettingsDialog;
protected Node settingsNode;
protected Node audioSettingsNode;
protected Node videoSettingsNode;
protected LabelButton infoLabel = null;
protected NanoTimer infoTimer = new NanoTimer();
private SettingsButtonDialog settingsButton;
private int settingsDepth = 0;
private Geometry settingsBackground;
private Geometry audioBackground;
private Geometry videoBackground;
private SettingsDialog settings;
private SettingsDialog audio;
private SettingsDialog video;
/**
* Constructor for MdgaView.
*
* @param app the application instance
*/
public MdgaView(MdgaApp app) {
this.app = app;
settingsButton = new SettingsButton(app, guiNode, this::enterSettings);
this.node = new Node();
settingsDialog = new SettingsDialog(app, settingsNode, this);
videoSettingsDialog = new VideoSettingsDialog(app, settingsNode, this);
audioSettingsDialog = new AudioSettingsDialog(app, settingsNode, this);
this.settingsNode = new Node();
this.audioSettingsNode = new Node();
this.videoSettingsNode = new Node();
this.settingsButton = new SettingsButtonDialog(app, node, "", () -> enterSettings(false));
this.settingsBackground = createBackground("background/zahnräder.png");
settingsNode.attachChild(settingsBackground);
this.audioBackground = createBackground("background/lautsprecher.png");
audioSettingsNode.attachChild(audioBackground);
this.videoBackground = createBackground("background/monitors.png");
videoSettingsNode.attachChild(videoBackground);
Vector3f size = new Vector3f(280, 60, 0);
this.settings = new SettingsDialog(app, settingsNode, "zahnrad.png");
this.settings.addButton("Video", () -> enterVideo(), size);
this.settings.addButton("Audio", () -> enterAudio(), size);
this.settings.addButton("Zurück", () -> leaveSettings(false), size);
this.audio = new SettingsDialog(app, audioSettingsNode, "audio_icon.png");
this.audio.addSlider("Lautstärke", new PercentRunnable(app.getAcousticHandler()::setMainVolume), new GetPercentRunnable(app.getAcousticHandler()::getMainVolume), size, (int) app.getAcousticHandler().getMainVolume() * 100);
this.audio.addSlider("Musik", new PercentRunnable(app.getAcousticHandler()::setMusicVolume), new GetPercentRunnable(app.getAcousticHandler()::getMusicVolume), size, (int) app.getAcousticHandler().getMusicVolume() * 100);
this.audio.addSlider("Sound", new PercentRunnable(app.getAcousticHandler()::setSoundVolume), new GetPercentRunnable(app.getAcousticHandler()::getSoundVolume), size, (int) app.getAcousticHandler().getSoundVolume() * 100);
this.audio.addButton("Zurück", () -> leaveAudio(), size);
this.video = new SettingsDialog(app, videoSettingsNode, "monitor.png");
this.video.addButton("A", () -> System.out.println("A"), size);
this.video.addButton("B", () -> System.out.println("B"), size);
this.video.addButton("Zurück", () -> leaveVideo(), size);
}
/**
* Method to enter the view.
*/
public void enter() {
app.getRootNode().attachChild(rootNode);
app.getGuiNode().attachChild(guiNode);
app.getGuiNode().attachChild(node);
audio.initVolume();
settingsButton.show();
onEnter();
}
/**
* Method to leave the view.
*/
public void leave() {
onLeave();
settingsButton.hide();
while (settingsDepth > 0) {
pressEscape();
}
app.getRootNode().detachChild(rootNode);
app.getGuiNode().detachChild(guiNode);
app.getGuiNode().detachChild(node);
}
/**
* Method to enter an overlay.
*
* @param overlay the overlay to enter
*/
public void enterOverlay(Overlay overlay) {
app.getGuiNode().detachChild(guiNode);
onEnterOverlay(overlay);
public void update() {
audio.update();
onUpdate();
}
/**
* Method to leave an overlay.
*
* @param overlay the overlay to leave
*/
public void leaveOverlay(Overlay overlay) {
app.getGuiNode().attachChild(guiNode);
onLeaveOverlay(overlay);
}
/**
* Method to update the view.
*
* @param tpf time per frame
*/
public void update(float tpf) {
videoSettingsDialog.update();
audioSettingsDialog.update();
if (null != infoLabel && infoTimer.getTimeInSeconds() > 5) {
infoLabel.hide();
infoLabel = null;
}
onUpdate(tpf);
}
/**
* Abstract method to handle entering the view.
*/
protected abstract void onEnter();
/**
* Abstract method to handle leaving the view.
*/
protected abstract void onLeave();
protected void onUpdate() {}
/**
* Method to handle updating the view.
*
* @param tpf time per frame
*/
protected void onUpdate(float tpf) {
}
/**
* Abstract method to handle entering an overlay.
*
* @param overlay the overlay to enter
*/
protected abstract void onEnterOverlay(Overlay overlay);
/**
* Abstract method to handle leaving an overlay.
*
* @param overlay the overlay to leave
*/
protected abstract void onLeaveOverlay(Overlay overlay);
/**
* Method to create a background geometry with a texture.
*
* @param texturePath the path to the texture
* @return the created background geometry
*/
protected Geometry createBackground(String texturePath) {
TextureKey key = new TextureKey(texturePath, true);
Texture backgroundTexture = app.getAssetManager().loadTexture(key);
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
Geometry background = new Geometry("Background", quad);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", backgroundTexture);
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
Geometry background = new Geometry("Background", quad);
background.setMaterial(mat);
background.setLocalTranslation(0, 0, -2);
background.setLocalTranslation(0, 0, -1);
return background;
}
/**
* Method to enter the settings view.
*/
public void enterSettings() {
enterOverlay(Overlay.SETTINGS);
protected void enterExtendedSettings() {}
protected void leaveExtendedSettings() {}
protected void enterSettings(boolean soft) {
leave();
app.getGuiNode().attachChild(settingsNode);
settingsDialog.show();
if(!soft) {
depth++;
settingsDepth++;
enterExtendedSettings();
}
settings.show();
}
/**
* Method to leave the settings view.
*/
public void leaveSettings() {
leaveOverlay(Overlay.SETTINGS);
protected void leaveSettings(boolean soft) {
settings.hide();
app.getGuiNode().detachChild(settingsNode);
settingsDialog.hide();
settingsDepth--;
if(!soft) {
leaveExtendedSettings();
enter();
depth--;
}
}
/**
* Method to enter the video settings view.
*/
public void enterVideoSettings() {
settingsDialog.hide();
videoSettingsDialog.show();
protected void enterAudio() {
leaveSettings(true);
settingsDepth++;
depth++;
isAudio = true;
app.getGuiNode().attachChild(audioSettingsNode);
audio.show();
}
/**
* Method to leave the video settings view.
*/
public void leaveVideoSettings() {
settingsDialog.show();
videoSettingsDialog.hide();
protected void leaveAudio() {
audio.hide();
settingsDepth--;
app.getGuiNode().detachChild(audioSettingsNode);
isAudio = false;
depth--;
enterSettings(true);
}
/**
* Method to enter the audio settings view.
*/
public void enterAudioSettings() {
settingsDialog.hide();
audioSettingsDialog.show();
protected void enterVideo() {
leaveSettings(true);
settingsDepth++;
app.getGuiNode().attachChild(videoSettingsNode);
depth++;
isVideo = true;
video.show();
}
/**
* Method to leave the audio settings view.
*/
public void leaveAudioSettings() {
settingsDialog.show();
audioSettingsDialog.hide();
protected void leaveVideo() {
video.hide();
settingsDepth--;
app.getGuiNode().detachChild(videoSettingsNode);
depth--;
isVideo = false;
enterSettings(true);
}
/**
* Method to leave advanced settings.
*/
private void leaveAdvanced() {
settingsDialog.show();
audioSettingsDialog.hide();
videoSettingsDialog.hide();
settingsDepth--;
}
/**
* Method to handle pressing the escape key.
*/
public void pressEscape() {
if (settingsDepth == 0) {
enterSettings();
} else if (settingsDepth == 1) {
leaveSettings();
} else {
leaveAdvanced();
if(depth == 0) {
enterSettings(false);
} else if(depth == 1) {
leaveSettings(false);
}
}
/**
* Method to handle pressing the forward key.
*/
public void pressForward() {
if (this instanceof MainView mainView) {
mainView.forward(false);
app.getAcousticHandler().playSound(MdgaSound.BUTTON_PRESSED);
}
if (this instanceof LobbyView lobbyView) {
lobbyView.ready();
}
if (this instanceof GameView gameView) {
if (gameView.needConfirm) {
app.getModelSynchronize().confirm();
} else if (gameView.needNoPower) {
app.getModelSynchronize().confirm();
} else {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
else if (depth == 2){
if(isVideo) {
leaveVideo();
}
if(isAudio) {
leaveAudio();
}
}
if (this instanceof CeremonyView ceremonyView) {
ceremonyView.forward();
}
}
/**
* Method to show information on the view.
*
* @param error the error message
* @param isError flag indicating if it is an error
*/
public void showInfo(String error, boolean isError) {
infoTimer.reset();
if (null != infoLabel) {
infoLabel.hide();
}
infoLabel = new LabelButton(app, guiNode, error, new Vector2f(5.5f, 2), new Vector2f(0.5f, AbstractButton.VERTICAL - 0.5f), false);
ColorRGBA color;
if (isError) {
color = ColorRGBA.Red.clone();
} else {
color = ColorRGBA.Green.clone();
throw new RuntimeException();
}
infoLabel.setColor(ColorRGBA.Black, color);
}
}

View File

@@ -1,195 +0,0 @@
info face=null size=178 bold=0 italic=0 charset=ASCII unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
common lineHeight=214 base=26 scaleW=2048 scaleH=2048 pages=1 packed=0
page id=0 file="Gunplay.png"
chars count=255
char id=9 x=0 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=10 x=6 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=13 x=12 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=32 x=18 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=59 page=0 chnl=0
char id=33 x=24 y=46 width=40 height=220 xoffset=8 yoffset=0 xadvance=49 page=0 chnl=0
char id=34 x=64 y=46 width=62 height=220 xoffset=8 yoffset=0 xadvance=71 page=0 chnl=0
char id=35 x=126 y=46 width=95 height=220 xoffset=3 yoffset=0 xadvance=95 page=0 chnl=0
char id=36 x=221 y=46 width=84 height=220 xoffset=4 yoffset=0 xadvance=86 page=0 chnl=0
char id=37 x=305 y=46 width=148 height=220 xoffset=6 yoffset=0 xadvance=153 page=0 chnl=0
char id=38 x=453 y=46 width=127 height=220 xoffset=6 yoffset=0 xadvance=131 page=0 chnl=0
char id=39 x=580 y=46 width=31 height=220 xoffset=8 yoffset=0 xadvance=39 page=0 chnl=0
char id=40 x=611 y=46 width=44 height=220 xoffset=4 yoffset=0 xadvance=44 page=0 chnl=0
char id=41 x=655 y=46 width=44 height=220 xoffset=4 yoffset=0 xadvance=44 page=0 chnl=0
char id=42 x=699 y=46 width=71 height=220 xoffset=4 yoffset=0 xadvance=71 page=0 chnl=0
char id=43 x=770 y=46 width=86 height=220 xoffset=3 yoffset=0 xadvance=85 page=0 chnl=0
char id=44 x=856 y=46 width=38 height=220 xoffset=7 yoffset=0 xadvance=43 page=0 chnl=0
char id=45 x=894 y=46 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=46 x=959 y=46 width=39 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=47 x=998 y=46 width=102 height=220 xoffset=1 yoffset=0 xadvance=97 page=0 chnl=0
char id=48 x=1100 y=46 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=49 x=1207 y=46 width=61 height=220 xoffset=2 yoffset=0 xadvance=64 page=0 chnl=0
char id=50 x=1268 y=46 width=104 height=220 xoffset=6 yoffset=0 xadvance=111 page=0 chnl=0
char id=51 x=1372 y=46 width=100 height=220 xoffset=6 yoffset=0 xadvance=106 page=0 chnl=0
char id=52 x=1472 y=46 width=111 height=220 xoffset=3 yoffset=0 xadvance=109 page=0 chnl=0
char id=53 x=1583 y=46 width=98 height=220 xoffset=7 yoffset=0 xadvance=105 page=0 chnl=0
char id=54 x=1681 y=46 width=104 height=220 xoffset=8 yoffset=0 xadvance=112 page=0 chnl=0
char id=55 x=1785 y=46 width=92 height=220 xoffset=6 yoffset=0 xadvance=93 page=0 chnl=0
char id=56 x=1877 y=46 width=100 height=220 xoffset=7 yoffset=0 xadvance=107 page=0 chnl=0
char id=57 x=0 y=266 width=105 height=220 xoffset=7 yoffset=0 xadvance=112 page=0 chnl=0
char id=58 x=105 y=266 width=36 height=220 xoffset=8 yoffset=0 xadvance=43 page=0 chnl=0
char id=59 x=141 y=266 width=37 height=220 xoffset=7 yoffset=0 xadvance=43 page=0 chnl=0
char id=60 x=178 y=266 width=59 height=220 xoffset=1 yoffset=0 xadvance=58 page=0 chnl=0
char id=61 x=237 y=266 width=92 height=220 xoffset=8 yoffset=0 xadvance=97 page=0 chnl=0
char id=62 x=329 y=266 width=59 height=220 xoffset=8 yoffset=0 xadvance=61 page=0 chnl=0
char id=63 x=388 y=266 width=96 height=220 xoffset=4 yoffset=0 xadvance=98 page=0 chnl=0
char id=64 x=484 y=266 width=132 height=220 xoffset=4 yoffset=0 xadvance=136 page=0 chnl=0
char id=65 x=616 y=266 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=66 x=736 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=67 x=843 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=68 x=950 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=69 x=1057 y=266 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=70 x=1144 y=266 width=87 height=220 xoffset=8 yoffset=0 xadvance=91 page=0 chnl=0
char id=71 x=1231 y=266 width=105 height=220 xoffset=8 yoffset=0 xadvance=114 page=0 chnl=0
char id=72 x=1336 y=266 width=105 height=220 xoffset=8 yoffset=0 xadvance=114 page=0 chnl=0
char id=73 x=1441 y=266 width=41 height=220 xoffset=8 yoffset=0 xadvance=50 page=0 chnl=0
char id=74 x=1482 y=266 width=63 height=220 xoffset=3 yoffset=0 xadvance=67 page=0 chnl=0
char id=75 x=1545 y=266 width=114 height=220 xoffset=8 yoffset=0 xadvance=118 page=0 chnl=0
char id=76 x=1659 y=266 width=83 height=220 xoffset=8 yoffset=0 xadvance=87 page=0 chnl=0
char id=77 x=1742 y=266 width=142 height=220 xoffset=8 yoffset=0 xadvance=151 page=0 chnl=0
char id=78 x=1884 y=266 width=111 height=220 xoffset=8 yoffset=0 xadvance=121 page=0 chnl=0
char id=79 x=0 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=80 x=107 y=486 width=105 height=220 xoffset=8 yoffset=0 xadvance=108 page=0 chnl=0
char id=81 x=212 y=486 width=107 height=220 xoffset=6 yoffset=0 xadvance=113 page=0 chnl=0
char id=82 x=319 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=113 page=0 chnl=0
char id=83 x=426 y=486 width=112 height=220 xoffset=6 yoffset=0 xadvance=117 page=0 chnl=0
char id=84 x=538 y=486 width=107 height=220 xoffset=3 yoffset=0 xadvance=105 page=0 chnl=0
char id=85 x=645 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=86 x=752 y=486 width=117 height=220 xoffset=1 yoffset=0 xadvance=111 page=0 chnl=0
char id=87 x=869 y=486 width=169 height=220 xoffset=1 yoffset=0 xadvance=165 page=0 chnl=0
char id=88 x=1038 y=486 width=123 height=220 xoffset=3 yoffset=0 xadvance=124 page=0 chnl=0
char id=89 x=1161 y=486 width=119 height=220 xoffset=2 yoffset=0 xadvance=115 page=0 chnl=0
char id=90 x=1280 y=486 width=101 height=220 xoffset=5 yoffset=0 xadvance=104 page=0 chnl=0
char id=91 x=1381 y=486 width=37 height=220 xoffset=8 yoffset=0 xadvance=44 page=0 chnl=0
char id=92 x=1418 y=486 width=102 height=220 xoffset=1 yoffset=0 xadvance=97 page=0 chnl=0
char id=93 x=1520 y=486 width=37 height=220 xoffset=8 yoffset=0 xadvance=44 page=0 chnl=0
char id=94 x=1557 y=486 width=95 height=220 xoffset=4 yoffset=0 xadvance=96 page=0 chnl=0
char id=95 x=1652 y=486 width=125 height=220 xoffset=-1 yoffset=0 xadvance=115 page=0 chnl=0
char id=96 x=1777 y=486 width=57 height=220 xoffset=-2 yoffset=0 xadvance=56 page=0 chnl=0
char id=97 x=1834 y=486 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=98 x=1932 y=486 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=99 x=0 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=100 x=91 y=706 width=90 height=220 xoffset=4 yoffset=0 xadvance=96 page=0 chnl=0
char id=101 x=181 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=102 x=272 y=706 width=70 height=220 xoffset=1 yoffset=0 xadvance=66 page=0 chnl=0
char id=103 x=342 y=706 width=89 height=220 xoffset=6 yoffset=0 xadvance=96 page=0 chnl=0
char id=104 x=431 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=105 x=522 y=706 width=37 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=106 x=559 y=706 width=57 height=220 xoffset=-14 yoffset=0 xadvance=44 page=0 chnl=0
char id=107 x=616 y=706 width=98 height=220 xoffset=8 yoffset=0 xadvance=101 page=0 chnl=0
char id=108 x=714 y=706 width=37 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=109 x=751 y=706 width=138 height=220 xoffset=8 yoffset=0 xadvance=147 page=0 chnl=0
char id=110 x=889 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=111 x=980 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=112 x=1071 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=113 x=1162 y=706 width=90 height=220 xoffset=5 yoffset=0 xadvance=96 page=0 chnl=0
char id=114 x=1252 y=706 width=65 height=220 xoffset=8 yoffset=0 xadvance=69 page=0 chnl=0
char id=115 x=1317 y=706 width=91 height=220 xoffset=6 yoffset=0 xadvance=96 page=0 chnl=0
char id=116 x=1408 y=706 width=73 height=220 xoffset=1 yoffset=0 xadvance=68 page=0 chnl=0
char id=117 x=1481 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=118 x=1572 y=706 width=99 height=220 xoffset=1 yoffset=0 xadvance=95 page=0 chnl=0
char id=119 x=1671 y=706 width=146 height=220 xoffset=3 yoffset=0 xadvance=144 page=0 chnl=0
char id=120 x=1817 y=706 width=102 height=220 xoffset=4 yoffset=0 xadvance=103 page=0 chnl=0
char id=121 x=1919 y=706 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0
char id=122 x=0 y=926 width=84 height=220 xoffset=5 yoffset=0 xadvance=86 page=0 chnl=0
char id=123 x=84 y=926 width=67 height=220 xoffset=3 yoffset=0 xadvance=68 page=0 chnl=0
char id=124 x=151 y=926 width=27 height=220 xoffset=8 yoffset=0 xadvance=36 page=0 chnl=0
char id=125 x=178 y=926 width=67 height=220 xoffset=6 yoffset=0 xadvance=68 page=0 chnl=0
char id=126 x=245 y=926 width=93 height=220 xoffset=12 yoffset=0 xadvance=110 page=0 chnl=0
char id=161 x=338 y=926 width=40 height=220 xoffset=8 yoffset=0 xadvance=49 page=0 chnl=0
char id=162 x=378 y=926 width=82 height=220 xoffset=8 yoffset=0 xadvance=90 page=0 chnl=0
char id=163 x=460 y=926 width=93 height=220 xoffset=5 yoffset=0 xadvance=95 page=0 chnl=0
char id=164 x=553 y=926 width=80 height=220 xoffset=12 yoffset=0 xadvance=97 page=0 chnl=0
char id=165 x=633 y=926 width=119 height=220 xoffset=4 yoffset=0 xadvance=120 page=0 chnl=0
char id=166 x=752 y=926 width=27 height=220 xoffset=8 yoffset=0 xadvance=35 page=0 chnl=0
char id=167 x=779 y=926 width=95 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=168 x=874 y=926 width=68 height=220 xoffset=8 yoffset=0 xadvance=78 page=0 chnl=0
char id=169 x=942 y=926 width=153 height=220 xoffset=7 yoffset=0 xadvance=157 page=0 chnl=0
char id=170 x=1095 y=926 width=68 height=220 xoffset=7 yoffset=0 xadvance=75 page=0 chnl=0
char id=171 x=1163 y=926 width=77 height=220 xoffset=8 yoffset=0 xadvance=88 page=0 chnl=0
char id=172 x=1240 y=926 width=102 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=174 x=1342 y=926 width=93 height=220 xoffset=5 yoffset=0 xadvance=95 page=0 chnl=0
char id=175 x=1435 y=926 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=176 x=1500 y=926 width=70 height=220 xoffset=6 yoffset=0 xadvance=73 page=0 chnl=0
char id=177 x=1570 y=926 width=93 height=220 xoffset=12 yoffset=0 xadvance=110 page=0 chnl=0
char id=178 x=1663 y=926 width=55 height=220 xoffset=8 yoffset=0 xadvance=63 page=0 chnl=0
char id=179 x=1718 y=926 width=53 height=220 xoffset=8 yoffset=0 xadvance=60 page=0 chnl=0
char id=180 x=1771 y=926 width=60 height=220 xoffset=9 yoffset=0 xadvance=61 page=0 chnl=0
char id=182 x=1831 y=926 width=73 height=220 xoffset=14 yoffset=0 xadvance=92 page=0 chnl=0
char id=183 x=1904 y=926 width=39 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=184 x=1943 y=926 width=44 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=185 x=1987 y=926 width=33 height=220 xoffset=9 yoffset=0 xadvance=46 page=0 chnl=0
char id=186 x=0 y=1146 width=69 height=220 xoffset=7 yoffset=0 xadvance=78 page=0 chnl=0
char id=187 x=69 y=1146 width=77 height=220 xoffset=8 yoffset=0 xadvance=88 page=0 chnl=0
char id=188 x=146 y=1146 width=128 height=220 xoffset=10 yoffset=0 xadvance=139 page=0 chnl=0
char id=189 x=274 y=1146 width=126 height=220 xoffset=9 yoffset=0 xadvance=140 page=0 chnl=0
char id=190 x=400 y=1146 width=135 height=220 xoffset=3 yoffset=0 xadvance=138 page=0 chnl=0
char id=191 x=535 y=1146 width=97 height=220 xoffset=4 yoffset=0 xadvance=98 page=0 chnl=0
char id=192 x=632 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=193 x=752 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=194 x=872 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=195 x=992 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=196 x=1112 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=197 x=1232 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=198 x=1352 y=1146 width=131 height=220 xoffset=1 yoffset=0 xadvance=133 page=0 chnl=0
char id=199 x=1483 y=1146 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=200 x=1590 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=201 x=1677 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=202 x=1764 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=203 x=1851 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=204 x=1938 y=1146 width=56 height=220 xoffset=-3 yoffset=0 xadvance=50 page=0 chnl=0
char id=205 x=0 y=1366 width=60 height=220 xoffset=2 yoffset=0 xadvance=50 page=0 chnl=0
char id=206 x=60 y=1366 width=69 height=220 xoffset=-6 yoffset=0 xadvance=50 page=0 chnl=0
char id=207 x=129 y=1366 width=68 height=220 xoffset=-5 yoffset=0 xadvance=50 page=0 chnl=0
char id=208 x=197 y=1366 width=117 height=220 xoffset=5 yoffset=0 xadvance=123 page=0 chnl=0
char id=209 x=314 y=1366 width=111 height=220 xoffset=8 yoffset=0 xadvance=121 page=0 chnl=0
char id=210 x=425 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=211 x=532 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=212 x=639 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=213 x=746 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=214 x=853 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=215 x=960 y=1366 width=77 height=220 xoffset=8 yoffset=0 xadvance=85 page=0 chnl=0
char id=216 x=1037 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=217 x=1144 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=218 x=1251 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=219 x=1358 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=220 x=1465 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=221 x=1572 y=1366 width=119 height=220 xoffset=2 yoffset=0 xadvance=115 page=0 chnl=0
char id=222 x=1691 y=1366 width=84 height=220 xoffset=8 yoffset=0 xadvance=92 page=0 chnl=0
char id=223 x=1775 y=1366 width=99 height=220 xoffset=7 yoffset=0 xadvance=106 page=0 chnl=0
char id=224 x=1874 y=1366 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=225 x=0 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=226 x=98 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=227 x=196 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=228 x=294 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=229 x=392 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=230 x=490 y=1586 width=148 height=220 xoffset=7 yoffset=0 xadvance=155 page=0 chnl=0
char id=231 x=638 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=232 x=729 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=233 x=820 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=234 x=911 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=235 x=1002 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=236 x=1093 y=1586 width=56 height=220 xoffset=-5 yoffset=0 xadvance=46 page=0 chnl=0
char id=237 x=1149 y=1586 width=61 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=238 x=1210 y=1586 width=69 height=220 xoffset=-8 yoffset=0 xadvance=46 page=0 chnl=0
char id=239 x=1279 y=1586 width=67 height=220 xoffset=-7 yoffset=0 xadvance=46 page=0 chnl=0
char id=240 x=1346 y=1586 width=105 height=220 xoffset=8 yoffset=0 xadvance=112 page=0 chnl=0
char id=241 x=1451 y=1586 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=242 x=1542 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=243 x=1633 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=244 x=1724 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=245 x=1815 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=246 x=1906 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=247 x=0 y=1806 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=248 x=65 y=1806 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=249 x=156 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=250 x=247 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=251 x=338 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=252 x=429 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=253 x=520 y=1806 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0
char id=254 x=619 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=255 x=710 y=1806 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 KiB

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