Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc7ce154bd | ||
|
|
d18ddc7b07 | ||
|
|
b06ba40d16 | ||
|
|
3eca590c6f | ||
|
|
f6ac150a8f | ||
|
|
92a655e107 | ||
|
|
77a740edb7 | ||
|
|
4a87bb3fad | ||
|
|
ca0e364d85 |
No files matched your search
@@ -1,3 +1,11 @@
|
||||
# Chatcoloring
|
||||
Der Spieler benötigt das Recht "chatcolor" damit dessen Namen blau im Chat angezeigt wird.
|
||||
|
||||
# Wandering Trader
|
||||
|
||||
Ich würde vorschlagen, das Plugin WanderingTrades zu verwenden, da es uns ermöglicht, eigene Handelsangebote festzulegen und gleichzeitig die Köpfe von Spielern unseres Servers zu verkaufen.
|
||||
|
||||
Da WanderingTrades auch Mini-Blöcke unterstützt, sollten wir überlegen, ob wir das Plugin MiniBlocks nicht vollständig weglassen. Was das Plugin nicht kann ist Player Heads zu droppen, wenn ein Spieler einen anderen tötet, dass müssten wir dann noch selber schreiben.
|
||||
|
||||
Zu tun wäre dann noch:
|
||||
Die Config des Plugins um Sniffing Potion und Heart of the Seas hinzuzufügen und Preis für Player Heads hinzufügen.
|
||||
@@ -237,11 +237,22 @@ final class Fight {
|
||||
* The player took lethal damage: out of the fight and back to the tribune.
|
||||
*/
|
||||
void defeated(Player player) {
|
||||
this.knockOut(player, Component.empty(), "ist ausgeschieden");
|
||||
}
|
||||
|
||||
/**
|
||||
* The player got out of the box, for example by glitching through a wall with an ender pearl.
|
||||
*/
|
||||
void leftBox(Player player) {
|
||||
this.knockOut(player, Component.text("Du hast die Arena verlassen", NamedTextColor.GRAY), "hat die Arena verlassen");
|
||||
}
|
||||
|
||||
private void knockOut(Player player, Component subtitle, String what) {
|
||||
if(this.over || !this.eliminate(player.getUniqueId())) return;
|
||||
player.showTitle(Title.title(Component.text("Ausgeschieden", NamedTextColor.RED), Component.empty()));
|
||||
player.showTitle(Title.title(Component.text("Ausgeschieden", NamedTextColor.RED), subtitle));
|
||||
Style.play(player, Sound.BLOCK_BEACON_DEACTIVATE, 1f);
|
||||
this.arena.toTribune(player);
|
||||
this.announceElimination(player.getUniqueId(), "ist ausgeschieden");
|
||||
this.announceElimination(player.getUniqueId(), what);
|
||||
this.checkEnd();
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -12,6 +12,7 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -94,4 +95,17 @@ class FightListener extends ApplianceListener<Arena> {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whoever ends up outside the box anyway (ender pearl through a wall or corner) loses.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onMove(PlayerMoveEvent event) {
|
||||
if(!event.hasChangedBlock()) return;
|
||||
Player player = event.getPlayer();
|
||||
if(this.getAppliance().state(player) != Arena.PlayerState.FIGHT || !this.isFighting(player)) return;
|
||||
Fight fight = this.getAppliance().fightOf(player);
|
||||
if(event.getTo().getWorld() == this.getAppliance().worlds().play() && fight.region().contains(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())) return;
|
||||
fight.leftBox(player);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.TextDisplay;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
@@ -31,6 +32,10 @@ import java.util.UUID;
|
||||
final class Lobby implements PadLobby.Listener {
|
||||
/** How far around a taken pad other players get pushed away. */
|
||||
private static final double PUSH_MARGIN = 0.4;
|
||||
/** How long a full lobby waits for the challenger to close the options. */
|
||||
private static final int CHOOSING_SECONDS = 20;
|
||||
/** Whoever keeps the options open this long without clicking anything gets sent off the pad. */
|
||||
private static final int MENU_IDLE_TICKS = 60 * 20;
|
||||
|
||||
private final Arena arena;
|
||||
private final Region[] regions;
|
||||
@@ -38,11 +43,13 @@ final class Lobby implements PadLobby.Listener {
|
||||
private final TextDisplay[] displays = new TextDisplay[PadLobby.PADS];
|
||||
private final Map<UUID, Long> lastPushSound = new HashMap<>();
|
||||
private long ticks;
|
||||
private @Nullable UUID menuViewer;
|
||||
private int menuIdleTicks;
|
||||
|
||||
Lobby(Arena arena, Region[] regions) {
|
||||
this.arena = arena;
|
||||
this.regions = regions;
|
||||
this.pads = new PadLobby(this, arena.settings().countdownSeconds());
|
||||
this.pads = new PadLobby(this, arena.settings().countdownSeconds(), CHOOSING_SECONDS);
|
||||
for(int i = 0; i < PadLobby.PADS; i++) {
|
||||
Region region = regions[i];
|
||||
this.displays[i] = Displays.spawn(new Location(arena.worlds().play(), region.centerX(), region.maxY() + 2.6, region.centerZ()));
|
||||
@@ -57,12 +64,23 @@ final class Lobby implements PadLobby.Listener {
|
||||
else if(state == Arena.PlayerState.TRIBUNE) this.checkEnter(player);
|
||||
}
|
||||
|
||||
this.checkMenuIdle();
|
||||
this.pads.tick(this.arena.hasFreeSlot());
|
||||
|
||||
if(this.ticks % 3 == 0) this.drawParticles();
|
||||
if(this.ticks % 5 == 0) this.updateDisplays();
|
||||
}
|
||||
|
||||
private void checkMenuIdle() {
|
||||
if(this.menuViewer == null || ++this.menuIdleTicks < MENU_IDLE_TICKS) return;
|
||||
Player player = Bukkit.getPlayer(this.menuViewer);
|
||||
this.menuViewer = null;
|
||||
if(player == null || !ModeMenu.isOpen(player)) return;
|
||||
this.arena.toTribune(player);
|
||||
player.sendMessage(Style.message("Du hast eine Minute lang nichts ausgewählt und wurdest vom Feld geschickt."));
|
||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_BASS, 0.6f);
|
||||
}
|
||||
|
||||
private void checkLeave(Player player) {
|
||||
int pad = this.pads.padOf(player.getUniqueId());
|
||||
if(pad != -1 && this.isOn(player, pad, 0)) return;
|
||||
@@ -81,7 +99,9 @@ final class Lobby implements PadLobby.Listener {
|
||||
this.arena.setState(player, Arena.PlayerState.PAD);
|
||||
this.arena.teams().join(player, Teams.pad(pad));
|
||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1.4f);
|
||||
if(!player.getUniqueId().equals(this.pads.challenger())) {
|
||||
if(this.pads.isWaitingForChallenger()) {
|
||||
player.sendActionBar(this.choosingText());
|
||||
} else if(!player.getUniqueId().equals(this.pads.challenger())) {
|
||||
player.sendActionBar(Component.text(Style.PAD_NAMES[pad] + " " + this.pads.players(pad).size() + "/" + this.pads.size(pad)
|
||||
+ ", Modus: " + this.modeName(), Style.PAD_TEXT_COLORS[pad]));
|
||||
}
|
||||
@@ -104,6 +124,7 @@ final class Lobby implements PadLobby.Listener {
|
||||
int ownSize = menu.ownSize;
|
||||
int opponentSize = menu.opponentSize;
|
||||
FightMode mode = menu.mode;
|
||||
this.menuIdleTicks = 0;
|
||||
switch(click) {
|
||||
case ModeMenu.OwnSize(int size) -> ownSize = size;
|
||||
case ModeMenu.OpponentSize(int size) -> opponentSize = size;
|
||||
@@ -148,9 +169,26 @@ final class Lobby implements PadLobby.Listener {
|
||||
int pad = this.pads.challengerPad();
|
||||
InteractSounds.of(player).open();
|
||||
ModeMenu.open(player, pad, this.pads.size(pad), this.pads.size(1 - pad), this.pads.mode());
|
||||
this.menuViewer = player.getUniqueId();
|
||||
this.menuIdleTicks = 0;
|
||||
this.pads.setChoosing(player.getUniqueId(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The challenger closed the options: a full lobby starts counting down now.
|
||||
*/
|
||||
void menuClosed(Player player) {
|
||||
if(player.getUniqueId().equals(this.menuViewer)) this.menuViewer = null;
|
||||
this.pads.setChoosing(player.getUniqueId(), false);
|
||||
}
|
||||
|
||||
private Component choosingText() {
|
||||
Player challenger = this.pads.challenger() == null ? null : Bukkit.getPlayer(this.pads.challenger());
|
||||
String name = challenger == null ? "Der Herausforderer" : challenger.getName();
|
||||
return Component.text(name + " wählt noch Teams und Modus", NamedTextColor.YELLOW);
|
||||
}
|
||||
|
||||
private boolean isTeamFight() {
|
||||
return this.pads.size(0) > 1 || this.pads.size(1) > 1;
|
||||
}
|
||||
@@ -201,6 +239,22 @@ final class Lobby implements PadLobby.Listener {
|
||||
this.openMenu(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitingForChallenger() {
|
||||
UUID challenger = this.pads.challenger();
|
||||
Player player = challenger == null ? null : Bukkit.getPlayer(challenger);
|
||||
if(player != null && player.getOpenInventory().getTopInventory().getHolder(false) instanceof ModeMenu menu) {
|
||||
menu.everyoneThere = true;
|
||||
menu.draw();
|
||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1.8f);
|
||||
}
|
||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||
this.playersOn(pad).stream()
|
||||
.filter(occupant -> !occupant.getUniqueId().equals(challenger))
|
||||
.forEach(occupant -> occupant.sendActionBar(this.choosingText()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void countdownStarted() {
|
||||
UUID challenger = this.pads.challenger();
|
||||
@@ -283,6 +337,9 @@ final class Lobby implements PadLobby.Listener {
|
||||
if(waiting) {
|
||||
body = this.namesOn(pad).appendNewline()
|
||||
.append(Component.text("Alle Arenen belegt, gleich geht es los", NamedTextColor.GRAY));
|
||||
} else if(this.pads.isWaitingForChallenger() && challenger != null) {
|
||||
body = this.namesOn(pad).appendNewline()
|
||||
.append(Component.text(challenger.getName() + " wählt noch", NamedTextColor.YELLOW));
|
||||
} else if(this.pads.isCountingDown()) {
|
||||
body = this.namesOn(pad).appendNewline()
|
||||
.append(Component.text("Kampf beginnt in " + this.pads.secondsLeft(), NamedTextColor.GOLD));
|
||||
|
||||
@@ -48,6 +48,8 @@ final class ModeMenu implements InventoryHolder {
|
||||
int ownSize;
|
||||
int opponentSize;
|
||||
FightMode mode;
|
||||
/** Both pads are full, only the challenger closing the menu is missing. */
|
||||
boolean everyoneThere;
|
||||
|
||||
private ModeMenu(Player challenger, int ownPad, int ownSize, int opponentSize, FightMode mode) {
|
||||
this.challenger = challenger;
|
||||
@@ -95,10 +97,12 @@ final class ModeMenu implements InventoryHolder {
|
||||
this.inventory.setItem(MODE_SLOTS[i], this.modeItem(mode, teams, mode == this.mode, mode.isAllowed(this.ownSize, this.opponentSize)));
|
||||
}
|
||||
|
||||
this.inventory.setItem(DONE_SLOT, ItemBuilder.of(Material.LIME_CONCRETE)
|
||||
.displayName(Component.text("Fertig", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false))
|
||||
.lore(this.ownSize + " gegen " + this.opponentSize + ", " + this.mode.displayName(teams) + ". Jetzt fehlen nur noch die anderen auf den Feldern.")
|
||||
.build());
|
||||
ItemBuilder done = ItemBuilder.of(Material.LIME_CONCRETE)
|
||||
.displayName(Component.text(this.everyoneThere ? "Fertig, Kampf starten" : "Fertig", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false))
|
||||
.lore(this.ownSize + " gegen " + this.opponentSize + ", " + this.mode.displayName(teams) + ". "
|
||||
+ (this.everyoneThere ? "Alle sind da, der Countdown startet, sobald du fertig bist." : "Jetzt fehlen nur noch die anderen auf den Feldern."));
|
||||
if(this.everyoneThere) done.glint();
|
||||
this.inventory.setItem(DONE_SLOT, done.build());
|
||||
}
|
||||
|
||||
private ItemBuilder label(Material material, String name, int pad, String text) {
|
||||
|
||||
+9
@@ -7,6 +7,7 @@ import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
|
||||
class ModeMenuListener extends ApplianceListener<Arena> {
|
||||
@EventHandler
|
||||
@@ -21,6 +22,14 @@ class ModeMenuListener extends ApplianceListener<Arena> {
|
||||
if(click != null && lobby != null) lobby.handleMenuClick(player, menu, click);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onClose(InventoryCloseEvent event) {
|
||||
if(!(event.getInventory().getHolder(false) instanceof ModeMenu)) return;
|
||||
if(!(event.getPlayer() instanceof Player player)) return;
|
||||
Lobby lobby = this.getAppliance().lobby();
|
||||
if(lobby != null) lobby.menuClosed(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onRightClick(PlayerInteractEvent event) {
|
||||
if(event.getHand() != EquipmentSlot.HAND) return;
|
||||
|
||||
+55
-13
@@ -9,9 +9,9 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* State of the two start pads. The first player on a pad becomes the challenger and picks team sizes and mode
|
||||
* while standing there alone. Everyone else joins a team by stepping on a pad. Once both pads hold exactly
|
||||
* the chosen number of players long enough, the fight starts.
|
||||
* State of the two start pads. The first player on a pad becomes the challenger and picks team sizes and mode.
|
||||
* Everyone else joins a team by stepping on a pad. Once both pads hold exactly the chosen number of players
|
||||
* and the challenger is done choosing (or took too long), the countdown runs and then the fight starts.
|
||||
*/
|
||||
public class PadLobby {
|
||||
public static final int PADS = 2;
|
||||
@@ -24,6 +24,9 @@ public class PadLobby {
|
||||
/** The player should now hold the inventory of {@code kitOwner} (the own one to revert). */
|
||||
void kitAssigned(UUID player, UUID kitOwner);
|
||||
|
||||
/** Both pads are exactly full, but the challenger still has the options open. */
|
||||
void waitingForChallenger();
|
||||
|
||||
/** Both pads are exactly full, the options are fixed now. */
|
||||
void countdownStarted();
|
||||
|
||||
@@ -40,6 +43,7 @@ public class PadLobby {
|
||||
|
||||
private final Listener listener;
|
||||
private final int countdownTicks;
|
||||
private final int choosingTicks;
|
||||
private final List<List<UUID>> pads = List.of(new ArrayList<>(), new ArrayList<>());
|
||||
/** Everyone on a pad, in the order they stepped on. The first one is the challenger. */
|
||||
private final List<UUID> order = new ArrayList<>();
|
||||
@@ -47,10 +51,17 @@ public class PadLobby {
|
||||
private final int[] sizes = {1, 1};
|
||||
private FightMode mode = FightMode.OWN;
|
||||
private int ticksLeft = -1;
|
||||
private boolean choosing;
|
||||
/** How much longer everyone waits for the challenger to finish choosing; -1 while nobody waits. */
|
||||
private int choosingTicksLeft = -1;
|
||||
|
||||
public PadLobby(Listener listener, int countdownSeconds) {
|
||||
/**
|
||||
* @param choosingSeconds how long a full lobby waits for the challenger to close the options
|
||||
*/
|
||||
public PadLobby(Listener listener, int countdownSeconds, int choosingSeconds) {
|
||||
this.listener = listener;
|
||||
this.countdownTicks = countdownSeconds * 20;
|
||||
this.choosingTicks = choosingSeconds * 20;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,10 +84,7 @@ public class PadLobby {
|
||||
}
|
||||
|
||||
this.refreshKits();
|
||||
if(this.isFull()) {
|
||||
this.ticksLeft = this.countdownTicks;
|
||||
this.listener.countdownStarted();
|
||||
}
|
||||
this.startCountdownIfReady();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -92,9 +100,11 @@ public class PadLobby {
|
||||
this.listener.kitAssigned(player, player);
|
||||
|
||||
if(wasFull) {
|
||||
if(this.isCountingDown()) this.listener.countdownCancelled();
|
||||
this.ticksLeft = -1;
|
||||
this.listener.countdownCancelled();
|
||||
this.choosingTicksLeft = -1;
|
||||
}
|
||||
if(wasChallenger) this.choosing = false;
|
||||
|
||||
this.refreshKits();
|
||||
// the longest waiting player takes over; team sizes and mode stay as chosen
|
||||
@@ -130,13 +140,40 @@ public class PadLobby {
|
||||
this.sizes[1 - own] = opponentSize;
|
||||
this.mode = mode;
|
||||
this.refreshKits();
|
||||
if(this.isFull()) {
|
||||
this.ticksLeft = this.countdownTicks;
|
||||
this.listener.countdownStarted();
|
||||
}
|
||||
this.startCountdownIfReady();
|
||||
return ConfigureResult.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* The challenger opened or closed the options. While they are open, a full lobby waits instead of counting down.
|
||||
*/
|
||||
public void setChoosing(UUID player, boolean choosing) {
|
||||
if(!this.canConfigure(player) || this.choosing == choosing) return;
|
||||
this.choosing = choosing;
|
||||
this.startCountdownIfReady();
|
||||
}
|
||||
|
||||
public boolean isWaitingForChallenger() {
|
||||
return this.choosingTicksLeft >= 0;
|
||||
}
|
||||
|
||||
private void startCountdownIfReady() {
|
||||
if(this.isCountingDown()) return;
|
||||
if(!this.isFull()) {
|
||||
this.choosingTicksLeft = -1;
|
||||
return;
|
||||
}
|
||||
if(this.choosing) {
|
||||
if(this.choosingTicksLeft >= 0) return;
|
||||
this.choosingTicksLeft = this.choosingTicks;
|
||||
this.listener.waitingForChallenger();
|
||||
return;
|
||||
}
|
||||
this.choosingTicksLeft = -1;
|
||||
this.ticksLeft = this.countdownTicks;
|
||||
this.listener.countdownStarted();
|
||||
}
|
||||
|
||||
public boolean canConfigure(UUID player) {
|
||||
return player.equals(this.challenger()) && !this.isCountingDown();
|
||||
}
|
||||
@@ -145,6 +182,10 @@ public class PadLobby {
|
||||
* Called every server tick. The countdown only runs while an arena is free.
|
||||
*/
|
||||
public void tick(boolean arenaAvailable) {
|
||||
if(this.choosingTicksLeft >= 0 && this.choosingTicksLeft-- == 0) {
|
||||
this.choosing = false;
|
||||
this.startCountdownIfReady();
|
||||
}
|
||||
if(this.ticksLeft < 0) return;
|
||||
if(!arenaAvailable) {
|
||||
this.ticksLeft = this.countdownTicks;
|
||||
@@ -165,6 +206,7 @@ public class PadLobby {
|
||||
this.sizes[1] = 1;
|
||||
this.mode = FightMode.OWN;
|
||||
this.ticksLeft = -1;
|
||||
this.choosing = false;
|
||||
this.listener.start(teams, challenger, mode, kits);
|
||||
}
|
||||
|
||||
|
||||
+61
-1
@@ -28,6 +28,10 @@ class PadLobbyTest {
|
||||
PadLobbyTest.this.events.add("challenger " + PadLobbyTest.this.name(challenger) + (first ? " first" : ""));
|
||||
}
|
||||
|
||||
public void waitingForChallenger() {
|
||||
PadLobbyTest.this.events.add("waiting");
|
||||
}
|
||||
|
||||
public void countdownStarted() {
|
||||
PadLobbyTest.this.events.add("countdown started");
|
||||
}
|
||||
@@ -49,7 +53,7 @@ class PadLobbyTest {
|
||||
PadLobbyTest.this.events.add("start " + PadLobbyTest.this.names(teams.get(0)) + " vs " + PadLobbyTest.this.names(teams.get(1))
|
||||
+ " by " + PadLobbyTest.this.name(challenger) + " " + mode);
|
||||
}
|
||||
}, 2);
|
||||
}, 2, 3);
|
||||
}
|
||||
|
||||
private String name(UUID uuid) {
|
||||
@@ -239,4 +243,60 @@ class PadLobbyTest {
|
||||
this.lobby.leave(this.b);
|
||||
assertEquals(List.of("challenger a first", "kit a<-a"), this.events);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullLobbyWaitsWhileChallengerChooses() {
|
||||
this.lobby.enter(0, this.a);
|
||||
this.lobby.setChoosing(this.a, true);
|
||||
this.lobby.enter(1, this.b);
|
||||
assertEquals("waiting", this.events.getLast());
|
||||
assertFalse(this.lobby.isCountingDown());
|
||||
assertTrue(this.lobby.isWaitingForChallenger());
|
||||
assertEquals(PadLobby.ConfigureResult.OK, this.lobby.configure(this.a, 1, 1, FightMode.CHALLENGER), "the menu stays usable");
|
||||
|
||||
this.lobby.setChoosing(this.a, false);
|
||||
assertEquals("countdown started", this.events.getLast());
|
||||
assertFalse(this.lobby.isWaitingForChallenger());
|
||||
}
|
||||
|
||||
@Test
|
||||
void choosingTimesOut() {
|
||||
this.lobby.enter(0, this.a);
|
||||
this.lobby.setChoosing(this.a, true);
|
||||
this.lobby.enter(1, this.b);
|
||||
this.run(60);
|
||||
assertFalse(this.lobby.isCountingDown());
|
||||
this.run(1);
|
||||
assertTrue(this.lobby.isCountingDown());
|
||||
this.lobby.setChoosing(this.a, false);
|
||||
assertEquals(1, this.events.stream().filter("countdown started"::equals).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyChallengerChoosingBlocks() {
|
||||
this.lobby.enter(0, this.a);
|
||||
this.lobby.setChoosing(this.b, true);
|
||||
this.lobby.enter(1, this.b);
|
||||
assertTrue(this.lobby.isCountingDown());
|
||||
}
|
||||
|
||||
@Test
|
||||
void opponentLeavingWhileWaitingIsQuiet() {
|
||||
this.lobby.enter(0, this.a);
|
||||
this.lobby.setChoosing(this.a, true);
|
||||
this.lobby.enter(1, this.b);
|
||||
this.lobby.leave(this.b);
|
||||
assertFalse(this.events.contains("cancelled"));
|
||||
assertFalse(this.lobby.isWaitingForChallenger());
|
||||
}
|
||||
|
||||
@Test
|
||||
void newChallengerIsNotChoosing() {
|
||||
this.lobby.enter(0, this.a);
|
||||
this.lobby.setChoosing(this.a, true);
|
||||
this.lobby.enter(1, this.b);
|
||||
this.lobby.leave(this.a);
|
||||
this.lobby.enter(0, this.c);
|
||||
assertTrue(this.lobby.isCountingDown());
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ChatMention extends Appliance {
|
||||
private static List<String> playerNames;
|
||||
|
||||
public String formatPlayer(String name) {
|
||||
return "@" + name;
|
||||
}
|
||||
|
||||
public List<String> getPlayerNames() {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
public void refreshPlayers() {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(
|
||||
Main.instance(),
|
||||
() -> playerNames = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.map(OfflinePlayer::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
public void notifyPlayers(List<String> playerNames) {
|
||||
playerNames.stream()
|
||||
.distinct()
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(player -> Settings.instance()
|
||||
.getSetting(player, Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class)
|
||||
.notifyOnMention()
|
||||
)
|
||||
.forEach(player -> player.playSound(
|
||||
Sound.sound(
|
||||
org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP,
|
||||
Sound.Source.PLAYER,
|
||||
1.0f,
|
||||
1.0f
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Settings.instance().declareSetting(ChatMentionSetting.class);
|
||||
this.refreshPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(new ChatMentionListener());
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages.ChatMessages;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
|
||||
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class ChatMentionListener extends ApplianceListener<ChatMention> {
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@EventHandler
|
||||
public void coloringEvent(AsyncChatDecorateEvent event) {
|
||||
String message = PlainTextComponentSerializer.plainText().serialize(event.result());
|
||||
List<String> words = List.of(message.split(" "));
|
||||
List<String> mentioned = new ArrayList<>();
|
||||
|
||||
ChatMentionSetting.ChatMentionConfig config = Settings.instance()
|
||||
.getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
|
||||
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
|
||||
|
||||
Component result = words.stream()
|
||||
.map(word -> {
|
||||
String wordWithoutAnnotation = word.replace("@", "");
|
||||
boolean isPlayer = this.getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
|
||||
if(isPlayer && config.applyMentions()) {
|
||||
mentioned.add(wordWithoutAnnotation);
|
||||
Component mention = Component.text(
|
||||
this.getAppliance().formatPlayer(wordWithoutAnnotation),
|
||||
NamedTextColor.GOLD
|
||||
);
|
||||
return chatMessages.addReportActions(mention, wordWithoutAnnotation);
|
||||
} else {
|
||||
return Component.text(word);
|
||||
}
|
||||
})
|
||||
.reduce(ComponentUtil::appendWithSpace)
|
||||
.orElseThrow();
|
||||
|
||||
this.getAppliance().notifyPlayers(mentioned);
|
||||
event.result(result);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
this.getAppliance().refreshPlayers();
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChatMessages extends Appliance {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Settings.instance().declareSetting(ShowJoinAndLeaveMessagesSetting.class);
|
||||
}
|
||||
|
||||
public Component getReportablePlayerName(Player player) {
|
||||
return this.addReportActions(player.displayName(), player.getName());
|
||||
}
|
||||
|
||||
public Component addReportActions(Component message, String username) {
|
||||
return message
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
|
||||
.clickEvent(ClickEvent.suggestCommand(String.format("/report %s ", username)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected List<Listener> listeners() {
|
||||
return List.of(new ChatMessagesListener());
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import eu.mhsl.craftattack.spawn.core.util.IteratorUtil;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
class ChatMessagesListener extends ApplianceListener<ChatMessages> {
|
||||
@EventHandler
|
||||
public void onPlayerChatEvent(AsyncChatEvent event) {
|
||||
event.renderer(
|
||||
(source, sourceDisplayName, message, viewer) ->
|
||||
Component.text("")
|
||||
.append(this.getAppliance().getReportablePlayerName(source))
|
||||
.append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB())))
|
||||
.append(message).color(TextColor.color(Color.SILVER.asRGB()))
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
boolean wasHidden = event.joinMessage() == null;
|
||||
event.joinMessage(null);
|
||||
if(wasHidden) return;
|
||||
IteratorUtil.onlinePlayers(player -> {
|
||||
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
|
||||
player.sendMessage(
|
||||
Component
|
||||
.text(">>> ").color(NamedTextColor.GREEN)
|
||||
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerLeave(PlayerQuitEvent event) {
|
||||
boolean wasHidden = event.quitMessage() == null;
|
||||
event.quitMessage(null);
|
||||
if(wasHidden) return;
|
||||
IteratorUtil.onlinePlayers(player -> {
|
||||
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
|
||||
player.sendMessage(
|
||||
Component
|
||||
.text("<<< ").color(NamedTextColor.RED)
|
||||
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDeath(PlayerDeathEvent event) {
|
||||
event.deathMessage(
|
||||
Component
|
||||
.text("☠ ")
|
||||
.append(
|
||||
Optional
|
||||
.ofNullable(event.deathMessage())
|
||||
.orElse(Component.text(event.getPlayer().getName()))
|
||||
)
|
||||
.color(TextColor.color(Color.SILVER.asRGB()))
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
-5
@@ -2,10 +2,12 @@ package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.networkChat.NetworkChat;
|
||||
import eu.mhsl.craftattack.spawn.core.util.server.Floodgate;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentBuilder;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@@ -49,15 +51,27 @@ public class DisplayName extends Appliance {
|
||||
.map(appliance -> (Prefixed) appliance)
|
||||
.toList();
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
|
||||
prefixes.stream()
|
||||
List<Component> prefixComponents = prefixes.stream()
|
||||
.map(prefixed -> prefixed.getNamePrefix(player))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(prefix -> playerName
|
||||
.append(prefix)
|
||||
.append(ComponentUtil.clearedSpace())
|
||||
.toList();
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
|
||||
prefixComponents.forEach(prefix -> playerName
|
||||
.append(prefix)
|
||||
.append(ComponentUtil.clearedSpace())
|
||||
);
|
||||
|
||||
// Den Chat-Namen baut Velocity selbst (Farbe, Bedrock), es braucht von hier nur die Prefixe
|
||||
Main.instance().getAppliances().stream()
|
||||
.filter(NetworkChat.class::isInstance)
|
||||
.map(NetworkChat.class::cast)
|
||||
.findFirst()
|
||||
.ifPresent(networkChat -> networkChat.setPrefix(
|
||||
player,
|
||||
prefixComponents.isEmpty() ? null : Component.join(JoinConfiguration.separator(ComponentUtil.clearedSpace()), prefixComponents)
|
||||
));
|
||||
|
||||
if(Floodgate.isBedrock(player)) {
|
||||
playerName
|
||||
.append(
|
||||
|
||||
+3
-2
@@ -19,7 +19,8 @@ public class InfoBarSetting extends MultiBoolSetting<InfoBarSetting.InfoBarConfi
|
||||
public record InfoBarConfiguration(
|
||||
@DisplayName("Millisekunden pro Tick") boolean mspt,
|
||||
@DisplayName("Spieler online") boolean playerCounter,
|
||||
@DisplayName("Ticks pro Sekunde") boolean tps
|
||||
@DisplayName("Ticks pro Sekunde") boolean tps,
|
||||
@DisplayName("TPS, MSPT & Spieler") boolean tpsMsptPlayerCounter
|
||||
) {}
|
||||
|
||||
@Override
|
||||
@@ -39,7 +40,7 @@ public class InfoBarSetting extends MultiBoolSetting<InfoBarSetting.InfoBarConfi
|
||||
|
||||
@Override
|
||||
protected InfoBarConfiguration defaultValue() {
|
||||
return new InfoBarConfiguration(false, false, false);
|
||||
return new InfoBarConfiguration(false, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-1
@@ -1,5 +1,6 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.TpsMsptPlayerCounterBar;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
@@ -16,7 +17,8 @@ public class InfoBars extends Appliance {
|
||||
private final List<Bar> infoBars = List.of(
|
||||
new TpsBar(),
|
||||
new MsptBar(),
|
||||
new PlayerCounterBar()
|
||||
new PlayerCounterBar(),
|
||||
new TpsMsptPlayerCounterBar()
|
||||
);
|
||||
|
||||
public void showAllEnabled(Player player) {
|
||||
@@ -24,6 +26,7 @@ public class InfoBars extends Appliance {
|
||||
this.setVisible(player, MsptBar.name, config.mspt());
|
||||
this.setVisible(player, PlayerCounterBar.name, config.playerCounter());
|
||||
this.setVisible(player, TpsBar.name, config.tps());
|
||||
this.setVisible(player, TpsMsptPlayerCounterBar.name, config.tpsMsptPlayerCounter());
|
||||
}
|
||||
|
||||
public void setVisible(Player player, String bar, boolean visible) {
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.Bar;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.playerlimit.PlayerLimit;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.util.statistics.ServerMonitor;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.ColorUtil;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class TpsMsptPlayerCounterBar extends Bar {
|
||||
public static String name = "serverStats";
|
||||
|
||||
@Override
|
||||
protected Duration refresh() {
|
||||
return Duration.ofSeconds(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component title() {
|
||||
float tps = this.currentTps();
|
||||
float mspt = this.currentMspt();
|
||||
int players = this.currentPlayerCount();
|
||||
|
||||
Component separator = Component.text(" | ", NamedTextColor.DARK_GRAY);
|
||||
TextColor color = ColorUtil.mapGreenToRed(players, 0, this.getMaxPlayerCount(), true);
|
||||
|
||||
return Component.text()
|
||||
.append(Component.text("TPS: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(String.format("%.2f", tps), ColorUtil.tpsColor(tps)))
|
||||
.append(separator)
|
||||
.append(Component.text("MSPT: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(String.format("%.2f", mspt), ColorUtil.msptColor(mspt)))
|
||||
.append(separator)
|
||||
.append(Component.text("Spieler: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(players, color))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float progress() {
|
||||
return Math.clamp(this.currentTps() / 20f, 0f, 1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BossBar.Color color() {
|
||||
int severity = Math.max(
|
||||
this.tpsSeverity(),
|
||||
Math.max(this.msptSeverity(), this.playerSeverity())
|
||||
);
|
||||
|
||||
return switch (severity) {
|
||||
case 2 -> BossBar.Color.RED;
|
||||
case 1 -> BossBar.Color.YELLOW;
|
||||
default -> BossBar.Color.GREEN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BossBar.Overlay overlay() {
|
||||
return BossBar.Overlay.PROGRESS;
|
||||
}
|
||||
|
||||
private int tpsSeverity() {
|
||||
float tps = this.currentTps();
|
||||
return tps >= 18 ? 0 : tps >= 15 ? 1 : 2;
|
||||
}
|
||||
|
||||
private int msptSeverity() {
|
||||
return this.currentMspt() <= 50 ? 0 : 2;
|
||||
}
|
||||
|
||||
private int playerSeverity() {
|
||||
int freeSlots = this.getMaxPlayerCount() - this.currentPlayerCount();
|
||||
return freeSlots <= 0 ? 2 : freeSlots < 5 ? 1 : 0;
|
||||
}
|
||||
|
||||
private float currentTps() {
|
||||
return (float) Bukkit.getTPS()[0];
|
||||
}
|
||||
|
||||
private float currentMspt() {
|
||||
return ServerMonitor.getServerMSPT();
|
||||
}
|
||||
|
||||
private int currentPlayerCount() {
|
||||
return Bukkit.getOnlinePlayers().size();
|
||||
}
|
||||
|
||||
private int getMaxPlayerCount() {
|
||||
return Main.instance().getAppliance(PlayerLimit.class).getLimit();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.networkChat;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
|
||||
@@ -29,7 +29,7 @@ public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.Chat
|
||||
|
||||
@Override
|
||||
protected String description() {
|
||||
return "Erwähnungen werden automatisch im Chat angewandt und der Empfänger erhält einen Signalton";
|
||||
return "Wirst du im Chat erwähnt, wird dein Name hervorgehoben und du hörst einen Signalton";
|
||||
}
|
||||
|
||||
@Override
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.networkChat;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Chat läuft über Velocity (Plugin chat-bridge): Der öffentliche Chat, /msg, /r, /adminchat, /mute und
|
||||
* Join/Leave-Meldungen gelten im ganzen Netzwerk. Hier wird der Chat nur verworfen, Velocity bekommt
|
||||
* die Rang-Prefixe für den Namen, und die Chat-Einstellungen werden mit Velocity abgeglichen.
|
||||
* <p>
|
||||
* Kanal {@value #channel}, Nachrichten (UTF-Typ, dann Daten):
|
||||
* <ul>
|
||||
* <li>→ Proxy {@code prefix}: Adventure-JSON oder leer</li>
|
||||
* <li>→ Proxy {@code settings-hello} beim Join, {@code settings-set} nach einer Änderung: Join/Leave,
|
||||
* Erwähnungen hervorheben, Erwähnungston (je boolean). Velocity speichert sie; kennt es schon
|
||||
* Werte, antwortet es auf das Hello mit {@code settings}.</li>
|
||||
* <li>← Proxy {@code settings}: gespeicherte Werte, werden hier übernommen</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class NetworkChat extends Appliance {
|
||||
public static final String channel = "craftattack:chat";
|
||||
|
||||
private final Map<UUID, Component> prefixes = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Bukkit.getMessenger().registerOutgoingPluginChannel(Main.instance(), channel);
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(Main.instance(), channel, (channel, player, message) -> this.receive(player, message));
|
||||
|
||||
Settings.instance().declareSetting(ShowJoinAndLeaveMessagesSetting.class);
|
||||
Settings.instance().declareSetting(ChatMentionSetting.class);
|
||||
Settings.instance().addChangeListener(ShowJoinAndLeaveMessagesSetting.class, player -> this.sendSettings(player, "settings-set"));
|
||||
Settings.instance().addChangeListener(ChatMentionSetting.class, player -> this.sendSettings(player, "settings-set"));
|
||||
}
|
||||
|
||||
public void setPrefix(Player player, @Nullable Component prefix) {
|
||||
if(prefix == null) this.prefixes.remove(player.getUniqueId());
|
||||
else this.prefixes.put(player.getUniqueId(), prefix);
|
||||
this.sendPrefix(player);
|
||||
}
|
||||
|
||||
void sendPrefix(Player player) {
|
||||
Component prefix = this.prefixes.get(player.getUniqueId());
|
||||
ByteArrayDataOutput output = ByteStreams.newDataOutput();
|
||||
output.writeUTF("prefix");
|
||||
output.writeUTF(prefix == null ? "" : GsonComponentSerializer.gson().serialize(prefix));
|
||||
this.send(player, output);
|
||||
}
|
||||
|
||||
void sendSettings(Player player, String type) {
|
||||
ChatMentionSetting.ChatMentionConfig mentions = Settings.instance()
|
||||
.getSetting(player, Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
|
||||
ByteArrayDataOutput output = ByteStreams.newDataOutput();
|
||||
output.writeUTF(type);
|
||||
output.writeBoolean(Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class));
|
||||
output.writeBoolean(mentions.applyMentions());
|
||||
output.writeBoolean(mentions.notifyOnMention());
|
||||
this.send(player, output);
|
||||
}
|
||||
|
||||
private void receive(Player player, byte[] message) {
|
||||
ByteArrayDataInput input = ByteStreams.newDataInput(message);
|
||||
if(!input.readUTF().equals("settings")) return;
|
||||
boolean joinAndLeave = input.readBoolean();
|
||||
boolean applyMentions = input.readBoolean();
|
||||
boolean notifyOnMention = input.readBoolean();
|
||||
Settings.instance().setSetting(player, Settings.Key.ShowJoinAndLeaveMessages, joinAndLeave);
|
||||
Settings.instance().setSetting(player, Settings.Key.ChatMentions, new ChatMentionSetting.ChatMentionConfig(applyMentions, notifyOnMention));
|
||||
}
|
||||
|
||||
private void send(Player player, ByteArrayDataOutput output) {
|
||||
// Velocity meldet den Kanal erst kurz nach dem Join an, dann folgt PlayerRegisterChannelEvent
|
||||
if(!player.getListeningPluginChannels().contains(channel)) return;
|
||||
player.sendPluginMessage(Main.instance(), channel, output.toByteArray());
|
||||
}
|
||||
|
||||
void forget(Player player) {
|
||||
this.prefixes.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(new NetworkChatListener());
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.networkChat;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerRegisterChannelEvent;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
class NetworkChatListener extends ApplianceListener<NetworkChat> {
|
||||
// Vanilla-Befehle, die am Proxy vorbei in den lokalen Chat schreiben; /msg, /tell, /w fängt Velocity ab
|
||||
private static final Set<String> localChatCommands = Set.of(
|
||||
"me", "minecraft:me", "teammsg", "tm", "minecraft:teammsg", "minecraft:tm",
|
||||
"minecraft:msg", "minecraft:tell", "minecraft:w"
|
||||
);
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onChat(AsyncChatEvent event) {
|
||||
// Velocity hat die Nachricht schon an alle verteilt
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onCommand(PlayerCommandPreprocessEvent event) {
|
||||
String label = event.getMessage().substring(1).split(" ", 2)[0].toLowerCase(Locale.ROOT);
|
||||
if(!localChatCommands.contains(label)) return;
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(Component.text("Dieser Befehl ist hier nicht verfügbar.", NamedTextColor.RED));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onRegisterChannel(PlayerRegisterChannelEvent event) {
|
||||
if(!event.getChannel().equals(NetworkChat.channel)) return;
|
||||
this.getAppliance().sendPrefix(event.getPlayer());
|
||||
this.getAppliance().sendSettings(event.getPlayer(), "settings-hello");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
// Join- und Leave-Meldungen schreibt Velocity, beim Serverwechsel gar keine
|
||||
event.joinMessage(null);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
event.quitMessage(null);
|
||||
this.getAppliance().forget(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDeath(PlayerDeathEvent event) {
|
||||
event.deathMessage(
|
||||
Component
|
||||
.text("☠ ")
|
||||
.append(
|
||||
Optional
|
||||
.ofNullable(event.deathMessage())
|
||||
.orElse(Component.text(event.getPlayer().getName()))
|
||||
)
|
||||
.color(TextColor.color(Color.SILVER.asRGB()))
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.networkChat;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage;
|
||||
|
||||
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.common.appliances.metaGameplay.chatMessages.ChatMessages;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands.PrivateMessageCommand;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands.PrivateReplyCommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentBuilder;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PrivateMessage extends Appliance {
|
||||
public final int targetChangeTimeoutSeconds = 30;
|
||||
public final int conversationTimeoutMinutes = 30;
|
||||
|
||||
private record Conversation(UUID target, Long lastSet) {
|
||||
}
|
||||
|
||||
private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
|
||||
|
||||
public void reply(Player sender, String message) {
|
||||
this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
|
||||
|
||||
List<Conversation> replyList = this.replyMapping.get(sender);
|
||||
|
||||
List<Conversation> tooOldConversations = replyList.stream()
|
||||
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (this.conversationTimeoutMinutes * 60 * 1000))
|
||||
.toList();
|
||||
replyList.removeAll(tooOldConversations);
|
||||
|
||||
if(replyList.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
|
||||
|
||||
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
|
||||
|
||||
Conversation youngestEntry = replyList.stream()
|
||||
.max(Comparator.comparingLong(o -> o.lastSet))
|
||||
.orElse(replyList.getLast());
|
||||
|
||||
if(message.isBlank()) {
|
||||
Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
|
||||
|
||||
Component currentTargetComponent = currentTargetPlayer != null
|
||||
? Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer)
|
||||
: Component.text("niemandem.");
|
||||
|
||||
sender.sendMessage(
|
||||
privatePrefix
|
||||
.append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
|
||||
.append(currentTargetComponent)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Conversation> oldConversations = replyList.stream()
|
||||
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (this.targetChangeTimeoutSeconds * 1000))
|
||||
.toList();
|
||||
|
||||
if(oldConversations.contains(youngestEntry) || replyList.size() == 1) {
|
||||
Player target = Bukkit.getPlayer(youngestEntry.target());
|
||||
if(target == null)
|
||||
throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
|
||||
|
||||
replyList.clear();
|
||||
this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
|
||||
|
||||
component.append(
|
||||
Component.newline()
|
||||
.append(privatePrefix)
|
||||
.append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
|
||||
.append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
|
||||
.append(Component.text(" Sekunden geändert. Wer soll deine Nachricht erhalten? ", NamedTextColor.RED))
|
||||
.appendNewline()
|
||||
.appendNewline()
|
||||
);
|
||||
|
||||
if(!oldConversations.isEmpty()) {
|
||||
Conversation youngestOldConversation = oldConversations.stream()
|
||||
.max(Comparator.comparingLong(o -> o.lastSet))
|
||||
.orElse(oldConversations.getLast());
|
||||
replyList.removeAll(oldConversations);
|
||||
replyList.add(youngestOldConversation);
|
||||
}
|
||||
|
||||
List<String> playerNames = replyList.stream()
|
||||
.map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
playerNames.forEach(playerName -> component.append(
|
||||
Component.text("[")
|
||||
.append(Component.text(playerName, NamedTextColor.GOLD))
|
||||
.append(Component.text("]"))
|
||||
.clickEvent(ClickEvent.runCommand(String.format("/msg %s %s", playerName, message)))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
|
||||
.append(Component.text(" "))
|
||||
);
|
||||
component.appendNewline();
|
||||
|
||||
sender.sendMessage(component.build());
|
||||
|
||||
}
|
||||
|
||||
public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
|
||||
Conversation newReceiverConversation = new Conversation(
|
||||
sender.getUniqueId(),
|
||||
System.currentTimeMillis()
|
||||
);
|
||||
|
||||
if(this.replyMapping.get(userArguments.receiver) != null) {
|
||||
List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
|
||||
.filter(conversation -> conversation.target() == sender.getUniqueId())
|
||||
.toList();
|
||||
this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
|
||||
} else {
|
||||
this.replyMapping.put(
|
||||
userArguments.receiver,
|
||||
new ArrayList<>()
|
||||
);
|
||||
}
|
||||
|
||||
this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
|
||||
|
||||
List<Conversation> senderConversationList = new ArrayList<>();
|
||||
senderConversationList.add(
|
||||
new Conversation(
|
||||
userArguments.receiver.getUniqueId(),
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
);
|
||||
|
||||
this.replyMapping.put(
|
||||
sender,
|
||||
senderConversationList
|
||||
);
|
||||
|
||||
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
|
||||
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
|
||||
|
||||
sender.sendMessage(
|
||||
Component.text()
|
||||
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", userArguments.receiver.getName()))))
|
||||
.append(sender.displayName())
|
||||
.append(Component.text(" zu ", NamedTextColor.GRAY))
|
||||
.append(chatMessages.getReportablePlayerName(userArguments.receiver))
|
||||
.append(Component.text(" > ", NamedTextColor.GRAY))
|
||||
.append(Component.text(userArguments.message))
|
||||
);
|
||||
userArguments.receiver.sendMessage(
|
||||
Component.text()
|
||||
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", sender.getName()))))
|
||||
.append(chatMessages.getReportablePlayerName(sender))
|
||||
.append(Component.text(" zu ", NamedTextColor.GRAY))
|
||||
.append(userArguments.receiver.displayName())
|
||||
.append(Component.text(" > ", NamedTextColor.GRAY))
|
||||
.append(Component.text(userArguments.message))
|
||||
);
|
||||
}
|
||||
|
||||
public record ResolvedPmUserArguments(Player receiver, String message) {
|
||||
}
|
||||
|
||||
public ResolvedPmUserArguments resolveImplicit(String[] args) {
|
||||
if(args.length < 2)
|
||||
throw new ApplianceCommand.Error("Es muss ein Spieler sowie eine Nachricht angegeben werden.");
|
||||
List<String> arguments = List.of(args);
|
||||
Player targetPlayer = Bukkit.getPlayer(arguments.getFirst());
|
||||
if(targetPlayer == null)
|
||||
throw new ApplianceCommand.Error(String.format("Der Spieler %s konnte nicht gefunden werden.", arguments.getFirst()));
|
||||
String message = arguments.stream().skip(1).collect(Collectors.joining(" "));
|
||||
return new ResolvedPmUserArguments(targetPlayer, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<ApplianceCommand<?>> commands() {
|
||||
return List.of(
|
||||
new PrivateMessageCommand(),
|
||||
new PrivateReplyCommand()
|
||||
);
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.PrivateMessage;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PrivateMessageCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
|
||||
public PrivateMessageCommand() {
|
||||
super("msg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
this.getAppliance().sendWhisper(this.getPlayer(), this.getAppliance().resolveImplicit(args));
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.PrivateMessage;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PrivateReplyCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
|
||||
public PrivateReplyCommand() {
|
||||
super("r");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
this.getAppliance().reply(this.getPlayer(), String.join(" ", args));
|
||||
}
|
||||
}
|
||||
+12
@@ -112,6 +112,18 @@ public class Settings extends Appliance {
|
||||
return clazz.cast(setting.state());
|
||||
}
|
||||
|
||||
/** Setzt einen Wert von außen (z. B. vom Proxy), ohne die Change-Listener auszulösen. */
|
||||
public <T> void setSetting(Player player, Key key, T value) {
|
||||
Setting<?> setting = this.getSettings(player).stream()
|
||||
.filter(s -> Objects.equals(s.getKey(), key))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
if(!setting.dataType().isInstance(value))
|
||||
throw new IllegalStateException("Tried to set Setting with Datatype " + value.getClass().getSimpleName() + " but expected " + setting.dataType().getSimpleName());
|
||||
setting.restore(player, value);
|
||||
}
|
||||
|
||||
public void openSettings(Player player) {
|
||||
List<Setting<?>> settings = this.getSettings(player);
|
||||
Inventory inventory = Bukkit.createInventory(null, this.calculateInvSize(settings), Component.text("Einstellungen"));
|
||||
|
||||
+6
@@ -62,6 +62,12 @@ public abstract class Setting<TDataType> {
|
||||
Settings.instance().invokeChangeListener(p, this.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void restore(Player p, Object value) {
|
||||
this.state = (TDataType) value;
|
||||
this.toStorage(p.getPersistentDataContainer(), this.state);
|
||||
}
|
||||
|
||||
public ItemStack buildItem() {
|
||||
ItemStack stack = new ItemStack(this.icon(), 1);
|
||||
stack.setItemMeta(this.buildMeta(stack.getItemMeta()));
|
||||
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.adminChat;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AdminChat extends Appliance {
|
||||
public void sendMessage(Player sender, String message) {
|
||||
Component privatePrefix = Component
|
||||
.text("[Admin] ", NamedTextColor.LIGHT_PURPLE)
|
||||
.clickEvent(ClickEvent.suggestCommand(AdminChatCommand.commandName));
|
||||
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
.filter(player -> player.hasPermission("admin"))
|
||||
.forEach(player -> {
|
||||
Component formattedMessage = Component.text()
|
||||
.append(privatePrefix)
|
||||
.append(sender.displayName())
|
||||
.append(Component.text(" > ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text(message))
|
||||
.build();
|
||||
player.sendMessage(formattedMessage);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<ApplianceCommand<?>> commands() {
|
||||
return List.of(new AdminChatCommand());
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.adminChat;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class AdminChatCommand extends ApplianceCommand.PlayerChecked<AdminChat> {
|
||||
public static final String commandName = "adminchat";
|
||||
|
||||
public AdminChatCommand() {
|
||||
super(commandName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
if(!sender.hasPermission("admin")) return;
|
||||
|
||||
String message = String.join(" ", args);
|
||||
this.getAppliance().sendMessage(this.getPlayer(), message);
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.chatMute;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ChatMute extends Appliance {
|
||||
private static final String namespace = ChatMute.class.getSimpleName().toLowerCase(Locale.ROOT);
|
||||
public static NamespacedKey mutedUntilKey = new NamespacedKey(namespace, "mutedUntilMillis".toLowerCase());
|
||||
|
||||
public void mutePlayer(Player player, int durationHours) {
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
long mutedUntil = System.currentTimeMillis() + (long) durationHours * 60 * 60 * 1000;
|
||||
container.set(ChatMute.mutedUntilKey, PersistentDataType.LONG, mutedUntil);
|
||||
}
|
||||
|
||||
public @Nullable Long muteStatus(Player player) {
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
if(!container.has(mutedUntilKey)) return null;
|
||||
long mutedUntil = Objects.requireNonNull(container.get(mutedUntilKey, PersistentDataType.LONG));
|
||||
if(mutedUntil < System.currentTimeMillis()) return null;
|
||||
return mutedUntil;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<ApplianceCommand<?>> commands() {
|
||||
return List.of(new MuteCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(new ChatMuteListener());
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.chatMute;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.DataSizeConverter;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class ChatMuteListener extends ApplianceListener<ChatMute> {
|
||||
@EventHandler
|
||||
public void onChat(AsyncChatEvent event) {
|
||||
@Nullable Long muteDuration = this.getAppliance().muteStatus(event.getPlayer());
|
||||
if(muteDuration == null) return;
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(Component.text(
|
||||
String.format(
|
||||
"Du bist für %s gestummt!",
|
||||
DataSizeConverter.formatSecondsToHumanReadable((int) ((muteDuration - System.currentTimeMillis()) / 1000))
|
||||
),
|
||||
NamedTextColor.RED
|
||||
));
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.chatMute;
|
||||
|
||||
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;
|
||||
import java.util.Objects;
|
||||
|
||||
class MuteCommand extends ApplianceCommand<ChatMute> {
|
||||
public MuteCommand() {
|
||||
super("mute");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
if(args.length < 2) throw new Error("Syntax: mute <player> <duration in hours>");
|
||||
Player player = Objects.requireNonNull(Bukkit.getPlayer(args[0]));
|
||||
int durationInHours = Integer.parseInt(args[1]);
|
||||
this.getAppliance().mutePlayer(player, durationInHours);
|
||||
sender.sendMessage(String.format("%s wurde für %d Stunden gestummt!", player.getName(), durationInHours));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if(args.length == 2) {
|
||||
return List.of("1", "2", "4", "8", "24", "48");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -110,9 +110,6 @@ tablist:
|
||||
projectTitle: "Title"
|
||||
interface: eth0
|
||||
|
||||
outlawed:
|
||||
voluntarily: []
|
||||
|
||||
packselect:
|
||||
packs:
|
||||
- somepack:
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.outlawed;
|
||||
|
||||
class OutlawChangeNotPermitted extends Exception {
|
||||
public OutlawChangeNotPermitted(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.outlawed;
|
||||
|
||||
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.common.appliances.metaGameplay.displayName.DisplayName;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.whitelist.Whitelist;
|
||||
import eu.mhsl.craftattack.spawn.core.config.Configuration;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Outlawed extends Appliance implements DisplayName.Prefixed {
|
||||
public final int timeoutInMs = 1000 * 60 * 60 * 6;
|
||||
private final Map<UUID, Long> timeouts = new HashMap<>();
|
||||
|
||||
public enum Status {
|
||||
DISABLED,
|
||||
VOLUNTARILY,
|
||||
FORCED
|
||||
}
|
||||
|
||||
private final Map<Player, Status> playerStatusMap = new WeakHashMap<>();
|
||||
private final String voluntarilyEntry = "voluntarily";
|
||||
|
||||
public Outlawed() {
|
||||
super("outlawed");
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(
|
||||
Main.instance(),
|
||||
() -> this.playerStatusMap.forEach((player, status) -> {
|
||||
if(!player.isOnline()) return;
|
||||
if(status != Status.FORCED) return;
|
||||
try {
|
||||
this.queryAppliance(Whitelist.class).fullIntegrityCheck(player);
|
||||
} catch(DisconnectInfo.Throwable e) {
|
||||
e.getDisconnectScreen().applyKick(player);
|
||||
}
|
||||
}),
|
||||
20 * 60,
|
||||
20 * 60 * 5
|
||||
);
|
||||
}
|
||||
|
||||
void askForConfirmation(Player player) {
|
||||
Component confirmationMessage = switch(this.getLawStatus(player)) {
|
||||
case DISABLED -> Component.text("Wenn du den PVP-Modus aktivierst, darfst du von allen anderen PVP-Spielern grundlos angegriffen werden." +
|
||||
" Du kannst den PVP-Modus erst wieder nach " + this.timeoutInMs / 1000 / 60 / 60 + " Stunden deaktivieren!");
|
||||
case VOLUNTARILY -> Component.text("Wenn du den PVP-Modus deaktivierst, darfst du nicht mehr grundlos von anderen Spielern angegriffen werden.");
|
||||
case FORCED -> Component.text("Du darfst zurzeit deinen PVP-Modus nicht ändern, da die˝ser als Strafe auferlegt wurde!");
|
||||
};
|
||||
String command = String.format("/%s confirm", OutlawedCommand.commandName);
|
||||
Component changeText = Component.text(
|
||||
String.format(
|
||||
"Zum ändern deines PVP-Status klicke auf diese Nachricht oder tippe '%s'",
|
||||
command
|
||||
),
|
||||
NamedTextColor.GOLD
|
||||
).clickEvent(ClickEvent.suggestCommand(command));
|
||||
|
||||
player.sendMessage(
|
||||
Component.text()
|
||||
.append(confirmationMessage.color(NamedTextColor.RED))
|
||||
.appendNewline()
|
||||
.append(changeText)
|
||||
);
|
||||
}
|
||||
|
||||
void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
|
||||
if(this.getLawStatus(player).equals(Status.FORCED)) {
|
||||
throw new OutlawChangeNotPermitted("Dein PVP-Status wurde als Strafe auferlegt und kann daher nicht verändert werden.");
|
||||
}
|
||||
|
||||
if(this.isTimeout(player)) {
|
||||
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.setTimeout(player);
|
||||
}
|
||||
|
||||
public void updateForcedStatus(Player player, boolean forced) {
|
||||
this.setLawStatus(player, forced ? Status.FORCED : this.getLawStatus(player) == Status.FORCED ? Status.DISABLED : this.getLawStatus(player));
|
||||
}
|
||||
|
||||
public Status getLawStatus(Player player) {
|
||||
return this.playerStatusMap.computeIfAbsent(player, p -> {
|
||||
if(this.localConfig().getStringList(this.voluntarilyEntry).contains(p.getUniqueId().toString()))
|
||||
return Status.VOLUNTARILY;
|
||||
return Status.DISABLED;
|
||||
});
|
||||
}
|
||||
|
||||
private void setLawStatus(Player player, Status status) {
|
||||
this.playerStatusMap.put(player, status);
|
||||
this.queryAppliance(DisplayName.class).update(player);
|
||||
|
||||
List<String> newList = this.localConfig().getStringList(this.voluntarilyEntry);
|
||||
if(status.equals(Status.VOLUNTARILY)) {
|
||||
newList.add(player.getUniqueId().toString());
|
||||
} else {
|
||||
newList.remove(player.getUniqueId().toString());
|
||||
}
|
||||
this.localConfig().set(this.voluntarilyEntry, newList.stream().distinct().toList());
|
||||
Configuration.saveChanges();
|
||||
|
||||
}
|
||||
|
||||
public boolean isOutlawed(Player player) {
|
||||
return this.getLawStatus(player) != Status.DISABLED;
|
||||
}
|
||||
|
||||
private boolean isTimeout(Player player) {
|
||||
return this.timeouts.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis() - this.timeoutInMs;
|
||||
}
|
||||
|
||||
private void setTimeout(Player player) {
|
||||
this.timeouts.put(player.getUniqueId(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Component getStatusDescription(Status status) {
|
||||
return switch(status) {
|
||||
case DISABLED -> Component.text("PVP-Modus inaktiv: ", NamedTextColor.GREEN)
|
||||
.append(Component.text("Es gelten die normalen Regeln!", NamedTextColor.GOLD));
|
||||
|
||||
case VOLUNTARILY, FORCED -> Component.text("PVP-Modus aktiv: ", NamedTextColor.RED)
|
||||
.append(Component.text(
|
||||
"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!",
|
||||
NamedTextColor.GOLD
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getNamePrefix(Player player) {
|
||||
if(this.isOutlawed(player)) {
|
||||
return Component.text("[☠]", NamedTextColor.RED)
|
||||
.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"PVP-Modus Spieler dürfen von anderen vogelfreien Spielern ohne Grund angegriffen werden!"
|
||||
)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected List<ApplianceCommand<?>> commands() {
|
||||
return List.of(new OutlawedCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected List<Listener> listeners() {
|
||||
return List.of(new OutlawedReminderListener());
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.outlawed;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
|
||||
public static final String commandName = "pvp";
|
||||
|
||||
public OutlawedCommand() {
|
||||
super(commandName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
if(args.length == 1 && args[0].equals("confirm")) {
|
||||
try {
|
||||
this.getAppliance().switchLawStatus(this.getPlayer());
|
||||
sender.sendMessage(this.getAppliance().getStatusDescription(this.getAppliance().getLawStatus(this.getPlayer())));
|
||||
} catch(OutlawChangeNotPermitted e) {
|
||||
sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
|
||||
}
|
||||
} else {
|
||||
this.getAppliance().askForConfirmation(this.getPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.outlawed;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
class OutlawedReminderListener extends ApplianceListener<Outlawed> {
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if(this.getAppliance().isOutlawed(e.getPlayer())) {
|
||||
e.getPlayer().sendMessage(this.getAppliance().getStatusDescription(this.getAppliance().getLawStatus(e.getPlayer())));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user