41 Commits

Author SHA1 Message Date
239094971c Revert "simplified event message handling logic in ChatMessagesListener"
This reverts commit db13a9f0a2.
2025-11-02 14:18:43 +01:00
91a28b4500 Merge branch 'master' into develop-backendUpdate 2025-10-27 17:11:01 +01:00
e745ff4721 added AntiFormattedBook to detect and sanitize illegal book formatting 2025-10-27 17:10:44 +01:00
23af3ff784 Merge branch 'master' into develop-backendUpdate 2025-10-27 16:14:24 +01:00
bc5c9a2a13 added AntiIllegalBundlePicker to track and notify admins on illegal bundle interactions 2025-10-27 16:02:13 +01:00
c220479052 WIP: refactored report and feedback systems; updated repository models, APIs, and component utilities 2025-10-27 14:25:44 +01:00
78f87d97f0 Merge remote-tracking branch 'origin/master' 2025-10-19 12:54:35 +02:00
db13a9f0a2 simplified event message handling logic in ChatMessagesListener 2025-10-19 12:54:31 +02:00
09abfefe33 added PhantomReducer 2025-10-17 18:39:05 +02:00
bd3546abc8 changed report appliance to craftattack 2025-10-17 18:00:15 +02:00
8a7a0453ce updated Whitelist api for new Backend 2025-10-17 17:28:25 +02:00
64d0d817c0 Merge pull request 'added IronGolemAnimation' (#8) from develop-animatedIronGolem into master
Reviewed-on: #8
Reviewed-by: Lars Neuhaus <larslukasneuhaus@gmx.de>
2025-10-14 22:42:52 +00:00
713561bf07 adjusted Iron Golem animation viewer calculation to account for block distance 2025-10-12 23:07:09 +02:00
4be3e528b1 fixed spawn reason check for Iron Golem spawning logic in NaturalIronGolemSpawnEvent 2025-10-12 23:05:19 +02:00
53fca580f3 added IronGolemAnimation 2025-10-12 23:04:05 +02:00
20fb4bf9fb added example local Gradle tasks for deploying and uploading plugins 2025-10-12 21:31:35 +02:00
c42d259909 Merge pull request 'added RecoveryCompass' (#7) from develop-recoveryCompass into master
Reviewed-on: #7
Reviewed-by: Lars Neuhaus <larslukasneuhaus@gmx.de>
2025-10-11 00:42:05 +00:00
5c82c8d6da resolved pr comments 2025-10-11 00:04:09 +02:00
5910847172 added RecoveryCompass 2025-10-10 23:39:29 +02:00
7c254707c1 removed permanent coordinate broadcasting 2025-10-03 17:47:23 +02:00
9ee5f6e419 updated InfoBars to use Bukkit scheduler for data container updates; enhanced CoordinateDisplay with Unicode icons and adjusted text styling 2025-10-03 17:38:29 +02:00
e49c3b1987 added category support to CoordinateDisplaySetting to align with gameplay setting structure 2025-10-03 17:06:56 +02:00
5ca4c70a41 added CoordinateDisplay with settings preferences, directional updates, and time display 2025-10-03 17:06:20 +02:00
040cae6cd1 updated InfoBars: localized names, dynamic coloring, and progress clamping adjustments 2025-10-03 15:49:17 +02:00
324defc4a8 removed InfoBarCommand and integrated InfoBars with Settings preference handling 2025-10-03 15:30:36 +02:00
dc0003b91e Merge branch 'master-antiGrief' 2025-10-01 19:12:01 +02:00
d4a3c798f8 added LocatorBar preferences 2025-10-01 19:11:16 +02:00
c88c2ab6aa updated dependencies 2025-09-28 13:22:42 +02:00
32a20cd4c5 added antiGrief command 2025-09-28 12:59:18 +02:00
d7cc141b94 added antiGrief inhabited chunk time calculation 2025-09-27 07:42:13 +02:00
16d7347fd0 antigrief false positives tweaks 2025-09-27 07:30:23 +02:00
fdf3b5c73f smaller lavaCast detector to prevent natural flow detection 2025-09-21 13:31:21 +02:00
74f17e1b6d WIP: AntiGrief 2025-09-21 01:54:20 +02:00
0d18b81399 changed coloring of VaroRank 2025-09-21 01:06:29 +02:00
1fa5fdfeb7 tweaked settings defaults 2025-09-20 20:35:02 +02:00
3bec5f4cbd added confirmation on outlawed change 2025-09-20 20:30:47 +02:00
d38871eac9 added MendingReducer tweak 2025-09-20 14:43:19 +02:00
238df2feff added armadilloExpFarm tweaks 2025-09-20 13:01:41 +02:00
e752d7f73b moved AntiAutoTotem to common project 2025-09-20 12:51:48 +02:00
b0df982be3 made antiSignEdit message clearer 2025-09-20 12:16:26 +02:00
dc1b5957f6 WIP: different method for grief detection 2025-09-20 11:35:19 +02:00
74 changed files with 2070 additions and 290 deletions

View File

@@ -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.8-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'

View File

@@ -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);
}
} }

View File

@@ -6,20 +6,19 @@ 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
); );

View File

@@ -23,19 +23,18 @@ public abstract class ReportRepository extends HttpRepository {
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( public enum Status {
@NotNull String username, open,
@NotNull String uuid closed,
) {
} }
} }
} }

View File

@@ -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());
}
}

View File

@@ -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()
);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
} }

View File

@@ -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();
}
}

View File

@@ -0,0 +1,51 @@
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;
}
}

View File

@@ -1,11 +1,13 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars; package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.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.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.Bukkit;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -24,39 +26,42 @@ public class InfoBars extends Appliance {
new PlayerCounterBar() new PlayerCounterBar()
); );
public void showAll(Player player) { public void showAllEnabled(Player player) {
this.getStoredBars(player).forEach(bar -> this.show(player, bar)); this.getEnabledBars(player).forEach(bar -> this.show(player, bar));
} }
public void hideAll(Player player) { public void hideAllEnabled(Player player) {
this.getStoredBars(player).forEach(bar -> this.hide(player, bar)); this.getEnabledBars(player).forEach(bar -> this.hide(player, bar));
this.setStoredBars(player, List.of()); this.setEnabledBars(player, List.of());
} }
public void show(Player player, String bar) { public void show(Player player, String bar) {
this.validateBarName(bar); this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player)); List<String> existingBars = new ArrayList<>(this.getEnabledBars(player));
existingBars.add(bar); existingBars.add(bar);
player.showBossBar(this.getBarByName(bar).getBossBar()); player.showBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars); this.setEnabledBars(player, existingBars);
} }
public void hide(Player player, String bar) { public void hide(Player player, String bar) {
this.validateBarName(bar); this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player)); List<String> existingBars = new ArrayList<>(this.getEnabledBars(player));
existingBars.remove(bar); existingBars.remove(bar);
player.hideBossBar(this.getBarByName(bar).getBossBar()); player.hideBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars); this.setEnabledBars(player, existingBars);
} }
private List<String> getStoredBars(Player player) { private List<String> getEnabledBars(Player player) {
PersistentDataContainer container = player.getPersistentDataContainer(); PersistentDataContainer container = player.getPersistentDataContainer();
if(!container.has(this.infoBarKey)) return List.of(); if(!container.has(this.infoBarKey)) return List.of();
return container.get(this.infoBarKey, PersistentDataType.LIST.strings()); return container.get(this.infoBarKey, PersistentDataType.LIST.strings());
} }
private void setStoredBars(Player player, List<String> bars) { private void setEnabledBars(Player player, List<String> bars) {
player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars); Bukkit.getScheduler().runTask(
Main.instance(),
() -> player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars)
);
} }
private Bar getBarByName(String name) { private Bar getBarByName(String name) {
@@ -71,8 +76,16 @@ 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, player -> {
this.hideAllEnabled(player);
InfoBarSetting.InfoBarConfiguration config = Settings.instance().getSetting(player, Settings.Key.InfoBars, InfoBarSetting.InfoBarConfiguration.class);
if(config.mspt()) this.show(player, MsptBar.name);
if(config.playerCounter()) this.show(player, PlayerCounterBar.name);
if(config.tps()) this.show(player, TpsBar.name);
});
} }
@Override @Override
@@ -84,9 +97,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());
}
} }

View File

@@ -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());
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,12 +1,12 @@
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.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;
@@ -20,7 +20,9 @@ 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 +66,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 +81,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 +117,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,43 +131,50 @@ 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.PlayerReports.Report.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()
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
);
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text() ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
.append(Component.newline()) .append(Component.text(
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD)) !reportsToSelf.isEmpty()
.appendNewline(); ? "Du wurdest insgesamt %d mal von anderen Spielern gemeldet.".formatted(reportsToSelf.size())
: "Du wurdest von keinem anderen Spieler gemeldet.",
NamedTextColor.GOLD)
);
reports.forEach(report -> {
component
.append(Component.text(" - ", NamedTextColor.WHITE))
.append(
report.reported() != null
? Component.text(report.reported().username(), NamedTextColor.WHITE)
: Component.text("Unbekannt", NamedTextColor.YELLOW)
)
.append(Component.text(String.format(": %s", report.subject()), NamedTextColor.GRAY))
.clickEvent(ClickEvent.openUrl(report.url()))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)));
component.appendNewline(); component.appendNewline();
component.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD));
reportsToOthers.forEach(report -> {
Component button = Component.text("[\uD83D\uDC41/\uD83D\uDD8A]")
.clickEvent(ClickEvent.openUrl(report.url()))
.hoverEvent(HoverEvent.showText(ComponentUtil.clickLink(report.url())));
Component reportedDisplayName = report.reported() != null
? Component.text(Optional.ofNullable(Bukkit.getOfflinePlayer(report.reported()).getName()).orElse(report.reported().toString()), NamedTextColor.WHITE)
: Component.text("Unbekannt", NamedTextColor.YELLOW);
component
.appendNewline()
.append(Component.text(" \u27A1 ", NamedTextColor.GRAY))
.append(button)
.append(Component.text(" du gegen ", NamedTextColor.GRAY))
.append(reportedDisplayName)
.append(Component.text(String.format(": %s", report.reason()), NamedTextColor.GRAY));
}); });
if(reportsToOthers.isEmpty()) {
component
.appendNewline()
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY));
}
issuer.sendMessage(component.build()); issuer.sendMessage(component.build());
} }

View File

@@ -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,10 @@ public class Settings extends Appliance {
ChatMentions, ChatMentions,
DoubleDoors, DoubleDoors,
KnockDoors, KnockDoors,
BorderWarning BorderWarning,
LocatorBar,
InfoBars,
CoordinateDisplay
} }
public static Settings instance() { public static Settings instance() {
@@ -58,6 +62,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);

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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()
);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()
);
}
}

View File

@@ -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);
}
}

View File

@@ -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(" "))

View File

@@ -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);
} }
}
} }

View File

@@ -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.8-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'

View File

@@ -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;
}
} }

View File

@@ -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() {

View File

@@ -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";
}
}
} }

View File

@@ -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);
}
}

View File

@@ -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.8-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'

View File

@@ -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);
} }
} }

View File

@@ -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(int 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
); );
} }

View File

@@ -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;
} }
} }

View File

@@ -28,7 +28,7 @@ public class DoubleDoorSetting extends BoolSetting implements CategorizedSetting
@Override @Override
protected Boolean defaultValue() { protected Boolean defaultValue() {
return false; return true;
} }
@Override @Override

View File

@@ -0,0 +1,113 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.ironGolemAnimation;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.IronGolem;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class IronGolemAnimation extends Appliance {
record BlockChange(Block original, BlockData fakeBlock) {}
public void onGolemSpawn(IronGolem golem) {
this.modifyGolem(golem, false);
Location golemLocation = golem.getLocation();
BlockData bodyBlockData = Bukkit.createBlockData(Material.IRON_BLOCK);
BlockData headBlockData = Bukkit.createBlockData(
Material.CARVED_PUMPKIN,
blockData -> ((Directional) blockData).setFacing(golem.getFacing())
);
Vector facingVector = golem.getFacing().getDirection().rotateAroundY(Math.toRadians(90));
Block golemCenterBlock = golemLocation.getBlock().getRelative(BlockFace.UP);
List<BlockChange> buildBlocks = List.of(
new BlockChange(golemCenterBlock.getRelative(BlockFace.DOWN), bodyBlockData),
new BlockChange(golemCenterBlock, bodyBlockData),
new BlockChange(golemCenterBlock.getLocation().add(facingVector).getBlock(), bodyBlockData),
new BlockChange(golemCenterBlock.getLocation().add(facingVector.multiply(-1)).getBlock(), bodyBlockData),
new BlockChange(golemCenterBlock.getRelative(BlockFace.UP), headBlockData)
);
Collection<Player> viewers = golemLocation.getNearbyPlayers(golemLocation.getWorld().getViewDistance() * 16);
BiConsumer<Location, BlockData> changeBlockForViewers = (location, blockData) -> {
viewers.forEach(player -> player.sendBlockChange(location, blockData));
golem.getWorld().playSound(
location,
blockData.getSoundGroup().getPlaceSound(),
SoundCategory.BLOCKS,
1f,
1f
);
};
for(int i = 0; i < buildBlocks.size(); i++) {
BlockChange blockChange = buildBlocks.get(i);
Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> changeBlockForViewers.accept(blockChange.original.getLocation(), blockChange.fakeBlock),
6L * i
);
}
Consumer<List<BlockChange>> restoreBlockChanges = (blocks) -> {
buildBlocks.forEach((blockChange) -> changeBlockForViewers.accept(
blockChange.original().getLocation(),
blockChange.original.getBlockData()
));
this.modifyGolem(golem, true);
this.spawnEffect(buildBlocks);
};
Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> restoreBlockChanges.accept(buildBlocks),
6L * buildBlocks.size() + 2
);
}
private void spawnEffect(List<BlockChange> buildBlocks) {
buildBlocks.forEach((blockChange) -> {
World world = blockChange.original.getLocation().getWorld();
world.spawnParticle(
Particle.BLOCK,
blockChange.original.getLocation().add(0.5, 0.5, 0.5),
50,
blockChange.fakeBlock
);
world.playSound(
blockChange.original.getLocation(),
blockChange.fakeBlock.getSoundGroup().getBreakSound(),
SoundCategory.BLOCKS,
1f,
1f
);
});
}
public void modifyGolem(IronGolem golem, boolean setVisible) {
golem.setInvisible(!setVisible);
golem.setInvulnerable(!setVisible);
golem.setAI(setVisible);
golem.setGravity(setVisible);
golem.setCollidable(setVisible);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new NaturalIronGolemSpawnEvent()
);
}
}

View File

@@ -0,0 +1,15 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.ironGolemAnimation;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.IronGolem;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
class NaturalIronGolemSpawnEvent extends ApplianceListener<IronGolemAnimation> {
@EventHandler
public void onGolemSpawn(CreatureSpawnEvent event) {
if(!(event.getEntity() instanceof IronGolem golem)) return;
if(event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE) return;
this.getAppliance().onGolemSpawn(golem);
}
}

View File

@@ -45,6 +45,6 @@ public class KnockDoorSetting extends SelectSetting implements CategorizedSettin
@Override @Override
protected Options.Option defaultValue() { protected Options.Option defaultValue() {
return disabled; return knockThreeTimes;
} }
} }

View File

@@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.locatorBar;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
public class LocatorBar extends Appliance {
private enum Distance {
MAX(6.0e7),
ZERO(0.0);
final double distance;
Distance(double distance) {
this.distance = distance;
}
}
@Override
public void onEnable() {
Settings.instance().declareSetting(LocatorBarSettings.class);
Settings.instance().addChangeListener(LocatorBarSettings.class, this::updateLocatorBar);
}
public void updateLocatorBar(Player player) {
boolean enabled = Settings.instance().getSetting(player, Settings.Key.LocatorBar, Boolean.class);
AttributeInstance receive = player.getAttribute(Attribute.WAYPOINT_RECEIVE_RANGE);
AttributeInstance transmit = player.getAttribute(Attribute.WAYPOINT_TRANSMIT_RANGE);
Objects.requireNonNull(receive);
Objects.requireNonNull(transmit);
receive.setBaseValue(enabled ? Distance.MAX.distance : Distance.ZERO.distance);
transmit.setBaseValue(enabled ? Distance.MAX.distance : Distance.ZERO.distance);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new LocatorBarUpdateListener()
);
}
}

View File

@@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.locatorBar;
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 org.bukkit.Material;
public class LocatorBarSettings extends BoolSetting implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
public LocatorBarSettings() {
super(Settings.Key.LocatorBar);
}
@Override
protected String title() {
return "Ortungsleiste / Locator Bar";
}
@Override
protected String description() {
return "Konfiguriere, ob andere Spieler deine Position und du die Position anderer sehen möchtest";
}
@Override
protected Material icon() {
return Material.COMPASS;
}
@Override
protected Boolean defaultValue() {
return true;
}
}

View File

@@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.locatorBar;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class LocatorBarUpdateListener extends ApplianceListener<LocatorBar> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().updateLocatorBar(event.getPlayer());
}
}

View File

@@ -8,6 +8,7 @@ import eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.whitelist.Whitel
import eu.mhsl.craftattack.spawn.core.config.Configuration; import eu.mhsl.craftattack.spawn.core.config.Configuration;
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.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -48,6 +49,29 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
); );
} }
void askForConfirmation(Player player) {
Component confirmationMessage = switch(this.getLawStatus(player)) {
case DISABLED -> Component.text("Wenn du Vogelfrei aktivierst, darfst du von allen Spielern grundlos angegriffen werden.");
case VOLUNTARILY -> Component.text("Wenn du Vogelfrei deaktivierst, darfst du nicht mehr grundlos von Spielern angegriffen werden.");
case FORCED -> Component.text("Du darfst zurzeit deinen Vogelfreistatus nicht ändern, da dieser als Strafe auferlegt wurde!");
};
String command = String.format("/%s confirm", OutlawedCommand.commandName);
Component changeText = Component.text(
String.format(
"Zum ändern deines Vogelfrei status klicke auf diese Nachricht oder tippe '%s'",
command
),
NamedTextColor.GOLD
).clickEvent(ClickEvent.suggestCommand(command));
player.sendMessage(
Component.text()
.append(confirmationMessage.color(NamedTextColor.RED))
.appendNewline()
.append(changeText)
);
}
void switchLawStatus(Player player) throws OutlawChangeNotPermitted { void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
if(this.getLawStatus(player).equals(Status.FORCED)) { if(this.getLawStatus(player).equals(Status.FORCED)) {
throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden."); throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden.");
@@ -103,7 +127,7 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
public Component getStatusDescription(Status status) { public Component getStatusDescription(Status status) {
return switch(status) { return switch(status) {
case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN) case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN)
.append(Component.text("Es gelten die Standard Regeln!", NamedTextColor.GOLD)); .append(Component.text("Es gelten die normalen Regeln!", NamedTextColor.GOLD));
case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED) case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED)
.append(Component.text("Du darfst von jedem angegriffen und getötet werden!", NamedTextColor.GOLD)); .append(Component.text("Du darfst von jedem angegriffen und getötet werden!", NamedTextColor.GOLD));

View File

@@ -8,20 +8,23 @@ import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> { class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
public static final String commandName = "vogelfrei";
public OutlawedCommand() { public OutlawedCommand() {
super("vogelfrei"); super(commandName);
} }
@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 && args[0].equals("confirm")) {
try { try {
this.getAppliance().switchLawStatus(this.getPlayer()); this.getAppliance().switchLawStatus(this.getPlayer());
sender.sendMessage( sender.sendMessage(this.getAppliance().getStatusDescription(this.getAppliance().getLawStatus(this.getPlayer())));
this.getAppliance()
.getStatusDescription(this.getAppliance().getLawStatus(this.getPlayer()))
);
} catch(OutlawChangeNotPermitted e) { } catch(OutlawChangeNotPermitted e) {
sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED)); sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
} }
} else {
this.getAppliance().askForConfirmation(this.getPlayer());
}
} }
} }

View File

@@ -0,0 +1,32 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.recoveryCompass;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
import java.util.Objects;
class KeepRecoveryCompassOnDeathListener extends ApplianceListener<RecoveryCompass> {
@EventHandler
public void onDeath(PlayerDeathEvent event) {
ItemStack source = event.getDrops().stream()
.filter(Objects::nonNull)
.filter(item -> item.getType() == Material.RECOVERY_COMPASS)
.findFirst()
.orElse(null);
if (source == null) return;
if (source.getAmount() > 1) {
source.setAmount(source.getAmount() - 1);
} else {
event.getDrops().remove(source);
}
ItemStack kept = source.clone();
kept.setAmount(1);
event.getItemsToKeep().add(kept);
}
}

View File

@@ -0,0 +1,24 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.recoveryCompass;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
class PlayerFirstJoinCompassGift extends ApplianceListener<RecoveryCompass> {
private final NamespacedKey alreadyGiftedKey = new NamespacedKey(this.getClass().getSimpleName().toLowerCase(), "alreadyGifted".toLowerCase());
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
PersistentDataContainer container = player.getPersistentDataContainer();
if(container.has(alreadyGiftedKey)) return;
player.getInventory().addItem(ItemStack.of(Material.RECOVERY_COMPASS));
container.set(alreadyGiftedKey, PersistentDataType.BOOLEAN, true);
}
}

View File

@@ -0,0 +1,17 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.recoveryCompass;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class RecoveryCompass extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new PlayerFirstJoinCompassGift(),
new KeepRecoveryCompassOnDeathListener()
);
}
}

View File

@@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.feedback; package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp; import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.craftattack.api.repositories.FeedbackRepository; import eu.mhsl.craftattack.spawn.craftattack.api.repositories.FeedbackRepository;
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;
@@ -9,7 +9,6 @@ import eu.mhsl.craftattack.spawn.core.api.HttpStatus;
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.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@@ -18,32 +17,27 @@ 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.Map;
import java.util.UUID;
public class Feedback extends Appliance { public class Feedback extends Appliance {
public Feedback() { public Feedback() {
super("feedback"); super("feedback");
} }
public void requestFeedback(String eventName, List<Player> receivers, @Nullable String question) { public void requestFeedback(String eventName, String title, List<Player> receivers, @Nullable String question) {
ReqResp<Map<UUID, String>> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls( ReqResp<FeedbackRepository.Response> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls(
new FeedbackRepository.Request(eventName, receivers.stream().map(Entity::getUniqueId).toList()) new FeedbackRepository.Request(eventName, title, receivers.stream().map(Entity::getUniqueId).toList())
); );
System.out.println(response.toString()); if(response.status() != HttpStatus.OK) throw new RuntimeException();
System.out.println(response.status());
if(response.status() != HttpStatus.CREATED) throw new RuntimeException();
Component border = Component.text("-".repeat(40), NamedTextColor.GRAY); Component border = Component.text("-".repeat(40), NamedTextColor.GRAY);
receivers.forEach(player -> { receivers.forEach(player -> {
String feedbackUrl = response.data().get(player.getUniqueId()); String feedbackUrl = response.data().feedback().stream()
if(feedbackUrl == null) { .filter(feedback -> feedback.uuid().equals(player.getUniqueId()))
Main.logger().warning(String.format("FeedbackUrl not found for player '%s' from backend!", player.getUniqueId())); .findFirst()
return; .orElseThrow()
} .url();
ComponentBuilder<TextComponent, TextComponent.Builder> message = Component.text() ComponentBuilder<TextComponent, TextComponent.Builder> message = Component.text()
.append(border) .append(border)
@@ -58,8 +52,7 @@ public class Feedback extends Appliance {
message message
.append(Component.text("Klicke hier und gib uns Feedback, damit wir dein Spielerlebnis verbessern können!", NamedTextColor.DARK_GREEN) .append(Component.text("Klicke hier und gib uns Feedback, damit wir dein Spielerlebnis verbessern können!", NamedTextColor.DARK_GREEN)
.clickEvent(ClickEvent.openUrl(feedbackUrl))) .hoverEvent(HoverEvent.showText(ComponentUtil.clickLink(feedbackUrl))))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um Feedback zu geben.")))
.appendNewline() .appendNewline()
.append(border); .append(border);

View File

@@ -22,6 +22,7 @@ class FeedbackCommand extends ApplianceCommand.PlayerChecked<Feedback> {
Main.instance(), Main.instance(),
() -> this.getAppliance().requestFeedback( () -> this.getAppliance().requestFeedback(
"self-issued-ingame", "self-issued-ingame",
"Dein Feedback an uns",
List.of(this.getPlayer()), List.of(this.getPlayer()),
null null
) )

View File

@@ -20,6 +20,7 @@ class RequestFeedbackCommand extends ApplianceCommand<Feedback> {
Main.instance(), Main.instance(),
() -> this.getAppliance().requestFeedback( () -> this.getAppliance().requestFeedback(
"admin-issued-ingame", "admin-issued-ingame",
"Hilf uns dein Spielerlebnis zu verbessern!",
new ArrayList<>(Bukkit.getOnlinePlayers()), String.join(" ", args) new ArrayList<>(Bukkit.getOnlinePlayers()), String.join(" ", args)
) )
); );

View File

@@ -17,9 +17,9 @@ public class VaroRank extends Appliance implements DisplayName.Prefixed {
private List<UUID> winners = new ArrayList<>(); private List<UUID> winners = new ArrayList<>();
private List<UUID> mostKills = new ArrayList<>(); private List<UUID> mostKills = new ArrayList<>();
private final Component winnerBadge = Component.text("\uD83D\uDC51", NamedTextColor.YELLOW) private final Component winnerBadge = Component.text("\uD83D\uDC51", NamedTextColor.GOLD)
.hoverEvent(HoverEvent.showText(Component.text("Hat zusammen mit seinem Team Varo gewonnen"))); .hoverEvent(HoverEvent.showText(Component.text("Hat zusammen mit seinem Team Varo gewonnen")));
private final Component killBadge = Component.text("\uD83D\uDDE1", NamedTextColor.RED) private final Component killBadge = Component.text("\uD83D\uDDE1", NamedTextColor.GOLD)
.hoverEvent(HoverEvent.showText(Component.text("Hat zusammen mit seinem Team die meisten Kills in Varo"))); .hoverEvent(HoverEvent.showText(Component.text("Hat zusammen mit seinem Team die meisten Kills in Varo")));
public VaroRank() { public VaroRank() {

View File

@@ -0,0 +1,350 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.util.NumberUtil;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.commands.AntiGriefCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.kyori.adventure.text.Component.text;
public class AntiGrief extends Appliance {
public record GriefIncident(
long timestamp,
UUID worldId,
int x,
int y,
int z,
String event,
String data,
Severity severity
) {
public GriefIncident(Location loc, Event event, @Nullable Object data, Severity severity) {
this(
System.currentTimeMillis(),
loc.getWorld().getUID(),
loc.getBlockX(),
loc.getBlockY(),
loc.getBlockZ(),
event.getEventName(),
String.valueOf(data),
severity
);
}
public enum Severity {
/**
* No direct severity, but possible beginning of an incident
*/
INFO(0.5f),
/**
* Direct interaction which can lead to damage
*/
LIGHT(1),
/**
* Direkt interaction which can spread to severe incidents
*/
MODERATE(3),
/**
* Direct and most likely harmful interaction
*/
SEVERE(5);
public final float weight;
Severity(float weight) {
this.weight = weight;
}
}
}
public static final class AreaState {
public final UUID worldId;
public final int chunkX, chunkZ;
/** Rolling bucket scores for Spike Detection. */
public final NavigableMap<Long, Double> scores = new ConcurrentSkipListMap<>();
/** Incidents per Bucket */
public final Map<Long, List<GriefIncident>> incidentsByBucket = new ConcurrentHashMap<>();
public volatile double ema = 0.0;
public volatile long lastAlertAt = 0L;
AreaState(UUID worldId, int chunkX, int chunkZ) {
this.worldId = worldId;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
}
public long getInhabitedTime() {
return Objects.requireNonNull(Bukkit.getWorld(this.worldId))
.getChunkAt(this.chunkX, this.chunkX).getInhabitedTime();
}
void addIncident(GriefIncident incident) {
long b = incident.timestamp / BUCKET_DURATION_MS;
this.scores.merge(b, (double) incident.severity.weight, Double::sum);
this.incidentsByBucket
.computeIfAbsent(b, k -> Collections.synchronizedList(new ArrayList<>()))
.add(incident);
}
double currentScore(long bucketIdx) {
return this.scores.getOrDefault(bucketIdx, 0.0);
}
void prune(long bucket) {
long oldest = bucket - BUCKETS_PER_CHUNK;
this.scores.headMap(oldest, true).clear();
this.incidentsByBucket.keySet().removeIf(b -> b < oldest);
}
boolean isEmpty() {
return this.scores.isEmpty() && this.incidentsByBucket.isEmpty();
}
}
/** Duration of a time bucket in milliseconds. */
private static final long BUCKET_DURATION_MS = 60 * 1000;
/** Number of buckets kept in memory per area. Defines analysis window length. */
private static final int BUCKETS_PER_CHUNK = 30;
/** Maximum retention time for individual incidents in milliseconds. */
private static final long INCIDENT_RETAIN_MS = 60 * 60 * 1000;
/** Spike factor against EMA baseline. Triggers if current score >= baseline * FACTOR_SPIKE. */
private static final double FACTOR_SPIKE = 5.0;
/** Absolute threshold for spike detection. Triggers if current score exceeds this value. */
private static final double HARD_THRESHOLD = 50.0;
/** Cooldown time in ms to suppress repeated alerts for the same area. */
private static final long ALERT_COOLDOWN_MS = 2 * 60 * 1000;
/** Smoothing factor for EMA baseline. Lower = smoother, higher = more reactive. 0.0 < EMA_ALPHA <= 1.0 */
private static final double EMA_ALPHA = 0.2;
/** Minimal chunk inhabited time to start registering scores linearly to INHABITED_FULL_MS */
private static final int INHABITED_MIN_MS = 60 * 60 * 1000;
/** Max time to reach 100% effect on the score */
private static final int INHABITED_FULL_MS = 24 * 60 * 60 * 1000;
/** Stores direct incidents mapped by player UUID. */
private final Map<UUID, Set<GriefIncident>> directGriefRegistry = new ConcurrentHashMap<>();
/** Stores passive incidents mapped by chunk key. */
private final Map<Long, Set<GriefIncident>> passiveGriefRegistry = new ConcurrentHashMap<>();
/** Stores scores by area */
private final Map<Long, AreaState> areas = new ConcurrentHashMap<>();
public void trackDirect(Player player, GriefIncident incident) {
this.directGriefRegistry
.computeIfAbsent(player.getUniqueId(), uuid -> ConcurrentHashMap.newKeySet())
.add(incident);
this.trackPassive(player.getLocation().getChunk(), incident);
}
public void trackPassive(Chunk chunk, GriefIncident incident) {
this.passiveGriefRegistry
.computeIfAbsent(chunk.getChunkKey(), aLong -> ConcurrentHashMap.newKeySet())
.add(incident);
final long areaKey = this.packArea(incident.worldId, chunk.getX(), chunk.getZ());
this.areas
.computeIfAbsent(areaKey, key -> new AreaState(incident.worldId, chunk.getX(), chunk.getZ()))
.addIncident(incident);
}
@Override
public void onEnable() {
Bukkit.getScheduler().runTaskTimerAsynchronously(Main.instance(), () -> {
final long now = System.currentTimeMillis();
final long bucketIdx = this.bucketIdx(now);
this.areas.forEach((areaKey, state) -> {
final double currentScore = state.currentScore(bucketIdx);
if (currentScore <= 0.0) return;
final double adjustedScore = adjustScoreToInhabitantTime(state, currentScore);
if (adjustedScore <= 0.0) return;
final double base = (state.ema == 0.0) ? adjustedScore : state.ema;
final double newBase = EMA_ALPHA * adjustedScore + (1 - EMA_ALPHA) * base;
state.ema = Math.max(3, newBase);
final boolean spike = adjustedScore >= HARD_THRESHOLD || adjustedScore >= base * FACTOR_SPIKE;
if (spike && (now - state.lastAlertAt) >= ALERT_COOLDOWN_MS) {
state.lastAlertAt = now;
Bukkit.getScheduler().runTask(Main.instance(), () ->
this.alertAdmins(areaKey, bucketIdx, adjustedScore, newBase)
);
}
});
}, Ticks.TICKS_PER_SECOND, Ticks.TICKS_PER_SECOND);
Bukkit.getScheduler().runTaskTimerAsynchronously(Main.instance(), () -> {
final long cutoff = System.currentTimeMillis() - INCIDENT_RETAIN_MS;
final long nowBucket = this.bucketIdx(System.currentTimeMillis());
this.directGriefRegistry.entrySet().removeIf(e -> {
e.getValue().removeIf(inc -> inc.timestamp < cutoff);
return e.getValue().isEmpty();
});
this.passiveGriefRegistry.entrySet().removeIf(e -> {
e.getValue().removeIf(inc -> inc.timestamp < cutoff);
return e.getValue().isEmpty();
});
this.areas.entrySet().removeIf(en -> {
AreaState state = en.getValue();
state.prune(nowBucket);
return state.isEmpty();
});
}, Ticks.TICKS_PER_SECOND * 30, Ticks.TICKS_PER_SECOND * 30);
}
private static double adjustScoreToInhabitantTime(AreaState state, double currentScore) {
final long inhabitedMs = state.getInhabitedTime() * Ticks.SINGLE_TICK_DURATION_MS / Ticks.TICKS_PER_SECOND;
double factor = (double) (inhabitedMs - INHABITED_MIN_MS) / (double) (INHABITED_FULL_MS - INHABITED_MIN_MS);
factor = NumberUtil.clamp(factor, 0.0, 1.0);
return currentScore * factor;
}
private void alertAdmins(long areaKey, long bucketIdx, double curr, double baseline) {
AreaState meta = this.areas.get(areaKey);
if (meta == null) return;
int cx = meta.chunkX, cz = meta.chunkZ;
UUID worldId = meta.worldId;
World world = Bukkit.getWorld(worldId);
if (world == null) return;
int bx = (cx << 4) + 8;
int bz = (cz << 4) + 8;
int by = world.getHighestBlockYAt(bx, bz);
Location center = new Location(world, bx + 0.5, by + 1.0, bz + 0.5);
List<Player> nearest = Bukkit.getOnlinePlayers().stream()
.filter(p -> p.getWorld().equals(world))
.sorted(Comparator.comparingDouble(p -> p.getLocation().distanceSquared(center)))
.limit(3)
.collect(Collectors.toList());
String playersHover = nearest.isEmpty()
? "Keine Spieler in der Nähe"
: String.join("\n", nearest.stream()
.map(p -> String.format("- %s (%.1fm)", p.getName(), Math.sqrt(p.getLocation().distanceSquared(center))))
.toList());
List<GriefIncident> incidents = meta.incidentsByBucket.getOrDefault(bucketIdx, List.of());
String incidentsHover = incidents.isEmpty()
? "Keine Details"
: String.join("\n", incidents.stream().limit(20)
.map(i -> String.format("• %s · %s · %s", i.event, i.severity, i.data))
.toList());
Component coords = text("[" + cx + ", " + cz + "]")
.hoverEvent(HoverEvent.showText(text(
"Chunk: [" + cx + ", " + cz + "]\n" +
"Block: [" + bx + ", " + by + ", " + bz + "]\n\n" +
"Nächste Spieler:\n" + playersHover
)))
.clickEvent(ClickEvent.suggestCommand(String.format("/tp %d %d %d", bx, by, bz)));
Component incCount = text(incidents.size() + " Incidents")
.hoverEvent(HoverEvent.showText(text(incidentsHover)));
Component msg = text("Möglicher Grief in ").append(coords)
.append(text(" - score=" + String.format("%.1f", curr)))
.append(text(" base=" + String.format("%.1f", baseline)))
.append(text(" - ")).append(incCount);
Bukkit.getOnlinePlayers().stream()
.filter(p -> p.hasPermission("antigrief.alert"))
.forEach(p -> p.sendMessage(msg));
Bukkit.getConsoleSender().sendMessage(
String.format("[AntiGrief] Alert %s score=%.1f base=%.1f incidents=%d @ [%d,%d]",
world.getName(), curr, baseline, incidents.size(), cx, cz));
}
private long bucketIdx(long timestamp) {
return timestamp / AntiGrief.BUCKET_DURATION_MS;
}
private long packArea(UUID worldId, int chunkX, int chunkZ) {
long chunkKey = (((long)chunkX) << 32) ^ (chunkZ & 0xffffffffL);
return worldId.getMostSignificantBits() ^ worldId.getLeastSignificantBits() ^ chunkKey;
}
public Stream<Block> getSurroundingBlocks(Location location) {
Block center = location.getBlock();
World world = center.getWorld();
int x = center.getX();
int y = center.getY();
int z = center.getZ();
return Stream.of(
world.getBlockAt(x + 1, y, z),
world.getBlockAt(x - 1, y, z),
world.getBlockAt(x, y + 1, z),
world.getBlockAt(x, y - 1, z),
world.getBlockAt(x, y, z + 1),
world.getBlockAt(x, y, z - 1)
);
}
public @Nullable AreaState getInfoAtChunk(Chunk chunk) {
long areaKey = this.packArea(chunk.getWorld().getUID(), chunk.getX(), chunk.getZ());
return this.areas.get(areaKey);
}
public List<AreaState> getHighesScoredChunks(int limit) {
long nowBucket = this.bucketIdx(System.currentTimeMillis());
return this.areas.values().stream()
.sorted(Comparator.comparingDouble((AreaState st) -> st.currentScore(nowBucket)).reversed())
.limit(limit)
.toList();
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new BlockRelatedGriefListener(),
new ExplosionRelatedGriefListener(),
new FireRelatedGriefListener(),
new LiquidRelatedGriefListener(),
new EntityRelatedGriefListener()
);
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new AntiGriefCommand()
);
}
}

View File

@@ -0,0 +1,63 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.commands;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class AntiGriefCommand extends ApplianceCommand.PlayerChecked<AntiGrief> {
private final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
public AntiGriefCommand() {
super("antiGrief");
}
@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("One argument expected");
switch(args[0]) {
case "currentChunk": {
AntiGrief.AreaState state = this.getAppliance().getInfoAtChunk(this.getPlayer().getChunk());
if(state == null) throw new Error("The current chunk does not have a Score!");
sender.sendMessage(this.areaStateDisplay(state));
sender.sendMessage(String.format("ChunkLoaded: %ds", state.getInhabitedTime() / 1000));
break;
}
case "topChunks": {
List<AntiGrief.AreaState> states = this.getAppliance().getHighesScoredChunks(10);
sender.sendMessage(Component.empty().append(
states.stream().map(state -> this.areaStateDisplay(state).appendNewline()).toList()
));
break;
}
default: throw new Error("No such option!");
}
}
private Component areaStateDisplay(AntiGrief.AreaState state) {
var object = Component.text("[\uD83D\uDCC2]", NamedTextColor.GRAY)
.append(Component.text(" - ", NamedTextColor.GOLD))
.hoverEvent(HoverEvent.showText(Component.text(this.prettyGson.toJson(state))));
var location = Component.text(String.format("[%d,%d]", state.chunkX, state.chunkZ), NamedTextColor.YELLOW)
.append(Component.text(" > ", NamedTextColor.DARK_GRAY));
int incidentCount = state.incidentsByBucket.values().stream().map(List::size).mapToInt(Integer::intValue).sum();
var total = Component.text(String.format("ema:%.2f, totalIncidents:%d", state.ema, incidentCount), NamedTextColor.GRAY);
return Component.empty().append(object, location, total);
}
@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("currentChunk", "topChunks");
return null;
}
}

View File

@@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.block.Container;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.Inventory;
import java.util.Arrays;
import java.util.Objects;
public class BlockRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void containerBlockBreak(BlockBreakEvent event) {
if(!(event.getBlock().getState() instanceof Container container)) return;
Inventory containerInv = container.getInventory();
if(containerInv.isEmpty()) return;
long itemCount = Arrays.stream(containerInv.getStorageContents())
.filter(Objects::nonNull)
.filter(itemStack -> !itemStack.isEmpty())
.count();
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
itemCount > containerInv.getSize() / 2
? AntiGrief.GriefIncident.Severity.SEVERE
: AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,142 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import com.destroystokyo.paper.event.entity.CreeperIgniteEvent;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import java.util.List;
import java.util.Set;
public class EntityRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void buildWither(CreatureSpawnEvent event) {
if(!event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) return;
this.getAppliance().trackPassive(
event.getEntity().getLocation().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void creeperPurposelyIgnite(CreeperIgniteEvent event) {
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
AntiGrief.GriefIncident.Severity.SEVERE
)
);
}
@EventHandler
public void villagerDeath(EntityDeathEvent event) {
if(!(event.getEntity() instanceof Villager villager)) return;
if(event.getEntity().getLastDamageCause() != null) {
EntityDamageEvent lastDamage = event.getEntity().getLastDamageCause();
List<EntityDamageEvent.DamageCause> suspiciousCauses = List.of(
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.PROJECTILE,
EntityDamageEvent.DamageCause.ENTITY_ATTACK,
EntityDamageEvent.DamageCause.FIRE_TICK
);
if(!suspiciousCauses.contains(lastDamage.getCause())) return;
}
this.getAppliance().trackPassive(
villager.getChunk(),
new AntiGrief.GriefIncident(
villager.getLocation(),
event,
List.of(villager.getVillagerType(), String.valueOf(villager.getLastDamageCause())),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void petKilled(EntityDeathEvent event) {
Set<EntityType> petEntities = Set.of(
EntityType.SNIFFER,
EntityType.WOLF,
EntityType.AXOLOTL,
EntityType.ALLAY,
EntityType.CAMEL,
EntityType.PARROT,
EntityType.CAT,
EntityType.OCELOT,
EntityType.HORSE,
EntityType.DONKEY,
EntityType.MULE,
EntityType.LLAMA,
EntityType.FOX,
EntityType.TURTLE,
EntityType.PANDA,
EntityType.GOAT,
EntityType.BEE
);
if(!petEntities.contains(event.getEntity().getType())) return;
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
event.getEntity() instanceof Tameable tameable
? tameable.isTamed() ? AntiGrief.GriefIncident.Severity.SEVERE : AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void itemBurned(EntityDamageEvent event) {
if(!(event.getEntity() instanceof Item item)) return;
int amount = item.getItemStack().getAmount();
int half = item.getItemStack().getMaxStackSize() / 2;
if (amount < half / 2) return;
List<EntityDamageEvent.DamageCause> forbiddenCauses = List.of(
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.FIRE_TICK,
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.HOT_FLOOR,
EntityDamageEvent.DamageCause.CAMPFIRE
);
if(forbiddenCauses.contains(event.getCause())) return;
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> {
if (item.isValid() || !item.isDead()) return;
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
amount > half
? AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}, 1L);
}
}

View File

@@ -0,0 +1,105 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.minecart.ExplosiveMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.TNTPrimeEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPlaceEvent;
import org.bukkit.event.vehicle.VehicleCreateEvent;
import java.util.List;
public class ExplosionRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void tntPlacement(BlockPlaceEvent event) {
if(!event.getBlockPlaced().getType().equals(Material.TNT)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void crystalPlacement(EntityPlaceEvent event) {
if(!event.getEntityType().equals(EntityType.END_CRYSTAL)) return;
AntiGrief.GriefIncident incident = new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntityType(),
AntiGrief.GriefIncident.Severity.LIGHT
);
if(event.getPlayer() != null)
this.getAppliance().trackDirect(event.getPlayer(), incident);
else
this.getAppliance().trackPassive(event.getBlock().getChunk(), incident);
}
@EventHandler
public void tntPrime(TNTPrimeEvent event) {
AntiGrief.GriefIncident incident = new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getCause(), event.getBlock().getType()),
AntiGrief.GriefIncident.Severity.MODERATE
);
if(event.getCause().equals(TNTPrimeEvent.PrimeCause.PLAYER) && event.getPrimingEntity() instanceof Player player) {
this.getAppliance().trackDirect(player, incident);
}
this.getAppliance().trackPassive(event.getBlock().getChunk(), incident);
}
@EventHandler
public void tntMinecartPlace(VehicleCreateEvent event) {
if(!(event.getVehicle() instanceof ExplosiveMinecart minecart)) return;
this.getAppliance().trackPassive(
event.getVehicle().getChunk(),
new AntiGrief.GriefIncident(
minecart.getLocation(),
event,
minecart.getType(),
AntiGrief.GriefIncident.Severity.SEVERE
)
);
}
@EventHandler
public void entityExplosion(EntityExplodeEvent event) {
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getLocation(),
event,
List.of(event.getEntityType(), event.blockList().stream().map(Block::getType).distinct().toList()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void blockExplosion(BlockExplodeEvent event) {
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getExplodedBlockState().getType()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,82 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.*;
import java.util.List;
public class FireRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void activeBlockIgnite(BlockPlaceEvent event) {
if(!event.getBlock().getType().equals(Material.FIRE)) return;
if(this.getAppliance().getSurroundingBlocks(event.getBlock().getLocation()).noneMatch(Block::isBurnable)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void blockIgnite(BlockIgniteEvent event) {
if(!event.getBlock().isBurnable()) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getCause()),
event.getCause().equals(BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL)
? AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void fireSpread(BlockSpreadEvent event) {
if(!event.getBlock().getType().equals(Material.FIRE)) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void blockBurned(BlockBurnEvent event) {
if(event.getBlock().isReplaceable()) return;
if(event.getBlock().isPassable()) return;
if(event.getBlock().getPistonMoveReaction().equals(PistonMoveReaction.BREAK)) return;
if(event.getBlock().getType().name().endsWith("_LEAVES")) return;
if(event.getBlock().getType().name().endsWith("_LOG")) return;
List<Material> allowed = List.of(
Material.MOSS_BLOCK,
Material.MOSS_CARPET
);
if(allowed.contains(event.getBlock().getType())) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,61 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class LiquidRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void liquidFlow(BlockFromToEvent event) {
if(event.getToBlock().isEmpty()) return;
if(event.getToBlock().isSolid()) return;
if(ThreadLocalRandom.current().nextDouble() < 0.95) return;
this.getAppliance().trackPassive(
event.getToBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getToBlock().getLocation(),
event,
event.getToBlock().getType(),
AntiGrief.GriefIncident.Severity.INFO
)
);
}
@EventHandler
public void lavaCast(BlockFormEvent event) {
if(!event.getNewState().getType().equals(Material.COBBLESTONE)) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getNewState().getType()),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void lavaPlace(PlayerBucketEmptyEvent event) {
if(!event.getBucket().equals(Material.LAVA_BUCKET)) return;
if(this.getAppliance().getSurroundingBlocks(event.getBlockClicked().getLocation()).noneMatch(Block::isBurnable)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getBucket()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,21 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tooling.strike;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import java.time.Duration;
import java.util.Map;
public class Strike extends Appliance {
public Strike() {
super("strike");
}
private final Map<Integer, Duration> strikePunishmentMap = Map.of(
1, Duration.ofHours(1),
2, Duration.ofHours(24),
3, Duration.ofDays(3),
4, Duration.ofDays(7)
);
}

View File

@@ -47,7 +47,7 @@ public class Whitelist extends Appliance {
player.getUniqueId() player.getUniqueId()
); );
} }
this.queryAppliance(Outlawed.class).updateForcedStatus(player, this.timestampRelevant(user.outlawed_until())); this.queryAppliance(Outlawed.class).updateForcedStatus(player, this.timestampRelevant(0L)); // TODO
String purePlayerName = Floodgate.isBedrock(player) String purePlayerName = Floodgate.isBedrock(player)
? Floodgate.getBedrockPlayer(player).getUsername() ? Floodgate.getBedrockPlayer(player).getUsername()
@@ -67,14 +67,14 @@ public class Whitelist extends Appliance {
Main.instance().getLogger().info(String.format("Running integrityCheck for %s", name)); Main.instance().getLogger().info(String.format("Running integrityCheck for %s", name));
boolean overrideCheck = this.localConfig().getBoolean("overrideIntegrityCheck", false); boolean overrideCheck = this.localConfig().getBoolean("overrideIntegrityCheck", false);
WhitelistRepository.UserData user = overrideCheck WhitelistRepository.UserData user = overrideCheck
? new WhitelistRepository.UserData(uuid, name, "", "", 0L, 0L) ? new WhitelistRepository.UserData(uuid, name, "", "", List.of())
: this.fetchUserData(uuid); : this.fetchUserData(uuid);
this.userData.put(uuid, user); this.userData.put(uuid, user);
Main.logger().info(String.format("got userdata %s", user.toString())); Main.logger().info(String.format("got userdata %s", user.toString()));
if(this.timestampRelevant(user.banned_until())) { if(this.timestampRelevant(0L)) { //TODO
Instant bannedDate = new Date(user.banned_until() * 1000L) Instant bannedDate = new Date(0 * 1000L) // TODO
.toInstant() .toInstant()
.plus(1, ChronoUnit.HOURS); .plus(1, ChronoUnit.HOURS);
@@ -118,7 +118,7 @@ public class Whitelist extends Appliance {
); );
if(response.status() != HttpStatus.OK) if(response.status() != HttpStatus.OK)
throw new IllegalStateException(String.format("Http Reponse %d", response.status())); throw new IllegalStateException(String.format("Unwanted response %d!", response.status()));
return response.data(); return response.data();
} }

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.armadilloInfectionReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ArmadilloInfectionReducer extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new InfectionSpawnListener()
);
}
}

View File

@@ -0,0 +1,17 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.armadilloInfectionReducer;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.concurrent.ThreadLocalRandom;
class InfectionSpawnListener extends ApplianceListener<ArmadilloInfectionReducer> {
@EventHandler
public void onSpawn(CreatureSpawnEvent event) {
if(!event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) return;
if(!event.getEntity().getType().equals(EntityType.SILVERFISH)) return;
if(ThreadLocalRandom.current().nextDouble() > 0.7) event.setCancelled(true);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.endermanBlockGriefReducer;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.Enderman;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import java.util.concurrent.ThreadLocalRandom;
class EndermanBlockChangeListener extends ApplianceListener<EndermanBlockGriefReducer> {
@EventHandler
public void onBlockPickup(EntityChangeBlockEvent event) {
if(!(event.getEntity() instanceof Enderman)) return;
if(ThreadLocalRandom.current().nextDouble() > 0.7) event.setCancelled(true);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.endermanBlockGriefReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class EndermanBlockGriefReducer extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new EndermanBlockChangeListener()
);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.mendingReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class MendingReducer extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new MendingRepairListener()
);
}
}

View File

@@ -0,0 +1,23 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.mendingReducer;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerItemMendEvent;
public class MendingRepairListener extends ApplianceListener<MendingReducer> {
private static final double COST_MULTIPLIER = 2.0;
@EventHandler
public void onMendingRepair(PlayerItemMendEvent event) {
int baseConsumed = event.getConsumedExperience();
int orbExp = event.getExperienceOrb().getExperience();
int desiredTotal = (int) Math.ceil(baseConsumed * COST_MULTIPLIER);
int extraCost = Math.max(0, desiredTotal - baseConsumed);
int maxExtraPossible = Math.max(0, orbExp - baseConsumed);
int extraApplied = Math.min(extraCost, maxExtraPossible);
if (extraApplied > 0) event.getExperienceOrb().setExperience(orbExp - extraApplied);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.phantomReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class PhantomReducer extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new PhantomSpawnListener()
);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.phantomReducer;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.Phantom;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.concurrent.ThreadLocalRandom;
class PhantomSpawnListener extends ApplianceListener<PhantomReducer> {
@EventHandler
public void onPhantomSpawn(CreatureSpawnEvent event) {
if(!(event.getEntity() instanceof Phantom)) return;
if(ThreadLocalRandom.current().nextDouble() > 0.8) event.setCancelled(true);
}
}

View File

@@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.silverfishExpReducer;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent;
class SilverfishDeathListener extends ApplianceListener<SilverfishExpReducer> {
@EventHandler
public void onDeath(EntityDeathEvent event) {
if(!event.getEntity().getType().equals(EntityType.SILVERFISH)) return;
event.setDroppedExp(event.getDroppedExp() / 3);
}
}

View File

@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.silverfishExpReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class SilverfishExpReducer extends Appliance {
@Override
public @NotNull List<Listener> listeners() {
return List.of(
new SilverfishDeathListener()
);
}
}

24
local.gradle.example Normal file
View File

@@ -0,0 +1,24 @@
tasks.register('deployVaroPlugin', Copy) {
dependsOn ":varo:shadowJar"
from { project(":varo").shadowJar.archivePath }
into file('path') // path to plugins folder
rename { fileName -> "varo.jar" }
}
tasks.register("uploadVaroPlugin") {
dependsOn(":varo:shadowJar")
doLast {
def jarFile = project(":varo").tasks.named("shadowJar").get().outputs.files.singleFile
exec {
commandLine "scp", "-4", "-P", "22", jarFile.absolutePath, "user@host:path/varo.jar"
}
}
}
tasks.register('deployCraftAttackPlugin', Copy) {
dependsOn ":craftattack:shadowJar"
from { project(":craftattack").shadowJar.archivePath }
into file('path') // path to plugins folder
rename { fileName -> "craftattack.jar" }
}

View File

@@ -1,49 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.tooling.antiGrief;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.tooling.antiGrief.player.PlayerGriefListener;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Transformation;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class AntiGrief extends Appliance {
private final Map<Block, UUID> blockRegistry = new HashMap<>();
public void addTracking(Block block, UUID player) {
this.blockRegistry.put(block, player);
block.getLocation().getWorld().spawn(block.getLocation(), ItemDisplay.class, itemDisplay -> {
itemDisplay.setItemStack(ItemStack.of(Material.FIRE_CHARGE));
itemDisplay.teleport(block.getLocation().add(0.5, 0.5, 0.5));
});
}
public @Nullable UUID getTracked(Block block) {
return this.blockRegistry.get(block);
}
public void addDestroyed(Block block, UUID player) {
block.getLocation().getWorld().spawn(block.getLocation().add(0.5, 0.5, 0.5), BlockDisplay.class, blockDisplay -> {
blockDisplay.setBlock(Material.GOLD_BLOCK.createBlockData());
Transformation transformation = blockDisplay.getTransformation();
transformation.getScale().set(0.3);
blockDisplay.setTransformation(transformation);
});
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new PlayerGriefListener());
}
}

View File

@@ -1,51 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.tooling.antiGrief.player;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.tooling.antiGrief.AntiGrief;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockSpreadEvent;
import java.util.UUID;
public class PlayerGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void onIgnite(BlockIgniteEvent event) {
Bukkit.broadcast(Component.text(event.getCause().toString()));
switch(event.getCause()) {
case LAVA:
case SPREAD:
case EXPLOSION:
if(event.getIgnitingBlock() == null) return;
UUID ignitedBy = this.getAppliance().getTracked(event.getIgnitingBlock());
this.getAppliance().addTracking(event.getBlock(), ignitedBy);
break;
case FLINT_AND_STEEL:
case ENDER_CRYSTAL:
case ARROW:
case FIREBALL:
if(event.getPlayer() == null) return;
this.getAppliance().addTracking(event.getBlock(), event.getPlayer().getUniqueId());
break;
}
}
@EventHandler
public void onSpread(BlockSpreadEvent event) {
if(!event.getBlock().getType().equals(Material.FIRE)) return;
UUID ignitedBy = this.getAppliance().getTracked(event.getBlock());
this.getAppliance().addTracking(event.getBlock(), ignitedBy);
}
@EventHandler
public void onDestroy(BlockBurnEvent event) {
UUID ignitedBy = this.getAppliance().getTracked(event.getIgnitingBlock());
if(ignitedBy == null) return;
this.getAppliance().addDestroyed(event.getBlock(), ignitedBy);
}
}

View File

@@ -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.8-R0.1-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'
} }