Compare commits

..

16 Commits

21 changed files with 596 additions and 76 deletions

View File

@@ -16,7 +16,6 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -179,13 +178,6 @@ public class Report extends Appliance {
issuer.sendMessage(component.build());
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new ReportCreatedListener()
);
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {

View File

@@ -1,26 +0,0 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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));
}
}

View File

@@ -11,7 +11,6 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Appliance.Flags(enabled = false)
public class AntiInventoryMove extends Appliance {
private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2;

View File

@@ -3,15 +3,13 @@ package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
class InventoryTrackerListener extends ApplianceListener<AntiInventoryMove> {
@EventHandler(priority = EventPriority.MONITOR)
@EventHandler
public void onOpen(InventoryOpenEvent event) {
if(!(event.getPlayer() instanceof Player player)) return;
if(event.isCancelled()) return;
this.getAppliance().setInvOpen(player, true);
}

View File

@@ -10,6 +10,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.reflections.Reflections;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
@@ -49,7 +50,7 @@ public final class Main extends JavaPlugin {
Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
Main.logger().info("Loading appliances...");
this.appliances = this.findSubtypesOf(Appliance.class).stream()
this.appliances = new ArrayList<>(this.findSubtypesOf(Appliance.class).stream()
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
.filter(appliance -> {
Appliance.Flags flags = appliance.getAnnotation(Appliance.Flags.class);
@@ -63,14 +64,17 @@ public final class Main extends JavaPlugin {
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
}
})
.toList();
.toList());
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
Main.logger().info("Initializing appliances...");
this.appliances.forEach(appliance -> {
appliance.onEnable();
appliance.initialize(this);
});
this.appliances.stream()
.filter(appliance -> {
Appliance.Flags flags = appliance.getClass().getAnnotation(Appliance.Flags.class);
if(flags == null) return true;
return flags.autoload();
})
.forEach(appliance -> appliance.enableSequence(this));
Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
if(Configuration.pluginConfig.getBoolean("httpServerEnabled", true)) {
@@ -85,17 +89,34 @@ public final class Main extends JavaPlugin {
@Override
public void onDisable() {
Main.logger().info("Disabling appliances...");
this.appliances.forEach(appliance -> {
Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
appliance.onDisable();
appliance.destruct(this);
});
this.appliances.forEach(appliance -> appliance.disableSequence(this));
HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTasks(this);
Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
}
public Appliance restartAppliance(Class<? extends Appliance> applianceClass) {
Appliance oldAppliance = this.appliances.stream()
.filter(appliance -> appliance.getClass().equals(applianceClass))
.findFirst()
.orElseThrow();
oldAppliance.disableSequence(this);
this.appliances.remove(oldAppliance);
Appliance newAppliance = this.createApplianceInstance(applianceClass);
this.appliances.add(newAppliance);
newAppliance.enableSequence(this);
return newAppliance;
}
private Appliance createApplianceInstance(Class<? extends Appliance> applianceClass) {
try {
return applianceClass.getDeclaredConstructor().newInstance();
} catch(Exception e) {
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
}
}
public <T extends Appliance> T getAppliance(Class<T> clazz) {
return this.appliances.stream()
.filter(clazz::isInstance)

View File

@@ -28,16 +28,14 @@ public class WebsiteHook extends HttpHook {
return HttpServer.nothing;
}));
record CreatedReport(UUID reporter, UUID reported, String reason) {}
record CreatedReport(String reporter, String reported, String reason) {}
this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> {
Main.logger().info(String.format("New Report from Hook: (%s) Reporter: %s Reported: %s", createdReport.reason, createdReport.reporter, createdReport.reported));
SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason)));
return HttpServer.nothing;
}));
record CreatedStrike(UUID uuid) {}
this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> {
Main.logger().info(String.format("New Strike from Hook! (User %s)", createdStrike.uuid));
SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid)));
return HttpServer.nothing;
}));

View File

@@ -28,6 +28,7 @@ public abstract class Appliance {
@Retention(RetentionPolicy.RUNTIME)
public @interface Flags {
boolean enabled() default true;
boolean autoload() default true;
}
private String localConfigPath;
@@ -101,9 +102,20 @@ public abstract class Appliance {
}
public void destruct(@NotNull JavaPlugin plugin) {
if(this.listeners == null) return;
this.listeners.forEach(HandlerList::unregisterAll);
}
public void enableSequence(JavaPlugin plugin) {
this.onEnable();
this.initialize(plugin);
}
public void disableSequence(JavaPlugin plugin) {
this.onDisable();
this.destruct(plugin);
}
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
return Main.instance().getAppliance(clazz);
}

View File

@@ -4,8 +4,6 @@ import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ReportCreatedEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
@Override
@@ -17,7 +15,7 @@ public class ReportCreatedEvent extends Event {
return HANDLERS;
}
public record CreatedReport(UUID reporter, UUID reported, String reason) {}
public record CreatedReport(String reporter, String reported, String reason) {}
private final CreatedReport report;
public ReportCreatedEvent(CreatedReport report) {

View File

@@ -92,10 +92,10 @@ public class Bloodmoon extends Appliance {
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
private long lastBloodmoonStartTick = 0;
public final int ticksPerDay = 24000;
public final int bloodmoonLength = ticksPerDay/2;
public final int bloodmoonLength = this.ticksPerDay /2;
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
private final int bloodmoonFreeDaysAtStart = 3;
private final int bloodmoonStartTime = ticksPerDay/2;
private final int bloodmoonStartTime = this.ticksPerDay /2;
private final int bloodmoonDayInterval = 30;
private final int minBonusDrops = 1;
private final int maxBonusDrops = 4;

View File

@@ -37,6 +37,11 @@ public class Event extends Appliance {
DONE
}
public enum EventType {
BIG,
SMALL
}
Countdown advertiseCountdown = new Countdown(
120,
announcementData -> Component.text()
@@ -50,6 +55,7 @@ public class Event extends Appliance {
);
public DisplayVillager.ConfigBound villager;
private boolean isOpen = false;
private EventType eventType;
private AdvertisementStatus advertiseStatus = AdvertisementStatus.BEFORE;
private UUID roomId;
private final List<Reward> pendingRewards = new ArrayList<>();
@@ -79,18 +85,24 @@ public class Event extends Appliance {
if(this.isOpen) this.roomId = UUID.fromString(this.localConfig().getString("roomId", ""));
}
public void openEvent() {
public void openEvent(EventType type) {
if(this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit bereits ein Event!");
this.eventType = type;
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.CreatedRoom> sessionResponse = this.queryRepository(EventRepository.class).createSession();
if(type.equals(EventType.SMALL)) {
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.CreatedRoom> sessionResponse = this.queryRepository(EventRepository.class).createSession();
if(sessionResponse.status() != HttpStatus.OK)
throw new ApplianceCommand.Error("Event-Server meldet Fehler: " + sessionResponse.status());
if(sessionResponse.status() != HttpStatus.OK)
throw new ApplianceCommand.Error("Event-Server meldet Fehler: " + sessionResponse.status());
this.isOpen = true;
this.roomId = sessionResponse.data().uuid();
});
this.isOpen = true;
this.roomId = sessionResponse.data().uuid();
});
return;
}
this.isOpen = true;
}
public void joinEvent(Player p) {
@@ -112,18 +124,22 @@ public class Event extends Appliance {
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.QueueRoom.Response> queueResponse = this.queryRepository(EventRepository.class)
.queueRoom(new EventRepository.QueueRoom(p.getUniqueId(), this.roomId));
if(this.eventType.equals(EventType.SMALL)) {
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.QueueRoom.Response> queueResponse = this.queryRepository(EventRepository.class)
.queueRoom(new EventRepository.QueueRoom(p.getUniqueId(), this.roomId));
if(queueResponse.status() != HttpStatus.OK || queueResponse.data().error() != null) {
p.sendMessage(Component.text("Fehler beim Betreten: " + queueResponse.data().error(), NamedTextColor.RED));
return;
}
if(queueResponse.status() != HttpStatus.OK || queueResponse.data().error() != null) {
p.sendMessage(Component.text("Fehler beim Betreten: " + queueResponse.data().error(), NamedTextColor.RED));
return;
}
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, this.localConfig().getString("connect-server-name"));
});
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, this.localConfig().getString("connect-server-name"));
});
}
PluginMessage.connect(p, "grand-event");
}
public void endEvent() {

View File

@@ -7,6 +7,10 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
public class EventOpenSessionCommand extends ApplianceCommand<Event> {
public EventOpenSessionCommand() {
@@ -15,7 +19,17 @@ public class EventOpenSessionCommand extends ApplianceCommand<Event> {
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().openEvent();
if(args.length == 1) {
this.getAppliance().openEvent(Event.EventType.SMALL);
} else {
this.getAppliance().openEvent(Event.EventType.valueOf(args[1]));
}
sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 1) return Arrays.stream(Event.EventType.values()).map(Enum::toString).toList();
return null;
}
}

9
event/build.gradle Normal file
View File

@@ -0,0 +1,9 @@
dependencies {
implementation project(':common')
implementation project(':core')
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
compileOnly 'org.geysermc.floodgate:api:2.2.4-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4'
}

View File

@@ -0,0 +1,104 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Event;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import org.bukkit.Bukkit;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@Appliance.Flags(autoload = false)
public class Deathrun extends Appliance implements Event, Scorable {
private final EventScoreboardBuilder scoreboardBuilder = new EventScoreboardBuilder(this, 3, 2, 3);
private final double borderDistance = 100;
private final int borderVisibilityDistance = 8;
private long durationSeconds;
@Override
public void onEnable() {
World world = Bukkit.getWorlds().getFirst();
world.getWorldBorder().setCenter(world.getSpawnLocation());
world.getWorldBorder().setSize(20);
}
public double getBorderDistance() {
return this.borderDistance;
}
public int getBorderVisibilityDistance() {
return this.borderVisibilityDistance;
}
public void spawnParticleWall(Player p, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) {
Particle particle = Particle.WAX_ON;
for (double y = yMin; y <= yMax; y += 0.5) {
for (double z = zMin; z <= zMax; z += 0.5) {
for (double x = xMin; x <= xMax; x += 0.5) {
p.spawnParticle(particle, x, y, z, 1, 0.05, 0.05, 0.05, 0);
}
}
}
}
@Override
public void onDisable() {
World world = Bukkit.getWorlds().getFirst();
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize());
}
@Override
public void start(long durationSeconds) {
this.durationSeconds = durationSeconds;
Title title = Title.title(Component.text("Start"), Component.text("Laufe Richtung Osten! (positiv x)"));
Bukkit.getOnlinePlayers().forEach(player -> player.showTitle(title));
World world = Bukkit.getWorlds().getFirst();
world.getWorldBorder().setSize(world.getWorldBorder().getMaxSize());
}
@Override
public int getScore(Player p) {
return (int) Math.ceil(p.getLocation().x());
}
@Override
public void stop() {
this.getScoreboardBuilder().stopAutomaticUpdates();
Title title = Title.title(Component.text("Ende!"), Component.empty());
Bukkit.getOnlinePlayers().forEach(player -> {
player.showTitle(title);
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
});
}
@Override
public long getDurationSeconds() {
return this.durationSeconds;
}
@Override
public String getScoreboardName() {
return "Deathrun";
}
@Override
public EventScoreboardBuilder getScoreboardBuilder() {
return this.scoreboardBuilder;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(
new DeathrunPlayerMoveListener()
);
}
}

View File

@@ -0,0 +1,35 @@
package eu.mhsl.craftattack.spawn.event.appliances.deathrun;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerMoveEvent;
public class DeathrunPlayerMoveListener extends ApplianceListener<Deathrun> {
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
Location spawnLocation = Bukkit.getWorlds().getFirst().getSpawnLocation();
double minX = spawnLocation.x() - this.getAppliance().getBorderDistance();
double minZ = spawnLocation.z() - this.getAppliance().getBorderDistance();
double maxZ = spawnLocation.z() + this.getAppliance().getBorderDistance();
if(event.getTo().x() < minX + this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().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);
if(event.getTo().x() < minX) {
event.setTo(event.getTo().clone().set(minX, event.getTo().y(), event.getTo().z()));
}
}
if(event.getTo().z() < minZ + this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().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);
if(event.getTo().z() < minZ) {
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), minZ));
}
}
if(event.getTo().z() > maxZ - this.getAppliance().getBorderVisibilityDistance()) {
this.getAppliance().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);
if(event.getTo().z() > maxZ) {
event.setTo(event.getTo().clone().set(event.getTo().x(), event.getTo().y(), maxZ));
}
}
}
}

View File

@@ -0,0 +1,7 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
public interface Event {
void start(long durationSeconds);
void stop();
long getDurationSeconds();
}

View File

@@ -0,0 +1,101 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.commands.BigEventCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class EventController extends Appliance {
private List<Appliance> eventAppliances = null;
private Event selectedEvent = null;
private long timerStart;
private int timerTaskId = -1;
@Override
public void onEnable() {
this.eventAppliances = Main.instance().getAppliances().stream()
.filter(appliance -> appliance instanceof Event)
.toList();
}
public List<Appliance> getEventAppliances() {
if(this.eventAppliances == null) throw new IllegalStateException("Event appliances were not initialized");
return this.eventAppliances;
}
public void loadEvent(Appliance appliance) {
if(!(appliance instanceof Event)) throw new IllegalArgumentException("Appliance has to implement Event.");
this.unloadEvent();
Appliance newAppliance = Main.instance().restartAppliance(appliance.getClass());
if(!(newAppliance instanceof Event newEvent)) throw new IllegalArgumentException("Appliance has to implement Event.");
this.selectedEvent = newEvent;
}
public void unloadEvent() {
if(this.selectedEvent == null) return;
this.selectedEvent.stop();
if(this.selectedEvent instanceof Appliance appliance) {
appliance.disableSequence(Main.instance());
}
this.selectedEvent = null;
}
public void startEvent(long durationMinutes) {
if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!");
this.selectedEvent.start(durationMinutes * 60);
if(this.selectedEvent instanceof Scorable scorable && scorable.automaticUpdates()) scorable.getScoreboardBuilder().startAutomaticUpdates();
// TODO: possibility for other dimensions
this.timerStart = Bukkit.getWorlds().getFirst().getFullTime();
this.timerTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
Main.instance(),
this::updateTimer,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND
);
}
public void stopEvent() {
if(this.selectedEvent == null) throw new ApplianceCommand.Error("There is no event selected!");
this.selectedEvent.stop();
if(this.timerTaskId != -1) Bukkit.getScheduler().cancelTask(this.timerTaskId);
this.timerTaskId = -1;
}
public String getSelectedEvent() {
if(this.selectedEvent == null) return "nothing selected";
return this.selectedEvent.getClass().getSimpleName().toLowerCase();
}
private void updateTimer() {
long ticksLeft = this.timerStart - (Bukkit.getWorlds().getFirst().getFullTime() - this.selectedEvent.getDurationSeconds() * Ticks.TICKS_PER_SECOND);
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!")));
return;
}
Bukkit.getOnlinePlayers().forEach(player -> player.sendActionBar(
Component.text(formatSeconds(ticksLeft / Ticks.TICKS_PER_SECOND), NamedTextColor.GOLD)
));
}
public static String formatSeconds(long seconds){
return String.format("%02d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60);
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new BigEventCommand()
);
}
}

View File

@@ -0,0 +1,13 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard.EventScoreboardBuilder;
import org.bukkit.entity.Player;
public interface Scorable {
String getScoreboardName();
default boolean automaticUpdates() {
return true;
}
EventScoreboardBuilder getScoreboardBuilder();
int getScore(Player p);
}

View File

@@ -0,0 +1,75 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController.commands;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.EventController;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class BigEventCommand extends ApplianceCommand<EventController> {
public BigEventCommand() {
super("event");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 0) throw new Error("No argument selected!");
switch(args[0]) {
case "unload": {
this.getAppliance().unloadEvent();
break;
}
case "load": {
if(args.length == 1) throw new Error("Not enough arguments for select.");
this.getAppliance().loadEvent(this.findApplianceFromString(args[1]));
break;
}
case "selected": {
sender.sendMessage("This Event is currently selected: %s".formatted(this.getAppliance().getSelectedEvent()));
break;
}
case "start": {
if(args.length == 1) {
this.getAppliance().startEvent(60 * 2);
break;
}
if(args.length == 2) {
try {
this.getAppliance().startEvent(Long.parseLong(args[1]));
} catch(NumberFormatException e) {
throw new Error("Last argument has to be a long.");
}
}
break;
}
case "stop": {
this.getAppliance().stopEvent();
break;
}
default: throw new Error("No such option: '%s' !".formatted(args[0]));
}
}
private Appliance findApplianceFromString(String name) {
System.out.println(this.getAppliance().getEventAppliances());
return this.getAppliance().getEventAppliances().stream()
.filter(appliance -> appliance.getClass().getSimpleName().equalsIgnoreCase(name))
.findFirst()
.orElseThrow(() -> new Error("Event appliance '%s' not found.".formatted(name)));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 1) return List.of("load", "unload", "selected", "start", "stop");
if(args.length == 2 && args[0].equals("load")) return this.getAppliance().getEventAppliances().stream()
.map(appliance -> appliance.getClass().getSimpleName().toLowerCase())
.toList();
if(args.length == 2 && args[0].equals("start")) return List.of("<minutes>");
return null;
}
}

View File

@@ -0,0 +1,6 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard;
import java.util.UUID;
public record EventScoreEntry(UUID playerUuid, String name, int score) {
}

View File

@@ -0,0 +1,147 @@
package eu.mhsl.craftattack.spawn.event.appliances.eventController.scoreboard;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.event.appliances.eventController.Scorable;
import io.papermc.paper.scoreboard.numbers.NumberFormat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import java.util.*;
import java.util.stream.IntStream;
public class EventScoreboardBuilder {
private final int topPlaces;
private final int aroundPlaces;
private final int bottomPlaces;
private final Scorable scorable;
private final Comparator<EventScoreEntry> scoreComparator;
private final List<EventScoreEntry> playerScores = new ArrayList<>();
private int scoreboardUpdateTaskId = -1;
public EventScoreboardBuilder(Scorable scorable, int topPlaces, int aroundPlaces, int bottomPlaces) {
this(scorable, topPlaces, aroundPlaces, bottomPlaces, Comparator.comparingInt(EventScoreEntry::score).reversed());
}
public EventScoreboardBuilder(Scorable scorable, int topPlaces, int aroundPlaces, int bottomPlaces, Comparator<EventScoreEntry> scoreComparator) {
this.scorable = scorable;
this.topPlaces = topPlaces;
this.aroundPlaces = aroundPlaces;
this.bottomPlaces = bottomPlaces;
this.scoreComparator = scoreComparator;
}
public Scoreboard buildFor(Player p) {
List<EventScoreEntry> scoreList = new ArrayList<>(this.playerScores);
Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
Objective objective = scoreboard.registerNewObjective(
"event", "dummy",
Component.text(
"Scoreboard %s".formatted(this.scorable.getScoreboardName()),
NamedTextColor.GOLD, TextDecoration.BOLD
)
);
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
objective.numberFormat(NumberFormat.blank());
UUID uuid = p.getUniqueId();
scoreList.removeIf(e -> e.playerUuid().equals(uuid));
scoreList.add(new EventScoreEntry(uuid, p.getName(), this.scorable.getScore(p)));
scoreList.sort(this.scoreComparator);
int size = scoreList.size();
int playerIndex = IntStream.range(0, size)
.filter(i -> scoreList.get(i).playerUuid().equals(uuid))
.findFirst()
.orElse(0);
IntStream top = IntStream.range(0, Math.min(this.topPlaces, size));
int aroundStart = Math.max(0, playerIndex - this.aroundPlaces);
int aroundEnd = Math.min(size, playerIndex + this.aroundPlaces + 1);
IntStream around = IntStream.range(aroundStart, aroundEnd);
IntStream bottom = IntStream.range(Math.max(0, size - this.bottomPlaces), size);
int threshold = this.topPlaces + this.bottomPlaces + this.aroundPlaces * 2 + 1;
IntStream indices;
if (size <= threshold) {
indices = IntStream.range(0, size);
} else if (playerIndex <= this.topPlaces + this.aroundPlaces) {
indices = IntStream.concat(
IntStream.range(0, Math.min(size, playerIndex + this.aroundPlaces + 1)),
bottom
);
} else if (playerIndex >= size - this.bottomPlaces - this.aroundPlaces - 1) {
indices = IntStream.concat(
top,
IntStream.range(Math.max(0, playerIndex - this.aroundPlaces), size)
);
} else {
indices = IntStream.concat(IntStream.concat(top, around), bottom);
}
int[] display = indices.distinct().sorted().toArray();
for (int i = 0; i < display.length; i++) {
int idx = display[i];
EventScoreEntry entry = scoreList.get(idx);
String line = this.formattedLine(idx, entry.name(), entry.score());
objective.getScore(line).setScore(display.length - i);
}
return scoreboard;
}
public void startAutomaticUpdates() {
this.scoreboardUpdateTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
Main.instance(),
this::updateScoreboards,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND
);
}
public void stopAutomaticUpdates() {
if(this.scoreboardUpdateTaskId != -1) Bukkit.getScheduler().cancelTask(this.scoreboardUpdateTaskId);
this.scoreboardUpdateTaskId = -1;
}
private void updateScore(Player p) {
this.playerScores.removeIf(entry -> entry.playerUuid().equals(p.getUniqueId()));
this.playerScores.add(new EventScoreEntry(p.getUniqueId(), p.getName(), this.scorable.getScore(p)));
}
public void updateScoreboards() {
Bukkit.getOnlinePlayers().forEach(player -> {
this.updateScore(player);
Scoreboard scoreboard = this.buildFor(player);
player.setScoreboard(scoreboard);
});
}
private String formattedLine(int place, String name, int score) {
name = this.trimName(name);
return "%s. %s: %s".formatted(place+1, name, score);
}
private String trimName(String name) {
if (name == null) return "Unknown";
if (name.length() > 12) {
return name.substring(0, 12);
}
return name;
}
}

View File

@@ -4,4 +4,5 @@ include 'core'
include 'craftattack'
include 'common'
include 'varo'
include 'event'