Merge work #7
@@ -0,0 +1,131 @@
|
|||||||
|
package pp.mdga.client;
|
||||||
|
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
import com.simsilica.lemur.Container;
|
||||||
|
import com.simsilica.lemur.Label;
|
||||||
|
import com.simsilica.lemur.Button;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CeremonyView manages two sub-states: the Award Ceremony and the Statistics screen.
|
||||||
|
*/
|
||||||
|
public class CeremonyView extends MdgaView {
|
||||||
|
|
||||||
|
private enum SubState {
|
||||||
|
AWARD_CEREMONY,
|
||||||
|
STATISTICS
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubState currentSubState; // Tracks the current sub-state
|
||||||
|
private Node awardCeremonyNode; // Node for the award ceremony UI
|
||||||
|
private Node statisticsNode; // Node for the statistics UI
|
||||||
|
|
||||||
|
public CeremonyView(MdgaApp app) {
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enter() {
|
||||||
|
// Initialize sub-states
|
||||||
|
setupAwardCeremony();
|
||||||
|
setupStatistics();
|
||||||
|
|
||||||
|
// Start with the Award Ceremony state
|
||||||
|
switchToSubState(SubState.AWARD_CEREMONY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave() {
|
||||||
|
rootNode.detachAllChildren();
|
||||||
|
app.getGuiNode().detachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the Award Ceremony sub-state.
|
||||||
|
*/
|
||||||
|
private void setupAwardCeremony() {
|
||||||
|
awardCeremonyNode = new Node("AwardCeremonyNode");
|
||||||
|
|
||||||
|
// Add a background for the award ceremony
|
||||||
|
Geometry background = createBackground("b1.jpg");
|
||||||
|
awardCeremonyNode.attachChild(background);
|
||||||
|
|
||||||
|
// Create a container for the UI elements
|
||||||
|
Container container = new Container();
|
||||||
|
container.setLocalTranslation(300, app.getCamera().getHeight() - 100, 0);
|
||||||
|
|
||||||
|
container.addChild(new Label("GAME OVER"));
|
||||||
|
container.addChild(new Label("Spieler 1 auf Platz 1"));
|
||||||
|
container.addChild(new Label("Spieler 2 auf Platz 2"));
|
||||||
|
container.addChild(new Label("Spieler 3 auf Platz 3"));
|
||||||
|
|
||||||
|
Button continueButton = container.addChild(new Button("Weiter"));
|
||||||
|
continueButton.addClickCommands(source -> switchToSubState(SubState.STATISTICS));
|
||||||
|
|
||||||
|
awardCeremonyNode.attachChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the Statistics sub-state.
|
||||||
|
*/
|
||||||
|
private void setupStatistics() {
|
||||||
|
statisticsNode = new Node("StatisticsNode");
|
||||||
|
|
||||||
|
// Add a background for the statistics
|
||||||
|
Geometry background = createBackground("b2.jpg");
|
||||||
|
statisticsNode.attachChild(background);
|
||||||
|
|
||||||
|
// Create a container for the statistics UI
|
||||||
|
Container container = new Container();
|
||||||
|
container.setLocalTranslation(200, app.getCamera().getHeight() - 100, 0);
|
||||||
|
|
||||||
|
container.addChild(new Label("Spielerstatistiken"));
|
||||||
|
container.addChild(new Label("Spieler 1: Punkte 100"));
|
||||||
|
container.addChild(new Label("Spieler 2: Punkte 80"));
|
||||||
|
container.addChild(new Label("Spieler 3: Punkte 60"));
|
||||||
|
|
||||||
|
Button exitButton = container.addChild(new Button("Verlassen"));
|
||||||
|
exitButton.addClickCommands(source -> app.stop());
|
||||||
|
|
||||||
|
statisticsNode.attachChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches between the Award Ceremony and Statistics sub-states.
|
||||||
|
*
|
||||||
|
* @param subState The target sub-state.
|
||||||
|
*/
|
||||||
|
private void switchToSubState(SubState subState) {
|
||||||
|
rootNode.detachAllChildren();
|
||||||
|
currentSubState = subState;
|
||||||
|
|
||||||
|
switch (subState) {
|
||||||
|
case AWARD_CEREMONY:
|
||||||
|
rootNode.attachChild(awardCeremonyNode);
|
||||||
|
break;
|
||||||
|
case STATISTICS:
|
||||||
|
rootNode.attachChild(statisticsNode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.getGuiNode().attachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a background geometry with the given texture.
|
||||||
|
*
|
||||||
|
* @param texturePath The path to the background texture.
|
||||||
|
* @return The Geometry for the background.
|
||||||
|
*/
|
||||||
|
private Geometry createBackground(String texturePath) {
|
||||||
|
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
|
||||||
|
Geometry geom = new Geometry("Background", quad);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(texturePath));
|
||||||
|
geom.setMaterial(mat);
|
||||||
|
geom.setLocalTranslation(0, 0, -1); // Slightly behind other UI elements
|
||||||
|
return geom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,101 @@
|
|||||||
package pp.mdga.client.Dialog;
|
package pp.mdga.client.Dialog;
|
||||||
|
|
||||||
public class Dialog {
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.simsilica.lemur.Button;
|
||||||
|
import com.simsilica.lemur.Container;
|
||||||
|
import com.simsilica.lemur.GuiGlobals;
|
||||||
|
import pp.mdga.client.MdgaApp;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for dialogs in the game.
|
||||||
|
*/
|
||||||
|
public abstract class Dialog {
|
||||||
|
|
||||||
|
protected MdgaApp app; // Reference to the MdgaApp
|
||||||
|
protected Container dialogContainer; // Lemur container for the dialog UI
|
||||||
|
private boolean isVisible; // Visibility flag for the dialog
|
||||||
|
protected Map<String, Button> buttons; // Map to store buttons by their IDs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the dialog.
|
||||||
|
* @param app The MdgaApp instance to attach this dialog to.
|
||||||
|
*/
|
||||||
|
public Dialog(MdgaApp app) {
|
||||||
|
this.app = app;
|
||||||
|
this.buttons = new HashMap<>();
|
||||||
|
|
||||||
|
// Initialize Lemur GUI system if not already done
|
||||||
|
GuiGlobals.initialize(app);
|
||||||
|
|
||||||
|
// Create the dialog container
|
||||||
|
dialogContainer = new Container();
|
||||||
|
dialogContainer.setLocalTranslation(100, 500, 0); // Default position, can be adjusted
|
||||||
|
buildDialogContents(dialogContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method for building the dialog contents.
|
||||||
|
* Subclasses must override this to define their specific UI.
|
||||||
|
*
|
||||||
|
* @param container The container to add UI elements to.
|
||||||
|
*/
|
||||||
|
protected abstract void buildDialogContents(Container container);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a button to the dialog.
|
||||||
|
*
|
||||||
|
* @param id Unique ID for the button.
|
||||||
|
* @param name Display name of the button.
|
||||||
|
* @param container The container to which the button is added.
|
||||||
|
* @return The created Button.
|
||||||
|
*/
|
||||||
|
protected Button addButton(String id, String name, Container container) {
|
||||||
|
Button button = new Button(name);
|
||||||
|
buttons.put(id, button);
|
||||||
|
container.addChild(button);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a button by its ID.
|
||||||
|
*
|
||||||
|
* @param id The ID of the button.
|
||||||
|
* @return The Button, or null if not found.
|
||||||
|
*/
|
||||||
|
public Button getButton(String id) {
|
||||||
|
return buttons.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the dialog by attaching it to the GUI node of the MdgaApp.
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
if (!isVisible) {
|
||||||
|
app.getGuiNode().attachChild(dialogContainer);
|
||||||
|
isVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the dialog by detaching it from the GUI node of the MdgaApp.
|
||||||
|
*/
|
||||||
|
public void hide() {
|
||||||
|
if (isVisible) {
|
||||||
|
dialogContainer.removeFromParent();
|
||||||
|
isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up resources used by the dialog.
|
||||||
|
* Call this when the dialog is no longer needed.
|
||||||
|
*/
|
||||||
|
public void cleanup() {
|
||||||
|
hide();
|
||||||
|
buttons.clear();
|
||||||
|
dialogContainer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
package pp.mdga.client.Dialog;
|
package pp.mdga.client.Dialog;
|
||||||
|
|
||||||
public class SoundDialog {
|
import com.jme3.scene.Node;
|
||||||
|
import com.simsilica.lemur.Container;
|
||||||
|
import com.simsilica.lemur.Label;
|
||||||
|
import com.simsilica.lemur.input.Button;
|
||||||
|
import pp.mdga.client.MdgaApp;
|
||||||
|
|
||||||
|
public class SoundDialog extends Dialog {
|
||||||
|
|
||||||
|
public SoundDialog(MdgaApp app) {
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void buildDialogContents(Container container) {
|
||||||
|
// Add a title
|
||||||
|
container.addChild(new Label("Settings"));
|
||||||
|
|
||||||
|
// Add buttons with ID and name
|
||||||
|
addButton("volume_up", "Increase Volume", container)
|
||||||
|
.addClickCommands(source -> System.out.println("Volume increased"));
|
||||||
|
|
||||||
|
addButton("volume_down", "Decrease Volume", container)
|
||||||
|
.addClickCommands(source -> System.out.println("Volume decreased"));
|
||||||
|
|
||||||
|
// Add a close button
|
||||||
|
addButton("close", "Close", container)
|
||||||
|
.addClickCommands(source -> hide()); // Close the dialog
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package pp.mdga.client;
|
||||||
|
|
||||||
|
public class GameView {
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package pp.mdga.client.Gui;
|
|
||||||
|
|
||||||
public class GuiView {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package pp.mdga.client;
|
||||||
|
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lobby view, allowing players to choose a color.
|
||||||
|
*/
|
||||||
|
public class LobbyView extends MdgaView {
|
||||||
|
|
||||||
|
public LobbyView(MdgaApp app) {
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enter() {
|
||||||
|
// Add a background
|
||||||
|
Geometry background = createBackground("sky.jpg");
|
||||||
|
rootNode.attachChild(background);
|
||||||
|
|
||||||
|
// Add color selection boxes
|
||||||
|
float boxSize = 100;
|
||||||
|
float spacing = 20;
|
||||||
|
float totalWidth = 4 * boxSize + 3 * spacing;
|
||||||
|
float startX = (app.getCamera().getWidth() - totalWidth) / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Geometry colorBox = createColorBox(startX + i * (boxSize + spacing), app.getCamera().getHeight() / 2 - boxSize / 2, boxSize);
|
||||||
|
rootNode.attachChild(colorBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.getGuiNode().attachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave() {
|
||||||
|
rootNode.detachAllChildren();
|
||||||
|
app.getGuiNode().detachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a background geometry with the given texture.
|
||||||
|
*
|
||||||
|
* @param texturePath The path to the background texture.
|
||||||
|
* @return The Geometry for the background.
|
||||||
|
*/
|
||||||
|
private Geometry createBackground(String texturePath) {
|
||||||
|
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
|
||||||
|
Geometry geom = new Geometry("Background", quad);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(texturePath));
|
||||||
|
geom.setMaterial(mat);
|
||||||
|
geom.setLocalTranslation(0, 0, -1); // Slightly behind other UI elements
|
||||||
|
return geom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a color selection box.
|
||||||
|
*
|
||||||
|
* @param x The x-coordinate of the box.
|
||||||
|
* @param y The y-coordinate of the box.
|
||||||
|
* @param size The size of the box.
|
||||||
|
* @return The Geometry for the box.
|
||||||
|
*/
|
||||||
|
private Geometry createColorBox(float x, float y, float size) {
|
||||||
|
Quad quad = new Quad(size, size);
|
||||||
|
Geometry geom = new Geometry("ColorBox", quad);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", ColorRGBA.White); // Example texture/color
|
||||||
|
geom.setMaterial(mat);
|
||||||
|
geom.setLocalTranslation(x, y, 0);
|
||||||
|
return geom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package pp.mdga.client;
|
||||||
|
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
import com.simsilica.lemur.Button;
|
||||||
|
import com.simsilica.lemur.Container;
|
||||||
|
import com.simsilica.lemur.Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main menu view, providing options to host a game, join a game, or exit.
|
||||||
|
*/
|
||||||
|
public class MainMenuView extends MdgaView {
|
||||||
|
|
||||||
|
private Container menuContainer; // Lemur container for menu options
|
||||||
|
|
||||||
|
public MainMenuView(MdgaApp app) {
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enter() {
|
||||||
|
// Add a background
|
||||||
|
Geometry background = createBackground("powercards.png");
|
||||||
|
rootNode.attachChild(background);
|
||||||
|
|
||||||
|
// Create a menu container
|
||||||
|
menuContainer = new Container();
|
||||||
|
menuContainer.setLocalTranslation(300, app.getCamera().getHeight() - 200, 0);
|
||||||
|
|
||||||
|
// Add menu options
|
||||||
|
menuContainer.addChild(new Label("Main Menu"));
|
||||||
|
|
||||||
|
Button hostGameButton = menuContainer.addChild(new Button("Host Game"));
|
||||||
|
hostGameButton.addClickCommands(source -> System.out.println("Hosting game..."));
|
||||||
|
|
||||||
|
Button joinGameButton = menuContainer.addChild(new Button("Join Game"));
|
||||||
|
joinGameButton.addClickCommands(source -> System.out.println("Joining game..."));
|
||||||
|
|
||||||
|
Button exitButton = menuContainer.addChild(new Button("Exit"));
|
||||||
|
exitButton.addClickCommands(source -> app.stop());
|
||||||
|
|
||||||
|
rootNode.attachChild(menuContainer);
|
||||||
|
app.getGuiNode().attachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave() {
|
||||||
|
rootNode.detachAllChildren();
|
||||||
|
app.getGuiNode().detachChild(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a background geometry with the given texture.
|
||||||
|
*
|
||||||
|
* @param texturePath The path to the background texture.
|
||||||
|
* @return The Geometry for the background.
|
||||||
|
*/
|
||||||
|
private Geometry createBackground(String texturePath) {
|
||||||
|
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
|
||||||
|
Geometry geom = new Geometry("Background", quad);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(texturePath));
|
||||||
|
geom.setMaterial(mat);
|
||||||
|
geom.setLocalTranslation(0, 0, -1); // Slightly behind other UI elements
|
||||||
|
return geom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.system.NanoTimer;
|
import com.jme3.system.NanoTimer;
|
||||||
|
import com.simsilica.lemur.Button;
|
||||||
|
import com.simsilica.lemur.GuiGlobals;
|
||||||
import pp.mdga.client.Acoustic.AcousticHandler;
|
import pp.mdga.client.Acoustic.AcousticHandler;
|
||||||
import pp.mdga.client.Acoustic.MdgaSound;
|
import pp.mdga.client.Acoustic.MdgaSound;
|
||||||
import pp.mdga.client.Animation.AnimationHandler;
|
import pp.mdga.client.Animation.AnimationHandler;
|
||||||
import com.jme3.system.AppSettings;
|
import com.jme3.system.AppSettings;
|
||||||
import pp.mdga.client.Board.BoardView;
|
import pp.mdga.client.Board.BoardView;
|
||||||
import pp.mdga.client.Dialog.DialogView;
|
import pp.mdga.client.Dialog.DialogView;
|
||||||
|
import pp.mdga.client.Dialog.SoundDialog;
|
||||||
|
|
||||||
public class MdgaApp extends SimpleApplication {
|
public class MdgaApp extends SimpleApplication {
|
||||||
private AnimationHandler animationHandler;
|
private AnimationHandler animationHandler;
|
||||||
@@ -43,6 +46,22 @@ public void simpleInitApp() {
|
|||||||
|
|
||||||
acousticHandler.playSound(MdgaSound.LOST);
|
acousticHandler.playSound(MdgaSound.LOST);
|
||||||
acousticHandler.playSound(MdgaSound.VICTORY);
|
acousticHandler.playSound(MdgaSound.VICTORY);
|
||||||
|
|
||||||
|
//SoundDialog settingsDialog = new SoundDialog(this);
|
||||||
|
//settingsDialog.show();
|
||||||
|
|
||||||
|
// Interact with the dialog
|
||||||
|
//Button closeButton = settingsDialog.getButton("close");
|
||||||
|
//closeButton.addClickCommands(source -> System.out.println("Closing dialog..."));
|
||||||
|
|
||||||
|
GuiGlobals.initialize(this);
|
||||||
|
MainMenuView mainMenuView = new MainMenuView(this);
|
||||||
|
LobbyView lobbyView = new LobbyView(this);
|
||||||
|
//mainMenuView.enter();
|
||||||
|
//lobbyView.enter();
|
||||||
|
|
||||||
|
CeremonyView ceremonyView = new CeremonyView(this);
|
||||||
|
ceremonyView.enter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package pp.mdga.client;
|
||||||
|
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
|
||||||
|
public abstract class MdgaView {
|
||||||
|
protected MdgaApp app; // Reference to the game application
|
||||||
|
protected Node rootNode; // Node for the state-specific UI elements
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for MdgaView.
|
||||||
|
*
|
||||||
|
* @param app The MdgaApp instance.
|
||||||
|
*/
|
||||||
|
public MdgaView(MdgaApp app) {
|
||||||
|
this.app = app;
|
||||||
|
this.rootNode = new Node("StateViewRoot");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when entering this state.
|
||||||
|
* Subclasses should override this method to define specific behavior.
|
||||||
|
*/
|
||||||
|
public abstract void enter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when leaving this state.
|
||||||
|
* Subclasses should override this method to define specific behavior.
|
||||||
|
*/
|
||||||
|
public abstract void leave();
|
||||||
|
}
|
||||||
BIN
Projekte/mdga/client/src/main/resources/b1.jpg
Normal file
BIN
Projekte/mdga/client/src/main/resources/b1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
Projekte/mdga/client/src/main/resources/b2.jpg
Normal file
BIN
Projekte/mdga/client/src/main/resources/b2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
Projekte/mdga/client/src/main/resources/powercards.png
Normal file
BIN
Projekte/mdga/client/src/main/resources/powercards.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
Projekte/mdga/client/src/main/resources/sky.jpg
Normal file
BIN
Projekte/mdga/client/src/main/resources/sky.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
Reference in New Issue
Block a user