mirror of
https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-02.git
synced 2025-08-01 00:57:39 +02:00
working version of battleship
This commit is contained in:
@@ -10,6 +10,7 @@ package pp.battleship.game.client;
|
||||
import pp.battleship.message.client.ShootMessage;
|
||||
import pp.battleship.message.server.EffectMessage;
|
||||
import pp.battleship.model.IntPoint;
|
||||
import pp.battleship.model.Shell;
|
||||
import pp.battleship.model.ShipMap;
|
||||
import pp.battleship.notification.Sound;
|
||||
|
||||
@@ -53,16 +54,13 @@ class BattleState extends ClientState {
|
||||
@Override
|
||||
public void receivedEffect(EffectMessage msg) {
|
||||
ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
|
||||
playSound(msg);
|
||||
|
||||
myTurn = msg.isMyTurn();
|
||||
logic.setInfoText(msg.getInfoTextKey());
|
||||
affectedMap(msg).add(msg.getShot());
|
||||
if (destroyedOpponentShip(msg))
|
||||
logic.getOpponentMap().add(msg.getDestroyedShip());
|
||||
if (msg.isGameOver()) {
|
||||
msg.getRemainingOpponentShips().forEach(logic.getOwnMap()::add);
|
||||
logic.setState(new GameOverState(logic));
|
||||
}
|
||||
Shell shell = new Shell(msg.getShot());
|
||||
affectedMap(msg).add(shell);
|
||||
logic.playSound(Sound.SHELL_FLYING);
|
||||
logic.setState(new ShootingState(logic, shell, myTurn, msg));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,29 +72,4 @@ class BattleState extends ClientState {
|
||||
private ShipMap affectedMap(EffectMessage msg) {
|
||||
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the opponent's ship was destroyed by the player's shot.
|
||||
*
|
||||
* @param msg the effect message received from the server
|
||||
* @return true if the shot destroyed an opponent's ship, false otherwise
|
||||
*/
|
||||
private boolean destroyedOpponentShip(EffectMessage msg) {
|
||||
return msg.getDestroyedShip() != null && msg.isOwnShot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
|
||||
* or destruction of a ship.
|
||||
*
|
||||
* @param msg the effect message containing the result of the shot
|
||||
*/
|
||||
private void playSound(EffectMessage msg) {
|
||||
if (!msg.getShot().isHit())
|
||||
logic.playSound(Sound.SPLASH);
|
||||
else if (msg.getDestroyedShip() == null)
|
||||
logic.playSound(Sound.EXPLOSION);
|
||||
else
|
||||
logic.playSound(Sound.DESTROYED_SHIP);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,114 @@
|
||||
package pp.battleship.game.client;
|
||||
|
||||
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||
import pp.battleship.message.server.EffectMessage;
|
||||
import pp.battleship.model.Battleship;
|
||||
import pp.battleship.model.Shell;
|
||||
import pp.battleship.model.ShipMap;
|
||||
import pp.battleship.notification.Sound;
|
||||
|
||||
/**
|
||||
* Represents the shooting state of the game where a shell is fired at the opponent.
|
||||
*/
|
||||
public class ShootingState extends ClientState {
|
||||
private float shootValue;
|
||||
private final static float SHELL_SPEED = 0.3f;
|
||||
private final Shell shell;
|
||||
private final boolean myTurn;
|
||||
private final EffectMessage msg;
|
||||
|
||||
/**
|
||||
* Constructs a shooting state with the specified game logic.
|
||||
*
|
||||
* @param logic the game logic
|
||||
* @param shell the shell being shot
|
||||
* @param myTurn indicates if it is the player's turn
|
||||
* @param msg the effect message associated with the shooting action
|
||||
*/
|
||||
public ShootingState(ClientGameLogic logic, Shell shell, boolean myTurn, EffectMessage msg) {
|
||||
super(logic);
|
||||
this.msg = msg;
|
||||
this.myTurn = myTurn;
|
||||
this.shell = shell;
|
||||
this.shootValue = 0;
|
||||
shell.move(shootValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showBattle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the shooting state by moving the shell based on the elapsed time.
|
||||
*
|
||||
* @param delta the time in seconds since the last update
|
||||
*/
|
||||
@Override
|
||||
void update(float delta) {
|
||||
super.update(delta);
|
||||
if (shootValue > 1) {
|
||||
endState();
|
||||
}
|
||||
else {
|
||||
shootValue += delta * SHELL_SPEED;
|
||||
shell.move(shootValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the shooting state and processes the effects of the shot.
|
||||
*/
|
||||
private void endState() {
|
||||
playSound(msg);
|
||||
affectedMap(msg).add(msg.getShot());
|
||||
affectedMap(msg).remove(shell);
|
||||
|
||||
if (destroyedOpponentShip(msg))
|
||||
logic.getOpponentMap().add(msg.getDestroyedShip());
|
||||
if (msg.isGameOver()) {
|
||||
for (Battleship ship : msg.getRemainingOpponentShips()) {
|
||||
logic.getOpponentMap().add(ship);
|
||||
}
|
||||
logic.setState(new GameOverState(logic));
|
||||
return;
|
||||
}
|
||||
logic.send(new AnimationFinishedMessage());
|
||||
logic.setState(new BattleState(logic, myTurn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an opponent's ship was destroyed by the shot.
|
||||
*
|
||||
* @param msg the effect message containing the shot details
|
||||
* @return true if an opponent's ship was destroyed, false otherwise
|
||||
*/
|
||||
private boolean destroyedOpponentShip(EffectMessage msg) {
|
||||
return msg.getDestroyedShip() != null && msg.isOwnShot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the affected map based on whether the shot was owned by the player or the opponent.
|
||||
*
|
||||
* @param msg the effect message containing shot details
|
||||
* @return the ShipMap that was affected by the shot
|
||||
*/
|
||||
private ShipMap affectedMap(EffectMessage msg) {
|
||||
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
|
||||
* or destruction of a ship.
|
||||
*
|
||||
* @param msg the effect message containing the result of the shot
|
||||
*/
|
||||
private void playSound(EffectMessage msg) {
|
||||
if (!msg.getShot().isHit())
|
||||
logic.playSound(Sound.SPLASH);
|
||||
else if (msg.getDestroyedShip() == null)
|
||||
logic.playSound(Sound.EXPLOSION);
|
||||
else
|
||||
logic.playSound(Sound.DESTROYED_SHIP);
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@
|
||||
package pp.battleship.game.server;
|
||||
|
||||
import pp.battleship.BattleshipConfig;
|
||||
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||
import pp.battleship.message.client.ClientInterpreter;
|
||||
import pp.battleship.message.client.MapMessage;
|
||||
import pp.battleship.message.client.ShootMessage;
|
||||
@@ -38,6 +39,7 @@ public class ServerGameLogic implements ClientInterpreter {
|
||||
private final ServerSender serverSender;
|
||||
private Player activePlayer;
|
||||
private ServerState state = ServerState.WAIT;
|
||||
private Set<Player> waitPlayers = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Constructs a ServerGameLogic with the specified sender and configuration.
|
||||
@@ -142,8 +144,73 @@ public class ServerGameLogic implements ClientInterpreter {
|
||||
public void received(MapMessage msg, int from) {
|
||||
if (state != ServerState.SET_UP)
|
||||
LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS
|
||||
else
|
||||
playerReady(getPlayerById(from), msg.getShips());
|
||||
else {
|
||||
if (checkMap(msg.getShips())) {
|
||||
playerReady(getPlayerById(from), msg.getShips());
|
||||
}
|
||||
else {
|
||||
LOGGER.log(Level.WARNING, "Invalid Map sent from player {0}", from); //NON-NLS
|
||||
send(players.get(from), new GameDetails(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the reception of a AnimationFinishedMessage.
|
||||
*
|
||||
* @param msg the received MapMessage
|
||||
* @param from the ID of the sender client
|
||||
*/
|
||||
@Override
|
||||
public void received(AnimationFinishedMessage msg, int from) {
|
||||
if (state != ServerState.ANIMATION) {
|
||||
LOGGER.log(Level.ERROR, "animation finished not allowed in {0}", state);
|
||||
}
|
||||
else {
|
||||
LOGGER.log(Level.DEBUG, "anim received from {0}", getPlayerById(from));
|
||||
Player player = getPlayerById(from);
|
||||
if (!waitPlayers.add(player)) {
|
||||
LOGGER.log(Level.ERROR, "{0} already sent animation finished", player); //NON-NLS
|
||||
return;
|
||||
}
|
||||
if (waitPlayers.size() == 2) {
|
||||
waitPlayers = new HashSet<>();
|
||||
setState(ServerState.BATTLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the placement of battleships on the map.
|
||||
* Ensures that:
|
||||
* <ul>
|
||||
* <li>The number of ships matches the configuration.</li>
|
||||
* <li>Ships are within the map's boundaries.</li>
|
||||
* <li>Ships do not overlap.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param ships the list of {@link Battleship} objects to validate
|
||||
* @return {@code true} if all ships are placed correctly; {@code false} otherwise
|
||||
*/
|
||||
private boolean checkMap(List<Battleship> ships) {
|
||||
int numShips = config.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
|
||||
if (numShips != ships.size()) return false;
|
||||
|
||||
List<IntPoint> occupied = new ArrayList<>();
|
||||
|
||||
for (Battleship battleship : ships) {
|
||||
int x = battleship.getX();
|
||||
int y = battleship.getY();
|
||||
for (int i = 0; i < battleship.getLength(); i++) {
|
||||
if (x >= 0 && x < config.getMapWidth() && y >= 0 && y < config.getMapHeight() && !occupied.contains(new IntPoint(x, y))) {
|
||||
occupied.add(new IntPoint(x, y));
|
||||
x += battleship.getRot().dx();
|
||||
y += battleship.getRot().dy();
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,8 +223,11 @@ public class ServerGameLogic implements ClientInterpreter {
|
||||
public void received(ShootMessage msg, int from) {
|
||||
if (state != ServerState.BATTLE)
|
||||
LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
|
||||
else
|
||||
else{
|
||||
setState(ServerState.ANIMATION);
|
||||
shoot(getPlayerById(from), msg.getPosition());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,6 +26,11 @@ enum ServerState {
|
||||
*/
|
||||
BATTLE,
|
||||
|
||||
/**
|
||||
* The server is waiting for all clients to finish the shoot animation.
|
||||
*/
|
||||
ANIMATION,
|
||||
|
||||
/**
|
||||
* The game has ended because all the ships of one player have been destroyed.
|
||||
*/
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
package pp.battleship.game.singlemode;
|
||||
|
||||
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||
import pp.battleship.message.client.ClientInterpreter;
|
||||
import pp.battleship.message.client.ClientMessage;
|
||||
import pp.battleship.message.client.MapMessage;
|
||||
@@ -63,6 +64,11 @@ class Copycat implements ClientInterpreter {
|
||||
copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(AnimationFinishedMessage msg, int from) {
|
||||
copiedMessage = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the provided {@link Battleship}.
|
||||
*
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package pp.battleship.game.singlemode;
|
||||
|
||||
import pp.battleship.game.client.BattleshipClient;
|
||||
import pp.battleship.message.client.AnimationFinishedMessage;
|
||||
import pp.battleship.message.client.MapMessage;
|
||||
import pp.battleship.message.client.ShootMessage;
|
||||
import pp.battleship.message.server.EffectMessage;
|
||||
@@ -71,6 +72,7 @@ class RobotClient implements ServerInterpreter {
|
||||
* Makes the RobotClient take a shot by sending a ShootMessage with the target position.
|
||||
*/
|
||||
private void robotShot() {
|
||||
|
||||
connection.sendRobotMessage(new ShootMessage(getShotPosition()));
|
||||
}
|
||||
|
||||
@@ -121,6 +123,7 @@ class RobotClient implements ServerInterpreter {
|
||||
@Override
|
||||
public void received(EffectMessage msg) {
|
||||
LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS
|
||||
connection.sendRobotMessage(new AnimationFinishedMessage());
|
||||
if (msg.isMyTurn())
|
||||
shoot();
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
package pp.battleship.message.client;
|
||||
|
||||
import com.jme3.network.serializing.Serializable;
|
||||
|
||||
/**
|
||||
* Represents a message indicating that an animation has finished on the client side.
|
||||
*/
|
||||
@Serializable
|
||||
public class AnimationFinishedMessage extends ClientMessage {
|
||||
public AnimationFinishedMessage() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a visitor to process this message.
|
||||
*
|
||||
* @param interpreter the visitor to process this message
|
||||
* @param from the connection ID from which the message was received
|
||||
*/
|
||||
@Override
|
||||
public void accept(ClientInterpreter interpreter, int from) {
|
||||
interpreter.received(this, from);
|
||||
}
|
||||
}
|
@@ -26,4 +26,12 @@ public interface ClientInterpreter {
|
||||
* @param from the connection ID from which the message was received
|
||||
*/
|
||||
void received(MapMessage msg, int from);
|
||||
|
||||
/**
|
||||
* Processes a received AnimationFinishedMessage.
|
||||
*
|
||||
* @param msg the MapMessage to be processed
|
||||
* @param from the connection ID from which the message was received
|
||||
*/
|
||||
void received(AnimationFinishedMessage msg, int from);
|
||||
}
|
||||
|
@@ -0,0 +1,125 @@
|
||||
package pp.battleship.model;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/**
|
||||
* The {@code Shell} class represents a projectile fired by a ship in the Battleship game.
|
||||
* It models the position and rotation of the projectile and allows for its movement along
|
||||
* a Bezier curve.
|
||||
*/
|
||||
public class Shell implements Item {
|
||||
/**
|
||||
* Initial position of the shell
|
||||
*/
|
||||
private final static Vector3f INIT_POS = new Vector3f(-3, 7, -3);
|
||||
/**
|
||||
* The overshot difference vector used to get a shallower flight path
|
||||
*/
|
||||
private final static Vector3f OVER_SHOT_DIFF = new Vector3f(-1, -1, -1);
|
||||
// Target shot position
|
||||
private final Vector3f shotPosition;
|
||||
// Position on top of shotPosition used for Bezier curve
|
||||
private final Vector3f overShotPosition;
|
||||
// Current position of the shell
|
||||
private Vector3f position;
|
||||
// Current rotation of the shell
|
||||
private final Quaternion rotation;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Shell} object using the given {@code Shot} target.
|
||||
* The initial position, target position, and overshot position are calculated.
|
||||
*
|
||||
* @param shot The target {@code Shot} object containing the destination coordinates.
|
||||
*/
|
||||
public Shell(Shot shot) {
|
||||
this.shotPosition = new Vector3f(shot.getX(), 0, shot.getY());
|
||||
this.overShotPosition = new Vector3f(shotPosition.x, INIT_POS.y, shotPosition.z).add(OVER_SHOT_DIFF);
|
||||
this.position = INIT_POS;
|
||||
this.rotation = new Quaternion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current position of the shell.
|
||||
*
|
||||
* @return The current position as a {@code Vector3f}.
|
||||
*/
|
||||
public Vector3f getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current rotation of the shell.
|
||||
*
|
||||
* @return The current rotation as a {@code Quaternion}.
|
||||
*/
|
||||
public Quaternion getRotation() {
|
||||
return this.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the shell along a Bezier curve based on the given time factor {@code t}.
|
||||
* The position and rotation of the shell are updated.
|
||||
*
|
||||
* @param t The time factor between 0 and 1, representing the progress of the shell's flight.
|
||||
*/
|
||||
public void move(float t) {
|
||||
if (t > 1f) t = 1f;
|
||||
Vector3f newPosition = bezInt(INIT_POS, overShotPosition, shotPosition, t);
|
||||
updateRotation(position, newPosition);
|
||||
this.position = newPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a quadratic Bezier interpolation between three points based on the time factor {@code t}.
|
||||
*
|
||||
* @param p1 The start position.
|
||||
* @param p2 The overshot position.
|
||||
* @param p3 The target position.
|
||||
* @param t The time factor for interpolation.
|
||||
* @return The interpolated position as a {@code Vector3f}.
|
||||
*/
|
||||
private Vector3f bezInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
|
||||
Vector3f inA = linInt(p1, p2, t);
|
||||
Vector3f inB = linInt(p2, p3, t);
|
||||
return linInt(inA, inB, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two points {@code p1} and {@code p2} based on the time factor {@code t}.
|
||||
*
|
||||
* @param p1 The start position.
|
||||
* @param p2 The end position.
|
||||
* @param t The time factor for interpolation.
|
||||
* @return The interpolated position as a {@code Vector3f}.
|
||||
*/
|
||||
private Vector3f linInt(Vector3f p1, Vector3f p2, float t) {
|
||||
float x = p1.getX() + t * (p2.getX() - p1.getX());
|
||||
float y = p1.getY() + t * (p2.getY() - p1.getY());
|
||||
float z = p1.getZ() + t * (p2.getZ() - p1.getZ());
|
||||
return new Vector3f(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rotation of the shell to face the new position along its flight path.
|
||||
*
|
||||
* @param oldPos The previous position of the shell.
|
||||
* @param newPos The new position of the shell.
|
||||
*/
|
||||
private void updateRotation(Vector3f oldPos, Vector3f newPos) {
|
||||
Vector3f direction = newPos.subtract(oldPos).normalize();
|
||||
if (direction.lengthSquared() > 0) {
|
||||
this.rotation.lookAt(direction, Vector3f.UNIT_Y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(VoidVisitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ package pp.battleship.model;
|
||||
import pp.battleship.notification.GameEvent;
|
||||
import pp.battleship.notification.GameEventBroker;
|
||||
import pp.battleship.notification.ItemAddedEvent;
|
||||
import pp.battleship.notification.ItemRemovedEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -77,6 +78,15 @@ public class ShipMap {
|
||||
addItem(ship);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a shot on the map and triggers an item addition event.
|
||||
*
|
||||
* @param shell the shell to be registered on the map
|
||||
*/
|
||||
public void add(Shell shell) {
|
||||
addItem(shell);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a shot on the map, updates the state of the affected ship (if any),
|
||||
* and triggers an item addition event.
|
||||
@@ -97,7 +107,7 @@ public class ShipMap {
|
||||
*/
|
||||
public void remove(Item item) {
|
||||
items.remove(item);
|
||||
notifyListeners(new ItemAddedEvent(item, this));
|
||||
notifyListeners(new ItemRemovedEvent(item, this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -28,4 +28,12 @@ public interface Visitor<T> {
|
||||
* @return the result of visiting the Battleship element
|
||||
*/
|
||||
T visit(Battleship ship);
|
||||
|
||||
/**
|
||||
* Visits a Shell element
|
||||
*
|
||||
* @param shell the Shell element to visit
|
||||
* @return the result of visitung the Battleship element
|
||||
*/
|
||||
T visit(Shell shell);
|
||||
}
|
||||
|
@@ -25,4 +25,10 @@ public interface VoidVisitor {
|
||||
* @param ship the Battleship element to visit
|
||||
*/
|
||||
void visit(Battleship ship);
|
||||
|
||||
/**
|
||||
* Visits a Shell element
|
||||
* @param shell the Shell element to visit
|
||||
*/
|
||||
void visit(Shell shell);
|
||||
}
|
||||
|
@@ -22,5 +22,9 @@ public enum Sound {
|
||||
/**
|
||||
* Sound of a ship being destroyed.
|
||||
*/
|
||||
DESTROYED_SHIP
|
||||
DESTROYED_SHIP,
|
||||
/**
|
||||
* Sound of a shell flying
|
||||
*/
|
||||
SHELL_FLYING
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ wait.its.not.your.turn=Wait, it's not your turn!!
|
||||
menu.quit=Quit game
|
||||
menu.return-to-game=Return to game
|
||||
menu.sound-enabled=Sound switched on
|
||||
menu.background-sound-enabled=Background music switched on
|
||||
menu.map.load=Load map from file...
|
||||
menu.map.save=Save map in file...
|
||||
label.file=File:
|
||||
@@ -37,3 +38,4 @@ dialog.error=Error
|
||||
dialog.question=Question
|
||||
port.must.be.integer=Port must be an integer number
|
||||
map.doesnt.fit=The map doesn't fit to this game
|
||||
client.server-start=Start server
|
||||
|
@@ -29,6 +29,7 @@ wait.its.not.your.turn=Warte, Du bist nicht dran!!
|
||||
menu.quit=Spiel beenden
|
||||
menu.return-to-game=Zurück zum Spiel
|
||||
menu.sound-enabled=Sound eingeschaltet
|
||||
menu.background-sound-enabled=Hintergrundmusik eingeschaltet
|
||||
menu.map.load=Karte von Datei laden...
|
||||
menu.map.save=Karte in Datei speichern...
|
||||
label.file=Datei:
|
||||
@@ -37,3 +38,4 @@ dialog.error=Fehler
|
||||
dialog.question=Frage
|
||||
port.must.be.integer=Der Port muss eine ganze Zahl sein
|
||||
map.doesnt.fit=Diese Karte passt nicht zu diesem Spiel
|
||||
client.server-start=Server starten
|
||||
|
Reference in New Issue
Block a user