Merge branch 'develop' into develop-hannes

# Conflicts:
#	build.gradle
#	src/main/java/eu/mhsl/minenet/minigames/instance/game/GameList.java
This commit is contained in:
2025-07-18 21:09:37 +02:00
78 changed files with 1677 additions and 347 deletions

View File

@@ -5,8 +5,6 @@ import eu.mhsl.minenet.minigames.command.Commands;
import eu.mhsl.minenet.minigames.handler.Listeners;
import eu.mhsl.minenet.minigames.lang.Languages;
import eu.mhsl.minenet.minigames.server.tasks.TablistUpdateTask;
import eu.mhsl.minenet.minigames.server.provider.ByPlayerNameUuidProvider;
import io.github.togar2.pvp.MinestomPvP;
import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.bungee.BungeeCordProxy;
import net.minestom.server.extras.lan.OpenToLAN;
@@ -42,14 +40,12 @@ public class Main {
logger.info("Initialize Minecraft server...");
MinecraftServer server = MinecraftServer.init();
MinestomPvP.init();
// MinestomPvP.init();
MinecraftServer.setBrandName("mhsl.eu - minenet - credits to minestom");
MinecraftServer.setCompressionThreshold(serverConfig.node("compression-threshold").getInt(0));
System.setProperty("minestom.chunk-view-distance", String.valueOf(serverConfig.node("view-distance").getInt()));
MinecraftServer.getConnectionManager().setUuidProvider(new ByPlayerNameUuidProvider());
Commands.values();
Listeners.values();
new HttpServer();

View File

@@ -1,6 +1,6 @@
package eu.mhsl.minenet.minigames.command;
import eu.mhsl.minenet.minigames.command.anonymous.SkinCommand;
import eu.mhsl.minenet.minigames.command.privileged.SkinCommand;
import eu.mhsl.minenet.minigames.command.privileged.*;
import eu.mhsl.minenet.minigames.command.anonymous.HubCommand;
import eu.mhsl.minenet.minigames.command.anonymous.LeaveCommand;
@@ -41,7 +41,7 @@ public enum Commands {
static {
MinecraftServer.getCommandManager().setUnknownCommandCallback((sender, command) -> {
if(command.isBlank()) return;
new ChatMessage(Icon.ERROR).appendStatic("Unknown command").quote(command).send(sender);
new ChatMessage(Icon.ERROR).appendStatic("Unknown command: ").quote(command).send(sender);
});
}
}

View File

@@ -2,6 +2,7 @@ package eu.mhsl.minenet.minigames.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,7 +28,7 @@ public class PrivilegedCommand extends Command {
}
protected CommandCondition isPrivileged() {
return (sender, commandString) -> sender.hasPermission("admin");
return (sender, commandString) -> ((Player) sender).getPermissionLevel() == 4;
}
protected void addCondition(CommandCondition condition) {

View File

@@ -10,9 +10,13 @@ public class HubCommand extends Command {
public HubCommand() {
super("hub");
setCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Room);
setCondition(
(sender, commandString) ->
((Player) sender).getInstance() instanceof Room room && !room.apiDriven
);
setDefaultExecutor((sender, context) -> {
if(Room.getRoom((Player) sender).orElseThrow().apiDriven) return;
Room.unsetRoom((Player) sender);
MoveInstance.move((Player) sender, Hub.INSTANCE);
});

View File

@@ -5,7 +5,6 @@ import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.util.PacketUtil;
import net.minestom.server.entity.Player;
import java.util.ArrayList;
@@ -36,8 +35,6 @@ public class DebugCommand extends PrivilegedCommand {
.appendTranslated("score#thanks")
.send(sender);
PacketUtil.resendPlayerList(((Player) sender));
new ChatMessage(Icon.SCIENCE).appendStatic(((Player) sender).getUuid().toString()).send(sender);
});

View File

@@ -6,7 +6,6 @@ import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.permission.Permission;
public class OpCommand extends PrivilegedCommand {
public OpCommand() {
@@ -15,7 +14,7 @@ public class OpCommand extends PrivilegedCommand {
addSyntax((sender, context) -> {
Player target = MinecraftServer.getConnectionManager().getOnlinePlayerByUsername(context.getRaw("target"));
if(target != null) {
target.addPermission(new Permission("admin"));
target.setPermissionLevel(4);
target.refreshCommands();
} else new ChatMessage(Icon.ERROR).appendStatic("Spieler nicht gefunden").send(sender);
}, ArgumentType.Entity("target").onlyPlayers(true));

View File

@@ -24,7 +24,7 @@ public class PublishRewardCommand extends PrivilegedCommand {
String rewardRequestJson = new Gson().toJson(room.getTournament().getRewards());
HttpRequest giveRewardsRequest = HttpRequest.newBuilder()
.uri(new URI("http://10.20.6.1:8080/api/event/reward"))
.uri(new URI("http://10.20.7.1:8080/api/event/reward"))
.POST(HttpRequest.BodyPublishers.ofString(rewardRequestJson))
.build();

View File

@@ -1,11 +1,11 @@
package eu.mhsl.minenet.minigames.command.anonymous;
package eu.mhsl.minenet.minigames.command.privileged;
import net.minestom.server.command.builder.Command;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
public class SkinCommand extends Command {
public class SkinCommand extends PrivilegedCommand {
public SkinCommand() {
super("skin");

View File

@@ -6,7 +6,6 @@ import net.minestom.server.event.EventListener;
public enum Listeners {
SPAWN(new AddEntityToInstanceEventListener()),
CHAT(new PlayerChatHandler()),
LOGIN(new PlayerLoginHandler()),
LEAVE(new PlayerLeaveHandler());

View File

@@ -1,7 +1,6 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.util.PacketUtil;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
@@ -26,9 +25,7 @@ public class AddEntityToInstanceEventListener implements EventListener<AddEntity
p.setRespawnPoint(instance.getSpawn());
}
PacketUtil.resendPlayerList(p);
p.addEffect(new Potion(PotionEffect.BLINDNESS, (byte) 1, 20)); //TODO Uncomment, currently buggy causes disconnect see https://github.com/Minestom/Minestom/discussions/1302
p.addEffect(new Potion(PotionEffect.BLINDNESS, (byte) 1, 20));
}
return Result.SUCCESS;

View File

@@ -1,26 +0,0 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerChatHandler implements EventListener<PlayerChatEvent> {
@Override
public @NotNull Class<PlayerChatEvent> eventType() {
return PlayerChatEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerChatEvent event) {
event.setChatFormat(
(messages) -> new ChatMessage()
.appendStatic(event.getPlayer().getUsername())
.pipe()
.appendStatic(messages.getMessage())
.build(event.getPlayer())
);
return Result.SUCCESS;
}
}

View File

@@ -11,7 +11,8 @@ import net.minestom.server.entity.Player;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.permission.Permission;
import net.minestom.server.network.packet.server.play.TeamsPacket;
import net.minestom.server.scoreboard.Team;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.configurate.serialize.SerializationException;
@@ -21,6 +22,11 @@ import java.util.UUID;
import java.util.logging.Logger;
public class PlayerLoginHandler implements EventListener<AsyncPlayerConfigurationEvent> {
public static final Team globalTeam = MinecraftServer.getTeamManager()
.createBuilder("global")
.collisionRule(TeamsPacket.CollisionRule.NEVER)
.build();
@Override
public @NotNull Class<AsyncPlayerConfigurationEvent> eventType() {
return AsyncPlayerConfigurationEvent.class;
@@ -37,22 +43,23 @@ public class PlayerLoginHandler implements EventListener<AsyncPlayerConfiguratio
UUID pushQueue = QueuedPlayerRooms.pullQueue(event.getPlayer().getUuid());
MinecraftServer.getSchedulerManager().scheduleTask(
() -> {
if(pushQueue != null) {
Room.setRoom(p, Room.getRoom(pushQueue).orElseThrow());
} else {
MoveInstance.move(p, Hub.INSTANCE);
}
},
TaskSchedule.seconds(5),
TaskSchedule.stop()
() -> {
p.setTeam(globalTeam);
if(pushQueue != null) {
Room.setRoom(p, Room.getRoom(pushQueue).orElseThrow());
} else {
MoveInstance.move(p, Hub.INSTANCE);
}
},
TaskSchedule.seconds(1),
TaskSchedule.stop()
);
SkinCache.applySkin(p);
try {
if(Objects.requireNonNull(Main.globalConfig.node("admins").getList(String.class)).stream().anyMatch(s -> s.equalsIgnoreCase(p.getUsername()))) {
p.addPermission(new Permission("admin"));
p.setPermissionLevel(4);
}
} catch (SerializationException | NullPointerException ignored) {}

View File

@@ -42,13 +42,13 @@ public abstract class Game extends MineNetInstance implements Spawnable {
MinecraftServer.getInstanceManager().registerInstance(this);
logger = Logger.getLogger("Game:" + getUniqueId());
logger = Logger.getLogger("Game:" + getUuid());
eventNode()
.addListener(PlayerMoveEvent.class, this::onPlayerMove)
.addListener(PlayerBlockBreakEvent.class, this::onBlockBreak)
.addListener(PlayerBlockPlaceEvent.class, this::onBlockPlace)
.addListener(ItemDropEvent.class, this::onItemDrop);
.addListener(PlayerMoveEvent.class, this::onPlayerMove)
.addListener(PlayerBlockBreakEvent.class, this::onBlockBreak)
.addListener(PlayerBlockPlaceEvent.class, this::onBlockPlace)
.addListener(ItemDropEvent.class, this::onItemDrop);
}
public Game setParent(Room parentRoom) {
@@ -63,6 +63,17 @@ public abstract class Game extends MineNetInstance implements Spawnable {
game.load();
Room.getRoom(owner).orElseThrow().moveMembersToInstance(game);
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
game.getPlayers().forEach(player -> new ChatMessage(Icon.SCIENCE)
.appendStatic(factory.name().getAssembled(player).asComponent())
.newLine()
.appendStatic(factory.description().getAssembled(player).asComponent())
.send(player));
return TaskSchedule.stop();
}, TaskSchedule.seconds(3));
} catch (Exception e) {
new ChatMessage(Icon.ERROR).appendStatic("Instance crashed: " + e.getMessage()).send(owner);
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> Room.getRoom(owner).orElseThrow().moveMembersToRoomLobby());
@@ -102,7 +113,7 @@ public abstract class Game extends MineNetInstance implements Spawnable {
scheduler().scheduleTask(() -> {
logger.info("stopping game instance " + this.uniqueId);
logger.info("stopping game instance " + this.uuid);
getPlayers().forEach(player -> player.kick("timeout"));
MinecraftServer.getInstanceManager().unregisterInstance(this);

View File

@@ -8,9 +8,11 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.Backroo
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.deathcube.DeathcubeFactory;
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.spleef.SpleefFactory;
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.test.Test;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.test.TestFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory;
@@ -19,18 +21,20 @@ import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.
public enum GameList {
DEATHCUBE(new DeathcubeFactory(), GameType.JUMPNRUN),
STICKFIGHT(new StickFightFactory(), GameType.PVP),
MINERUN(new MinerunFactory(), GameType.JUMPNRUN),
TRAFFICLIGHTRACE(new TrafficLightRaceFactory(), GameType.OTHER),
STICKFIGHT(new StickFightFactory(), GameType.PROTOTYPE),
TOWERDEFENSE(new TowerdefenseFactory(), GameType.PROTOTYPE),
BEDWARS(new BedwarsFactory(), GameType.PROTOTYPE),
BACKROOMS(new BackroomsFactory(), GameType.PROTOTYPE),
BOWSPLEEF(new BowSpleefFactory(), GameType.PROTOTYPE),
TETRIS(new TetrisFactory(), GameType.OTHER),
TNTRUN(new TntRunFactory(), GameType.OTHER),
ACIDRAIN(new AcidRainFactory(), GameType.PVE),
ANVILRUN(new AnvilRunFactory(), GameType.PVE),
ACIDRAIN(new AcidRainFactory(), GameType.PVE),
ELYTRARACE(new ElytraRaceFactory(), GameType.PVP),
SPLEEF(new SpleefFactory(), GameType.PVP),
BOWSPLEEF(new BowSpleefFactory(), GameType.PVP),
JUMPDIVE(new JumpDiveFactory(), GameType.JUMPNRUN),
Test(new TestFactory(), GameType.OTHER);
private final GameFactory factory;

View File

@@ -11,6 +11,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
@@ -20,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
public class StatelessGame extends Game {
private final String name;
private final Score score;
private Task timeLimitTask;
private int timeLimit = 0;
private int timePlayed = 0;
@@ -40,8 +42,12 @@ public class StatelessGame extends Game {
public void setTimeLimit(int limit) {
this.timeLimit = limit;
if(timeLimit > 0) {
scheduler().submitTask(() -> {
if(this.timeLimitTask != null) {
this.timeLimitTask.cancel();
this.timePlayed = 0;
}
if(this.timeLimit > 0) {
this.timeLimitTask = scheduler().submitTask(() -> {
if(!isRunning || timeLimit == 0) return TaskSchedule.stop();
if(timeLimit <= timePlayed) {
stop();
@@ -50,8 +56,8 @@ public class StatelessGame extends Game {
int timeLeft = timeLimit - timePlayed;
switch (timeLeft) {
case 60, 30, 10, 5, 4, 3, 2, 1 ->
new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers());
case 90, 60, 30, 10, 5, 4, 3, 2, 1 ->
new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers());
}
timePlayed++;

View File

@@ -9,6 +9,7 @@ import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import java.util.List;
import java.util.Objects;
public abstract class Option<T> {
private RestrictionHandler restrictionHandler;
@@ -25,7 +26,7 @@ public abstract class Option<T> {
this.name = name;
this.options = options;
currentValue = options.get(0);
currentValue = options.getFirst();
}
public void setRestrictionHandler(RestrictionHandler restrictionHandler) {
@@ -44,11 +45,11 @@ public abstract class Option<T> {
}
public ItemStack getCurrent(Player p) {
int amount = Integer.parseInt(options.get(pointer).toString());
String value = options.get(pointer).toString();
return ItemStack.builder(item)
.customName(name.getAssembled(p))
.lore(TranslatedComponent.byId("optionCommon#value").setColor(NamedTextColor.GOLD).getAssembled(p)
.append(Component.text(": ")).append(Component.text(amount)))
.append(Component.text(": ")).append(Component.text(value)))
.build();
}
@@ -56,6 +57,10 @@ public abstract class Option<T> {
return Integer.parseInt(getAsString());
}
public boolean getAsBoolean() {
return Objects.equals(getAsString(), "true") || Objects.equals(getAsString(), "1");
}
public String getAsString() {
return currentValue.toString();
}

View File

@@ -6,8 +6,8 @@ import net.minestom.server.item.Material;
import java.util.List;
public class BoolOption extends Option<Boolean> {
public class BoolOption extends Option<String> {
public BoolOption(String id, Material item, TranslatedComponent name) {
super(id, item, name, List.of(true, false));
super(id, item, name, List.of("true", "false"));
}
}

View File

@@ -15,6 +15,11 @@ public class AcidRainFactory implements GameFactory {
return TranslatedComponent.byId("game_AcidRain#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_AcidRain#description");
}
@Override
public Material symbol() {
return Material.SLIME_BALL;

View File

@@ -6,31 +6,22 @@ import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.GeneratorUtils;
import io.github.togar2.pvp.entity.projectile.CustomEntityProjectile;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.*;
import net.minestom.server.entity.metadata.other.PrimedTntMeta;
import net.minestom.server.entity.metadata.projectile.ArrowMeta;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.player.PlayerItemAnimationEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.enchant.Enchantment;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class BowSpleef extends StatelessGame {
@@ -41,40 +32,40 @@ public class BowSpleef extends StatelessGame {
public BowSpleef() {
super(Dimension.OVERWORLD.key, "bowSpleef", new LastWinsScore());
eventNode().addListener(
EventListener
.builder(PlayerItemAnimationEvent.class)
.handler(playerItemAnimationEvent -> playerItemAnimationEvent.getPlayer().setTag(CHARGE_BOW_SINCE, System.currentTimeMillis()))
.filter(playerItemAnimationEvent -> playerItemAnimationEvent.getItemAnimationType() == PlayerItemAnimationEvent.ItemAnimationType.BOW)
.build()
);
eventNode().addListener(
EventListener
.builder(ItemUpdateStateEvent.class)
.handler(event -> {
final Player player = event.getPlayer();
final double chargedFor = (System.currentTimeMillis() - player.getTag(CHARGE_BOW_SINCE)) / 1000D;
final double power = MathUtils.clamp((chargedFor * chargedFor + 2 * chargedFor) / 2D, 0, 1);
if (power > 0.2) {
final CustomEntityProjectile projectile = new CustomEntityProjectile(player, EntityType.ARROW);
final ArrowMeta meta = (ArrowMeta) projectile.getEntityMeta();
meta.setCritical(power >= 0.9);
projectile.scheduleRemove(Duration.of(100, TimeUnit.SERVER_TICK));
meta.setOnFire(true);
final Pos position = player.getPosition().add(0, player.getEyeHeight(), 0);
projectile.setInstance(Objects.requireNonNull(player.getInstance()), position);
final Vec direction = projectile.getPosition().direction();
projectile.shootFrom(position.add(direction).sub(0, 0.2, 0), power * 3, 1.0);
projectile.setTag(ARROW_FIRST_HIT, true);
}
})
.filter(itemUpdateStateEvent -> itemUpdateStateEvent.getItemStack().material() == Material.BOW)
.build()
);
// eventNode().addListener( TODO implement bow mechanism
// EventListener
// .builder(PlayerItemAnimationEvent.class)
// .handler(playerItemAnimationEvent -> playerItemAnimationEvent.getPlayer().setTag(CHARGE_BOW_SINCE, System.currentTimeMillis()))
// .filter(playerItemAnimationEvent -> playerItemAnimationEvent.getItemAnimationType() == PlayerItemAnimationEvent.ItemAnimationType.BOW)
// .build()
// );
//
// eventNode().addListener(
// EventListener
// .builder(ItemUpdateStateEvent.class)
// .handler(event -> {
// final Player player = event.getPlayer();
// final double chargedFor = (System.currentTimeMillis() - player.getTag(CHARGE_BOW_SINCE)) / 1000D;
// final double power = MathUtils.clamp((chargedFor * chargedFor + 2 * chargedFor) / 2D, 0, 1);
//
// if (power > 0.2) {
// final CustomEntityProjectile projectile = new CustomEntityProjectile(player, EntityType.ARROW);
// final ArrowMeta meta = (ArrowMeta) projectile.getEntityMeta();
// meta.setCritical(power >= 0.9);
// projectile.scheduleRemove(Duration.of(100, TimeUnit.SERVER_TICK));
// meta.setOnFire(true);
//
// final Pos position = player.getPosition().add(0, player.getEyeHeight(), 0);
// projectile.setInstance(Objects.requireNonNull(player.getInstance()), position);
//
// final Vec direction = projectile.getPosition().direction();
// projectile.shootFrom(position.add(direction).sub(0, 0.2, 0), power * 3, 1.0);
// projectile.setTag(ARROW_FIRST_HIT, true);
// }
// })
// .filter(itemUpdateStateEvent -> itemUpdateStateEvent.getItemStack().material() == Material.BOW)
// .build()
// );
eventNode().addListener(
EventListener

View File

@@ -12,7 +12,12 @@ import java.util.Map;
public class BowSpleefFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("");
return TranslatedComponent.byId("game_BowSpleef#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_BowSpleef#description");
}
@Override

View File

@@ -6,6 +6,7 @@ import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
@@ -27,12 +28,13 @@ class Deathcube extends StatelessGame {
this.percentage = percentage;
this.setGenerator(new CircularPlateTerrainGenerator(radius+10).setPlateHeight(50));
// if(pvpEnabled == 1) eventNode().addChild( // TODO update
// PvPConfig.emptyBuilder()
// .damage(DamageConfig.LEGACY.fallDamage(false))
// .attack(AttackConfig.DEFAULT.attackCooldown(true))
// .build().createNode()
// );
if(pvpEnabled == 1) eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build().createNode()
);
System.out.println(radius);
}

View File

@@ -28,7 +28,7 @@ public class DeathcubeFactory implements GameFactory {
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 10, 20, 30))
.addOption(new NumericOption("height", Material.SCAFFOLDING, TranslatedComponent.byId("optionCommon#height"), 10, 30, 50))
.addOption(new NumericOption("percentage", Material.COBWEB, TranslatedComponent.byId("game_Deathcube#optionPercentageBlocks"), 5, 7, 9, 11, 13))
.addOption(new NumericOption("pvpEnabled", Material.STICK, TranslatedComponent.byId("game_Deathcube#optionPvpEnabled"), 1, 0));
.addOption(new NumericOption("pvpEnabled", Material.STICK, TranslatedComponent.byId("game_Deathcube#optionPvpEnabled"), 0, 1));
}

View File

@@ -17,6 +17,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.*;
@@ -39,6 +40,7 @@ public class ElytraRace extends StatelessGame {
private final ValeGenerator vale = new ValeGenerator();
private final int gameHeight = 0;
private final int seaLevel = -55;
private final int ringSpacing = 100;
private final int ringCount;
@@ -61,7 +63,7 @@ public class ElytraRace extends StatelessGame {
this.ringCount = ringCount;
setGenerator(vale);
vale.setCalculateSeaLevel(point -> -55);
vale.setCalculateSeaLevel(point -> seaLevel);
vale.setXShiftMultiplier(integer -> NumberUtil.map(integer, 50, 500, 0, 1));
vale.addMixIn(new PlaneTerrainGenerator(gameHeight, Block.BARRIER));
@@ -105,9 +107,7 @@ public class ElytraRace extends StatelessGame {
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
Point spawnpoint = new Pos(vale.getXShiftAtZ(0), -46, 0);
GeneratorUtils.iterateArea(spawnpoint.sub(5, 0, 5), spawnpoint.add(5, 0, 5), point -> {
setBlock(point, BlockPallet.STREET.rnd());
});
GeneratorUtils.iterateArea(spawnpoint.sub(5, 0, 5), spawnpoint.add(5, 0, 5), point -> setBlock(point, BlockPallet.STREET.rnd()));
generateRing(ringSpacing);
generateRing(ringSpacing * 2);
@@ -117,7 +117,7 @@ public class ElytraRace extends StatelessGame {
@Override
protected void onStart() {
getPlayers().forEach(player -> {
player.getInventory().setChestplate(ItemStack.of(Material.ELYTRA));
player.getInventory().setEquipment(EquipmentSlot.CHESTPLATE, (byte) 0, ItemStack.of(Material.ELYTRA));
for(int i = 0; i < 3; i++) {
player.getInventory().setItemStack(i, ItemStack.builder(boostMaterial).customName(TranslatedComponent.byId("boost").getAssembled(player)).build());
}
@@ -169,6 +169,7 @@ public class ElytraRace extends StatelessGame {
if(newPos.z() > ringCount * ringSpacing) {
getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
player.setFlyingWithElytra(false);
}
}
@@ -194,8 +195,8 @@ public class ElytraRace extends StatelessGame {
Point ringPos = getRingPositionAtZ(zPos);
GeneratorUtils.iterateArea(
ringPos.sub(100, 0, 0).withY(0), // TODO 0 was before update getDimensionType().getMinY, might not work correctly
ringPos.add(100, 0, 0).withY(gameHeight),
ringPos.sub(100, 0, 0).withY(0),
ringPos.add(100, 0, 0).withY(seaLevel),
point -> batch.setBlock(point, Block.BARRIER)
);
GeneratorUtils.iterateArea(

View File

@@ -14,7 +14,12 @@ import java.util.Map;
public class ElytraRaceFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("");
return TranslatedComponent.byId("game_ElytraRace#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_ElytraRace#description");
}
@Override
@@ -25,7 +30,7 @@ public class ElytraRaceFactory implements GameFactory {
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("ringCount", Material.DIAMOND_BLOCK, TranslatedComponent.byId("ringCount"), 5, 10, 20, 30, 40, 50));
.addOption(new NumericOption("ringCount", Material.DIAMOND_BLOCK, TranslatedComponent.byId("game_ElytraRace#ringCount"), 5, 10, 20, 30, 40, 50));
}
@Override

View File

@@ -0,0 +1,99 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive;
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.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
public class JumpDive extends StatelessGame {
private final int radius;
private final int height;
private final int timeLimit;
private final WeakHashMap<Player, Integer> scores = new WeakHashMap<>();
public JumpDive(int radius, int height, int timeLimit) {
super(Dimension.OVERWORLD.key, "jumpDive", new PointsWinScore());
this.radius = radius;
this.height = height;
this.timeLimit = timeLimit;
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius*2; x <= radius*2; x++) {
for(int z = -radius*2; z <= radius*2; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) {
batch.setBlock(x, height, z, BlockPallet.STONE.rnd());
} else {
batch.setBlock(x, 0, z, BlockPallet.GROUND.rnd());
batch.setBlock(x, 1, z, Block.WATER);
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
setTimeLimit(timeLimit);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player p = playerMoveEvent.getPlayer();
if(
p.isOnGround() && playerMoveEvent.getNewPosition().y() < height
|| playerMoveEvent.getNewPosition().y() < 0
|| isBeforeBeginning && playerMoveEvent.getNewPosition().y() < height
) {
p.teleport(getSpawn());
playerMoveEvent.setCancelled(true);
}
if(
playerMoveEvent.getNewPosition().y() <= 1
&& playerMoveEvent.getNewPosition().distance(0, 1, 0) < radius + 0.5
&& !(!isBeforeBeginning && !isRunning)
) {
setBlock(playerMoveEvent.getNewPosition().withY(1), Block.REDSTONE_BLOCK);
scores.merge(p, 1, Integer::sum);
p.teleport(getSpawn());
playerMoveEvent.setCancelled(true);
p.playSound(Sound.sound(SoundEvent.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.PLAYER, 2f, 2f));
}
}
@Override
protected void onStop() {
getPlayers().forEach(player -> getScore().insertResult(player, scores.getOrDefault(player, 0)));
}
@Override
public Pos getSpawn() {
double theta = rnd.nextDouble() * 2 * Math.PI;
double spawnRadius = radius + 2;
double x = spawnRadius * Math.cos(theta);
double z = spawnRadius * Math.sin(theta);
return new Pos(x, height + 2, z).withLookAt(new Pos(0, height, 0));
}
}

View File

@@ -0,0 +1,43 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.jumpDive;
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 JumpDiveFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_jumpDive#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_jumpDive#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 5, 8, 10, 12, 14, 16))
.addOption(new NumericOption("height", Material.SCAFFOLDING, TranslatedComponent.byId("optionCommon#height"), 30, 60, 90))
.addOption(new NumericOption("timeLimit", Material.CLOCK, TranslatedComponent.byId("optionCommon#seconds"), 60, 120, 180, 240, 300));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new JumpDive(configuration.get("radius").getAsInt(), configuration.get("height").getAsInt(), configuration.get("timeLimit").getAsInt()).setParent(parent);
}
@Override
public Material symbol() {
return Material.WATER_BUCKET;
}
}

View File

@@ -10,7 +10,6 @@ import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquarePlateTerrainGenerator;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
@@ -87,7 +86,6 @@ class Minerun extends StatelessGame {
if(Intersect.withPressurePlate(this, BlockPallet.PRESSURE_PLATES, middle)) { //Player died
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EXPLODE, Sound.Source.PLAYER, 1f, 1f));
p.setPose(Entity.Pose.DYING);
p.teleport(new Pos(p.getPosition().x(), getSpawn().y(), getSpawn().z()));
}

View File

@@ -22,7 +22,7 @@ public class MinerunFactory implements GameFactory {
return new ConfigManager()
.addOption(new NumericOption("width", Material.OAK_FENCE, TranslatedComponent.byId("optionCommon#width"), 10, 30, 50, 100))
.addOption(new NumericOption("length", Material.ZOMBIE_HEAD, TranslatedComponent.byId("optionCommon#length"), 50, 100, 150, 200))
.addOption(new NumericOption("percentage", Material.LIGHT_WEIGHTED_PRESSURE_PLATE, TranslatedComponent.byId("game_Minerun#optionPercentageMiens"), 30, 40, 50, 60, 70));
.addOption(new NumericOption("percentage", Material.LIGHT_WEIGHTED_PRESSURE_PLATE, TranslatedComponent.byId("game_Minerun#optionPercentageMines"), 30, 40, 50, 60, 70));
}
@Override

View File

@@ -78,6 +78,7 @@ public class Spleef extends StatelessGame {
}
private void destroyBlock(PlayerStartDiggingEvent event) {
if(!isRunning) return;
setBlock(event.getBlockPosition(), Block.AIR);
}

View File

@@ -33,7 +33,7 @@ public class SpleefFactory implements GameFactory {
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("game_Spleef#radius"), 10, 20, 30))
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 10, 20, 30))
.addOption(new NumericOption("stackCount", Material.SCAFFOLDING, TranslatedComponent.byId("game_Spleef#stackCount"), 1, 2, 3, 4, 5));
}

View File

@@ -3,29 +3,33 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.togar2.pvp.config.PvPConfig;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
public class Stickfight extends StatelessGame {
private final double radius = 20;
private final WeakHashMap<Player, Pos> spawnPoints = new WeakHashMap<>();
public Stickfight() {
super(Dimension.OVERWORLD.key, "Stickfight", new LastWinsScore());
// eventNode().addChild( // TODO update
// PvPConfig.emptyBuilder()
// .damage(DamageConfig.legacyBuilder().fallDamage(false))
// .attack(AttackConfig.legacyBuilder().attackCooldown(true))
// .build().createNode()
// );
eventNode().addChild(
CombatFeatures.empty()
.add(CombatFeatures.VANILLA_ATTACK)
.add(CombatFeatures.VANILLA_DAMAGE)
.add(CombatFeatures.VANILLA_KNOCKBACK)
.build().createNode()
);
eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
finalAttackEvent.setBaseDamage(0);
@@ -37,21 +41,56 @@ public class Stickfight extends StatelessGame {
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int z = -10; z <= 10; z++) {
batch.setBlock(0, 50, z, Block.SANDSTONE);
}
batch.setBlock(0, 50, 0, Block.GOLD_BLOCK);
setBlock(0, 50, 0, Block.DIAMOND_BLOCK);
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
@Override
protected void start() {
List<Player> players = getPlayers().stream().toList();
int numPlayers = players.size();
for (int i = 0; i < numPlayers; i++) {
double angle = (2 * Math.PI / numPlayers) * i;
int spawnX = (int) (radius * Math.cos(angle));
int spawnZ = (int) (radius * Math.sin(angle));
int spawnY = 50;
Pos spawnpoint = new Pos(spawnX, spawnY + 1, spawnZ).add(0.5);
spawnPoints.put(players.get(i), spawnpoint.withLookAt(getSpawn()));
players.get(i).teleport(spawnpoint);
generateBridge(spawnX, spawnY, spawnZ);
}
setBlock(0, 50, 0, Block.GOLD_BLOCK);
super.start();
}
private void generateBridge(int startX, int startY, int startZ) {
int steps = (int) (radius * 1.5);
for (int i = 0; i < steps; i++) {
double t = (double) i / steps;
int x = (int) (startX * (1 - t));
int z = (int) (startZ * (1 - t));
setBlock(x, startY, z, Block.SANDSTONE);
}
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(isBeforeBeginning) playerMoveEvent.setCancelled(true);
if(!spawnPoints.containsKey(playerMoveEvent.getPlayer())) {
playerMoveEvent.setCancelled(true);
return;
}
if(isBeforeBeginning) {
if(spawnPoints.get(playerMoveEvent.getPlayer()).distance(playerMoveEvent.getNewPosition()) < 1) return;
playerMoveEvent.setCancelled(true);
playerMoveEvent.getPlayer().teleport(spawnPoints.get(playerMoveEvent.getPlayer()));
}
if(playerMoveEvent.getNewPosition().y() < 40) {
playerMoveEvent.getPlayer().teleport(new Pos(0, 51, 0));
playerMoveEvent.getPlayer().teleport(spawnPoints.get(playerMoveEvent.getPlayer()));
}
}

View File

@@ -0,0 +1,160 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.TetrisGame;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.Tetromino;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.*;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
import java.util.*;
class Tetris extends StatelessGame {
private final Map<Player, TetrisGame> tetrisGames = new WeakHashMap<>();
private final int nextTetrominoesCount;
private final boolean isFast;
private final boolean hasCombat;
private boolean setTimeLimit = false;
private final long randomSeed;
public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) {
super(Dimension.THE_END.key, "Tetris", new PointsWinScore());
this.eventNode()
.addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
.addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
.addListener(PlayerTickEvent.class, this::onPlayerTick);
this.nextTetrominoesCount = nextTetrominoesCount;
this.isFast = isFast;
this.hasCombat = hasCombat;
Random random = new Random();
this.randomSeed = random.nextLong();
}
@Override
protected void onStart() {
this.getEntities().stream()
.filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType()))
.forEach(Entity::remove);
if(this.hasCombat) {
this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values()));
}
this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.start());
}
@Override
protected void onStop() {
this.tetrisGames.forEach((player, tetrisGame) -> {
tetrisGame.loose();
this.getScore().insertResult(player, tetrisGame.getScore());
tetrisGame.sidebar.removeViewer(player);
});
}
@Override
protected void onPlayerLeave(Player p) {
this.tetrisGames.get(p).sidebar.removeViewer(p);
this.letPlayerLoose(p);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent event) {
Player player = event.getPlayer();
Pos currentPosition = event.getNewPosition();
TetrisGame tetrisGame = this.tetrisGames.get(player);
if(tetrisGame == null) {
event.setCancelled(true);
return;
}
if(tetrisGame.lost) return;
if(player.getGameMode() == GameMode.SPECTATOR) return;
if(player.inputs().forward()) tetrisGame.pressedButton(TetrisGame.Button.W);
if(player.inputs().backward()) tetrisGame.pressedButton(TetrisGame.Button.S);
if(player.inputs().right()) tetrisGame.pressedButton(TetrisGame.Button.D);
if(player.inputs().left()) tetrisGame.pressedButton(TetrisGame.Button.A);
if(player.inputs().jump()) tetrisGame.pressedButton(TetrisGame.Button.space);
event.setNewPosition(tetrisGame.getPlayerSpawnPosition().withView(currentPosition));
player.setSprinting(false);
}
protected void onPlayerInteract(@NotNull PlayerUseItemEvent event) {
this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight);
}
protected void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) {
this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseLeft);
}
protected void onPlayerTick(PlayerTickEvent event) {
Player player = event.getPlayer();
TetrisGame tetrisGame = this.tetrisGames.get(player);
if(tetrisGame == null) return;
if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) {
this.letPlayerLoose(player);
}
}
private void letPlayerLoose(Player player) {
TetrisGame tetrisGame = this.tetrisGames.get(player);
player.setGameMode(GameMode.SPECTATOR);
player.setInvisible(true);
this.getScore().insertResult(player, tetrisGame.getScore());
boolean allGamesLost = this.tetrisGames.values().stream()
.filter(game -> !game.lost)
.toList()
.isEmpty();
if(!this.setTimeLimit && !allGamesLost) {
this.setTimeLimit(90);
this.setTimeLimit = true;
}
}
@Override
protected boolean onPlayerJoin(Player p) {
p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build());
p.setSprinting(false);
if(this.tetrisGames.get(p) == null) {
this.tetrisGames.put(p, new TetrisGame(
this,
this.getSpawn().sub(6, 8, 15).add(this.tetrisGames.size()*30, 0, 0),
Tetromino.Shape.J,
this.nextTetrominoesCount,
this.isFast,
this.hasCombat,
this.randomSeed
));
this.tetrisGames.get(p).generate();
this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values()));
}
TetrisGame tetrisGame = this.tetrisGames.get(p);
p.teleport(tetrisGame.getPlayerSpawnPosition());
tetrisGame.sidebar.addViewer(p);
return super.onPlayerJoin(p);
}
@Override
public Pos getSpawn() {
return new Pos(0, 30, 15).withView(180, 0);
}
}

View File

@@ -0,0 +1,43 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris;
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.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.BoolOption;
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 TetrisFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Tetris#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Tetris#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("nextTetrominoesCount", Material.LADDER, TranslatedComponent.byId("game_Tetris#nextTetrominoesCount"), 3, 4, 5, 1, 2))
.addOption(new BoolOption("isFast", Material.MINECART, TranslatedComponent.byId("game_Tetris#isFast")))
.addOption(new BoolOption("hasCombat", Material.DIAMOND_SWORD, TranslatedComponent.byId("game_Tetris#hasCombat")));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new Tetris(configuration.get("nextTetrominoesCount").getAsInt(), configuration.get("isFast").getAsBoolean(), configuration.get("hasCombat").getAsBoolean()).setParent(parent);
}
@Override
public Material symbol() {
return Material.PURPLE_WOOL;
}
}

View File

@@ -0,0 +1,191 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import org.apache.commons.lang3.ArrayUtils;
import java.util.Random;
public class Playfield {
private final Pos lowerLeftCorner;
private final StatelessGame instance;
private final static int height = 22;
private final static Block scoreBlock = Block.STONE;
private final int nextTetrominoesCount;
private final Random random;
public Playfield(Pos lowerLeftCorner, StatelessGame instance, int nextTetrominoesCount) {
this.nextTetrominoesCount = nextTetrominoesCount;
this.lowerLeftCorner = lowerLeftCorner;
this.instance = instance;
this.random = new Random();
}
public Pos getPlayerSpawnPosition() {
return this.lowerLeftCorner.add(6, 9, 20).withView(180, 0);
}
public Pos getTetrominoSpawnPosition() {
return this.lowerLeftCorner.add(5, 21, 1);
}
public Pos getHoldPosition() {
return this.lowerLeftCorner.add(-4, 18, 1);
}
public Pos getNextPosition() {
return this.lowerLeftCorner.add(15, 18, 1);
}
public Pos getScorePosition() {
return this.lowerLeftCorner.add(-5, height+3, 0);
}
public void generate() {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
// actual playfield:
for(int x=0; x<12; x++) {
for(int y = 0; y< height; y++) {
batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.GLASS);
batch.setBlock(this.lowerLeftCorner.add(x, y, -1), Block.BLACK_CONCRETE);
if(x==0 || x==11 || y==0) {
batch.setBlock(this.lowerLeftCorner.add(x, y, 1), Block.GRAY_CONCRETE);
batch.setBlock(this.lowerLeftCorner.add(x, y, 0), Block.GRAY_CONCRETE);
}
}
}
// hold position:
for(int x = 0; x < 4; x++) {
for(int y = 0; y < 4; y++) {
batch.setBlock(this.getHoldPosition().add(x-1, y-1, -1), Block.QUARTZ_BLOCK);
}
}
// next positions:
for(int x = 0; x < 4; x++) {
for(int y = -4*this.nextTetrominoesCount+4; y < 4; y++) {
batch.setBlock(this.getNextPosition().add(x-1, y-1, -1), Block.QUARTZ_BLOCK);
}
}
batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 0), Block.STONE);
batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 0), Block.STONE);
batch.setBlock(this.getPlayerSpawnPosition().sub(1, 1, 1), Block.STONE);
batch.setBlock(this.getPlayerSpawnPosition().sub(0, 1, 1), Block.STONE);
BatchUtil.loadAndApplyBatch(batch, this.instance, () -> {});
}
public int removeFullLines() {
int removedLinesCounter = 0;
for(int y = 1; y< height; y++) {
boolean isFullLine = true;
for(int x=1; x<11; x++) {
if(this.instance.getBlock(this.lowerLeftCorner.add(x, y, 1)) == Block.AIR) isFullLine = false;
}
if(isFullLine) {
this.removeFullLine(y);
removedLinesCounter += 1;
y -= 1;
}
}
return removedLinesCounter;
}
public void addLines(int lines) {
int xPosMissing = this.random.nextInt(1, 10);
for (int i = 0; i < lines; i++) {
this.moveAllLinesUp();
for (int x = 1; x < 11; x++) {
if(x != xPosMissing) {
this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.LIGHT_GRAY_CONCRETE);
} else {
this.instance.setBlock(this.lowerLeftCorner.add(x, 1, 1), Block.AIR);
}
}
}
}
public void updateAttackingLines(int attackingLines) {
for (int y = 0; y < height + 5; y++) {
if(attackingLines > 0) {
this.instance.setBlock(this.lowerLeftCorner.add(12, y, 1), Block.REDSTONE_BLOCK);
attackingLines -= 1;
} else {
this.instance.setBlock(this.lowerLeftCorner.add(12, y, 1), Block.AIR);
}
}
}
public void updateScore(int score) {
this.removeDigits();
String scoreString = String.valueOf(score);
char[] characters = scoreString.toCharArray();
ArrayUtils.reverse(characters);
for(int i = 6; i > 0; i--) {
char digit;
if(i <= characters.length) {
digit = characters[i-1];
} else {
digit = '0';
}
this.displayDigit(digit, 6-i);
}
}
private void displayDigit(char digit, int positionFromLeft) {
int[][] digitArray;
switch (digit) {
case '1' -> digitArray = new int[][]{{0,0,1},{0,1,1},{0,0,1},{0,0,1},{0,0,1}};
case '2' -> digitArray = new int[][]{{1,1,1},{0,0,1},{1,1,1},{1,0,0},{1,1,1}};
case '3' -> digitArray = new int[][]{{1,1,1},{0,0,1},{0,1,1},{0,0,1},{1,1,1}};
case '4' -> digitArray = new int[][]{{1,0,1},{1,0,1},{1,1,1},{0,0,1},{0,0,1}};
case '5' -> digitArray = new int[][]{{1,1,1},{1,0,0},{1,1,1},{0,0,1},{1,1,1}};
case '6' -> digitArray = new int[][]{{1,1,1},{1,0,0},{1,1,1},{1,0,1},{1,1,1}};
case '7' -> digitArray = new int[][]{{1,1,1},{0,0,1},{0,1,0},{0,1,0},{0,1,0}};
case '8' -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,1,1},{1,0,1},{1,1,1}};
case '9' -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,1,1},{0,0,1},{1,1,1}};
default -> digitArray = new int[][]{{1,1,1},{1,0,1},{1,0,1},{1,0,1},{1,1,1}};
}
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 5; y++) {
if(digitArray[4-y][x] == 1) this.instance.setBlock(this.getScorePosition().add(positionFromLeft*4+x, y, 0), scoreBlock);
}
}
}
private void removeDigits() {
for (int x = 0; x < 4 * 6; x++) {
for (int y = 0; y < 5; y++) {
this.instance.setBlock(this.getScorePosition().add(x, y, 0), Block.AIR);
}
}
}
private void moveAllLinesUp() {
for (int y = height + 3; y > 1; y--) {
for (int x = 1; x < 11; x++) {
Block blockBeneath = this.instance.getBlock(this.lowerLeftCorner.add(x, y - 1, 1));
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockBeneath);
}
}
}
private void removeFullLine(int positionY) {
for(int y = positionY; y< height; y++) {
for(int x=1; x<11; x++) {
Block blockAbove = this.instance.getBlock(this.lowerLeftCorner.add(x, y+1, 1));
this.instance.setBlock(this.lowerLeftCorner.add(x, y, 1), blockAbove);
}
}
}
}

View File

@@ -0,0 +1,318 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import java.util.*;
public class TetrisGame {
private final StatelessGame instance;
private final Playfield playfield;
private final boolean isFast;
private int level = 1;
private int lines = 0;
private int score = 0;
private int combo = 0;
private int attackingLines = 0;
public boolean lost = false;
public boolean paused = true;
private final boolean hasCombat;
public Tetromino currentTetromino;
private final List<Tetromino> nextTetrominoes = new ArrayList<>();
private Tetromino holdTetromino;
private final List<Tetromino> tetrominoBag = new ArrayList<>();
private boolean holdPossible = true;
private final Pos nextPosition;
private final Pos holdPosition;
private final Pos tetrominoSpawnPosition;
public Sidebar sidebar = new Sidebar(Component.text("Info:"));
private final Map<Button, Long> lastPresses = new HashMap<>();
private final List<TetrisGame> otherTetrisGames = new ArrayList<>();
private final Random random;
public enum Button {
W,
A,
S,
D,
mouseLeft,
mouseRight,
space
}
public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino.Shape startTetrominoShape, int nextTetrominoesCount, boolean isfast, boolean hasCombat, long randomSeed) {
this.isFast = isfast;
this.hasCombat = hasCombat;
this.instance = instance;
this.playfield = new Playfield(lowerLeftCorner, this.instance, nextTetrominoesCount);
this.random = new Random(randomSeed);
this.holdPosition = this.playfield.getHoldPosition();
this.nextPosition = this.playfield.getNextPosition();
this.tetrominoSpawnPosition = this.playfield.getTetrominoSpawnPosition();
this.buildSidebar();
this.currentTetromino = new Tetromino(this.instance, startTetrominoShape);
for (int i = 0; i < nextTetrominoesCount; i++) {
this.updateNextTetrominoes();
}
}
public void pressedButton(Button button) {
final int standardButtonDelay = 100;
final int buttonDebounce = 70;
if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-standardButtonDelay) return;
this.lastPresses.put(button, System.currentTimeMillis());
if(button == Button.W) this.lastPresses.put(button, System.currentTimeMillis()+buttonDebounce);
if(button == Button.S) this.lastPresses.put(button, System.currentTimeMillis()-buttonDebounce);
if(this.lost || this.paused) return;
switch (button) {
case A -> this.currentTetromino.moveLeft();
case S -> this.moveDown();
case D -> this.currentTetromino.moveRight();
case W -> this.hardDrop();
case mouseLeft -> this.currentTetromino.rotate(false);
case mouseRight -> this.currentTetromino.rotate(true);
case space -> this.switchHold();
}
}
public Pos getPlayerSpawnPosition() {
return this.playfield.getPlayerSpawnPosition();
}
public void start() {
this.paused = false;
Scheduler scheduler = MinecraftServer.getSchedulerManager();
scheduler.submitTask(() -> {
if(this.lost) return TaskSchedule.stop();
int standardTickDelay = 40;
if(this.isFast) standardTickDelay = 20;
TaskSchedule nextTick = TaskSchedule.tick(Math.round((float) standardTickDelay /this.level));
if(this.paused) return nextTick;
this.tick();
return nextTick;
});
this.updateInfo();
this.nextTetrominoes.forEach(tetromino -> {
double xChange = -tetromino.getXChange();
tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0));
tetromino.drawAsEntities();
});
}
public void generate() {
this.playfield.generate();
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw(false);
}
public void tick() {
if(this.lost || this.paused) return;
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
}
}
public int getScore() {
return this.score;
}
public void updateOtherTetrisGames(Collection<TetrisGame> tetrisGames) {
List<TetrisGame> games = new ArrayList<>(tetrisGames);
games.remove(this);
games.removeIf(tetrisGame -> tetrisGame.lost);
this.otherTetrisGames.clear();
this.otherTetrisGames.addAll(games);
}
public void getAttacked(int lines) {
if(this.hasCombat && !this.lost) {
this.attackingLines += lines;
this.playfield.updateAttackingLines(this.attackingLines);
}
}
public void loose() {
this.lost = true;
}
private boolean moveDown() {
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
return false;
}
this.score += 1;
this.updateInfo();
return true;
}
private boolean hardDrop() {
if(!this.currentTetromino.moveDown()) {
this.setActiveTetrominoDown();
return false;
}
this.score += 2;
this.updateInfo();
while(this.currentTetromino.moveDown()) {
this.score += 2;
this.updateInfo();
}
this.setActiveTetrominoDown();
return true;
}
private boolean switchHold() {
if(!this.holdPossible) return false;
Tetromino newCurrentTetromino;
if(this.holdTetromino == null) {
newCurrentTetromino = this.nextTetrominoes.removeFirst();
newCurrentTetromino.remove();
this.updateNextTetrominoes();
} else {
newCurrentTetromino = this.holdTetromino;
this.holdTetromino.remove();
}
this.currentTetromino.remove();
this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape());
this.currentTetromino = newCurrentTetromino;
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) this.loose();
double xChange = this.holdTetromino.getXChange();
this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
this.holdTetromino.drawAsEntities();
this.holdPossible = false;
return true;
}
private void updateNextTetrominoes() {
if(this.tetrominoBag.isEmpty()) {
for(Tetromino.Shape shape : Tetromino.Shape.values()) {
this.tetrominoBag.add(new Tetromino(this.instance, shape));
}
Collections.shuffle(this.tetrominoBag, this.random);
}
if(!this.nextTetrominoes.isEmpty()) this.nextTetrominoes.forEach(Tetromino::remove);
Tetromino newTetromino = this.tetrominoBag.removeFirst();
this.nextTetrominoes.add(newTetromino);
this.nextTetrominoes.forEach(tetromino -> {
double xChange = -tetromino.getXChange();
tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0));
tetromino.drawAsEntities();
});
}
private void buildSidebar() {
this.sidebar.createLine(new Sidebar.ScoreboardLine(
"0",
Component.text("Score: "),
0
));
this.sidebar.createLine(new Sidebar.ScoreboardLine(
"1",
Component.text("Lines: "),
0
));
this.sidebar.createLine(new Sidebar.ScoreboardLine(
"2",
Component.text("Level: "),
1
));
}
private void updateInfo() {
this.playfield.updateScore(this.score);
this.sidebar.updateLineScore("0", this.score);
this.sidebar.updateLineScore("1", this.lines);
this.sidebar.updateLineScore("2", this.level);
}
private void setActiveTetrominoDown() {
this.currentTetromino.removeOwnEntities();
this.currentTetromino = this.nextTetrominoes.removeFirst();
this.currentTetromino.remove();
this.updateNextTetrominoes();
int removedLines = this.playfield.removeFullLines();
int combatLines = 0;
this.combo += 1;
switch (removedLines) {
case 0 -> this.combo = 0;
case 1 -> {
this.lines += 1;
this.score += 40 * this.level;
}
case 2 -> {
combatLines = 1;
this.lines += 2;
this.score += 100 * this.level;
}
case 3 -> {
combatLines = 2;
this.lines += 3;
this.score += 300 * this.level;
}
case 4 -> {
combatLines = 4;
this.lines += 4;
this.score += 1200 * this.level;
}
}
this.score += 50 * this.combo * this.level;
if(this.combo >= 2) {
combatLines += (int) Math.floor((double) this.combo /2);
}
if(this.hasCombat && this.attackingLines > 0) {
if(combatLines > 0 && this.attackingLines >= combatLines) {
this.attackingLines -= combatLines;
combatLines = 0;
} else if(combatLines > 0) {
combatLines -= this.attackingLines;
this.attackingLines = 0;
} else {
this.playfield.addLines(this.attackingLines);
this.attackingLines = 0;
}
this.playfield.updateAttackingLines(this.attackingLines);
}
if(this.hasCombat && !this.otherTetrisGames.isEmpty()) {
Collections.shuffle(this.otherTetrisGames);
this.otherTetrisGames.getFirst().getAttacked(combatLines);
}
this.level = (int) Math.floor((double) this.lines / 10) + 1;
this.holdPossible = true;
this.updateInfo();
this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
this.currentTetromino.draw();
if(!this.currentTetromino.moveDown()) {
this.loose();
}
}
}

View File

@@ -0,0 +1,246 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.instance.block.Block;
import net.minestom.server.tag.Tag;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class Tetromino {
private final Shape shape;
private final StatelessGame instance;
private Pos position;
private int[][] shapeArray;
private final static EntityType ghostEntityType = EntityType.FALLING_BLOCK;
private final UUID uuid;
private final static Tag<String> uuidTag = Tag.String("uuid");
public enum Shape {
I,
J,
L,
O,
S,
T,
Z
}
public Tetromino(StatelessGame instance, Shape shape) {
this.instance = instance;
this.shape = shape;
this.uuid = UUID.randomUUID();
switch (this.shape) {
case I -> this.shapeArray = new int[][]{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}};
case J -> this.shapeArray = new int[][]{{1,0,0}, {1,1,1}, {0,0,0}};
case L -> this.shapeArray = new int[][]{{0,0,1}, {1,1,1}, {0,0,0}};
case O -> this.shapeArray = new int[][]{{1,1}, {1,1}};
case S -> this.shapeArray = new int[][]{{0,1,1}, {1,1,0}, {0,0,0}};
case T -> this.shapeArray = new int[][]{{0,1,0}, {1,1,1}, {0,0,0}};
case Z -> this.shapeArray = new int[][]{{1,1,0}, {0,1,1}, {0,0,0}};
}
}
public static EntityType getGhostEntityType() {
return ghostEntityType;
}
public void setPosition(Pos newPosition) {
this.position = newPosition;
}
public boolean rotate(boolean clockwise) {
int[][] newShapeArray = this.getTurnedShapeArray(clockwise);
return this.checkCollisionAndMove(this.position, newShapeArray);
}
public boolean moveDown() {
Pos newPosition = this.position.sub(0, 1, 0);
return this.checkCollisionAndMove(newPosition, this.shapeArray);
}
public boolean moveLeft() {
Pos newPosition = this.position.sub(1, 0, 0);
return this.checkCollisionAndMove(newPosition, this.shapeArray);
}
public boolean moveRight() {
Pos newPosition = this.position.add(1, 0, 0);
return this.checkCollisionAndMove(newPosition, this.shapeArray);
}
public void draw() {
this.draw(true);
}
public void draw(boolean withGhost) {
if(withGhost) {
Pos ghostPos = this.position;
while (!this.checkCollision(ghostPos.sub(0, 1, 0), this.shapeArray)) {
ghostPos = ghostPos.sub(0, 1, 0);
}
Pos positionChange = this.position.sub(ghostPos);
this.getBlockPositions().forEach(pos -> {
Entity ghostBlock = new Entity(ghostEntityType);
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getGhostBlock());
ghostBlock.setNoGravity(true);
ghostBlock.setGlowing(true);
ghostBlock.setTag(uuidTag, this.uuid.toString());
ghostBlock.setInstance(this.instance, pos.sub(positionChange).add(0.5, 0, 0.5));
});
}
this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, this.getColoredBlock()));
}
public void drawAsEntities() {
this.getBlockPositions().forEach(pos -> {
Entity ghostBlock = new Entity(ghostEntityType);
((FallingBlockMeta) ghostBlock.getEntityMeta()).setBlock(this.getColoredBlock());
ghostBlock.setNoGravity(true);
ghostBlock.setTag(uuidTag, this.uuid.toString());
ghostBlock.setInstance(this.instance, pos.add(0.5, 0, 0.5));
});
}
public void remove() {
this.removeOwnEntities();
this.getBlockPositions().forEach(pos -> this.instance.setBlock(pos, Block.AIR));
}
public Block getColoredBlock() {
Block returnBlock;
switch (this.shape) {
case I -> returnBlock = Block.LIGHT_BLUE_CONCRETE;
case J -> returnBlock = Block.BLUE_CONCRETE;
case L -> returnBlock = Block.ORANGE_CONCRETE;
case O -> returnBlock = Block.YELLOW_CONCRETE;
case S -> returnBlock = Block.GREEN_CONCRETE;
case T -> returnBlock = Block.PURPLE_CONCRETE;
case Z -> returnBlock = Block.RED_CONCRETE;
default -> returnBlock = Block.WHITE_CONCRETE;
}
return returnBlock;
}
public void removeOwnEntities() {
this.instance.getEntities().stream()
.filter(entity -> {
String tagValue = entity.getTag(uuidTag);
if(tagValue == null) return false;
return entity.getTag(uuidTag).equals(this.uuid.toString());
})
.forEach(Entity::remove);
}
public double getXChange() {
switch (this.shape) {
case O, I -> {
return 0;
}
case null, default -> {
return 0.5;
}
}
}
public Shape getShape() {
return this.shape;
}
private Block getGhostBlock() {
Block returnBlock;
switch (this.shape) {
case I -> returnBlock = Block.LIGHT_BLUE_STAINED_GLASS;
case J -> returnBlock = Block.BLUE_STAINED_GLASS;
case L -> returnBlock = Block.ORANGE_STAINED_GLASS;
case O -> returnBlock = Block.YELLOW_STAINED_GLASS;
case S -> returnBlock = Block.GREEN_STAINED_GLASS;
case T -> returnBlock = Block.PURPLE_STAINED_GLASS;
case Z -> returnBlock = Block.RED_STAINED_GLASS;
default -> returnBlock = Block.WHITE_STAINED_GLASS;
}
return returnBlock;
}
private int[][] getTurnedShapeArray(boolean clockwise) {
int iterations = 1;
if(!clockwise) iterations = 3;
int arrayLength = this.shapeArray.length;
int[][] startArray = Arrays.stream(this.shapeArray).map(int[]::clone).toArray(int[][]::new);
int[][] returnArray = new int[arrayLength][arrayLength];
for(int k=0; k<iterations; k++) {
for(int i=0; i<arrayLength; i++) {
for(int j=0; j<arrayLength; j++) {
returnArray[i][arrayLength-1-j] = startArray[j][i];
}
}
startArray = Arrays.stream(returnArray).map(int[]::clone).toArray(int[][]::new);
}
return returnArray;
}
private boolean isPartOfTetromino(Pos position) {
return this.getBlockPositions().stream()
.anyMatch(pos -> pos.equals(position));
}
private List<Pos> getBlockPositions() {
return this.getBlockPositions(this.position, this.shapeArray);
}
private List<Pos> getBlockPositions(Pos position, int[][] shapeArray) {
List<Pos> returnList = new ArrayList<>();
if(position == null) return returnList;
int arrayLength = shapeArray.length;
for(int x=0; x<arrayLength; x++) {
for(int y=0; y<arrayLength; y++) {
if(shapeArray[arrayLength-1-y][x] == 1) {
switch (this.shape) {
case I -> returnList.add(position.add(x-1, y-2, 0));
case O -> returnList.add(position.add(x, y, 0));
default -> returnList.add(position.add(x-1, y-1, 0));
}
}
}
}
return returnList;
}
private boolean checkCollision(Pos newPosition, int[][] newShapeArray) {
List<Pos> newBlockPositions = this.getBlockPositions(newPosition, newShapeArray);
for(Pos pos : newBlockPositions) {
if(this.isPartOfTetromino(pos)) continue;
if(this.instance.getBlock(pos) == this.getGhostBlock()) continue;
if(this.instance.getBlock(pos) != Block.AIR) return true;
}
return false;
}
private boolean checkCollisionAndMove(Pos newPosition, int[][] newShapeArray) {
if(!this.checkCollision(newPosition, newShapeArray)) {
this.remove();
this.shapeArray = Arrays.stream(newShapeArray).map(int[]::clone).toArray(int[][]::new);
this.setPosition(newPosition);
this.draw();
return true;
}
return false;
}
}

View File

@@ -7,10 +7,7 @@ import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.metadata.other.PrimedTntMeta;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
@@ -51,7 +48,13 @@ public class TntRun extends StatelessGame {
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < totalElevation) {
if(isBeforeBeginning) {
playerMoveEvent.getPlayer().teleport(getSpawn());
return;
}
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
getScore().insertResult(playerMoveEvent.getPlayer());
}
@@ -67,10 +70,10 @@ public class TntRun extends StatelessGame {
setBlock(firstLocation, Block.AIR);
setBlock(secondLocation, Block.AIR);
Entity fallingTnt = new Entity(EntityType.TNT);
PrimedTntMeta fallingTntMeta = (PrimedTntMeta) fallingTnt.getEntityMeta();
fallingTntMeta.setFuseTime(20);
fallingTnt.setInstance(this, secondLocation);
// Entity fallingTnt = new Entity(EntityType.TNT);
// PrimedTntMeta fallingTntMeta = (PrimedTntMeta) fallingTnt.getEntityMeta();
// fallingTntMeta.setFuseTime(20);
// fallingTnt.setInstance(this, secondLocation);
}
}
}

View File

@@ -14,7 +14,12 @@ import java.util.Map;
public class TntRunFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_tntRun#name");
return TranslatedComponent.byId("game_TntRun#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_TntRun#description");
}
@Override
@@ -25,8 +30,8 @@ public class TntRunFactory implements GameFactory {
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.STICK, TranslatedComponent.byId("game_tntRun#radius"), 20, 30, 50, 60))
.addOption(new NumericOption("levels", Material.SCAFFOLDING, TranslatedComponent.byId("game_tntRun#levels"), 1, 2, 3, 4, 5));
.addOption(new NumericOption("radius", Material.STICK, TranslatedComponent.byId("optionCommon#radius"), 20, 30, 50, 60))
.addOption(new NumericOption("levels", Material.SCAFFOLDING, TranslatedComponent.byId("game_TntRun#levels"), 1, 2, 3, 4, 5));
}
@Override

View File

@@ -3,23 +3,74 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.score.NoScore;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Towerdefense extends StatelessGame {
private final Random random = new Random();
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
private final List<Pos> mazePath = new ArrayList<>();
private List<TowerdefenseRoom> instances = new ArrayList<>();
public Towerdefense() {
super(Dimension.NETHER.key, "Towerdefense", new NoScore());
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
setGenerator(new MazeGenerator());
this.generateMaze();
}
private void generateMaze() {
Pos position = new Pos(0, 0, 0);
this.addMazePosition(position, Block.GREEN_WOOL);
List<Integer> previousDirections = new ArrayList<>();
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 3; j++) {
position = position.add(direction-1,0,direction%2);
this.addMazePosition(position, Block.WHITE_WOOL);
}
int origin = 0;
int bound = 3;
long rightLeftDifference = previousDirections.stream().filter(integer -> integer == 0).count() - previousDirections.stream().filter(integer -> integer == 2).count();
if(rightLeftDifference >= 2 || direction == 2) origin = 1;
if(rightLeftDifference <= -2 || direction == 0) bound = 2;
direction = this.random.nextInt(origin, bound);
previousDirections.add(direction);
}
this.addMazePosition(position, Block.WHITE_WOOL);
this.addMazePosition(position.add(0,0,1), Block.WHITE_WOOL);
this.addMazePosition(position.add(0,0,2), Block.RED_WOOL);
}
private void addMazePosition(Pos position, Block pathBlock) {
this.mazeBatch.setBlock(position, pathBlock);
this.mazePath.add(position.add(0.5,1,0.5));
}
public AbsoluteBlockBatch getMazeBatch() {
return this.mazeBatch;
}
public List<Pos> getMazePath() {
return this.mazePath;
}
@Override
protected boolean onPlayerJoin(Player p) {
p.getInventory().setItemStack(1, ItemStack.of(Material.ARMOR_STAND));
TowerdefenseRoom newRoom = new TowerdefenseRoom(p, this);
this.instances.add(newRoom);
p.setInstance(newRoom);
newRoom.startWave(List.of(EntityType.ENDERMAN, EntityType.BLAZE, EntityType.PLAYER, EntityType.HORSE, EntityType.ARMOR_STAND, EntityType.SKELETON));
return false;
}
}

View File

@@ -0,0 +1,58 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.*;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import java.util.List;
import java.util.UUID;
public class TowerdefenseRoom extends InstanceContainer {
private final Player player;
private final Towerdefense game;
public TowerdefenseRoom(Player player, Towerdefense game) {
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
MinecraftServer.getInstanceManager().registerInstance(this);
this.player = player;
this.game = game;
this.player.setGameMode(GameMode.ADVENTURE);
this.player.setAllowFlying(true);
this.player.getInventory().setItemStack(0, ItemStack.of(Material.ARMOR_STAND));
setGenerator(new MazeGenerator());
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
}
public void startWave(List<EntityType> entities) {
int counter = 0;
for(EntityType entityType : entities) {
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
this.addEntity(new EntityCreature(entityType));
return TaskSchedule.stop();
}, TaskSchedule.millis(800L*counter));
counter++;
}
}
private void addEntity(EntityCreature entity) {
entity.setInstance(this, this.game.getMazePath().getFirst());
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.15);
entity.getNavigator().setPathTo(this.game.getMazePath().get(1), 0.7, () -> changeEntityGoal(entity, 1));
}
private void changeEntityGoal(EntityCreature entity, int positionIndex) {
if(positionIndex == this.game.getMazePath().size()-1) {
return;
}
entity.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.7, () -> changeEntityGoal(entity, positionIndex+1));
}
}

View File

@@ -17,6 +17,11 @@ public class TrafficLightRaceFactory implements GameFactory {
return TranslatedComponent.byId("game_TrafficlightRace#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_TrafficlightRace#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()

View File

@@ -41,7 +41,7 @@ public class Room extends MineNetInstance implements Spawnable {
}
public static void deleteRoom(Room room) {
logger.info("Deleting room " + room.uniqueId);
logger.info("Deleting room " + room.uuid);
rooms.remove(room);
players.values().removeAll(Collections.singleton(room));
room.getAllMembers().forEach(player -> MoveInstance.move(player, Hub.INSTANCE));
@@ -65,6 +65,7 @@ public class Room extends MineNetInstance implements Spawnable {
p.clearTitle();
p.getInventory().clear();
p.setGameMode(GameMode.ADVENTURE);
p.setInvisible(false);
rooms.add(room);
players.put(p, room);
MoveInstance.move(p, room);

View File

@@ -19,4 +19,4 @@ public class Transfer extends MineNetInstance implements Spawnable {
public Pos getSpawn() {
return new Pos(0.5, 1, 0.5);
}
}
}

View File

@@ -28,10 +28,10 @@ public class Languages {
}
public Lang getLanguage(Player p) {
return getLanguage(p.getSettings().getLocale());
return getLanguage(p.getSettings().locale().toString()); // TODO funktioniert die locale noch?
}
public Lang getLanguage(String mapId) {
return languages.computeIfAbsent(mapId, unused -> languages.computeIfAbsent(defaultLanguage, (key) -> new DummyLang()));
return languages.computeIfAbsent(mapId.toLowerCase(), unused -> languages.computeIfAbsent(defaultLanguage, (key) -> new DummyLang()));
}
private void readAll() {

View File

@@ -10,7 +10,7 @@ import java.util.Set;
public class LastWinsScore extends Score {
@Override
public void insertResultImplementation(Set<Player> p) {
getScores().add(0, p);
getScores().addFirst(p);
}
@Override

View File

@@ -0,0 +1,65 @@
package eu.mhsl.minenet.minigames.score;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.util.MapUtil;
import net.minestom.server.entity.Player;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
public class PointsWinScore extends Score {
private Map<Set<Player>, Integer> scoreOrder = new HashMap<>();
@Override
protected void insertResultImplementation(Set<Player> p, int currentPoints) {
this.scoreOrder.put(p, currentPoints);
this.scoreOrder = MapUtil.sortByValue(this.scoreOrder);
getScores().clear();
this.scoreOrder.forEach((player, integer) -> getScores().addFirst(player));
}
@Override
protected void insertResultImplementation(Set<Player> p) {
this.insertResultImplementation(p, 0);
}
@Override
public void setDone() {
if(isClosed()) return;
close();
new ChatMessage(Icon.STAR, true)
.appendTranslated("score#result")
.newLine()
.indent()
.numberedList(
getScores()
.stream()
.filter(players -> !players.stream()
.filter(player -> !player.getUsername().isBlank())
.toList()
.isEmpty())
.map(players -> players
.stream()
.filter(player -> scoreOrder.get(Set.of(player)) != null)
.map(player -> player.getUsername()+" : "+scoreOrder.get(Set.of(player)).toString())
.collect(Collectors.joining(", "))
)
.toList()
)
.undent()
.newLine()
.appendTranslated("score#thanks")
.send(instance.getPlayers());
instance.stop();
}
@Override
protected TranslatableMessage scoreMessage() {
return new TitleMessage(Duration.ofMillis(1000), Duration.ofSeconds(1)).appendTranslated("score#death");
}
}

View File

@@ -8,6 +8,7 @@ import net.minestom.server.entity.Player;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import org.apache.commons.lang3.NotImplementedException;
import java.util.ArrayList;
import java.util.List;
@@ -22,10 +23,19 @@ public abstract class Score {
public Score() {}
public Score(int ignoreLastPlayers) {
this.ignoreLastPlayers = ignoreLastPlayers;
protected abstract void insertResultImplementation(Set<Player> p);
protected void insertResultImplementation(Set<Player> p, int points) {
throw new NotImplementedException("This Score type is not able to process points");
}
public void insertResult(Player p) {
this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p)));
}
public void insertResult(Player p, int points) {
this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p), points));
}
public void attachListeners() {
this.instance.eventNode()
@@ -45,15 +55,8 @@ public abstract class Score {
setDone();
}
}
protected abstract void insertResultImplementation(Set<Player> p);
protected abstract TranslatableMessage scoreMessage();
public void insertResult(Player p) {
if(hasResult(p)) return;
this.scoreMessage().send(p);
this.insertResultImplementation(Set.of(p));
this.checkGameEnd();
}
protected abstract TranslatableMessage scoreMessage();
public void insertRemainingPlayers(Set<Player> players) {
this.insertResultImplementation(players.stream().filter(p -> !hasResult(p)).collect(Collectors.toSet()));
@@ -93,10 +96,19 @@ public abstract class Score {
instance.stop();
}
private void insertResultProcessor(Player p, Runnable callback) {
if(hasResult(p)) return;
this.scoreMessage().send(p);
callback.run();
this.checkGameEnd();
}
public boolean isClosed() {
return isClosed;
}
public void close() { isClosed = true; }
protected void onGameEnd() {
this.instance.stop();
}

View File

@@ -19,9 +19,9 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
private final Tournament tournament;
private final Pos[] placePositions = new Pos[] {
new Pos(8.5, -57, 21.5, 180, 0),
new Pos(9.5, -58, 21.5, 180, 0),
new Pos(7.5, -59, 21.5, 180, 0)
new Pos(8.5, -56, 22.5, 180, 0),
new Pos(12.5, -57, 20.5, 180, 0),
new Pos(4.5, -58, 20.5, 180, 0)
};
public TournamentDisplay(Tournament tournament) {
@@ -30,6 +30,8 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
this.places = tournament.getPlaces();
this.tournament = tournament;
this.places.forEach(player -> player.setGameMode(GameMode.ADVENTURE));
eventNode().addListener(PlayerMoveEvent.class, playerMoveEvent -> {
if(isOnDisplay(playerMoveEvent.getPlayer()) && !playerMoveEvent.getNewPosition().sameBlock(placePositions[getRankPosition(playerMoveEvent.getPlayer())])) {
playerMoveEvent.setCancelled(true);
@@ -62,6 +64,6 @@ public class TournamentDisplay extends MineNetInstance implements Spawnable {
@Override
public Pos getSpawn() {
return new Pos(8.5, -55, 8.5);
return new Pos(8.5, -59, 11.5);
}
}

View File

@@ -1,30 +0,0 @@
package eu.mhsl.minenet.minigames.server.provider;
import eu.mhsl.minenet.minigames.util.UuidUtil;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.UuidProvider;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.mojang.MojangUtils;
import java.util.UUID;
import java.util.logging.Logger;
public class ByPlayerNameUuidProvider implements UuidProvider {
@Override
public UUID provide(PlayerConnection playerConnection, String username) {
try {
if(MinecraftServer.getConnectionManager().findOnlinePlayer(username) != null) throw new IllegalStateException();
String client = MojangUtils.fromUsername(username).get("id").getAsString();
return UuidUtil.unTrimm(client);
} catch (NullPointerException e) {
Logger.getGlobal().info("Player " + username + " is unknown by Mojang! (Using random UUID)");
} catch (IllegalStateException e) {
Logger.getGlobal().info("Player with the username " + username + " is already online.");
playerConnection.disconnect();
}
return UUID.randomUUID();
}
}

View File

@@ -28,10 +28,6 @@ public class InteractableEntity extends EntityCreature {
.addListener(PlayerEntityInteractEvent.class, this::onInteract) //TODO Why are some of these listeners not working?
.addListener(EntityAttackEvent.class, this::onAttack);
System.out.println(eventNode().getChildren());
System.out.println(eventNode().getParent().getChildren());
}
private void setInstanceEvent(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {

View File

@@ -3,7 +3,6 @@ package eu.mhsl.minenet.minigames.skin;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import java.util.HashMap;
@@ -12,19 +11,15 @@ import java.util.Map;
public class SkinCache {
private static final Map<String, PlayerSkin> skins = new HashMap<>();
public static PlayerSkin getSkin(Player p) {
return SkinCache.getSkin(p.getUsername());
}
public static PlayerSkin getSkin(String p) {
if(!skins.containsKey(p)) skins.put(p, PlayerSkin.fromUsername(p));
return skins.get(p);
}
public static void applySkin(Player p) {
MinecraftServer.getSchedulerManager().submitTask(() -> {
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
p.setSkin(SkinCache.getSkin(p.getUsername()));
return TaskSchedule.stop();
}, ExecutionType.TICK_END);
}, TaskSchedule.millis(500));
}
}

View File

@@ -130,25 +130,10 @@ public class InventoryItemAlignment {
}.get(count);
}
public static class ItemOffset {
private final int x;
private final int z;
public ItemOffset(int x, int z) {
this.x = x;
this.z = z;
}
public int getX() {
return x;
}
public int getZ() {
return z;
}
public record ItemOffset(int x, int z) {
public int get() {
return x + (z * 9);
return x + (z * 9);
}
}
}
}

View File

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

View File

@@ -1,39 +0,0 @@
package eu.mhsl.minenet.minigames.util;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket;
import java.util.List;
public class PacketUtil {
public static void resendPlayerList(Player p) {
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> {
if(player.getUuid().equals(p.getUuid())) return;
final PlayerSkin skin = player.getSkin();
List<PlayerInfoUpdatePacket.Property> properties =
skin != null ? List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())) : List.of();
p.sendPacket(
new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER,
new PlayerInfoUpdatePacket.Entry(
player.getUuid(),
player.getUsername(),
properties,
true,
0,
GameMode.SURVIVAL,
null,
null
)
)
);
// p.sendPacket( //TODO spawnPlayerPacket does no longer exists
// new SpawnPlayerPacket(player.getEntityId(), player.getUuid(), player.getPosition())
// );
});
}
}

View File

@@ -1,5 +0,0 @@
package eu.mhsl.minenet.minigames.util;
public abstract class Static {
public abstract void load(); //TODO REMOVE
}

View File

@@ -67,7 +67,7 @@ public class SquarePlateTerrainGenerator extends PlateTerrainGenerator {
}
if(generateBorders) {
Runnable generateBorder = () -> unit.modifier().fill(bottom, bottom.add(1, 0, 1), Block.BARRIER); // TODO DimensionType.OVERWORLD.getMaxY() is now hardcoded 0, might break behaviour
Runnable generateBorder = () -> unit.modifier().fill(bottom, bottom.add(1, DimensionType.VANILLA_MAX_Y, 1), Block.BARRIER);
if(bottom.z() <= length+1 && bottom.z() >= -1 && bottom.x() >= -1 && bottom.x() <= width+1) {
if(bottom.x() == -1 || bottom.x() == width+1) {