Refactored Score system

This commit is contained in:
2023-09-30 21:58:08 +02:00
parent 4fc8503d63
commit eecdeff77c
33 changed files with 248 additions and 103 deletions

Binary file not shown.

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">

2
.idea/modules.xml generated
View File

@@ -4,6 +4,8 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Minigames.main.iml" filepath="$PROJECT_DIR$/.idea/modules/Minigames.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Minigames.test.iml" filepath="$PROJECT_DIR$/.idea/modules/Minigames.test.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/eu.mhsl.minenet.Minigames.main.iml" filepath="$PROJECT_DIR$/.idea/modules/eu.mhsl.minenet.Minigames.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/eu.mhsl.minenet.Minigames.test.iml" filepath="$PROJECT_DIR$/.idea/modules/eu.mhsl.minenet.Minigames.test.iml" />
</modules>
</component>
</project>

View File

@@ -9,4 +9,8 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -9,4 +9,8 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
</configuration>
</facet>
</component>
</module>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
</configuration>
</facet>
</component>
</module>

View File

@@ -33,7 +33,11 @@ dependencies {
//https://jitpack.io/#Minestom/Minestom
//implementation 'com.github.Minestom:Minestom:aa621021e2'
implementation 'com.github.Minestom.Minestom:Minestom:79ce9570ea'
//implementation 'com.github.Minestom.Minestom:Minestom:4f7ff5b474'
// implementation 'com.github.Minestom.Minestom:Minestom:2cdb3911b0'
// implementation 'com.github.Minestom:MinestomDataGenerator:ddde11056e'
implementation 'com.github.waxeria:Minestom:e0427a36f3'
//Tools
//implementation 'com.github.Articdive.JNoise:jnoise-core:4.0.0'

View File

@@ -37,7 +37,8 @@ join_notFound;Lobby not found: ;Lobby konnte nicht gefunden werden:
ns:score#;;
result;Results;Ergebnisse
thanks;Thank you for Playing;Danke für‘s spielen
finish;Finish;Fertig
finish;Yout did it;Du hast es geschafft
death;You are out;Du hast verloren
;;
ns:restriction#;;
fail;Some requirements are not met;Bedinungen sind nicht erfüllt
@@ -83,3 +84,8 @@ description;Only go forward if the Trafficlights show green;Gehe nur bei Grün v
ns:game_Towerdefense#;;
name;Towerdefense;Towerdefense
description;Protect the path ????;??????
;;
ns:game_Spleef#;;
name;Spleef;Spleef;
description;Spleef other players and be the last survivor;Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
shovelName;Snow thrower;Schneeflug
1 map en_us de_de
37 ns:score#
38 result Results Ergebnisse
39 thanks Thank you for Playing Danke für‘s spielen
40 finish Finish Yout did it Fertig Du hast es geschafft
41 death You are out Du hast verloren
42
43 ns:restriction#
44 fail Some requirements are not met Bedinungen sind nicht erfüllt
84 ns:game_Towerdefense#
85 name Towerdefense Towerdefense
86 description Protect the path ???? ??????
87
88 ns:game_Spleef#
89 name Spleef Spleef
90 description Spleef other players and be the last survivor Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
91 shovelName Snow thrower Schneeflug

View File

@@ -37,7 +37,8 @@ join_notFound;Lobby not found: ;Lobby konnte nicht gefunden werden:
ns:score#;;
result;Results;Ergebnisse
thanks;Thank you for Playing;Danke für‘s spielen
finish;Finish;Fertig
finish;Yout did it;Du hast es geschafft
death;You are out;Du hast verloren
;;
ns:restriction#;;
fail;Some requirements are not met;Bedinungen sind nicht erfüllt
@@ -83,3 +84,8 @@ description;Only go forward if the Trafficlights show green;Gehe nur bei Grün v
ns:game_Towerdefense#;;
name;Towerdefense;Towerdefense
description;Protect the path ????;??????
;;
ns:game_Spleef#;;
name;Spleef;Spleef;
description;Spleef other players and be the last survivor;Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
shovelName;Snow thrower;Schneeflug
1 map en_us de_de
37 ns:score#
38 result Results Ergebnisse
39 thanks Thank you for Playing Danke für‘s spielen
40 finish Finish Yout did it Fertig Du hast es geschafft
41 death You are out Du hast verloren
42
43 ns:restriction#
44 fail Some requirements are not met Bedinungen sind nicht erfüllt
84 ns:game_Towerdefense#
85 name Towerdefense Towerdefense
86 description Protect the path ???? ??????
87
88 ns:game_Spleef#
89 name Spleef Spleef
90 description Spleef other players and be the last survivor Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
91 shovelName Snow thrower Schneeflug

View File

@@ -101,7 +101,7 @@ public abstract class Game extends MineNetInstance implements Spawnable {
}, TaskSchedule.seconds(10), TaskSchedule.stop());
}
protected void onLoad(CompletableFuture<Void> callback) {
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
callback.complete(null);
}
@@ -133,7 +133,7 @@ public abstract class Game extends MineNetInstance implements Spawnable {
protected void checkAbandoned() {
scheduleNextTick((instance) -> {
if(instance.getPlayers().size() == 0) this.unload();
if(instance.getPlayers().isEmpty()) this.unload();
});
}

View File

@@ -18,14 +18,14 @@ import java.util.concurrent.CompletableFuture;
public class StatelessGame extends Game {
private final String name;
private final Score score = new Score(this);
private Score score;
private int timeLimit = 0;
private int timePlayed = 0;
private final boolean preventExit = false;
public StatelessGame(DimensionType dimensionType, String gameName) {
public StatelessGame(DimensionType dimensionType, String gameName, Score score) {
super(dimensionType);
this.score = score;
this.name = gameName;
}
@@ -61,6 +61,9 @@ public class StatelessGame extends Game {
@Override
protected void start() {
score.setInstance(this);
score.attachListeners();
countdownStart().thenRun(() -> {
super.start();

View File

@@ -2,13 +2,19 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.NoScore;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import org.jetbrains.annotations.NotNull;
public class Backrooms extends StatelessGame {
public Backrooms() {
super(Dimension.NETHER.DIMENSION, "Backrooms");
super(Dimension.NETHER.DIMENSION, "Backrooms", new NoScore());
BackroomsGenerator generator = new BackroomsGenerator();
setGenerator(unit -> generator.generateRoom(unit, 50));
}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
playerBlockBreakEvent.setCancelled(false);
}
}

View File

@@ -5,6 +5,7 @@ import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.data.BedwarsTeam;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.MaterialUtil;
import eu.mhsl.minenet.minigames.util.Position;
import net.kyori.adventure.text.Component;
@@ -37,7 +38,7 @@ public class Bedwars extends StatelessGame {
public Bedwars() throws IOException {
super(Dimension.OVERWORLD.DIMENSION, "Bedwars");
super(Dimension.OVERWORLD.DIMENSION, "Bedwars", new LastWinsScore());
setChunkLoader(new AnvilLoader(Resource.GAME_MAP.getPath().resolve("bedwars/test")));
Configuration config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(Resource.GAME_MAP.getPath().resolve("bedwars/test/config.yml").toFile());

View File

@@ -1,6 +1,7 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.generator.BlockPallet;
@@ -22,7 +23,7 @@ class Deathcube extends StatelessGame {
final int percentage;
public Deathcube(int radius, int height, int percentage, int pvpEnabled) {
super(Dimension.THE_END.DIMENSION, "Deathcube");
super(Dimension.THE_END.DIMENSION, "Deathcube", new FirstWinsScore());
this.radius = radius;
this.height = height + 49;
this.percentage = percentage;
@@ -38,7 +39,7 @@ class Deathcube extends StatelessGame {
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius; x <= radius; x++) {

View File

@@ -2,6 +2,7 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.Intersect;
import eu.mhsl.minenet.minigames.instance.Dimension;
@@ -24,16 +25,16 @@ import java.util.concurrent.CompletableFuture;
class Minerun extends StatelessGame {
private int minePercentage = 50;
private int width = 100;
private int length = 50;
private int minePercentage;
private int width;
private int length;
private final int preRun = 5;
private final int afterMines = 2;
private final int afterFinishLine = 10;
public Minerun(int width, int length, int minePercentage) {
super(Dimension.THE_END.DIMENSION, "Minerun");
super(Dimension.THE_END.DIMENSION, "Minerun", new FirstWinsScore());
setGenerator(new SquareTerrainGenerator(width, length + preRun + afterFinishLine, true));
this.width = width;
@@ -42,7 +43,7 @@ class Minerun extends StatelessGame {
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
int spawnToFinishLine = preRun + length + afterMines;
Random random = new Random();
@@ -97,9 +98,9 @@ class Minerun extends StatelessGame {
playerMoveEvent.setCancelled(true);
}
if(getScore().hasResult(p) && middle.z() < preRun + length + afterMines) { // player cannot go back
playerMoveEvent.setCancelled(true);
new ActionBarMessage().appendStatic(Component.text("You cannot go back on the Field!", NamedTextColor.RED)).send(p);
if(middle.z() < preRun + length + afterMines) { // player cannot go back
// playerMoveEvent.setCancelled(true);
// new ActionBarMessage().appendStatic(Component.text("You cannot go back on the Field!", NamedTextColor.RED)).send(p);
return;
}

View File

@@ -2,6 +2,7 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularTerrainGenerator;
import io.github.bloepiloepi.pvp.config.*;
@@ -17,7 +18,7 @@ import java.util.concurrent.CompletableFuture;
public class Stickfight extends StatelessGame {
public Stickfight() {
super(Dimension.OVERWORLD.DIMENSION, "Stickfight");
super(Dimension.OVERWORLD.DIMENSION, "Stickfight", new LastWinsScore());
eventNode().addChild(
PvPConfig.emptyBuilder()
@@ -35,7 +36,7 @@ public class Stickfight extends StatelessGame {
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int z = -10; z <= 10; z++) {
batch.setBlock(0, 50, z, Block.SANDSTONE);

View File

@@ -3,13 +3,14 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.score.NoScore;
import net.minestom.server.entity.Player;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class Towerdefense extends StatelessGame {
public Towerdefense() {
super(Dimension.NETHER.DIMENSION, "Towerdefense");
super(Dimension.NETHER.DIMENSION, "Towerdefense", new NoScore());
setGenerator(new MazeGenerator());
}

View File

@@ -1,6 +1,7 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import net.minestom.server.coordinate.Vec;
@@ -18,11 +19,11 @@ class TrafficLightRace extends StatelessGame {
private int phaseCounter = 1;
public TrafficLightRace() {
super(Dimension.THE_END.DIMENSION, "Ampelrennen");
super(Dimension.THE_END.DIMENSION, "Ampelrennen", new FirstWinsScore());
}
@Override
protected void onLoad(CompletableFuture<Void> callback) {
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int x = -10; x <= 10; x++) {
for (int z = 5; z <= 100; z++) {

View File

@@ -13,13 +13,11 @@ import eu.mhsl.minenet.minigames.instance.hub.Hub;
import eu.mhsl.minenet.minigames.instance.room.entity.GameSelector;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.instance.AnvilLoader;
import team.unnamed.hephaestus.ModelDataCursor;
import team.unnamed.hephaestus.reader.ModelReader;
import team.unnamed.hephaestus.reader.blockbench.BBModelReader;
import java.util.*;
import java.util.logging.Logger;
@@ -55,6 +53,7 @@ public class Room extends MineNetInstance implements Spawnable {
p.clearEffects();
p.clearTitle();
p.getInventory().clear();
p.setGameMode(GameMode.ADVENTURE);
rooms.put(p, room);
MoveInstance.move(p, room);
}

View File

@@ -0,0 +1,45 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import net.minestom.server.entity.Player;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class FirstWinsScore extends Score {
private List<Player> scores = new ArrayList<>();
private int ignoreLastPlayers = 0;
public FirstWinsScore() {}
public FirstWinsScore(int ignoreLastPlayers) {
this.ignoreLastPlayers = ignoreLastPlayers;
}
public void setIgnoreLastPlayers(int ignoreLastPlayers) {
this.ignoreLastPlayers = ignoreLastPlayers;
}
@Override
protected void checkGameEnd() {
if(this.isDone()) return;
if(instance.getPlayers().isEmpty()) return;
if(scores.size() >= instance.getPlayers().size() - ignoreLastPlayers) setDone();
}
@Override
public void addResult(Player p) {
scores.add(p);
new TitleMessage(Duration.ofMillis(1000), Duration.ofSeconds(1)).appendTranslated("score#finish").send(p);
this.checkGameEnd();
}
@Override
protected List<String> getResults() {
return scores.stream().map(Player::getUsername).toList();
}
}

View File

@@ -0,0 +1,44 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import net.minestom.server.entity.Player;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class LastWinsScore extends Score {
private List<Player> scores = new ArrayList<>();
private int ignoreLastPlayers = 0;
public LastWinsScore() {}
public LastWinsScore(int ignoreLastPlayers) {
this.ignoreLastPlayers = ignoreLastPlayers;
}
public void setIgnoreLastPlayers(int ignoreLastPlayers) {
this.ignoreLastPlayers = ignoreLastPlayers;
}
@Override
protected void checkGameEnd() {
if(this.isDone()) return;
if(instance.getPlayers().isEmpty()) return;
if(scores.size() >= instance.getPlayers().size() - ignoreLastPlayers) setDone();
}
@Override
public void addResult(Player p) {
scores.add(0, p);
new TitleMessage(Duration.ofMillis(1000), Duration.ofSeconds(1)).appendTranslated("score#death").send(p);
this.checkGameEnd();
}
@Override
protected List<String> getResults() {
return scores.stream().map(Player::getUsername).toList();
}
}

View File

@@ -0,0 +1,21 @@
package eu.mhsl.minenet.minigames.score;
import net.minestom.server.entity.Player;
import java.util.List;
public class NoScore extends Score {
@Override
protected void checkGameEnd() {
}
@Override
public void addResult(Player p) {
}
@Override
protected List<String> getResults() {
return null;
}
}

View File

@@ -1,87 +1,53 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.instance.game.Game;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import java.time.Duration;
import java.util.*;
import java.util.List;
public class Score {
public abstract class Score {
private boolean isDone = false;
protected Game instance;
private boolean closed = false;
private final HashMap<Player, Integer> results = new HashMap<>();
protected final Game instance;
private Runnable callback;
public void attachListeners() {
this.instance.eventNode()
.addListener(AddEntityToInstanceEvent.class, addEntityToInstanceEvent -> checkGameEnd())
.addListener(RemoveEntityFromInstanceEvent.class, addEntityToInstanceEvent -> checkGameEnd())
.addListener(PlayerDisconnectEvent.class, addEntityToInstanceEvent -> checkGameEnd());
}
public Score(Game instance) {
abstract protected void checkGameEnd();
public abstract void addResult(Player p);
abstract protected List<String> getResults();
public boolean isDone() {
return isDone;
}
public void setDone() {
isDone = true;
new ChatMessage(Icon.STAR)
.appendTranslated("score#result")
.indent(1)
.pipe()
.list(getResults())
.indent(-1).newLine()
.appendTranslated("score#thanks")
.send(instance.getPlayers());
instance.stop();
}
protected void onGameEnd() {
this.instance.stop();
}
public void setInstance(Game instance) {
this.instance = instance;
this.callback = instance::stop;
instance.eventNode()
.addListener(AddEntityToInstanceEvent.class, this::checkGameEnd)
.addListener(RemoveEntityFromInstanceEvent.class, this::checkGameEnd)
.addListener(PlayerDisconnectEvent.class, this::checkGameEnd);
}
public void onClose(Runnable callback) {
this.callback = callback;
}
private void checkGameEnd(Event e) {
if(closed) return;
if(instance.getPlayers().size() < 1) return;
if(countResults() >= instance.getPlayers().size()) {
callback.run();
new ChatMessage(Icon.STAR)
.appendTranslated("score#result").indent(1)
.list(getMapFormatted())
.indent(-1).newLine()
.appendTranslated("score#thanks")
.send(instance.getPlayers());
closed = true;
}
}
public void addResult(Player p) {
if(closed) return;
if(results.containsKey(p)) return;
results.put(p, countResults()+1);
new TitleMessage(Duration.ofMillis(500), Duration.ofSeconds(1)).appendTranslated("score#finish").send(p);
checkGameEnd(null);
}
public boolean hasResult(Player p) {
return results.containsKey(p);
}
public HashMap<Player, Integer> getMap() {
return results;
}
public List<String> getMapFormatted() {
List<String> out = new ArrayList<>();
for (Map.Entry<Player, Integer> entry : getMap().entrySet()) {
out.add(entry.getValue() + ": " + entry.getKey().getUsername());
}
return out;
}
public int countResults() {
return results.size();
}
}

View File

@@ -37,7 +37,8 @@ join_notFound;Lobby not found: ;Lobby konnte nicht gefunden werden:
ns:score#;;
result;Results;Ergebnisse
thanks;Thank you for Playing;Danke für‘s spielen
finish;Finish;Fertig
finish;Yout did it;Du hast es geschafft
death;You are out;Du hast verloren
;;
ns:restriction#;;
fail;Some requirements are not met;Bedinungen sind nicht erfüllt
@@ -83,3 +84,8 @@ description;Only go forward if the Trafficlights show green;Gehe nur bei Grün v
ns:game_Towerdefense#;;
name;Towerdefense;Towerdefense
description;Protect the path ????;??????
;;
ns:game_Spleef#;;
name;Spleef;Spleef;
description;Spleef other players and be the last survivor;Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
shovelName;Snow thrower;Schneeflug
1 map en_us de_de
37 ns:score#
38 result Results Ergebnisse
39 thanks Thank you for Playing Danke für‘s spielen
40 finish Finish Yout did it Fertig Du hast es geschafft
41 death You are out Du hast verloren
42
43 ns:restriction#
44 fail Some requirements are not met Bedinungen sind nicht erfüllt
84 ns:game_Towerdefense#
85 name Towerdefense Towerdefense
86 description Protect the path ???? ??????
87
88 ns:game_Spleef#
89 name Spleef Spleef
90 description Spleef other players and be the last survivor Zerstöre Blöcke unter anderen Spielern und sei der letzte im Feld
91 shovelName Snow thrower Schneeflug