Compare commits

...

7 Commits

18 changed files with 342 additions and 61 deletions

View File

@@ -16,6 +16,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -178,6 +179,13 @@ public class Report extends Appliance {
issuer.sendMessage(component.build()); issuer.sendMessage(component.build());
} }
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new ReportCreatedListener()
);
}
@Override @Override
@NotNull @NotNull
protected List<ApplianceCommand<?>> commands() { protected List<ApplianceCommand<?>> commands() {

View File

@@ -0,0 +1,26 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.core.event.ReportCreatedEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.event.EventHandler;
public class ReportCreatedListener extends ApplianceListener<Report> {
@EventHandler
public void onReport(ReportCreatedEvent event) {
OfflinePlayer reporter = Bukkit.getOfflinePlayer(event.getReport().reporter());
OfflinePlayer reported = Bukkit.getOfflinePlayer(event.getReport().reported());
Component message = Component.text(
"\uD83D\uDD14 Neuer Report von %s gegen %s: %s".formatted(reporter.getName(), reported.getName(), event.getReport().reason()),
NamedTextColor.YELLOW
);
Bukkit.getOnlinePlayers().stream()
.filter(player -> player.hasPermission("admin"))
.forEach(player -> player.sendMessage(message));
}
}

View File

@@ -5,15 +5,10 @@ import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Boat; import org.bukkit.entity.Boat;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class AntiBoatFreecam extends Appliance { public class AntiBoatFreecam extends Appliance {
private static final float MAX_YAW_OFFSET = 106.0f; private static final float MAX_YAW_OFFSET = 106.0f;
private final Map<Player, Float> violatedPlayers = new HashMap<>();
public AntiBoatFreecam() { public AntiBoatFreecam() {
Bukkit.getScheduler().runTaskTimerAsynchronously( Bukkit.getScheduler().runTaskTimerAsynchronously(
@@ -27,14 +22,12 @@ public class AntiBoatFreecam extends Appliance {
float yawDelta = wrapDegrees(playerYaw - boatYaw); float yawDelta = wrapDegrees(playerYaw - boatYaw);
if(Math.abs(yawDelta) <= MAX_YAW_OFFSET) return; if(Math.abs(yawDelta) <= MAX_YAW_OFFSET) return;
this.violatedPlayers.merge(player, 1f, Float::sum); Main.instance().getAppliance(AcInform.class).slowedNotifyAdmins(
float violationCount = this.violatedPlayers.get(player);
if(violationCount != 1 && violationCount % 100 != 0) return;
Main.instance().getAppliance(AcInform.class).notifyAdmins(
"internal", "internal",
player.getName(), player.getName(),
"illegalBoatLookYaw", "illegalBoatLookYaw",
violationCount yawDelta,
3000
); );
}), }),
1L, 1L,

View File

@@ -0,0 +1,39 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import net.kyori.adventure.util.Ticks;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Appliance.Flags(enabled = false)
public class AntiInventoryMove extends Appliance {
private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2;
private final Map<UUID, Long> invOpen = new ConcurrentHashMap<>();
public void setInvOpen(Player player, boolean open) {
if(open)
this.invOpen.put(player.getUniqueId(), System.currentTimeMillis());
else
this.invOpen.remove(player.getUniqueId());
}
public boolean hasInventoryOpen(Player player) {
if(!this.invOpen.containsKey(player.getUniqueId())) return false;
return this.invOpen.get(player.getUniqueId()) < System.currentTimeMillis() - errorTimeMargin;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new InventoryTrackerListener(),
new InInventoryMoveListener()
);
}
}

View File

@@ -0,0 +1,21 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerInputEvent;
class InInventoryMoveListener extends ApplianceListener<AntiInventoryMove> {
@EventHandler
public void onInput(PlayerInputEvent event) {
if(!this.getAppliance().hasInventoryOpen(event.getPlayer())) return;
Main.instance().getAppliance(AcInform.class).slowedNotifyAdmins(
"internal",
event.getPlayer().getName(),
"inInventoryMove",
-1f,
3000
);
}
}

View File

@@ -0,0 +1,23 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> {
@EventHandler(priority = EventPriority.MONITOR)
public void onOpen(InventoryOpenEvent event) {
if(!(event.getPlayer() instanceof Player player)) return;
if(event.isCancelled()) return;
this.getAppliance().setInvOpen(player, true);
}
@EventHandler
public void onClose(InventoryCloseEvent event) {
if(!(event.getPlayer() instanceof Player player)) return;
this.getAppliance().setInvOpen(player, false);
}
}

View File

@@ -11,14 +11,20 @@ import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class AcInform extends Appliance { public class AcInform extends Appliance {
private final Map<String, Map<String, Long>> violationSlowdowns = new ConcurrentHashMap<>();
public void processCommand(@NotNull String[] args) { public void processCommand(@NotNull String[] args) {
String anticheatName = null; String anticheatName = null;
String playerName = null; String playerName = null;
String checkName = null; String checkName = null;
Float violationCount = null; Float violationCount = null;
int notifyEvery = 0;
for(int i = 0; i < args.length; i++) { for(int i = 0; i < args.length; i++) {
if(!args[i].startsWith("--")) continue; if(!args[i].startsWith("--")) continue;
@@ -36,13 +42,32 @@ public class AcInform extends Appliance {
case "--playerName" -> playerName = value; case "--playerName" -> playerName = value;
case "--check" -> checkName = value; case "--check" -> checkName = value;
case "--violationCount" -> violationCount = value.isEmpty() ? null : Float.valueOf(value); case "--violationCount" -> violationCount = value.isEmpty() ? null : Float.valueOf(value);
case "--notifyEvery" -> notifyEvery = Integer.parseInt(value);
} }
} }
if(notifyEvery == 0) {
this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
} else {
this.slowedNotifyAdmins(anticheatName, playerName, checkName, violationCount, notifyEvery);
}
}
public void slowedNotifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount, int notifyEvery) {
this.violationSlowdowns.putIfAbsent(playerName, new HashMap<>());
var slowdowns = this.violationSlowdowns.get(playerName);
if(slowdowns.containsKey(checkName)) {
if(slowdowns.get(checkName) > System.currentTimeMillis() - notifyEvery) return;
}
this.notifyAdmins(anticheatName, playerName, checkName, violationCount); this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
} }
public void notifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount) { public void notifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount) {
this.violationSlowdowns.putIfAbsent(playerName, new HashMap<>());
this.violationSlowdowns.get(playerName).put(checkName, System.currentTimeMillis());
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text(); ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
NamedTextColor textColor = NamedTextColor.GRAY; NamedTextColor textColor = NamedTextColor.GRAY;
@@ -85,28 +110,42 @@ public class AcInform extends Appliance {
Component.newline() Component.newline()
.append(Component.text("", NamedTextColor.GRAY)) .append(Component.text("", NamedTextColor.GRAY))
.append(Component.text("[", NamedTextColor.GRAY)) .append(Component.text("[", NamedTextColor.GRAY))
.append(Component.text("Report", NamedTextColor.GOLD)) .append(Component.text("\uD83D\uDCD6", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY)) .append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName))) .clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
); );
component.append( component.append(
Component.text(" [", NamedTextColor.GRAY) Component.text(" [", NamedTextColor.GRAY)
.append(Component.text("Kick", NamedTextColor.GOLD)) .append(Component.text("\u23F1", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY)) .append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName))) .clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
); );
component.append( component.append(
Component.text(" [", NamedTextColor.GRAY) Component.text(" [", NamedTextColor.GRAY)
.append(Component.text("Panic Ban", NamedTextColor.GOLD)) .append(Component.text("\uD83E\uDDB6", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/kickunsuspected %s", playerName)))
);
component.append(
Component.text(" [", NamedTextColor.GRAY)
.append(Component.text("\u2623", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/kickcrash %s", playerName)))
);
component.append(
Component.text(" [", NamedTextColor.GRAY)
.append(Component.text("\uD83D\uDD12", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY)) .append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName))) .clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName)))
); );
component.append( component.append(
Component.text(" [", NamedTextColor.GRAY) Component.text(" [", NamedTextColor.GRAY)
.append(Component.text("Spectate/Teleport", NamedTextColor.GOLD)) .append(Component.text("\uD83D\uDC41", NamedTextColor.GOLD))
.append(Component.text("]", NamedTextColor.GRAY)) .append(Component.text("]", NamedTextColor.GRAY))
.clickEvent(ClickEvent.suggestCommand(String.format("/grim spectate %s", playerName))) .clickEvent(ClickEvent.suggestCommand(String.format("/grim spectate %s", playerName)))
); );

View File

@@ -1,9 +1,14 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick; package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -25,9 +30,36 @@ public class Kick extends Appliance {
).applyKick(player); ).applyKick(player);
} }
public void unsuspectedKick(@NotNull String playerName) {
Player player = Bukkit.getPlayer(playerName);
if(player == null)
throw new ApplianceCommand.Error("Player not found");
String material = Material.values()[(int)(Math.random() * Material.values().length)].name();
player.kick(Component.text("java.lang.IllegalStateException: Failed to create model for minecraft:%s".formatted(material)));
}
public void crashKick(@NotNull String playerName) {
Player player = Bukkit.getPlayer(playerName);
if(player == null)
throw new ApplianceCommand.Error("Player not found");
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
PlayerUtils.sendCube(player, 100, Material.ENCHANTING_TABLE.createBlockData());
PlayerUtils.sendCube(player, 5, Material.DIRT.createBlockData());
});
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> player.kick(Component.empty()), Ticks.TICKS_PER_SECOND * 15);
}
@Override @Override
@NotNull @NotNull
protected List<ApplianceCommand<?>> commands() { protected List<ApplianceCommand<?>> commands() {
return List.of(new KickCommand()); return List.of(
new KickCommand(),
new KickUnsuspectedCommand(),
new KickCrashCommand()
);
} }
} }

View File

@@ -0,0 +1,31 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class KickCrashCommand extends ApplianceCommand<Kick> {
public KickCrashCommand() {
super("kickCrash");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
if(args.length < 1) throw new Error("Es muss ein Spielername angegeben werden!");
this.getAppliance().crashKick(args[0]);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return super.tabCompleteReducer(
Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(),
args
);
}
}

View File

@@ -0,0 +1,31 @@
package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class KickUnsuspectedCommand extends ApplianceCommand<Kick> {
public KickUnsuspectedCommand() {
super("kickUnsuspected");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
if(args.length < 1) throw new Error("Es muss ein Spielername angegeben werden!");
this.getAppliance().unsuspectedKick(args[0]);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return super.tabCompleteReducer(
Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(),
args
);
}
}

View File

@@ -28,14 +28,16 @@ public class WebsiteHook extends HttpHook {
return HttpServer.nothing; return HttpServer.nothing;
})); }));
record CreatedReport(String reporter, String reported, String reason) {} record CreatedReport(UUID reporter, UUID reported, String reason) {}
this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> { this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> {
Main.logger().info(String.format("New Report from Hook: (%s) Reporter: %s Reported: %s", createdReport.reason, createdReport.reporter, createdReport.reported));
SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason))); SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason)));
return HttpServer.nothing; return HttpServer.nothing;
})); }));
record CreatedStrike(UUID uuid) {} record CreatedStrike(UUID uuid) {}
this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> { this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> {
Main.logger().info(String.format("New Strike from Hook! (User %s)", createdStrike.uuid));
SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid))); SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid)));
return HttpServer.nothing; return HttpServer.nothing;
})); }));

View File

@@ -4,6 +4,8 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ReportCreatedEvent extends Event { public class ReportCreatedEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList(); private static final HandlerList HANDLERS = new HandlerList();
@Override @Override
@@ -15,7 +17,7 @@ public class ReportCreatedEvent extends Event {
return HANDLERS; return HANDLERS;
} }
public record CreatedReport(String reporter, String reported, String reason) {} public record CreatedReport(UUID reporter, UUID reported, String reason) {}
private final CreatedReport report; private final CreatedReport report;
public ReportCreatedEvent(CreatedReport report) { public ReportCreatedEvent(CreatedReport report) {

View File

@@ -1,10 +1,16 @@
package eu.mhsl.craftattack.spawn.core.util.entity; package eu.mhsl.craftattack.spawn.core.util.entity;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Statistic; import org.bukkit.Statistic;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
public class PlayerUtils { public class PlayerUtils {
public static void resetStatistics(Player player) { public static void resetStatistics(Player player) {
for(Statistic statistic : Statistic.values()) { for(Statistic statistic : Statistic.values()) {
@@ -30,4 +36,30 @@ public class PlayerUtils {
} }
} }
} }
public static void sendCube(Player player, int cubeSize, BlockData fakeBlock) {
Location loc = player.getLocation();
World world = player.getWorld();
int half = cubeSize / 2;
int cx = loc.getBlockX();
int cy = loc.getBlockY();
int cz = loc.getBlockZ();
int minY = world.getMinHeight();
int maxY = world.getMaxHeight() - 1;
Map<Location, BlockData> changes = new HashMap<>();
for (int x = cx - half; x <= cx + half; x++) {
for (int y = Math.max(cy - half, minY); y <= Math.min(cy + half, maxY); y++) {
for (int z = cz - half; z <= cz + half; z++) {
changes.put(new Location(world, x, y, z), fakeBlock);
}
}
}
//noinspection UnstableApiUsage
player.sendMultiBlockChange(changes);
}
} }

View File

@@ -33,43 +33,43 @@ import java.util.concurrent.ThreadLocalRandom;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
public class Bloodmoon extends Appliance { public class Bloodmoon extends Appliance {
// für alle Dimensionen? einstellbar machen? public final Map<EntityType, Set<MobEffect>> affectedMobEffectMap = Map.ofEntries(
Map.entry(EntityType.ZOMBIE, Set.of(
public final Map<EntityType, Set<MobEffect>> affectedMobEffectMap = Map.of(
EntityType.ZOMBIE, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1) new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
), )),
EntityType.SKELETON, Set.of( Map.entry(EntityType.SKELETON, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 3.5, 1) new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 3.5, 1)
), )),
EntityType.SPIDER, Set.of( Map.entry(EntityType.SPIDER, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.POISON, 4, 1), new MobEffect.PotionMobEffect(PotionEffectType.POISON, 4, 1),
new MobEffect.PotionMobEffect(PotionEffectType.NAUSEA, 6, 10) new MobEffect.PotionMobEffect(PotionEffectType.NAUSEA, 6, 10)
), )),
EntityType.CREEPER, Set.of( Map.entry(EntityType.CREEPER, Set.of(
new MobEffect.LightningMobEffect() new MobEffect.LightningMobEffect()
), )),
EntityType.HUSK, Set.of( Map.entry(EntityType.HUSK, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1) new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
), )),
EntityType.DROWNED, Set.of( Map.entry(EntityType.STRAY, Set.of()),
Map.entry(EntityType.DROWNED, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1) new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
), )),
EntityType.WITCH, Set.of(), Map.entry(EntityType.WITCH, Set.of()),
EntityType.ZOMBIE_VILLAGER, Set.of( Map.entry(EntityType.ZOMBIE_VILLAGER, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1) new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
), )),
EntityType.PHANTOM, Set.of( Map.entry(EntityType.PHANTOM, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.LEVITATION, 1.5, 3) new MobEffect.PotionMobEffect(PotionEffectType.LEVITATION, 1.5, 3)
), )),
EntityType.ENDERMAN, Set.of( Map.entry(EntityType.ENDERMAN, Set.of(
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 2.5, 2) new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 2.5, 2)
) ))
); );
public final int expMultiplier = 3; public final int expMultiplier = 3;
public final double mobDamageMultiplier = 2; public final double mobDamageMultiplier = 2;
public final double mobHealthMultiplier = 2; public final double mobHealthMultiplier = 2;
private final ThreadLocalRandom random = ThreadLocalRandom.current();
private boolean isActive = false; private boolean isActive = false;
private final BossBar bossBar = BossBar.bossBar( private final BossBar bossBar = BossBar.bossBar(
Component.text("Blutmond", NamedTextColor.DARK_RED), Component.text("Blutmond", NamedTextColor.DARK_RED),
@@ -89,10 +89,11 @@ public class Bloodmoon extends Appliance {
EntityType.SKELETON, EntityType.SKELETON,
EntityType.SPIDER EntityType.SPIDER
); );
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new HashMap<>(); private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
private long lastBloodmoonStartTick = 0; private long lastBloodmoonStartTick = 0;
public final int ticksPerDay = 24000; public final int ticksPerDay = 24000;
public final int bloodmoonLength = ticksPerDay/2; public final int bloodmoonLength = ticksPerDay/2;
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
private final int bloodmoonFreeDaysAtStart = 3; private final int bloodmoonFreeDaysAtStart = 3;
private final int bloodmoonStartTime = ticksPerDay/2; private final int bloodmoonStartTime = ticksPerDay/2;
private final int bloodmoonDayInterval = 30; private final int bloodmoonDayInterval = 30;
@@ -145,7 +146,7 @@ public class Bloodmoon extends Appliance {
} }
private int getRandomHordeSpawnDelay() { private int getRandomHordeSpawnDelay() {
return this.hordeSpawnRateTicks + ThreadLocalRandom.current().nextInt(this.hordeSpawnRateVariationTicks); return this.hordeSpawnRateTicks + this.random.nextInt(this.hordeSpawnRateVariationTicks);
} }
private void startHordeSpawning(int delay) { private void startHordeSpawning(int delay) {
@@ -155,7 +156,7 @@ public class Bloodmoon extends Appliance {
private void startHordeSpawning(int delay, Player player) { private void startHordeSpawning(int delay, Player player) {
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player); @Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
if(task != null) task.cancel(); if(task != null) task.cancel();
task = Bukkit.getScheduler().runTaskLater( BukkitTask newTask = Bukkit.getScheduler().runTaskLater(
Main.instance(), Main.instance(),
() -> { () -> {
if(!this.bloodmoonIsActive()) return; if(!this.bloodmoonIsActive()) return;
@@ -164,7 +165,7 @@ public class Bloodmoon extends Appliance {
}, },
delay delay
); );
this.hordeSpawnTasks.put(player, task); this.hordeSpawnTasks.put(player, newTask);
} }
public void addPlayerToBossBar(Player player) { public void addPlayerToBossBar(Player player) {
@@ -187,9 +188,9 @@ public class Bloodmoon extends Appliance {
public void spawnRandomHorde(Player player) { public void spawnRandomHorde(Player player) {
if(!this.hordesEnabled) return; if(!this.hordesEnabled) return;
if(!player.getGameMode().equals(GameMode.SURVIVAL)) return; if(!player.getGameMode().equals(GameMode.SURVIVAL)) return;
ThreadLocalRandom random = ThreadLocalRandom.current();
EntityType hordeEntityType = this.hordeMobList.get(random.nextInt(this.hordeMobList.size())); EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size()));
int hordeSize = random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1); int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
this.spawnHorde(player, hordeSize, hordeEntityType); this.spawnHorde(player, hordeSize, hordeEntityType);
} }
@@ -207,7 +208,7 @@ public class Bloodmoon extends Appliance {
private void spawnHorde(Player player, int size, EntityType type) { private void spawnHorde(Player player, int size, EntityType type) {
for(int i = 0; i < size; i++) { for(int i = 0; i < size; i++) {
double spawnRadiant = ThreadLocalRandom.current().nextDouble(0, 2*Math.PI); double spawnRadiant = this.random.nextDouble(0, 2*Math.PI);
Location mobSpawnLocation = player.getLocation().add( Location mobSpawnLocation = player.getLocation().add(
Math.sin(spawnRadiant)*this.hordeSpawnDistance, Math.sin(spawnRadiant)*this.hordeSpawnDistance,
0, 0,
@@ -220,7 +221,7 @@ public class Bloodmoon extends Appliance {
} }
public List<ItemStack> getRandomBonusDrops() { public List<ItemStack> getRandomBonusDrops() {
int itemCount = ThreadLocalRandom.current().nextInt(this.minBonusDrops, this.maxBonusDrops + 1); int itemCount = this.random.nextInt(this.minBonusDrops, this.maxBonusDrops + 1);
List<ItemStack> result = new ArrayList<>(); List<ItemStack> result = new ArrayList<>();
for(int i = 0; i < itemCount; i++) { for(int i = 0; i < itemCount; i++) {
result.add(this.getRandomBonusDrop()); result.add(this.getRandomBonusDrop());
@@ -228,9 +229,9 @@ public class Bloodmoon extends Appliance {
return result; return result;
} }
private ItemStack getRandomBonusDrop() { private @Nullable ItemStack getRandomBonusDrop() {
int totalWeight = this.bonusDropWeightMap.values().stream().mapToInt(value -> value).sum(); int totalWeight = this.bonusDropWeightMap.values().stream().mapToInt(value -> value).sum();
int randomInt = ThreadLocalRandom.current().nextInt(0, totalWeight + 1); int randomInt = this.random.nextInt(0, totalWeight + 1);
int cumulativeWeight = 0; int cumulativeWeight = 0;
for(Map.Entry<ItemStack, Integer> entry : this.bonusDropWeightMap.entrySet()) { for(Map.Entry<ItemStack, Integer> entry : this.bonusDropWeightMap.entrySet()) {
cumulativeWeight += entry.getValue(); cumulativeWeight += entry.getValue();

View File

@@ -34,7 +34,7 @@ public class BloodmoonSetting extends BoolSetting implements CategorizedSetting
@Override @Override
protected Material icon() { protected Material icon() {
return Material.CLOCK; return Material.SKELETON_SKULL;
} }
@Override @Override

View File

@@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.list
import com.destroystokyo.paper.event.server.ServerTickStartEvent; import com.destroystokyo.paper.event.server.ServerTickStartEvent;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -18,12 +19,12 @@ public class BloodmoonTimeListener extends ApplianceListener<Bloodmoon> {
this.getAppliance().stopBloodmoon(); this.getAppliance().stopBloodmoon();
return; return;
} }
this.getAppliance().updateBossBar(); if(currentTime % Ticks.TICKS_PER_SECOND == 0) this.getAppliance().updateBossBar();
if(this.getAppliance().isStartTick(currentTime + this.getAppliance().ticksPerDay)) { if(this.getAppliance().isStartTick(currentTime + this.getAppliance().ticksPerDay)) {
this.getAppliance().sendAnnouncementMessages(); this.getAppliance().sendAnnouncementMessages();
return; return;
} }
if(this.getAppliance().isStartTick(currentTime + 1000)) { if(this.getAppliance().isStartTick(currentTime + this.getAppliance().preStartMessageTicks)) {
this.getAppliance().sendPreStartMessages(); this.getAppliance().sendPreStartMessages();
} }
} }

View File

@@ -74,11 +74,11 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
void switchLawStatus(Player player) throws OutlawChangeNotPermitted { void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
if(this.getLawStatus(player).equals(Status.FORCED)) { if(this.getLawStatus(player).equals(Status.FORCED)) {
throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden."); throw new OutlawChangeNotPermitted("Dein PVP-Status wurde als Strafe auferlegt und kann daher nicht verändert werden.");
} }
if(this.isTimeout(player)) { if(this.isTimeout(player)) {
throw new OutlawChangeNotPermitted("Du kannst deinen Vogelfreistatus nicht so schnell wechseln. Bitte warte einige Stunden bevor du umschaltest!"); throw new OutlawChangeNotPermitted("Du kannst deinen PVP-Status nicht so schnell wechseln. Bitte warte einige Stunden bevor du umschaltest!");
} }
this.setLawStatus(player, this.isOutlawed(player) ? Status.DISABLED : Status.VOLUNTARILY); this.setLawStatus(player, this.isOutlawed(player) ? Status.DISABLED : Status.VOLUNTARILY);
@@ -126,13 +126,13 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
public Component getStatusDescription(Status status) { public Component getStatusDescription(Status status) {
return switch(status) { return switch(status) {
case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN) case DISABLED -> Component.text("PVP-Modus inaktiv: ", NamedTextColor.GREEN)
.append(Component.text("Es gelten die normalen Regeln!", NamedTextColor.GOLD)); .append(Component.text("Es gelten die normalen Regeln!", NamedTextColor.GOLD));
case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED) case VOLUNTARILY, FORCED -> Component.text("PVP-Modus aktiv: ", NamedTextColor.RED)
.append(Component.text( .append(Component.text(
"Du darfst von allen anderen vogelfreien Spielern angegriffen und getötet werden!" + "Du darfst von allen anderen PVP-Spielern angegriffen und getötet werden!\n" +
"Wenn du getötet wirst, müssen andere Spieler deine Items nicht zurückerstatten!", "Wenn du getötet wirst, müssen andere Spieler deine Items *nicht* zurückerstatten!",
NamedTextColor.GOLD NamedTextColor.GOLD
)); ));
}; };
@@ -143,7 +143,7 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
if(this.isOutlawed(player)) { if(this.isOutlawed(player)) {
return Component.text("[☠]", NamedTextColor.RED) return Component.text("[☠]", NamedTextColor.RED)
.hoverEvent(HoverEvent.showText(Component.text( .hoverEvent(HoverEvent.showText(Component.text(
"Vogelfreie Spieler dürfen von anderen vogelfreien Spielern ohne Grund angegriffen werden!" "PVP-Modus Spieler dürfen von anderen vogelfreien Spielern ohne Grund angegriffen werden!"
))); )));
} }

View File

@@ -8,7 +8,7 @@ import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> { class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
public static final String commandName = "vogelfrei"; public static final String commandName = "pvp";
public OutlawedCommand() { public OutlawedCommand() {
super(commandName); super(commandName);