Compare commits
53 Commits
53dbeff829
...
master-big
| Author | SHA1 | Date | |
|---|---|---|---|
| ef153d5d8f | |||
| bd883a4fa1 | |||
| 5cda58408a | |||
| 9767896cde | |||
| f0e0cfbb85 | |||
| de112f7e13 | |||
| 215259c6b9 | |||
| 36520a87f9 | |||
| 914aaff10b | |||
| e015bbb356 | |||
| ec262710ec | |||
| 1ac19014c1 | |||
| dd1518fce4 | |||
| 04cb233604 | |||
| 2ff95f8450 | |||
| 6ed48895ca | |||
| bff8cf24cd | |||
| 212f84b6de | |||
| f7430c8fc8 | |||
| c81a2d2161 | |||
| 2087b4c379 | |||
| 4d9548aafc | |||
| 4c63800189 | |||
| b8725bc0f2 | |||
| c90d767f0f | |||
| 143f4ee8eb | |||
| ba2befb467 | |||
| 7a2b9b9763 | |||
| 0b9dc5358d | |||
| 448e9472db | |||
| 933c4c0db0 | |||
| f27474016a | |||
| 17e5b2e049 | |||
| d3512cb2eb | |||
| 7b19bfd39e | |||
| 0ab67bb426 | |||
| 29a362b580 | |||
| a7f298682b | |||
| 895a51e71a | |||
| 4a5c24235b | |||
| 62c0250049 | |||
| b4ccc3c4c8 | |||
| 239094971c | |||
| 469cd19b55 | |||
| 91a28b4500 | |||
| e745ff4721 | |||
| 23af3ff784 | |||
| bc5c9a2a13 | |||
| c220479052 | |||
| 78f87d97f0 | |||
| db13a9f0a2 | |||
| aad1fcafa6 | |||
| 9fca7430a8 |
@@ -1,7 +1,7 @@
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT'
|
||||
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'
|
||||
|
||||
@@ -11,15 +11,14 @@ public class CraftAttackReportRepository extends ReportRepository {
|
||||
|
||||
public ReqResp<PlayerReports> queryReports(UUID player) {
|
||||
return this.get(
|
||||
"report",
|
||||
(parameters) -> parameters.addParameter("uuid", player.toString()),
|
||||
"users/%s/reports".formatted(player.toString()),
|
||||
PlayerReports.class
|
||||
);
|
||||
}
|
||||
|
||||
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
|
||||
return this.post(
|
||||
"report",
|
||||
"reports",
|
||||
data,
|
||||
ReportUrl.class
|
||||
);
|
||||
|
||||
@@ -23,19 +23,18 @@ public abstract class ReportRepository extends HttpRepository {
|
||||
|
||||
public record PlayerReports(
|
||||
List<Report> from_self,
|
||||
Object to_self
|
||||
List<Report> to_self
|
||||
) {
|
||||
public record Report(
|
||||
@Nullable Reporter reported,
|
||||
@NotNull String subject,
|
||||
boolean draft,
|
||||
@NotNull String status,
|
||||
@Nullable UUID reported,
|
||||
@NotNull String reason,
|
||||
@Nullable Long created,
|
||||
@Nullable Status status,
|
||||
@NotNull String url
|
||||
) {
|
||||
public record Reporter(
|
||||
@NotNull String username,
|
||||
@NotNull String uuid
|
||||
) {
|
||||
public enum Status {
|
||||
open,
|
||||
closed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,4 @@ public class InfoBarSetting extends MultiBoolSetting<InfoBarSetting.InfoBarConfi
|
||||
public Class<?> dataType() {
|
||||
return InfoBarConfiguration.class;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
||||
|
||||
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 eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.MsptBar;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.PlayerCounterBar;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.TpsBar;
|
||||
import org.bukkit.Bukkit;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class InfoBars extends Appliance {
|
||||
private final NamespacedKey infoBarKey = new NamespacedKey(Main.instance(), "infobars");
|
||||
private final List<Bar> infoBars = List.of(
|
||||
new TpsBar(),
|
||||
new MsptBar(),
|
||||
@@ -27,41 +20,19 @@ public class InfoBars extends Appliance {
|
||||
);
|
||||
|
||||
public void showAllEnabled(Player player) {
|
||||
this.getEnabledBars(player).forEach(bar -> this.show(player, bar));
|
||||
InfoBarSetting.InfoBarConfiguration config = Settings.instance().getSetting(player, Settings.Key.InfoBars, InfoBarSetting.InfoBarConfiguration.class);
|
||||
this.setVisible(player, MsptBar.name, config.mspt());
|
||||
this.setVisible(player, PlayerCounterBar.name, config.playerCounter());
|
||||
this.setVisible(player, TpsBar.name, config.tps());
|
||||
}
|
||||
|
||||
public void hideAllEnabled(Player player) {
|
||||
this.getEnabledBars(player).forEach(bar -> this.hide(player, bar));
|
||||
this.setEnabledBars(player, List.of());
|
||||
}
|
||||
|
||||
public void show(Player player, String bar) {
|
||||
public void setVisible(Player player, String bar, boolean visible) {
|
||||
this.validateBarName(bar);
|
||||
List<String> existingBars = new ArrayList<>(this.getEnabledBars(player));
|
||||
existingBars.add(bar);
|
||||
if(visible) {
|
||||
player.showBossBar(this.getBarByName(bar).getBossBar());
|
||||
this.setEnabledBars(player, existingBars);
|
||||
}
|
||||
|
||||
public void hide(Player player, String bar) {
|
||||
this.validateBarName(bar);
|
||||
List<String> existingBars = new ArrayList<>(this.getEnabledBars(player));
|
||||
existingBars.remove(bar);
|
||||
} else {
|
||||
player.hideBossBar(this.getBarByName(bar).getBossBar());
|
||||
this.setEnabledBars(player, existingBars);
|
||||
}
|
||||
|
||||
private List<String> getEnabledBars(Player player) {
|
||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
if(!container.has(this.infoBarKey)) return List.of();
|
||||
return container.get(this.infoBarKey, PersistentDataType.LIST.strings());
|
||||
}
|
||||
|
||||
private void setEnabledBars(Player player, List<String> bars) {
|
||||
Bukkit.getScheduler().runTask(
|
||||
Main.instance(),
|
||||
() -> player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars)
|
||||
);
|
||||
}
|
||||
|
||||
private Bar getBarByName(String name) {
|
||||
@@ -79,13 +50,7 @@ public class InfoBars extends Appliance {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Settings.instance().declareSetting(InfoBarSetting.class);
|
||||
Settings.instance().addChangeListener(InfoBarSetting.class, player -> {
|
||||
this.hideAllEnabled(player);
|
||||
InfoBarSetting.InfoBarConfiguration config = Settings.instance().getSetting(player, Settings.Key.InfoBars, InfoBarSetting.InfoBarConfiguration.class);
|
||||
if(config.mspt()) this.show(player, MsptBar.name);
|
||||
if(config.playerCounter()) this.show(player, PlayerCounterBar.name);
|
||||
if(config.tps()) this.show(player, TpsBar.name);
|
||||
});
|
||||
Settings.instance().addChangeListener(InfoBarSetting.class, this::showAllEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
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.TextComponent;
|
||||
@@ -19,7 +20,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Report extends Appliance {
|
||||
public static Component helpText() {
|
||||
@@ -78,7 +81,7 @@ public class Report extends Appliance {
|
||||
.appendNewline()
|
||||
.append(
|
||||
Component
|
||||
.text(createdReport.data().url(), NamedTextColor.GRAY) // URL mit Weltkugel-Emoji
|
||||
.text(createdReport.data().url(), NamedTextColor.GRAY)
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.data().url()))
|
||||
)
|
||||
.appendNewline()
|
||||
@@ -128,43 +131,50 @@ public class Report extends Appliance {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ReportRepository.PlayerReports.Report> reports = userReports
|
||||
.data()
|
||||
.from_self()
|
||||
.stream()
|
||||
.filter(report -> !report.draft())
|
||||
.toList()
|
||||
.reversed();
|
||||
Function<List<ReportRepository.PlayerReports.Report>, List<ReportRepository.PlayerReports.Report>> filterClosed = reports -> reports.stream()
|
||||
.filter(report -> Objects.equals(report.status(), ReportRepository.PlayerReports.Report.Status.closed))
|
||||
.toList();
|
||||
|
||||
if(reports.isEmpty()) {
|
||||
issuer.sendMessage(
|
||||
Component.text()
|
||||
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
|
||||
.appendNewline()
|
||||
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
|
||||
);
|
||||
return;
|
||||
}
|
||||
List<ReportRepository.PlayerReports.Report> reportsToOthers = filterClosed.apply(userReports.data().from_self()).reversed();
|
||||
List<ReportRepository.PlayerReports.Report> reportsToSelf = filterClosed.apply(userReports.data().to_self()).reversed();
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
|
||||
.append(Component.newline())
|
||||
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
|
||||
.appendNewline();
|
||||
.append(Component.text(
|
||||
!reportsToSelf.isEmpty()
|
||||
? "Du wurdest insgesamt %d mal von anderen Spielern gemeldet.".formatted(reportsToSelf.size())
|
||||
: "Du wurdest von keinem anderen Spieler gemeldet.",
|
||||
NamedTextColor.GOLD)
|
||||
);
|
||||
|
||||
reports.forEach(report -> {
|
||||
component
|
||||
.append(Component.text(" - ", NamedTextColor.WHITE))
|
||||
.append(
|
||||
report.reported() != null
|
||||
? Component.text(report.reported().username(), NamedTextColor.WHITE)
|
||||
: Component.text("Unbekannt", NamedTextColor.YELLOW)
|
||||
)
|
||||
.append(Component.text(String.format(": %s", report.subject()), NamedTextColor.GRAY))
|
||||
.clickEvent(ClickEvent.openUrl(report.url()))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)));
|
||||
component.appendNewline();
|
||||
|
||||
component.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD));
|
||||
reportsToOthers.forEach(report -> {
|
||||
Component button = Component.text("[\uD83D\uDC41/\uD83D\uDD8A]")
|
||||
.clickEvent(ClickEvent.openUrl(report.url()))
|
||||
.hoverEvent(HoverEvent.showText(ComponentUtil.clickLink(report.url())));
|
||||
|
||||
Component reportedDisplayName = report.reported() != null
|
||||
? Component.text(Optional.ofNullable(Bukkit.getOfflinePlayer(report.reported()).getName()).orElse(report.reported().toString()), NamedTextColor.WHITE)
|
||||
: Component.text("Unbekannt", NamedTextColor.YELLOW);
|
||||
|
||||
component
|
||||
.appendNewline()
|
||||
.append(Component.text(" \u27A1 ", NamedTextColor.GRAY))
|
||||
.append(button)
|
||||
.append(Component.text(" du gegen ", NamedTextColor.GRAY))
|
||||
.append(reportedDisplayName)
|
||||
.append(Component.text(String.format(": %s", report.reason()), NamedTextColor.GRAY));
|
||||
});
|
||||
|
||||
if(reportsToOthers.isEmpty()) {
|
||||
component
|
||||
.appendNewline()
|
||||
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
|
||||
.appendNewline()
|
||||
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY));
|
||||
}
|
||||
|
||||
issuer.sendMessage(component.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class SettingsShortcutSetting extends BoolSetting implements CategorizedS
|
||||
|
||||
@Override
|
||||
protected Boolean defaultValue() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.security.antiBoatFreecam;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Boat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AntiBoatFreecam extends Appliance {
|
||||
private static final float MAX_YAW_OFFSET = 106.0f;
|
||||
|
||||
public AntiBoatFreecam() {
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(
|
||||
Main.instance(),
|
||||
() -> Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
if(!(player.getVehicle() instanceof Boat boat)) return;
|
||||
if(!boat.getPassengers().getFirst().equals(player)) return;
|
||||
float playerYaw = player.getYaw();
|
||||
float boatYaw = boat.getYaw();
|
||||
|
||||
float yawDelta = wrapDegrees(playerYaw - boatYaw);
|
||||
if(Math.abs(yawDelta) <= MAX_YAW_OFFSET) return;
|
||||
|
||||
Main.instance().getAppliance(AcInform.class).slowedNotifyAdmins(
|
||||
"internal",
|
||||
player.getName(),
|
||||
"illegalBoatLookYaw",
|
||||
yawDelta,
|
||||
3000
|
||||
);
|
||||
}),
|
||||
1L,
|
||||
1L
|
||||
);
|
||||
}
|
||||
|
||||
private static float wrapDegrees(float deg) {
|
||||
deg = deg % 360f;
|
||||
if (deg >= 180f) deg -= 360f;
|
||||
if (deg < -180f) deg += 360f;
|
||||
return deg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.security.antiFormattedBook;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AntiFormattedBook extends Appliance {
|
||||
private static final char SECTION = '\u00A7';
|
||||
|
||||
public boolean containsFormatting(BookMeta meta) {
|
||||
if (this.hasFormattingDeep(meta.title())) return true;
|
||||
if (this.hasFormattingDeep(meta.author())) return true;
|
||||
|
||||
for (Component c : meta.pages()) {
|
||||
if (this.hasFormattingDeep(c)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasFormattingDeep(@Nullable Component component) {
|
||||
if(component == null) return false;
|
||||
if (this.hastFormatting(component)) return true;
|
||||
|
||||
if (component instanceof TextComponent tc && tc.content().indexOf(SECTION) >= 0) return true;
|
||||
|
||||
if (component instanceof NBTComponent<?, ?> nbt) {
|
||||
if (nbt.separator() != null && this.hasFormattingDeep(nbt.separator())) return true;
|
||||
}
|
||||
|
||||
for (Component child : component.children()) {
|
||||
if (this.hasFormattingDeep(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hastFormatting(Component component) {
|
||||
Style style = component.style();
|
||||
|
||||
TextColor color = style.color();
|
||||
if (color != null) return true;
|
||||
if (style.font() != null) return true;
|
||||
if (style.insertion() != null && !Objects.requireNonNull(style.insertion()).isEmpty()) return true;
|
||||
|
||||
for (var decoration : style.decorations().entrySet()) {
|
||||
if (decoration.getValue() == TextDecoration.State.TRUE) return true;
|
||||
}
|
||||
|
||||
return style.hoverEvent() != null || style.clickEvent() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(
|
||||
new BookEditListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.security.antiFormattedBook;
|
||||
|
||||
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 net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerEditBookEvent;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class BookEditListener extends ApplianceListener<AntiFormattedBook> {
|
||||
@EventHandler
|
||||
public void onBookEdit(PlayerEditBookEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
BookMeta meta = event.getNewBookMeta();
|
||||
|
||||
if (this.getAppliance().containsFormatting(meta)) {
|
||||
Main.instance().getAppliance(AcInform.class).notifyAdmins(
|
||||
"internal",
|
||||
player.getName(),
|
||||
"illegalBookFormatting",
|
||||
1f
|
||||
);
|
||||
|
||||
BookMeta sanitized = meta.clone();
|
||||
sanitized.title(null);
|
||||
sanitized.author(null);
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
sanitized.pages(List.of(Component.empty()));
|
||||
event.setNewBookMeta(sanitized);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalBundlePicker;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BundleMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AntiIllegalBundlePicker extends Appliance {
|
||||
private static final int visibleSlotsInBundle = 9;
|
||||
|
||||
public void trackBundle(InventoryClickEvent event) {
|
||||
ItemStack bundle = Objects.requireNonNull(event.getCurrentItem());
|
||||
final int rawSlot = event.getRawSlot();
|
||||
final Player player = (Player) event.getWhoClicked();
|
||||
final InventoryView view = event.getView();
|
||||
final List<ItemStack> before = this.getBundleContents(bundle);
|
||||
|
||||
Bukkit.getScheduler().runTask(Main.instance(), () -> {
|
||||
ItemStack afterStack = view.getItem(rawSlot);
|
||||
if(afterStack == null || afterStack.getType() != Material.BUNDLE) return;
|
||||
|
||||
List<ItemStack> after = this.getBundleContents(afterStack);
|
||||
int removedSlotIndex = this.findRemoved(before, after);
|
||||
|
||||
if(removedSlotIndex >= visibleSlotsInBundle) {
|
||||
Main.instance().getAppliance(AcInform.class).notifyAdmins(
|
||||
"internal",
|
||||
player.getName(),
|
||||
"illegalBundlePick",
|
||||
(float) removedSlotIndex
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int findRemoved(@NotNull List<ItemStack> before, @NotNull List<ItemStack> after) {
|
||||
for (int i = 0; i < Math.max(before.size(), after.size()); i++) {
|
||||
ItemStack a = i < after.size() ? after.get(i) : null;
|
||||
ItemStack b = i < before.size() ? before.get(i) : null;
|
||||
|
||||
if (b == null && a == null) continue;
|
||||
if (b == null) throw new IllegalStateException("Size of bundle was smaller before pickup Action");
|
||||
|
||||
if (a == null) return i;
|
||||
if (!a.isSimilar(b)) return i;
|
||||
if (a.getAmount() != b.getAmount()) return i;
|
||||
}
|
||||
throw new IllegalStateException("Failed to find picked Item in bundle");
|
||||
}
|
||||
|
||||
private List<ItemStack> getBundleContents(@NotNull ItemStack bundle) {
|
||||
if (bundle.getType() != Material.BUNDLE)
|
||||
throw new IllegalStateException("ItemStack is not a bundle");
|
||||
|
||||
BundleMeta meta = (BundleMeta) bundle.getItemMeta();
|
||||
return meta.getItems().stream()
|
||||
.map(ItemStack::clone)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(
|
||||
new OnBundlePickListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalBundlePicker;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.inventory.InventoryAction;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
class OnBundlePickListener extends ApplianceListener<AntiIllegalBundlePicker> {
|
||||
@EventHandler
|
||||
public void onBundlePick(InventoryClickEvent event) {
|
||||
if(!event.getAction().equals(InventoryAction.PICKUP_FROM_BUNDLE)) return;
|
||||
final ItemStack bundle = event.getCurrentItem();
|
||||
if (bundle == null || bundle.getType() != Material.BUNDLE) return;
|
||||
this.getAppliance().trackBundle(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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;
|
||||
|
||||
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,21 @@
|
||||
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.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
|
||||
class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> {
|
||||
@EventHandler
|
||||
public void onOpen(InventoryOpenEvent event) {
|
||||
if(!(event.getPlayer() instanceof Player player)) 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.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AcInform extends Appliance {
|
||||
private final Map<String, Map<String, Long>> violationSlowdowns = new ConcurrentHashMap<>();
|
||||
|
||||
public void processCommand(@NotNull String[] args) {
|
||||
String anticheatName = null;
|
||||
String playerName = null;
|
||||
String checkName = null;
|
||||
Float violationCount = null;
|
||||
int notifyEvery = 0;
|
||||
|
||||
for(int i = 0; i < args.length; i++) {
|
||||
if(!args[i].startsWith("--")) continue;
|
||||
@@ -36,13 +42,32 @@ public class AcInform extends Appliance {
|
||||
case "--playerName" -> playerName = value;
|
||||
case "--check" -> checkName = 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);
|
||||
}
|
||||
|
||||
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();
|
||||
NamedTextColor textColor = NamedTextColor.GRAY;
|
||||
|
||||
@@ -85,28 +110,42 @@ public class AcInform extends Appliance {
|
||||
Component.newline()
|
||||
.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))
|
||||
.clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
|
||||
);
|
||||
|
||||
component.append(
|
||||
Component.text(" [", NamedTextColor.GRAY)
|
||||
.append(Component.text("Kick", NamedTextColor.GOLD))
|
||||
.append(Component.text("\u23F1", NamedTextColor.GOLD))
|
||||
.append(Component.text("]", NamedTextColor.GRAY))
|
||||
.clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
|
||||
);
|
||||
|
||||
component.append(
|
||||
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))
|
||||
.clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName)))
|
||||
);
|
||||
|
||||
component.append(
|
||||
Component.text(" [", NamedTextColor.GRAY)
|
||||
.append(Component.text("Spectate/Teleport", NamedTextColor.GOLD))
|
||||
.append(Component.text("\uD83D\uDC41", NamedTextColor.GOLD))
|
||||
.append(Component.text("]", NamedTextColor.GRAY))
|
||||
.clickEvent(ClickEvent.suggestCommand(String.format("/grim spectate %s", playerName)))
|
||||
);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import net.kyori.adventure.resource.ResourcePackInfo;
|
||||
import net.kyori.adventure.resource.ResourcePackRequest;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import spark.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
@Appliance.Flags(enabled = false)
|
||||
public class DeviceFingerprinting extends Appliance {
|
||||
public record PackInfo(@NotNull String url, @NotNull UUID uuid, @NotNull String hash) {
|
||||
private static final String failingUrl = "http://127.0.0.1:0";
|
||||
public PackInfo asFailing() {
|
||||
return new PackInfo(failingUrl, this.uuid, this.hash);
|
||||
}
|
||||
}
|
||||
|
||||
public enum PackStatus {
|
||||
UNCACHED,
|
||||
CACHED,
|
||||
INVALID;
|
||||
|
||||
public static PackStatus fromBukkitStatus(PlayerResourcePackStatusEvent.Status status) {
|
||||
return switch(status) {
|
||||
case DISCARDED -> CACHED;
|
||||
case FAILED_DOWNLOAD -> UNCACHED;
|
||||
default -> INVALID;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum PlayerStatus {
|
||||
PREPARATION,
|
||||
TESTING,
|
||||
FINISHED,
|
||||
NEW
|
||||
}
|
||||
|
||||
private List<PackInfo> packs;
|
||||
private final Map<Player, FingerprintData> fingerprints = new WeakHashMap<>();
|
||||
private final UUID basePackId = UUID.randomUUID();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.packs = this.readPacksFromConfig();
|
||||
}
|
||||
|
||||
public void startFingerprinting(Player player) {
|
||||
this.fingerprints.put(player, FingerprintData.create(player));
|
||||
Main.logger().info(String.format("Sending base ressource-pack with id '%s' to '%s'%n", this.basePackId, player.getName()));
|
||||
this.sendPack(player, new PackInfo("http://localhost:8080/api/devicefingerprinting/base.zip", this.basePackId, "3296e8bdd30b4f7cffd11c780a1dc70da2948e71"));
|
||||
}
|
||||
|
||||
public void onPackUpdate(Player player, UUID packId, PlayerResourcePackStatusEvent.Status status) {
|
||||
if(!this.fingerprints.containsKey(player)) return;
|
||||
FingerprintData playerFingerprint = this.fingerprints.get(player);
|
||||
if(!playerFingerprint.isInTestingOrPreparation()) return;
|
||||
|
||||
if(packId.equals(this.basePackId)) {
|
||||
Main.logger().info(String.format("Base pack for '%s' updated: '%s'", player.getName(), status));
|
||||
|
||||
if(status != PlayerResourcePackStatusEvent.Status.ACCEPTED) return;
|
||||
Main.logger().info(String.format("Base pack loaded successfully, sending now all packs to '%s'...", player.getName()));
|
||||
playerFingerprint.setTesting();
|
||||
this.packs.forEach(pack -> this.sendPack(player, pack.asFailing()));
|
||||
return;
|
||||
}
|
||||
|
||||
PackInfo pack = this.packs.stream()
|
||||
.filter(packInfo -> Objects.equals(packInfo.uuid, packId))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if(pack == null) return;
|
||||
int packIndex = this.packs.indexOf(pack);
|
||||
|
||||
List<PackStatus> pendingPacks = playerFingerprint.getPendingPacks();
|
||||
PackStatus newPackStatus = PackStatus.fromBukkitStatus(status);
|
||||
if(newPackStatus == PackStatus.INVALID) return;
|
||||
pendingPacks.set(packIndex, newPackStatus);
|
||||
|
||||
playerFingerprint.updateFingerprint();
|
||||
if(Objects.requireNonNull(playerFingerprint.getStatus()) == PlayerStatus.NEW) {
|
||||
Main.logger().info(String.format("Sending fingerprint packs to Player '%s', as it is a unseen Player!", player.getName()));
|
||||
this.sendNewFingerprint(player, Objects.requireNonNull(playerFingerprint.getFingerPrint()));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNewFingerprint(Player player, long fingerprintId) {
|
||||
for (int i = 0; i < this.packs.size(); i++) {
|
||||
if ((fingerprintId & (1L << i)) != 0) {
|
||||
PackInfo pack = this.packs.get(i);
|
||||
this.sendPack(player, pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPack(Player player, PackInfo pack) {
|
||||
player.sendResourcePacks(
|
||||
ResourcePackRequest.resourcePackRequest()
|
||||
.required(true)
|
||||
.packs(ResourcePackInfo.resourcePackInfo(pack.uuid, URI.create(pack.url), pack.hash))
|
||||
);
|
||||
}
|
||||
|
||||
private List<DeviceFingerprinting.PackInfo> readPacksFromConfig() {
|
||||
try (InputStreamReader reader = new InputStreamReader(Objects.requireNonNull(Main.class.getResourceAsStream("/deviceFingerprinting/packs.json")))) {
|
||||
Type packListType = new TypeToken<List<DeviceFingerprinting.PackInfo>>(){}.getType();
|
||||
List<DeviceFingerprinting.PackInfo> packs = new Gson().fromJson(reader, packListType);
|
||||
if (packs.isEmpty()) throw new IllegalStateException("No resource packs found in packs.json.");
|
||||
return packs;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to parse packs.json.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
apiBuilder.rawGet(
|
||||
"base.zip",
|
||||
(request, response) -> this.servePack("base.zip", response)
|
||||
);
|
||||
|
||||
for(int i = 0; i < this.packs.size(); i++) {
|
||||
int packIndex = i;
|
||||
apiBuilder.rawGet(
|
||||
String.format("packs/%d", i),
|
||||
(request, response) -> this.servePack(String.valueOf(packIndex), response)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Object servePack(String name, Response response) {
|
||||
try {
|
||||
String resourcePath = String.format("/deviceFingerprinting/packs/%s", name);
|
||||
var inputStream = Main.class.getResourceAsStream(resourcePath);
|
||||
|
||||
if (inputStream == null) {
|
||||
throw new IllegalStateException("Pack file not found: " + resourcePath);
|
||||
}
|
||||
|
||||
response.header("Content-Type", "application/zip");
|
||||
response.header("Content-Disposition", String.format("attachment; filename=\"pack-%s.zip\"", name));
|
||||
|
||||
var outputStream = response.raw().getOutputStream();
|
||||
inputStream.transferTo(outputStream);
|
||||
outputStream.close();
|
||||
|
||||
return HttpServer.nothing;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(String.format("Failed to serve pack '%s'", name), e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PackInfo> getPacks() {
|
||||
return this.packs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(
|
||||
new PlayerJoinListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
class FingerprintData {
|
||||
public final Player player;
|
||||
private DeviceFingerprinting.PlayerStatus status;
|
||||
private @Nullable Long fingerPrint;
|
||||
private final List<DeviceFingerprinting.PackStatus> pendingPacks;
|
||||
int packCount = Main.instance().getAppliance(DeviceFingerprinting.class).getPacks().size();
|
||||
|
||||
private FingerprintData(Player player) {
|
||||
this.player = player;
|
||||
this.status = DeviceFingerprinting.PlayerStatus.PREPARATION;
|
||||
this.fingerPrint = null;
|
||||
this.pendingPacks = Arrays.asList(new DeviceFingerprinting.PackStatus[this.packCount]);
|
||||
}
|
||||
|
||||
public static FingerprintData create(Player player) {
|
||||
return new FingerprintData(player);
|
||||
}
|
||||
|
||||
public void setTesting() {
|
||||
this.status = DeviceFingerprinting.PlayerStatus.TESTING;
|
||||
}
|
||||
|
||||
public void updateFingerprint() {
|
||||
long fingerPrint = 0;
|
||||
for (int i = 0; i < this.pendingPacks.size(); i++) {
|
||||
var status = this.pendingPacks.get(i);
|
||||
if(status == null) return;
|
||||
switch (status) {
|
||||
case CACHED:
|
||||
fingerPrint |= 1L << i;
|
||||
break;
|
||||
case UNCACHED:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(fingerPrint == 0) {
|
||||
this.status = DeviceFingerprinting.PlayerStatus.NEW;
|
||||
this.fingerPrint = this.createNewFingerprint();
|
||||
Main.logger().info(String.format("Player %s's was marked as a new Player!", this.player.getName()));
|
||||
} else {
|
||||
this.status = DeviceFingerprinting.PlayerStatus.FINISHED;
|
||||
this.fingerPrint = fingerPrint;
|
||||
}
|
||||
|
||||
Main.logger().info(String.format("Player %s's fingerprint is '%s'", this.player.getName(), fingerPrint));
|
||||
}
|
||||
|
||||
private long createNewFingerprint() {
|
||||
long id = 0;
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < this.packCount / 2; i++) {
|
||||
while (true) {
|
||||
int bitIndex = random.nextInt(this.packCount);
|
||||
if ((id & (1L << bitIndex)) == 0) {
|
||||
id |= 1L << bitIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<DeviceFingerprinting.PackStatus> getPendingPacks() {
|
||||
return this.pendingPacks;
|
||||
}
|
||||
|
||||
public DeviceFingerprinting.PlayerStatus getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public @Nullable Long getFingerPrint() {
|
||||
return this.fingerPrint;
|
||||
}
|
||||
|
||||
public boolean isInTestingOrPreparation() {
|
||||
return this.status == DeviceFingerprinting.PlayerStatus.TESTING || this.status == DeviceFingerprinting.PlayerStatus.PREPARATION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
|
||||
|
||||
class PlayerJoinListener extends ApplianceListener<DeviceFingerprinting> {
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
this.getAppliance().startFingerprinting(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onResourcePackEvent(PlayerResourcePackStatusEvent event) {
|
||||
this.getAppliance().onPackUpdate(
|
||||
event.getPlayer(),
|
||||
event.getID(),
|
||||
event.getStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
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.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
|
||||
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.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -25,9 +30,36 @@ public class Kick extends Appliance {
|
||||
).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
|
||||
@NotNull
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
5
common/src/main/resources/deviceFingerprinting/README.md
Normal file
5
common/src/main/resources/deviceFingerprinting/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## Files originally from "TrackPack"
|
||||
https://github.com/ALaggyDev/TrackPack/blob/main/README.md
|
||||
|
||||
|
||||
Discovered by: [Laggy](https://github.com/ALaggyDev/) and [NikOverflow](https://github.com/NikOverflow)
|
||||
33
common/src/main/resources/deviceFingerprinting/gen_packs.py
Normal file
33
common/src/main/resources/deviceFingerprinting/gen_packs.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import zipfile
|
||||
import hashlib
|
||||
import uuid
|
||||
import json
|
||||
|
||||
SERVER_URL = "http://localhost:8080/api/devicefingerprinting"
|
||||
packs = []
|
||||
|
||||
def file_sha1(path):
|
||||
h = hashlib.sha1()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
for i in range(0, 24):
|
||||
path = f"packs/{i}"
|
||||
|
||||
with zipfile.ZipFile(path, mode="w") as zf:
|
||||
zf.writestr(
|
||||
"pack.mcmeta",
|
||||
'{"pack":{"pack_format":22,"supported_formats":[22,1000],"description":"pack ' + str(i) + '"}}',
|
||||
)
|
||||
|
||||
hash = file_sha1(path)
|
||||
packs.append({
|
||||
"url": f"{SERVER_URL}/packs/{i}",
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"hash": hash
|
||||
})
|
||||
|
||||
with open("packs.json", "w") as f:
|
||||
json.dump(packs, f, indent=4)
|
||||
122
common/src/main/resources/deviceFingerprinting/packs.json
Normal file
122
common/src/main/resources/deviceFingerprinting/packs.json
Normal file
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/0",
|
||||
"uuid": "b35f6e2f-1b50-4493-85be-fb18bd90f9bb",
|
||||
"hash": "7a39af839ea6484431f7b707759546bea991d435"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/1",
|
||||
"uuid": "71095b62-d5ef-4ab2-ba3b-3c1b403f5e34",
|
||||
"hash": "a9192ee73df1c5cff2c188fac6e9e638a1e7b6ce"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/2",
|
||||
"uuid": "a4dba0a2-f8f2-4a81-bbb2-a9a818820330",
|
||||
"hash": "6b85b0eb54865dae70bbda89746d83717dc2a214"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/3",
|
||||
"uuid": "79fa2dc4-8c84-45fc-a09f-d89906f0d900",
|
||||
"hash": "c7abf7a316f7e8c98985e8317a8b649e824e9f79"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/4",
|
||||
"uuid": "15702c9b-a22b-426d-b48a-3d65b0368e9a",
|
||||
"hash": "10cd0e2c46f192deb87ac75c149827d44a713017"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/5",
|
||||
"uuid": "3d702d41-8e2f-4920-8dd0-1fd2146da9fb",
|
||||
"hash": "8ad517d259e800b88a38ff00ee6721d5656822f2"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/6",
|
||||
"uuid": "c20a2e47-ef43-49da-a80d-adf238df3695",
|
||||
"hash": "798677405a4fd678892e1cf55585c8c91f82e1e2"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/7",
|
||||
"uuid": "7ce51b81-1263-4919-9f4e-bb749ffe6e2e",
|
||||
"hash": "af473b8eb7572f35d307bede5f2e20f263c0d804"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/8",
|
||||
"uuid": "0c70d586-fe48-4ffc-86b0-6b9ec3bfe045",
|
||||
"hash": "2fb698ff88f2436637641f3b2e6792201feb5144"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/9",
|
||||
"uuid": "c7af75a8-0b72-495d-a0ff-c1c40e229c13",
|
||||
"hash": "cf660460798eecf451d639873cc1fedc4661db1b"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/10",
|
||||
"uuid": "248dbce6-4b2a-44b5-b038-8d718b0ced99",
|
||||
"hash": "a8ebe708d0f3747c76e4e5e68db5dcb561922706"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/11",
|
||||
"uuid": "10979174-cb02-40eb-a754-275551ad608d",
|
||||
"hash": "54961b48db1582a1a0981c8cc9be5ae0f3122cf3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/12",
|
||||
"uuid": "a361cfa7-674c-4493-a4cf-4baff851f276",
|
||||
"hash": "013719dc8da79c96b45a1c5319c20bffe1a56cc9"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/13",
|
||||
"uuid": "24b39bdb-ada9-40ec-9e3a-132c74b81dc6",
|
||||
"hash": "206898c6b6600d2648b2d79c61fc6255b19587d9"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/14",
|
||||
"uuid": "158fc5b4-be2c-4f7a-98cb-af5993adcc90",
|
||||
"hash": "061b266a7c526fb3a3152a4ea70ca5592e0b503c"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/15",
|
||||
"uuid": "4f9097a7-be02-48ad-919c-f292307f8490",
|
||||
"hash": "45a667a0fe06246defabca14ef1271fb6db5a1ac"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/16",
|
||||
"uuid": "3ce31e60-7e8a-4fb1-8c6d-da9065bea798",
|
||||
"hash": "75bb12e46203d49e89aa9a826d267552372758bc"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/17",
|
||||
"uuid": "cd978e5c-3de0-4ada-8ec5-3a88a305eec6",
|
||||
"hash": "5b20261f7be03e83e9c52307f1408b0c5e58358c"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/18",
|
||||
"uuid": "75001e58-3999-4779-a1d1-43ab161770ce",
|
||||
"hash": "544420cffb6c17113c06fb49eeba892c208719d3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/19",
|
||||
"uuid": "6a7005a9-c2ca-476d-9a12-07d120ee121a",
|
||||
"hash": "fcc066a4d3193b60b102e3d906ad8dc0b0fcf65b"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/20",
|
||||
"uuid": "521c0d84-d82e-49ef-b096-d9b90f15aa19",
|
||||
"hash": "4545835983ec7f07d02675a69181a80dc396f038"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/21",
|
||||
"uuid": "c1b590c5-43fc-41e3-83c0-47f35b14f845",
|
||||
"hash": "8d4c670eaefc0482734e839b72758226dde13bc3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/22",
|
||||
"uuid": "43958a18-c087-4f2b-a6ea-066231606eb1",
|
||||
"hash": "004282602f7bdbb7cd7724f23aae23876f224092"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/23",
|
||||
"uuid": "4b91ac81-9de4-4c2b-a876-47e621496d10",
|
||||
"hash": "dae68eae109e08ea4c4c943905502eb331939f64"
|
||||
}
|
||||
]
|
||||
1
common/src/main/resources/deviceFingerprinting/packs/.gitignore
vendored
Normal file
1
common/src/main/resources/deviceFingerprinting/packs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!base.zip
|
||||
BIN
common/src/main/resources/deviceFingerprinting/packs/0
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/0
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/1
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/1
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/10
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/10
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/11
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/11
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/12
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/12
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/13
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/13
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/14
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/14
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/15
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/15
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/16
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/16
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/17
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/17
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/18
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/18
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/19
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/19
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/2
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/2
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/20
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/20
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/21
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/21
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/22
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/22
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/23
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/23
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/3
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/3
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/4
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/4
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/5
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/5
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/6
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/6
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/7
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/7
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/8
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/8
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/9
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/9
Normal file
Binary file not shown.
BIN
common/src/main/resources/deviceFingerprinting/packs/base.zip
Normal file
BIN
common/src/main/resources/deviceFingerprinting/packs/base.zip
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT'
|
||||
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'
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
@@ -49,8 +50,13 @@ public final class Main extends JavaPlugin {
|
||||
Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
|
||||
|
||||
Main.logger().info("Loading appliances...");
|
||||
this.appliances = this.findSubtypesOf(Appliance.class).stream()
|
||||
this.appliances = new ArrayList<>(this.findSubtypesOf(Appliance.class).stream()
|
||||
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
|
||||
.filter(appliance -> {
|
||||
Appliance.Flags flags = appliance.getAnnotation(Appliance.Flags.class);
|
||||
if(flags == null) return true;
|
||||
return flags.enabled();
|
||||
})
|
||||
.map(applianceClass -> {
|
||||
try {
|
||||
return (Appliance) applianceClass.getDeclaredConstructor().newInstance();
|
||||
@@ -58,14 +64,17 @@ public final class Main extends JavaPlugin {
|
||||
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("Initializing appliances...");
|
||||
this.appliances.forEach(appliance -> {
|
||||
appliance.onEnable();
|
||||
appliance.initialize(this);
|
||||
});
|
||||
this.appliances.stream()
|
||||
.filter(appliance -> {
|
||||
Appliance.Flags flags = appliance.getClass().getAnnotation(Appliance.Flags.class);
|
||||
if(flags == null) return true;
|
||||
return flags.autoload();
|
||||
})
|
||||
.forEach(appliance -> appliance.enableSequence(this));
|
||||
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
|
||||
|
||||
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
|
||||
@@ -80,17 +89,34 @@ public final class Main extends JavaPlugin {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Main.logger().info("Disabling appliances...");
|
||||
this.appliances.forEach(appliance -> {
|
||||
Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
|
||||
appliance.onDisable();
|
||||
appliance.destruct(this);
|
||||
});
|
||||
this.appliances.forEach(appliance -> appliance.disableSequence(this));
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
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) {
|
||||
return this.appliances.stream()
|
||||
.filter(clazz::isInstance)
|
||||
|
||||
@@ -2,11 +2,13 @@ package eu.mhsl.craftattack.spawn.core.api.server;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.hooks.HttpHook;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import spark.Request;
|
||||
import spark.Spark;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -21,6 +23,11 @@ public class HttpServer {
|
||||
|
||||
Spark.get("/ping", (request, response) -> System.currentTimeMillis());
|
||||
|
||||
Spark.post("/hook/:hookId", (request, response) -> {
|
||||
HttpHook.Hook hook = HttpHook.Hook.valueOf(request.params(":hookId").toUpperCase());
|
||||
return hook.getHook().runAction(request.headers(hook.getHeaderAction()), request, response);
|
||||
});
|
||||
|
||||
Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance)));
|
||||
}
|
||||
|
||||
@@ -43,12 +50,16 @@ public class HttpServer {
|
||||
this.applianceName = appliance.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
public void rawGet(String path, BiFunction<Request, spark.Response, Object> onCall) {
|
||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req, resp)));
|
||||
}
|
||||
|
||||
public void get(String path, Function<Request, Object> onCall) {
|
||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
}
|
||||
|
||||
public void rawPost(String path, Function<Request, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
public void rawPost(String path, BiFunction<Request, spark.Response, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req, resp)));
|
||||
}
|
||||
|
||||
public <TRequest> void post(String path, Class<TRequest> clazz, RequestProvider<TRequest, Request, Object> onCall) {
|
||||
@@ -59,7 +70,7 @@ public class HttpServer {
|
||||
});
|
||||
}
|
||||
|
||||
public String buildRoute(String path) {
|
||||
private String buildRoute(String path) {
|
||||
return String.format("/api/%s/%s", this.applianceName, path);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package eu.mhsl.craftattack.spawn.core.api.server.hooks;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.hooks.impl.WebsiteHook;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class HttpHook {
|
||||
public enum Hook {
|
||||
WEBSITE("x-webhook-action", new WebsiteHook());
|
||||
|
||||
private final String headerAction;
|
||||
private final HttpHook hook;
|
||||
Hook(String headerAction, HttpHook handler) {
|
||||
this.headerAction = headerAction;
|
||||
this.hook = handler;
|
||||
this.hook.registerHooks();
|
||||
}
|
||||
|
||||
public HttpHook getHook() {
|
||||
return this.hook;
|
||||
}
|
||||
|
||||
public String getHeaderAction() {
|
||||
return this.headerAction;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class Action {
|
||||
public abstract Object run(Request request, Response response);
|
||||
}
|
||||
|
||||
private final Map<String, Action> actions = new HashMap<>();
|
||||
|
||||
protected HttpHook() {
|
||||
}
|
||||
|
||||
protected abstract void registerHooks();
|
||||
|
||||
protected void addAction(String name, Action action) {
|
||||
this.actions.put(name, action);
|
||||
}
|
||||
|
||||
public Object runAction(String action, Request request, Response response) {
|
||||
if(!this.actions.containsKey(action)) {
|
||||
Main.logger().warning(String.format("Webhook-Action '%s' not registered, skipping!", action));
|
||||
return HttpServer.nothing;
|
||||
}
|
||||
return this.actions.get(action).run(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package eu.mhsl.craftattack.spawn.core.api.server.hooks;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class JsonAction<TRequest, TResponse> extends HttpHook.Action {
|
||||
private final Function<TRequest, TResponse> handler;
|
||||
private final Class<TRequest> requestClass;
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public JsonAction(Class<TRequest> requestClass, Function<TRequest, TResponse> handler) {
|
||||
this.requestClass = requestClass;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run(Request request, Response response) {
|
||||
TRequest req = this.gson.fromJson(request.body(), this.requestClass);
|
||||
response.type("application/json");
|
||||
return this.gson.toJson(this.handler.apply(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package eu.mhsl.craftattack.spawn.core.api.server.hooks;
|
||||
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class RawAction extends HttpHook.Action {
|
||||
private final BiFunction<Request, Response, Object> handler;
|
||||
public RawAction(BiFunction<Request, Response, Object> handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run(Request request, Response response) {
|
||||
return this.handler.apply(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package eu.mhsl.craftattack.spawn.core.api.server.hooks.impl;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.hooks.HttpHook;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.hooks.JsonAction;
|
||||
import eu.mhsl.craftattack.spawn.core.event.ReportCreatedEvent;
|
||||
import eu.mhsl.craftattack.spawn.core.event.SpawnEvent;
|
||||
import eu.mhsl.craftattack.spawn.core.event.StrikeCreatedEvent;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WebsiteHook extends HttpHook {
|
||||
@Override
|
||||
protected void registerHooks() {
|
||||
record CreatedSignup(
|
||||
String firstname,
|
||||
String lastname,
|
||||
String birthday,
|
||||
@Nullable String telephone,
|
||||
String username,
|
||||
String edition,
|
||||
@Nullable UUID uuid
|
||||
) {}
|
||||
this.addAction("signup", new JsonAction<>(CreatedSignup.class, createdSignup -> {
|
||||
Main.logger().info(String.format("New Website-signup from Hook: %s %s (%s)", createdSignup.firstname, createdSignup.lastname, createdSignup.username));
|
||||
return HttpServer.nothing;
|
||||
}));
|
||||
|
||||
record CreatedReport(String reporter, String reported, String reason) {}
|
||||
this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> {
|
||||
SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason)));
|
||||
return HttpServer.nothing;
|
||||
}));
|
||||
|
||||
record CreatedStrike(UUID uuid) {}
|
||||
this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> {
|
||||
SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid)));
|
||||
return HttpServer.nothing;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,6 +25,12 @@ import java.util.Optional;
|
||||
* Appliances can be enabled or disabled independent of other appliances
|
||||
*/
|
||||
public abstract class Appliance {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Flags {
|
||||
boolean enabled() default true;
|
||||
boolean autoload() default true;
|
||||
}
|
||||
|
||||
private String localConfigPath;
|
||||
private List<Listener> listeners;
|
||||
private List<ApplianceCommand<?>> commands;
|
||||
@@ -94,9 +102,20 @@ public abstract class Appliance {
|
||||
}
|
||||
|
||||
public void destruct(@NotNull JavaPlugin plugin) {
|
||||
if(this.listeners == null) return;
|
||||
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) {
|
||||
return Main.instance().getAppliance(clazz);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package eu.mhsl.craftattack.spawn.core.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReportCreatedEvent extends Event {
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public record CreatedReport(String reporter, String reported, String reason) {}
|
||||
|
||||
private final CreatedReport report;
|
||||
public ReportCreatedEvent(CreatedReport report) {
|
||||
super(true);
|
||||
this.report = report;
|
||||
}
|
||||
|
||||
public CreatedReport getReport() {
|
||||
return this.report;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package eu.mhsl.craftattack.spawn.core.event;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Event;
|
||||
|
||||
public abstract class SpawnEvent {
|
||||
public static void call(Event event) {
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package eu.mhsl.craftattack.spawn.core.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class StrikeCreatedEvent extends Event {
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public record CreatedStrike(UUID playerToStrike) {}
|
||||
|
||||
private final CreatedStrike strike;
|
||||
public StrikeCreatedEvent(CreatedStrike strike) {
|
||||
super(true);
|
||||
this.strike = strike;
|
||||
}
|
||||
|
||||
public CreatedStrike getStrike() {
|
||||
return this.strike;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package eu.mhsl.craftattack.spawn.core.util.entity;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayerUtils {
|
||||
public static void resetStatistics(Player player) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import eu.mhsl.craftattack.spawn.core.util.statistics.ServerMonitor;
|
||||
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.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -19,7 +20,12 @@ import java.util.stream.Stream;
|
||||
|
||||
public class ComponentUtil {
|
||||
public static TextComponent pleaseWait() {
|
||||
return Component.text("Bitte warte einen Augenblick...", NamedTextColor.GRAY);
|
||||
return Component.text("\uD83D\uDCBE Daten werden geladen... Warte einen Augenblick!", NamedTextColor.GRAY);
|
||||
}
|
||||
|
||||
public static TextComponent clickLink(String url) {
|
||||
return Component.text("Klicke, um zu öffnen: \uD83D\uDD17[%s]".formatted(url))
|
||||
.clickEvent(ClickEvent.openUrl(url));
|
||||
}
|
||||
|
||||
public static Component clearedSpace() {
|
||||
|
||||
@@ -2,7 +2,7 @@ dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':common')
|
||||
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT'
|
||||
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,13 +1,10 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.api.repositories;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FeedbackRepository extends HttpRepository {
|
||||
@@ -15,14 +12,15 @@ public class FeedbackRepository extends HttpRepository {
|
||||
super(CraftAttackApi.getBaseUri(), new RequestModifier(null, CraftAttackApi::withAuthorizationHeader));
|
||||
}
|
||||
|
||||
public record Request(String event, List<UUID> users) {
|
||||
public record Request(String event, String title, List<UUID> users) {
|
||||
}
|
||||
|
||||
public ReqResp<Map<UUID, String>> createFeedbackUrls(Request data) {
|
||||
final Type responseType = new TypeToken<Map<UUID, String>>() {
|
||||
}.getType();
|
||||
ReqResp<Object> rawData = this.post("feedback", data, Object.class);
|
||||
// TODO: use convertToTypeToken from ReqResp
|
||||
return new ReqResp<>(rawData.status(), this.gson.fromJson(this.gson.toJson(rawData.data()), responseType));
|
||||
public record Response(List<Feedback> feedback) {
|
||||
public record Feedback(UUID uuid, String url) {
|
||||
}
|
||||
}
|
||||
|
||||
public ReqResp<Response> createFeedbackUrls(Request data) {
|
||||
return this.post("feedback", data, Response.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
|
||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WhitelistRepository extends HttpRepository {
|
||||
@@ -16,17 +17,15 @@ public class WhitelistRepository extends HttpRepository {
|
||||
String username,
|
||||
String firstname,
|
||||
String lastname,
|
||||
Long banned_until,
|
||||
Long outlawed_until
|
||||
List<Strike> strikes
|
||||
) {
|
||||
public record Strike(long at, int weight) {
|
||||
}
|
||||
}
|
||||
|
||||
private record UserQuery(UUID uuid) {}
|
||||
|
||||
public ReqResp<UserData> getUserData(UUID userId) {
|
||||
return this.post(
|
||||
"player",
|
||||
new UserQuery(userId),
|
||||
return this.get(
|
||||
"users/%s".formatted(userId.toString()),
|
||||
UserData.class
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ 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.TextDecoration;
|
||||
import net.kyori.adventure.util.Ticks;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
@@ -30,44 +31,45 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class Bloodmoon extends Appliance {
|
||||
// für alle Dimensionen? einstellbar machen?
|
||||
|
||||
public final Map<EntityType, Set<MobEffect>> affectedMobEffectMap = Map.of(
|
||||
EntityType.ZOMBIE, Set.of(
|
||||
public final Map<EntityType, Set<MobEffect>> affectedMobEffectMap = Map.ofEntries(
|
||||
Map.entry(EntityType.ZOMBIE, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||
),
|
||||
EntityType.SKELETON, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.SKELETON, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 3.5, 1)
|
||||
),
|
||||
EntityType.SPIDER, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.SPIDER, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.POISON, 4, 1),
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.NAUSEA, 6, 10)
|
||||
),
|
||||
EntityType.CREEPER, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.CREEPER, Set.of(
|
||||
new MobEffect.LightningMobEffect()
|
||||
),
|
||||
EntityType.HUSK, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.HUSK, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||
),
|
||||
EntityType.DROWNED, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.STRAY, Set.of()),
|
||||
Map.entry(EntityType.DROWNED, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||
),
|
||||
EntityType.WITCH, Set.of(),
|
||||
EntityType.ZOMBIE_VILLAGER, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.WITCH, Set.of()),
|
||||
Map.entry(EntityType.ZOMBIE_VILLAGER, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||
),
|
||||
EntityType.PHANTOM, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.PHANTOM, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.LEVITATION, 1.5, 3)
|
||||
),
|
||||
EntityType.ENDERMAN, Set.of(
|
||||
)),
|
||||
Map.entry(EntityType.ENDERMAN, Set.of(
|
||||
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 2.5, 2)
|
||||
)
|
||||
))
|
||||
);
|
||||
public final int expMultiplier = 4;
|
||||
public final double mobDamageMultipier = 2;
|
||||
public final double mobHealthMultiplier = 3;
|
||||
public final int expMultiplier = 3;
|
||||
public final double mobDamageMultiplier = 2;
|
||||
public final double mobHealthMultiplier = 2;
|
||||
|
||||
private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
private boolean isActive = false;
|
||||
private final BossBar bossBar = BossBar.bossBar(
|
||||
Component.text("Blutmond", NamedTextColor.DARK_RED),
|
||||
@@ -77,8 +79,8 @@ public class Bloodmoon extends Appliance {
|
||||
Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN)
|
||||
);
|
||||
private final boolean hordesEnabled = true;
|
||||
private final int hordeSpawnRateTicks = 800;
|
||||
private final int hordeSpawnRateVariationTicks = 800;
|
||||
private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND;
|
||||
private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND;
|
||||
private final int hordeMinPopulation = 3;
|
||||
private final int hordeMaxPopulation = 10;
|
||||
private final int hordeSpawnDistance = 10;
|
||||
@@ -87,12 +89,14 @@ public class Bloodmoon extends Appliance {
|
||||
EntityType.SKELETON,
|
||||
EntityType.SPIDER
|
||||
);
|
||||
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new HashMap<>();
|
||||
public final int bloodmoonLength = 12000;
|
||||
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
|
||||
private long lastBloodmoonStartTick = 0;
|
||||
public final int ticksPerDay = 24000;
|
||||
public final int bloodmoonLength = this.ticksPerDay /2;
|
||||
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
|
||||
private final int bloodmoonFreeDaysAtStart = 3;
|
||||
private final int bloodmoonStartTime = 12000;
|
||||
private final int bloodmoonDayInterval = 2;
|
||||
private final int bloodmoonStartTime = this.ticksPerDay /2;
|
||||
private final int bloodmoonDayInterval = 30;
|
||||
private final int minBonusDrops = 1;
|
||||
private final int maxBonusDrops = 4;
|
||||
private final Map<ItemStack, Integer> bonusDropWeightMap = Map.of(
|
||||
@@ -112,7 +116,8 @@ public class Bloodmoon extends Appliance {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public void startBloodmoon() {
|
||||
public void startBloodmoon(long startTick) {
|
||||
this.lastBloodmoonStartTick = startTick;
|
||||
this.isActive = true;
|
||||
Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar);
|
||||
this.startHordeSpawning(this.getRandomHordeSpawnDelay());
|
||||
@@ -125,6 +130,14 @@ public class Bloodmoon extends Appliance {
|
||||
this.sendStopMessages();
|
||||
}
|
||||
|
||||
public void updateBossBar() {
|
||||
long tick = Bukkit.getWorlds().getFirst().getFullTime();
|
||||
long sinceStart = tick - this.lastBloodmoonStartTick;
|
||||
float progress = 1f - ((float) sinceStart / this.bloodmoonLength);
|
||||
if(progress < 0) progress = 1f;
|
||||
this.bossBar.progress(progress);
|
||||
}
|
||||
|
||||
public boolean isStartTick(long tick) {
|
||||
long day = tick / this.ticksPerDay;
|
||||
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false;
|
||||
@@ -133,7 +146,7 @@ public class Bloodmoon extends Appliance {
|
||||
}
|
||||
|
||||
private int getRandomHordeSpawnDelay() {
|
||||
return this.hordeSpawnRateTicks + ThreadLocalRandom.current().nextInt(this.hordeSpawnRateVariationTicks);
|
||||
return this.hordeSpawnRateTicks + this.random.nextInt(this.hordeSpawnRateVariationTicks);
|
||||
}
|
||||
|
||||
private void startHordeSpawning(int delay) {
|
||||
@@ -143,16 +156,16 @@ public class Bloodmoon extends Appliance {
|
||||
private void startHordeSpawning(int delay, Player player) {
|
||||
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
|
||||
if(task != null) task.cancel();
|
||||
task = Bukkit.getScheduler().runTaskLater(
|
||||
BukkitTask newTask = Bukkit.getScheduler().runTaskLater(
|
||||
Main.instance(),
|
||||
() -> {
|
||||
if(!this.bloodmoonIsActive()) return;
|
||||
this.spawnRandomHorde();
|
||||
this.spawnRandomHorde(player);
|
||||
this.startHordeSpawning(this.getRandomHordeSpawnDelay(), player);
|
||||
},
|
||||
delay
|
||||
);
|
||||
this.hordeSpawnTasks.put(player, task);
|
||||
this.hordeSpawnTasks.put(player, newTask);
|
||||
}
|
||||
|
||||
public void addPlayerToBossBar(Player player) {
|
||||
@@ -172,17 +185,12 @@ public class Bloodmoon extends Appliance {
|
||||
);
|
||||
}
|
||||
|
||||
public void spawnRandomHorde() {
|
||||
if(Bukkit.getOnlinePlayers().isEmpty()) return;
|
||||
public void spawnRandomHorde(Player player) {
|
||||
if(!this.hordesEnabled) return;
|
||||
List<? extends Player> onlinePlayerList = Bukkit.getOnlinePlayers().stream()
|
||||
.filter(this::getBloodmoonSetting)
|
||||
.filter(player -> player.getGameMode().equals(GameMode.SURVIVAL))
|
||||
.toList();
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
Player player = onlinePlayerList.get(random.nextInt(onlinePlayerList.size()));
|
||||
EntityType hordeEntityType = this.hordeMobList.get(random.nextInt(this.hordeMobList.size()));
|
||||
int hordeSize = random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
|
||||
if(!player.getGameMode().equals(GameMode.SURVIVAL)) return;
|
||||
|
||||
EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size()));
|
||||
int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
|
||||
this.spawnHorde(player, hordeSize, hordeEntityType);
|
||||
}
|
||||
|
||||
@@ -200,7 +208,7 @@ public class Bloodmoon extends Appliance {
|
||||
|
||||
private void spawnHorde(Player player, int size, EntityType type) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
double spawnRadiant = ThreadLocalRandom.current().nextDouble(0, 2*Math.PI);
|
||||
double spawnRadiant = this.random.nextDouble(0, 2*Math.PI);
|
||||
Location mobSpawnLocation = player.getLocation().add(
|
||||
Math.sin(spawnRadiant)*this.hordeSpawnDistance,
|
||||
0,
|
||||
@@ -213,7 +221,7 @@ public class Bloodmoon extends Appliance {
|
||||
}
|
||||
|
||||
public List<ItemStack> getRandomBonusDrops() {
|
||||
int itemCount = ThreadLocalRandom.current().nextInt(this.minBonusDrops, this.maxBonusDrops + 1);
|
||||
int itemCount = this.random.nextInt(this.minBonusDrops, this.maxBonusDrops + 1);
|
||||
List<ItemStack> result = new ArrayList<>();
|
||||
for(int i = 0; i < itemCount; i++) {
|
||||
result.add(this.getRandomBonusDrop());
|
||||
@@ -221,9 +229,9 @@ public class Bloodmoon extends Appliance {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ItemStack getRandomBonusDrop() {
|
||||
private @Nullable ItemStack getRandomBonusDrop() {
|
||||
int totalWeight = this.bonusDropWeightMap.values().stream().mapToInt(value -> value).sum();
|
||||
int randomInt = ThreadLocalRandom.current().nextInt(0, totalWeight + 1);
|
||||
int randomInt = this.random.nextInt(0, totalWeight + 1);
|
||||
int cumulativeWeight = 0;
|
||||
for(Map.Entry<ItemStack, Integer> entry : this.bonusDropWeightMap.entrySet()) {
|
||||
cumulativeWeight += entry.getValue();
|
||||
|
||||
@@ -34,7 +34,7 @@ public class BloodmoonSetting extends BoolSetting implements CategorizedSetting
|
||||
|
||||
@Override
|
||||
protected Material icon() {
|
||||
return Material.CLOCK;
|
||||
return Material.SKELETON_SKULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,7 @@ public class BloodmoonCommand extends ApplianceCommand<Bloodmoon> {
|
||||
|
||||
switch(args[0]) {
|
||||
case "start": {
|
||||
this.getAppliance().startBloodmoon();
|
||||
this.getAppliance().startBloodmoon(0L);
|
||||
sender.sendMessage("Started bloodmoon.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class BloodmoonEntityDamageListener extends ApplianceListener<Bloodmoon>
|
||||
|
||||
if(this.getAppliance().isAffectedMob(damager) && receiver instanceof Player player) {
|
||||
if(!this.getAppliance().getBloodmoonSetting(player)) return;
|
||||
event.setDamage(event.getDamage() * this.getAppliance().mobDamageMultipier);
|
||||
event.setDamage(event.getDamage() * this.getAppliance().mobDamageMultiplier);
|
||||
this.getAppliance().affectedMobEffectMap.get(damager.getType()).forEach(mobEffect -> mobEffect.apply(player));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,25 +3,28 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.list
|
||||
import com.destroystokyo.paper.event.server.ServerTickStartEvent;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
|
||||
import net.kyori.adventure.util.Ticks;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
public class BloodmoonTimeListener extends ApplianceListener<Bloodmoon> {
|
||||
@EventHandler
|
||||
public void onServerTick(ServerTickStartEvent event) {
|
||||
if(this.getAppliance().isStartTick(Bukkit.getWorlds().getFirst().getFullTime())) {
|
||||
this.getAppliance().startBloodmoon();
|
||||
long currentTime = Bukkit.getWorlds().getFirst().getFullTime();
|
||||
if(this.getAppliance().isStartTick(currentTime)) {
|
||||
this.getAppliance().startBloodmoon(currentTime);
|
||||
return;
|
||||
}
|
||||
if(this.getAppliance().isStartTick(Bukkit.getWorlds().getFirst().getFullTime() - this.getAppliance().bloodmoonLength)) {
|
||||
if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) {
|
||||
this.getAppliance().stopBloodmoon();
|
||||
return;
|
||||
}
|
||||
if(this.getAppliance().isStartTick(Bukkit.getWorlds().getFirst().getFullTime() + this.getAppliance().ticksPerDay)) {
|
||||
if(currentTime % Ticks.TICKS_PER_SECOND == 0) this.getAppliance().updateBossBar();
|
||||
if(this.getAppliance().isStartTick(currentTime + this.getAppliance().ticksPerDay)) {
|
||||
this.getAppliance().sendAnnouncementMessages();
|
||||
return;
|
||||
}
|
||||
if(this.getAppliance().isStartTick(Bukkit.getWorlds().getFirst().getFullTime() + 1000)) {
|
||||
if(this.getAppliance().isStartTick(currentTime + this.getAppliance().preStartMessageTicks)) {
|
||||
this.getAppliance().sendPreStartMessages();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
|
||||
|
||||
void askForConfirmation(Player player) {
|
||||
Component confirmationMessage = switch(this.getLawStatus(player)) {
|
||||
case DISABLED -> Component.text("Wenn du Vogelfrei aktivierst, darfst du von allen Spielern grundlos angegriffen werden.");
|
||||
case VOLUNTARILY -> Component.text("Wenn du Vogelfrei deaktivierst, darfst du nicht mehr grundlos von Spielern angegriffen werden.");
|
||||
case DISABLED -> Component.text("Wenn du Vogelfrei aktivierst, darfst du von allen anderen vogelfreien Spielern grundlos angegriffen werden.");
|
||||
case VOLUNTARILY -> Component.text("Wenn du Vogelfrei deaktivierst, darfst du nicht mehr grundlos von anderen Spielern angegriffen werden.");
|
||||
case FORCED -> Component.text("Du darfst zurzeit deinen Vogelfreistatus nicht ändern, da dieser als Strafe auferlegt wurde!");
|
||||
};
|
||||
String command = String.format("/%s confirm", OutlawedCommand.commandName);
|
||||
@@ -74,11 +74,11 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
|
||||
|
||||
void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
|
||||
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)) {
|
||||
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);
|
||||
@@ -126,11 +126,15 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
|
||||
|
||||
public Component getStatusDescription(Status 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));
|
||||
|
||||
case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED)
|
||||
.append(Component.text("Du darfst von jedem angegriffen und getötet werden!", 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
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,7 +142,9 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
|
||||
public Component getNamePrefix(Player player) {
|
||||
if(this.isOutlawed(player)) {
|
||||
return Component.text("[☠]", NamedTextColor.RED)
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Vogelfreie Spieler dürfen ohne Grund angegriffen werden!")));
|
||||
.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"PVP-Modus Spieler dürfen von anderen vogelfreien Spielern ohne Grund angegriffen werden!"
|
||||
)));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
|
||||
public static final String commandName = "vogelfrei";
|
||||
public static final String commandName = "pvp";
|
||||
|
||||
public OutlawedCommand() {
|
||||
super(commandName);
|
||||
|
||||
@@ -37,6 +37,11 @@ public class Event extends Appliance {
|
||||
DONE
|
||||
}
|
||||
|
||||
public enum EventType {
|
||||
BIG,
|
||||
SMALL
|
||||
}
|
||||
|
||||
Countdown advertiseCountdown = new Countdown(
|
||||
120,
|
||||
announcementData -> Component.text()
|
||||
@@ -50,6 +55,7 @@ public class Event extends Appliance {
|
||||
);
|
||||
public DisplayVillager.ConfigBound villager;
|
||||
private boolean isOpen = false;
|
||||
private EventType eventType;
|
||||
private AdvertisementStatus advertiseStatus = AdvertisementStatus.BEFORE;
|
||||
private UUID roomId;
|
||||
private final List<Reward> pendingRewards = new ArrayList<>();
|
||||
@@ -79,9 +85,11 @@ public class Event extends Appliance {
|
||||
if(this.isOpen) this.roomId = UUID.fromString(this.localConfig().getString("roomId", ""));
|
||||
}
|
||||
|
||||
public void openEvent() {
|
||||
public void openEvent(EventType type) {
|
||||
if(this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit bereits ein Event!");
|
||||
this.eventType = type;
|
||||
|
||||
if(type.equals(EventType.SMALL)) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
|
||||
ReqResp<EventRepository.CreatedRoom> sessionResponse = this.queryRepository(EventRepository.class).createSession();
|
||||
|
||||
@@ -91,6 +99,10 @@ public class Event extends Appliance {
|
||||
this.isOpen = true;
|
||||
this.roomId = sessionResponse.data().uuid();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.isOpen = true;
|
||||
}
|
||||
|
||||
public void joinEvent(Player p) {
|
||||
@@ -112,6 +124,7 @@ public class Event extends Appliance {
|
||||
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
|
||||
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
|
||||
|
||||
if(this.eventType.equals(EventType.SMALL)) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
|
||||
ReqResp<EventRepository.QueueRoom.Response> queueResponse = this.queryRepository(EventRepository.class)
|
||||
.queueRoom(new EventRepository.QueueRoom(p.getUniqueId(), this.roomId));
|
||||
@@ -126,6 +139,9 @@ public class Event extends Appliance {
|
||||
});
|
||||
}
|
||||
|
||||
PluginMessage.connect(p, "grand-event");
|
||||
}
|
||||
|
||||
public void endEvent() {
|
||||
if(!this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit kein Event!");
|
||||
this.isOpen = false;
|
||||
|
||||
@@ -7,6 +7,10 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class EventOpenSessionCommand extends ApplianceCommand<Event> {
|
||||
public EventOpenSessionCommand() {
|
||||
@@ -15,7 +19,17 @@ public class EventOpenSessionCommand extends ApplianceCommand<Event> {
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||
this.getAppliance().openEvent();
|
||||
if(args.length == 1) {
|
||||
this.getAppliance().openEvent(Event.EventType.SMALL);
|
||||
} else {
|
||||
this.getAppliance().openEvent(Event.EventType.valueOf(args[1]));
|
||||
}
|
||||
sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if(args.length == 1) return Arrays.stream(Event.EventType.values()).map(Enum::toString).toList();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.feedback;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.api.repositories.FeedbackRepository;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
@@ -9,7 +9,6 @@ import eu.mhsl.craftattack.spawn.core.api.HttpStatus;
|
||||
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.entity.Entity;
|
||||
@@ -18,32 +17,27 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Feedback extends Appliance {
|
||||
public Feedback() {
|
||||
super("feedback");
|
||||
}
|
||||
|
||||
public void requestFeedback(String eventName, List<Player> receivers, @Nullable String question) {
|
||||
ReqResp<Map<UUID, String>> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls(
|
||||
new FeedbackRepository.Request(eventName, receivers.stream().map(Entity::getUniqueId).toList())
|
||||
public void requestFeedback(String eventName, String title, List<Player> receivers, @Nullable String question) {
|
||||
ReqResp<FeedbackRepository.Response> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls(
|
||||
new FeedbackRepository.Request(eventName, title, receivers.stream().map(Entity::getUniqueId).toList())
|
||||
);
|
||||
|
||||
System.out.println(response.toString());
|
||||
System.out.println(response.status());
|
||||
|
||||
if(response.status() != HttpStatus.CREATED) throw new RuntimeException();
|
||||
if(response.status() != HttpStatus.OK) throw new RuntimeException();
|
||||
|
||||
Component border = Component.text("-".repeat(40), NamedTextColor.GRAY);
|
||||
|
||||
receivers.forEach(player -> {
|
||||
String feedbackUrl = response.data().get(player.getUniqueId());
|
||||
if(feedbackUrl == null) {
|
||||
Main.logger().warning(String.format("FeedbackUrl not found for player '%s' from backend!", player.getUniqueId()));
|
||||
return;
|
||||
}
|
||||
String feedbackUrl = response.data().feedback().stream()
|
||||
.filter(feedback -> feedback.uuid().equals(player.getUniqueId()))
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.url();
|
||||
|
||||
ComponentBuilder<TextComponent, TextComponent.Builder> message = Component.text()
|
||||
.append(border)
|
||||
@@ -58,8 +52,7 @@ public class Feedback extends Appliance {
|
||||
|
||||
message
|
||||
.append(Component.text("Klicke hier und gib uns Feedback, damit wir dein Spielerlebnis verbessern können!", NamedTextColor.DARK_GREEN)
|
||||
.clickEvent(ClickEvent.openUrl(feedbackUrl)))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um Feedback zu geben.")))
|
||||
.hoverEvent(HoverEvent.showText(ComponentUtil.clickLink(feedbackUrl))))
|
||||
.appendNewline()
|
||||
.append(border);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class FeedbackCommand extends ApplianceCommand.PlayerChecked<Feedback> {
|
||||
Main.instance(),
|
||||
() -> this.getAppliance().requestFeedback(
|
||||
"self-issued-ingame",
|
||||
"Dein Feedback an uns",
|
||||
List.of(this.getPlayer()),
|
||||
null
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ class RequestFeedbackCommand extends ApplianceCommand<Feedback> {
|
||||
Main.instance(),
|
||||
() -> this.getAppliance().requestFeedback(
|
||||
"admin-issued-ingame",
|
||||
"Hilf uns dein Spielerlebnis zu verbessern!",
|
||||
new ArrayList<>(Bukkit.getOnlinePlayers()), String.join(" ", args)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.statistics;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Statistics extends Appliance {
|
||||
record StatisticsResponse(List<PlayerStatistics> playerStatistics) {
|
||||
record PlayerStatistics(String playerName, String playerUuid, List<MaterialStatistic> statistics) {
|
||||
}
|
||||
}
|
||||
|
||||
record MaterialStatistic(String name, String material, int value) {
|
||||
}
|
||||
|
||||
record StatisticsRequest(List<MaterialStatistic> categories) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
apiBuilder.post(
|
||||
"getStatistics",
|
||||
StatisticsRequest.class,
|
||||
(statistics, request) -> {
|
||||
Main.instance().getLogger().info("API requested statistics");
|
||||
List<StatisticsResponse.PlayerStatistics> statisticsList = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.map(player -> new StatisticsResponse.PlayerStatistics(
|
||||
player.getName(),
|
||||
player.getUniqueId().toString(),
|
||||
statistics.categories().stream()
|
||||
.map(category -> {
|
||||
String material = (category.material() == null || category.material().isBlank()) ? null : category.material();
|
||||
return new MaterialStatistic(category.name(), material, material == null
|
||||
? player.getStatistic(Statistic.valueOf(category.name()))
|
||||
: player.getStatistic(Statistic.valueOf(category.name()), Material.valueOf(material))
|
||||
);
|
||||
})
|
||||
.toList()
|
||||
))
|
||||
.toList();
|
||||
return new StatisticsResponse(statisticsList);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.strikes;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.api.repositories.WhitelistRepository;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Strikes extends Appliance {
|
||||
public Strikes() {
|
||||
super("strike");
|
||||
}
|
||||
|
||||
private static final BanInfo falseInfo = new BanInfo(false, null, false);
|
||||
public record BanInfo(boolean isBanned, @Nullable Instant bannedUntil, boolean isPermanent) {
|
||||
}
|
||||
|
||||
private final Map<Integer, Duration> strikePunishmentMap = Map.of(
|
||||
1, Duration.ofHours(1),
|
||||
2, Duration.ofHours(24),
|
||||
3, Duration.ofDays(3),
|
||||
4, Duration.ofDays(7)
|
||||
);
|
||||
|
||||
|
||||
public BanInfo isBanned(List<WhitelistRepository.UserData.Strike> strikes) {
|
||||
if (strikes.isEmpty()) return falseInfo;
|
||||
|
||||
int strikeCount = strikes.stream().mapToInt(WhitelistRepository.UserData.Strike::weight).sum();
|
||||
if (strikeCount < 1) return falseInfo;
|
||||
if (strikeCount > this.strikePunishmentMap.size()) return new BanInfo(true, null, true);
|
||||
|
||||
Instant lastPunishment = strikes.stream()
|
||||
.map(strike -> Instant.ofEpochMilli(strike.at()))
|
||||
.max(Instant::compareTo)
|
||||
.orElse(Instant.EPOCH);
|
||||
|
||||
Duration duration = this.strikePunishmentMap.get(strikeCount);
|
||||
Instant bannedUntil = lastPunishment.plus(duration);
|
||||
|
||||
boolean stillBanned = bannedUntil.isAfter(Instant.now());
|
||||
|
||||
return new BanInfo(stillBanned, bannedUntil, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.whitelist;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.core.event.StrikeCreatedEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
public class StrikeUpdateListener extends ApplianceListener<Whitelist> {
|
||||
@EventHandler
|
||||
public void onStrikeUpdate(StrikeCreatedEvent event) {
|
||||
this.getAppliance().profileUpdated(event.getStrike().playerToStrike());
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,18 @@ import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.api.HttpStatus;
|
||||
import eu.mhsl.craftattack.spawn.core.util.server.Floodgate;
|
||||
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.outlawed.Outlawed;
|
||||
import eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.strikes.Strikes;
|
||||
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.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import javax.inject.Provider;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Whitelist extends Appliance {
|
||||
@@ -47,7 +44,6 @@ public class Whitelist extends Appliance {
|
||||
player.getUniqueId()
|
||||
);
|
||||
}
|
||||
this.queryAppliance(Outlawed.class).updateForcedStatus(player, this.timestampRelevant(user.outlawed_until()));
|
||||
|
||||
String purePlayerName = Floodgate.isBedrock(player)
|
||||
? Floodgate.getBedrockPlayer(player).getUsername()
|
||||
@@ -67,23 +63,31 @@ public class Whitelist extends Appliance {
|
||||
Main.instance().getLogger().info(String.format("Running integrityCheck for %s", name));
|
||||
boolean overrideCheck = this.localConfig().getBoolean("overrideIntegrityCheck", false);
|
||||
WhitelistRepository.UserData user = overrideCheck
|
||||
? new WhitelistRepository.UserData(uuid, name, "", "", 0L, 0L)
|
||||
? new WhitelistRepository.UserData(uuid, name, "", "", List.of())
|
||||
: this.fetchUserData(uuid);
|
||||
|
||||
this.userData.put(uuid, user);
|
||||
Main.logger().info(String.format("got userdata %s", user.toString()));
|
||||
|
||||
if(this.timestampRelevant(user.banned_until())) {
|
||||
Instant bannedDate = new Date(user.banned_until() * 1000L)
|
||||
.toInstant()
|
||||
.plus(1, ChronoUnit.HOURS);
|
||||
Strikes.BanInfo banInfo = Main.instance().getAppliance(Strikes.class).isBanned(user.strikes());
|
||||
Main.logger().info(String.format("got baninfo %s", banInfo.toString()));
|
||||
|
||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy").withZone(ZoneOffset.UTC);
|
||||
DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneOffset.UTC);
|
||||
if (banInfo.isBanned()) {
|
||||
Provider<ZonedDateTime> zoned = () -> {
|
||||
Objects.requireNonNull(banInfo.bannedUntil(), "Cannot get zoned date time from null");
|
||||
return banInfo.bannedUntil().atZone(ZoneId.of("Europe/Berlin"));
|
||||
};
|
||||
|
||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy");
|
||||
DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
String unbanText = banInfo.bannedUntil() != null
|
||||
? String.format("Dein Bann läuft am %s um %s ab!", dateFormat.format(zoned.get()), timeFormat.format(zoned.get()))
|
||||
: "Bandauer ist unbekannt.";
|
||||
|
||||
throw new DisconnectInfo.Throwable(
|
||||
"Du wurdest vom Server gebannt.",
|
||||
String.format("Dein Bann läuft am %s um %s ab!", dateFormat.format(bannedDate), timeFormat.format(bannedDate)),
|
||||
banInfo.isPermanent() ? "Dein Bann läuft nicht ab!" : unbanText,
|
||||
"Wende dich an einen Admin für weitere Informationen.",
|
||||
uuid
|
||||
);
|
||||
@@ -101,11 +105,6 @@ public class Whitelist extends Appliance {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean timestampRelevant(Long timestamp) {
|
||||
if(timestamp == null) return false;
|
||||
return timestamp > System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
|
||||
private WhitelistRepository.UserData fetchUserData(UUID uuid) throws DisconnectInfo.Throwable {
|
||||
ReqResp<WhitelistRepository.UserData> response = this.queryRepository(WhitelistRepository.class).getUserData(uuid);
|
||||
|
||||
@@ -123,13 +122,9 @@ public class Whitelist extends Appliance {
|
||||
return response.data();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
record User(UUID user) {
|
||||
}
|
||||
apiBuilder.post("update", User.class, (user, request) -> {
|
||||
Main.instance().getLogger().info(String.format("API Triggered Profile update for %s", user.user));
|
||||
Player player = Bukkit.getPlayer(user.user);
|
||||
public void profileUpdated(UUID uuid) {
|
||||
Main.instance().getLogger().info(String.format("API Triggered Profile update for %s", uuid));
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
if(player != null) {
|
||||
try {
|
||||
this.fullIntegrityCheck(player);
|
||||
@@ -137,6 +132,14 @@ public class Whitelist extends Appliance {
|
||||
e.getDisconnectScreen().applyKick(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
record User(UUID user) {
|
||||
}
|
||||
apiBuilder.post("update", User.class, (user, request) -> {
|
||||
this.profileUpdated(user.user);
|
||||
return HttpServer.nothing;
|
||||
});
|
||||
}
|
||||
@@ -145,7 +148,8 @@ public class Whitelist extends Appliance {
|
||||
@NotNull
|
||||
protected List<Listener> listeners() {
|
||||
return List.of(
|
||||
new PlayerJoinListener()
|
||||
new PlayerJoinListener(),
|
||||
new StrikeUpdateListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
9
event/build.gradle
Normal file
9
event/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
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'
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerDamageListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerJoinListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerMoveListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPortalListener;
|
||||
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.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import net.kyori.adventure.util.Ticks;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@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 = 75;
|
||||
private final int borderVisibilityDistance = 8;
|
||||
private long durationSeconds;
|
||||
private boolean isBeforeStart = true;
|
||||
private boolean pvpDisabled = true;
|
||||
private final World world = Bukkit.getWorlds().getFirst();
|
||||
private BukkitTask pvpTask;
|
||||
public ArrayList<UUID> previouslyJoinedPlayers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.world.getWorldBorder().setCenter(this.world.getSpawnLocation());
|
||||
this.world.getWorldBorder().setSize(20);
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
player.teleport(this.world.getSpawnLocation());
|
||||
this.previouslyJoinedPlayers.add(player.getUniqueId());
|
||||
});
|
||||
this.pvpDisabled = true;
|
||||
}
|
||||
|
||||
public double getBorderDistance() {
|
||||
return this.borderDistance;
|
||||
}
|
||||
|
||||
public int getBorderVisibilityDistance() {
|
||||
return this.borderVisibilityDistance;
|
||||
}
|
||||
|
||||
public void spawnParticles(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() {
|
||||
this.world.getWorldBorder().setSize(this.world.getWorldBorder().getMaxSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(long durationSeconds) {
|
||||
this.isBeforeStart = false;
|
||||
this.durationSeconds = durationSeconds;
|
||||
Title title = Title.title(Component.text("Start", NamedTextColor.GOLD), Component.text("Laufe Richtung Osten! (positiv x)", NamedTextColor.YELLOW));
|
||||
// TODO: Soll PvP überhaupt aktiviert werden? Soll Respawn erlaubt sein?
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
player.showTitle(title);
|
||||
player.sendMessage(Component.text("PvP wird in 10 Minuten aktiviert!", NamedTextColor.GOLD));
|
||||
});
|
||||
|
||||
this.world.getWorldBorder().setSize(this.world.getWorldBorder().getMaxSize());
|
||||
this.pvpTask = Bukkit.getScheduler().runTaskLater(
|
||||
Main.instance(),
|
||||
() -> {
|
||||
this.pvpDisabled = false;
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(Component.text("PvP ist jetzt aktiviert!", NamedTextColor.GOLD)));
|
||||
},
|
||||
Ticks.TICKS_PER_SECOND * 60 * 10
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isBeforeStart() {
|
||||
return this.isBeforeStart;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
public boolean isPvpDisabled() {
|
||||
return this.pvpDisabled;
|
||||
}
|
||||
|
||||
@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());
|
||||
this.pvpTask.cancel();
|
||||
this.pvpDisabled = true;
|
||||
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(),
|
||||
new DeathrunPlayerDamageListener(),
|
||||
new DeathrunPlayerJoinListener(),
|
||||
new DeathrunPortalListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
|
||||
public class DeathrunPlayerDamageListener extends ApplianceListener<Deathrun> {
|
||||
@EventHandler
|
||||
public void onPlayerDamagePlayer(EntityDamageByEntityEvent event) {
|
||||
if(!this.getAppliance().isPvpDisabled()) return;
|
||||
if(!(event.getDamager() instanceof Player)) return;
|
||||
if(!(event.getEntity() instanceof Player)) return;
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDamage(EntityDamageEvent event) {
|
||||
if(!this.getAppliance().isBeforeStart()) return;
|
||||
if(!(event.getEntity() instanceof Player)) return;
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHunger(FoodLevelChangeEvent event) {
|
||||
if(!this.getAppliance().isBeforeStart()) event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
public class DeathrunPlayerJoinListener extends ApplianceListener<Deathrun> {
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if(
|
||||
this.getAppliance().isBeforeStart() ||
|
||||
!this.getAppliance().previouslyJoinedPlayers.contains(player.getUniqueId())
|
||||
) {
|
||||
player.teleport(this.getAppliance().getWorld().getSpawnLocation());
|
||||
player.setGameMode(GameMode.ADVENTURE);
|
||||
this.getAppliance().previouslyJoinedPlayers.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
if(!this.getAppliance().isBeforeStart() && player.getGameMode().equals(GameMode.ADVENTURE)) {
|
||||
player.setGameMode(GameMode.SURVIVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
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().spawnParticles(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().spawnParticles(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().spawnParticles(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) throws Exception {
|
||||
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 || event.getTo().z() < minZ || event.getTo().z() > maxZ) {
|
||||
event.setCancelled(true);
|
||||
throw new Exception("Player %s teleported outside the border.".formatted(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
|
||||
public class DeathrunPortalListener extends ApplianceListener<Deathrun> {
|
||||
@EventHandler
|
||||
public void onPlayerPortal(PlayerPortalEvent event) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
|
||||
|
||||
public interface Event {
|
||||
void start(long durationSeconds);
|
||||
void stop();
|
||||
long getDurationSeconds();
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
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.core.util.IteratorUtil;
|
||||
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
|
||||
import eu.mhsl.craftattack.spawn.core.util.server.PluginMessage;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.BigEventCommand;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.JoinCraftattackCommand;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
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.bukkit.GameMode;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Statistic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
public class EventController extends Appliance {
|
||||
private List<Appliance> eventAppliances = null;
|
||||
private Event selectedEvent = null;
|
||||
private int timerTaskId = -1;
|
||||
|
||||
private final Map<GameRule<Boolean>, Boolean> gameRulesAfterStart = Map.ofEntries(
|
||||
entry(GameRule.DO_DAYLIGHT_CYCLE, true),
|
||||
entry(GameRule.DO_INSOMNIA, true),
|
||||
entry(GameRule.DISABLE_RAIDS, false),
|
||||
entry(GameRule.DO_FIRE_TICK, true),
|
||||
entry(GameRule.DO_ENTITY_DROPS, true),
|
||||
entry(GameRule.DO_PATROL_SPAWNING, true),
|
||||
entry(GameRule.DO_TRADER_SPAWNING, true),
|
||||
entry(GameRule.DO_WEATHER_CYCLE, true),
|
||||
entry(GameRule.FALL_DAMAGE, true),
|
||||
entry(GameRule.FIRE_DAMAGE, true)
|
||||
);
|
||||
|
||||
@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;
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.ADVENTURE));
|
||||
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, true));
|
||||
}
|
||||
|
||||
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!");
|
||||
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, false));
|
||||
IteratorUtil.worlds(world -> world.setFullTime(0));
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
player.setFoodLevel(20);
|
||||
player.setHealth(20);
|
||||
player.getInventory().clear();
|
||||
player.setGameMode(GameMode.SURVIVAL);
|
||||
player.setExp(0);
|
||||
player.setLevel(0);
|
||||
|
||||
player.playSound(Sound.sound(org.bukkit.Sound.ITEM_GOAT_HORN_SOUND_6, Sound.Source.MASTER, 500f, 1f));
|
||||
|
||||
player.sendMessage(Component.text("Viel Spaß bei %s!".formatted(this.selectedEvent.getClass().getSimpleName()), NamedTextColor.GREEN));
|
||||
|
||||
player.setStatistic(Statistic.TIME_SINCE_REST, 0);
|
||||
PlayerUtils.resetStatistics(player);
|
||||
});
|
||||
this.selectedEvent.start(durationMinutes * 60);
|
||||
if(this.selectedEvent instanceof Scorable scorable && scorable.automaticUpdates()) scorable.getScoreboardBuilder().startAutomaticUpdates();
|
||||
// TODO: possibility for other dimensions
|
||||
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;
|
||||
if(this.selectedEvent instanceof Scorable scorable) {
|
||||
String scores = String.join("\n", scorable.getScoreboardBuilder().getScores());
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(scores));
|
||||
Main.instance().getLogger().info(scores);
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
Main.instance(),
|
||||
() -> Bukkit.getOnlinePlayers().forEach(player -> PluginMessage.connect(player, "craftattack")),
|
||||
Ticks.TICKS_PER_SECOND * 7
|
||||
);
|
||||
}
|
||||
|
||||
public String getSelectedEvent() {
|
||||
if(this.selectedEvent == null) return "nothing selected";
|
||||
return this.selectedEvent.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
private void updateTimer() {
|
||||
long ticksLeft = -(Bukkit.getWorlds().getFirst().getFullTime() - this.selectedEvent.getDurationSeconds() * Ticks.TICKS_PER_SECOND);
|
||||
if(ticksLeft <= 0) {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(Component.text("Fertig!")));
|
||||
this.stopEvent();
|
||||
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 BigEventCommand(),
|
||||
new JoinCraftattackCommand()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 BigEventCommand extends ApplianceCommand<EventController> {
|
||||
public BigEventCommand() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController.commands;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.core.util.server.PluginMessage;
|
||||
import eu.mhsl.craftattack.spawn.event.appliances.eventController.EventController;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class JoinCraftattackCommand extends ApplianceCommand.PlayerChecked<EventController> {
|
||||
public JoinCraftattackCommand() {
|
||||
super("craftattack");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
PluginMessage.connect(this.getPlayer(), "craftattack");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record EventScoreEntry(UUID playerUuid, String name, int score) {
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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);
|
||||
}
|
||||
|
||||
public List<String> getScores() {
|
||||
List<EventScoreEntry> scoreList = new ArrayList<>(this.playerScores);
|
||||
scoreList.sort(this.scoreComparator);
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
for(int i = 0; i < scoreList.size(); i++) {
|
||||
result.add(this.formattedLine(i, scoreList.get(i).name(), scoreList.get(i).score()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String trimName(String name) {
|
||||
if (name == null) return "Unknown";
|
||||
if (name.length() > 12) {
|
||||
return name.substring(0, 12);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,5 @@ include 'core'
|
||||
include 'craftattack'
|
||||
include 'common'
|
||||
include 'varo'
|
||||
include 'event'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':common')
|
||||
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT'
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
||||
}
|
||||
Reference in New Issue
Block a user