28 Commits

Author SHA1 Message Date
ae59482d7c solved pr comment 2025-10-11 11:36:01 +02:00
123c01da14 added possibility for same scores Tournament 2025-10-11 01:07:40 +02:00
e871c0bcb5 added possibility for same scores in PointsWinScore 2025-10-11 00:19:02 +02:00
c3f5170c33 added random height variations for elytra race gates 2025-10-10 22:53:56 +02:00
90832aacd0 added some globalRestrictions 2025-10-10 16:56:26 +02:00
dccb57b056 added LowestPointsWinScore and Stickfight win condition 2025-10-10 16:15:02 +02:00
368356c739 fixed unbreakable snow in Spleef 2025-10-10 15:20:43 +02:00
14a7e0c25f removed knockback for finished players in TrafficLightRace 2025-10-10 15:09:43 +02:00
398c3666e4 deathcube spectator mode when done 2025-10-10 12:00:12 +02:00
96170e9486 Merge pull request 'added BlockBreakRace' (#3) from develop-blockBreakRace into develop
Reviewed-on: #3
Reviewed-by: Lars Neuhaus <larslukasneuhaus@gmx.de>
2025-10-05 08:03:04 +00:00
9a97b746bc Merge branch 'develop' into develop-blockBreakRace 2025-10-05 08:02:56 +00:00
a2afc49d20 refactored BlockBreakRace to show players all items before the game starts 2025-10-04 21:38:50 +02:00
20b93cc9ae added BlockBreakRace game and related assets 2025-10-04 21:34:40 +02:00
148b5fc634 fixed spectator mode after playing tetris 2025-10-04 13:50:53 +02:00
fa69d4976d symbols and fastbridge description 2025-09-19 21:48:33 +02:00
e4fff421f5 added fastbridge terrain generator 2025-09-19 21:41:01 +02:00
e6bded1c9e implemented fastbridge gameplay 2025-09-13 19:01:21 +02:00
37a63e10b0 WIP: Fastbridge 2025-08-09 00:06:11 +02:00
13cc6c30b5 highground finished 2025-07-26 19:03:48 +02:00
3dd41979f7 WIP: highground 2025-07-25 23:04:03 +02:00
a4c46bc298 sumo renamed 2025-07-25 20:31:50 +02:00
aaad777f9b added Sumo Minigame 2025-07-25 20:22:45 +02:00
c62c7cfd1a added Sumo Minigame 2025-07-19 01:33:37 +02:00
4575164e80 Merge branch 'develop' into develop-hannes
# Conflicts:
#	build.gradle
#	src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java
2025-07-18 21:09:37 +02:00
edf26785a3 removed accidental code change 2025-04-16 23:45:03 +02:00
4d90f5fc28 added missing 'this' qualifier 2025-04-13 12:59:22 +02:00
343decb05a added this qualifier to tetris classes 2025-04-12 23:53:40 +02:00
cc371a9c12 Test game 2024-11-02 21:53:17 +01:00
47 changed files with 911 additions and 917 deletions

View File

@@ -48,7 +48,7 @@ dependencies {
//Tools //Tools
implementation 'de.articdive:jnoise:3.0.2' implementation 'de.articdive:jnoise:3.0.2'
implementation 'net.md-5:bungeecord-config:1.19-R0.1-SNAPSHOT' implementation 'net.md-5:bungeecord-config:1.21-R0.3'
implementation 'org.apache.commons:commons-text:1.10.0' implementation 'org.apache.commons:commons-text:1.10.0'
implementation 'org.spongepowered:configurate-yaml:4.1.2' implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'

View File

@@ -2,17 +2,21 @@ package eu.mhsl.minenet.minigames.instance.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.anvilRun.AnvilRunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.anvilRun.AnvilRunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace.BlockBreakRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.BackroomsFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.BackroomsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.BedwarsFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.BedwarsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain.AcidRainFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain.AcidRainFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge.FastbridgeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.highGround.HighGroundFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive.JumpDiveFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive.JumpDiveFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef.SpleefFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef.SpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight.StickFightFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight.StickFightFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.TetrisFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.sumo.SumoFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory;
@@ -32,7 +36,11 @@ public enum GameList {
ACIDRAIN(new AcidRainFactory(), GameType.PVE), ACIDRAIN(new AcidRainFactory(), GameType.PVE),
ELYTRARACE(new ElytraRaceFactory(), GameType.PVP), ELYTRARACE(new ElytraRaceFactory(), GameType.PVP),
SPLEEF(new SpleefFactory(), GameType.PVP), SPLEEF(new SpleefFactory(), GameType.PVP),
JUMPDIVE(new JumpDiveFactory(), GameType.JUMPNRUN); JUMPDIVE(new JumpDiveFactory(), GameType.JUMPNRUN),
SUMO(new SumoFactory(), GameType.PVP),
HIGHGROUND(new HighGroundFactory(), GameType.PVP),
FASTBRIDGE(new FastbridgeFactory(), GameType.OTHER),
BLOCKBREAKRACE(new BlockBreakRaceFactory(), GameType.OTHER);
private final GameFactory factory; private final GameFactory factory;
private final GameType type; private final GameType type;
@@ -45,6 +53,6 @@ public enum GameList {
} }
public GameType getType() { public GameType getType() {
return type; return this.type;
} }
} }

View File

@@ -26,7 +26,7 @@ public class AnvilRunFactory implements GameFactory {
public ConfigManager configuration() { public ConfigManager configuration() {
return new ConfigManager() return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 5, 10, 15, 20, 25, 30)) .addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 5, 10, 15, 20, 25, 30))
.addOption(new NumericOption("seconds", Material.STICK, TranslatedComponent.byId("optionCommon#seconds"), 10, 30, 60, 90, 120)); .addOption(new NumericOption("seconds", Material.CLOCK, TranslatedComponent.byId("optionCommon#seconds"), 10, 30, 60, 90, 120));
} }
@Override @Override

View File

@@ -0,0 +1,79 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
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.PlayerMoveEvent;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class BlockBreakRace extends StatelessGame {
private int spawnCount = 0;
private final int height;
public BlockBreakRace(int height) {
super(Dimension.OVERWORLD.key, "blockBreakRace", new FirstWinsScore());
this.height = height;
this.setGenerator(new BlockBreakRaceGenerator(height));
}
@Override
protected boolean onPlayerJoin(Player p) {
PlayerInventory inv = p.getInventory();
inv.addItemStack(ItemStack.of(Material.DIAMOND_PICKAXE));
inv.addItemStack(ItemStack.of(Material.DIAMOND_AXE));
inv.addItemStack(ItemStack.of(Material.DIAMOND_SHOVEL));
return super.onPlayerJoin(p);
}
@Override
protected void onStart() {
this.getPlayers().forEach(player -> player.setGameMode(GameMode.SURVIVAL));
}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent event) {
List<Material> allowedMaterials = List.of(Material.STONE, Material.OAK_PLANKS, Material.DIRT);
if(!allowedMaterials.contains(event.getBlock().registry().material())) event.setCancelled(true);
if(this.isBeforeBeginning) event.setCancelled(true);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(this.isBeforeBeginning) return;
if(playerMoveEvent.getNewPosition().y() < BlockBreakRaceGenerator.BOTTOM_Y) {
Player player = playerMoveEvent.getPlayer();
this.getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
player.getInventory().clear();
}
}
@Override
public Pos getSpawn() {
int idx = this.spawnCount++;
int cols = BlockBreakRaceGenerator.ROW_OFFSETS_X.length;
int rows = BlockBreakRaceGenerator.ROW_OFFSETS_Z.length;
int perChunk = cols * rows;
int zChunk = idx / perChunk;
int gridIndex = idx % perChunk;
int xIndex = gridIndex % cols;
int zIndex = gridIndex / cols;
int localX = BlockBreakRaceGenerator.ROW_OFFSETS_X[xIndex];
int localZ = BlockBreakRaceGenerator.ROW_OFFSETS_Z[zIndex];
int absZ = (zChunk * 16) + localZ;
return new Pos(localX, BlockBreakRaceGenerator.BOTTOM_Y + this.height + 1, absZ).add(0.5);
}
}

View File

@@ -0,0 +1,40 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class BlockBreakRaceFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_BlockBreakRace#name");
}
@Override
public Material symbol() {
return Material.DIAMOND_PICKAXE;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_BlockBreakRace#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("height", Material.SCAFFOLDING, TranslatedComponent.byId("optionCommon#height"), 20, 30, 40, 50));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new BlockBreakRace(configuration.get("height").getAsInt()).setParent(parent);
}
}

View File

@@ -0,0 +1,67 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.blockBreakRace;
import eu.mhsl.minenet.minigames.world.generator.featureEnriched.ValeGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.BaseGenerator;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class BlockBreakRaceGenerator extends BaseGenerator {
public static final int BOTTOM_Y = 50;
public final int TOP_Y;
public static final int[] ROW_OFFSETS_X = {4, 8, 12};
public static final int[] ROW_OFFSETS_Z = {4, 8, 12};
private static final Block[] FILL_BLOCKS = {
Block.STONE,
Block.DIRT,
Block.OAK_PLANKS
};
private final Random random = ThreadLocalRandom.current();
public BlockBreakRaceGenerator(int height) {
this.TOP_Y = BOTTOM_Y + height;
ValeGenerator vale = new ValeGenerator();
vale.setXShiftMultiplier(i -> 0.5d);
vale.setHeightNoiseMultiplier(i -> 2);
vale.setXShiftOffset(i -> 40d);
this.addMixIn(vale);
this.addMixIn(unit -> {
if(unit.absoluteStart().chunkX() != 0) return;
for (int localX : ROW_OFFSETS_X) {
for (int localZ : ROW_OFFSETS_Z) {
final int absZ = unit.absoluteStart().blockZ() + localZ;
this.placeTube(unit, localX, absZ);
}
}
});
}
private void placeTube(GenerationUnit unit, int x, int z) {
for (int y = BOTTOM_Y; y < this.TOP_Y; y++) {
Block fill = FILL_BLOCKS[this.random.nextInt(FILL_BLOCKS.length)];
unit.modifier().fill(
new Pos(x, y, z),
new Pos(x, y, z).add(1),
fill
);
}
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
if (dx == 0 && dz == 0) continue; // Zentrum überspringen
unit.modifier().fill(
new Pos(x + dx, BOTTOM_Y, z + dz),
new Pos(x + dx, this.TOP_Y + 3, z + dz).add(1),
Block.BARRIER
);
}
}
}
}

View File

@@ -3,6 +3,8 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef;
import eu.mhsl.minenet.minigames.instance.game.Game; import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option; import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room; import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent; import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
@@ -29,4 +31,10 @@ public class BowSpleefFactory implements GameFactory {
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception { public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new BowSpleef().setParent(parent); return new BowSpleef().setParent(parent);
} }
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(2));
}
} }

View File

@@ -8,6 +8,7 @@ import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator; import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.togar2.pvp.feature.CombatFeatures; import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -68,7 +69,9 @@ class Deathcube extends StatelessGame {
playerMoveEvent.setCancelled(true); playerMoveEvent.setCancelled(true);
return; return;
} }
if(playerMoveEvent.getNewPosition().y() > height) getScore().insertResult(playerMoveEvent.getPlayer()); if(playerMoveEvent.getNewPosition().y() <= height) return;
getScore().insertResult(playerMoveEvent.getPlayer());
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import org.jetbrains.annotations.NotNull;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -183,7 +184,8 @@ public class ElytraRace extends StatelessGame {
} }
private Point getRingPositionAtZ(int z) { private Point getRingPositionAtZ(int z) {
return new Pos(vale.getXShiftAtZ(z), -45, z); Random random = new Random(this.hashCode() + z);
return new Pos(vale.getXShiftAtZ(z), -45 + random.nextInt(-5, 15), z);
} }
private CompletableFuture<Void> generateRing(int zPos) { private CompletableFuture<Void> generateRing(int zPos) {

View File

@@ -0,0 +1,64 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
public class Fastbridge extends StatelessGame {
private int currentSpawn = 0;
public Fastbridge() {
super(Dimension.OVERWORLD.key, "Fastbridge", new FirstWinsScore());
this.setGenerator(new FastbridgeChunkgenerator());
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player player = playerMoveEvent.getPlayer();
Pos newPos = playerMoveEvent.getNewPosition();
if(this.getScore().hasResult(player)) return;
if(newPos.y() < 0) {
player.teleport(getSpawn());
if(!isBeforeBeginning) this.resetPlayer(player);
}
if(newPos.x() > 53) {
this.getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
}
}
@Override
protected void onStart() {
getPlayers().forEach(player -> {
player.setGameMode(GameMode.SURVIVAL);
resetPlayer(player);
});
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
if(isBeforeBeginning) playerBlockPlaceEvent.setCancelled(true);
}
private void resetPlayer(Player player) {
if(isBeforeBeginning) return;
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.addItemStack(ItemStack.of(Material.WHITE_WOOL, 64));
}
@Override
public synchronized Pos getSpawn() {
return new Pos(24, 1, currentSpawn++*Chunk.CHUNK_SIZE_Z*2-8, -90, 0);
}
}

View File

@@ -0,0 +1,29 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge;
import eu.mhsl.minenet.minigames.world.generator.featureEnriched.ValeGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.BaseGenerator;
import net.minestom.server.instance.block.Block;
public class FastbridgeChunkgenerator extends BaseGenerator {
public FastbridgeChunkgenerator() {
this.addMixIn(unit -> {
if (unit.absoluteStart().chunkZ() % 2 == 0) {
unit.modifier().fill(Block.BARRIER);
return;
}
if (unit.absoluteStart().chunkX() != 1 && unit.absoluteStart().chunkX() != 3) return;
for (int x = 5; x <= 10; x++){
for (int z = 5; z <= 10; z++){
unit.modifier().setRelative(x, 64, z, unit.absoluteStart().chunkX() == 3 ? Block.GOLD_BLOCK : Block.GRASS_BLOCK);
}
}
});
ValeGenerator vale = new ValeGenerator();
vale.setXShiftMultiplier(integer -> 0.5d);
vale.setHeightNoiseMultiplier(integer -> 2);
vale.setXShiftOffset(integer -> 40d);
this.addMixIn(vale);
}
}

View File

@@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.fastbridge;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class FastbridgeFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Fastbridge#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Fastbridge#description");
}
@Override
public Material symbol() {
return Material.WHITE_WOOL;
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Fastbridge().setParent(parent);
}
}

View File

@@ -0,0 +1,126 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.highGround;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import io.github.togar2.pvp.events.EntityKnockbackEvent;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.events.PrepareAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.instance.InstanceTickEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
class HighGround extends StatelessGame {
private final int radius;
private final int seconds;
private final WeakHashMap<Player, Integer> scoreMap = new WeakHashMap<>();
HighGround(int radius, int seconds) {
super(Dimension.THE_END.key, "highground", new PointsWinScore());
this.radius = radius;
this.seconds = seconds;
this.eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build()
.createNode()
);
this.eventNode().addListener(
FinalAttackEvent.class,
finalAttackEvent -> finalAttackEvent.setBaseDamage(0)
);
this.eventNode().addListener(PrepareAttackEvent.class, prepareAttackEvent -> {
if(this.isBeforeBeginning){
prepareAttackEvent.setCancelled(true);
}
});
this.eventNode().addListener(
EntityKnockbackEvent.class,
entityKnockbackEvent -> entityKnockbackEvent.setStrength(1.1f)
);
this.eventNode().addListener(InstanceTickEvent.class, instanceTickEvent -> {
if (this.isBeforeBeginning || !this.isRunning) return;
this.getPlayers().forEach(player -> {
if((player.isOnGround() && player.getPosition().y() >= 1) || (!player.isOnGround() && player.getPosition().y() >= 1.5)){
this.scoreMap.put(player, this.scoreMap.get(player) + 1);
player.setLevel(this.scoreMap.get(player) / 20);
player.setExp((this.scoreMap.get(player) % 20) / 20.0f);
}
});
});
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
for (int y = 0; y >= -3; y--) {
int radius = (Math.abs(y) * 5) + this.radius;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
double distance = new Pos(x, 0, z).distance(0, 0, 0);
if (distance <= radius) {
this.setBlock(x, y, z, y == 0 ? Block.DIAMOND_BLOCK : Block.GRASS_BLOCK);
Pos featurePosition = new Pos(x, y + 1, z);
if(y >= 0 || this.getBlock(featurePosition).isSolid()) continue;
if (this.rnd.nextDouble() < 0.1){
this.setBlock(featurePosition, Block.SHORT_GRASS);
}
if (this.rnd.nextDouble() < 0.01){
this.setBlock(featurePosition, BlockPallet.FLOWER.rnd());
}
}
}
}
}
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player player = playerMoveEvent.getPlayer();
if(playerMoveEvent.getNewPosition().y() < -10){
player.teleport(this.getSpawn());
}
}
@Override
protected void start() {
this.getPlayers().forEach(player -> this.scoreMap.put(player, 0));
super.start();
}
@Override
protected void onStart() {
this.setTimeLimit(this.seconds);
}
@Override
protected void onStop() {
this.getPlayers().forEach(player -> this.getScore().insertResult(player, this.scoreMap.get(player)));
}
@Override
public Pos getSpawn() {
double theta = this.rnd.nextDouble() * 2 * Math.PI;
double spawnRadius = this.radius + 5;
double x = spawnRadius * Math.cos(theta);
double z = spawnRadius * Math.sin(theta);
return new Pos(x, 0, z).withLookAt(new Pos(0, 0, 0));
}
}

View File

@@ -0,0 +1,49 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.highGround;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class HighGroundFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Highground#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Highground#description");
}
@Override
public Material symbol() {
return Material.GOLDEN_HELMET;
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 3, 5, 7, 10))
.addOption(new NumericOption("seconds", Material.CLOCK, TranslatedComponent.byId("optionCommon#seconds"), 30, 60, 90, 120));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new HighGround(configuration.get("radius").getAsInt(), configuration.get("seconds").getAsInt()).setParent(parent);
}
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(2));
}
}

View File

@@ -9,6 +9,7 @@ import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator; import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode; import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.player.PlayerStartDiggingEvent; import net.minestom.server.event.player.PlayerStartDiggingEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.batch.AbsoluteBlockBatch;
@@ -77,6 +78,15 @@ public class Spleef extends StatelessGame {
} }
} }
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent event) {
if(!isRunning) {
event.setCancelled(true);
return;
}
setBlock(event.getBlockPosition(), Block.AIR);
}
private void destroyBlock(PlayerStartDiggingEvent event) { private void destroyBlock(PlayerStartDiggingEvent event) {
if(!isRunning) return; if(!isRunning) return;
setBlock(event.getBlockPosition(), Block.AIR); setBlock(event.getBlockPosition(), Block.AIR);

View File

@@ -2,7 +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.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame; import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore; import eu.mhsl.minenet.minigames.score.LowestPointsWinScore;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator; import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.togar2.pvp.events.FinalAttackEvent; import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures; import io.github.togar2.pvp.feature.CombatFeatures;
@@ -13,15 +13,17 @@ import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class Stickfight extends StatelessGame { public class Stickfight extends StatelessGame {
private final double radius = 20; private final double radius = 20;
private final WeakHashMap<Player, Pos> spawnPoints = new WeakHashMap<>(); private final WeakHashMap<Player, Pos> spawnPoints = new WeakHashMap<>();
private final Map<Player, Integer> scoreMap = new WeakHashMap<>();
public Stickfight() { public Stickfight() {
super(Dimension.OVERWORLD.key, "Stickfight", new LastWinsScore()); super(Dimension.OVERWORLD.key, "Stickfight", new LowestPointsWinScore());
eventNode().addChild( eventNode().addChild(
CombatFeatures.empty() CombatFeatures.empty()
@@ -66,6 +68,11 @@ public class Stickfight extends StatelessGame {
super.start(); super.start();
} }
@Override
protected void onStop() {
this.scoreMap.forEach((player, score) -> getScore().insertResult(player, score));
}
private void generateBridge(int startX, int startY, int startZ) { private void generateBridge(int startX, int startY, int startZ) {
int steps = (int) (radius * 1.5); int steps = (int) (radius * 1.5);
for (int i = 0; i < steps; i++) { for (int i = 0; i < steps; i++) {
@@ -78,19 +85,22 @@ public class Stickfight extends StatelessGame {
@Override @Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) { protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(!spawnPoints.containsKey(playerMoveEvent.getPlayer())) { Player player = playerMoveEvent.getPlayer();
if(!spawnPoints.containsKey(player)) {
playerMoveEvent.setCancelled(true); playerMoveEvent.setCancelled(true);
return; return;
} }
if(isBeforeBeginning) { if(isBeforeBeginning) {
if(spawnPoints.get(playerMoveEvent.getPlayer()).distance(playerMoveEvent.getNewPosition()) < 1) return; if(spawnPoints.get(player).distance(playerMoveEvent.getNewPosition()) < 1) return;
playerMoveEvent.setCancelled(true); playerMoveEvent.setCancelled(true);
playerMoveEvent.getPlayer().teleport(spawnPoints.get(playerMoveEvent.getPlayer())); player.teleport(spawnPoints.get(player));
} }
if(playerMoveEvent.getNewPosition().y() < 40) { if(playerMoveEvent.getNewPosition().y() < 40) {
playerMoveEvent.getPlayer().teleport(spawnPoints.get(playerMoveEvent.getPlayer())); player.teleport(spawnPoints.get(player));
this.scoreMap.putIfAbsent(player, 0);
this.scoreMap.put(player, this.scoreMap.get(player) + 1);
} }
} }

View File

@@ -0,0 +1,129 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.sumo;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.events.PrepareAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
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.PlayerMoveEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
public class Sumo extends StatelessGame {
int radius;
int health;
int seconds;
int originalRadius;
int timer;
WeakHashMap<Player, Integer> healthMap = new WeakHashMap<>();
public Sumo(int radius, int health, int seconds) {
super(Dimension.OVERWORLD.key, "Sumo", new LastWinsScore());
this.getScore().setIgnoreLastPlayers(1);
this.setTime(6000);
this.setTimeRate(0);
this.radius = radius;
this.health = health;
this.seconds = seconds;
this.originalRadius = radius;
this.timer = seconds;
this.eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build()
.createNode()
);
this.eventNode().addListener(PrepareAttackEvent.class, prepareAttackEvent -> {
if (this.isBeforeBeginning)
prepareAttackEvent.setCancelled(true);
});
this.eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
finalAttackEvent.setBaseDamage(0);
((Player) finalAttackEvent.getTarget()).setHealth(20);
});
}
@Override
protected void start() {
this.getPlayers().forEach(player -> {
this.healthMap.put(player, this.health);
player.setLevel(this.healthMap.get(player));
});
MinecraftServer.getSchedulerManager().scheduleTask(
() -> {
if(this.isBeforeBeginning) return TaskSchedule.seconds(1);
this.timer--;
double percent = (double) this.timer / this.seconds;
int radius = (int) (this.originalRadius * percent);
if (this.radius >= 5) {
this.radius = radius;
this.generatePlatform();
return TaskSchedule.seconds(1);
}
return TaskSchedule.stop();
},
TaskSchedule.seconds(1)
);
super.start();
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
this.generatePlatform();
}
private void generatePlatform() {
int buffer = 5;
for(int x = -this.radius - buffer; x <= this.radius + buffer; x++) {
for(int z = -this.radius - buffer; z <= this.radius + buffer; z++) {
double distance = new Pos(x, 0, z).distance(new Pos(0, 0, 0));
if(distance <= this.radius) {
boolean isEdge = this.radius - 1 < distance;
this.setBlock(x, 0, z, isEdge ? Block.RED_CONCRETE : Block.WHITE_CONCRETE);
} else {
this.setBlock(x, 0, z, Block.AIR);
}
}
}
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player player = playerMoveEvent.getPlayer();
if(playerMoveEvent.getNewPosition().y() < -10) {
player.teleport(this.getSpawn());
this.healthMap.put(player, this.healthMap.get(player) - 1);
player.setLevel(this.healthMap.get(player));
if (this.healthMap.get(player) == 0) {
this.getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
}
}
}
@Override
public Pos getSpawn() {
return new Pos(0, 2, 0);
}
}

View File

@@ -0,0 +1,49 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.sumo;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class SumoFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Sumo#name");
}
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Sumo#description");
}
@Override
public Material symbol() {
return Material.SLIME_BALL;
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 10, 20, 30))
.addOption(new NumericOption("health", Material.GOLDEN_APPLE, TranslatedComponent.byId("game_Sumo#lives"), 1, 2, 3, 4, 5))
.addOption(new NumericOption("seconds", Material.CLOCK, TranslatedComponent.byId("optionCommon#seconds"), 30, 60, 90, 120));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Sumo(configuration.get("radius").getAsInt(), configuration.get("health").getAsInt(), configuration.get("seconds").getAsInt()).setParent(parent);
}
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(2));
}
}

View File

@@ -28,7 +28,7 @@ class Tetris extends StatelessGame {
public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) { public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) {
super(Dimension.THE_END.key, "Tetris", new PointsWinScore()); super(Dimension.THE_END.key, "Tetris", new PointsWinScore());
eventNode() this.eventNode()
.addListener(PlayerUseItemEvent.class, this::onPlayerInteract) .addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack) .addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
.addListener(PlayerTickEvent.class, this::onPlayerTick); .addListener(PlayerTickEvent.class, this::onPlayerTick);
@@ -58,7 +58,7 @@ class Tetris extends StatelessGame {
protected void onStop() { protected void onStop() {
this.tetrisGames.forEach((player, tetrisGame) -> { this.tetrisGames.forEach((player, tetrisGame) -> {
tetrisGame.loose(); tetrisGame.loose();
getScore().insertResult(player, tetrisGame.getScore()); this.getScore().insertResult(player, tetrisGame.getScore());
tetrisGame.sidebar.removeViewer(player); tetrisGame.sidebar.removeViewer(player);
}); });
} }
@@ -106,23 +106,25 @@ class Tetris extends StatelessGame {
TetrisGame tetrisGame = this.tetrisGames.get(player); TetrisGame tetrisGame = this.tetrisGames.get(player);
if(tetrisGame == null) return; if(tetrisGame == null) return;
if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) { if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) {
letPlayerLoose(player); this.letPlayerLoose(player);
} }
} }
private void letPlayerLoose(Player player) { private void letPlayerLoose(Player player) {
TetrisGame tetrisGame = this.tetrisGames.get(player); TetrisGame tetrisGame = this.tetrisGames.get(player);
if(!this.getScore().hasResult(player)) {
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.SPECTATOR);
player.setInvisible(true); player.setInvisible(true);
getScore().insertResult(player, tetrisGame.getScore()); this.getScore().insertResult(player, tetrisGame.getScore());
}
boolean allGamesLost = this.tetrisGames.values().stream() boolean allGamesLost = this.tetrisGames.values().stream()
.filter(game -> !game.lost) .filter(game -> !game.lost)
.toList() .toList()
.isEmpty(); .isEmpty();
if(!setTimeLimit && !allGamesLost) { if(!this.setTimeLimit && !allGamesLost) {
this.setTimeLimit(90); this.setTimeLimit(90);
setTimeLimit = true; this.setTimeLimit = true;
} }
} }
@@ -134,7 +136,7 @@ class Tetris extends StatelessGame {
if(this.tetrisGames.get(p) == null) { if(this.tetrisGames.get(p) == null) {
this.tetrisGames.put(p, new TetrisGame( this.tetrisGames.put(p, new TetrisGame(
this, this,
getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*30, 0, 0), this.getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*30, 0, 0),
Tetromino.Shape.J, Tetromino.Shape.J,
this.nextTetrominoesCount, this.nextTetrominoesCount,
this.isFast, this.isFast,

View File

@@ -74,10 +74,10 @@ public class Playfield {
} }
} }
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE); batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE); batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE); batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE);
batch.setBlock(getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE); batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {}); BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
} }
@@ -90,7 +90,7 @@ public class Playfield {
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false; if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
} }
if(isFullLine) { if(isFullLine) {
removeFullLine(y); this.removeFullLine(y);
removedLinesCounter += 1; removedLinesCounter += 1;
y -= 1; y -= 1;
} }
@@ -99,10 +99,10 @@ public class Playfield {
} }
public void addLines(int lines) { public void addLines(int lines) {
int xPosMissing = random.nextInt(1, 10); int xPosMissing = this.random.nextInt(1, 10);
for (int i = 0; i < lines; i++) { for (int i = 0; i < lines; i++) {
moveAllLinesUp(); this.moveAllLinesUp();
for (int x = 1; x < 11; x++) { for (int x = 1; x < 11; x++) {
if(x != xPosMissing) { if(x != xPosMissing) {
this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE); this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE);

View File

@@ -121,8 +121,8 @@ public class TetrisGame {
public void tick() { public void tick() {
if(this.lost || this.paused) return; if(this.lost || this.paused) return;
if(!currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {
setActiveTetrominoDown(); this.setActiveTetrominoDown();
} }
} }
@@ -176,7 +176,7 @@ public class TetrisGame {
} }
private boolean switchHold() { private boolean switchHold() {
if(!holdPossible) return false; if(!this.holdPossible) return false;
Tetromino newCurrentTetromino; Tetromino newCurrentTetromino;
if(this.holdTetromino == null) { if(this.holdTetromino == null) {
@@ -194,7 +194,7 @@ public class TetrisGame {
this.currentTetromino.setPosition(this.tetrominoSpawnPosition); this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw(); this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) loose(); if(!this.currentTetromino.moveDown()) this.loose();
double xChange = this.holdTetromino.getXChange(); double xChange = this.holdTetromino.getXChange();
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0)); this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
@@ -312,7 +312,7 @@ public class TetrisGame {
this.currentTetromino.setPosition(this.tetrominoSpawnPosition); this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw(); this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) { if(!this.currentTetromino.moveDown()) {
loose(); this.loose();
} }
} }
} }

View File

@@ -38,13 +38,13 @@ public class Tetromino {
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
switch (this.shape) { switch (this.shape) {
case I -> shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}; case I -> this.shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}};
case J -> shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}}; case J -> this.shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}};
case L -> shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}}; case L -> this.shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}};
case O -> shapeArray = new int[][]{{1,1}, {1,1}}; case O -> this.shapeArray = new int[][]{{1,1}, {1,1}};
case S -> shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}}; case S -> this.shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}};
case T -> shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}}; case T -> this.shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}};
case Z -> shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}}; case Z -> this.shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}};
} }
} }
@@ -58,22 +58,22 @@ public class Tetromino {
public boolean rotate(boolean clockwise) { public boolean rotate(boolean clockwise) {
int[][] newShapeArray = this.getTurnedShapeArray(clockwise); int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
return checkCollisionAndMove(this.position, newShapeArray); return this.checkCollisionAndMove(this.position, newShapeArray);
} }
public boolean moveDown() { public boolean moveDown() {
Pos newPosition = this.position.sub(0, 1, 0); Pos newPosition = this.position.sub(0, 1, 0);
return checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public boolean moveLeft() { public boolean moveLeft() {
Pos newPosition = this.position.sub(1, 0, 0); Pos newPosition = this.position.sub(1, 0, 0);
return checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public boolean moveRight() { public boolean moveRight() {
Pos newPosition = this.position.add(1, 0, 0); Pos newPosition = this.position.add(1, 0, 0);
return checkCollisionAndMove(newPosition, this.shapeArray); return this.checkCollisionAndMove(newPosition, this.shapeArray);
} }
public void draw() { public void draw() {
@@ -83,11 +83,11 @@ public class Tetromino {
public void draw(boolean withGhost) { public void draw(boolean withGhost) {
if(withGhost) { if(withGhost) {
Pos ghostPos = this.position; Pos ghostPos = this.position;
while (!checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) { while (!this.checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
ghostPos = ghostPos.sub(0, 1, 0); ghostPos = ghostPos.sub(0, 1, 0);
} }
Pos positionChange = this.position.sub(ghostPos); Pos positionChange = this.position.sub(ghostPos);
getBlockPositions().forEach(pos -> { this.getBlockPositions().forEach(pos -> {
Entity ghostBlock = new Entity(ghostEntityType); Entity ghostBlock = new Entity(ghostEntityType);
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock()); ((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock());
ghostBlock.setNoGravity(true); ghostBlock.setNoGravity(true);
@@ -97,11 +97,11 @@ public class Tetromino {
}); });
} }
getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock())); this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
} }
public void drawAsEntities() { public void drawAsEntities() {
getBlockPositions().forEach(pos -> { this.getBlockPositions().forEach(pos -> {
Entity ghostBlock = new Entity(ghostEntityType); Entity ghostBlock = new Entity(ghostEntityType);
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock()); ((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock());
ghostBlock.setNoGravity(true); ghostBlock.setNoGravity(true);
@@ -222,10 +222,10 @@ public class Tetromino {
} }
private boolean checkCollision(Pos newPosition, int[][] newShapeArray) { private boolean checkCollision(Pos newPosition, int[][] newShapeArray) {
List<Pos> newBlockPositions = getBlockPositions(newPosition, newShapeArray); List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray);
for(Pos pos : newBlockPositions) { for(Pos pos : newBlockPositions) {
if(isPartOfTetromino(pos)) continue; if(this.isPartOfTetromino(pos)) continue;
if(this.instance.getBlock(pos) == this.getGhostBlock()) continue; if(this.instance.getBlock(pos) == this.getGhostBlock()) continue;
if(this.instance.getBlock(pos) != Block.AIR) return true; if(this.instance.getBlock(pos) != Block.AIR) return true;
} }
@@ -234,7 +234,7 @@ public class Tetromino {
} }
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) { private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
if(!checkCollision(newPosition, newShapeArray)) { if(!this.checkCollision(newPosition, newShapeArray)) {
this.remove(); this.remove();
this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new); this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
this.setPosition(newPosition); this.setPosition(newPosition);

View File

@@ -1,89 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
public class Cursor extends Entity {
private boolean cursorActive = false;
private final int reach;
public Cursor(int reach) {
super(EntityType.ARMOR_STAND);
this.setBoundingBox(0,0,0);
this.reach = reach;
}
public void updateCursorPosition(Player player) {
Point targetPosition = player.getTargetBlockPosition(this.reach);
this.moveCursorTo(targetPosition);
}
private void moveCursorTo(@Nullable Point newTargetBlockPosition) {
this.setCursorEnabled(true);
if(newTargetBlockPosition == null) {
this.setCursorEnabled(false);
return;
}
if(!this.getInstance().getBlock(newTargetBlockPosition).equals(Block.BLACK_WOOL)) this.setCursorEnabled(false);
Material holdingMaterial = this.getInstance().getPlayerHandMaterial();
if(!this.getInstance().getGame().getAvailableTowers().containsKey(holdingMaterial)) this.setCursorEnabled(false);
this.teleport(new Pos(newTargetBlockPosition.add(0.5,1,0.5)));
this.showRange(this.getInstance().getGame().getAvailableTowers().get(holdingMaterial));
}
private void setCursorEnabled(boolean enabled) {
this.cursorActive = enabled;
this.setInvisible(!enabled);
}
private void showRange(@Nullable Class<? extends Tower> towerClass) {
if(towerClass == null) return;
if(!this.isCursorActive()) return;
try {
int range = towerClass.getConstructor().newInstance().getRange();
Collection<SendablePacket> particles = new ArrayList<>();
double circumference = 2 * Math.PI * range;
int count = (int) (circumference * 1.5);
for (int i = 0; i < count; i++) {
double radians = ((2 * Math.PI) / count) * i;
Vec relativePosition = new Vec(Math.sin(radians)*range, 0, Math.cos(radians)*range);
ParticlePacket particle = new ParticlePacket(Particle.COMPOSTER, this.position.add(relativePosition), Pos.ZERO, 0, 1);
particles.add(particle);
}
this.getInstance().getPlayer().sendPackets(particles);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public TowerdefenseRoom getInstance() {
return (TowerdefenseRoom) super.getInstance();
}
public boolean isCursorActive() {
return this.cursorActive;
}
public Block getTargetBlock() {
return this.getInstance().getBlock(this.getTargetBlockPosition());
}
public Pos getTargetBlockPosition() {
return this.getPosition().sub(0, 0.5, 0);
}
}

View File

@@ -2,93 +2,39 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension; import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame; import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies.WaveGenerator;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.*;
import eu.mhsl.minenet.minigames.score.LastWinsScore; import eu.mhsl.minenet.minigames.score.LastWinsScore;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
public class Towerdefense extends StatelessGame { public class Towerdefense extends StatelessGame {
private final Random random = new Random(); private final Random random = new Random();
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch(); private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
private final List<Pos> mazePath = new ArrayList<>(); private final List<Pos> mazePath = new ArrayList<>();
private final List<TowerdefenseRoom> instances = new ArrayList<>(); private List<TowerdefenseRoom> instances = new ArrayList<>();
private final WaveGenerator waveGenerator;
private Task waveTask;
private final Map<Material, Class<? extends Tower>> availableTowers = Map.of(
Material.SKELETON_SPAWN_EGG, SkeletonTower.class,
Material.ZOMBIE_SPAWN_EGG, ZombieTower.class,
Material.BLAZE_SPAWN_EGG, BlazeTower.class,
Material.ENDERMAN_SPAWN_EGG, EndermanTower.class,
Material.PIGLIN_BRUTE_SPAWN_EGG, BruteTower.class,
Material.WITHER_SPAWN_EGG, WitherTower.class,
Material.ENDER_DRAGON_SPAWN_EGG, EnderDragonTower.class
);
private final Map<Class<? extends Tower>, Integer> prices = Map.of(
ZombieTower.class, 4,
SkeletonTower.class, 3,
BlazeTower.class, 8,
EndermanTower.class, 20,
BruteTower.class, 27,
WitherTower.class, 40,
EnderDragonTower.class, 200
);
private static final int pathLength = 10;
public Towerdefense() { public Towerdefense() {
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore()); super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
this.setGenerator(new MazeGenerator()); setGenerator(new MazeGenerator());
this.generateMaze(); this.generateMaze();
this.waveGenerator = new WaveGenerator(this);
}
@Override
protected void checkAbandoned() {
this.scheduleNextTick((instance) -> {
if(this.instances.stream().allMatch(room -> room.getPlayers().isEmpty()) && this.getPlayers().isEmpty()) {
this.waveTask.cancel();
this.unload();
}
});
}
@Override
protected void onStart() {
this.getPlayers().forEach(player -> {
TowerdefenseRoom newRoom = new TowerdefenseRoom(player, this);
this.instances.add(newRoom);
player.setInstance(newRoom, new Pos(0, 1, 0));
});
this.waveTask = MinecraftServer.getSchedulerManager().scheduleTask(
this.waveGenerator::startNextWave,
TaskSchedule.seconds(3),
TaskSchedule.seconds(25)
);
} }
private void generateMaze() { private void generateMaze() {
Pos position = new Pos(0, 0, 0); Pos position = new Pos(0, 0, 0);
this.addMazePosition(position, Block.GREEN_WOOL); this.addMazePosition(position, Block.GREEN_WOOL);
position = position.add(0,0,2);
List<Integer> previousDirections = new ArrayList<>(); List<Integer> previousDirections = new ArrayList<>();
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
for (int i = 0; i < pathLength; i++) { for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) { for (int j = 0; j < 3; j++) {
position = position.add(direction-1,0,direction%2); position = position.add(direction-1,0,direction%2);
this.addMazePosition(position, Block.WHITE_WOOL); this.addMazePosition(position, Block.WHITE_WOOL);
} }
@@ -102,16 +48,12 @@ public class Towerdefense extends StatelessGame {
previousDirections.add(direction); previousDirections.add(direction);
} }
this.addMazePosition(position, Block.WHITE_WOOL); this.addMazePosition(position, Block.WHITE_WOOL);
this.addMazePosition(position.add(0,0,3), Block.WHITE_WOOL); this.addMazePosition(position.add(0,0,1), Block.WHITE_WOOL);
this.addMazePosition(position.add(0,0,6), Block.RED_WOOL); this.addMazePosition(position.add(0,0,2), Block.RED_WOOL);
} }
private void addMazePosition(Pos position, Block pathBlock) { private void addMazePosition(Pos position, Block pathBlock) {
for (int i = 0; i < 3; i++) { this.mazeBatch.setBlock(position, pathBlock);
for (int j = 0; j < 3; j++) {
this.mazeBatch.setBlock(position.add(i-1,0,j-1), pathBlock);
}
}
this.mazePath.add(position.add(0.5,1,0.5)); this.mazePath.add(position.add(0.5,1,0.5));
} }
@@ -123,21 +65,12 @@ public class Towerdefense extends StatelessGame {
return this.mazePath; return this.mazePath;
} }
public List<TowerdefenseRoom> getInstances() {
return this.instances;
}
@Override @Override
protected boolean onPlayerJoin(Player p) { protected boolean onPlayerJoin(Player p) {
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> p.teleport(new Pos((long) -this.getPlayers().size(), 1, 0))); TowerdefenseRoom newRoom = new TowerdefenseRoom(p, this);
return super.onPlayerJoin(p); this.instances.add(newRoom);
} p.setInstance(newRoom);
newRoom.startWave(List.of(EntityType.ENDERMAN, EntityType.BLAZE, EntityType.PLAYER, EntityType.HORSE, EntityType.ARMOR_STAND, EntityType.SKELETON));
public Map<Class<? extends Tower>, Integer> getPrices() { return false;
return this.prices;
}
public Map<Material, Class<? extends Tower>> getAvailableTowers() {
return this.availableTowers;
} }
} }

View File

@@ -1,47 +1,22 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense; package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension; import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies.GroupFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator; import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
import eu.mhsl.minenet.minigames.util.BatchUtil; import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.collision.Aerodynamics;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.*; import net.minestom.server.entity.*;
import net.minestom.server.entity.attribute.Attribute; import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.event.entity.EntityDeathEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.PlayerBeginItemUseEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemAnimation;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import org.jetbrains.annotations.Nullable; import net.minestom.server.timer.TaskSchedule;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public class TowerdefenseRoom extends InstanceContainer { public class TowerdefenseRoom extends InstanceContainer {
private final static int reach = 30;
private long lastPlayerAttack = 0;
private final Player player; private final Player player;
private final Towerdefense game; private final Towerdefense game;
private final List<EntityCreature> enemies = new ArrayList<>();
private final List<Tower> towers = new ArrayList<>();
private final Cursor cursor;
private int money = 0;
public TowerdefenseRoom(Player player, Towerdefense game) { public TowerdefenseRoom(Player player, Towerdefense game) {
super(UUID.randomUUID(), Dimension.OVERWORLD.key); super(UUID.randomUUID(), Dimension.OVERWORLD.key);
@@ -50,189 +25,34 @@ public class TowerdefenseRoom extends InstanceContainer {
this.game = game; this.game = game;
this.player.setGameMode(GameMode.ADVENTURE); this.player.setGameMode(GameMode.ADVENTURE);
this.player.setAllowFlying(true); this.player.setAllowFlying(true);
this.addMoney(0); this.player.getInventory().setItemStack(0, ItemStack.of(Material.ARMOR_STAND));
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach);
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach);
this.player.getInventory().addItemStack( setGenerator(new MazeGenerator());
ItemStack.of(Material.BOW).withCustomName(Component.text("Schießen", TextColor.color(255, 180, 0)))
);
this.player.getInventory().addItemStack(
ItemStack.of(Material.BARRIER).withCustomName(Component.text("Löschen", TextColor.color(255,0,0)))
);
this.game.getAvailableTowers().forEach((material, tower) -> {
int price = this.game.getPrices().get(tower);
this.player.getInventory().addItemStack(ItemStack.of(material).withMaxStackSize(price).withAmount(price));
});
this.setGenerator(new MazeGenerator());
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {}); BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
this.cursor = new Cursor(reach);
this.cursor.setInstance(this);
this.cursor.setBoundingBox(0,0,0);
this.eventNode()
.addListener(EntityDeathEvent.class, event -> {
if(!(event.getEntity() instanceof EntityCreature enemy)) return;
this.enemies.remove(enemy);
})
.addListener(PlayerTickEvent.class, event -> this.cursor.updateCursorPosition(this.player))
.addListener(PlayerUseItemEvent.class, event -> this.useItem())
.addListener(PlayerUseItemOnBlockEvent.class, event -> this.useItem())
.addListener(PlayerEntityInteractEvent.class, event -> this.useItem())
.addListener(PlayerHandAnimationEvent.class, event -> this.useItem())
.addListener(ItemDropEvent.class, CommonEventHandles::cancel)
.addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel)
.addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel)
.addListener(PlayerBeginItemUseEvent.class, event -> {
if(event.getAnimation().equals(ItemAnimation.BOW)) event.setCancelled(true);
});
} }
private void useItem() { public void startWave(List<EntityType> entities) {
Material itemInHand = this.player.getItemInMainHand().material(); int counter = 0;
if(itemInHand.equals(Material.BOW)) { for(EntityType entityType : entities) {
this.playerAttack(); MinecraftServer.getSchedulerManager().scheduleTask(() -> {
return; this.addEntity(new EntityCreature(entityType));
return TaskSchedule.stop();
}, TaskSchedule.millis(800L*counter));
counter++;
} }
if(itemInHand.equals(Material.BARRIER)) {
this.removeTower();
return;
}
this.placeTower();
} }
private void playerAttack() { private void addEntity(EntityCreature entity) {
if(this.lastPlayerAttack > System.currentTimeMillis()-200) return; entity.setInstance(this, this.game.getMazePath().getFirst());
Player p = this.player; entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.15);
this.lastPlayerAttack = System.currentTimeMillis(); entity.getNavigator().setPathTo(this.game.getMazePath().get(1), 0.7, () -> changeEntityGoal(entity, 1));
EntityProjectile projectile = new EntityProjectile(p, EntityType.SPECTRAL_ARROW);
projectile.setView(p.getPosition().yaw(), p.getPosition().pitch());
projectile.setNoGravity(true);
projectile.setAerodynamics(new Aerodynamics(0, 1, 1));
Vec projectileVelocity = p.getPosition().direction().normalize().mul(20);
projectile.setVelocity(projectileVelocity);
projectile.scheduleRemove(Duration.ofSeconds(5));
projectile.setInstance(this, p.getPosition().add(0, p.getEyeHeight()-0.5, 0));
projectile.eventNode()
.addListener(ProjectileCollideWithEntityEvent.class, hitEvent -> {
if(!(hitEvent.getTarget() instanceof EntityCreature target)) return;
if(!this.getEnemies().stream().filter(entityCreature -> !entityCreature.isDead()).toList().contains(target)) return;
target.damage(DamageType.PLAYER_ATTACK, 0.1f);
if(target.isDead()) this.addMoney((int) target.getAttribute(Attribute.MAX_HEALTH).getBaseValue());
projectile.remove();
})
.addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove());
} }
public void addMoney(int amount) { private void changeEntityGoal(EntityCreature entity, int positionIndex) {
this.money += amount;
this.player.setLevel(this.money);
}
public Material getPlayerHandMaterial() {
return this.player.getItemInMainHand().material();
}
private synchronized void placeTower() {
if(!this.canPlaceActiveTower()) {
if(this.cursor.isCursorActive() && !this.enoughMoneyForActiveTower()) {
this.player.sendActionBar(Component.text("Nicht genug Geld!", TextColor.color(255,0,0)));
}
return;
}
try {
Material usedMaterial = this.player.getItemInMainHand().material();
Tower tower = this.game.getAvailableTowers().entrySet().stream()
.filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial))
.findFirst()
.orElseThrow()
.getValue()
.getConstructor()
.newInstance();
this.setBlock(this.cursor.getTargetBlockPosition(), Block.BLUE_WOOL);
tower.setInstance(this, this.cursor.getPosition());
this.towers.add(tower);
tower.startShooting();
this.addMoney(-this.game.getPrices().get(tower.getClass()));
} catch (Exception ignored) {
}
this.cursor.updateCursorPosition(this.player);
}
private void removeTower() {
Entity entity = this.player.getLineOfSightEntity(reach, entity1 -> true);
if(!(entity instanceof Tower tower)) return;
if(!this.towers.contains(tower)) return;
this.setBlock(tower.getPosition().sub(0, 0.5, 0), Block.BLACK_WOOL);
this.addMoney(tower.getSellingPrice(this.game.getPrices().get(tower.getClass())));
this.towers.remove(tower);
tower.remove();
}
private boolean canPlaceActiveTower() {
Material usedMaterial = this.player.getItemInMainHand().material();
return this.canPlaceTower(this.game.getAvailableTowers().get(usedMaterial));
}
private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) {
if(towerClass == null) return false;
return this.cursor.isCursorActive() && this.enoughMoney(towerClass);
}
private boolean enoughMoneyForActiveTower() {
Material usedMaterial = this.player.getItemInMainHand().material();
return this.enoughMoney(this.game.getAvailableTowers().get(usedMaterial));
}
private boolean enoughMoney(@Nullable Class<? extends Tower> towerClass) {
if(towerClass == null) return true;
return this.money >= this.game.getPrices().get(towerClass);
}
void startWave(List<GroupFactory> enemyGroups) {
enemyGroups.forEach(groupFactory -> groupFactory.summonGroup(this));
}
public void addEnemy(EntityCreature enemy) {
enemy.setInstance(this, this.game.getMazePath().getFirst());
this.enemies.add(enemy);
this.changeEnemyGoal(enemy, 0);
}
private synchronized void changeEnemyGoal(EntityCreature enemy, int positionIndex) {
if(positionIndex == this.game.getMazePath().size()-1) { if(positionIndex == this.game.getMazePath().size()-1) {
this.enemies.remove(enemy);
enemy.remove();
float damage = (float) Math.ceil(enemy.getHealth()/10);
if(this.player.getHealth() - damage <= 0) {
this.getEnemies().forEach(Entity::remove);
this.towers.forEach(Entity::remove);
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue());
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue());
this.player.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this));
this.player.heal();
this.game.getScore().insertResult(this.player);
return; return;
} }
this.player.damage(DamageType.PLAYER_ATTACK, damage); entity.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.7, () -> changeEntityGoal(entity, positionIndex+1));
return;
}
enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));
} }
public Towerdefense getGame() {
return this.game;
}
public List<EntityCreature> getEnemies() {
return this.enemies;
}
public Player getPlayer() {
return this.player;
}
} }

View File

@@ -1,33 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.attribute.Attribute;
public record EnemyFactory(EntityType entityType, float health, double speed) {
/**
* Factory for a tower defense enemy.
* @param entityType type of enemy
* @param health base health (between 0 and 1024, default 10)
* @param speed walk speed (default 0.1)
*/
public EnemyFactory {
if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024");
}
public EnemyFactory(EntityType entityType) {
this(entityType, 10, 0.1);
}
public EntityCreature buildEntity() {
EntityCreature entity = new EntityCreature(this.entityType);
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(this.speed);
entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(this.health);
entity.setHealth(this.health);
return entity;
}
public int getStrength() {
return (int) Math.floor(this.health * this.speed * 5);
}
}

View File

@@ -1,16 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
import net.minestom.server.MinecraftServer;
import net.minestom.server.timer.TaskSchedule;
public record GroupFactory(EnemyFactory enemyFactory, int count, long delay) {
public void summonGroup(TowerdefenseRoom instance) {
for (int i = 0; i < this.count; i++) {
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
instance.addEnemy(this.enemyFactory.buildEntity());
return TaskSchedule.stop();
}, TaskSchedule.millis(this.delay*i));
}
}
}

View File

@@ -1,77 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.Towerdefense;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.EntityType;
import net.minestom.server.timer.TaskSchedule;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
public class WaveGenerator {
final Towerdefense game;
int currentLevel;
Random random = new Random();
private final Map<EnemyFactory, Integer> availableEnemies = Map.of(
new EnemyFactory(EntityType.PIG, 2, 0.1), new EnemyFactory(EntityType.PIG, 2, 0.1).getStrength(),
new EnemyFactory(EntityType.VILLAGER, 6, 0.1), new EnemyFactory(EntityType.VILLAGER, 6, 0.1).getStrength(),
new EnemyFactory(EntityType.SHEEP, 4, 0.2), new EnemyFactory(EntityType.SHEEP, 4, 0.2).getStrength(),
new EnemyFactory(EntityType.COW, 10, 0.1), new EnemyFactory(EntityType.COW, 10, 0.1).getStrength(),
new EnemyFactory(EntityType.CAMEL, 10, 0.2), new EnemyFactory(EntityType.CAMEL, 10, 0.2).getStrength(),
new EnemyFactory(EntityType.ARMADILLO, 20, 0.4), new EnemyFactory(EntityType.ARMADILLO, 20, 0.3).getStrength(),
new EnemyFactory(EntityType.CHICKEN, 8, 0.5), new EnemyFactory(EntityType.CHICKEN, 8, 0.6).getStrength()
);
public WaveGenerator(Towerdefense game) {
this(game, 1);
}
public WaveGenerator(Towerdefense game, int startLevel) {
this.game = game;
this.currentLevel = startLevel - 1;
}
public void startNextWave() {
this.currentLevel += 1;
List<EnemyFactory> enemyList = this.chooseEnemies();
Collections.shuffle(enemyList);
int averageDelay = Math.min(20000 / enemyList.size(), 1500);
AtomicInteger delay = new AtomicInteger(0);
enemyList.forEach(enemy -> {
delay.addAndGet(averageDelay);
MinecraftServer.getSchedulerManager().scheduleTask(
() -> this.spawnEnemy(enemyList.removeFirst()),
TaskSchedule.millis(delay.get() + this.random.nextInt(averageDelay-200, averageDelay+200)),
TaskSchedule.stop()
);
});
}
private void spawnEnemy(EnemyFactory enemy) {
this.game.getInstances().forEach(instance -> instance.addEnemy(enemy.buildEntity()));
}
private int getCurrentLevelStrength() {
return (int) (0.5 * Math.pow(2, this.currentLevel));
}
private List<EnemyFactory> chooseEnemies() {
int strength = this.getCurrentLevelStrength();
final List<EnemyFactory> usedEnemies = new ArrayList<>();
while(strength > 0) {
int finalStrength = strength;
Map.Entry<EnemyFactory, Integer> chosenEnemy = this.availableEnemies.entrySet().stream()
.filter(e -> e.getValue() <= finalStrength)
.sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2))
.findAny()
.get();
usedEnemies.add(chosenEnemy.getKey());
strength -= chosenEnemy.getKey().getStrength();
}
return usedEnemies;
}
}

View File

@@ -1,16 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
public class BlazeTower extends ShootingTower {
public BlazeTower() {
super(EntityType.BLAZE, 2, 2, 15);
}
@Override
protected void attack(EntityCreature enemy) {
this.swingMainHand();
this.shootProjectile(enemy, EntityType.FIREBALL, 2, 0);
}
}

View File

@@ -1,19 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class BruteTower extends Tower {
public BruteTower() {
super(EntityType.PIGLIN_BRUTE, 20, 2, 4);
this.setItemInMainHand(ItemStack.of(Material.GOLDEN_AXE));
}
@Override
protected void attack(EntityCreature enemy) {
this.swingMainHand();
this.causeDamage(enemy);
}
}

View File

@@ -1,20 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
public class EnderDragonTower extends ShootingTower{
public EnderDragonTower() {
super(EntityType.ENDER_DRAGON, 40, 7, 20);
}
@Override
protected void attack(EntityCreature enemy) {
this.shootProjectile(enemy, EntityType.DRAGON_FIREBALL, 2, 0);
}
@Override
protected void onProjectileHit(EntityCreature enemy) {
super.onProjectileHit(enemy);
}
}

View File

@@ -1,16 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
public class EndermanTower extends Tower {
public EndermanTower() {
super(EntityType.ENDERMAN, 12, 1, 6);
}
@Override
protected void attack(EntityCreature enemy) {
this.swingMainHand();
this.causeDamage(enemy);
}
}

View File

@@ -1,63 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.collision.Aerodynamics;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityProjectile;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
public abstract class ShootingTower extends Tower {
public ShootingTower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
super(entityType, damage, attacksPerSecond, range);
}
protected void shootProjectile(EntityCreature enemy, EntityType projectileType, double power, double spread) {
EntityProjectile projectile = new EntityProjectile(this, projectileType);
projectile.setView(this.getPosition().yaw(), this.getPosition().pitch());
projectile.setNoGravity(true);
projectile.setAerodynamics(new Aerodynamics(0, 1, 1));
Pos startingPoint = this.getPosition().add(0, this.getEyeHeight(), 0);
double enemySpeed = enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() / 0.05;
Point enemyGoal = enemy.getNavigator().getGoalPosition();
if(enemyGoal == null) enemyGoal = enemy.getPosition();
Pos enemyPosition = enemy.getPosition();
Vec enemyMovement = Vec.fromPoint(enemyGoal.sub(enemyPosition)).normalize().mul(enemySpeed);
// just an approximation, not calculated correctly:
double projectileSpeed = 20 * power;
double enemyTowerDistance = startingPoint.distance(enemyPosition);
double estimatedFlightTime = (enemyTowerDistance / projectileSpeed);
Pos targetPosition = enemyPosition.add(enemyMovement.mul(estimatedFlightTime)).withY(enemyPosition.y()+enemy.getEyeHeight());
projectile.shoot(targetPosition, power, spread);
projectile.scheduleRemove(Duration.ofSeconds(5));
projectile.setInstance(this.getInstance(), startingPoint);
projectile.eventNode()
.addListener(ProjectileCollideWithEntityEvent.class, event -> {
if(!(event.getTarget() instanceof EntityCreature target)) return;
if(!this.getRoomInstance().getEnemies().contains(target)) return;
this.causeDamage(target);
this.onProjectileHit(target);
target.setFlyingWithElytra(true);
projectile.remove();
})
.addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove());
}
protected void onProjectileHit(EntityCreature enemy) {
}
@Override
protected void attack(EntityCreature enemy) {
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
}
}

View File

@@ -1,25 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class SkeletonTower extends ShootingTower {
public SkeletonTower() {
super(EntityType.SKELETON, 1, 2, 10);
this.setItemInMainHand(ItemStack.of(Material.BOW));
}
@Override
protected void attack(EntityCreature enemy) {
this.swingMainHand();
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
}
@Override
protected void onProjectileHit(EntityCreature enemy) {
super.onProjectileHit(enemy);
// enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
}
}

View File

@@ -1,135 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.*;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Stream;
public abstract class Tower extends EntityCreature {
public enum Priority {
FIRST(Type.PATH_DISTANCE),
LAST(Type.PATH_DISTANCE),
STRONG(Type.HEALTH),
WEAK(Type.HEALTH),
CLOSE(Type.TOWER_DISTANCE),
FAR(Type.TOWER_DISTANCE);
final Type type;
Priority(Type type) {
this.type = type;
}
public Type getType() {
return this.type;
}
public enum Type {
PATH_DISTANCE,
HEALTH,
TOWER_DISTANCE
}
}
private final static int damageDivider = 10;
private Priority priority = Priority.FIRST;
protected float damage;
protected int range;
protected double attacksPerSecond;
protected TaskSchedule attackDelay;
private @Nullable Task attackTask;
private float sellingPriceMultiplier = 1;
public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
super(entityType);
this.damage = (float) damage / damageDivider;
this.range = range;
this.attacksPerSecond = attacksPerSecond;
this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
}
public void startShooting() {
this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
EntityCreature nextEnemy = this.getNextEnemy();
if(nextEnemy == null) return this.attackDelay;
this.lookAt(nextEnemy);
this.attack(nextEnemy);
return this.attackDelay;
}, TaskSchedule.immediate());
}
@Override
protected void remove(boolean permanent) {
if(this.attackTask != null) this.attackTask.cancel();
super.remove(permanent);
}
private @Nullable EntityCreature getNextEnemy() {
List<EntityCreature> enemies = this.getSortedEnemies();
if(enemies.isEmpty()) return null;
return switch (this.priority) {
case LAST, STRONG, FAR -> enemies.getLast();
case FIRST, WEAK, CLOSE -> enemies.getFirst();
};
}
private List<EntityCreature> getSortedEnemies() {
Stream<EntityCreature> enemyStream = this.getRoomInstance().getEnemies().stream().parallel()
.filter(enemy -> !enemy.isDead())
.filter(enemy -> enemy.getPosition().distance(this.getPosition()) <= this.range);
return switch (this.priority.getType()) {
case PATH_DISTANCE -> enemyStream
.sorted((o1, o2) -> {
Pos endPoint = this.getRoomInstance().getGame().getMazePath().getLast();
return Double.compare(
o1.getPosition().distance(endPoint),
o2.getPosition().distance(endPoint)
);
}).toList();
case TOWER_DISTANCE -> enemyStream
.sorted((o1, o2) -> Double.compare(
o1.getPosition().distance(this.getPosition()),
o2.getPosition().distance(this.getPosition())
)).toList();
case HEALTH -> enemyStream
.sorted((o1, o2) -> Float.compare(o1.getHealth(), o2.getHealth()))
.toList();
};
}
protected TowerdefenseRoom getRoomInstance() {
return (TowerdefenseRoom) this.getInstance();
}
public int getSellingPrice(int buyPrice) {
return (int) (this.sellingPriceMultiplier * buyPrice);
}
public double getValue() {
return this.damage * this.attacksPerSecond * this.range * 2;
}
public int getRange() {
return this.range;
}
protected void causeDamage(EntityCreature enemy) {
this.causeDamage(enemy, this.damage);
}
protected void causeDamage(EntityCreature enemy, float amount) {
enemy.damage(DamageType.PLAYER_ATTACK, amount);
if(enemy.isDead()) this.getRoomInstance().addMoney((int) enemy.getAttribute(Attribute.MAX_HEALTH).getBaseValue());
}
protected abstract void attack(EntityCreature enemy);
}

View File

@@ -1,20 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
public class WitherTower extends ShootingTower {
public WitherTower() {
super(EntityType.WITHER, 18, 4, 13);
}
@Override
protected void attack(EntityCreature enemy) {
this.shootProjectile(enemy, EntityType.WITHER_SKULL, 2, 0);
}
@Override
protected void onProjectileHit(EntityCreature enemy) {
super.onProjectileHit(enemy);
}
}

View File

@@ -1,19 +0,0 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class ZombieTower extends Tower {
public ZombieTower() {
super(EntityType.ZOMBIE, 7, 1, 4);
this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));
}
@Override
protected void attack(EntityCreature enemy) {
this.swingMainHand();
this.causeDamage(enemy);
}
}

View File

@@ -156,6 +156,7 @@ class TrafficLightRace extends StatelessGame {
@Override @Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) { protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(isBeforeBeginning) return; if(isBeforeBeginning) return;
if(getScore().hasResult(playerMoveEvent.getPlayer())) return;
if(phase.equals(LightPhase.RED) && playerMoveEvent.getNewPosition().z()-0.01 > playerMoveEvent.getPlayer().getPosition().z()) { if(phase.equals(LightPhase.RED) && playerMoveEvent.getNewPosition().z()-0.01 > playerMoveEvent.getPlayer().getPosition().z()) {
playerMoveEvent.getPlayer().setVelocity(new Vec(0, 8, -15)); playerMoveEvent.getPlayer().setVelocity(new Vec(0, 8, -15));

View File

@@ -15,7 +15,6 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode; import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.event.player.PlayerBlockBreakEvent; import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.anvil.AnvilLoader;
@@ -67,9 +66,6 @@ public class Room extends MineNetInstance implements Spawnable {
p.getInventory().clear(); p.getInventory().clear();
p.setGameMode(GameMode.ADVENTURE); p.setGameMode(GameMode.ADVENTURE);
p.setInvisible(false); p.setInvisible(false);
p.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(p.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue());
p.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(p.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue());
p.heal();
p.setExp(0); p.setExp(0);
p.setLevel(0); p.setLevel(0);
rooms.add(room); rooms.add(room);
@@ -94,29 +90,29 @@ public class Room extends MineNetInstance implements Spawnable {
private Room(Player owner) { private Room(Player owner) {
super(Dimension.THE_END.key); super(Dimension.THE_END.key);
this.apiDriven = false; this.apiDriven = false;
construct(); this.construct();
setOwner(owner); this.setOwner(owner);
} }
protected Room() { protected Room() {
super(Dimension.THE_END.key); super(Dimension.THE_END.key);
this.apiDriven = true; this.apiDriven = true;
construct(); this.construct();
} }
private void construct() { private void construct() {
MinecraftServer.getInstanceManager().registerInstance(this); MinecraftServer.getInstanceManager().registerInstance(this);
setChunkLoader(new AnvilLoader(Resource.LOBBY_MAP.getPath())); this.setChunkLoader(new AnvilLoader(Resource.LOBBY_MAP.getPath()));
this.gameSelector = new GameSelector(); this.gameSelector = new GameSelector();
this.gameSelector.setInstance(this, new Pos(0.5, 50, 19.5)); this.gameSelector.setInstance(this, new Pos(0.5, 50, 19.5));
eventNode().addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel); this.eventNode().addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel);
eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> unsetRoom(playerDisconnectEvent.getPlayer())); this.eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> unsetRoom(playerDisconnectEvent.getPlayer()));
} }
public Player getOwner() { public Player getOwner() {
return owner; return this.owner;
} }
public void setOwner(Player newOwner) { public void setOwner(Player newOwner) {
@@ -129,7 +125,7 @@ public class Room extends MineNetInstance implements Spawnable {
if(p != this.owner) return; if(p != this.owner) return;
getAllMembers().stream() this.getAllMembers().stream()
.filter(player -> player != p) // exclude the current leaving owner .filter(player -> player != p) // exclude the current leaving owner
.findFirst() .findFirst()
.ifPresentOrElse( .ifPresentOrElse(
@@ -139,8 +135,8 @@ public class Room extends MineNetInstance implements Spawnable {
Room.unsetRoom(p); Room.unsetRoom(p);
new ChatMessage(Icon.ERROR).appendStatic("The room leader left!").send(getAllMembers()); new ChatMessage(Icon.ERROR).appendStatic("The room leader left!").send(this.getAllMembers());
new ChatMessage(Icon.SCIENCE).appendStatic(this.owner.getUsername()).appendStatic(" is the new Leader!").send(getAllMembers().stream().filter(player -> player != this.owner).collect(Collectors.toSet())); new ChatMessage(Icon.SCIENCE).appendStatic(this.owner.getUsername()).appendStatic(" is the new Leader!").send(this.getAllMembers().stream().filter(player -> player != this.owner).collect(Collectors.toSet()));
new ChatMessage(Icon.SUCCESS).appendStatic("You are now the leader.").send(this.owner); new ChatMessage(Icon.SUCCESS).appendStatic("You are now the leader.").send(this.owner);
}); });
} }

View File

@@ -0,0 +1,29 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.util.MapUtil;
import net.minestom.server.entity.Player;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class LowestPointsWinScore extends PointsWinScore {
@Override
protected void insertResultImplementation(Set<Player> p, int currentPoints) {
Set<Player> combined = scoreOrder.entrySet().stream()
.filter(entrySet -> Objects.equals(entrySet.getValue(), currentPoints))
.map(Map.Entry::getKey)
.findFirst()
.orElseGet(() -> {
Set<Player> s = new HashSet<>();
scoreOrder.put(s, currentPoints);
return s;
});
combined.addAll(p);
this.scoreOrder = MapUtil.sortReversedByValue(this.scoreOrder);
getScores().clear();
this.scoreOrder.forEach((player, integer) -> getScores().addFirst(player));
}
}

View File

@@ -12,11 +12,21 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class PointsWinScore extends Score { public class PointsWinScore extends Score {
private Map<Set<Player>, Integer> scoreOrder = new HashMap<>(); Map<Set<Player>, Integer> scoreOrder = new HashMap<>();
@Override @Override
protected void insertResultImplementation(Set<Player> p, int currentPoints) { protected void insertResultImplementation(Set<Player> p, int currentPoints) {
this.scoreOrder.put(p, currentPoints); Set<Player> combined = scoreOrder.entrySet().stream()
.filter(entrySet -> Objects.equals(entrySet.getValue(), currentPoints))
.map(Map.Entry::getKey)
.findFirst()
.orElseGet(() -> {
Set<Player> s = new HashSet<>();
scoreOrder.put(s, currentPoints);
return s;
});
combined.addAll(p);
this.scoreOrder = MapUtil.sortByValue(this.scoreOrder); this.scoreOrder = MapUtil.sortByValue(this.scoreOrder);
getScores().clear(); getScores().clear();
this.scoreOrder.forEach((player, integer) -> getScores().addFirst(player)); this.scoreOrder.forEach((player, integer) -> getScores().addFirst(player));
@@ -42,13 +52,12 @@ public class PointsWinScore extends Score {
.filter(player -> !player.getUsername().isBlank()) .filter(player -> !player.getUsername().isBlank())
.toList() .toList()
.isEmpty()) .isEmpty())
.map(players -> players .map(players -> players.stream()
.stream() .map(Player::getUsername)
.filter(player -> scoreOrder.get(Set.of(player)) != null) .sorted()
.map(player -> player.getUsername()+" : "+scoreOrder.get(Set.of(player)).toString())
.collect(Collectors.joining(", ")) .collect(Collectors.joining(", "))
) + " : " + scoreOrder.get(players)
.toList() ).toList()
) )
.undent() .undent()
.newLine() .newLine()

View File

@@ -4,6 +4,7 @@ import eu.mhsl.minenet.minigames.score.Score;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
public class Tournament { public class Tournament {
private final List<Score> gameScores = new ArrayList<>(); private final List<Score> gameScores = new ArrayList<>();
@@ -43,9 +44,11 @@ public class Tournament {
public Rewards getRewards() { public Rewards getRewards() {
Map<UUID, Integer> itemCount = new HashMap<>(); Map<UUID, Integer> itemCount = new HashMap<>();
int count = 0; int count = 0;
for (Player player : getPlaces()) { for (Set<Player> players : getPlaces()) {
if(count >= this.rewardConfiguration.rewardCount().size()) break; if(count >= this.rewardConfiguration.rewardCount().size()) break;
for(Player player : players) {
itemCount.put(player.getUuid(), this.rewardConfiguration.rewardCount().get(count)); itemCount.put(player.getUuid(), this.rewardConfiguration.rewardCount().get(count));
}
count++; count++;
} }
@@ -53,18 +56,21 @@ public class Tournament {
this.memorialConfiguration.memorialMaterial().namespace().value(), this.memorialConfiguration.memorialMaterial().namespace().value(),
this.memorialConfiguration.memorialTitle(), this.memorialConfiguration.memorialTitle(),
this.memorialConfiguration.memorialLore(), this.memorialConfiguration.memorialLore(),
getPlaces().stream().map(Player::getUuid).toList(), getGameScores().keySet().stream().map(Player::getUuid).toList(),
this.rewardConfiguration.item().namespace().value(), this.rewardConfiguration.item().namespace().value(),
itemCount itemCount
); );
} }
public List<Player> getPlaces() { public List<Set<Player>> getPlaces() {
List<Player> players = new ArrayList<>( List<Set<Player>> players = new ArrayList<>(
getGameScores().entrySet().stream() getGameScores().entrySet().stream()
.sorted(Map.Entry.comparingByValue()) .collect(
.map(Map.Entry::getKey) Collectors.groupingBy(
.toList() Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())
)
).values()
); );
Collections.reverse(players); Collections.reverse(players);

View File

@@ -12,10 +12,12 @@ import net.minestom.server.entity.*;
import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.anvil.AnvilLoader; import net.minestom.server.instance.anvil.AnvilLoader;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set;
public class TournamentDisplay extends MineNetInstance implements Spawnable { public class TournamentDisplay extends MineNetInstance implements Spawnable {
private final List<Player> places; private final List<Set<Player>> places;
private final Tournament tournament; private final Tournament tournament;
private final Pos[] placePositions = new Pos[] { private final Pos[] placePositions = new Pos[] {
@@ -30,7 +32,7 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
this.places = tournament.getPlaces(); this.places = tournament.getPlaces();
this.tournament = tournament; this.tournament = tournament;
this.places.forEach(player -> player.setGameMode(GameMode.ADVENTURE)); this.places.forEach(players -> players.forEach(player -> player.setGameMode(GameMode.ADVENTURE)));
eventNode().addListener(PlayerMoveEvent.class, playerMoveEvent -> { eventNode().addListener(PlayerMoveEvent.class, playerMoveEvent -> {
if(isOnDisplay(playerMoveEvent.getPlayer()) && !playerMoveEvent.getNewPosition().sameBlock(placePositions[getRankPosition(playerMoveEvent.getPlayer())])) { if(isOnDisplay(playerMoveEvent.getPlayer()) && !playerMoveEvent.getNewPosition().sameBlock(placePositions[getRankPosition(playerMoveEvent.getPlayer())])) {
@@ -45,7 +47,10 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
} }
private int getRankPosition(Player player) { private int getRankPosition(Player player) {
return this.places.indexOf(player); for (int i = 0; i < places.size(); i++) {
if (places.get(i).contains(player)) return i;
}
return -1;
} }
@Override @Override
@@ -56,8 +61,11 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
p.teleport(placePositions[getRankPosition(p)]); p.teleport(placePositions[getRankPosition(p)]);
} }
}); });
List<Player> players = this.places.stream()
.flatMap(s -> s.stream().sorted(Comparator.comparing(Player::getUsername)))
.toList();
new ChatMessage(Icon.STAR) new ChatMessage(Icon.STAR)
.numberedList(this.places.stream().map(player -> String.format("%s - %s Punkte", player.getUsername(), tournament.getGameScores().get(player))).toList()) .list(players.stream().map(player -> String.format("%d. %s - %s Punkte", this.getRankPosition(player)+1, player.getUsername(), tournament.getGameScores().get(player))).toList())
.send(p); .send(p);
return false; return false;
} }

View File

@@ -1,9 +1,6 @@
package eu.mhsl.minenet.minigames.util; package eu.mhsl.minenet.minigames.util;
import java.util.ArrayList; import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class MapUtil { public class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) { public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
@@ -17,4 +14,16 @@ public class MapUtil {
return result; return result;
} }
public static <K, V extends Comparable<? super V>> Map<K, V> sortReversedByValue(Map<K, V> map) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
list.sort(Map.Entry.<K, V>comparingByValue().reversed());
Map<K, V> result = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
} }

View File

@@ -12,7 +12,7 @@ public enum BlockPallet {
STONE(new Block[] {Block.CHISELED_STONE_BRICKS, Block.STONE_BRICKS, Block.POLISHED_ANDESITE, Block.POLISHED_BLACKSTONE, Block.POLISHED_DIORITE}), STONE(new Block[] {Block.CHISELED_STONE_BRICKS, Block.STONE_BRICKS, Block.POLISHED_ANDESITE, Block.POLISHED_BLACKSTONE, Block.POLISHED_DIORITE}),
WINTER(new Block[] {Block.SNOW_BLOCK, Block.ICE, Block.PACKED_ICE, Block.BLUE_CONCRETE, Block.SEA_LANTERN}), WINTER(new Block[] {Block.SNOW_BLOCK, Block.ICE, Block.PACKED_ICE, Block.BLUE_CONCRETE, Block.SEA_LANTERN}),
STREET(new Block[] {Block.BLACK_CONCRETE_POWDER, Block.GRAY_CONCRETE_POWDER, Block.GRAVEL, Block.BLACK_CONCRETE, Block.GRAY_CONCRETE}), STREET(new Block[] {Block.BLACK_CONCRETE_POWDER, Block.GRAY_CONCRETE_POWDER, Block.GRAVEL, Block.BLACK_CONCRETE, Block.GRAY_CONCRETE}),
FLOWER(new Block[] {Block.ORANGE_TULIP, Block.PINK_TULIP, Block.RED_TULIP, Block.WHITE_TULIP}),
PRESSURE_PLATES(new Block[] {Block.ACACIA_PRESSURE_PLATE, Block.BIRCH_PRESSURE_PLATE, Block.CRIMSON_PRESSURE_PLATE, Block.JUNGLE_PRESSURE_PLATE, Block.OAK_PRESSURE_PLATE, Block.DARK_OAK_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.POLISHED_BLACKSTONE_PRESSURE_PLATE, Block.SPRUCE_PRESSURE_PLATE, Block.STONE_PRESSURE_PLATE, Block.WARPED_PRESSURE_PLATE}); PRESSURE_PLATES(new Block[] {Block.ACACIA_PRESSURE_PLATE, Block.BIRCH_PRESSURE_PLATE, Block.CRIMSON_PRESSURE_PLATE, Block.JUNGLE_PRESSURE_PLATE, Block.OAK_PRESSURE_PLATE, Block.DARK_OAK_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.HEAVY_WEIGHTED_PRESSURE_PLATE, Block.POLISHED_BLACKSTONE_PRESSURE_PLATE, Block.SPRUCE_PRESSURE_PLATE, Block.STONE_PRESSURE_PLATE, Block.WARPED_PRESSURE_PLATE});
final List<Block> list; final List<Block> list;

View File

@@ -21,6 +21,7 @@ public class ValeGenerator extends HeightTerrainGenerator {
.build(); .build();
private Function<Integer, Double> xShiftMultiplier = multiplier -> 1d; private Function<Integer, Double> xShiftMultiplier = multiplier -> 1d;
private Function<Integer, Double> xShiftOffset = z -> 0d;
public ValeGenerator() { public ValeGenerator() {
setCalculateHeight(this::calculateY); setCalculateHeight(this::calculateY);
@@ -32,10 +33,14 @@ public class ValeGenerator extends HeightTerrainGenerator {
} }
public int getXShiftAtZ(int z) { public int getXShiftAtZ(int z) {
return (int) ((curves.getNoise(z) * 32 + largeCurves.getNoise(z) * 64) * xShiftMultiplier.apply(z)); return (int) ((curves.getNoise(z) * 32 + largeCurves.getNoise(z) * 64) * xShiftMultiplier.apply(z) + xShiftOffset.apply(z));
} }
public void setXShiftMultiplier(Function<Integer, Double> xShiftMultiplier) { public void setXShiftMultiplier(Function<Integer, Double> xShiftMultiplier) {
this.xShiftMultiplier = xShiftMultiplier; this.xShiftMultiplier = xShiftMultiplier;
} }
public void setXShiftOffset(Function<Integer, Double> xShiftOffset) {
this.xShiftOffset = xShiftOffset;
}
} }

View File

@@ -130,3 +130,21 @@ description;Run away from falling anvils;Renne von fallenden Ambossen davon
ns:game_jumpDive#;; ns:game_jumpDive#;;
name;Jump dive;Wassersprung name;Jump dive;Wassersprung
description;Jump into the water, avoiding already used spots!;Springe ins wasser an stellen, in denen noch niemand zuvor gelandet ist! description;Jump into the water, avoiding already used spots!;Springe ins wasser an stellen, in denen noch niemand zuvor gelandet ist!
;;
ns:game_Sumo#;;
name;Sumo;Sumo
lives;Lives;Leben
description;Knock your enemies off and stay on top!;Versuche deinen Gegner von der Plattform zu schubsen!
;;
ns:game_Highground#;;
name;Highground;Hochburg
description;Stay on the high ground to win!;Bleibe solange wie möglich auf der Hochburg, um zu gewinnen!
;;
ns:game_Fastbridge#;;
name;Fastbridge;Fastbridge
description;Speedbridge to the other platform. The first one there wins!;Baue dich so schnell wie möglich zur anderen Plattform. Wer zuerst dort ist, gewinnt!
;;
ns:game_BlockBreakRace#;;
name;Block Break Race;Blockbruch-Rennen
description;Dig down through the tubes using the right tools. The first player to reach the bottom wins!;Grabe dich durch die Röhren nach unten und verwende dabei das richtige Werkzeug. Wer zuerst unten ankommt, gewinnt!
;;
Can't render this file because it has a wrong number of fields in line 114.