Compare commits
5 Commits
f0e0cfbb85
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a257b604ea | |||
| ac7e04829e | |||
| 9767896cde | |||
| e015bbb356 | |||
| 1ac19014c1 |
@@ -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() {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -50,7 +49,7 @@ public final class Main extends JavaPlugin {
|
|||||||
Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
|
Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
|
||||||
|
|
||||||
Main.logger().info("Loading appliances...");
|
Main.logger().info("Loading appliances...");
|
||||||
this.appliances = new ArrayList<>(this.findSubtypesOf(Appliance.class).stream()
|
this.appliances = this.findSubtypesOf(Appliance.class).stream()
|
||||||
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
|
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
|
||||||
.filter(appliance -> {
|
.filter(appliance -> {
|
||||||
Appliance.Flags flags = appliance.getAnnotation(Appliance.Flags.class);
|
Appliance.Flags flags = appliance.getAnnotation(Appliance.Flags.class);
|
||||||
@@ -64,17 +63,14 @@ public final class Main extends JavaPlugin {
|
|||||||
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toList());
|
.toList();
|
||||||
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
|
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
|
||||||
|
|
||||||
Main.logger().info("Initializing appliances...");
|
Main.logger().info("Initializing appliances...");
|
||||||
this.appliances.stream()
|
this.appliances.forEach(appliance -> {
|
||||||
.filter(appliance -> {
|
appliance.onEnable();
|
||||||
Appliance.Flags flags = appliance.getClass().getAnnotation(Appliance.Flags.class);
|
appliance.initialize(this);
|
||||||
if(flags == null) return true;
|
});
|
||||||
return flags.autoload();
|
|
||||||
})
|
|
||||||
.forEach(appliance -> appliance.enableSequence(this));
|
|
||||||
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
|
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
|
||||||
|
|
||||||
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
|
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
|
||||||
@@ -89,34 +85,17 @@ public final class Main extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
Main.logger().info("Disabling appliances...");
|
Main.logger().info("Disabling appliances...");
|
||||||
this.appliances.forEach(appliance -> appliance.disableSequence(this));
|
this.appliances.forEach(appliance -> {
|
||||||
|
Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
|
||||||
|
appliance.onDisable();
|
||||||
|
appliance.destruct(this);
|
||||||
|
});
|
||||||
|
|
||||||
HandlerList.unregisterAll(this);
|
HandlerList.unregisterAll(this);
|
||||||
Bukkit.getScheduler().cancelTasks(this);
|
Bukkit.getScheduler().cancelTasks(this);
|
||||||
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
|
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Appliance restartAppliance(Class<? extends Appliance> applianceClass) {
|
|
||||||
Appliance oldAppliance = this.appliances.stream()
|
|
||||||
.filter(appliance -> appliance.getClass().equals(applianceClass))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow();
|
|
||||||
oldAppliance.disableSequence(this);
|
|
||||||
this.appliances.remove(oldAppliance);
|
|
||||||
Appliance newAppliance = this.createApplianceInstance(applianceClass);
|
|
||||||
this.appliances.add(newAppliance);
|
|
||||||
newAppliance.enableSequence(this);
|
|
||||||
return newAppliance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Appliance createApplianceInstance(Class<? extends Appliance> applianceClass) {
|
|
||||||
try {
|
|
||||||
return applianceClass.getDeclaredConstructor().newInstance();
|
|
||||||
} catch(Exception e) {
|
|
||||||
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends Appliance> T getAppliance(Class<T> clazz) {
|
public <T extends Appliance> T getAppliance(Class<T> clazz) {
|
||||||
return this.appliances.stream()
|
return this.appliances.stream()
|
||||||
.filter(clazz::isInstance)
|
.filter(clazz::isInstance)
|
||||||
|
|||||||
@@ -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;
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public abstract class Appliance {
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Flags {
|
public @interface Flags {
|
||||||
boolean enabled() default true;
|
boolean enabled() default true;
|
||||||
boolean autoload() default true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String localConfigPath;
|
private String localConfigPath;
|
||||||
@@ -102,20 +101,9 @@ public abstract class Appliance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void destruct(@NotNull JavaPlugin plugin) {
|
public void destruct(@NotNull JavaPlugin plugin) {
|
||||||
if(this.listeners == null) return;
|
|
||||||
this.listeners.forEach(HandlerList::unregisterAll);
|
this.listeners.forEach(HandlerList::unregisterAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableSequence(JavaPlugin plugin) {
|
|
||||||
this.onEnable();
|
|
||||||
this.initialize(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disableSequence(JavaPlugin plugin) {
|
|
||||||
this.onDisable();
|
|
||||||
this.destruct(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
|
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
|
||||||
return Main.instance().getAppliance(clazz);
|
return Main.instance().getAppliance(clazz);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ public class Bloodmoon extends Appliance {
|
|||||||
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
|
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 = this.ticksPerDay /2;
|
public final int bloodmoonLength = ticksPerDay/2;
|
||||||
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
|
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
|
||||||
private final int bloodmoonFreeDaysAtStart = 3;
|
private final int bloodmoonFreeDaysAtStart = 3;
|
||||||
private final int bloodmoonStartTime = this.ticksPerDay /2;
|
private final int bloodmoonStartTime = ticksPerDay/2;
|
||||||
private final int bloodmoonDayInterval = 30;
|
private final int bloodmoonDayInterval = 30;
|
||||||
private final int minBonusDrops = 1;
|
private final int minBonusDrops = 1;
|
||||||
private final int maxBonusDrops = 4;
|
private final int maxBonusDrops = 4;
|
||||||
|
|||||||
@@ -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!"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
implementation project(':common')
|
|
||||||
implementation project(':core')
|
|
||||||
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
|
||||||
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
|
||||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
|
||||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Event;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.title.Title;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Particle;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Appliance.Flags(autoload = false)
|
|
||||||
public class Deathrun extends Appliance implements Event, Scorable {
|
|
||||||
private final EventScoreboardBuilder scoreboardBuilder = new EventScoreboardBuilder(this, 3, 2, 3);
|
|
||||||
private final double borderDistance = 100;
|
|
||||||
private final int borderVisibilityDistance = 8;
|
|
||||||
private long durationSeconds;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
World world = Bukkit.getWorlds().getFirst();
|
|
||||||
world.getWorldBorder().setCenter(world.getSpawnLocation());
|
|
||||||
world.getWorldBorder().setSize(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getBorderDistance() {
|
|
||||||
return this.borderDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBorderVisibilityDistance() {
|
|
||||||
return this.borderVisibilityDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void spawnParticleWall(Player p, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) {
|
|
||||||
Particle particle = Particle.WAX_ON;
|
|
||||||
|
|
||||||
for (double y = yMin; y <= yMax; y += 0.5) {
|
|
||||||
for (double z = zMin; z <= zMax; z += 0.5) {
|
|
||||||
for (double x = xMin; x <= xMax; x += 0.5) {
|
|
||||||
p.spawnParticle(particle, x, y, z, 1, 0.05, 0.05, 0.05, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
World world = Bukkit.getWorlds().getFirst();
|
|
||||||
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(long durationSeconds) {
|
|
||||||
this.durationSeconds = durationSeconds;
|
|
||||||
Title title = Title.title(Component.text("Start"), Component.text("Laufe Richtung Osten! (positiv x)"));
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
|
|
||||||
|
|
||||||
World world = Bukkit.getWorlds().getFirst();
|
|
||||||
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getScore(Player p) {
|
|
||||||
return (int) Math.ceil(p.getLocation().x());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
this.getScoreboardBuilder().stopAutomaticUpdates();
|
|
||||||
Title title = Title.title(Component.text("Ende!"), Component.empty());
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
|
||||||
player.showTitle(title);
|
|
||||||
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDurationSeconds() {
|
|
||||||
return this.durationSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getScoreboardName() {
|
|
||||||
return "Deathrun";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EventScoreboardBuilder getScoreboardBuilder() {
|
|
||||||
return this.scoreboardBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @NotNull List<Listener> listeners() {
|
|
||||||
return List.of(
|
|
||||||
new DeathrunPlayerMoveListener()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.player.PlayerMoveEvent;
|
|
||||||
|
|
||||||
public class DeathrunPlayerMoveListener extends ApplianceListener<Deathrun> {
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerMove(PlayerMoveEvent event) {
|
|
||||||
Location spawnLocation = Bukkit.getWorlds().getFirst().getSpawnLocation();
|
|
||||||
double minX = spawnLocation.x() - this.getAppliance().getBorderDistance();
|
|
||||||
double minZ = spawnLocation.z() - this.getAppliance().getBorderDistance();
|
|
||||||
double maxZ = spawnLocation.z() + this.getAppliance().getBorderDistance();
|
|
||||||
if(event.getTo().x() < minX + this.getAppliance().getBorderVisibilityDistance()) {
|
|
||||||
this.getAppliance().spawnParticleWall(event.getPlayer(), minX-0.2, minX-0.2, event.getTo().y()-0.5, event.getTo().y()+2.5, event.getTo().z()-1.5, event.getTo().z()+1.5);
|
|
||||||
if(event.getTo().x() < minX) {
|
|
||||||
event.setTo(event.getTo().clone().set(minX, event.getTo().y(), event.getTo().z()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(event.getTo().z() < minZ + this.getAppliance().getBorderVisibilityDistance()) {
|
|
||||||
this.getAppliance().spawnParticleWall(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, minZ-0.2, minZ-0.2);
|
|
||||||
if(event.getTo().z() < minZ) {
|
|
||||||
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), minZ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(event.getTo().z() > maxZ - this.getAppliance().getBorderVisibilityDistance()) {
|
|
||||||
this.getAppliance().spawnParticleWall(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, maxZ+0.2, maxZ+0.2);
|
|
||||||
if(event.getTo().z() > maxZ) {
|
|
||||||
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), maxZ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
|
|
||||||
|
|
||||||
public interface Event {
|
|
||||||
void start(long durationSeconds);
|
|
||||||
void stop();
|
|
||||||
long getDurationSeconds();
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.EventCommand;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.util.Ticks;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class EventController extends Appliance {
|
|
||||||
private List<Appliance> eventAppliances = null;
|
|
||||||
private Event selectedEvent = null;
|
|
||||||
private long timerStart;
|
|
||||||
private int timerTaskId = -1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
this.eventAppliances = Main.instance().getAppliances().stream()
|
|
||||||
.filter(appliance -> appliance instanceof Event)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Appliance> getEventAppliances() {
|
|
||||||
if(this.eventAppliances == null) throw new IllegalStateException("Event appliances were not initialized");
|
|
||||||
return this.eventAppliances;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadEvent(Appliance appliance) {
|
|
||||||
if(!(appliance instanceof Event)) throw new IllegalArgumentException("Appliance has to implement Event.");
|
|
||||||
this.unloadEvent();
|
|
||||||
Appliance newAppliance = Main.instance().restartAppliance(appliance.getClass());
|
|
||||||
if(!(newAppliance instanceof Event newEvent)) throw new IllegalArgumentException("Appliance has to implement Event.");
|
|
||||||
this.selectedEvent = newEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unloadEvent() {
|
|
||||||
if(this.selectedEvent == null) return;
|
|
||||||
this.selectedEvent.stop();
|
|
||||||
if(this.selectedEvent instanceof Appliance appliance) {
|
|
||||||
appliance.disableSequence(Main.instance());
|
|
||||||
}
|
|
||||||
this.selectedEvent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startEvent(long durationMinutes) {
|
|
||||||
if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!");
|
|
||||||
this.selectedEvent.start(durationMinutes * 60);
|
|
||||||
if(this.selectedEvent instanceof Scorable scorable && scorable.automaticUpdates()) scorable.getScoreboardBuilder().startAutomaticUpdates();
|
|
||||||
// TODO: possibility for other dimensions
|
|
||||||
this.timerStart = Bukkit.getWorlds().getFirst().getFullTime();
|
|
||||||
this.timerTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
|
|
||||||
Main.instance(),
|
|
||||||
this::updateTimer,
|
|
||||||
Ticks.TICKS_PER_SECOND,
|
|
||||||
Ticks.TICKS_PER_SECOND
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopEvent() {
|
|
||||||
if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!");
|
|
||||||
this.selectedEvent.stop();
|
|
||||||
if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId);
|
|
||||||
this.timerTaskId = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSelectedEvent() {
|
|
||||||
if(this.selectedEvent == null) return "nothing selected";
|
|
||||||
return this.selectedEvent.getClass().getSimpleName().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTimer() {
|
|
||||||
long ticksLeft = this.timerStart - (Bukkit.getWorlds().getFirst().getFullTime() - this.selectedEvent.getDurationSeconds() * Ticks.TICKS_PER_SECOND);
|
|
||||||
if(ticksLeft <= 0) {
|
|
||||||
if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId);
|
|
||||||
this.timerTaskId = -1;
|
|
||||||
this.selectedEvent.stop();
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(Component.text("Fertig!")));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(
|
|
||||||
Component.text(formatSeconds(ticksLeft / Ticks.TICKS_PER_SECOND), NamedTextColor.GOLD)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatSeconds(long seconds){
|
|
||||||
return String.format("%02d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @NotNull List<ApplianceCommand<?>> commands() {
|
|
||||||
return List.of(
|
|
||||||
new EventCommand()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
public interface Scorable {
|
|
||||||
String getScoreboardName();
|
|
||||||
default boolean automaticUpdates() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
EventScoreboardBuilder getScoreboardBuilder();
|
|
||||||
int getScore(Player p);
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController.commands;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.EventController;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class EventCommand extends ApplianceCommand<EventController> {
|
|
||||||
public EventCommand() {
|
|
||||||
super("event");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
|
||||||
if(args.length == 0) throw new Error("No argument selected!");
|
|
||||||
|
|
||||||
switch(args[0]) {
|
|
||||||
case "unload": {
|
|
||||||
this.getAppliance().unloadEvent();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "load": {
|
|
||||||
if(args.length == 1) throw new Error("Not enough arguments for select.");
|
|
||||||
this.getAppliance().loadEvent(this.findApplianceFromString(args[1]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "selected": {
|
|
||||||
sender.sendMessage("This Event is currently selected: %s".formatted(this.getAppliance().getSelectedEvent()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "start": {
|
|
||||||
if(args.length == 1) {
|
|
||||||
this.getAppliance().startEvent(60 * 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(args.length == 2) {
|
|
||||||
try {
|
|
||||||
this.getAppliance().startEvent(Long.parseLong(args[1]));
|
|
||||||
} catch(NumberFormatException e) {
|
|
||||||
throw new Error("Last argument has to be a long.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "stop": {
|
|
||||||
this.getAppliance().stopEvent();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: throw new Error("No such option: '%s' !".formatted(args[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Appliance findApplianceFromString(String name) {
|
|
||||||
System.out.println(this.getAppliance().getEventAppliances());
|
|
||||||
return this.getAppliance().getEventAppliances().stream()
|
|
||||||
.filter(appliance -> appliance.getClass().getSimpleName().equalsIgnoreCase(name))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new Error("Event appliance '%s' not found.".formatted(name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
|
||||||
if(args.length == 1) return List.of("load", "unload", "selected", "start", "stop");
|
|
||||||
if(args.length == 2 && args[0].equals("load")) return this.getAppliance().getEventAppliances().stream()
|
|
||||||
.map(appliance -> appliance.getClass().getSimpleName().toLowerCase())
|
|
||||||
.toList();
|
|
||||||
if(args.length == 2 && args[0].equals("start")) return List.of("<minutes>");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public record EventScoreEntry(UUID playerUuid, String name, int score) {
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
|
||||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable;
|
|
||||||
import io.papermc.paper.scoreboard.numbers.NumberFormat;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
|
||||||
import net.kyori.adventure.util.Ticks;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.scoreboard.DisplaySlot;
|
|
||||||
import org.bukkit.scoreboard.Objective;
|
|
||||||
import org.bukkit.scoreboard.Scoreboard;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
public class EventScoreboardBuilder {
|
|
||||||
private final int topPlaces;
|
|
||||||
private final int aroundPlaces;
|
|
||||||
private final int bottomPlaces;
|
|
||||||
private final Scorable scorable;
|
|
||||||
private final Comparator<EventScoreEntry> scoreComparator;
|
|
||||||
private final List<EventScoreEntry> playerScores = new ArrayList<>();
|
|
||||||
private int scoreboardUpdateTaskId = -1;
|
|
||||||
|
|
||||||
public EventScoreboardBuilder(Scorable scorable, int topPlaces, int aroundPlaces, int bottomPlaces) {
|
|
||||||
this(scorable, topPlaces, aroundPlaces, bottomPlaces, Comparator.comparingInt(EventScoreEntry::score).reversed());
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventScoreboardBuilder(Scorable scorable, int topPlaces, int aroundPlaces, int bottomPlaces, Comparator<EventScoreEntry> scoreComparator) {
|
|
||||||
this.scorable = scorable;
|
|
||||||
this.topPlaces = topPlaces;
|
|
||||||
this.aroundPlaces = aroundPlaces;
|
|
||||||
this.bottomPlaces = bottomPlaces;
|
|
||||||
this.scoreComparator = scoreComparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Scoreboard buildFor(Player p) {
|
|
||||||
List<EventScoreEntry> scoreList = new ArrayList<>(this.playerScores);
|
|
||||||
|
|
||||||
Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
|
||||||
Objective objective = scoreboard.registerNewObjective(
|
|
||||||
"event", "dummy",
|
|
||||||
Component.text(
|
|
||||||
"Scoreboard %s".formatted(this.scorable.getScoreboardName()),
|
|
||||||
NamedTextColor.GOLD, TextDecoration.BOLD
|
|
||||||
)
|
|
||||||
);
|
|
||||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
|
||||||
objective.numberFormat(NumberFormat.blank());
|
|
||||||
|
|
||||||
UUID uuid = p.getUniqueId();
|
|
||||||
scoreList.removeIf(e -> e.playerUuid().equals(uuid));
|
|
||||||
scoreList.add(new EventScoreEntry(uuid, p.getName(), this.scorable.getScore(p)));
|
|
||||||
|
|
||||||
scoreList.sort(this.scoreComparator);
|
|
||||||
|
|
||||||
int size = scoreList.size();
|
|
||||||
int playerIndex = IntStream.range(0, size)
|
|
||||||
.filter(i -> scoreList.get(i).playerUuid().equals(uuid))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(0);
|
|
||||||
|
|
||||||
IntStream top = IntStream.range(0, Math.min(this.topPlaces, size));
|
|
||||||
|
|
||||||
int aroundStart = Math.max(0, playerIndex - this.aroundPlaces);
|
|
||||||
int aroundEnd = Math.min(size, playerIndex + this.aroundPlaces + 1);
|
|
||||||
IntStream around = IntStream.range(aroundStart, aroundEnd);
|
|
||||||
|
|
||||||
IntStream bottom = IntStream.range(Math.max(0, size - this.bottomPlaces), size);
|
|
||||||
|
|
||||||
int threshold = this.topPlaces + this.bottomPlaces + this.aroundPlaces * 2 + 1;
|
|
||||||
|
|
||||||
IntStream indices;
|
|
||||||
if (size <= threshold) {
|
|
||||||
indices = IntStream.range(0, size);
|
|
||||||
} else if (playerIndex <= this.topPlaces + this.aroundPlaces) {
|
|
||||||
indices = IntStream.concat(
|
|
||||||
IntStream.range(0, Math.min(size, playerIndex + this.aroundPlaces + 1)),
|
|
||||||
bottom
|
|
||||||
);
|
|
||||||
} else if (playerIndex >= size - this.bottomPlaces - this.aroundPlaces - 1) {
|
|
||||||
indices = IntStream.concat(
|
|
||||||
top,
|
|
||||||
IntStream.range(Math.max(0, playerIndex - this.aroundPlaces), size)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
indices = IntStream.concat(IntStream.concat(top, around), bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] display = indices.distinct().sorted().toArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < display.length; i++) {
|
|
||||||
int idx = display[i];
|
|
||||||
EventScoreEntry entry = scoreList.get(idx);
|
|
||||||
|
|
||||||
String line = this.formattedLine(idx, entry.name(), entry.score());
|
|
||||||
|
|
||||||
objective.getScore(line).setScore(display.length - i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return scoreboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void startAutomaticUpdates() {
|
|
||||||
this.scoreboardUpdateTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
|
|
||||||
Main.instance(),
|
|
||||||
this::updateScoreboards,
|
|
||||||
Ticks.TICKS_PER_SECOND,
|
|
||||||
Ticks.TICKS_PER_SECOND
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopAutomaticUpdates() {
|
|
||||||
if(this.scoreboardUpdateTaskId != -1) Bukkit.getScheduler().cancelTask(this.scoreboardUpdateTaskId);
|
|
||||||
this.scoreboardUpdateTaskId = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateScore(Player p) {
|
|
||||||
this.playerScores.removeIf(entry -> entry.playerUuid().equals(p.getUniqueId()));
|
|
||||||
this.playerScores.add(new EventScoreEntry(p.getUniqueId(), p.getName(), this.scorable.getScore(p)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateScoreboards() {
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
|
||||||
this.updateScore(player);
|
|
||||||
Scoreboard scoreboard = this.buildFor(player);
|
|
||||||
player.setScoreboard(scoreboard);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formattedLine(int place, String name, int score) {
|
|
||||||
name = this.trimName(name);
|
|
||||||
return "%s. %s: %s".formatted(place+1, name, score);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String trimName(String name) {
|
|
||||||
if (name == null) return "Unknown";
|
|
||||||
if (name.length() > 12) {
|
|
||||||
return name.substring(0, 12);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,5 +4,4 @@ include 'core'
|
|||||||
include 'craftattack'
|
include 'craftattack'
|
||||||
include 'common'
|
include 'common'
|
||||||
include 'varo'
|
include 'varo'
|
||||||
include 'event'
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user