16 Commits

Author SHA1 Message Date
469cd19b55 added AntiBoatFreecam to detect and notify admins of illegal boat yaw behavior 2025-10-27 17:54:19 +01:00
e745ff4721 added AntiFormattedBook to detect and sanitize illegal book formatting 2025-10-27 17:10:44 +01:00
bc5c9a2a13 added AntiIllegalBundlePicker to track and notify admins on illegal bundle interactions 2025-10-27 16:02:13 +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
23 changed files with 536 additions and 127 deletions

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,7 +6,7 @@ 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) {

View File

@@ -21,18 +21,17 @@ class ChatMessagesListener extends ApplianceListener<ChatMessages> {
public void onPlayerChatEvent(AsyncChatEvent event) { public void onPlayerChatEvent(AsyncChatEvent event) {
event.renderer( event.renderer(
(source, sourceDisplayName, message, viewer) -> (source, sourceDisplayName, message, viewer) ->
Component.text("") Component.text()
.append(this.getAppliance().getReportablePlayerName(source)) .append(this.getAppliance().getReportablePlayerName(source))
.append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB()))) .append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB())))
.append(message).color(TextColor.color(Color.SILVER.asRGB())) .append(message).color(TextColor.color(Color.SILVER.asRGB()))
.build()
); );
} }
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
boolean wasHidden = event.joinMessage() == null; if(event.joinMessage() == null) return;
event.joinMessage(null);
if(wasHidden) return;
IteratorUtil.onlinePlayers(player -> { IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return; if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage( player.sendMessage(
@@ -45,9 +44,7 @@ class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler @EventHandler
public void onPlayerLeave(PlayerQuitEvent event) { public void onPlayerLeave(PlayerQuitEvent event) {
boolean wasHidden = event.quitMessage() == null; if(event.quitMessage() == null) return;
event.quitMessage(null);
if(wasHidden) return;
IteratorUtil.onlinePlayers(player -> { IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return; if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage( player.sendMessage(

View File

@@ -1,7 +1,6 @@
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;
@@ -64,7 +63,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()) {
@@ -115,7 +114,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) {

View File

@@ -0,0 +1,51 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiBoatFreecam;
import eu.mhsl.craftattack.spawn.common.appliances.tooling.acInform.AcInform;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.Bukkit;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
public class AntiBoatFreecam extends Appliance {
private static final float MAX_YAW_OFFSET = 106.0f;
private final Map<Player, Float> violatedPlayers = new HashMap<>();
public AntiBoatFreecam() {
Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
() -> Bukkit.getOnlinePlayers().forEach(player -> {
if(!(player.getVehicle() instanceof Boat boat)) return;
if(!boat.getPassengers().getFirst().equals(player)) return;
float playerYaw = player.getYaw();
float boatYaw = boat.getYaw();
float yawDelta = wrapDegrees(playerYaw - boatYaw);
if(Math.abs(yawDelta) <= MAX_YAW_OFFSET) return;
this.violatedPlayers.merge(player, 1f, Float::sum);
float violationCount = this.violatedPlayers.get(player);
if(violationCount != 1 && violationCount % 100 != 0) return;
Main.instance().getAppliance(AcInform.class).notifyAdmins(
"internal",
player.getName(),
"illegalBoatLookYaw",
violationCount
);
}),
1L,
1L
);
}
private static float wrapDegrees(float deg) {
deg = deg % 360f;
if (deg >= 180f) deg -= 360f;
if (deg < -180f) deg += 360f;
return deg;
}
}

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

@@ -9,16 +9,16 @@ 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) {
playerPreLoginEvent.kickMessage( if(Bukkit.getOnlinePlayers().size() >= this.getAppliance().getLimit()) {
new DisconnectInfo( playerPreLoginEvent.kickMessage(
"Hohe Serverauslastung", new DisconnectInfo(
"Der Server ist momentan an seiner Kapazitätsgrenze angelangt!", "Hohe Serverauslastung",
"Bitte versuche es zu einem späteren Zeitpunkt erneut.", "Der Server ist momentan an seiner Kapazitätsgrenze angelangt!",
playerPreLoginEvent.getUniqueId() "Bitte versuche es zu einem späteren Zeitpunkt erneut.",
).getComponent() playerPreLoginEvent.getUniqueId()
); ).getComponent()
);
if(Bukkit.getOnlinePlayers().size() >= this.getAppliance().getLimit())
playerPreLoginEvent.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_FULL); playerPreLoginEvent.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_FULL);
}
} }
} }

View File

@@ -12,7 +12,7 @@ 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, List<UUID> users) {

View File

@@ -8,7 +8,7 @@ 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(
@@ -21,10 +21,12 @@ public class WhitelistRepository extends HttpRepository {
) { ) {
} }
private record UserQuery(UUID uuid) {}
public ReqResp<UserData> getUserData(UUID userId) { public ReqResp<UserData> getUserData(UUID userId) {
return this.get( return this.post(
"user", "player",
parameters -> parameters.addParameter("uuid", userId.toString()), new UserQuery(userId),
UserData.class UserData.class
); );
} }

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

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

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

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