Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f615dc867a | |||
| 673bae4d50 | |||
| 6ad9407cb4 | |||
| 9dad34719b | |||
| 323e316f0f | |||
| 0a16e1e049 | |||
| c371893407 | |||
| f2bf8f1858 | |||
| a0a33f1f56 | |||
| 164a160dbb | |||
| efdbc6fe9f | |||
| 7ac02b4ec4 | |||
| 93971650ce | |||
| 336e3f934c | |||
| 91e350ee62 | |||
| 8736e78adf | |||
| 329ec8e1da | |||
| ebf392b024 | |||
| 33d00e97c6 | |||
| 1fdcc11211 | |||
| e0ed7ecdf5 | |||
| 2ca97c88fc | |||
| fc502e3da8 | |||
| 5e3db1e78c | |||
| fc6fd9ebb5 | |||
| 84edbcc0e4 | |||
| 9ec883d6ad | |||
| 2b0c7c1a9e | |||
| df39093c69 | |||
| dfbf87dcd4 | |||
| 8953a19400 | |||
| 85065bcc73 | |||
| 2209b42766 | |||
| bf11bb0b70 | |||
| b98d33af40 | |||
| 0d6b21701f | |||
| b3240cdb22 | |||
| 89c1c4335b | |||
| 71f2da8e99 | |||
| a257b604ea | |||
| ef153d5d8f | |||
| bd883a4fa1 | |||
| ac7e04829e | |||
| 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 | |||
| 53dbeff829 | |||
| b5da06fd49 | |||
| b55035f1f0 | |||
| 469cd19b55 | |||
| 91a28b4500 | |||
| e745ff4721 | |||
| 23af3ff784 | |||
| bc5c9a2a13 | |||
| c220479052 | |||
| 0565f5de9e | |||
| ff81c91661 | |||
| 1182cb2ed6 | |||
| 045f31d4ca | |||
| 263fd85df7 | |||
| d70b025502 | |||
| 78f87d97f0 | |||
| db13a9f0a2 | |||
| e14c87c2fb | |||
| 9433495a52 | |||
| 4955e94306 | |||
| 09abfefe33 | |||
| bd3546abc8 | |||
| 8a7a0453ce | |||
| 64d0d817c0 | |||
| 713561bf07 | |||
| 4be3e528b1 | |||
| 53fca580f3 | |||
| 20fb4bf9fb | |||
| c42d259909 | |||
| 5c82c8d6da | |||
| 5910847172 | |||
| aad1fcafa6 | |||
| 9fca7430a8 | |||
| 7c254707c1 | |||
| 9ee5f6e419 | |||
| e49c3b1987 | |||
| 5ca4c70a41 | |||
| 040cae6cd1 | |||
| 324defc4a8 | |||
| dc0003b91e | |||
| d4a3c798f8 | |||
| c88c2ab6aa | |||
| 32a20cd4c5 | |||
| d7cc141b94 | |||
| 16d7347fd0 | |||
| fdf3b5c73f | |||
| 74f17e1b6d | |||
| 0d18b81399 | |||
| 1fa5fdfeb7 | |||
| 3bec5f4cbd | |||
| d38871eac9 | |||
| 238df2feff | |||
| e752d7f73b | |||
| b0df982be3 | |||
| dc1b5957f6 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
|
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
||||||
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
||||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
implementation 'com.sparkjava:spark-core:2.9.4'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class CraftAttackApi {
|
public class CraftAttackApi {
|
||||||
@@ -25,4 +26,8 @@ public class CraftAttackApi {
|
|||||||
public static void withAuthorizationSecret(URIBuilder builder) {
|
public static void withAuthorizationSecret(URIBuilder builder) {
|
||||||
builder.addParameter("secret", apiSecret);
|
builder.addParameter("secret", apiSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void withAuthorizationHeader(HttpRequest.Builder builder) {
|
||||||
|
builder.header("Authorization", apiSecret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -6,22 +6,25 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public class CraftAttackReportRepository extends ReportRepository {
|
public class CraftAttackReportRepository extends ReportRepository {
|
||||||
public CraftAttackReportRepository() {
|
public CraftAttackReportRepository() {
|
||||||
super(CraftAttackApi.getBaseUri(), new RequestModifier(CraftAttackApi::withAuthorizationSecret, null));
|
super(CraftAttackApi.getBaseUri(), new RequestModifier(null, CraftAttackApi::withAuthorizationHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReqResp<PlayerReports> queryReports(UUID player) {
|
public ReqResp<PlayerReports> queryReports(UUID player) {
|
||||||
return this.get(
|
return this.get(
|
||||||
"report",
|
"users/%s/reports".formatted(player.toString()),
|
||||||
(parameters) -> parameters.addParameter("uuid", player.toString()),
|
|
||||||
PlayerReports.class
|
PlayerReports.class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
|
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
|
||||||
return this.post(
|
return this.post(
|
||||||
"report",
|
"reports",
|
||||||
data,
|
data,
|
||||||
ReportUrl.class
|
ReportUrl.class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReqResp<AllReports> queryAllReports() {
|
||||||
|
return this.get("reports", AllReports.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-12
@@ -21,22 +21,32 @@ public abstract class ReportRepository extends HttpRepository {
|
|||||||
public record ReportUrl(@NotNull String url) {
|
public record ReportUrl(@NotNull String url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
open,
|
||||||
|
closed,
|
||||||
|
}
|
||||||
|
|
||||||
public record PlayerReports(
|
public record PlayerReports(
|
||||||
List<Report> from_self,
|
List<Report> from_self,
|
||||||
Object to_self
|
List<Report> to_self
|
||||||
) {
|
) {
|
||||||
public record Report(
|
public record Report(
|
||||||
@Nullable Reporter reported,
|
@Nullable UUID reported,
|
||||||
@NotNull String subject,
|
@NotNull String reason,
|
||||||
boolean draft,
|
@Nullable Long created,
|
||||||
@NotNull String status,
|
@Nullable Status status,
|
||||||
@NotNull String url
|
@NotNull String url
|
||||||
) {
|
) { }
|
||||||
public record Reporter(
|
}
|
||||||
@NotNull String username,
|
|
||||||
@NotNull String uuid
|
public record AllReports(List<Report> reports) {
|
||||||
) {
|
public record Report(
|
||||||
}
|
@NotNull UUID reporter,
|
||||||
}
|
@Nullable UUID reported,
|
||||||
|
@NotNull String reason,
|
||||||
|
@Nullable Long created,
|
||||||
|
@Nullable Status status,
|
||||||
|
@NotNull String url
|
||||||
|
) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.cordinateDisplay;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
|
||||||
|
public class CoordinateChangedListener extends ApplianceListener<CoordinateDisplay> {
|
||||||
|
@EventHandler
|
||||||
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
|
this.getAppliance().updateEnabled(event.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onMove(PlayerMoveEvent event) {
|
||||||
|
if(!this.getAppliance().isEnabled(event.getPlayer())) return;
|
||||||
|
boolean hasChangedOrientation = this.getAppliance().hasChangedDirection(event.getFrom(), event.getTo());
|
||||||
|
if(!event.hasChangedBlock() && !hasChangedOrientation) return;
|
||||||
|
this.getAppliance().sendCoordinates(event.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.cordinateDisplay;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.text.DataSizeConverter;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.world.WorldUtils;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class CoordinateDisplay extends Appliance {
|
||||||
|
Map<Player, CoordinateDisplaySetting.CoordinateDisplayConfiguration> enabledPlayers = new WeakHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
Settings.instance().declareSetting(CoordinateDisplaySetting.class);
|
||||||
|
Settings.instance().addChangeListener(CoordinateDisplaySetting.class, this::updateEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEnabled(Player player) {
|
||||||
|
CoordinateDisplaySetting.CoordinateDisplayConfiguration configuration = Settings.instance().getSetting(
|
||||||
|
player,
|
||||||
|
Settings.Key.CoordinateDisplay,
|
||||||
|
CoordinateDisplaySetting.CoordinateDisplayConfiguration.class
|
||||||
|
);
|
||||||
|
this.enabledPlayers.put(player, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled(Player player) {
|
||||||
|
return Optional.ofNullable(this.enabledPlayers.get(player))
|
||||||
|
.map(CoordinateDisplaySetting.CoordinateDisplayConfiguration::anyEnabled)
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCoordinates(Player player) {
|
||||||
|
CoordinateDisplaySetting.CoordinateDisplayConfiguration config = this.enabledPlayers.get(player);
|
||||||
|
List<Component> components = new ArrayList<>();
|
||||||
|
|
||||||
|
if (config.coordinates()) {
|
||||||
|
components.add(Component.text("\uD83C\uDF0E ", NamedTextColor.GOLD));
|
||||||
|
components.add(Component.text(String.format(
|
||||||
|
"%d %d %d",
|
||||||
|
player.getLocation().getBlockX(),
|
||||||
|
player.getLocation().getBlockY(),
|
||||||
|
player.getLocation().getBlockZ()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.direction()) {
|
||||||
|
if (!components.isEmpty()) {
|
||||||
|
components.add(Component.text(" | ", NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
components.add(Component.text("\uD83E\uDDED ", NamedTextColor.GOLD));
|
||||||
|
components.add(Component.text(DataSizeConverter.getCardinalDirection(player.getLocation())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.time()) {
|
||||||
|
if (!components.isEmpty()) {
|
||||||
|
components.add(Component.text(" | ", NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
components.add(Component.text("⏱ ", NamedTextColor.GOLD));
|
||||||
|
components.add(Component.text(WorldUtils.getGameTime(player.getWorld())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!components.isEmpty()) {
|
||||||
|
Component actionBar = Component.empty();
|
||||||
|
for (Component component : components) {
|
||||||
|
actionBar = actionBar.append(component);
|
||||||
|
}
|
||||||
|
player.sendActionBar(actionBar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasChangedDirection(Location previous, Location next) {
|
||||||
|
return !Objects.equals(
|
||||||
|
DataSizeConverter.getCardinalDirection(previous),
|
||||||
|
DataSizeConverter.getCardinalDirection(next)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> listeners() {
|
||||||
|
return List.of(
|
||||||
|
new CoordinateChangedListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.cordinateDisplay;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
public class CoordinateDisplaySetting extends MultiBoolSetting<CoordinateDisplaySetting.CoordinateDisplayConfiguration> implements CategorizedSetting {
|
||||||
|
public CoordinateDisplaySetting() {
|
||||||
|
super(Settings.Key.CoordinateDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SettingCategory category() {
|
||||||
|
return SettingCategory.Gameplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CoordinateDisplayConfiguration(
|
||||||
|
@DisplayName("Koordinaten") boolean coordinates,
|
||||||
|
@DisplayName("Richtung") boolean direction,
|
||||||
|
@DisplayName("Zeit") boolean time
|
||||||
|
) {
|
||||||
|
public boolean anyEnabled() {
|
||||||
|
return this.coordinates || this.direction || this.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String title() {
|
||||||
|
return "Koordinatenanzeige";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String description() {
|
||||||
|
return "Zeige deine aktuelle Position über der Hotbar an";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Material icon() {
|
||||||
|
return Material.RECOVERY_COMPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CoordinateDisplayConfiguration defaultValue() {
|
||||||
|
return new CoordinateDisplayConfiguration(false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> dataType() {
|
||||||
|
return CoordinateDisplayConfiguration.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class AdminMarker extends Appliance implements DisplayName.Colored {
|
public class AdminMarker extends Appliance implements DisplayName.Colored {
|
||||||
public final static String adminPermission = "admin";
|
public final static String adminPermission = "adminmarker";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextColor getNameColor(Player player) {
|
public @Nullable TextColor getNameColor(Player player) {
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ public class HelpCommand extends ApplianceCommand<Help> {
|
|||||||
Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD)
|
Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD)
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." +
|
.append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." +
|
||||||
" Weitere Informationen zu Funktionen und Befehlen erhältst du zudem im Turm am Spawn.", NamedTextColor.GRAY))
|
" Weitere Informationen zu Funktionen und Befehlen erhältst du zudem am Spawn.", NamedTextColor.GRAY))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -13,6 +13,7 @@ import java.time.temporal.ChronoUnit;
|
|||||||
public abstract class Bar {
|
public abstract class Bar {
|
||||||
private BossBar bossBar;
|
private BossBar bossBar;
|
||||||
private final BukkitTask updateTask;
|
private final BukkitTask updateTask;
|
||||||
|
public static String name;
|
||||||
|
|
||||||
public Bar() {
|
public Bar() {
|
||||||
long refreshRateInTicks = this.refresh().get(ChronoUnit.SECONDS) * Ticks.TICKS_PER_SECOND;
|
long refreshRateInTicks = this.refresh().get(ChronoUnit.SECONDS) * Ticks.TICKS_PER_SECOND;
|
||||||
@@ -32,7 +33,7 @@ public abstract class Bar {
|
|||||||
private BossBar createBar() {
|
private BossBar createBar() {
|
||||||
return BossBar.bossBar(
|
return BossBar.bossBar(
|
||||||
this.title(),
|
this.title(),
|
||||||
this.correctedProgress(),
|
this.clampedProgress(),
|
||||||
this.color(),
|
this.color(),
|
||||||
this.overlay()
|
this.overlay()
|
||||||
);
|
);
|
||||||
@@ -43,7 +44,7 @@ public abstract class Bar {
|
|||||||
|
|
||||||
this.beforeRefresh();
|
this.beforeRefresh();
|
||||||
this.bossBar.name(this.title());
|
this.bossBar.name(this.title());
|
||||||
this.bossBar.progress(this.correctedProgress());
|
this.bossBar.progress(this.clampedProgress());
|
||||||
this.bossBar.color(this.color());
|
this.bossBar.color(this.color());
|
||||||
this.bossBar.overlay(this.overlay());
|
this.bossBar.overlay(this.overlay());
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ public abstract class Bar {
|
|||||||
this.updateTask.cancel();
|
this.updateTask.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private float correctedProgress() {
|
private float clampedProgress() {
|
||||||
return Math.clamp(this.progress(), 0, 1);
|
return Math.clamp(this.progress(), 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
class InfoBarCommand extends ApplianceCommand.PlayerChecked<InfoBars> {
|
|
||||||
public InfoBarCommand() {
|
|
||||||
super("infobar");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
|
||||||
if(args.length == 0) throw new Error("<show|hide|hideall> [bar name]");
|
|
||||||
switch(args[0]) {
|
|
||||||
case "hideAll" -> this.getAppliance().hideAll(this.getPlayer());
|
|
||||||
case "show" -> this.getAppliance().show(this.getPlayer(), args[1]);
|
|
||||||
case "hide" -> this.getAppliance().hide(this.getPlayer(), args[1]);
|
|
||||||
default -> throw new Error("Erlaubte Optionen sind 'show', 'hide', 'hideAll'!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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("show", "hide", "hideAll");
|
|
||||||
return this.getAppliance().getInfoBars().stream().map(Bar::name).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
public class InfoBarSetting extends MultiBoolSetting<InfoBarSetting.InfoBarConfiguration> implements CategorizedSetting {
|
||||||
|
public InfoBarSetting() {
|
||||||
|
super(Settings.Key.InfoBars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SettingCategory category() {
|
||||||
|
return SettingCategory.Misc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record InfoBarConfiguration(
|
||||||
|
@DisplayName("Millisekunden pro Tick") boolean mspt,
|
||||||
|
@DisplayName("Spieler online") boolean playerCounter,
|
||||||
|
@DisplayName("Ticks pro Sekunde") boolean tps
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String title() {
|
||||||
|
return "Informationsleisten";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String description() {
|
||||||
|
return "Wähle anzuzeigende Informationsleisten aus";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Material icon() {
|
||||||
|
return Material.COMMAND_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InfoBarConfiguration defaultValue() {
|
||||||
|
return new InfoBarConfiguration(false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> dataType() {
|
||||||
|
return InfoBarConfiguration.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-40
@@ -1,62 +1,38 @@
|
|||||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
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.MsptBar;
|
||||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.PlayerCounterBar;
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.PlayerCounterBar;
|
||||||
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.TpsBar;
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.TpsBar;
|
||||||
import org.bukkit.NamespacedKey;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class InfoBars extends Appliance {
|
public class InfoBars extends Appliance {
|
||||||
private final NamespacedKey infoBarKey = new NamespacedKey(Main.instance(), "infobars");
|
|
||||||
private final List<Bar> infoBars = List.of(
|
private final List<Bar> infoBars = List.of(
|
||||||
new TpsBar(),
|
new TpsBar(),
|
||||||
new MsptBar(),
|
new MsptBar(),
|
||||||
new PlayerCounterBar()
|
new PlayerCounterBar()
|
||||||
);
|
);
|
||||||
|
|
||||||
public void showAll(Player player) {
|
public void showAllEnabled(Player player) {
|
||||||
this.getStoredBars(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 hideAll(Player player) {
|
public void setVisible(Player player, String bar, boolean visible) {
|
||||||
this.getStoredBars(player).forEach(bar -> this.hide(player, bar));
|
|
||||||
this.setStoredBars(player, List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show(Player player, String bar) {
|
|
||||||
this.validateBarName(bar);
|
this.validateBarName(bar);
|
||||||
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
|
if(visible) {
|
||||||
existingBars.add(bar);
|
|
||||||
player.showBossBar(this.getBarByName(bar).getBossBar());
|
player.showBossBar(this.getBarByName(bar).getBossBar());
|
||||||
this.setStoredBars(player, existingBars);
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
public void hide(Player player, String bar) {
|
|
||||||
this.validateBarName(bar);
|
|
||||||
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
|
|
||||||
existingBars.remove(bar);
|
|
||||||
player.hideBossBar(this.getBarByName(bar).getBossBar());
|
player.hideBossBar(this.getBarByName(bar).getBossBar());
|
||||||
this.setStoredBars(player, existingBars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getStoredBars(Player player) {
|
|
||||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
|
||||||
if(!container.has(this.infoBarKey)) return List.of();
|
|
||||||
return container.get(this.infoBarKey, PersistentDataType.LIST.strings());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setStoredBars(Player player, List<String> bars) {
|
|
||||||
player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bar getBarByName(String name) {
|
private Bar getBarByName(String name) {
|
||||||
@@ -71,8 +47,10 @@ public class InfoBars extends Appliance {
|
|||||||
throw new ApplianceCommand.Error(String.format("Ungültiger infobar name '%s'", name));
|
throw new ApplianceCommand.Error(String.format("Ungültiger infobar name '%s'", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Bar> getInfoBars() {
|
@Override
|
||||||
return this.infoBars;
|
public void onEnable() {
|
||||||
|
Settings.instance().declareSetting(InfoBarSetting.class);
|
||||||
|
Settings.instance().addChangeListener(InfoBarSetting.class, this::showAllEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,9 +62,4 @@ public class InfoBars extends Appliance {
|
|||||||
protected @NotNull List<Listener> listeners() {
|
protected @NotNull List<Listener> listeners() {
|
||||||
return List.of(new ShowPreviousBarsListener());
|
return List.of(new ShowPreviousBarsListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @NotNull List<ApplianceCommand<?>> commands() {
|
|
||||||
return List.of(new InfoBarCommand());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,6 +7,6 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
|||||||
class ShowPreviousBarsListener extends ApplianceListener<InfoBars> {
|
class ShowPreviousBarsListener extends ApplianceListener<InfoBars> {
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onJoin(PlayerJoinEvent event) {
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
// this.getAppliance().showAll(event.getPlayer());
|
this.getAppliance().showAllEnabled(event.getPlayer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -10,6 +10,8 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class MsptBar extends Bar {
|
public class MsptBar extends Bar {
|
||||||
|
public static String name = "msptd";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Duration refresh() {
|
protected Duration refresh() {
|
||||||
return Duration.ofSeconds(3);
|
return Duration.ofSeconds(3);
|
||||||
@@ -17,7 +19,7 @@ public class MsptBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String name() {
|
protected String name() {
|
||||||
return "mspt";
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -25,10 +27,10 @@ public class MsptBar extends Bar {
|
|||||||
return Component.text()
|
return Component.text()
|
||||||
.append(Component.text("M"))
|
.append(Component.text("M"))
|
||||||
.append(Component.text("illi", NamedTextColor.GRAY))
|
.append(Component.text("illi", NamedTextColor.GRAY))
|
||||||
.append(Component.text("S"))
|
.append(Component.text("s"))
|
||||||
.append(Component.text("econds ", NamedTextColor.GRAY))
|
.append(Component.text("ekunden ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("P"))
|
.append(Component.text("p"))
|
||||||
.append(Component.text("er ", NamedTextColor.GRAY))
|
.append(Component.text("ro ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("T"))
|
.append(Component.text("T"))
|
||||||
.append(Component.text("ick", NamedTextColor.GRAY))
|
.append(Component.text("ick", NamedTextColor.GRAY))
|
||||||
.append(Component.text(": "))
|
.append(Component.text(": "))
|
||||||
@@ -43,7 +45,7 @@ public class MsptBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BossBar.Color color() {
|
protected BossBar.Color color() {
|
||||||
return BossBar.Color.BLUE;
|
return this.currentMSPT() <= 50 ? BossBar.Color.GREEN : BossBar.Color.RED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+7
-2
@@ -12,6 +12,8 @@ import org.bukkit.Bukkit;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class PlayerCounterBar extends Bar {
|
public class PlayerCounterBar extends Bar {
|
||||||
|
public static String name = "playerCounter";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Duration refresh() {
|
protected Duration refresh() {
|
||||||
return Duration.ofSeconds(3);
|
return Duration.ofSeconds(3);
|
||||||
@@ -19,7 +21,7 @@ public class PlayerCounterBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String name() {
|
protected String name() {
|
||||||
return "playerCounter";
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -38,7 +40,10 @@ public class PlayerCounterBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BossBar.Color color() {
|
protected BossBar.Color color() {
|
||||||
return BossBar.Color.BLUE;
|
int freeSlots = this.getMaxPlayerCount() - this.getCurrentPlayerCount();
|
||||||
|
return freeSlots <= 0
|
||||||
|
? BossBar.Color.RED
|
||||||
|
: freeSlots < 5 ? BossBar.Color.YELLOW : BossBar.Color.GREEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+9
-5
@@ -10,6 +10,8 @@ import org.bukkit.Bukkit;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class TpsBar extends Bar {
|
public class TpsBar extends Bar {
|
||||||
|
public static String name = "tps";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Duration refresh() {
|
protected Duration refresh() {
|
||||||
return Duration.ofSeconds(3);
|
return Duration.ofSeconds(3);
|
||||||
@@ -17,7 +19,7 @@ public class TpsBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String name() {
|
protected String name() {
|
||||||
return "tps";
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -25,10 +27,10 @@ public class TpsBar extends Bar {
|
|||||||
return Component.text()
|
return Component.text()
|
||||||
.append(Component.text("T"))
|
.append(Component.text("T"))
|
||||||
.append(Component.text("icks ", NamedTextColor.GRAY))
|
.append(Component.text("icks ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("P"))
|
.append(Component.text("p"))
|
||||||
.append(Component.text("er ", NamedTextColor.GRAY))
|
.append(Component.text("ro ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("S"))
|
.append(Component.text("S"))
|
||||||
.append(Component.text("econds", NamedTextColor.GRAY))
|
.append(Component.text("ekunde", NamedTextColor.GRAY))
|
||||||
.append(Component.text(": "))
|
.append(Component.text(": "))
|
||||||
.append(Component.text(String.format("%.2f", this.currentTps()), ColorUtil.tpsColor(this.currentTps())))
|
.append(Component.text(String.format("%.2f", this.currentTps()), ColorUtil.tpsColor(this.currentTps())))
|
||||||
.build();
|
.build();
|
||||||
@@ -41,7 +43,9 @@ public class TpsBar extends Bar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BossBar.Color color() {
|
protected BossBar.Color color() {
|
||||||
return BossBar.Color.BLUE;
|
return this.currentTps() >= 18
|
||||||
|
? BossBar.Color.GREEN
|
||||||
|
: this.currentTps() >= 15 ? BossBar.Color.YELLOW : BossBar.Color.RED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+75
-34
@@ -1,12 +1,14 @@
|
|||||||
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
|
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.common.api.repositories.ReportRepository;
|
import eu.mhsl.craftattack.spawn.common.api.repositories.ReportRepository;
|
||||||
import eu.mhsl.craftattack.spawn.common.api.repositories.VaroReportRepository;
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners.ReportCreatedListener;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners.ReportJoinListener;
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||||
import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository;
|
import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.ComponentBuilder;
|
import net.kyori.adventure.text.ComponentBuilder;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
@@ -16,11 +18,14 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class Report extends Appliance {
|
public class Report extends Appliance {
|
||||||
public static Component helpText() {
|
public static Component helpText() {
|
||||||
@@ -64,7 +69,7 @@ public class Report extends Appliance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
|
private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
|
||||||
ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(VaroReportRepository.class)
|
ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(CraftAttackReportRepository.class) // TODO: Besser machen!!
|
||||||
.createReport(reportRequest);
|
.createReport(reportRequest);
|
||||||
|
|
||||||
switch(createdReport.status()) {
|
switch(createdReport.status()) {
|
||||||
@@ -79,7 +84,7 @@ public class Report extends Appliance {
|
|||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(
|
.append(
|
||||||
Component
|
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()))
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.data().url()))
|
||||||
)
|
)
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
@@ -115,7 +120,7 @@ public class Report extends Appliance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void queryReports(Player issuer) {
|
public void queryReports(Player issuer) {
|
||||||
ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(VaroReportRepository.class)
|
ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(CraftAttackReportRepository.class) // TODO: Besser machen!!
|
||||||
.queryReports(issuer.getUniqueId());
|
.queryReports(issuer.getUniqueId());
|
||||||
|
|
||||||
if(userReports.status() != 200) {
|
if(userReports.status() != 200) {
|
||||||
@@ -129,44 +134,80 @@ public class Report extends Appliance {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ReportRepository.PlayerReports.Report> reports = userReports
|
Function<List<ReportRepository.PlayerReports.Report>, List<ReportRepository.PlayerReports.Report>> filterClosed = reports -> reports.stream()
|
||||||
.data()
|
.filter(report -> Objects.equals(report.status(), ReportRepository.Status.closed))
|
||||||
.from_self()
|
.toList();
|
||||||
.stream()
|
|
||||||
.filter(report -> !report.draft())
|
|
||||||
.toList()
|
|
||||||
.reversed();
|
|
||||||
|
|
||||||
if(reports.isEmpty()) {
|
List<ReportRepository.PlayerReports.Report> reportsToOthers = filterClosed.apply(userReports.data().from_self()).reversed();
|
||||||
issuer.sendMessage(
|
List<ReportRepository.PlayerReports.Report> reportsToSelf = filterClosed.apply(userReports.data().to_self()).reversed();
|
||||||
Component.text()
|
|
||||||
|
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
|
||||||
|
.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)
|
||||||
|
);
|
||||||
|
|
||||||
|
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))
|
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
|
||||||
.appendNewline()
|
.appendNewline()
|
||||||
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
|
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY));
|
||||||
);
|
}
|
||||||
|
|
||||||
|
issuer.sendMessage(component.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendReportsInfo(Player player) {
|
||||||
|
ReqResp<ReportRepository.AllReports> allReportsResponse = this.queryRepository(CraftAttackReportRepository.class).queryAllReports();
|
||||||
|
|
||||||
|
if(allReportsResponse.status() != 200) {
|
||||||
|
Main.logger().warning("Failed to request Reports: " + allReportsResponse.status());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
|
List<ReportRepository.AllReports.Report> allOpenReports = allReportsResponse.data().reports().stream()
|
||||||
.append(Component.newline())
|
.filter(report -> report.status() == null && report.created() != null)
|
||||||
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
|
.toList();
|
||||||
.appendNewline();
|
|
||||||
|
|
||||||
reports.forEach(report -> {
|
if(allOpenReports.isEmpty()) return;
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
issuer.sendMessage(component.build());
|
player.sendMessage(
|
||||||
|
Component.text("Hey, es gibt noch ", NamedTextColor.GOLD)
|
||||||
|
.append(Component.text(allOpenReports.size(), NamedTextColor.RED))
|
||||||
|
.append(Component.text(" unbearbeitete Reports!", NamedTextColor.GOLD))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> listeners() {
|
||||||
|
return List.of(
|
||||||
|
new ReportCreatedListener(),
|
||||||
|
new ReportJoinListener()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+2
-1
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class ReportCommand extends ApplianceCommand.PlayerChecked<Report> {
|
|||||||
response = Stream.concat(
|
response = Stream.concat(
|
||||||
Bukkit.getOnlinePlayers().stream().map(Player::getName),
|
Bukkit.getOnlinePlayers().stream().map(Player::getName),
|
||||||
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
|
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
|
||||||
).toList();
|
).filter(Objects::nonNull).distinct().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(args.length == 2) {
|
if(args.length == 2) {
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.Report;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.event.ReportCreatedEvent;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
|
||||||
|
public class ReportCreatedListener extends ApplianceListener<Report> {
|
||||||
|
@EventHandler
|
||||||
|
public void onReport(ReportCreatedEvent event) {
|
||||||
|
OfflinePlayer reporter = Bukkit.getOfflinePlayer(event.getReport().reporter());
|
||||||
|
OfflinePlayer reported = Bukkit.getOfflinePlayer(event.getReport().reported());
|
||||||
|
|
||||||
|
Component message = Component.text(
|
||||||
|
"\uD83D\uDD14 Neuer Report von %s gegen %s: %s".formatted(reporter.getName(), reported.getName(), event.getReport().reason()),
|
||||||
|
NamedTextColor.YELLOW
|
||||||
|
);
|
||||||
|
|
||||||
|
Bukkit.getOnlinePlayers().stream()
|
||||||
|
.filter(player -> player.hasPermission("admin"))
|
||||||
|
.forEach(player -> player.sendMessage(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.Report;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
|
public class ReportJoinListener extends ApplianceListener<Report> {
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
if(!event.getPlayer().hasPermission("admin")) return;
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(
|
||||||
|
Main.instance(),
|
||||||
|
() -> this.getAppliance().sendReportsInfo(event.getPlayer())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-9
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Settings extends Appliance {
|
public class Settings extends Appliance {
|
||||||
@@ -34,7 +35,11 @@ public class Settings extends Appliance {
|
|||||||
ChatMentions,
|
ChatMentions,
|
||||||
DoubleDoors,
|
DoubleDoors,
|
||||||
KnockDoors,
|
KnockDoors,
|
||||||
BorderWarning
|
BorderWarning,
|
||||||
|
LocatorBar,
|
||||||
|
InfoBars,
|
||||||
|
CoordinateDisplay,
|
||||||
|
Bloodmoon
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Settings instance() {
|
public static Settings instance() {
|
||||||
@@ -58,6 +63,16 @@ public class Settings extends Appliance {
|
|||||||
|
|
||||||
private final WeakHashMap<Player, OpenSettingsInventory> openSettingsInventories = new WeakHashMap<>();
|
private final WeakHashMap<Player, OpenSettingsInventory> openSettingsInventories = new WeakHashMap<>();
|
||||||
private final WeakHashMap<Player, List<Setting<?>>> settingsCache = new WeakHashMap<>();
|
private final WeakHashMap<Player, List<Setting<?>>> settingsCache = new WeakHashMap<>();
|
||||||
|
protected final Map<Class<? extends Setting<?>>, Consumer<Player>> changeListeners = new WeakHashMap<>();
|
||||||
|
|
||||||
|
public <TDataType extends Setting<?>> void addChangeListener(Class<TDataType> setting, Consumer<Player> listener) {
|
||||||
|
this.changeListeners.merge(setting, listener, Consumer::andThen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <TDataType extends Setting<?>> void invokeChangeListener(Player player, Class<TDataType> setting) {
|
||||||
|
Optional.ofNullable(this.changeListeners.get(setting))
|
||||||
|
.ifPresent(listener -> listener.accept(player));
|
||||||
|
}
|
||||||
|
|
||||||
private List<Setting<?>> getSettings(Player player) {
|
private List<Setting<?>> getSettings(Player player) {
|
||||||
if(this.settingsCache.containsKey(player)) return this.settingsCache.get(player);
|
if(this.settingsCache.containsKey(player)) return this.settingsCache.get(player);
|
||||||
@@ -113,12 +128,12 @@ public class Settings extends Appliance {
|
|||||||
if(categorizedSettings.isEmpty()) return;
|
if(categorizedSettings.isEmpty()) return;
|
||||||
|
|
||||||
for(int i = 0; i < categorizedSettings.size(); i++) {
|
for(int i = 0; i < categorizedSettings.size(); i++) {
|
||||||
int slot = row.get() * 9 + i % 9;
|
if(i % 9 == 0 && i != 0) {
|
||||||
inventory.setItem(slot, categorizedSettings.get(i).buildItem());
|
|
||||||
|
|
||||||
if(i % 9 == 8) {
|
|
||||||
row.incrementAndGet();
|
row.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int slot = row.get() * 9 + i % 9;
|
||||||
|
inventory.setItem(slot, categorizedSettings.get(i).buildItem());
|
||||||
}
|
}
|
||||||
row.incrementAndGet();
|
row.incrementAndGet();
|
||||||
});
|
});
|
||||||
@@ -128,12 +143,12 @@ public class Settings extends Appliance {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for(int i = 0; i < uncategorizedSettings.size(); i++) {
|
for(int i = 0; i < uncategorizedSettings.size(); i++) {
|
||||||
int slot = row.get() * 9 + i % 9;
|
if(i % 9 == 0 && i != 0) {
|
||||||
inventory.setItem(slot, uncategorizedSettings.get(i).buildItem());
|
|
||||||
|
|
||||||
if(i % 9 == 8) {
|
|
||||||
row.incrementAndGet();
|
row.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int slot = row.get() * 9 + i % 9;
|
||||||
|
inventory.setItem(slot, uncategorizedSettings.get(i).buildItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
player.openInventory(inventory);
|
player.openInventory(inventory);
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ public class SettingsShortcutSetting extends BoolSetting implements CategorizedS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean defaultValue() {
|
protected Boolean defaultValue() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+20
-2
@@ -26,7 +26,7 @@ public abstract class Setting<TDataType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public NamespacedKey getNamespacedKey() {
|
public NamespacedKey getNamespacedKey() {
|
||||||
return new NamespacedKey(Main.instance(), this.key.name());
|
return new NamespacedKey(Settings.class.getSimpleName().toLowerCase(), this.key.name().toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Settings.Key getKey() {
|
public Settings.Key getKey() {
|
||||||
@@ -34,7 +34,24 @@ public abstract class Setting<TDataType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initializeFromPlayer(Player p) {
|
public void initializeFromPlayer(Player p) {
|
||||||
this.fromStorage(p.getPersistentDataContainer());
|
PersistentDataContainer dataContainer = p.getPersistentDataContainer();
|
||||||
|
try {
|
||||||
|
this.fromStorage(dataContainer);
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
Main.logger().warning(String.format(
|
||||||
|
"Could not load state of setting %s from player %s: '%s'\n Did the datatype of the setting change?",
|
||||||
|
this.getNamespacedKey(),
|
||||||
|
e.getMessage(),
|
||||||
|
p.getName()
|
||||||
|
));
|
||||||
|
dataContainer.remove(this.getNamespacedKey());
|
||||||
|
this.fromStorage(dataContainer);
|
||||||
|
Main.logger().info(String.format(
|
||||||
|
"Restoring defaults of setting %s of player %s",
|
||||||
|
this.getNamespacedKey(),
|
||||||
|
p.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void triggerChange(Player p, ClickType clickType) {
|
public void triggerChange(Player p, ClickType clickType) {
|
||||||
@@ -42,6 +59,7 @@ public abstract class Setting<TDataType> {
|
|||||||
this.change(p, clickType);
|
this.change(p, clickType);
|
||||||
InteractSounds.of(p).click();
|
InteractSounds.of(p).click();
|
||||||
this.toStorage(p.getPersistentDataContainer(), this.state());
|
this.toStorage(p.getPersistentDataContainer(), this.state());
|
||||||
|
Settings.instance().invokeChangeListener(p, this.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack buildItem() {
|
public ItemStack buildItem() {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiAutoTotem;
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiAutoTotem;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
|
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiAutoTotem;
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiAutoTotem;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
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")
|
||||||
|
@Appliance.Flags(enabled = false)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AntiIllegalSignCharacters extends Appliance {
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> listeners() {
|
||||||
|
return List.of(
|
||||||
|
new SignEditListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.block.SignChangeEvent;
|
||||||
|
|
||||||
|
import java.text.Normalizer;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class SignEditListener extends ApplianceListener<AntiIllegalSignCharacters> {
|
||||||
|
private static final Set<Integer> ALLOWED_CHARS = Set.of(
|
||||||
|
(int)' ', (int)'.', (int)',', (int)';', (int)':', (int)'!', (int)'?',
|
||||||
|
(int)'"', (int)'\'',
|
||||||
|
(int)'(', (int)')', (int)'[', (int)']', (int)'{', (int)'}',
|
||||||
|
(int)'-', (int)'_', (int)'+', (int)'=', (int)'/', (int)'\\',
|
||||||
|
(int)'@', (int)'#', (int)'$', (int)'%', (int)'&', (int)'*',
|
||||||
|
(int)'<', (int)'>', (int)'|',
|
||||||
|
(int)'~', (int)'`', (int)'^'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Set<Integer> ALLOWED_EXTRA = Set.of(
|
||||||
|
(int)'Ä', (int)'Ö', (int)'Ü', (int)'ä', (int)'ö', (int)'ü', (int)'ß',
|
||||||
|
(int)'€', (int)'°', (int)'µ'
|
||||||
|
);
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSignEdit(SignChangeEvent event) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Component line = event.line(i);
|
||||||
|
if (line == null) continue;
|
||||||
|
String plainString = PlainTextComponentSerializer.plainText().serialize(line);
|
||||||
|
plainString = Normalizer.normalize(plainString, Normalizer.Form.NFC);
|
||||||
|
String cleaned = filterAllowed(plainString);
|
||||||
|
event.line(i, Component.text(cleaned));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String filterAllowed(String s) {
|
||||||
|
StringBuilder out = new StringBuilder(s.length());
|
||||||
|
|
||||||
|
for (int off = 0; off < s.length(); ) {
|
||||||
|
int cp = s.codePointAt(off);
|
||||||
|
off += Character.charCount(cp);
|
||||||
|
|
||||||
|
if (isForbidden(cp)) continue;
|
||||||
|
|
||||||
|
if (Character.isLetterOrDigit(cp)) {
|
||||||
|
out.appendCodePoint(cp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ALLOWED_CHARS.contains(cp) || ALLOWED_EXTRA.contains(cp)) {
|
||||||
|
out.appendCodePoint(cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isForbidden(int cp) {
|
||||||
|
// Surrogates / invalid
|
||||||
|
if (cp >= 0xD800 && cp <= 0xDFFF) return true;
|
||||||
|
|
||||||
|
// Private Use Area (Mod/Pack-Icons/Placeholder)
|
||||||
|
if (cp >= 0xE000 && cp <= 0xF8FF) return true;
|
||||||
|
|
||||||
|
// Zero-width and control characters
|
||||||
|
if (cp == 0x200B || cp == 0x200C || cp == 0x200D || cp == 0xFEFF) return true;
|
||||||
|
|
||||||
|
// BiDi-Steuerzeichen
|
||||||
|
if (cp >= 0x202A && cp <= 0x202E) return true;
|
||||||
|
if (cp >= 0x2066 && cp <= 0x2069) return true;
|
||||||
|
|
||||||
|
return cp == '§';
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
|
import net.kyori.adventure.util.Ticks;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Appliance.Flags(enabled = false)
|
||||||
|
public class AntiInventoryMove extends Appliance {
|
||||||
|
private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2;
|
||||||
|
|
||||||
|
private final Map<UUID, Long> invOpen = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void setInvOpen(Player player, boolean open) {
|
||||||
|
if(open)
|
||||||
|
this.invOpen.put(player.getUniqueId(), System.currentTimeMillis());
|
||||||
|
else
|
||||||
|
this.invOpen.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasInventoryOpen(Player player) {
|
||||||
|
if(!this.invOpen.containsKey(player.getUniqueId())) return false;
|
||||||
|
return this.invOpen.get(player.getUniqueId()) < System.currentTimeMillis() - errorTimeMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> listeners() {
|
||||||
|
return List.of(
|
||||||
|
new InventoryTrackerListener(),
|
||||||
|
new InInventoryMoveListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||||
|
|
||||||
|
class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> {
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onOpen(InventoryOpenEvent event) {
|
||||||
|
if(!(event.getPlayer() instanceof Player player)) return;
|
||||||
|
if(event.isCancelled()) return;
|
||||||
|
this.getAppliance().setInvOpen(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onClose(InventoryCloseEvent event) {
|
||||||
|
if(!(event.getPlayer() instanceof Player player)) return;
|
||||||
|
this.getAppliance().setInvOpen(player, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-4
@@ -11,14 +11,20 @@ import org.bukkit.Bukkit;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class AcInform extends Appliance {
|
public class AcInform extends Appliance {
|
||||||
|
private final Map<String, Map<String, Long>> violationSlowdowns = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public void processCommand(@NotNull String[] args) {
|
public void processCommand(@NotNull String[] args) {
|
||||||
String anticheatName = null;
|
String anticheatName = null;
|
||||||
String playerName = null;
|
String playerName = null;
|
||||||
String checkName = null;
|
String checkName = null;
|
||||||
Float violationCount = null;
|
Float violationCount = null;
|
||||||
|
int notifyEvery = 0;
|
||||||
|
|
||||||
for(int i = 0; i < args.length; i++) {
|
for(int i = 0; i < args.length; i++) {
|
||||||
if(!args[i].startsWith("--")) continue;
|
if(!args[i].startsWith("--")) continue;
|
||||||
@@ -36,13 +42,32 @@ public class AcInform extends Appliance {
|
|||||||
case "--playerName" -> playerName = value;
|
case "--playerName" -> playerName = value;
|
||||||
case "--check" -> checkName = value;
|
case "--check" -> checkName = value;
|
||||||
case "--violationCount" -> violationCount = value.isEmpty() ? null : Float.valueOf(value);
|
case "--violationCount" -> violationCount = value.isEmpty() ? null : Float.valueOf(value);
|
||||||
|
case "--notifyEvery" -> notifyEvery = Integer.parseInt(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(notifyEvery == 0) {
|
||||||
|
this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
|
||||||
|
} else {
|
||||||
|
this.slowedNotifyAdmins(anticheatName, playerName, checkName, violationCount, notifyEvery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void slowedNotifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount, int notifyEvery) {
|
||||||
|
this.violationSlowdowns.putIfAbsent(playerName, new HashMap<>());
|
||||||
|
|
||||||
|
var slowdowns = this.violationSlowdowns.get(playerName);
|
||||||
|
if(slowdowns.containsKey(checkName)) {
|
||||||
|
if(slowdowns.get(checkName) > System.currentTimeMillis() - notifyEvery) return;
|
||||||
|
}
|
||||||
|
|
||||||
this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
|
this.notifyAdmins(anticheatName, playerName, checkName, violationCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount) {
|
public void notifyAdmins(@Nullable String anticheatName, @Nullable String playerName, @Nullable String checkName, @Nullable Float violationCount) {
|
||||||
|
this.violationSlowdowns.putIfAbsent(playerName, new HashMap<>());
|
||||||
|
this.violationSlowdowns.get(playerName).put(checkName, System.currentTimeMillis());
|
||||||
|
|
||||||
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
|
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
|
||||||
NamedTextColor textColor = NamedTextColor.GRAY;
|
NamedTextColor textColor = NamedTextColor.GRAY;
|
||||||
|
|
||||||
@@ -85,28 +110,42 @@ public class AcInform extends Appliance {
|
|||||||
Component.newline()
|
Component.newline()
|
||||||
.append(Component.text("⊥ ", NamedTextColor.GRAY))
|
.append(Component.text("⊥ ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("[", NamedTextColor.GRAY))
|
.append(Component.text("[", NamedTextColor.GRAY))
|
||||||
.append(Component.text("Report", NamedTextColor.GOLD))
|
.append(Component.text("\uD83D\uDCD6", NamedTextColor.GOLD))
|
||||||
.append(Component.text("]", NamedTextColor.GRAY))
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
.clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
|
.clickEvent(ClickEvent.suggestCommand(String.format("/report %s anticheat %s flagged %s", playerName, anticheatName, checkName)))
|
||||||
);
|
);
|
||||||
|
|
||||||
component.append(
|
component.append(
|
||||||
Component.text(" [", NamedTextColor.GRAY)
|
Component.text(" [", NamedTextColor.GRAY)
|
||||||
.append(Component.text("Kick", NamedTextColor.GOLD))
|
.append(Component.text("\u23F1", NamedTextColor.GOLD))
|
||||||
.append(Component.text("]", NamedTextColor.GRAY))
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
.clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
|
.clickEvent(ClickEvent.suggestCommand(String.format("/kick %s", playerName)))
|
||||||
);
|
);
|
||||||
|
|
||||||
component.append(
|
component.append(
|
||||||
Component.text(" [", NamedTextColor.GRAY)
|
Component.text(" [", NamedTextColor.GRAY)
|
||||||
.append(Component.text("Panic Ban", NamedTextColor.GOLD))
|
.append(Component.text("\uD83E\uDDB6", NamedTextColor.GOLD))
|
||||||
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
|
.clickEvent(ClickEvent.suggestCommand(String.format("/kickunsuspected %s", playerName)))
|
||||||
|
);
|
||||||
|
|
||||||
|
component.append(
|
||||||
|
Component.text(" [", NamedTextColor.GRAY)
|
||||||
|
.append(Component.text("\u2623", NamedTextColor.GOLD))
|
||||||
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
|
.clickEvent(ClickEvent.suggestCommand(String.format("/kickcrash %s", playerName)))
|
||||||
|
);
|
||||||
|
|
||||||
|
component.append(
|
||||||
|
Component.text(" [", NamedTextColor.GRAY)
|
||||||
|
.append(Component.text("\uD83D\uDD12", NamedTextColor.GOLD))
|
||||||
.append(Component.text("]", NamedTextColor.GRAY))
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
.clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName)))
|
.clickEvent(ClickEvent.suggestCommand(String.format("/panicban %s", playerName)))
|
||||||
);
|
);
|
||||||
|
|
||||||
component.append(
|
component.append(
|
||||||
Component.text(" [", NamedTextColor.GRAY)
|
Component.text(" [", NamedTextColor.GRAY)
|
||||||
.append(Component.text("Spectate/Teleport", NamedTextColor.GOLD))
|
.append(Component.text("\uD83D\uDC41", NamedTextColor.GOLD))
|
||||||
.append(Component.text("]", NamedTextColor.GRAY))
|
.append(Component.text("]", NamedTextColor.GRAY))
|
||||||
.clickEvent(ClickEvent.suggestCommand(String.format("/grim spectate %s", playerName)))
|
.clickEvent(ClickEvent.suggestCommand(String.format("/grim spectate %s", playerName)))
|
||||||
);
|
);
|
||||||
|
|||||||
+177
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
-1
@@ -1,9 +1,14 @@
|
|||||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick;
|
package eu.mhsl.craftattack.spawn.common.appliances.tooling.kick;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
|
||||||
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
|
import eu.mhsl.craftattack.spawn.core.util.text.DisconnectInfo;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.util.Ticks;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -25,9 +30,36 @@ public class Kick extends Appliance {
|
|||||||
).applyKick(player);
|
).applyKick(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void unsuspectedKick(@NotNull String playerName) {
|
||||||
|
Player player = Bukkit.getPlayer(playerName);
|
||||||
|
|
||||||
|
if(player == null)
|
||||||
|
throw new ApplianceCommand.Error("Player not found");
|
||||||
|
|
||||||
|
String material = Material.values()[(int)(Math.random() * Material.values().length)].name();
|
||||||
|
player.kick(Component.text("java.lang.IllegalStateException: Failed to create model for minecraft:%s".formatted(material)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void crashKick(@NotNull String playerName) {
|
||||||
|
Player player = Bukkit.getPlayer(playerName);
|
||||||
|
|
||||||
|
if(player == null)
|
||||||
|
throw new ApplianceCommand.Error("Player not found");
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
|
||||||
|
PlayerUtils.sendCube(player, 100, Material.ENCHANTING_TABLE.createBlockData());
|
||||||
|
PlayerUtils.sendCube(player, 5, Material.DIRT.createBlockData());
|
||||||
|
});
|
||||||
|
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> player.kick(Component.empty()), Ticks.TICKS_PER_SECOND * 15);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
protected List<ApplianceCommand<?>> commands() {
|
protected List<ApplianceCommand<?>> commands() {
|
||||||
return List.of(new KickCommand());
|
return List.of(
|
||||||
|
new KickCommand(),
|
||||||
|
new KickUnsuspectedCommand(),
|
||||||
|
new KickCrashCommand()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -19,6 +19,7 @@ class KickCommand extends ApplianceCommand<Kick> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
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().kick(
|
this.getAppliance().kick(
|
||||||
args[0],
|
args[0],
|
||||||
Arrays.stream(args).skip(1).collect(Collectors.joining(" "))
|
Arrays.stream(args).skip(1).collect(Collectors.joining(" "))
|
||||||
|
|||||||
+31
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -9,6 +9,7 @@ import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
|||||||
class PlayerLimiterListener extends ApplianceListener<PlayerLimit> {
|
class PlayerLimiterListener extends ApplianceListener<PlayerLimit> {
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onLogin(AsyncPlayerPreLoginEvent playerPreLoginEvent) {
|
public void onLogin(AsyncPlayerPreLoginEvent playerPreLoginEvent) {
|
||||||
|
if(Bukkit.getOnlinePlayers().size() >= this.getAppliance().getLimit()) {
|
||||||
playerPreLoginEvent.kickMessage(
|
playerPreLoginEvent.kickMessage(
|
||||||
new DisconnectInfo(
|
new DisconnectInfo(
|
||||||
"Hohe Serverauslastung",
|
"Hohe Serverauslastung",
|
||||||
@@ -17,8 +18,7 @@ class PlayerLimiterListener extends ApplianceListener<PlayerLimit> {
|
|||||||
playerPreLoginEvent.getUniqueId()
|
playerPreLoginEvent.getUniqueId()
|
||||||
).getComponent()
|
).getComponent()
|
||||||
);
|
);
|
||||||
|
|
||||||
if(Bukkit.getOnlinePlayers().size() >= this.getAppliance().getLimit())
|
|
||||||
playerPreLoginEvent.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_FULL);
|
playerPreLoginEvent.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_FULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!base.zip
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
|
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
||||||
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
||||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
implementation 'com.sparkjava:spark-core:2.9.4'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -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(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
|
||||||
|
|
||||||
Main.logger().info("Loading appliances...");
|
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(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 -> {
|
.map(applianceClass -> {
|
||||||
try {
|
try {
|
||||||
return (Appliance) applianceClass.getDeclaredConstructor().newInstance();
|
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);
|
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toList();
|
.toList());
|
||||||
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
|
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
|
||||||
|
|
||||||
Main.logger().info("Initializing appliances...");
|
Main.logger().info("Initializing appliances...");
|
||||||
this.appliances.forEach(appliance -> {
|
this.appliances.stream()
|
||||||
appliance.onEnable();
|
.filter(appliance -> {
|
||||||
appliance.initialize(this);
|
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()));
|
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
|
||||||
|
|
||||||
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
|
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
|
||||||
@@ -80,17 +89,34 @@ public final class Main extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
Main.logger().info("Disabling appliances...");
|
Main.logger().info("Disabling appliances...");
|
||||||
this.appliances.forEach(appliance -> {
|
this.appliances.forEach(appliance -> appliance.disableSequence(this));
|
||||||
Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
|
|
||||||
appliance.onDisable();
|
|
||||||
appliance.destruct(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
HandlerList.unregisterAll(this);
|
HandlerList.unregisterAll(this);
|
||||||
Bukkit.getScheduler().cancelTasks(this);
|
Bukkit.getScheduler().cancelTasks(this);
|
||||||
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
|
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Appliance restartAppliance(Class<? extends Appliance> applianceClass) {
|
||||||
|
Appliance oldAppliance = this.appliances.stream()
|
||||||
|
.filter(appliance -> appliance.getClass().equals(applianceClass))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
oldAppliance.disableSequence(this);
|
||||||
|
this.appliances.remove(oldAppliance);
|
||||||
|
Appliance newAppliance = this.createApplianceInstance(applianceClass);
|
||||||
|
this.appliances.add(newAppliance);
|
||||||
|
newAppliance.enableSequence(this);
|
||||||
|
return newAppliance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Appliance createApplianceInstance(Class<? extends Appliance> applianceClass) {
|
||||||
|
try {
|
||||||
|
return applianceClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch(Exception e) {
|
||||||
|
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public <T extends Appliance> T getAppliance(Class<T> clazz) {
|
public <T extends Appliance> T getAppliance(Class<T> clazz) {
|
||||||
return this.appliances.stream()
|
return this.appliances.stream()
|
||||||
.filter(clazz::isInstance)
|
.filter(clazz::isInstance)
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package eu.mhsl.craftattack.spawn.core.api.server;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
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 eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
import org.bukkit.configuration.ConfigurationSection;
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
import spark.Spark;
|
import spark.Spark;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@@ -21,6 +23,11 @@ public class HttpServer {
|
|||||||
|
|
||||||
Spark.get("/ping", (request, response) -> System.currentTimeMillis());
|
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)));
|
Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,12 +50,16 @@ public class HttpServer {
|
|||||||
this.applianceName = appliance.getClass().getSimpleName().toLowerCase();
|
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) {
|
public void get(String path, Function<Request, Object> onCall) {
|
||||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rawPost(String path, Function<Request, Object> onCall) {
|
public void rawPost(String path, BiFunction<Request, spark.Response, Object> onCall) {
|
||||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
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) {
|
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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
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(UUID reporter, UUID reported, String reason) {}
|
||||||
|
this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> {
|
||||||
|
Main.logger().info(String.format("New Report from Hook: (%s) Reporter: %s Reported: %s", createdReport.reason, createdReport.reporter, createdReport.reported));
|
||||||
|
SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason)));
|
||||||
|
return HttpServer.nothing;
|
||||||
|
}));
|
||||||
|
|
||||||
|
record CreatedStrike(UUID uuid) {}
|
||||||
|
this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> {
|
||||||
|
Main.logger().info(String.format("New Strike from Hook! (User %s)", createdStrike.uuid));
|
||||||
|
SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid)));
|
||||||
|
return HttpServer.nothing;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -23,6 +25,12 @@ import java.util.Optional;
|
|||||||
* Appliances can be enabled or disabled independent of other appliances
|
* Appliances can be enabled or disabled independent of other appliances
|
||||||
*/
|
*/
|
||||||
public abstract class Appliance {
|
public abstract class Appliance {
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Flags {
|
||||||
|
boolean enabled() default true;
|
||||||
|
boolean autoload() default true;
|
||||||
|
}
|
||||||
|
|
||||||
private String localConfigPath;
|
private String localConfigPath;
|
||||||
private List<Listener> listeners;
|
private List<Listener> listeners;
|
||||||
private List<ApplianceCommand<?>> commands;
|
private List<ApplianceCommand<?>> commands;
|
||||||
@@ -94,9 +102,20 @@ public abstract class Appliance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void destruct(@NotNull JavaPlugin plugin) {
|
public void destruct(@NotNull JavaPlugin plugin) {
|
||||||
|
if(this.listeners == null) return;
|
||||||
this.listeners.forEach(HandlerList::unregisterAll);
|
this.listeners.forEach(HandlerList::unregisterAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void enableSequence(JavaPlugin plugin) {
|
||||||
|
this.onEnable();
|
||||||
|
this.initialize(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableSequence(JavaPlugin plugin) {
|
||||||
|
this.onDisable();
|
||||||
|
this.destruct(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
|
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
|
||||||
return Main.instance().getAppliance(clazz);
|
return Main.instance().getAppliance(clazz);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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(UUID reporter, UUID 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,10 @@ public class NumberUtil {
|
|||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T extends Comparable<T>> T clamp(T value, T min, T max) {
|
||||||
|
if (value.compareTo(min) < 0) return min;
|
||||||
|
if (value.compareTo(max) > 0) return max;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package eu.mhsl.craftattack.spawn.core.util.entity;
|
package eu.mhsl.craftattack.spawn.core.util.entity;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.Statistic;
|
import org.bukkit.Statistic;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class PlayerUtils {
|
public class PlayerUtils {
|
||||||
public static void resetStatistics(Player player) {
|
public static void resetStatistics(Player player) {
|
||||||
for(Statistic statistic : Statistic.values()) {
|
for(Statistic statistic : Statistic.values()) {
|
||||||
@@ -30,4 +36,30 @@ public class PlayerUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void sendCube(Player player, int cubeSize, BlockData fakeBlock) {
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
int half = cubeSize / 2;
|
||||||
|
int cx = loc.getBlockX();
|
||||||
|
int cy = loc.getBlockY();
|
||||||
|
int cz = loc.getBlockZ();
|
||||||
|
|
||||||
|
int minY = world.getMinHeight();
|
||||||
|
int maxY = world.getMaxHeight() - 1;
|
||||||
|
|
||||||
|
Map<Location, BlockData> changes = new HashMap<>();
|
||||||
|
|
||||||
|
for (int x = cx - half; x <= cx + half; x++) {
|
||||||
|
for (int y = Math.max(cy - half, minY); y <= Math.min(cy + half, maxY); y++) {
|
||||||
|
for (int z = cz - half; z <= cz + half; z++) {
|
||||||
|
changes.put(new Location(world, x, y, z), fakeBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection UnstableApiUsage
|
||||||
|
player.sendMultiBlockChange(changes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import eu.mhsl.craftattack.spawn.core.util.statistics.ServerMonitor;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.ComponentBuilder;
|
import net.kyori.adventure.text.ComponentBuilder;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
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.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -19,7 +20,12 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
public class ComponentUtil {
|
public class ComponentUtil {
|
||||||
public static TextComponent pleaseWait() {
|
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() {
|
public static Component clearedSpace() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package eu.mhsl.craftattack.spawn.core.util.text;
|
package eu.mhsl.craftattack.spawn.core.util.text;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
|
||||||
public class DataSizeConverter {
|
public class DataSizeConverter {
|
||||||
public static String convertBytesPerSecond(long bytes) {
|
public static String convertBytesPerSecond(long bytes) {
|
||||||
double kbits = bytes * 8.0 / 1000.0;
|
double kbits = bytes * 8.0 / 1000.0;
|
||||||
@@ -52,4 +54,27 @@ public class DataSizeConverter {
|
|||||||
|
|
||||||
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCardinalDirection(Location location) {
|
||||||
|
float yaw = location.getYaw();
|
||||||
|
yaw = (yaw % 360 + 360) % 360;
|
||||||
|
|
||||||
|
if (yaw >= 337.5 || yaw < 22.5) {
|
||||||
|
return "S";
|
||||||
|
} else if (yaw >= 22.5 && yaw < 67.5) {
|
||||||
|
return "SW";
|
||||||
|
} else if (yaw >= 67.5 && yaw < 112.5) {
|
||||||
|
return "W";
|
||||||
|
} else if (yaw >= 112.5 && yaw < 157.5) {
|
||||||
|
return "NW";
|
||||||
|
} else if (yaw >= 157.5 && yaw < 202.5) {
|
||||||
|
return "N";
|
||||||
|
} else if (yaw >= 202.5 && yaw < 247.5) {
|
||||||
|
return "NO";
|
||||||
|
} else if (yaw >= 247.5 && yaw < 292.5) {
|
||||||
|
return "O";
|
||||||
|
} else {
|
||||||
|
return "SO";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.core.util.world;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
public class WorldUtils {
|
||||||
|
public static String getGameTime(World world) {
|
||||||
|
long timeOfDay = world.getTime() % 24000;
|
||||||
|
|
||||||
|
int hours = (int) ((timeOfDay / 1000 + 6) % 24);
|
||||||
|
int minutes = (int) ((timeOfDay % 1000) * 60 / 1000);
|
||||||
|
|
||||||
|
return String.format("%02d:%02d", hours, minutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ dependencies {
|
|||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT'
|
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
||||||
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
|
||||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
implementation 'com.sparkjava:spark-core:2.9.4'
|
||||||
|
|||||||
+9
-11
@@ -1,28 +1,26 @@
|
|||||||
package eu.mhsl.craftattack.spawn.craftattack.api.repositories;
|
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.HttpRepository;
|
||||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||||
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class FeedbackRepository extends HttpRepository {
|
public class FeedbackRepository extends HttpRepository {
|
||||||
public FeedbackRepository() {
|
public FeedbackRepository() {
|
||||||
super(CraftAttackApi.getBaseUri(), new RequestModifier(CraftAttackApi::withAuthorizationSecret, null));
|
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) {
|
public record Response(List<Feedback> feedback) {
|
||||||
final Type responseType = new TypeToken<Map<UUID, String>>() {
|
public record Feedback(UUID uuid, String url) {
|
||||||
}.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 ReqResp<Response> createFeedbackUrls(Request data) {
|
||||||
|
return this.post("feedback", data, Response.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -4,11 +4,12 @@ import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
|
|||||||
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
|
||||||
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
import eu.mhsl.craftattack.spawn.common.api.CraftAttackApi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class WhitelistRepository extends HttpRepository {
|
public class WhitelistRepository extends HttpRepository {
|
||||||
public WhitelistRepository() {
|
public WhitelistRepository() {
|
||||||
super(CraftAttackApi.getBaseUri(), new RequestModifier(CraftAttackApi::withAuthorizationSecret, null));
|
super(CraftAttackApi.getBaseUri(), new RequestModifier(null, CraftAttackApi::withAuthorizationHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UserData(
|
public record UserData(
|
||||||
@@ -16,15 +17,15 @@ public class WhitelistRepository extends HttpRepository {
|
|||||||
String username,
|
String username,
|
||||||
String firstname,
|
String firstname,
|
||||||
String lastname,
|
String lastname,
|
||||||
Long banned_until,
|
List<Strike> strikes
|
||||||
Long outlawed_until
|
|
||||||
) {
|
) {
|
||||||
|
public record Strike(long at, int weight) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReqResp<UserData> getUserData(UUID userId) {
|
public ReqResp<UserData> getUserData(UUID userId) {
|
||||||
return this.get(
|
return this.get(
|
||||||
"user",
|
"users/%s".formatted(userId.toString()),
|
||||||
parameters -> parameters.addParameter("uuid", userId.toString()),
|
|
||||||
UserData.class
|
UserData.class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -14,6 +14,11 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AntiSignEdit extends Appliance {
|
public class AntiSignEdit extends Appliance {
|
||||||
|
private final Component disallowMessage = Component.text(
|
||||||
|
"Nutze /settings um das Bearbeiten von Schildern zu aktivieren!",
|
||||||
|
NamedTextColor.RED
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
Settings.instance().declareSetting(SignEditSetting.class);
|
Settings.instance().declareSetting(SignEditSetting.class);
|
||||||
@@ -22,8 +27,9 @@ public class AntiSignEdit extends Appliance {
|
|||||||
public boolean preventSignEdit(Player p, SignSide sign) {
|
public boolean preventSignEdit(Player p, SignSide sign) {
|
||||||
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class);
|
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class);
|
||||||
if(setting.is(SignEditSetting.editable)) return false;
|
if(setting.is(SignEditSetting.editable)) return false;
|
||||||
|
|
||||||
if(setting.is(SignEditSetting.readOnly)) {
|
if(setting.is(SignEditSetting.readOnly)) {
|
||||||
p.sendActionBar(Component.text("Das Bearbeiten von Schildern ist in deinen Einstellungen deaktiviert.", NamedTextColor.RED));
|
p.sendActionBar(this.disallowMessage);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +38,7 @@ public class AntiSignEdit extends Appliance {
|
|||||||
.anyMatch(line -> !PlainTextComponentSerializer.plainText().serialize(line).isBlank());
|
.anyMatch(line -> !PlainTextComponentSerializer.plainText().serialize(line).isBlank());
|
||||||
|
|
||||||
if(hasText) {
|
if(hasText) {
|
||||||
p.sendActionBar(Component.text("Das Bearbeiten von Schildern, welch bereits beschrieben sind, ist bei dir deaktiviert.", NamedTextColor.RED));
|
p.sendActionBar(this.disallowMessage);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+324
@@ -0,0 +1,324 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon;
|
||||||
|
|
||||||
|
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.craftattack.appliances.gameplay.bloodmoon.commands.BloodmoonCommand;
|
||||||
|
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.*;
|
||||||
|
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.*;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
|
public class Bloodmoon extends Appliance {
|
||||||
|
public final Map<EntityType, Set<MobEffect>> affectedMobEffectMap = Map.ofEntries(
|
||||||
|
Map.entry(EntityType.ZOMBIE, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.SKELETON, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 3.5, 1)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.SPIDER, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.POISON, 4, 1),
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.NAUSEA, 6, 10)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.CREEPER, Set.of(
|
||||||
|
new MobEffect.LightningMobEffect()
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.HUSK, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.STRAY, Set.of()),
|
||||||
|
Map.entry(EntityType.DROWNED, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.WITCH, Set.of()),
|
||||||
|
Map.entry(EntityType.ZOMBIE_VILLAGER, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.WITHER, 7, 1)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.PHANTOM, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.LEVITATION, 1.5, 3)
|
||||||
|
)),
|
||||||
|
Map.entry(EntityType.ENDERMAN, Set.of(
|
||||||
|
new MobEffect.PotionMobEffect(PotionEffectType.SLOWNESS, 2.5, 2)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
public final int expMultiplier = 3;
|
||||||
|
public final double mobDamageMultiplier = 2;
|
||||||
|
public final double mobHealthMultiplier = 2;
|
||||||
|
|
||||||
|
private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
private final BossBar bossBar = BossBar.bossBar(
|
||||||
|
Component.text("Blutmond", NamedTextColor.DARK_RED),
|
||||||
|
1f,
|
||||||
|
BossBar.Color.RED,
|
||||||
|
BossBar.Overlay.NOTCHED_12,
|
||||||
|
Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN)
|
||||||
|
);
|
||||||
|
private final boolean hordesEnabled = true;
|
||||||
|
public final boolean bloodmoonSkippable = true;
|
||||||
|
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;
|
||||||
|
private final List<EntityType> hordeMobList = List.of(
|
||||||
|
EntityType.ZOMBIE,
|
||||||
|
EntityType.SKELETON,
|
||||||
|
EntityType.SPIDER
|
||||||
|
);
|
||||||
|
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
|
||||||
|
public final int ticksPerDay = 24000;
|
||||||
|
public final int bloodmoonLength = this.ticksPerDay / 2;
|
||||||
|
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
|
||||||
|
private boolean bossbarActive = false;
|
||||||
|
private final int bloodmoonFreeDaysAtStart = 3;
|
||||||
|
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(
|
||||||
|
new ItemStack(Material.IRON_INGOT, 5), 10,
|
||||||
|
new ItemStack(Material.GOLD_INGOT, 2), 5,
|
||||||
|
new ItemStack(Material.DIAMOND, 1), 1,
|
||||||
|
new ItemStack(Material.IRON_BLOCK, 1), 5,
|
||||||
|
new ItemStack(Material.GOLD_BLOCK, 1), 2
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
Settings.instance().declareSetting(BloodmoonSetting.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean bloodmoonIsActive() {
|
||||||
|
long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
|
||||||
|
long day = currentTick / this.ticksPerDay;
|
||||||
|
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false;
|
||||||
|
long time = currentTick % this.ticksPerDay;
|
||||||
|
return time >= this.bloodmoonStartTime && time <= this.bloodmoonStartTime + this.bloodmoonLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startBloodmoon() {
|
||||||
|
this.bossbarActive = true;
|
||||||
|
Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar);
|
||||||
|
this.startHordeSpawning(this.getRandomHordeSpawnDelay());
|
||||||
|
this.sendStartMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopBloodmoonIfInactive() {
|
||||||
|
Bukkit.getScheduler().runTaskLater(
|
||||||
|
Main.instance(),
|
||||||
|
() -> {
|
||||||
|
if(!this.bossbarActive) return;
|
||||||
|
if(this.bloodmoonIsActive()) return;
|
||||||
|
this.bossbarActive = false;
|
||||||
|
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
|
||||||
|
this.stopHordeSpawning();
|
||||||
|
this.sendStopMessages();
|
||||||
|
},
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not working for entity effects and debuffs...
|
||||||
|
public void forceStopBloodmoon() {
|
||||||
|
this.bossbarActive = false;
|
||||||
|
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
|
||||||
|
this.stopHordeSpawning();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long lastBloodmoonStartTick() {
|
||||||
|
long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
|
||||||
|
long day = currentTick / this.ticksPerDay;
|
||||||
|
long time = currentTick % this.ticksPerDay;
|
||||||
|
|
||||||
|
boolean todayIsBloodmoon = (day % this.bloodmoonDayInterval == 0) && (day > this.bloodmoonFreeDaysAtStart);
|
||||||
|
if (todayIsBloodmoon && time < this.bloodmoonStartTime) {
|
||||||
|
day -= this.bloodmoonDayInterval;
|
||||||
|
} else if (!todayIsBloodmoon) {
|
||||||
|
day -= (day % this.bloodmoonDayInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day <= this.bloodmoonFreeDaysAtStart) return -1L;
|
||||||
|
return day * this.ticksPerDay + this.bloodmoonStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartTick(long tick) {
|
||||||
|
long day = tick / this.ticksPerDay;
|
||||||
|
if(day % this.bloodmoonDayInterval != 0 || day <= this.bloodmoonFreeDaysAtStart) return false;
|
||||||
|
long time = tick % this.ticksPerDay;
|
||||||
|
return time == this.bloodmoonStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRandomHordeSpawnDelay() {
|
||||||
|
return this.hordeSpawnRateTicks + this.random.nextInt(this.hordeSpawnRateVariationTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startHordeSpawning(int delay) {
|
||||||
|
Bukkit.getOnlinePlayers().forEach(player -> this.startHordeSpawning(delay, player));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopHordeSpawning() {
|
||||||
|
this.hordeSpawnTasks.forEach((player, bukkitTask) -> {
|
||||||
|
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
|
||||||
|
if(task != null) task.cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startHordeSpawning(int delay, Player player) {
|
||||||
|
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
|
||||||
|
if(task != null) task.cancel();
|
||||||
|
BukkitTask newTask = Bukkit.getScheduler().runTaskLater(
|
||||||
|
Main.instance(),
|
||||||
|
() -> {
|
||||||
|
if(!this.bloodmoonIsActive()) return;
|
||||||
|
this.spawnRandomHorde(player);
|
||||||
|
this.startHordeSpawning(this.getRandomHordeSpawnDelay(), player);
|
||||||
|
},
|
||||||
|
delay
|
||||||
|
);
|
||||||
|
this.hordeSpawnTasks.put(player, newTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPlayerToBossBar(Player player) {
|
||||||
|
if(!this.getBloodmoonSetting(player)) return;
|
||||||
|
player.showBossBar(this.bossBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAffectedMob(Entity entity) {
|
||||||
|
return this.affectedMobEffectMap.containsKey(entity.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBloodmoonSetting(Player player) {
|
||||||
|
return Settings.instance().getSetting(
|
||||||
|
player,
|
||||||
|
Settings.Key.Bloodmoon,
|
||||||
|
Boolean.class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawnRandomHorde(Player player) {
|
||||||
|
if(!this.hordesEnabled) return;
|
||||||
|
if(!this.getBloodmoonSetting(player)) return;
|
||||||
|
if(!player.getGameMode().equals(GameMode.SURVIVAL)) return;
|
||||||
|
|
||||||
|
EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size()));
|
||||||
|
if(player.getLocation().getWorld().getEnvironment().equals(World.Environment.THE_END)) hordeEntityType = EntityType.ENDERMITE;
|
||||||
|
int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
|
||||||
|
this.spawnHorde(player, hordeSize, hordeEntityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendWarningMessage(Player p) {
|
||||||
|
if(!this.getBloodmoonSetting(p)) return;
|
||||||
|
p.sendMessage(Component.text("Der Blutmond waltet in diesem Augenblick!", NamedTextColor.RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendAnnouncementMessages() {
|
||||||
|
this.sendMessage(Component.text("Der Blutmond naht; morgen wird er den Himmel beherrschen", NamedTextColor.GOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPreStartMessages() {
|
||||||
|
this.sendMessage(Component.text("Der Himmel ist heute Nacht ungewöhnlich düster", NamedTextColor.DARK_PURPLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnHorde(Player player, int size, EntityType type) {
|
||||||
|
for(int i = 0; i < size; i++) {
|
||||||
|
double spawnRadiant = this.random.nextDouble(0, 2*Math.PI);
|
||||||
|
Location mobSpawnLocation = player.getLocation().add(
|
||||||
|
Math.sin(spawnRadiant)*this.hordeSpawnDistance,
|
||||||
|
0,
|
||||||
|
Math.cos(spawnRadiant)*this.hordeSpawnDistance
|
||||||
|
);
|
||||||
|
mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1);
|
||||||
|
player.getWorld().spawnEntity(mobSpawnLocation, type, CreatureSpawnEvent.SpawnReason.NATURAL);
|
||||||
|
player.getWorld().strikeLightningEffect(mobSpawnLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ItemStack> getRandomBonusDrops() {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ItemStack getRandomBonusDrop() {
|
||||||
|
int totalWeight = this.bonusDropWeightMap.values().stream().mapToInt(value -> value).sum();
|
||||||
|
int randomInt = this.random.nextInt(0, totalWeight + 1);
|
||||||
|
int cumulativeWeight = 0;
|
||||||
|
for(Map.Entry<ItemStack, Integer> entry : this.bonusDropWeightMap.entrySet()) {
|
||||||
|
cumulativeWeight += entry.getValue();
|
||||||
|
if(randomInt <= cumulativeWeight) return entry.getKey();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(Component message) {
|
||||||
|
if(Bukkit.getOnlinePlayers().isEmpty()) return;
|
||||||
|
List<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers().stream()
|
||||||
|
.filter(this::getBloodmoonSetting)
|
||||||
|
.toList();
|
||||||
|
onlinePlayers.forEach(player -> player.sendMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendStartMessages() {
|
||||||
|
this.sendMessage(
|
||||||
|
Component.empty()
|
||||||
|
.append(Component.text("Der Blutmond ist über uns", NamedTextColor.DARK_RED, TextDecoration.BOLD))
|
||||||
|
.appendNewline()
|
||||||
|
.append(Component.text("Erfahrung vervielfacht sich und Mobs lassen reichere Beute fallen", NamedTextColor.RED))
|
||||||
|
.appendNewline()
|
||||||
|
.append(Component.text("Mobs sind erstarkt und belegen dich mit finsteren Debuffs", NamedTextColor.RED))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendStopMessages() {
|
||||||
|
this.sendMessage(Component.text("Der Blutmond weicht...", NamedTextColor.GREEN, TextDecoration.BOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> listeners() {
|
||||||
|
return List.of(
|
||||||
|
new BloodmoonMonsterDeathListener(),
|
||||||
|
new BloodmoonPlayerJoinListener(),
|
||||||
|
new BloodmoonEntityDamageListener(),
|
||||||
|
new BloodmoonTimeListener(),
|
||||||
|
new BloodmoonTimeSkipListener()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<ApplianceCommand<?>> commands() {
|
||||||
|
return List.of(
|
||||||
|
new BloodmoonCommand()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
|
||||||
|
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.BoolSetting;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.world.InteractSounds;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.inventory.ClickType;
|
||||||
|
|
||||||
|
public class BloodmoonSetting extends BoolSetting implements CategorizedSetting {
|
||||||
|
public BloodmoonSetting() {
|
||||||
|
super(Settings.Key.Bloodmoon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SettingCategory category() {
|
||||||
|
return SettingCategory.Gameplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String title() {
|
||||||
|
return "Blutmond";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String description() {
|
||||||
|
return "Kämpfe während dem Blutmond gegen stärkere Monster mit besseren Drops. Kann nicht während dem Blutmond geändert werden!";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Material icon() {
|
||||||
|
return Material.SKELETON_SKULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean defaultValue() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Settings deaktivierbar machen
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void change(Player player, ClickType clickType) {
|
||||||
|
if(Main.instance().getAppliance(Bloodmoon.class).bloodmoonIsActive()) {
|
||||||
|
InteractSounds.of(player).delete();
|
||||||
|
player.sendMessage(Component.text("Während dem Blutmond kann diese Einstellung nicht geändert werden!", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.change(player, clickType);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon;
|
||||||
|
|
||||||
|
import net.kyori.adventure.util.Ticks;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
|
||||||
|
public abstract class MobEffect {
|
||||||
|
public abstract void apply(Player p);
|
||||||
|
|
||||||
|
public static class PotionMobEffect extends MobEffect {
|
||||||
|
private final PotionEffect effect;
|
||||||
|
public PotionMobEffect(PotionEffectType type, double seconds, int amplifier) {
|
||||||
|
this.effect = new PotionEffect(type, (int) (seconds * Ticks.TICKS_PER_SECOND), amplifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(Player p) {
|
||||||
|
p.addPotionEffect(this.effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LightningMobEffect extends MobEffect {
|
||||||
|
@Override
|
||||||
|
public void apply(Player p) {
|
||||||
|
p.getWorld().strikeLightning(p.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.commands;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||||
|
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
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 BloodmoonCommand extends ApplianceCommand<Bloodmoon> {
|
||||||
|
public BloodmoonCommand() {
|
||||||
|
super("bloodmoon");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
|
||||||
|
if(args.length == 0) {
|
||||||
|
sender.sendMessage("Bloodmoon is currently %s.".formatted(this.getAppliance().bloodmoonIsActive() ? "active" : "inactive"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(args[0]) {
|
||||||
|
case "start": {
|
||||||
|
this.getAppliance().startBloodmoon();
|
||||||
|
sender.sendMessage("Started bloodmoon.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "stop": {
|
||||||
|
this.getAppliance().forceStopBloodmoon();
|
||||||
|
sender.sendMessage("Stopped bloodmoon.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "time": {
|
||||||
|
sender.sendMessage(String.valueOf(Bukkit.getWorlds().getFirst().getFullTime()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: throw new Error("No such option: '%s' !".formatted(args[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("start", "stop");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Projectile;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
|
||||||
|
public class BloodmoonEntityDamageListener extends ApplianceListener<Bloodmoon> {
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamage(EntityDamageByEntityEvent event) {
|
||||||
|
if(!this.getAppliance().bloodmoonIsActive()) return;
|
||||||
|
Entity damagerEntity = event.getDamager();
|
||||||
|
if(damagerEntity instanceof Projectile projectile) {
|
||||||
|
if(!(projectile.getShooter() instanceof Entity shooter)) return;
|
||||||
|
damagerEntity = shooter;
|
||||||
|
}
|
||||||
|
if(!(damagerEntity instanceof LivingEntity damager && event.getEntity() instanceof LivingEntity receiver)) return;
|
||||||
|
if(event.getFinalDamage() == 0) return;
|
||||||
|
|
||||||
|
if(this.getAppliance().isAffectedMob(damager) && receiver instanceof Player player) {
|
||||||
|
if(!this.getAppliance().getBloodmoonSetting(player)) return;
|
||||||
|
event.setDamage(event.getDamage() * this.getAppliance().mobDamageMultiplier);
|
||||||
|
this.getAppliance().affectedMobEffectMap.get(damager.getType()).forEach(mobEffect -> mobEffect.apply(player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(this.getAppliance().isAffectedMob(receiver) && damager instanceof Player player) {
|
||||||
|
if(!this.getAppliance().getBloodmoonSetting(player)) return;
|
||||||
|
event.setDamage(event.getDamage() / this.getAppliance().mobHealthMultiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
|
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityDeathEvent;
|
||||||
|
|
||||||
|
public class BloodmoonMonsterDeathListener extends ApplianceListener<Bloodmoon> {
|
||||||
|
@EventHandler
|
||||||
|
public void onMonsterDeath(EntityDeathEvent event) {
|
||||||
|
if(!this.getAppliance().bloodmoonIsActive()) return;
|
||||||
|
if(!(event.getDamageSource().getCausingEntity() instanceof Player player)) return;
|
||||||
|
if(!this.getAppliance().getBloodmoonSetting(player)) return;
|
||||||
|
LivingEntity entity = event.getEntity();
|
||||||
|
if(!this.getAppliance().isAffectedMob(entity)) return;
|
||||||
|
|
||||||
|
event.setDroppedExp(event.getDroppedExp() * this.getAppliance().expMultiplier);
|
||||||
|
event.getDrops().addAll(this.getAppliance().getRandomBonusDrops());
|
||||||
|
entity.getWorld().strikeLightningEffect(entity.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user