Compare commits

...

42 Commits

Author SHA1 Message Date
besiece f615dc867a Rerunning my first test, to verify if my install worked properly 2026-08-13 13:09:14 +02:00
besiece 673bae4d50 Fixing my first test, to verify if my install worked properly 2026-08-13 10:33:38 +02:00
besiece 6ad9407cb4 Creating a first test, to verify if my install worked properly 2026-08-13 10:14:38 +02:00
Pupsi 9dad34719b removed Exception throw from CustomAdvancements, fixed time reset at project start 2026-07-15 16:54:44 +02:00
Pupsi 323e316f0f fixed bloodmoon sleep / time skip issue, added endermite hordes for end, fixed server restart during bloodmoon issue 2026-01-17 17:09:54 +01:00
Pupsi 0a16e1e049 added missing setting check for bloodmoon warning messages 2026-01-09 19:25:10 +01:00
MineTec c371893407 added support for portable stonecutter alongside crafting table, renamed listener for clarity 2026-01-02 22:29:27 +01:00
MineTec f2bf8f1858 fixed username validation to allow exceptions for Bedrock players due to specific naming constraints 2025-12-29 09:05:57 +01:00
MineTec a0a33f1f56 filtered null and duplicate player names in ReportCommand responses for improved reliability 2025-12-28 17:36:01 +01:00
Pupsi 164a160dbb fixed bloodmoon hordes for players with deactivated setting 2025-12-28 17:21:33 +01:00
MineTec efdbc6fe9f updated feedback link behavior, disabled outdated appliances, adjusted infection spawn and silverfish mechanics 2025-12-28 11:40:21 +01:00
Pupsi 7ac02b4ec4 added missing return for /event 2025-12-27 11:35:20 +01:00
MineTec 93971650ce adjusted strike punishment durations for better balance and scaling 2025-12-27 11:08:47 +01:00
MineTec 336e3f934c remove wrong location description 2025-12-27 00:47:13 +01:00
Pupsi 91e350ee62 disabled worldmuseum for craftattack 8 2025-12-26 19:50:59 +01:00
MineTec 8736e78adf Merge remote-tracking branch 'origin/master' 2025-12-25 22:49:19 +01:00
MineTec 329ec8e1da renamed adminPermission constant to adminmarker for improved clarity and consistency 2025-12-25 22:49:15 +01:00
Pupsi ebf392b024 fixed bloodmoon progress bar trying updates when inactive 2025-12-24 14:26:23 +01:00
Pupsi 33d00e97c6 fixed recovery compass at project start 2025-12-24 01:07:53 +01:00
Pupsi 1fdcc11211 Merge pull request 'master-big-events' (#11) from master-big-events into master
Reviewed-on: #11
2025-12-23 23:54:14 +00:00
Pupsi e0ed7ecdf5 Merge branch 'master' into master-big-events 2025-12-23 23:53:42 +00:00
Pupsi 2ca97c88fc made score staying the highest ever reached 2025-12-24 00:49:50 +01:00
Pupsi fc502e3da8 fixed player join when no event is loaded 2025-12-24 00:16:22 +01:00
Pupsi 5e3db1e78c fixed event commands, removed infinite saturation, added player join check 2025-12-23 23:40:58 +01:00
MineTec fc6fd9ebb5 Merge branch 'master-big-events' 2025-12-23 22:34:51 +01:00
MineTec 84edbcc0e4 Merge branch 'master-big-events' 2025-12-23 22:34:33 +01:00
Pupsi 9ec883d6ad made Scoreboard more beautiful 2025-12-23 22:33:32 +01:00
Pupsi 2b0c7c1a9e added countdown and sounds 2025-12-23 21:43:25 +01:00
MineTec df39093c69 enhanced Outlawed PVP activation message to include cooldown duration; fixed minor typo in forced status message 2025-12-23 21:32:35 +01:00
MineTec dfbf87dcd4 updated ProjectStart to adjust coordinates, music, and glass material; refined player inventory handling logic; renamed vogelfrei to PVP in Outlawed messages for clarity 2025-12-23 21:07:32 +01:00
MineTec 8953a19400 added Bedrock player warning in joinEvent method to handle potential compatibility issues 2025-12-23 19:52:17 +01:00
MineTec 85065bcc73 made SignEditListener formatting consistent and optimized ReportJoinListener to handle admin notifications asynchronously 2025-12-23 19:14:53 +01:00
Pupsi 2209b42766 fixed wrong reports info filtering 2025-12-23 19:11:59 +01:00
Pupsi bf11bb0b70 fixed wrong reports info filtering 2025-12-23 19:05:53 +01:00
Pupsi b98d33af40 added report reminder on admin join 2025-12-23 18:56:30 +01:00
Pupsi 0d6b21701f Merge remote-tracking branch 'origin/master' 2025-12-23 15:50:41 +01:00
Pupsi b3240cdb22 fixed maximum of 8 settings per category 2025-12-23 15:50:23 +01:00
MineTec 89c1c4335b refined SignEditListener to filter illegal characters more robustly by normalizing input and introducing specific character whitelists 2025-12-22 21:41:34 +01:00
MineTec 71f2da8e99 introduced AntiIllegalSignCharacters appliance to prevent usage of illegal characters in sign edits 2025-12-22 18:44:22 +01:00
MineTec a257b604ea updated InventoryTrackerListener to monitor InventoryOpenEvent with high priority and prevent processing of cancelled events 2025-12-21 09:32:40 +01:00
Pupsi ef153d5d8f added invincibility before event start, added join craftattack command, added player teleport, portal and pvp listeners to deathrun 2025-12-20 23:13:14 +01:00
MineTec ac7e04829e introduced ReportCreatedListener, updated ReportCreatedEvent to use UUIDs for reporter and reported, and improved admin notifications for new reports 2025-12-20 17:46:02 +01:00
49 changed files with 792 additions and 129 deletions
@@ -23,4 +23,8 @@ public class CraftAttackReportRepository extends ReportRepository {
ReportUrl.class ReportUrl.class
); );
} }
public ReqResp<AllReports> queryAllReports() {
return this.get("reports", AllReports.class);
}
} }
@@ -21,6 +21,11 @@ public abstract class ReportRepository extends HttpRepository {
public record ReportUrl(@NotNull String url) { public record ReportUrl(@NotNull String url) {
} }
public enum Status {
open,
closed,
}
public record PlayerReports( public record PlayerReports(
List<Report> from_self, List<Report> from_self,
List<Report> to_self List<Report> to_self
@@ -31,11 +36,17 @@ public abstract class ReportRepository extends HttpRepository {
@Nullable Long created, @Nullable Long created,
@Nullable Status status, @Nullable Status status,
@NotNull String url @NotNull String url
) { ) { }
public enum Status { }
open,
closed, public record AllReports(List<Report> reports) {
} public record Report(
} @NotNull UUID reporter,
@Nullable UUID reported,
@NotNull String reason,
@Nullable Long created,
@Nullable Status status,
@NotNull String url
) { }
} }
} }
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
public class AdminMarker extends Appliance implements DisplayName.Colored { public class AdminMarker extends Appliance implements DisplayName.Colored {
public final static String adminPermission = "admin"; public final static String adminPermission = "adminmarker";
@Override @Override
public @Nullable TextColor getNameColor(Player player) { public @Nullable TextColor getNameColor(Player player) {
@@ -19,7 +19,7 @@ public class HelpCommand extends ApplianceCommand<Help> {
Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD) Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD)
.appendNewline() .appendNewline()
.append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." + .append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." +
" Weitere Informationen zu Funktionen und Befehlen erhältst du zudem im Turm am Spawn.", NamedTextColor.GRAY)) " Weitere Informationen zu Funktionen und Befehlen erhältst du zudem am Spawn.", NamedTextColor.GRAY))
); );
} }
} }
@@ -1,6 +1,8 @@
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.appliances.metaGameplay.report.listeners.ReportCreatedListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners.ReportJoinListener;
import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp; import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository; import eu.mhsl.craftattack.spawn.common.api.repositories.CraftAttackReportRepository;
@@ -16,6 +18,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -132,7 +135,7 @@ public class Report extends Appliance {
} }
Function<List<ReportRepository.PlayerReports.Report>, List<ReportRepository.PlayerReports.Report>> filterClosed = reports -> reports.stream() Function<List<ReportRepository.PlayerReports.Report>, List<ReportRepository.PlayerReports.Report>> filterClosed = reports -> reports.stream()
.filter(report -> Objects.equals(report.status(), ReportRepository.PlayerReports.Report.Status.closed)) .filter(report -> Objects.equals(report.status(), ReportRepository.Status.closed))
.toList(); .toList();
List<ReportRepository.PlayerReports.Report> reportsToOthers = filterClosed.apply(userReports.data().from_self()).reversed(); List<ReportRepository.PlayerReports.Report> reportsToOthers = filterClosed.apply(userReports.data().from_self()).reversed();
@@ -178,6 +181,35 @@ public class Report extends Appliance {
issuer.sendMessage(component.build()); issuer.sendMessage(component.build());
} }
public void sendReportsInfo(Player player) {
ReqResp<ReportRepository.AllReports> allReportsResponse = this.queryRepository(CraftAttackReportRepository.class).queryAllReports();
if(allReportsResponse.status() != 200) {
Main.logger().warning("Failed to request Reports: " + allReportsResponse.status());
return;
}
List<ReportRepository.AllReports.Report> allOpenReports = allReportsResponse.data().reports().stream()
.filter(report -> report.status() == null && report.created() != null)
.toList();
if(allOpenReports.isEmpty()) return;
player.sendMessage(
Component.text("Hey, es gibt noch ", NamedTextColor.GOLD)
.append(Component.text(allOpenReports.size(), NamedTextColor.RED))
.append(Component.text(" unbearbeitete Reports!", NamedTextColor.GOLD))
);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new ReportCreatedListener(),
new ReportJoinListener()
);
}
@Override @Override
@NotNull @NotNull
protected List<ApplianceCommand<?>> commands() { protected List<ApplianceCommand<?>> commands() {
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -46,7 +47,7 @@ class ReportCommand extends ApplianceCommand.PlayerChecked<Report> {
response = Stream.concat( response = Stream.concat(
Bukkit.getOnlinePlayers().stream().map(Player::getName), Bukkit.getOnlinePlayers().stream().map(Player::getName),
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName) Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
).toList(); ).filter(Objects::nonNull).distinct().toList();
} }
if(args.length == 2) { if(args.length == 2) {
@@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.Report;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.core.event.ReportCreatedEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.event.EventHandler;
public class ReportCreatedListener extends ApplianceListener<Report> {
@EventHandler
public void onReport(ReportCreatedEvent event) {
OfflinePlayer reporter = Bukkit.getOfflinePlayer(event.getReport().reporter());
OfflinePlayer reported = Bukkit.getOfflinePlayer(event.getReport().reported());
Component message = Component.text(
"\uD83D\uDD14 Neuer Report von %s gegen %s: %s".formatted(reporter.getName(), reported.getName(), event.getReport().reason()),
NamedTextColor.YELLOW
);
Bukkit.getOnlinePlayers().stream()
.filter(player -> player.hasPermission("admin"))
.forEach(player -> player.sendMessage(message));
}
}
@@ -0,0 +1,19 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.listeners;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report.Report;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class ReportJoinListener extends ApplianceListener<Report> {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if(!event.getPlayer().hasPermission("admin")) return;
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().sendReportsInfo(event.getPlayer())
);
}
}
@@ -128,12 +128,12 @@ public class Settings extends Appliance {
if(categorizedSettings.isEmpty()) return; if(categorizedSettings.isEmpty()) return;
for(int i = 0; i < categorizedSettings.size(); i++) { for(int i = 0; i < categorizedSettings.size(); i++) {
int slot = row.get() * 9 + i % 9; if(i % 9 == 0 && i != 0) {
inventory.setItem(slot, categorizedSettings.get(i).buildItem());
if(i % 9 == 8) {
row.incrementAndGet(); row.incrementAndGet();
} }
int slot = row.get() * 9 + i % 9;
inventory.setItem(slot, categorizedSettings.get(i).buildItem());
} }
row.incrementAndGet(); row.incrementAndGet();
}); });
@@ -143,12 +143,12 @@ public class Settings extends Appliance {
.toList(); .toList();
for(int i = 0; i < uncategorizedSettings.size(); i++) { for(int i = 0; i < uncategorizedSettings.size(); i++) {
int slot = row.get() * 9 + i % 9; if(i % 9 == 0 && i != 0) {
inventory.setItem(slot, uncategorizedSettings.get(i).buildItem());
if(i % 9 == 8) {
row.incrementAndGet(); row.incrementAndGet();
} }
int slot = row.get() * 9 + i % 9;
inventory.setItem(slot, uncategorizedSettings.get(i).buildItem());
} }
player.openInventory(inventory); player.openInventory(inventory);
@@ -7,6 +7,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Boat; import org.bukkit.entity.Boat;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Appliance.Flags(enabled = false)
public class AntiBoatFreecam extends Appliance { public class AntiBoatFreecam extends Appliance {
private static final float MAX_YAW_OFFSET = 106.0f; private static final float MAX_YAW_OFFSET = 106.0f;
@@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AntiIllegalSignCharacters extends Appliance {
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new SignEditListener()
);
}
}
@@ -0,0 +1,77 @@
package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.SignChangeEvent;
import java.text.Normalizer;
import java.util.Set;
class SignEditListener extends ApplianceListener<AntiIllegalSignCharacters> {
private static final Set<Integer> ALLOWED_CHARS = Set.of(
(int)' ', (int)'.', (int)',', (int)';', (int)':', (int)'!', (int)'?',
(int)'"', (int)'\'',
(int)'(', (int)')', (int)'[', (int)']', (int)'{', (int)'}',
(int)'-', (int)'_', (int)'+', (int)'=', (int)'/', (int)'\\',
(int)'@', (int)'#', (int)'$', (int)'%', (int)'&', (int)'*',
(int)'<', (int)'>', (int)'|',
(int)'~', (int)'`', (int)'^'
);
private static final Set<Integer> ALLOWED_EXTRA = Set.of(
(int)'Ä', (int)'Ö', (int)'Ü', (int)'ä', (int)'ö', (int)'ü', (int)'ß',
(int)'€', (int)'°', (int)'µ'
);
@EventHandler
public void onSignEdit(SignChangeEvent event) {
for (int i = 0; i < 4; i++) {
Component line = event.line(i);
if (line == null) continue;
String plainString = PlainTextComponentSerializer.plainText().serialize(line);
plainString = Normalizer.normalize(plainString, Normalizer.Form.NFC);
String cleaned = filterAllowed(plainString);
event.line(i, Component.text(cleaned));
}
}
private static String filterAllowed(String s) {
StringBuilder out = new StringBuilder(s.length());
for (int off = 0; off < s.length(); ) {
int cp = s.codePointAt(off);
off += Character.charCount(cp);
if (isForbidden(cp)) continue;
if (Character.isLetterOrDigit(cp)) {
out.appendCodePoint(cp);
continue;
}
if (ALLOWED_CHARS.contains(cp) || ALLOWED_EXTRA.contains(cp)) {
out.appendCodePoint(cp);
}
}
return out.toString();
}
private static boolean isForbidden(int cp) {
// Surrogates / invalid
if (cp >= 0xD800 && cp <= 0xDFFF) return true;
// Private Use Area (Mod/Pack-Icons/Placeholder)
if (cp >= 0xE000 && cp <= 0xF8FF) return true;
// Zero-width and control characters
if (cp == 0x200B || cp == 0x200C || cp == 0x200D || cp == 0xFEFF) return true;
// BiDi-Steuerzeichen
if (cp >= 0x202A && cp <= 0x202E) return true;
if (cp >= 0x2066 && cp <= 0x2069) return true;
return cp == '§';
}
}
@@ -11,6 +11,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Appliance.Flags(enabled = false)
public class AntiInventoryMove extends Appliance { public class AntiInventoryMove extends Appliance {
private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2; private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2;
@@ -3,13 +3,15 @@ package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
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;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryOpenEvent;
class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> { class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> {
@EventHandler @EventHandler(priority = EventPriority.MONITOR)
public void onOpen(InventoryOpenEvent event) { public void onOpen(InventoryOpenEvent event) {
if(!(event.getPlayer() instanceof Player player)) return; if(!(event.getPlayer() instanceof Player player)) return;
if(event.isCancelled()) return;
this.getAppliance().setInvOpen(player, true); this.getAppliance().setInvOpen(player, true);
} }
@@ -28,14 +28,16 @@ public class WebsiteHook extends HttpHook {
return HttpServer.nothing; return HttpServer.nothing;
})); }));
record CreatedReport(String reporter, String reported, String reason) {} record CreatedReport(UUID reporter, UUID reported, String reason) {}
this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> { this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> {
Main.logger().info(String.format("New Report from Hook: (%s) Reporter: %s Reported: %s", createdReport.reason, createdReport.reporter, createdReport.reported));
SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason))); SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason)));
return HttpServer.nothing; return HttpServer.nothing;
})); }));
record CreatedStrike(UUID uuid) {} record CreatedStrike(UUID uuid) {}
this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> { this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> {
Main.logger().info(String.format("New Strike from Hook! (User %s)", createdStrike.uuid));
SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid))); SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid)));
return HttpServer.nothing; return HttpServer.nothing;
})); }));
@@ -4,6 +4,8 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ReportCreatedEvent extends Event { public class ReportCreatedEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList(); private static final HandlerList HANDLERS = new HandlerList();
@Override @Override
@@ -15,7 +17,7 @@ public class ReportCreatedEvent extends Event {
return HANDLERS; return HANDLERS;
} }
public record CreatedReport(String reporter, String reported, String reason) {} public record CreatedReport(UUID reporter, UUID reported, String reason) {}
private final CreatedReport report; private final CreatedReport report;
public ReportCreatedEvent(CreatedReport report) { public ReportCreatedEvent(CreatedReport report) {
@@ -5,23 +5,18 @@ 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.craftattack.appliances.gameplay.bloodmoon.commands.BloodmoonCommand; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.commands.BloodmoonCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonEntityDamageListener; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.*;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonMonsterDeathListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonPlayerJoinListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonTimeListener;
import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.*;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
@@ -70,7 +65,6 @@ public class Bloodmoon extends Appliance {
public final double mobHealthMultiplier = 2; public final double mobHealthMultiplier = 2;
private final ThreadLocalRandom random = ThreadLocalRandom.current(); private final ThreadLocalRandom random = ThreadLocalRandom.current();
private boolean isActive = false;
private final BossBar bossBar = BossBar.bossBar( private final BossBar bossBar = BossBar.bossBar(
Component.text("Blutmond", NamedTextColor.DARK_RED), Component.text("Blutmond", NamedTextColor.DARK_RED),
1f, 1f,
@@ -79,6 +73,7 @@ public class Bloodmoon extends Appliance {
Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN) Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN)
); );
private final boolean hordesEnabled = true; private final boolean hordesEnabled = true;
public final boolean bloodmoonSkippable = true;
private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND;
private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND;
private final int hordeMinPopulation = 3; private final int hordeMinPopulation = 3;
@@ -90,10 +85,10 @@ public class Bloodmoon extends Appliance {
EntityType.SPIDER EntityType.SPIDER
); );
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>(); private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
private long lastBloodmoonStartTick = 0;
public final int ticksPerDay = 24000; public final int ticksPerDay = 24000;
public final int bloodmoonLength = this.ticksPerDay /2; public final int bloodmoonLength = this.ticksPerDay / 2;
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50; public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
private boolean bossbarActive = false;
private final int bloodmoonFreeDaysAtStart = 3; private final int bloodmoonFreeDaysAtStart = 3;
private final int bloodmoonStartTime = this.ticksPerDay /2; private final int bloodmoonStartTime = this.ticksPerDay /2;
private final int bloodmoonDayInterval = 30; private final int bloodmoonDayInterval = 30;
@@ -113,35 +108,71 @@ public class Bloodmoon extends Appliance {
} }
public boolean bloodmoonIsActive() { public boolean bloodmoonIsActive() {
return this.isActive; long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
long day = currentTick / this.ticksPerDay;
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false;
long time = currentTick % this.ticksPerDay;
return time >= this.bloodmoonStartTime && time <= this.bloodmoonStartTime + this.bloodmoonLength;
} }
public void startBloodmoon(long startTick) { public void startBloodmoon() {
this.lastBloodmoonStartTick = startTick; this.bossbarActive = true;
this.isActive = true;
Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar); Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar);
this.startHordeSpawning(this.getRandomHordeSpawnDelay()); this.startHordeSpawning(this.getRandomHordeSpawnDelay());
this.sendStartMessages(); this.sendStartMessages();
} }
public void stopBloodmoon() { public void stopBloodmoonIfInactive() {
this.isActive = false; Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> {
if(!this.bossbarActive) return;
if(this.bloodmoonIsActive()) return;
this.bossbarActive = false;
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
this.stopHordeSpawning();
this.sendStopMessages();
},
1
);
}
// not working for entity effects and debuffs...
public void forceStopBloodmoon() {
this.bossbarActive = false;
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar)); Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
this.stopHordeSpawning();
this.sendStopMessages(); this.sendStopMessages();
} }
public void updateBossBar() { public void updateBossBar() {
long tick = Bukkit.getWorlds().getFirst().getFullTime(); long tick = Bukkit.getWorlds().getFirst().getFullTime();
long sinceStart = tick - this.lastBloodmoonStartTick; long sinceStart = tick - this.lastBloodmoonStartTick();
float progress = 1f - ((float) sinceStart / this.bloodmoonLength); float progress = 1f - ((float) sinceStart / this.bloodmoonLength);
if(progress < 0) progress = 1f; if(progress < 0) progress = 1f;
this.bossBar.progress(progress); this.bossBar.progress(progress);
} }
private long lastBloodmoonStartTick() {
long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
long day = currentTick / this.ticksPerDay;
long time = currentTick % this.ticksPerDay;
boolean todayIsBloodmoon = (day % this.bloodmoonDayInterval == 0) && (day > this.bloodmoonFreeDaysAtStart);
if (todayIsBloodmoon && time < this.bloodmoonStartTime) {
day -= this.bloodmoonDayInterval;
} else if (!todayIsBloodmoon) {
day -= (day % this.bloodmoonDayInterval);
}
if (day <= this.bloodmoonFreeDaysAtStart) return -1L;
return day * this.ticksPerDay + this.bloodmoonStartTime;
}
public boolean isStartTick(long tick) { public boolean isStartTick(long tick) {
long day = tick / this.ticksPerDay; long day = tick / this.ticksPerDay;
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false; if(day % this.bloodmoonDayInterval != 0 || day <= this.bloodmoonFreeDaysAtStart) return false;
long time = tick - (day * this.ticksPerDay); long time = tick % this.ticksPerDay;
return time == this.bloodmoonStartTime; return time == this.bloodmoonStartTime;
} }
@@ -153,7 +184,14 @@ public class Bloodmoon extends Appliance {
Bukkit.getOnlinePlayers().forEach(player -> this.startHordeSpawning(delay, player)); Bukkit.getOnlinePlayers().forEach(player -> this.startHordeSpawning(delay, player));
} }
private void startHordeSpawning(int delay, Player player) { private void stopHordeSpawning() {
this.hordeSpawnTasks.forEach((player, bukkitTask) -> {
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
if(task != null) task.cancel();
});
}
public void startHordeSpawning(int delay, Player player) {
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player); @Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
if(task != null) task.cancel(); if(task != null) task.cancel();
BukkitTask newTask = Bukkit.getScheduler().runTaskLater( BukkitTask newTask = Bukkit.getScheduler().runTaskLater(
@@ -187,14 +225,17 @@ public class Bloodmoon extends Appliance {
public void spawnRandomHorde(Player player) { public void spawnRandomHorde(Player player) {
if(!this.hordesEnabled) return; if(!this.hordesEnabled) return;
if(!this.getBloodmoonSetting(player)) return;
if(!player.getGameMode().equals(GameMode.SURVIVAL)) return; if(!player.getGameMode().equals(GameMode.SURVIVAL)) return;
EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size())); EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size()));
if(player.getLocation().getWorld().getEnvironment().equals(World.Environment.THE_END)) hordeEntityType = EntityType.ENDERMITE;
int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1); int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
this.spawnHorde(player, hordeSize, hordeEntityType); this.spawnHorde(player, hordeSize, hordeEntityType);
} }
public void sendWarningMessage(Player p) { public void sendWarningMessage(Player p) {
if(!this.getBloodmoonSetting(p)) return;
p.sendMessage(Component.text("Der Blutmond waltet in diesem Augenblick!", NamedTextColor.RED)); p.sendMessage(Component.text("Der Blutmond waltet in diesem Augenblick!", NamedTextColor.RED));
} }
@@ -215,7 +256,7 @@ public class Bloodmoon extends Appliance {
Math.cos(spawnRadiant)*this.hordeSpawnDistance Math.cos(spawnRadiant)*this.hordeSpawnDistance
); );
mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1); mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1);
player.getWorld().spawnEntity(mobSpawnLocation, type); player.getWorld().spawnEntity(mobSpawnLocation, type, CreatureSpawnEvent.SpawnReason.NATURAL);
player.getWorld().strikeLightningEffect(mobSpawnLocation); player.getWorld().strikeLightningEffect(mobSpawnLocation);
} }
} }
@@ -269,7 +310,8 @@ public class Bloodmoon extends Appliance {
new BloodmoonMonsterDeathListener(), new BloodmoonMonsterDeathListener(),
new BloodmoonPlayerJoinListener(), new BloodmoonPlayerJoinListener(),
new BloodmoonEntityDamageListener(), new BloodmoonEntityDamageListener(),
new BloodmoonTimeListener() new BloodmoonTimeListener(),
new BloodmoonTimeSkipListener()
); );
} }
@@ -19,7 +19,7 @@ public class BloodmoonSetting extends BoolSetting implements CategorizedSetting
@Override @Override
public SettingCategory category() { public SettingCategory category() {
return SettingCategory.Misc; // TODO: mehr als 8 bug fixen return SettingCategory.Gameplay;
} }
@Override @Override
@@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.comm
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -23,15 +24,19 @@ public class BloodmoonCommand extends ApplianceCommand<Bloodmoon> {
switch(args[0]) { switch(args[0]) {
case "start": { case "start": {
this.getAppliance().startBloodmoon(0L); this.getAppliance().startBloodmoon();
sender.sendMessage("Started bloodmoon."); sender.sendMessage("Started bloodmoon.");
break; break;
} }
case "stop": { case "stop": {
this.getAppliance().stopBloodmoon(); this.getAppliance().forceStopBloodmoon();
sender.sendMessage("Stopped bloodmoon."); sender.sendMessage("Stopped bloodmoon.");
break; break;
} }
case "time": {
sender.sendMessage(String.valueOf(Bukkit.getWorlds().getFirst().getFullTime()));
break;
}
default: throw new Error("No such option: '%s' !".formatted(args[0])); default: throw new Error("No such option: '%s' !".formatted(args[0]));
} }
} }
@@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.list
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
@@ -9,7 +10,9 @@ public class BloodmoonPlayerJoinListener extends ApplianceListener<Bloodmoon> {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
if(!this.getAppliance().bloodmoonIsActive()) return; if(!this.getAppliance().bloodmoonIsActive()) return;
this.getAppliance().addPlayerToBossBar(event.getPlayer()); Player player = event.getPlayer();
this.getAppliance().sendWarningMessage(event.getPlayer()); this.getAppliance().addPlayerToBossBar(player);
this.getAppliance().startHordeSpawning(1500, player);
this.getAppliance().sendWarningMessage(player);
} }
} }
@@ -12,14 +12,14 @@ public class BloodmoonTimeListener extends ApplianceListener<Bloodmoon> {
public void onServerTick(ServerTickStartEvent event) { public void onServerTick(ServerTickStartEvent event) {
long currentTime = Bukkit.getWorlds().getFirst().getFullTime(); long currentTime = Bukkit.getWorlds().getFirst().getFullTime();
if(this.getAppliance().isStartTick(currentTime)) { if(this.getAppliance().isStartTick(currentTime)) {
this.getAppliance().startBloodmoon(currentTime); this.getAppliance().startBloodmoon();
return; return;
} }
if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) { if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) {
this.getAppliance().stopBloodmoon(); this.getAppliance().stopBloodmoonIfInactive();
return; return;
} }
if(currentTime % Ticks.TICKS_PER_SECOND == 0) this.getAppliance().updateBossBar(); if(currentTime % Ticks.TICKS_PER_SECOND == 0 && this.getAppliance().bloodmoonIsActive()) this.getAppliance().updateBossBar();
if(this.getAppliance().isStartTick(currentTime + this.getAppliance().ticksPerDay)) { if(this.getAppliance().isStartTick(currentTime + this.getAppliance().ticksPerDay)) {
this.getAppliance().sendAnnouncementMessages(); this.getAppliance().sendAnnouncementMessages();
return; return;
@@ -0,0 +1,29 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import net.kyori.adventure.text.Component;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.event.world.TimeSkipEvent;
public class BloodmoonTimeSkipListener extends ApplianceListener<Bloodmoon> {
@EventHandler
public void onTimeSkip(TimeSkipEvent event) {
if(this.getAppliance().bloodmoonSkippable || !event.getSkipReason().equals(TimeSkipEvent.SkipReason.NIGHT_SKIP)) {
this.getAppliance().stopBloodmoonIfInactive();
return;
}
event.setCancelled(true);
}
@EventHandler
public void onPlayerSleep(PlayerBedEnterEvent event) {
if(!this.getAppliance().bloodmoonIsActive()) return;
if(this.getAppliance().bloodmoonSkippable) return;
if(!event.getBedEnterResult().equals(PlayerBedEnterEvent.BedEnterResult.OK)) return;
event.setUseBed(Event.Result.DENY);
event.getPlayer().sendActionBar(Component.text("Du kannst während dem Blutmond nicht schlafen"));
}
}
@@ -39,7 +39,6 @@ public class CustomAdvancements extends Appliance {
player.getAdvancementProgress(advancement).awardCriteria("criteria"); player.getAdvancementProgress(advancement).awardCriteria("criteria");
} catch(Exception e) { } catch(Exception e) {
Main.logger().info("Advancement " + advancementName + " not found! (is Custom Advancements data pack loaded?)"); Main.logger().info("Advancement " + advancementName + " not found! (is Custom Advancements data pack loaded?)");
throw e;
} }
} }
@@ -0,0 +1,21 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.mobSilenceNametag;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class FirstTestClass extends Appliance {
public String greetPlayer(Player player) {
return "Hello, " + player.getName() + "! Test 2";
}
protected @NotNull List<Listener> listeners() {
return List.of(new FirstTestListener());
}
}
@@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.mobSilenceNametag;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
public class FirstTestListener extends ApplianceListener<FirstTestClass> {
@EventHandler
public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) {
String greeting = this.getAppliance().greetPlayer(event.getPlayer());
event.getPlayer().sendMessage(greeting);
}
}
@@ -51,14 +51,15 @@ public class Outlawed extends Appliance implements DisplayName.Prefixed {
void askForConfirmation(Player player) { void askForConfirmation(Player player) {
Component confirmationMessage = switch(this.getLawStatus(player)) { Component confirmationMessage = switch(this.getLawStatus(player)) {
case DISABLED -> Component.text("Wenn du Vogelfrei aktivierst, darfst du von allen anderen vogelfreien Spielern grundlos angegriffen werden."); case DISABLED -> Component.text("Wenn du den PVP-Modus aktivierst, darfst du von allen anderen PVP-Spielern grundlos angegriffen werden." +
case VOLUNTARILY -> Component.text("Wenn du Vogelfrei deaktivierst, darfst du nicht mehr grundlos von anderen Spielern angegriffen werden."); " Du kannst den PVP-Modus erst wieder nach " + this.timeoutInMs / 1000 / 60 / 60 + " Stunden deaktivieren!");
case FORCED -> Component.text("Du darfst zurzeit deinen Vogelfreistatus nicht ändern, da dieser als Strafe auferlegt wurde!"); case VOLUNTARILY -> Component.text("Wenn du den PVP-Modus deaktivierst, darfst du nicht mehr grundlos von anderen Spielern angegriffen werden.");
case FORCED -> Component.text("Du darfst zurzeit deinen PVP-Modus nicht ändern, da die˝ser als Strafe auferlegt wurde!");
}; };
String command = String.format("/%s confirm", OutlawedCommand.commandName); String command = String.format("/%s confirm", OutlawedCommand.commandName);
Component changeText = Component.text( Component changeText = Component.text(
String.format( String.format(
"Zum ändern deines Vogelfrei status klicke auf diese Nachricht oder tippe '%s'", "Zum ändern deines PVP-Status klicke auf diese Nachricht oder tippe '%s'",
command command
), ),
NamedTextColor.GOLD NamedTextColor.GOLD
@@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
class OnCraftingBlockUseListener extends ApplianceListener<PortableCrafting> {
@EventHandler
public void inInteract(PlayerInteractEvent event) {
if(!event.getAction().equals(Action.RIGHT_CLICK_AIR)) return;
if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.EnablePortableCrafting, Boolean.class)) return;
switch(event.getMaterial()) {
case CRAFTING_TABLE -> this.getAppliance().openCraftingTable(event.getPlayer());
case STONECUTTER -> this.getAppliance().openStonecutter(event.getPlayer());
}
}
}
@@ -1,16 +0,0 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
class OnCraftingTableUseListener extends ApplianceListener<PortableCrafting> {
@EventHandler
public void inInteract(PlayerInteractEvent event) {
if(!event.getAction().equals(Action.RIGHT_CLICK_AIR)) return;
if(!event.getMaterial().equals(Material.CRAFTING_TABLE)) return;
this.getAppliance().openFor(event.getPlayer());
}
}
@@ -14,13 +14,16 @@ public class PortableCrafting extends Appliance {
Settings.instance().declareSetting(PortableCraftingSetting.class); Settings.instance().declareSetting(PortableCraftingSetting.class);
} }
public void openFor(Player player) { public void openCraftingTable(Player player) {
if(!Settings.instance().getSetting(player, Settings.Key.EnablePortableCrafting, Boolean.class)) return;
player.openWorkbench(null, true); player.openWorkbench(null, true);
} }
public void openStonecutter(Player player) {
player.openStonecutter(null, true);
}
@Override @Override
protected @NotNull List<Listener> listeners() { protected @NotNull List<Listener> listeners() {
return List.of(new OnCraftingTableUseListener()); return List.of(new OnCraftingBlockUseListener());
} }
} }
@@ -18,7 +18,7 @@ public class PortableCraftingSetting extends BoolSetting implements CategorizedS
@Override @Override
protected String description() { protected String description() {
return "Erlaubt das öffnen einer Werkbank in der Hand, ohne sie plazieren zu müssen"; return "Erlaubt das öffnen einer Werkbank oder einer Steinsäge in der Hand, ohne den Block plazieren zu müssen";
} }
@Override @Override
@@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.event;
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.core.util.server.Floodgate;
import eu.mhsl.craftattack.spawn.craftattack.api.repositories.EventRepository; import eu.mhsl.craftattack.spawn.craftattack.api.repositories.EventRepository;
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer; import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
@@ -26,6 +27,7 @@ import org.bukkit.entity.Villager;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.geysermc.cumulus.form.SimpleForm;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
@@ -106,6 +108,10 @@ public class Event extends Appliance {
} }
public void joinEvent(Player p) { public void joinEvent(Player p) {
this.joinEvent(p, false);
}
public void joinEvent(Player p, boolean ignoreBedrock) {
if(!this.isOpen) { if(!this.isOpen) {
p.sendMessage(Component.text("Zurzeit ist kein Event geöffnet.", NamedTextColor.RED)); p.sendMessage(Component.text("Zurzeit ist kein Event geöffnet.", NamedTextColor.RED));
return; return;
@@ -121,6 +127,23 @@ public class Event extends Appliance {
return; return;
} }
if(!ignoreBedrock && Floodgate.isBedrock(p)) {
Floodgate.getBedrockPlayer(p).sendForm(
SimpleForm.builder()
.title("Achtung!")
.content("Je nach deiner Minecraft-Bedrock-Version kann dein Minecraft in den Events abstürzen. " +
"Ggf. ist also für dich ein Mitspielen auf der Bedrock-Edition nicht möglich.")
.button("Ok, lass es uns versuchen")
.button("Abbrechen")
.validResultHandler(simpleFormResponse -> {
if(simpleFormResponse.clickedButtonId() != 0) return;
this.joinEvent(p, true);
})
.build()
);
return;
}
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName()); Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN)); p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
@@ -137,6 +160,7 @@ public class Event extends Appliance {
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN)); p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, this.localConfig().getString("connect-server-name")); PluginMessage.connect(p, this.localConfig().getString("connect-server-name"));
}); });
return;
} }
PluginMessage.connect(p, "grand-event"); PluginMessage.connect(p, "grand-event");
@@ -19,10 +19,10 @@ public class EventOpenSessionCommand extends ApplianceCommand<Event> {
@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) { if(args.length == 0) {
this.getAppliance().openEvent(Event.EventType.SMALL); this.getAppliance().openEvent(Event.EventType.SMALL);
} else { } else {
this.getAppliance().openEvent(Event.EventType.valueOf(args[1])); this.getAppliance().openEvent(Event.EventType.valueOf(args[0]));
} }
sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN)); sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN));
} }
@@ -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.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;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -52,7 +51,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)
.hoverEvent(HoverEvent.showText(ComponentUtil.clickLink(feedbackUrl)))) .append(ComponentUtil.clickLink(feedbackUrl)))
.appendNewline() .appendNewline()
.append(border); .append(border);
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
@Appliance.Flags(autoload = false, enabled = false)
public class WorldMuseum extends Appliance { public class WorldMuseum extends Appliance {
public DisplayVillager.ConfigBound villager; public DisplayVillager.ConfigBound villager;
@@ -22,6 +22,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
@@ -29,14 +30,14 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.bukkit.Sound.MUSIC_DISC_PRECIPICE; import static org.bukkit.Sound.MUSIC_DISC_LAVA_CHICKEN;
public class ProjectStart extends Appliance { public class ProjectStart extends Appliance {
private final int startMusicAt = 293; private final int startMusicAt = 130;
private final World startWorld = Bukkit.getWorld("world"); private final World startWorld = Bukkit.getWorld("world");
private final Location glassLocation = new Location(this.startWorld, 0, 64, -300); private final Location glassLocation = new Location(this.startWorld, -363, 126, 613);
private final List<Location> netherFireLocations = List.of( private final List<Location> netherFireLocations = List.of(
new Location(this.startWorld, 14, 71, -310) new Location(this.startWorld, -352, 131, 627)
); );
private final Countdown countdown = new Countdown( private final Countdown countdown = new Countdown(
@@ -47,7 +48,7 @@ public class ProjectStart extends Appliance {
); );
private final BlockCycle blockCycle = new BlockCycle( private final BlockCycle blockCycle = new BlockCycle(
this.glassLocation, this.glassLocation,
Material.RED_STAINED_GLASS, Material.WHITE_STAINED_GLASS,
List.of( List.of(
Material.RED_STAINED_GLASS, Material.RED_STAINED_GLASS,
Material.YELLOW_STAINED_GLASS, Material.YELLOW_STAINED_GLASS,
@@ -77,7 +78,7 @@ public class ProjectStart extends Appliance {
counter -> counter == this.startMusicAt, counter -> counter == this.startMusicAt,
counter -> this.glassLocation counter -> this.glassLocation
.getWorld() .getWorld()
.playSound(this.glassLocation, MUSIC_DISC_PRECIPICE, SoundCategory.RECORDS, 500f, 1f) .playSound(this.glassLocation, MUSIC_DISC_LAVA_CHICKEN, SoundCategory.RECORDS, 500f, 1f)
) )
); );
} }
@@ -121,14 +122,20 @@ public class ProjectStart extends Appliance {
IteratorUtil.worlds(World::getWorldBorder, worldBorder -> worldBorder.setSize(worldBorder.getMaxSize())); IteratorUtil.worlds(World::getWorldBorder, worldBorder -> worldBorder.setSize(worldBorder.getMaxSize()));
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, false)); IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, false));
IteratorUtil.worlds(world -> world.setFullTime(0)); Bukkit.getWorlds().getFirst().setFullTime(0);
this.netherFireLocations.forEach(location -> Objects.requireNonNull(this.startWorld).getBlockAt(location).setType(Material.FIRE)); this.netherFireLocations.forEach(location -> Objects.requireNonNull(this.startWorld).getBlockAt(location).setType(Material.FIRE));
Bukkit.getOnlinePlayers().forEach(player -> { Bukkit.getOnlinePlayers().forEach(player -> {
player.setFoodLevel(20); player.setFoodLevel(20);
player.setHealth(20); player.setHealth(20);
player.getInventory().clear(); if(player.getInventory().contains(Material.RECOVERY_COMPASS)) {
player.getInventory().clear();
player.getInventory().addItem(new ItemStack(Material.RECOVERY_COMPASS));
} else {
player.getInventory().clear();
}
player.setGameMode(GameMode.SURVIVAL); player.setGameMode(GameMode.SURVIVAL);
player.setExp(0); player.setExp(0);
player.setLevel(0); player.setLevel(0);
@@ -20,9 +20,10 @@ public class Strikes extends Appliance {
private final Map<Integer, Duration> strikePunishmentMap = Map.of( private final Map<Integer, Duration> strikePunishmentMap = Map.of(
1, Duration.ofHours(1), 1, Duration.ofHours(1),
2, Duration.ofHours(24), 2, Duration.ofHours(3),
3, Duration.ofDays(3), 3, Duration.ofDays(1),
4, Duration.ofDays(7) 4, Duration.ofDays(2),
5, Duration.ofDays(3)
); );
@@ -49,7 +49,7 @@ public class Whitelist extends Appliance {
? Floodgate.getBedrockPlayer(player).getUsername() ? Floodgate.getBedrockPlayer(player).getUsername()
: player.getName(); : player.getName();
if(!user.username().trim().equalsIgnoreCase(purePlayerName)) if(!user.username().trim().equalsIgnoreCase(purePlayerName) && !Floodgate.isBedrock(player)) // TODO: Bedrock Namen mit leerzeichen funktionieren nicht, daher die ausnahme bei der NUtzernamenprüfung
throw new DisconnectInfo.Throwable( throw new DisconnectInfo.Throwable(
"Nutzername geändert", "Nutzername geändert",
String.format("Der Name '%s' stimmt nicht mit '%s' überein.", user.username(), player.getName()), String.format("Der Name '%s' stimmt nicht mit '%s' überein.", user.username(), player.getName()),
@@ -12,6 +12,6 @@ class InfectionSpawnListener extends ApplianceListener<ArmadilloInfectionReducer
public void onSpawn(CreatureSpawnEvent event) { public void onSpawn(CreatureSpawnEvent event) {
if(!event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) return; if(!event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) return;
if(!event.getEntity().getType().equals(EntityType.SILVERFISH)) return; if(!event.getEntity().getType().equals(EntityType.SILVERFISH)) return;
if(ThreadLocalRandom.current().nextDouble() > 0.7) event.setCancelled(true); if(ThreadLocalRandom.current().nextDouble() > 0.8) event.setCancelled(true);
} }
} }
@@ -1,10 +1,12 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.silverfishExpReducer; package eu.mhsl.craftattack.spawn.craftattack.appliances.tweaks.silverfishExpReducer;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDeathEvent;
@Appliance.Flags(enabled = false)
class SilverfishDeathListener extends ApplianceListener<SilverfishExpReducer> { class SilverfishDeathListener extends ApplianceListener<SilverfishExpReducer> {
@EventHandler @EventHandler
public void onDeath(EntityDeathEvent event) { public void onDeath(EntityDeathEvent event) {
@@ -1,32 +1,52 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun; package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerDamageListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerJoinListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPlayerMoveListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners.DeathrunPortalListener;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Event; import eu.mhsl.craftattack.spawn.event.appliances.eventController.Event;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable; import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder; import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.title.Title; import net.kyori.adventure.title.Title;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
@Appliance.Flags(autoload = false) @Appliance.Flags(autoload = false)
public class Deathrun extends Appliance implements Event, Scorable { public class Deathrun extends Appliance implements Event, Scorable {
private final EventScoreboardBuilder scoreboardBuilder = new EventScoreboardBuilder(this, 3, 2, 3); private final EventScoreboardBuilder scoreboardBuilder = new EventScoreboardBuilder(this, 3, 2, 0);
private final double borderDistance = 100; private final double borderDistance = 100;
private final int borderVisibilityDistance = 8; private final int borderVisibilityDistance = 8;
private long durationSeconds; private long durationSeconds;
private boolean isBeforeStart = true;
private boolean pvpDisabled = true;
private final World world = Bukkit.getWorlds().getFirst();
private BukkitTask pvpTask;
public ArrayList<UUID> previouslyJoinedPlayers = new ArrayList<>();
@Override @Override
public void onEnable() { public void onEnable() {
World world = Bukkit.getWorlds().getFirst(); this.world.getWorldBorder().setCenter(this.world.getSpawnLocation());
world.getWorldBorder().setCenter(world.getSpawnLocation()); this.world.getWorldBorder().setSize(20);
world.getWorldBorder().setSize(20); Bukkit.getOnlinePlayers().forEach(player -> {
player.teleport(this.world.getSpawnLocation());
this.previouslyJoinedPlayers.add(player.getUniqueId());
});
this.pvpDisabled = true;
} }
public double getBorderDistance() { public double getBorderDistance() {
@@ -37,7 +57,7 @@ public class Deathrun extends Appliance implements Event, Scorable {
return this.borderVisibilityDistance; return this.borderVisibilityDistance;
} }
public void spawnParticleWall(Player p, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) { public void spawnParticles(Player p, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) {
Particle particle = Particle.WAX_ON; Particle particle = Particle.WAX_ON;
for (double y = yMin; y <= yMax; y += 0.5) { for (double y = yMin; y <= yMax; y += 0.5) {
@@ -51,18 +71,51 @@ public class Deathrun extends Appliance implements Event, Scorable {
@Override @Override
public void onDisable() { public void onDisable() {
World world = Bukkit.getWorlds().getFirst(); this.world.getWorldBorder().setSize(this.world.getWorldBorder().getMaxSize());
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize());
} }
@Override @Override
public void start(long durationSeconds) { public void start(long durationSeconds) {
this.isBeforeStart = false;
this.durationSeconds = durationSeconds; this.durationSeconds = durationSeconds;
Title title = Title.title(Component.text("Start"), Component.text("Laufe Richtung Osten! (positiv x)")); Title title = Title.title(Component.text("Start", NamedTextColor.GOLD), Component.text("Laufe Richtung Osten! (positiv x)", NamedTextColor.YELLOW));
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title)); // TODO: Soll PvP überhaupt aktiviert werden? Soll Respawn erlaubt sein?
Bukkit.getOnlinePlayers().forEach(player -> {
player.showTitle(title);
player.sendMessage(Component.text("Start! Laufe Richtung Osten (positiv x)!", NamedTextColor.YELLOW));
});
Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> Bukkit.getOnlinePlayers().forEach(player ->
player.sendMessage(Component.text("PvP wird in 10 Minuten aktiviert!", NamedTextColor.GOLD))
),
Ticks.TICKS_PER_SECOND * 5
);
World world = Bukkit.getWorlds().getFirst(); this.world.getWorldBorder().setSize(this.world.getWorldBorder().getMaxSize());
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize()); this.pvpTask = Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> {
this.pvpDisabled = false;
Bukkit.getOnlinePlayers().forEach(player -> {
player.sendMessage(Component.text("PvP ist jetzt aktiviert!", NamedTextColor.GOLD));
player.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.MASTER, 500f, 2f));
});
},
Ticks.TICKS_PER_SECOND * 60 * 10
);
}
public boolean isBeforeStart() {
return this.isBeforeStart;
}
public World getWorld() {
return this.world;
}
public boolean isPvpDisabled() {
return this.pvpDisabled;
} }
@Override @Override
@@ -74,6 +127,8 @@ public class Deathrun extends Appliance implements Event, Scorable {
public void stop() { public void stop() {
this.getScoreboardBuilder().stopAutomaticUpdates(); this.getScoreboardBuilder().stopAutomaticUpdates();
Title title = Title.title(Component.text("Ende!"), Component.empty()); Title title = Title.title(Component.text("Ende!"), Component.empty());
if(this.pvpTask != null) this.pvpTask.cancel();
this.pvpDisabled = true;
Bukkit.getOnlinePlayers().forEach(player -> { Bukkit.getOnlinePlayers().forEach(player -> {
player.showTitle(title); player.showTitle(title);
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
@@ -98,7 +153,10 @@ public class Deathrun extends Appliance implements Event, Scorable {
@Override @Override
protected @NotNull List<Listener> listeners() { protected @NotNull List<Listener> listeners() {
return List.of( return List.of(
new DeathrunPlayerMoveListener() new DeathrunPlayerMoveListener(),
new DeathrunPlayerDamageListener(),
new DeathrunPlayerJoinListener(),
new DeathrunPortalListener()
); );
} }
} }
@@ -0,0 +1,25 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
public class DeathrunPlayerDamageListener extends ApplianceListener<Deathrun> {
@EventHandler
public void onPlayerDamagePlayer(EntityDamageByEntityEvent event) {
if(!this.getAppliance().isPvpDisabled()) return;
if(!(event.getDamager() instanceof Player)) return;
if(!(event.getEntity() instanceof Player)) return;
event.setCancelled(true);
}
@EventHandler
public void onPlayerDamage(EntityDamageEvent event) {
if(!this.getAppliance().isBeforeStart()) return;
if(!(event.getEntity() instanceof Player)) return;
event.setCancelled(true);
}
}
@@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class DeathrunPlayerJoinListener extends ApplianceListener<Deathrun> {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if(
this.getAppliance().isBeforeStart() ||
!this.getAppliance().previouslyJoinedPlayers.contains(player.getUniqueId())
) {
player.teleport(this.getAppliance().getWorld().getSpawnLocation());
player.setGameMode(GameMode.ADVENTURE);
this.getAppliance().previouslyJoinedPlayers.add(player.getUniqueId());
}
if(!this.getAppliance().isBeforeStart() && player.getGameMode().equals(GameMode.ADVENTURE)) {
player.setGameMode(GameMode.SURVIVAL);
}
}
}
@@ -1,10 +1,12 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun; package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
public class DeathrunPlayerMoveListener extends ApplianceListener<Deathrun> { public class DeathrunPlayerMoveListener extends ApplianceListener<Deathrun> {
@EventHandler @EventHandler
@@ -14,22 +16,34 @@ public class DeathrunPlayerMoveListener extends ApplianceListener<Deathrun> {
double minZ = spawnLocation.z() - this.getAppliance().getBorderDistance(); double minZ = spawnLocation.z() - this.getAppliance().getBorderDistance();
double maxZ = spawnLocation.z() + this.getAppliance().getBorderDistance(); double maxZ = spawnLocation.z() + this.getAppliance().getBorderDistance();
if(event.getTo().x() < minX + this.getAppliance().getBorderVisibilityDistance()) { if(event.getTo().x() < minX + this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().spawnParticleWall(event.getPlayer(), minX-0.2, minX-0.2, event.getTo().y()-0.5, event.getTo().y()+2.5, event.getTo().z()-1.5, event.getTo().z()+1.5); this.getAppliance().spawnParticles(event.getPlayer(), minX-0.2, minX-0.2, event.getTo().y()-0.5, event.getTo().y()+2.5, event.getTo().z()-1.5, event.getTo().z()+1.5);
if(event.getTo().x() < minX) { if(event.getTo().x() < minX) {
event.setTo(event.getTo().clone().set(minX, event.getTo().y(), event.getTo().z())); event.setTo(event.getTo().clone().set(minX, event.getTo().y(), event.getTo().z()));
} }
} }
if(event.getTo().z() < minZ + this.getAppliance().getBorderVisibilityDistance()) { if(event.getTo().z() < minZ + this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().spawnParticleWall(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, minZ-0.2, minZ-0.2); this.getAppliance().spawnParticles(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, minZ-0.2, minZ-0.2);
if(event.getTo().z() < minZ) { if(event.getTo().z() < minZ) {
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), minZ)); event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), minZ));
} }
} }
if(event.getTo().z() > maxZ - this.getAppliance().getBorderVisibilityDistance()) { if(event.getTo().z() > maxZ - this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().spawnParticleWall(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, maxZ+0.2, maxZ+0.2); this.getAppliance().spawnParticles(event.getPlayer(), event.getTo().x()-1.5, event.getTo().x()+1.5, event.getTo().y()-0.5, event.getTo().y()+2.5, maxZ+0.2, maxZ+0.2);
if(event.getTo().z() > maxZ) { if(event.getTo().z() > maxZ) {
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), maxZ)); event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), maxZ));
} }
} }
} }
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) throws Exception {
Location spawnLocation = Bukkit.getWorlds().getFirst().getSpawnLocation();
double minX = spawnLocation.x() - this.getAppliance().getBorderDistance();
double minZ = spawnLocation.z() - this.getAppliance().getBorderDistance();
double maxZ = spawnLocation.z() + this.getAppliance().getBorderDistance();
if(event.getTo().x() < minX || event.getTo().z() < minZ || event.getTo().z() > maxZ) {
event.setCancelled(true);
throw new Exception("Player %s teleported outside the border.".formatted(event.getPlayer()));
}
}
} }
@@ -0,0 +1,13 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun.listeners;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.event.appliances.deathrun.Deathrun;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerPortalEvent;
public class DeathrunPortalListener extends ApplianceListener<Deathrun> {
@EventHandler
public void onPlayerPortal(PlayerPortalEvent event) {
event.setCancelled(true);
}
}
@@ -3,20 +3,53 @@ package eu.mhsl.craftattack.spawn.event.appliances.eventController;
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.core.util.IteratorUtil;
import eu.mhsl.craftattack.spawn.core.util.entity.PlayerUtils;
import eu.mhsl.craftattack.spawn.core.util.server.PluginMessage;
import eu.mhsl.craftattack.spawn.core.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.core.util.text.Countdown;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.BigEventCommand; import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.BigEventCommand;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.JoinCraftattackCommand;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.GameRule;
import org.bukkit.Statistic;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
import java.util.Map;
import static java.util.Map.entry;
public class EventController extends Appliance { public class EventController extends Appliance {
private List<Appliance> eventAppliances = null; private List<Appliance> eventAppliances = null;
private Event selectedEvent = null; private Event selectedEvent = null;
private long timerStart;
private int timerTaskId = -1; private int timerTaskId = -1;
private long durationMinutes;
private final Countdown countdown = new Countdown(
10,
this::format,
this::announce,
this::startEvent
);
private final Map<GameRule<Boolean>, Boolean> gameRulesAfterStart = Map.ofEntries(
entry(GameRule.DO_DAYLIGHT_CYCLE, true),
entry(GameRule.DO_INSOMNIA, true),
entry(GameRule.DISABLE_RAIDS, false),
entry(GameRule.DO_FIRE_TICK, true),
entry(GameRule.DO_ENTITY_DROPS, true),
entry(GameRule.DO_PATROL_SPAWNING, true),
entry(GameRule.DO_TRADER_SPAWNING, true),
entry(GameRule.DO_WEATHER_CYCLE, true),
entry(GameRule.FALL_DAMAGE, true),
entry(GameRule.FIRE_DAMAGE, true)
);
@Override @Override
public void onEnable() { public void onEnable() {
@@ -36,6 +69,8 @@ public class EventController extends Appliance {
Appliance newAppliance = Main.instance().restartAppliance(appliance.getClass()); Appliance newAppliance = Main.instance().restartAppliance(appliance.getClass());
if(!(newAppliance instanceof Event newEvent)) throw new IllegalArgumentException("Appliance has to implement Event."); if(!(newAppliance instanceof Event newEvent)) throw new IllegalArgumentException("Appliance has to implement Event.");
this.selectedEvent = newEvent; this.selectedEvent = newEvent;
Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.ADVENTURE));
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, true));
} }
public void unloadEvent() { public void unloadEvent() {
@@ -47,12 +82,62 @@ public class EventController extends Appliance {
this.selectedEvent = null; this.selectedEvent = null;
} }
public boolean hasLoadedEvent() {
return this.selectedEvent != null;
}
public void scheduleStart(long durationMinutes) {
if(!this.hasLoadedEvent()) throw new ApplianceCommand.Error("There is no event selected!");
this.durationMinutes = durationMinutes;
this.countdown.start();
}
public void cancelStart() {
if(this.countdown.isRunning()) this.countdown.cancel();
}
private Component format(Countdown.AnnouncementData data) {
return Component.text()
.append(ComponentUtil.createRainbowText(this.selectedEvent.getClass().getSimpleName(), 10))
.append(Component.text(" startet in ", NamedTextColor.GOLD))
.append(Component.text(data.count(), NamedTextColor.AQUA))
.append(Component.text(" " + data.unit() + "!", NamedTextColor.GOLD))
.build();
}
private void announce(Component message) {
IteratorUtil.onlinePlayers(player -> {
player.sendMessage(message);
player.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.MASTER, 500f, 1f));
});
}
private void startEvent() {
this.startEvent(this.durationMinutes);
}
public void startEvent(long durationMinutes) { public void startEvent(long durationMinutes) {
if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!"); if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!");
IteratorUtil.worlds(world -> IteratorUtil.setGameRules(this.gameRulesAfterStart, false));
IteratorUtil.worlds(world -> world.setFullTime(0));
Bukkit.getOnlinePlayers().forEach(player -> {
player.setFoodLevel(20);
player.setHealth(20);
player.getInventory().clear();
player.setGameMode(GameMode.SURVIVAL);
player.setExp(0);
player.setLevel(0);
player.playSound(Sound.sound(org.bukkit.Sound.ITEM_GOAT_HORN_SOUND_6, Sound.Source.MASTER, 500f, 1f));
player.sendMessage(Component.text("Viel Spaß bei %s!".formatted(this.selectedEvent.getClass().getSimpleName()), NamedTextColor.GREEN));
player.setStatistic(Statistic.TIME_SINCE_REST, 0);
PlayerUtils.resetStatistics(player);
});
this.selectedEvent.start(durationMinutes * 60); this.selectedEvent.start(durationMinutes * 60);
if(this.selectedEvent instanceof Scorable scorable && scorable.automaticUpdates()) scorable.getScoreboardBuilder().startAutomaticUpdates(); if(this.selectedEvent instanceof Scorable scorable && scorable.automaticUpdates()) scorable.getScoreboardBuilder().startAutomaticUpdates();
// TODO: possibility for other dimensions // TODO: possibility for other dimensions
this.timerStart = Bukkit.getWorlds().getFirst().getFullTime();
this.timerTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask( this.timerTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
Main.instance(), Main.instance(),
this::updateTimer, this::updateTimer,
@@ -66,6 +151,20 @@ public class EventController extends Appliance {
this.selectedEvent.stop(); this.selectedEvent.stop();
if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId); if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId);
this.timerTaskId = -1; this.timerTaskId = -1;
if(this.selectedEvent instanceof Scorable scorable) {
String scores = String.join("\n", scorable.getScoreboardBuilder().getScores());
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(scores));
Main.instance().getLogger().info(scores);
}
Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> {
Bukkit.getOnlinePlayers().forEach(player -> PluginMessage.connect(player, "craftattack"));
this.unloadEvent();
},
Ticks.TICKS_PER_SECOND * 7
);
} }
public String getSelectedEvent() { public String getSelectedEvent() {
@@ -74,12 +173,10 @@ public class EventController extends Appliance {
} }
private void updateTimer() { private void updateTimer() {
long ticksLeft = this.timerStart - (Bukkit.getWorlds().getFirst().getFullTime() - this.selectedEvent.getDurationSeconds() * Ticks.TICKS_PER_SECOND); long ticksLeft = -(Bukkit.getWorlds().getFirst().getFullTime() - this.selectedEvent.getDurationSeconds() * Ticks.TICKS_PER_SECOND);
if(ticksLeft <= 0) { if(ticksLeft <= 0) {
if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId);
this.timerTaskId = -1;
this.selectedEvent.stop();
Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(Component.text("Fertig!"))); Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(Component.text("Fertig!")));
this.stopEvent();
return; return;
} }
@@ -92,10 +189,18 @@ public class EventController extends Appliance {
return String.format("%02d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60); return String.format("%02d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60);
} }
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new EventPlayerLoginListener()
);
}
@Override @Override
protected @NotNull List<ApplianceCommand<?>> commands() { protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of( return List.of(
new BigEventCommand() new BigEventCommand(),
new JoinCraftattackCommand()
); );
} }
} }
@@ -0,0 +1,15 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import net.kyori.adventure.text.Component;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class EventPlayerLoginListener extends ApplianceListener<EventController> {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if(event.getPlayer().isOp()) return;
if(this.getAppliance().hasLoadedEvent()) return;
event.getPlayer().kick(Component.text("Es ist kein Event geladen."));
}
}
@@ -35,12 +35,12 @@ public class BigEventCommand extends ApplianceCommand<EventController> {
} }
case "start": { case "start": {
if(args.length == 1) { if(args.length == 1) {
this.getAppliance().startEvent(60 * 2); this.getAppliance().scheduleStart(60 * 2);
break; break;
} }
if(args.length == 2) { if(args.length == 2) {
try { try {
this.getAppliance().startEvent(Long.parseLong(args[1])); this.getAppliance().scheduleStart(Long.parseLong(args[1]));
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
throw new Error("Last argument has to be a long."); throw new Error("Last argument has to be a long.");
} }
@@ -48,6 +48,7 @@ public class BigEventCommand extends ApplianceCommand<EventController> {
break; break;
} }
case "stop": { case "stop": {
this.getAppliance().cancelStart();
this.getAppliance().stopEvent(); this.getAppliance().stopEvent();
break; break;
} }
@@ -0,0 +1,19 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController.commands;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.util.server.PluginMessage;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.EventController;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class JoinCraftattackCommand extends ApplianceCommand.PlayerChecked<EventController> {
public JoinCraftattackCommand() {
super("craftattack");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
PluginMessage.connect(this.getPlayer(), "craftattack");
}
}
@@ -8,6 +8,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Objective;
@@ -52,9 +53,6 @@ public class EventScoreboardBuilder {
objective.numberFormat(NumberFormat.blank()); objective.numberFormat(NumberFormat.blank());
UUID uuid = p.getUniqueId(); UUID uuid = p.getUniqueId();
scoreList.removeIf(e -> e.playerUuid().equals(uuid));
scoreList.add(new EventScoreEntry(uuid, p.getName(), this.scorable.getScore(p)));
scoreList.sort(this.scoreComparator); scoreList.sort(this.scoreComparator);
int size = scoreList.size(); int size = scoreList.size();
@@ -92,18 +90,37 @@ public class EventScoreboardBuilder {
int[] display = indices.distinct().sorted().toArray(); int[] display = indices.distinct().sorted().toArray();
for (int i = 0; i < display.length; i++) { List<String> lines = new ArrayList<>();
int idx = display[i]; int prevIdx = -1;
int sepNo = 0;
for (int idx : display) {
if (prevIdx != -1 && idx > prevIdx + 1) {
lines.add(this.separatorLine(sepNo++));
}
EventScoreEntry entry = scoreList.get(idx); EventScoreEntry entry = scoreList.get(idx);
if(!entry.playerUuid().equals(p.getUniqueId())) {
lines.add(this.formattedLine(idx, entry.name(), entry.score()));
} else {
lines.add(ChatColor.YELLOW + this.formattedLine(idx, entry.name(), entry.score()) + ChatColor.RESET);
}
String line = this.formattedLine(idx, entry.name(), entry.score()); prevIdx = idx;
}
objective.getScore(line).setScore(display.length - i); int score = lines.size();
for (String line : lines) {
objective.getScore(line).setScore(score--);
} }
return scoreboard; return scoreboard;
} }
private String separatorLine(int n) {
return ChatColor.GRAY + "..." + ChatColor.RESET + " ".repeat(n);
}
public void startAutomaticUpdates() { public void startAutomaticUpdates() {
this.scoreboardUpdateTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask( this.scoreboardUpdateTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
@@ -120,8 +137,19 @@ public class EventScoreboardBuilder {
} }
private void updateScore(Player p) { private void updateScore(Player p) {
this.playerScores.removeIf(entry -> entry.playerUuid().equals(p.getUniqueId())); EventScoreEntry previousEntry = this.playerScores.stream()
this.playerScores.add(new EventScoreEntry(p.getUniqueId(), p.getName(), this.scorable.getScore(p))); .filter(entry -> entry.playerUuid().equals(p.getUniqueId()))
.findFirst()
.orElse(null);
if(previousEntry == null) {
this.playerScores.add(new EventScoreEntry(p.getUniqueId(), p.getName(), this.scorable.getScore(p)));
return;
}
int currentScore = this.scorable.getScore(p);
if(previousEntry.score() < currentScore) {
this.playerScores.removeIf(entry -> entry.playerUuid().equals(p.getUniqueId()));
this.playerScores.add(new EventScoreEntry(p.getUniqueId(), p.getName(), currentScore));
}
} }
public void updateScoreboards() { public void updateScoreboards() {
@@ -134,7 +162,17 @@ public class EventScoreboardBuilder {
private String formattedLine(int place, String name, int score) { private String formattedLine(int place, String name, int score) {
name = this.trimName(name); name = this.trimName(name);
return "%s. %s: %s".formatted(place+1, name, score); return "%s. %s : %s".formatted(place+1, name, score);
}
public List<String> getScores() {
List<EventScoreEntry> scoreList = new ArrayList<>(this.playerScores);
scoreList.sort(this.scoreComparator);
ArrayList<String> result = new ArrayList<>();
for(int i = 0; i < scoreList.size(); i++) {
result.add(this.formattedLine(i, scoreList.get(i).name(), scoreList.get(i).score()));
}
return result;
} }
private String trimName(String name) { private String trimName(String name) {