diff --git a/.gitignore b/.gitignore index 36e1257e..e038e02a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,9 @@ steamwar.properties .idea *.iml +# VSCode +bin/ +.vscode + # Other lib \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index 009c3b36..5138608b 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { compileOnly(libs.spigotapi) compileOnly(libs.axiom) compileOnly(libs.authlib) + compileOnly(libs.viaapi) compileOnly(libs.nms20) compileOnly(libs.fawe18) diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index 7f44e063..67a16a47 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -1012,3 +1012,5 @@ COLORREPLACE_HELP=§8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- § TYPEREPLACE_HELP=§8//§etypereplace §8[§7type§8] §8[§7type§8] §8- §7Replace all blocks of one type with another # Schematic SCHEMATIC_GUI_ITEM=§eSchematics +#VersionAnnouncer +SERVER_VERSION=§7This server runs on Minecraft version §e{0} \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index 46899380..4013f3b2 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -953,3 +953,5 @@ COLORREPLACE_HELP=§8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- § TYPEREPLACE_HELP=§8//§etyreplace §8[§7type§8] §8[§7type§8] §8- §7Ersetzt einen Blockgruppe mit einer anderen # Schematics SCHEMATIC_GUI_ITEM=§eSchematics +#VersionAnnouncer +SERVER_VERSION=§7Dieser Server läuft auf Minecraft-Version §e{0} \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index b37e8e8b..0b1b160c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -28,6 +28,8 @@ import de.steamwar.bausystem.features.script.lua.libs.LuaLib; import de.steamwar.bausystem.features.slaves.panzern.Panzern; import de.steamwar.bausystem.features.slaves.panzern.PanzernAlgorithm; import de.steamwar.bausystem.features.tpslimit.TPSFreezeUtils; +import de.steamwar.bausystem.features.tracer.TraceManager; +import de.steamwar.bausystem.features.tracer.TraceRecorder; import de.steamwar.bausystem.features.world.BauScoreboard; import de.steamwar.bausystem.linkage.specific.BauGuiItem; import de.steamwar.bausystem.region.loader.PrototypeLoader; @@ -70,7 +72,7 @@ import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.Collectors; -public class BauSystem extends JavaPlugin implements Listener { +public class BauSystem extends JavaPlugin { // This should be treated as final! public static Message MESSAGE; @@ -197,6 +199,9 @@ public class BauSystem extends JavaPlugin implements Listener { }); TickListener.impl.init(); + + TraceManager.instance.init(); + TraceRecorder.instance.init(); } @Override diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java index 680224a7..3e3e4483 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java @@ -61,6 +61,9 @@ public class ResetCommand extends SWCommand { PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getResetFile(RegionType.NORMAL))) .color(region.getPlain(Flag.COLOR, ColorMode.class).getColor()); region.reset(pasteBuilder, RegionType.NORMAL, RegionExtensionType.NORMAL); + for (Flag value : Flag.values()) { + region.set(value, value.getDefaultValue()); + } RegionUtils.message(region, "REGION_RESET_RESETED"); } catch (SecurityException e) { BauSystem.MESSAGE.send("REGION_RESET_ERROR", p); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TNTPoint.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TNTPoint.java index ed0f1306..1e9fd38d 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TNTPoint.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TNTPoint.java @@ -22,6 +22,8 @@ package de.steamwar.bausystem.features.tracer; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.utils.RegionExtensionType; import de.steamwar.bausystem.region.utils.RegionType; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -39,8 +41,9 @@ import java.util.Optional; /** * Recording of a tnt at a specific tick */ +@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter -public class TNTPoint implements Externalizable { +public class TNTPoint{ /** * Unique number to identify records being of the same tnt */ @@ -97,12 +100,9 @@ public class TNTPoint implements Externalizable { private List history; /** - * Constructor for deserialization only !! Do not Call !! + * Constructor for object creation in trace recording */ - public TNTPoint() { - } - - public TNTPoint(int tntId, TNTPrimed tnt, boolean explosion, boolean afterFirstExplosion, long ticksSinceStart, + protected TNTPoint(int tntId, TNTPrimed tnt, boolean explosion, boolean afterFirstExplosion, long ticksSinceStart, List history, List destroyedBlocks) { this.tntId = tntId; this.explosion = explosion; @@ -161,44 +161,6 @@ public class TNTPoint implements Externalizable { this.history = history; } - @Override - public void writeExternal(ObjectOutput objectOutput) throws IOException { - objectOutput.writeInt(tntId); - objectOutput.writeBoolean(explosion); - objectOutput.writeBoolean(inWater); - objectOutput.writeBoolean(afterFirstExplosion); - objectOutput.writeBoolean(destroyedBuildArea); - objectOutput.writeBoolean(destroyedTestBlock); - objectOutput.writeLong(ticksSinceStart); - objectOutput.writeInt(fuse); - objectOutput.writeDouble(location.getX()); - objectOutput.writeDouble(location.getY()); - objectOutput.writeDouble(location.getZ()); - objectOutput.writeDouble(velocity.getX()); - objectOutput.writeDouble(velocity.getY()); - objectOutput.writeDouble(velocity.getZ()); - } - - @Override - public void readExternal(ObjectInput objectInput) throws IOException { - tntId = objectInput.readInt(); - explosion = objectInput.readBoolean(); - inWater = objectInput.readBoolean(); - afterFirstExplosion = objectInput.readBoolean(); - destroyedBuildArea = objectInput.readBoolean(); - destroyedTestBlock = objectInput.readBoolean(); - ticksSinceStart = objectInput.readLong(); - fuse = objectInput.readInt(); - double locX = objectInput.readDouble(); - double locY = objectInput.readDouble(); - double locZ = objectInput.readDouble(); - location = new Location(Bukkit.getWorlds().get(0), locX, locY, locZ); - double velX = objectInput.readDouble(); - double velY = objectInput.readDouble(); - double velZ = objectInput.readDouble(); - velocity = new Vector(velX, velY, velZ); - } - @Override public String toString() { return "TNTPoint{" + diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java index e3305def..9628570a 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java @@ -26,20 +26,16 @@ import de.steamwar.bausystem.features.tracer.rendering.ViewFlag; import de.steamwar.bausystem.region.Region; import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; -import lombok.Cleanup; import lombok.Getter; +import lombok.Setter; import lombok.SneakyThrows; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import java.io.*; +import java.io.File; import java.lang.ref.SoftReference; import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.zip.GZIPInputStream; public class Trace { /** @@ -77,6 +73,10 @@ public class Trace { */ private SoftReference> records; + @Setter + @Getter + private int recordsCount; + /** * A map of all REntityServers rendering this trace */ @@ -91,46 +91,25 @@ public class Trace { @SneakyThrows public Trace(Region region, List recordList) { this.uuid = UUID.randomUUID(); - recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records"); this.region = region; this.date = new Date(); records = new SoftReference<>(recordList); - metadataSaveFile = new File(TraceManager.tracesFolder, uuid + ".meta"); - - @Cleanup - ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(metadataSaveFile)); - outputStream.writeUTF(uuid.toString()); - outputStream.writeUTF(region.getName()); - outputStream.writeObject(date); + recordsSaveFile = new File(TraceRepository.tracesFolder, uuid + ".records"); + metadataSaveFile = new File(TraceRepository.tracesFolder, uuid + ".meta"); } /** - * Constructor for serialising a trace from the file system - * - * @param metadataSaveFile the file for this traces metadata + * Constructor for deserialising a trace from the file system */ @SneakyThrows - public Trace(File metadataSaveFile) { - String uuid = null; - Region region = null; - Date date = null; - - this.metadataSaveFile = metadataSaveFile; - - try { - @Cleanup - ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(metadataSaveFile)); - uuid = inputStream.readUTF(); - region = Region.getREGION_MAP().get(inputStream.readUTF()); - date = (Date) inputStream.readObject(); - inputStream.close(); - } finally { - this.uuid = UUID.fromString(uuid); - this.region = region; - this.date = date; - recordsSaveFile = new File(TraceManager.tracesFolder, uuid + ".records"); - this.records = new SoftReference<>(null); - } + protected Trace(UUID uuid, Region region, Date date, File metadataFile, File recordsFile, int recordsCount) { + this.metadataSaveFile = metadataFile; + recordsSaveFile = recordsFile; + this.uuid = uuid; + this.region = region; + this.date = date; + this.records = new SoftReference<>(null); + this.recordsCount = recordsCount; } /** @@ -173,7 +152,8 @@ public class Trace { entityServer = new REntityServer(); entityServer.addPlayer(player); entityServer.setCallback((p, rEntity, entityAction) -> { - if (entityAction != REntityServer.EntityAction.INTERACT) return; + if (entityAction != REntityServer.EntityAction.INTERACT) + return; if (rEntity instanceof TraceEntity) { ((TraceEntity) rEntity).printIntoChat(p); } @@ -195,7 +175,8 @@ public class Trace { REntityServer newEntityServer = new REntityServer(); newEntityServer.addPlayer(k); newEntityServer.setCallback((p, rEntity, entityAction) -> { - if (entityAction != REntityServer.EntityAction.INTERACT) return; + if (entityAction != REntityServer.EntityAction.INTERACT) + return; if (rEntity instanceof TraceEntity) { ((TraceEntity) rEntity).printIntoChat(p); } @@ -234,7 +215,8 @@ public class Trace { List entities = new LinkedList<>(); for (List bundle : bundles) { - entities.add(new TraceEntity(entityServer, bundle.get(0).getLocation(), bundle.get(0).isExplosion(), bundle, this)); + entities.add(new TraceEntity(entityServer, bundle.get(0).getLocation(), bundle.get(0).isExplosion(), bundle, + this)); } // Apply modifiers @@ -312,38 +294,7 @@ public class Trace { * Loads the records of this trace from storage to memory */ private void loadRecords() { - List records = new ArrayList<>(); - long readBytes = 0; - try { - FileInputStream fileInputStream = new FileInputStream(recordsSaveFile); - - @Cleanup - ObjectInputStream inputStream = new ObjectInputStream(new GZIPInputStream(fileInputStream)); - long fileLenght = recordsSaveFile.length(); - while (fileInputStream.getChannel().position() < fileLenght) { - records.add((TNTPoint) inputStream.readObject()); - readBytes = fileInputStream.getChannel().position(); - } - } catch (EOFException e) { - Logger logger = Bukkit.getLogger(); - logger.log(Level.WARNING, "EOF in trace read detected in " + uuid); - logger.log(Level.WARNING, "Read " + readBytes + "/" + recordsSaveFile.length() + " Bytes"); - logger.log(Level.WARNING, "Read so far: " + records); - - e.printStackTrace(); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - - Map> histories = new HashMap<>(); - for (TNTPoint record : records) { - int tntId = record.getTntId(); - List history = histories.computeIfAbsent(tntId, id -> new ArrayList<>()); - history.add(record); - record.setHistory(history); - } - - this.records = new SoftReference<>(records); + records = new SoftReference<>(TraceRepository.readTraceRecords(this)); } public synchronized List getRecords() { @@ -360,6 +311,7 @@ public class Trace { ", region=" + region + ", creationTime=" + date + ", recordsSaveFile=" + recordsSaveFile.getName() + + ", recordCount=" + recordsCount + ", records=" + getRecords() + '}'; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceManager.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceManager.java index 8ab7437c..1f0c5140 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceManager.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceManager.java @@ -30,11 +30,14 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.PluginEnableEvent; import java.io.File; import java.util.*; import java.util.stream.Collectors; +import static de.steamwar.bausystem.features.tracer.TraceRepository.tracesFolder; + @Linked public class TraceManager implements Listener { @@ -44,9 +47,9 @@ public class TraceManager implements Listener { instance = this; } - public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces"); - public TraceManager() { + + public void init() { if (!tracesFolder.exists()) tracesFolder.mkdir(); @@ -58,7 +61,15 @@ public class TraceManager implements Listener { if (traceFile.getName().contains(".records")) continue; - add(new Trace(traceFile)); + if (TraceRepository.getVersion(traceFile) == TraceRepository.SERIALISATION_VERSION) { + add(TraceRepository.readTrace(traceFile)); + } else { + String uuid = traceFile.getName().replace(".meta", ""); + + new File(tracesFolder, uuid + ".records").deleteOnExit(); + new File(tracesFolder, uuid + ".meta").deleteOnExit(); + } + } } @@ -160,8 +171,8 @@ public class TraceManager implements Listener { tracesByRegion.getOrDefault(region, new HashMap<>()) .forEach((i, trace) -> { if (trace.getRegion() != region) return; - trace.getMetadataSaveFile().delete(); - trace.getRecordsSaveFile().delete(); + trace.getMetadataSaveFile().deleteOnExit(); + trace.getRecordsSaveFile().deleteOnExit(); }); tracesByRegion.getOrDefault(region, new HashMap<>()).clear(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecorder.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecorder.java index fd0274fc..6a0a2314 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecorder.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecorder.java @@ -31,6 +31,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.server.PluginEnableEvent; import java.util.*; import java.util.logging.Level; @@ -70,7 +71,7 @@ public class TraceRecorder implements Listener { */ private final Set autoTraceRegions = new HashSet<>(); - public TraceRecorder() { + public void init() { BauSystem.runTaskTimer(BauSystem.getInstance(), () -> { record(); checkForAutoTraceFinish(); @@ -170,14 +171,13 @@ public class TraceRecorder implements Listener { if (history.size() == 0) { try { historyMap.put(tntPrimed, history); - } - catch (NullPointerException e) { + } catch (NullPointerException e) { Logger logger = Bukkit.getLogger(); //TODO remove when no longer neccecary logger.log(Level.WARNING, "Nullpointer thrown by historyMap"); logger.log(Level.WARNING, "TNT History: " + history); logger.log(Level.WARNING, "History Map: " + historyMap); - throw e; + throw e; } tntID = wrappedTrace.getNextOpenRecordIdAndIncrement(); } else { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecordingWrapper.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecordingWrapper.java index 7f23bb32..82eb3019 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecordingWrapper.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRecordingWrapper.java @@ -24,28 +24,22 @@ import de.steamwar.bausystem.region.Region; import lombok.Getter; import lombok.SneakyThrows; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; -import java.util.zip.GZIPOutputStream; public class TraceRecordingWrapper { + @Getter + private final Trace trace; + @Getter private final long startTick; private final List recordsToAdd; private final List recordList; - private final ObjectOutputStream recordsOutputStream; private int nextOpenRecordId = 0; @Getter private boolean explosionRecorded = false; - @Getter - private final Trace trace; - @SneakyThrows public TraceRecordingWrapper(Region region) { startTick = TPSUtils.currentRealTick.get(); @@ -53,8 +47,6 @@ public class TraceRecordingWrapper { recordList = new ArrayList<>(); trace = new Trace(region, recordList); - File recordsSaveFile = new File(TraceManager.tracesFolder, trace.getUuid() + ".records"); - recordsOutputStream = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(recordsSaveFile))); } public int getNextOpenRecordIdAndIncrement() { @@ -72,23 +64,14 @@ public class TraceRecordingWrapper { public void commitRecorded() { TraceManager.instance.showPartial(trace, recordsToAdd); - recordsToAdd.forEach(record -> { - try { - recordsOutputStream.writeObject(record); - recordsOutputStream.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - }); - recordList.addAll(recordsToAdd); + trace.setRecordsCount(recordList.size()); recordsToAdd.clear(); } @SneakyThrows protected void finalizeRecording() { - recordsOutputStream.flush(); - recordsOutputStream.close(); + TraceRepository.writeTrace(trace, recordList); TraceManager.instance.add(trace); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRepository.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRepository.java new file mode 100644 index 00000000..39f50fe1 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/TraceRepository.java @@ -0,0 +1,136 @@ +package de.steamwar.bausystem.features.tracer; + +import de.steamwar.bausystem.region.Region; +import lombok.Cleanup; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +import java.io.*; +import java.util.*; + +public class TraceRepository { + + /** + * Increment this when changing serialisation format + */ + public static final int SERIALISATION_VERSION = 1; + public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces"); + + @SneakyThrows + protected static int getVersion(File metadataFile) { + @Cleanup + ObjectInputStream reader = new ObjectInputStream(new FileInputStream(metadataFile)); + reader.readUTF(); + reader.readUTF(); + reader.readObject(); + try { + int version = reader.readInt(); + return version; + } catch (EOFException e) { + return 0; + } + } + + @SneakyThrows + public static Trace readTrace(File metadataFile) { + @Cleanup + ObjectInputStream reader = new ObjectInputStream(new FileInputStream(metadataFile)); + UUID uuid = UUID.fromString(reader.readUTF()); + Region region = Region.getREGION_MAP().get(reader.readUTF()); + Date date = (Date) reader.readObject(); + File recordsFile = new File(tracesFolder,uuid + ".records"); + int serialisationVersion = reader.readInt(); + int recordsCount = reader.readInt(); + + return new Trace(uuid, region, date, metadataFile, recordsFile, recordsCount); + } + + @SneakyThrows + protected static void writeTrace(Trace trace, List records) { + ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(trace.getMetadataSaveFile())); + outputStream.writeUTF(trace.getUuid().toString()); + outputStream.writeUTF(trace.getRegion().getName()); + outputStream.writeObject(trace.getDate()); + outputStream.writeInt(SERIALISATION_VERSION); + outputStream.writeInt(records.size()); + outputStream.flush(); + outputStream.close(); + + + writeTraceRecords(trace.getRecordsSaveFile(), records); + } + + @SneakyThrows + protected static void writeTraceRecords(File recordsFile, List records) { + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(recordsFile)); + for (TNTPoint record : records) { + outputStream.writeInt(record.getTntId()); + outputStream.writeBoolean(record.isExplosion()); + outputStream.writeBoolean(record.isInWater()); + outputStream.writeBoolean(record.isAfterFirstExplosion()); + outputStream.writeBoolean(record.isDestroyedBuildArea()); + outputStream.writeBoolean(record.isDestroyedTestBlock()); + outputStream.writeLong(record.getTicksSinceStart()); + outputStream.writeInt(record.getFuse()); + Location location = record.getLocation(); + outputStream.writeDouble(location.getX()); + outputStream.writeDouble(location.getY()); + outputStream.writeDouble(location.getZ()); + Vector velocity = record.getVelocity(); + outputStream.writeDouble(velocity.getX()); + outputStream.writeDouble(velocity.getY()); + outputStream.writeDouble(velocity.getZ()); + } + outputStream.flush(); + outputStream.close(); + } + + @SneakyThrows + protected static TNTPoint readTraceRecord(DataInputStream objectInput) { + + int tntId = objectInput.readInt(); + boolean explosion = objectInput.readBoolean(); + boolean inWater = objectInput.readBoolean(); + boolean afterFirstExplosion = objectInput.readBoolean(); + boolean destroyedBuildArea = objectInput.readBoolean(); + boolean destroyedTestBlock = objectInput.readBoolean(); + long ticksSinceStart = objectInput.readLong(); + int fuse = objectInput.readInt(); + + double locX = objectInput.readDouble(); + double locY = objectInput.readDouble(); + double locZ = objectInput.readDouble(); + Location location = new Location(Bukkit.getWorlds().get(0), locX, locY, locZ); + + double velX = objectInput.readDouble(); + double velY = objectInput.readDouble(); + double velZ = objectInput.readDouble(); + Vector velocity = new Vector(velX, velY, velZ); + + return new TNTPoint(tntId, explosion, inWater, afterFirstExplosion, destroyedBuildArea, destroyedTestBlock, ticksSinceStart, fuse, location, velocity, Collections.emptyList()); + } + + @SneakyThrows + protected static List readTraceRecords(Trace trace) { + File recordsFile = trace.getRecordsSaveFile(); + @Cleanup + DataInputStream inputStream = new DataInputStream(new FileInputStream(recordsFile)); + + List records = new ArrayList<>(); + for (int i = 0; i < trace.getRecordsCount(); i++) { + records.add(readTraceRecord(inputStream)); + } + + Map> histories = new HashMap<>(); + for (TNTPoint record : records) { + int tntId = record.getTntId(); + List history = histories.computeIfAbsent(tntId, id -> new ArrayList<>()); + history.add(record); + record.setHistory(history); + } + + return records; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java index 81e2f4dd..60a9651b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java @@ -20,6 +20,7 @@ package de.steamwar.bausystem.features.world; import de.steamwar.bausystem.BauSystem; +import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.core.CheckpointUtils; import de.steamwar.linkage.Linked; import org.bukkit.Bukkit; @@ -35,20 +36,16 @@ import org.bukkit.event.player.PlayerQuitEvent; @Linked public class AFKStopperListener implements Listener { - private int afkTicks = 0; + private long lastMovementTime = System.currentTimeMillis(); public AFKStopperListener() { Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - switch (afkTicks) { - case 15: - for (Player p : Bukkit.getOnlinePlayers()) { - p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); - } - case 14: - BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); - default: - afkTicks++; - } + long currentTime = System.currentTimeMillis(); + if(currentTime - lastMovementTime > 10*60000) { // 10 Minutes + for (Player p : Bukkit.getOnlinePlayers()) + p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); + } else if(currentTime - lastMovementTime > 9*60000) + BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); }, 1200, 1200); //every minute } @@ -60,7 +57,7 @@ public class AFKStopperListener implements Listener { Location from = event.getFrom(); if (from.getPitch() != to.getPitch() || from.getYaw() != to.getYaw()) - afkTicks = 0; + lastMovementTime = System.currentTimeMillis(); } @EventHandler(priority = EventPriority.LOWEST) //Potential fix for potential race condition with WE axe spontaneously not working @@ -73,4 +70,9 @@ public class AFKStopperListener implements Listener { if(Bukkit.getOnlinePlayers().isEmpty() || (Bukkit.getOnlinePlayers().size() == 1 && Bukkit.getOnlinePlayers().contains(event.getPlayer()))) CheckpointUtils.freeze(); } + + @EventHandler + public void onCRIUWakeup(CRIUWakeupEvent event) { + lastMovementTime = System.currentTimeMillis(); + } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java index 97b9455c..32888912 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.world; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.config.BauServer; +import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.linkage.Linked; import de.steamwar.sql.BauweltMember; import de.steamwar.sql.SteamwarUser; @@ -32,7 +33,6 @@ import org.bukkit.event.player.PlayerJoinEvent; @Linked public class AntiBauAddMemberFix implements Listener { - @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { if (BauSystem.DEV_SERVER) return; @@ -44,4 +44,9 @@ public class AntiBauAddMemberFix implements Listener { throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.get(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!"); } } + + @EventHandler + public void onCRIUWakeup(CRIUWakeupEvent event) { + BauweltMember.clear(); + } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditSelectionSaver.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditSelectionSaver.java new file mode 100644 index 00000000..3b80bf53 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditSelectionSaver.java @@ -0,0 +1,89 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.world; + +import com.sk89q.worldedit.regions.RegionSelector; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.shared.Pair; +import de.steamwar.bausystem.utils.WorldEditUtils; +import de.steamwar.linkage.Linked; +import de.steamwar.sql.SteamwarUser; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.io.*; +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Collectors; + +@Linked +public class WorldEditSelectionSaver implements Listener { + + private File WORLD_EDIT_SELECTIONS = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "world_edit_selections"); + + { + WORLD_EDIT_SELECTIONS.mkdir(); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + SteamwarUser steamwarUser = SteamwarUser.get(event.getPlayer().getUniqueId()); + File file = new File(WORLD_EDIT_SELECTIONS, Integer.toString(steamwarUser.getId())); + if (!file.exists()) return; + try { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + Class clazz = (Class) Class.forName(bufferedReader.readLine()); + List locations = bufferedReader.lines() + .map(s -> s.split(" ")) + .map(strings -> strings.length != 3 ? null : new Location(event.getPlayer().getWorld(), Integer.parseInt(strings[0]), Integer.parseInt(strings[1]), Integer.parseInt(strings[2]))) + .collect(Collectors.toList()); + WorldEditUtils.setVertices(event.getPlayer(), clazz, locations); + } catch (IOException | ClassNotFoundException e) { + BauSystem.getInstance().getLogger().log(Level.SEVERE, e.getMessage(), e); + file.delete(); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + SteamwarUser steamwarUser = SteamwarUser.get(event.getPlayer().getUniqueId()); + Pair, List> data = WorldEditUtils.getVertices(event.getPlayer()); + File file = new File(WORLD_EDIT_SELECTIONS, Integer.toString(steamwarUser.getId())); + try { + BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + bufferedWriter.write(data.getKey().getTypeName()); + for (Location location : data.getValue()) { + if (location == null) { + bufferedWriter.write("\n"); + } else { + bufferedWriter.write("\n" + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ()); + } + } + bufferedWriter.close(); + } catch (IOException e) { + BauSystem.getInstance().getLogger().log(Level.SEVERE, e.getMessage(), e); + file.delete(); + } + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java new file mode 100644 index 00000000..ee9e91be --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java @@ -0,0 +1,49 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.utils; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.ViaAPI; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.linkage.Linked; +import net.md_5.bungee.api.ChatMessageType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +@Linked +public class VersionAnnouncer implements Listener { + + private final String versionString = Bukkit.getBukkitVersion().split("-", 2)[0]; + + @SuppressWarnings("unchecked") + private final ViaAPI via = Via.getAPI(); + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if(via.getServerVersion().supportedVersions().contains(via.getPlayerVersion(player))) + return; + + BauSystem.MESSAGE.sendPrefixless("SERVER_VERSION", player, ChatMessageType.ACTION_BAR, versionString); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java index ec464ef4..4d20f346 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java @@ -19,17 +19,21 @@ package de.steamwar.bausystem.utils; +import com.comphenix.tinyprotocol.Reflection; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extension.factory.PatternFactory; +import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; import de.steamwar.bausystem.shared.Pair; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @@ -37,6 +41,9 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import java.util.List; +import java.util.stream.Collectors; + @UtilityClass public class WorldEditUtils { @@ -77,6 +84,40 @@ public class WorldEditUtils { return WorldEdit.getInstance().getPatternFactory(); } + public Pair, List> getVertices(Player player) { + RegionSelector regionSelector = WorldEdit.getInstance() + .getSessionManager() + .get(BukkitAdapter.adapt(player)) + .getRegionSelector(BukkitAdapter.adapt(player.getWorld())); + return new Pair<>(regionSelector.getClass(), regionSelector.getVertices() + .stream() + .map(blockVector3 -> blockVector3 == null ? null : adapt(player.getWorld(), blockVector3)) + .collect(Collectors.toList())); + } + + public void setVertices(Player player, Class clazz, List vertices) { + LocalSession localSession = WorldEdit.getInstance() + .getSessionManager() + .get(BukkitAdapter.adapt(player)); + + Reflection.ConstructorInvoker constructorInvoker = Reflection.getConstructor(clazz, com.sk89q.worldedit.world.World.class); + RegionSelector regionSelector = (RegionSelector) constructorInvoker.invoke(BukkitAdapter.adapt(player.getWorld())); + localSession.setRegionSelector(BukkitAdapter.adapt(player.getWorld()), regionSelector); + + if (vertices.isEmpty()) return; + + SelectorLimits selectorLimits = ActorSelectorLimits.forActor(BukkitAdapter.adapt(player)); + for (int i = 0; i < vertices.size(); i++) { + Location location = vertices.get(i); + if (location == null) continue; + if (i == 0) { + regionSelector.selectPrimary(BukkitAdapter.adapt(location).toBlockPoint(), selectorLimits); + } else { + regionSelector.selectSecondary(BukkitAdapter.adapt(location).toBlockPoint(), selectorLimits); + } + } + } + public Pair getSelection(Player player) { RegionSelector regionSelector = WorldEdit.getInstance() .getSessionManager() diff --git a/BauSystem/BauSystem_Main/src/plugin.yml b/BauSystem/BauSystem_Main/src/plugin.yml index 71b08d95..53585eaa 100644 --- a/BauSystem/BauSystem_Main/src/plugin.yml +++ b/BauSystem/BauSystem_Main/src/plugin.yml @@ -2,6 +2,8 @@ name: BauSystem authors: [ Lixfel, YoyoNow, Chaoscaot, Zeanon, D4rkr34lm ] version: "2.0" depend: [ WorldEdit, SpigotCore ] +softdepend: + - ViaVersion load: POSTWORLD main: de.steamwar.bausystem.BauSystem api-version: "1.13" diff --git a/KotlinCore/build.gradle.kts b/KotlinCore/build.gradle.kts index 6c16669c..5d00f0f6 100644 --- a/KotlinCore/build.gradle.kts +++ b/KotlinCore/build.gradle.kts @@ -18,17 +18,8 @@ */ plugins { - id("java") - kotlin("jvm") version "2.0.0" - - id("com.github.johnrengelman.shadow") -} - -group = "de.steamwar" -version = "" - -tasks.compileJava { - options.encoding = "UTF-8" + steamwar.kotlin + alias(libs.plugins.shadow) } tasks.build { @@ -39,29 +30,6 @@ tasks.shadowJar { exclude("org/**") } -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -sourceSets { - main { - java { - srcDirs("src/") - } - resources { - srcDirs("src/") - exclude("**/*.java", "**/*.kt") - } - } -} - dependencies { - implementation(kotlin("reflect")) - - compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") + compileOnly(libs.paperapi21) } - -kotlin { - jvmToolchain(21) -} \ No newline at end of file diff --git a/KotlinCore/src/de/steamwar/core/KotlinCore.kt b/KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt similarity index 93% rename from KotlinCore/src/de/steamwar/core/KotlinCore.kt rename to KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt index dd5a2946..e821ade4 100644 --- a/KotlinCore/src/de/steamwar/core/KotlinCore.kt +++ b/KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2024 SteamWar.de-Serverteam + * Copyright (C) 2024 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.core +package de.steamwar.kotlin import org.bukkit.plugin.java.JavaPlugin diff --git a/KotlinCore/src/plugin.yml b/KotlinCore/src/plugin.yml index 8b050f06..7117121e 100644 --- a/KotlinCore/src/plugin.yml +++ b/KotlinCore/src/plugin.yml @@ -1,5 +1,5 @@ name: KotlinCore version: '2.0.0' -main: de.steamwar.core.KotlinCore +main: de.steamwar.kotlin.KotlinCore load: POSTWORLD api-version: '1.21' diff --git a/MissileWars/src/de/steamwar/misslewars/items/SpecialItem.java b/MissileWars/src/de/steamwar/misslewars/items/SpecialItem.java index de8b6847..a287958b 100644 --- a/MissileWars/src/de/steamwar/misslewars/items/SpecialItem.java +++ b/MissileWars/src/de/steamwar/misslewars/items/SpecialItem.java @@ -111,11 +111,11 @@ public abstract class SpecialItem { } public static ItemStack getRandomItem() { - if (Config.MissileChance == 0 || consecutiveSupportItems > 1) { - consecutiveSupportItems = 0; + if (Config.MissileChance == 0) { return supportItems.get(random.nextInt(supportItems.size())).getItem(); } - if (Config.MissileChance == 1) { + if (Config.MissileChance == 1 || consecutiveSupportItems > 1) { + consecutiveSupportItems = 0; return missileItems.get(random.nextInt(missileItems.size())).getItem(); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUSleepEvent.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUSleepEvent.java new file mode 100644 index 00000000..eb8faaf0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUSleepEvent.java @@ -0,0 +1,36 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class CRIUSleepEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUWakeupEvent.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUWakeupEvent.java new file mode 100644 index 00000000..90a6ee32 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CRIUWakeupEvent.java @@ -0,0 +1,36 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class CRIUWakeupEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index 7b3b4b76..d65d7311 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -22,7 +22,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.Reflection; import com.comphenix.tinyprotocol.TinyProtocol; import com.viaversion.viaversion.api.Via; -import de.steamwar.sql.BauweltMember; import de.steamwar.sql.internal.Statement; import io.netty.channel.ChannelFuture; import org.bukkit.Bukkit; @@ -94,6 +93,7 @@ public class CheckpointUtils { private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); private static final Reflection.MethodInvoker bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class); private static void freezeInternal(Path path) throws Exception { + Bukkit.getPluginManager().callEvent(new CRIUSleepEvent()); Bukkit.getWorlds().forEach(FlatteningWrapper.impl::syncSave); Statement.closeAll(); @@ -141,7 +141,7 @@ public class CheckpointUtils { } Via.getManager().getInjector().inject(); - BauweltMember.clear(); + Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent()); Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); } } diff --git a/TNTLeague/build.gradle.kts b/TNTLeague/build.gradle.kts new file mode 100644 index 00000000..b9689d85 --- /dev/null +++ b/TNTLeague/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + steamwar.kotlin +} + +dependencies { + compileOnly(libs.paperapi21) + compileOnly(project(":SpigotCore")) + compileOnly(project(":KotlinCore")) +} \ No newline at end of file diff --git a/TNTLeague/src/config.yml b/TNTLeague/src/config.yml new file mode 100644 index 00000000..d2e0a1e6 --- /dev/null +++ b/TNTLeague/src/config.yml @@ -0,0 +1,100 @@ +startDelay: 10 +gameTime: 1200 + +prices: + TNT: + price: 4 + amount: 4 + REDSTONE: + price: 4 + amount: 4 + REPEATER: + price: 4 + amount: 2 + COMPARATOR: + price: 4 + amount: 1 + REDSTONE_BLOCK: + price: 4 + amount: 1 + REDSTONE_TORCH: + price: 4 + amount: 2 + END_STONE: + price: 4 + amount: 8 + ICE: + price: 8 + amount: 1 + LEVER: + price: 4 + amount: 1 + OAK_BUTTON: + price: 4 + amount: 1 + STONE_BUTTON: + price: 4 + amount: 1 + OAK_TRAPDOOR: + price: 4 + amount: 2 + IRON_TRAPDOOR: + price: 4 + amount: 1 + PISTON: + price: 4 + amount: 2 + STICKY_PISTON: + price: 4 + amount: 1 + GLASS: + price: 4 + amount: 2 + OAK_FENCE: + price: 4 + amount: 4 + LADDER: + price: 6 + amount: 2 + WHITE_GLAZED_TERRACOTTA: + price: 4 + amount: 3 + JUKEBOX: + price: 4 + amount: 2 + OBSERVER: + price: 10 + amount: 4 + BREWING_STAND: + price: 4 + amount: 1 + STRING: + price: 4 + amount: 2 + END_STONE_BRICK_SLAB: + price: 4 + amount: 2 + TARGET: + price: 4 + amount: 1 + COPPER_BULB: + price: 4 + amount: 1 + SLIME_BLOCK: + price: 6 + amount: 2 + HONEY_BLOCK: + price: 6 + amount: 2 + STONE_PRESSURE_PLATE: + price: 4 + amount: 1 + NOTE_BLOCK: + price: 3 + amount: 1 + HOPPER: + price: 3 + amount: 1 + GRAVEL: + price: 4 + amount: 3 diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt new file mode 100644 index 00000000..5479e4d7 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -0,0 +1,48 @@ +package de.steamwar.tntleague + +import de.steamwar.tntleague.command.AcceptCommand +import de.steamwar.tntleague.command.InviteCommand +import de.steamwar.tntleague.command.LeaveCommand +import de.steamwar.tntleague.command.RemoveCommand +import de.steamwar.tntleague.events.GlobalListener +import de.steamwar.tntleague.events.LobbyListener +import net.kyori.adventure.key.Key +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.TranslationRegistry +import net.kyori.adventure.util.UTF8ResourceBundleControl +import org.bukkit.plugin.java.JavaPlugin +import java.util.* + +lateinit var plugin: TNTLeague + +class TNTLeague : JavaPlugin() { + init { + plugin = this + } + + override fun onEnable() { + saveResource("config.yml", false) + saveDefaultConfig() + + val registry = TranslationRegistry.create(Key.key("steamwar:tntleague")) + + val bundleDe = ResourceBundle.getBundle("de.steamwar.tntleague.TNTLeague", Locale.GERMAN, UTF8ResourceBundleControl()) + val bundleEn = ResourceBundle.getBundle("de.steamwar.tntleague.TNTLeague", Locale.US, UTF8ResourceBundleControl()) + registry.defaultLocale(Locale.US) + + registry.registerAll(Locale.GERMAN, bundleDe, true) + registry.registerAll(Locale.US, bundleEn, true) + + GlobalTranslator.translator().addSource(registry) + + server.pluginManager.registerEvents(LobbyListener, this) + server.pluginManager.registerEvents(GlobalListener, this) + + logger.info("TNTLeague enabled") + + InviteCommand.register() + AcceptCommand.register() + RemoveCommand.register() + LeaveCommand.register() + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties new file mode 100644 index 00000000..7200a9e2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties @@ -0,0 +1,18 @@ +join={0} ist dem Spiel beigetreten! +joinTeam={0} ist dem {1} team begetreten! +quit={0} hat das Spiel verlassen! +blue=Blau +red=Rot +shutdown=Der Server fährt in {0} sekunden herunter! +teamWin=Team {0} gewinnt! +notEnoughCoins=Du hast nicht genug Coins um dir das zu kaufen! +gameStarting=Das Spiel beginnt in {0} Sekunden! +gameStart=Start in {0} +gameStarted=Das Spiel beginnt! +gameEnded=Das Spiel ist aus! +dealer=Händler +dealerItem= +dealerPrice=Kosten: {0} Coins +scoreboardTarget=Ziel: {0} +scoreboardTime=Zeit: {0}:{1} +scoreboardTeam= \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties new file mode 100644 index 00000000..2f7c11e2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties @@ -0,0 +1,37 @@ +join={0} joined the game! +joinTeam={0} joined the {1} team! +quit={0} left the game! +quitTeam={0} left the {1} team! +blue=Blue +red=Red +shutdown=The server stops in {0} seconds! +teamWin=Team {0} wins! + +notEnoughCoins=You don't have enough coins to buy this item! + +gameStarting=The game starts in {0} seconds! +gameStart=Starting in {0} +gameStarted=The game has started! + +timeRemaining={0} minutes remaining! + +gameEnded=The game has ended! +draw=The game ended in a draw! +chat={0}» {1} + +dealer=Shopkeeper +dealerItem={0} {1} +dealerPrice=Price: {0} Coins + +scoreboardTarget=Target: {0} +scoreboardTime=Time: {0}:{1} +scoreboardTeam=Team {0}: {1} + +ready=Ready +notReady=Not ready +isReady=Team {0} is ready! +isNotReady=Team {0} is not ready! + +invited={0} invited you to join the {1} team! *Click* +invitedHover=Click to join the {0} team! +invitedPlayer=Invited {0} to join your team! \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt new file mode 100644 index 00000000..3606bdbd --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt @@ -0,0 +1,40 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object AcceptCommand: SWCommand("accept") { + + @Register + fun acceptInvite(sender: Player, @Validator("isLeader") target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + + val team = TNTLeagueGame.getTeam(target) ?: return + if (team.leader != target) return + if (sender !in team.invites) return + + team.invites.remove(target) + team.opposite.invites.remove(target) + team.join(sender) + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt new file mode 100644 index 00000000..fdfaf359 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.command.TypeValidator +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.util.* +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent +import org.bukkit.entity.Player + +object InviteCommand: SWCommand("invite") { + + @Register + fun invitePlayer(@Validator("isLeader") sender: Player, target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + if (TNTLeagueGame.getTeam(target) != null) return + + val team = TNTLeagueGame.getTeam(sender)!! + team.invites.add(target) + + target.sendMessage(translate("invited", sender.name.yellow(), translate(team.name).colorByTeam(team)).basic().clickEvent( + ClickEvent.callback { + if (target !in team.invites) return@callback + + team.invites.remove(target) + team.opposite.invites.remove(target) + team.join(target) + }) + .hoverEvent(HoverEvent.showText(translate("invitedHover", translate(team.name).colorByTeam(team)).green()))) + sender.sendMessage(translate("invitedPlayer", target.name.yellow()).basic()) + } + + @Validator("isLeader", local = false) + fun isLeader(): TypeValidator { + return TypeValidator { _, player, _ -> TNTLeagueGame.getTeam(player)?.leader == player} + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt new file mode 100644 index 00000000..aeb96492 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt @@ -0,0 +1,30 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object LeaveCommand: SWCommand("leave", "l") { + + @Register + fun leave(player: Player) = TNTLeagueGame.getTeam(player)?.remove(player) +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt new file mode 100644 index 00000000..d889f31a --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt @@ -0,0 +1,39 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object RemoveCommand: SWCommand("remove") { + + @Register + fun removePlayer(@Validator("isLeader") sender: Player, target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + + if (sender == target) return + val team = TNTLeagueGame.getTeam(sender) ?: return + if (team.leader != sender) return + if (target !in team.members) return + + team.remove(target) + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt new file mode 100644 index 00000000..30fb8353 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt @@ -0,0 +1,35 @@ +package de.steamwar.tntleague.config + +import de.steamwar.tntleague.plugin +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.file.FileConfiguration + +data class TNTLeagueConfig( + val startDelay: Int = 10, + val gameTime: Int = 60 * 20, + + val prices: Map +) { + companion object { + val config: TNTLeagueConfig by lazy { loadConfig(plugin.config) } + + private fun loadConfig(config: FileConfiguration): TNTLeagueConfig { + return TNTLeagueConfig(config.getInt("startDelay"), config.getInt("gameTime"), loadPrices(config.getConfigurationSection("prices")!!)) + } + + private fun loadPrices(config: ConfigurationSection): Map { + return config.getKeys(false).associateWith { + Price( + config.getInt("$it.amount"), + config.getInt("$it.price") + ) + }.mapKeys { Material.getMaterial(it.key)!! } + } + } + + data class Price( + val amount: Int, + val price: Int, + ) +} diff --git a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt new file mode 100644 index 00000000..faaf73aa --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt @@ -0,0 +1,75 @@ +package de.steamwar.tntleague.config + +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.Area +import de.steamwar.tntleague.util.translate +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.entity.Villager +import org.bukkit.entity.WanderingTrader +import java.io.File + +val world by lazy { plugin.server.worlds.first()!! } + +private val targetedBlocksRed by lazy { TNTLeagueWorldConfig.redTeam.target.blocks.count { block -> block.type == TNTLeagueWorldConfig.targetMaterial } } + +private val targetedBlocksBlue by lazy { TNTLeagueWorldConfig.blueTeam.target.blocks.count { block -> block.type == TNTLeagueWorldConfig.targetMaterial } } + +private val targetedBlocksAll: Int + get() = if (targetedBlocksBlue == targetedBlocksRed) targetedBlocksBlue else error("Targeted blocks are not equal") + +val targetedBlocks: Int + get() = if (TNTLeagueWorldConfig.target != -1) TNTLeagueWorldConfig.target else targetedBlocksAll + +object TNTLeagueWorldConfig { + private val config: YamlConfiguration by lazy { + YamlConfiguration.loadConfiguration( + File( + plugin.server.worlds.first().worldFolder, + "tntleague.yml" + ) + ) + } + + val blueTeam: TeamConfig = TeamConfig.fromConfig(config.getConfigurationSection("blueTeam")!!) + val redTeam: TeamConfig = TeamConfig.fromConfig(config.getConfigurationSection("redTeam")!!) + val lobby: Location = config.getLocation("lobby", blueTeam.spawnLocation.clone().add(redTeam.spawnLocation).multiply(0.5))!! + val targetMaterial: Material = Material.matchMaterial(config.getString("targetMaterial", "IRON_BLOCK")!!)!! + val minHeight: Int = config.getInt("minHeight", 0) + val target: Int = config.getInt("target", -1) + + @JvmRecord + data class TeamConfig( + val spawnLocation: Location, + val dealerSpawn: Location, + val itemSpawn: Location, + val target: Area + ) { + companion object { + fun fromConfig(config: ConfigurationSection): TeamConfig { + val spawnLocation = config.getLocation("spawn")!! + val dealerSpawn = config.getLocation("dealerSpawn")!! + val itemSpawn = config.getLocation("itemSpawn")!! + val targetPos1 = config.getLocation("targetMin")!! + val targetPos2 = config.getLocation("targetMax")!! + + spawnDealer(dealerSpawn) + + return TeamConfig(spawnLocation, dealerSpawn, itemSpawn, Area(targetPos1, targetPos2)) + } + + private fun spawnDealer(loc: Location) = world.spawn(loc, WanderingTrader::class.java) + .apply { + customName(translate("dealer")) + isCustomNameVisible = false + isInvulnerable = true + isSilent = true + isCollidable = false + isAware = false + setAI(false) + } + } + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt new file mode 100644 index 00000000..f9f616de --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt @@ -0,0 +1,25 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.events + +import org.bukkit.event.Listener + +object DummyListener: Listener { +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt new file mode 100644 index 00000000..f2930adb --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt @@ -0,0 +1,88 @@ +package de.steamwar.tntleague.events + +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.game.TNTLeagueTeam +import de.steamwar.tntleague.inventory.SWInventoryHolder +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import io.papermc.paper.event.player.AsyncChatEvent +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.event.player.PlayerRespawnEvent + +object GlobalListener: Listener { + + @EventHandler(priority = EventPriority.LOW) + fun onPlayerJoin(e: PlayerJoinEvent) { + e.joinMessage(null) + with(e.player) { + teleport(TNTLeagueWorldConfig.lobby) + inventory.clear() + plugin.server.broadcast(translate("join", name.bold()).basic()) + isOp = false + gameMode = GameMode.SPECTATOR + respawnLocation = TNTLeagueWorldConfig.lobby + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerQuit(e: PlayerQuitEvent) { + e.quitMessage(null) + plugin.server.broadcast(translate("quit", e.player.name.bold().colorByTeam(TNTLeagueGame.getTeam(e.player))).basic()) + TNTLeagueGame.playerLeave(e.player) + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerClick(e: InventoryClickEvent) { + val holder = e.inventory.getHolder(false) + if (holder is SWInventoryHolder && e.clickedInventory == holder._inventory) { + e.isCancelled = true + holder.handleInventoryClick(e) + } + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerMove(e: PlayerMoveEvent) { + if (e.to.blockY < TNTLeagueWorldConfig.minHeight) { + when (val team = TNTLeagueGame.getTeam(e.player)) { + is TNTLeagueTeam -> e.player.teleport(team.config.spawnLocation) + null -> e.player.teleport(TNTLeagueWorldConfig.blueTeam.spawnLocation) + } + } + + e.player.foodLevel = 20 + e.player.saturation = 20f + } + + @EventHandler + fun onPlayerDeath(e: PlayerDeathEvent) { + e.deathMessage(null) + e.drops.clear() + + e.itemsToKeep.removeIf { it.type != Material.DIAMOND_PICKAXE } + } + + @EventHandler + fun onPlayerRespawn(e: PlayerRespawnEvent) { + when (val team = TNTLeagueGame.getTeam(e.player)) { + is TNTLeagueTeam -> e.respawnLocation = team.config.spawnLocation + null -> e.respawnLocation = TNTLeagueWorldConfig.lobby + } + } + + @EventHandler + fun onChat(e: AsyncChatEvent) { + e.renderer { source, sourceDisplayName, message, _ -> + translate("chat", sourceDisplayName.colorByTeam(TNTLeagueGame.getTeam(source)), message).basic() + } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt new file mode 100644 index 00000000..bb9b0415 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt @@ -0,0 +1,77 @@ +package de.steamwar.tntleague.events + +import de.steamwar.scoreboard.SWScoreboard +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.game.TNTLeagueTeam +import de.steamwar.tntleague.inventory.DealerInventory +import de.steamwar.tntleague.util.TNTLeagueScoreboard +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.EntityType +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEntityEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent + +object IngameListener: Listener { + + @EventHandler + fun onEntityInteract(e: PlayerInteractEntityEvent) { + if (e.player.gameMode == GameMode.SPECTATOR) return + + if(e.rightClicked.type == EntityType.VILLAGER) { + e.isCancelled = true + e.player.openInventory(DealerInventory(e.player).getInventory()) + } + } + + @EventHandler + fun onExplode(e: EntityExplodeEvent) { + e.blockList().filter { it.type == TNTLeagueWorldConfig.targetMaterial } + .groupBy { getTeamByTargetLocation(it.location) } + .filterKeysNotNull() + .mapValues { it.value.size } + .forEach { it.key.damagedBlocks += it.value } + } + + @EventHandler + fun onJoin(e: PlayerJoinEvent) { + SWScoreboard.createScoreboard(e.player, TNTLeagueScoreboard(e.player)) + } + + @EventHandler + fun onMove(e: PlayerMoveEvent) { + if (TNTLeagueGame.getTeam(e.player) != null) { + if (e.to.blockX >= TNTLeagueWorldConfig.lobby.blockX && e.to.blockX <= TNTLeagueWorldConfig.lobby.blockX + 1 || + e.to.blockZ >= TNTLeagueWorldConfig.lobby.blockZ && e.to.blockZ <= TNTLeagueWorldConfig.lobby.blockZ + 1) { + e.isCancelled = true + } + } + } + + @EventHandler + fun onDropPickaxe(e: PlayerDropItemEvent) { + if (e.itemDrop.itemStack.type == Material.DIAMOND_PICKAXE) { + e.isCancelled = true + } + } + + private fun getTeamByTargetLocation(location: Location): TNTLeagueTeam? = + when (location) { + in TNTLeagueWorldConfig.redTeam.target -> TNTLeagueGame.redTeam + in TNTLeagueWorldConfig.blueTeam.target -> TNTLeagueGame.blueTeam + else -> null + } + + private fun Map.filterKeysNotNull(destination: MutableMap = mutableMapOf()): Map { + this.forEach { (t, u) -> if(t != null) destination[t] = u } + return destination + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt new file mode 100644 index 00000000..e88cfbaa --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt @@ -0,0 +1,61 @@ +package de.steamwar.tntleague.events + +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.basic +import de.steamwar.tntleague.util.colorByTeam +import de.steamwar.tntleague.util.translate +import de.steamwar.tntleague.util.yellow +import io.papermc.paper.util.Tick +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent + +object LobbyListener: Listener { + + @EventHandler + fun onPlayerJoin(e: PlayerJoinEvent) { + TNTLeagueGame.getFreeTeam()?.run { + join(e.player) + TNTLeagueGame.checkStart() + } + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerQuit(e: PlayerQuitEvent) { + val team = TNTLeagueGame.getTeam(e.player) ?: return + team.leave(e.player) + } + + @EventHandler + fun onPlayerDamage(e: EntityDamageEvent) { + e.isCancelled = true + } + + @EventHandler + fun onDropEvent(e: PlayerDropItemEvent) { + e.isCancelled = true + } + + @EventHandler + fun interactEvent(e: PlayerInteractEvent) { + val team = TNTLeagueGame.getTeam(e.player) + if (e.action.isRightClick && team != null && e.item?.isSimilar(team.readyItem()) == true && team.opposite.leader != null) { + team.isReady = !team.isReady + } + } + + @EventHandler + fun inventoryClick(e: InventoryClickEvent) { + if (e.clickedInventory == e.whoClicked.inventory) { + e.isCancelled = true + } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt new file mode 100644 index 00000000..59d4e65c --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt @@ -0,0 +1,259 @@ +package de.steamwar.tntleague.game + +import de.steamwar.scoreboard.SWScoreboard +import de.steamwar.sql.Fight +import de.steamwar.sql.FightPlayer +import de.steamwar.sql.SteamwarUser +import de.steamwar.tntleague.config.TNTLeagueConfig +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.config.world +import de.steamwar.tntleague.events.DummyListener +import de.steamwar.tntleague.events.IngameListener +import de.steamwar.tntleague.events.LobbyListener +import de.steamwar.tntleague.inventory.DealerInventory +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.bossbar.BossBar +import net.kyori.adventure.sound.Sound +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.block.data.type.TNT +import org.bukkit.entity.Entity +import org.bukkit.entity.Item +import org.bukkit.entity.Player +import org.bukkit.entity.TNTPrimed +import org.bukkit.entity.Villager +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.inventory.ItemStack +import org.bukkit.scheduler.BukkitTask +import java.sql.Timestamp +import java.time.Instant + +object TNTLeagueGame { + var state: GameState = GameState.LOBBY + set(value) { + if (field.listener != value.listener) { + HandlerList.unregisterAll(field.listener) + plugin.server.pluginManager.registerEvents(value.listener, plugin) + } + field = value + } + + var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime + + val blueTeam = TNTLeagueTeam(TNTLeagueWorldConfig.blueTeam, TNTLeagueTeam.Team.BLUE) + val redTeam = TNTLeagueTeam(TNTLeagueWorldConfig.redTeam, TNTLeagueTeam.Team.RED) + + private lateinit var start: Timestamp + + private var task: Int? = null + private lateinit var spawnerTask: BukkitTask + private lateinit var timerTask: BukkitTask + + private fun setup() { + assert(state == GameState.STARTING) { "Game is already running" } + + state = GameState.RUNNING + + plugin.server.onlinePlayers.forEach { SWScoreboard.createScoreboard(it, TNTLeagueScoreboard(it)) } + + blueTeam.start() + redTeam.start() + + plugin.server.broadcast(translate("gameStarted").success()) + + val tnt = ItemStack(Material.TNT) + + start = Timestamp.from(Instant.now()) + + spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit { + if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) { + spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, tnt) + spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, DealerInventory.coins) + } + if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) { + spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, tnt) + spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, DealerInventory.coins) + } + }, 5, 10) + + timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit { + gameTimeRemaining-- + if (gameTimeRemaining == 0) { + draw(WinReason.TIMEOUT) + return@bukkit + } + + if (gameTimeRemaining % 300 == 0) { + plugin.server.broadcast(translate("timeRemaining", (gameTimeRemaining / 60).toString().yellow()).basic()) + plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 1f, 1f)) } + } + }, 20, 20) + } + + private fun bukkit(f: () -> Unit): () -> Unit = f + + private fun end() { + if(state != GameState.RUNNING) return + state = GameState.END + + plugin.server.onlinePlayers.forEach { + it.gameMode = GameMode.SPECTATOR + SWScoreboard.removeScoreboard(it) + it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_ENDER_DRAGON_DEATH.key, Sound.Source.MASTER, 1f, 1f)) + } + + plugin.server.broadcast(translate("gameEnded").success()) + + spawnerTask.cancel() + + var shutdown = 10 + + plugin.server.scheduler.runTaskTimer(plugin, bukkit { + if (shutdown == 0) { + plugin.server.shutdown() + } + + plugin.server.broadcast(translate("shutdown", shutdown.toString().yellow()).basic()) + + shutdown-- + }, 20, 20) + } + + private fun spawnItems(loc: Location, item: ItemStack) = plugin.server.worlds.first().dropItem(loc, item) + + fun getTeam(player: Player) = if (player in blueTeam.members) blueTeam else if (player in redTeam.members) redTeam else null + + fun getFreeTeam() = if (blueTeam.leader == null) blueTeam else if (redTeam.leader == null) redTeam else null + + fun checkStart() { + if (blueTeam.isReady && redTeam.isReady) { + blueTeam.leader?.inventory?.clear() + redTeam.leader?.inventory?.clear() + state = GameState.STARTING + + var countdown = TNTLeagueConfig.config.startDelay + plugin.server.broadcast(translate("gameStarting", countdown.toString().yellow()).basic()) + val bar = BossBar.bossBar(translate("gameStart", countdown.toString().yellow()).gray(), (TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat(), BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_10) + plugin.server.onlinePlayers.forEach { bar.addViewer(it) } + task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, { + plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP.key, Sound.Source.MASTER, 1f, 1f)) } + if (countdown-- == 0) { + plugin.server.onlinePlayers.forEach { it.hideBossBar(bar) } + task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } + setup() + } else { + bar.name(translate("gameStart", countdown.toString().yellow()).gray()) + bar.progress((TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat()) + plugin.server.onlinePlayers.filter { !it.activeBossBars().contains(bar) }.forEach { bar.addViewer(it) } + } + }, 20, 20) + + if (task == -1) { + error("Failed to start countdown task") + } + } + } + + fun playerLeave(player: Player) { + blueTeam.invites.remove(player) + redTeam.invites.remove(player) + getTeam(player)?.apply { + members.remove(player) + if (leader == player) { + win(this.opposite, WinReason.LEAVE) + } + } + } + + fun reset() { + assert(state == GameState.LOBBY || state == GameState.STARTING) { "Game is not in lobby or starting state" } + + if (state == GameState.STARTING) { + task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } + plugin.server.onlinePlayers.forEach { p -> p.activeBossBars().forEach { it.removeViewer(p) } } + state = GameState.LOBBY + } + } + + fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) { + if (state != GameState.RUNNING) return + end() + plugin.server.broadcast(translate("teamWin", translate(tntLeagueTeam.name).color(tntLeagueTeam.color)).success()) + statistic(tntLeagueTeam, reason) + explode(tntLeagueTeam.opposite) + } + + fun draw(reason: WinReason) { + if (state != GameState.RUNNING) return + end() + plugin.server.broadcast(translate("draw").success()) + statistic(null, reason) + } + + fun explode(team: TNTLeagueTeam) { + Area(team.config.spawnLocation.clone().add(20.0, 30.0, 20.0), team.config.spawnLocation.clone().subtract(20.0, 0.0, 20.0).add(0.0, 30.0, 0.0)) + .locations + .filterIndexed { index, _ -> index % 7 == 0 } + .forEachIndexed { index, location -> + world.spawn(location, TNTPrimed::class.java).apply { + fuseTicks = index + 40 + } + } + } + + private fun statistic(winTeam: TNTLeagueTeam?, reason: WinReason) { + val fightId = Fight.create( + "TNTLeague", + world.name, + start, + TNTLeagueConfig.config.gameTime - gameTimeRemaining, + SteamwarUser.get(blueTeam.leader!!.uniqueId).id, + SteamwarUser.get(redTeam.leader!!.uniqueId).id, + null, + null, + when (winTeam) { + blueTeam -> 1 + redTeam -> 2 + else -> 0 + }, + when (reason) { + WinReason.TIMEOUT -> "TIMEOUT" + WinReason.DESTROYED -> "DESTROYED" + WinReason.LEAVE -> "LEAVE" + } + ) + + addTeamMember(blueTeam, fightId) + addTeamMember(redTeam, fightId) + } + + private fun addTeamMember(team: TNTLeagueTeam, fightId: Int) { + team.members.filter { team.leader != it } + .forEach { + FightPlayer.create( + fightId, + SteamwarUser.get(it.uniqueId).id, + team == blueTeam, + "TNTLeague", + 0, + false + ) + } + } + + enum class GameState(val listener: Listener) { + LOBBY(LobbyListener), + STARTING(LobbyListener), + RUNNING(IngameListener), + END(DummyListener); + } + + enum class WinReason { + TIMEOUT, + DESTROYED, + LEAVE + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt new file mode 100644 index 00000000..cafa70c2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt @@ -0,0 +1,146 @@ +package de.steamwar.tntleague.game + +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.config.targetedBlocks +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.enchantments.Enchantment +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import java.awt.Color.green + +data class TNTLeagueTeam(val config: TNTLeagueWorldConfig.TeamConfig, private val team: Team) { + + var leader: Player? = null + set(player) { + field = player + if (player != null) { + with(player.inventory) { + clear() + setItem(4, readyItem()) + } + } + } + + val members = mutableListOf() + val invites = mutableListOf() + + val name: String + get() = team.name.lowercase() + + val color: TextColor + get() = team.color + + var isReady: Boolean = false + set(value) { + field = value + leader?.inventory?.setItem(4, readyItem()) + leader?.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 1f, 1f)) + + plugin.server.onlinePlayers.forEach { it.sendActionBar(translate(if (value) "isReady" else "isNotReady", translate(this.name).colorByTeam(this)).let { cmp -> + if (value) { + cmp.green() + } else { + cmp.red() + } + }) } + + if (value && opposite.isReady) { + TNTLeagueGame.checkStart() + } + } + + var damagedBlocks: Int = 0 + set(value) { + field = value + if (value >= targetedBlocks) { + TNTLeagueGame.win(this, TNTLeagueGame.WinReason.DESTROYED) + } + } + + val opposite: TNTLeagueTeam + get() = when (team) { + Team.BLUE -> TNTLeagueGame.redTeam + Team.RED -> TNTLeagueGame.blueTeam + } + + fun join(player: Player): Boolean { + members.add(player) + + with(player) { + teleport(config.spawnLocation) + gameMode = GameMode.ADVENTURE + inventory.clear() + plugin.server.broadcast(translate("joinTeam", name().colorByTeam(this@TNTLeagueTeam), translate(this@TNTLeagueTeam.name).colorByTeam(this@TNTLeagueTeam)).basic()) + } + + if (leader == null) { + leader = player + } + + return true + } + + fun readyItem() = if (isReady) { + ItemStack.of(Material.LIME_DYE).apply { + itemMeta = itemMeta.apply { + displayName(translate("ready").green().translate(leader!!)) + } + } + } else { + ItemStack.of(Material.RED_DYE).apply { + itemMeta = itemMeta.apply { + displayName(translate("notReady").red().translate(leader!!)) + } + } + } + + fun start() = members.forEach { + with(it) { + gameMode = GameMode.SURVIVAL + inventory.addItem(ItemStack.of(Material.DIAMOND_PICKAXE).apply { + itemMeta = itemMeta.apply { + isUnbreakable = true + addEnchant(Enchantment.EFFICIENCY, 1, false) + } + }) + } + } + + fun leave(player: Player) { + if (TNTLeagueGame.state == TNTLeagueGame.GameState.RUNNING) { + TNTLeagueGame.playerLeave(player) + } else { + members.remove(player) + + if (members.isEmpty()) { + plugin.server.onlinePlayers.firstOrNull { it != player && TNTLeagueGame.getTeam(it) == null }?.run { + members.add(this) + } + } + if (leader == player) { + leader = members.firstOrNull() + } + } + } + + fun remove(player: Player) { + leave(player) + with(player) { + teleport(TNTLeagueWorldConfig.lobby) + gameMode = GameMode.SPECTATOR + inventory.clear() + plugin.server.broadcast(translate("quitTeam", name().colorByTeam(this@TNTLeagueTeam), translate(this@TNTLeagueTeam.name).colorByTeam(this@TNTLeagueTeam)).basic()) + } + } + + enum class Team(val color: TextColor) { + BLUE(NamedTextColor.BLUE), + RED(NamedTextColor.RED); + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt b/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt new file mode 100644 index 00000000..04c1c288 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt @@ -0,0 +1,75 @@ +package de.steamwar.tntleague.inventory + +import de.steamwar.tntleague.config.TNTLeagueConfig +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.Style +import net.kyori.adventure.text.format.TextDecoration +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import java.util.* +import kotlin.math.ceil + +class DealerInventory(player: Player): SWInventoryHolder() { + + init { + items.forEachIndexed { index, item -> + this[index] = item.first to { + val price = item.second.price * if (it.isShiftClick) 5 else 1 + val amount = item.second.amount * if (it.isShiftClick) 5 else 1 + + if (!player.inventory.containsAtLeast(coins, price)) { + player.sendMessage(translate("notEnoughCoins").error()) + player.playSound(Sound.sound(org.bukkit.Sound.ENTITY_VILLAGER_HURT.key, net.kyori.adventure.sound.Sound.Source.MASTER, 1f, 1f)) + return@to + } + + player.inventory.removeItem(coins.asQuantity(price)) + player.inventory.addItem(ItemStack.of(item.first.type, amount)) + } + } + } + + override fun createInventory(): Inventory = plugin.server.createInventory(this, ceil(TNTLeagueConfig.config.prices.size / 9f).toInt() * 9, translate("dealer").reset()) + + companion object { + private val priceKey = NamespacedKey(plugin, "price") + private val amountKey = NamespacedKey(plugin, "amount") + private val coinKey = NamespacedKey(plugin, "coin") + + val coins = ItemStack(Material.RAW_GOLD).apply { + itemMeta = itemMeta.apply { + displayName(Component.text("Coins").bold().gold()) + persistentDataContainer.apply { + set(coinKey, PersistentDataType.BOOLEAN, true) + } + } + } + + val items by lazy { + val prices = TNTLeagueConfig.config.prices + + prices.map { (material, price) -> + ItemStack(material).apply { + itemMeta = itemMeta.apply { + displayName(material.name.lowercase().replace("_", " ") + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + .component().gray().appendSpace().append(price.amount.toString().component().yellow())) + amount = price.amount + lore(listOf(price.price.toString().component().yellow().bold().appendSpace().append(Component.text("Coins").yellow()))) + persistentDataContainer.apply { + set(priceKey, PersistentDataType.INTEGER, price.price) + set(amountKey, PersistentDataType.INTEGER, price.amount) + } + } + } to price + } + } + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt b/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt new file mode 100644 index 00000000..5d89e541 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt @@ -0,0 +1,41 @@ +package de.steamwar.tntleague.inventory + +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryHolder +import org.bukkit.inventory.ItemStack + +abstract class SWInventoryHolder: InventoryHolder { + + val _inventory: Inventory by lazy { createInventory() } + + private val callbacks = mutableMapOf Unit>() + + override fun getInventory(): Inventory = _inventory + + abstract fun createInventory(): Inventory + + open fun handleInventoryClick(event: InventoryClickEvent) { + callbacks[event.slot]?.invoke(event) + } + + fun addItem(item: ItemStack, slot: Int, callback: (event: InventoryClickEvent) -> Unit) { + _inventory.setItem(slot, item) + addCallback(slot, callback) + } + + fun addCallback(slot: Int, callback: (event: InventoryClickEvent) -> Unit) { + callbacks[slot] = callback + } + + open fun handleClose(event: InventoryCloseEvent) { } + + operator fun set(slot: Int, item: Pair Unit>) { + addItem(item.first, slot, item.second) + } + + operator fun set(slot: Int, item: ItemStack) { + addItem(item, slot) { } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/util/Area.kt b/TNTLeague/src/de/steamwar/tntleague/util/Area.kt new file mode 100644 index 00000000..0b27d227 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/Area.kt @@ -0,0 +1,46 @@ +package de.steamwar.tntleague.util + +import org.bukkit.Location +import org.bukkit.block.Block + +class Area(loc1: Location, loc2: Location) { + + val min: Location + val max: Location + + init { + require(loc1.world == loc2.world) { "Locations must be in the same world" } + this.min = loc1 min loc2 + this.max = loc1 max loc2 + } + + operator fun contains(loc: Location): Boolean { + return loc.world == min.world && loc.x >= min.x && loc.x <= max.x && loc.y >= min.y && loc.y <= max.y && loc.z >= min.z && loc.z <= max.z + } + + val blocks: Sequence + inline get() = sequence { + for (x in locations) { + yield(x.block) + } + } + + val locations: Sequence + inline get() = sequence { + for (x in min.blockX..max.blockX) { + for (y in min.blockY..max.blockY) { + for (z in min.blockZ..max.blockZ) { + yield(Location(min.world, x.toDouble(), y.toDouble(), z.toDouble())) + } + } + } + } +} + +infix fun Location.max(other: Location): Location { + return Location(world, x.coerceAtLeast(other.x), y.coerceAtLeast(other.y), z.coerceAtLeast(other.z)) +} + +infix fun Location.min(other: Location): Location { + return Location(world, x.coerceAtMost(other.x), y.coerceAtMost(other.y), z.coerceAtMost(other.z)) +} diff --git a/TNTLeague/src/de/steamwar/tntleague/util/Style.kt b/TNTLeague/src/de/steamwar/tntleague/util/Style.kt new file mode 100644 index 00000000..3ef28b9a --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/Style.kt @@ -0,0 +1,73 @@ +package de.steamwar.tntleague.util + +import de.steamwar.tntleague.game.TNTLeagueTeam +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentLike +import net.kyori.adventure.text.TranslatableComponent +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.Style +import net.kyori.adventure.text.format.TextDecoration +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.translation.GlobalTranslator +import org.bukkit.entity.Player +import java.util.Locale + +val prefix = Component.text("Steam").yellow() + .append(Component.text("War").darkGray()) + .appendSpace() + +val tntLeaguePrefix = Component.text("TNT").color(NamedTextColor.DARK_RED) + .append(Component.text("League").color(NamedTextColor.GOLD)) + +val tntLeagueChatPrefix: Component = tntLeaguePrefix + .append(Component.text("»").darkGray()) + .appendSpace() + +fun TranslatableComponent.basic(): Component = tntLeagueChatPrefix.append(this.gray()) + +fun TranslatableComponent.error(): Component = tntLeagueChatPrefix.append(this.red()) + +fun TranslatableComponent.success(): Component = tntLeagueChatPrefix.append(this.green()) + +fun String.component(): Component = Component.text(this) + +fun Component.bold(): Component = this.decorate(TextDecoration.BOLD) + +fun String.bold(): Component = this.component().bold() + +fun Component.yellow(): Component = this.color(NamedTextColor.YELLOW) + +fun String.yellow(): Component = this.component().yellow() + +fun Component.red(): Component = this.color(NamedTextColor.RED) + +fun String.red(): Component = this.component().red() + +fun Component.green(): Component = this.color(NamedTextColor.GREEN) + +fun String.green(): Component = this.component().green() + +fun Component.gray(): Component = this.color(NamedTextColor.GRAY) + +fun String.gray(): Component = this.component().gray() + +fun Component.darkGray(): Component = this.color(NamedTextColor.DARK_GRAY) + +fun String.darkGray(): Component = this.component().darkGray() + +fun Component.gold(): Component = this.color(NamedTextColor.GOLD) + +fun translate(key: String, vararg args: ComponentLike): TranslatableComponent = Component.translatable(key, *args).decoration(TextDecoration.ITALIC, false) + +fun Component.reset(): Component = this.style(Style.empty()) + +fun Component.colorByTeam(team: TNTLeagueTeam?) = when (team) { + null -> this.gray() + else -> this.color(team.color) +} + +fun Component.translate(locale: Locale): Component = GlobalTranslator.render(this, locale) + +fun Component.translate(p: Player): Component = this.translate(p.locale()) + +fun Component.toLegacy(): String = LegacyComponentSerializer.legacySection().serialize(this) diff --git a/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt b/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt new file mode 100644 index 00000000..7b8da592 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt @@ -0,0 +1,57 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.tntleague.util + +import de.steamwar.scoreboard.ScoreboardCallback +import de.steamwar.tntleague.config.targetedBlocks +import de.steamwar.tntleague.game.TNTLeagueGame +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player +import kotlin.collections.HashMap + +private val scoreboardTitle by lazy { tntLeaguePrefix.toLegacy() } + +data class TNTLeagueScoreboard(val p: Player): ScoreboardCallback { + override fun getData(): HashMap { + val lines = mutableListOf() + + lines.add(Component.space().green()) + + val minutes = TNTLeagueGame.gameTimeRemaining.floorDiv(60) + val seconds = TNTLeagueGame.gameTimeRemaining.rem(60).toString().padStart(2, '0') + lines.add(translate("scoreboardTime", minutes.toString().yellow(), seconds.yellow()).gray()) + + lines.add(Component.space().yellow()) + + with(TNTLeagueGame.blueTeam) { + lines.add(translate("scoreboardTeam", translate(name).colorByTeam(this), (targetedBlocks - damagedBlocks).toString().yellow()).gray()) + } + with(TNTLeagueGame.redTeam) { + lines.add(translate("scoreboardTeam", translate(name).colorByTeam(this), (targetedBlocks - damagedBlocks).toString().yellow()).gray()) + } + + lines.add(Component.space().gray()) + + return lines + .foldIndexed(HashMap()) { index, acc, component -> acc.also { it[component.translate(p).toLegacy()] = index } } + } + + override fun getTitle(): String = scoreboardTitle +} diff --git a/TNTLeague/src/paper-plugin.yml b/TNTLeague/src/paper-plugin.yml new file mode 100644 index 00000000..8687424a --- /dev/null +++ b/TNTLeague/src/paper-plugin.yml @@ -0,0 +1,10 @@ +name: TNTLeague +version: '1.0.0' +main: de.steamwar.tntleague.TNTLeague +load: POSTWORLD +api-version: '1.21' +dependencies: + - name: SpigotCore + required: true + - name: KotlinCore + required: true \ No newline at end of file diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index fe118680..5bb5238b 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -292,7 +292,7 @@ FIGHT_UNKNOWN_GAMEMODE=§cUnknown gamemode: {0} FIGHT_UNKNOWN_ARENA=§cThe desired arena does not exist. FIGHT_IN_ARENA=§cYou are already in an arena. FIGHT_BROADCAST=§7Click §ehere§7 to fight §e{0} §7against §e{1}! -FIGHT_BROADCAST_HOVER=§aFight §eagainst §7{1} +FIGHT_BROADCAST_HOVER=§aFight §eagainst §7{0} #CheckCommand CHECK_REMINDER=§7There are §e{0} §7schematics left for review§8! diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index 2dea174b..edc46c77 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -275,7 +275,7 @@ FIGHT_UNKNOWN_GAMEMODE=§cUnbekannter Spielmodus: {0} FIGHT_UNKNOWN_ARENA=§cDie gewünschte Arena gibt es nicht. FIGHT_IN_ARENA=§cDu befindest dich bereits in einer Arena. FIGHT_BROADCAST=§7Klicke §ehier§7, um §e{0} §7gegen §e{1} §7zu §7kämpfen! -FIGHT_BROADCAST_HOVER=§aGegen §7{1} §ekämpfen +FIGHT_BROADCAST_HOVER=§aGegen §7{0} §ekämpfen #CheckCommand CHECK_REMINDER=§7Es sind §e{0} §7Schematics zu prüfen§8! @@ -642,7 +642,6 @@ RANK_PLAYER_FOUND=§eRang §7von §e{0} RANK_HEADER={0} §e{1} {2} RANK_UNPLACED=§7unplatziert RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8. -RANK_EMBLEM=§7Emblem§8: {0} #Fabric Mod Sender MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren! diff --git a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java index 1be170f3..17d4f537 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java @@ -20,6 +20,7 @@ package de.steamwar.velocitycore; import de.steamwar.messages.Chatter; +import de.steamwar.messages.Message; import de.steamwar.persistent.Subserver; import de.steamwar.sql.EventFight; import de.steamwar.sql.Team; @@ -43,7 +44,7 @@ public class EventStarter { public EventStarter() { EventFight.loadAllComingFights(); - VelocityCore.schedule(this::run).delay(10, TimeUnit.SECONDS).schedule(); + VelocityCore.schedule(this::run).repeat(10, TimeUnit.SECONDS).schedule(); } public static Map getEventServer() { @@ -73,7 +74,7 @@ public class EventStarter { } else { command = "/" + spectatePorts.get(next.getSpectatePort()); } - Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER", ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName()); + Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", new Message("EVENT_FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName()); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java index 1d5283ea..633405e8 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java @@ -130,7 +130,7 @@ public class FightCommand extends SWCommand { public void fight(@Validator("arenaPlayer") PlayerChatter sender, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { createArena(sender, "/fight ", true, arenaMode, map, false, (p, mode, m) -> new ServerStarter().arena(mode, m).blueLeader(p.getPlayer()).callback( - arena -> Chatter.broadcast().system("FIGHT_BROADCAST", new Message("FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer().getUsername()) + arena -> Chatter.broadcast().system("FIGHT_BROADCAST", new Message("FIGHT_BROADCAST_HOVER", p.getPlayer().getUsername()), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer().getUsername()) ).start() ); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java index fe8795e6..bebf194c 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java @@ -85,7 +85,7 @@ public class ChannelListener extends ListenerAdapter { if(permission != null && !sender.user().perms().contains(permission)) return; - command.execute(sender, args.split(" ")); + command.execute(sender, args.isEmpty() ? new String[0] : args.split(" ")); }); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/listeners/PluginMessage.java b/VelocityCore/src/de/steamwar/velocitycore/listeners/PluginMessage.java index cdb36137..bd67e805 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/listeners/PluginMessage.java +++ b/VelocityCore/src/de/steamwar/velocitycore/listeners/PluginMessage.java @@ -22,9 +22,12 @@ package de.steamwar.velocitycore.listeners; import com.lunarclient.apollo.ApolloManager; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; +import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; @@ -373,11 +376,11 @@ public class PluginMessage extends BasicListener { )) register(channel, false, directional(UNKNOWN, DROP)); - register("REGISTER", false, directional(this::serverRegistersChannel, this::clientRegistersChannel)); - register("minecraft:register", false, directional(this::serverRegistersChannel, this::clientRegistersChannel)); + register("REGISTER", false, directional(this::serverRegistersChannel, UNKNOWN)); + register("minecraft:register", false, directional(this::serverRegistersChannel, UNKNOWN)); - register("MC|Brand", false, directional(this::steamWarBrand, this::userBrand)); - register("minecraft:brand", false, directional(this::steamWarBrand, this::userBrand)); + register("MC|Brand", false, directional(this::steamWarBrand, UNKNOWN)); + register("minecraft:brand", false, directional(this::steamWarBrand, UNKNOWN)); //Needs to be registered cause paper refuses to send PluginMessages on unregistered channels... register("sw:bridge", true, directional(onlySWSource(async(event -> NetworkPacket.handle(new ServerMetaInfo((ServerConnection) event.getSource()), event.getData()))), UNKNOWN)); @@ -401,6 +404,15 @@ public class PluginMessage extends BasicListener { // Hackclientlike modsuppressor for labymod: https://github.com/Neocraftr/LabyMod-NeoEssentials (Potentially recognizable from NO Addons/NO Mods?) https://github.com/Neocraftr/LabyMod-NeoEssentials/blob/master/src/main/java/de/neocraftr/neoessentials/utils/BytecodeMethods.java } + @Subscribe + public void onClientChannelRegister(PlayerChannelRegisterEvent event) { + Player player = event.getPlayer(); + + for(ChannelIdentifier channel : event.getChannels()) { + channelRegisterHandlers.getOrDefault(channel.getId(), p -> VelocityCore.getLogger().log(Level.WARNING, () -> p.getUsername() + " registered unknown channel " + channel)).accept(player); + } + } + @Subscribe public void onPluginMessage(PluginMessageEvent event) { event.setResult(PluginMessageEvent.ForwardResult.handled()); @@ -412,6 +424,20 @@ public class PluginMessage extends BasicListener { } } + @Subscribe + public void onClientBrand(PlayerClientBrandEvent event) { + Player player = event.getPlayer(); + + String brand = event.getBrand(); + boolean lunarclient = brand.startsWith("lunarclient:"); + + VelocityCore.getLogger().log(knownBrands.contains(brand) || lunarclient ? Level.INFO : Level.WARNING, () -> player.getUsername() + " joins with brand: " + brand); + if(lunarclient) + lunar.sendRestrictions(player); + if(brand.equals("badlion")) + badlion.sendRestrictions(player); + } + private void registerPassthroughToClient(String... channels) { for(String channel : channels) { channelRegisterHandlers.put(channel, player -> {}); @@ -437,16 +463,6 @@ public class PluginMessage extends BasicListener { VelocityCore.getProxy().getChannelRegistrar().register(channel.indexOf(':') != -1 ? MinecraftChannelIdentifier.from(channel) : new LegacyChannelIdentifier(channel)); } - private void clientRegistersChannel(PluginMessageEvent event) { - Player player = (Player) event.getSource(); - - for(String channel : new String(event.getData()).split("\0")) { - channelRegisterHandlers.getOrDefault(channel, p -> VelocityCore.getLogger().log(Level.WARNING, () -> p.getUsername() + " registered unknown channel " + channel)).accept(player); - } - - PASS_THROUGH.handle(event); - } - private void serverRegistersChannel(PluginMessageEvent event) { Player player = (Player) event.getTarget(); @@ -455,22 +471,6 @@ public class PluginMessage extends BasicListener { send(player, "REGISTER", "minecraft:register", String.join("\0", channels).getBytes()); } - private void userBrand(PluginMessageEvent event) { - Player player = (Player) event.getSource(); - - ByteBuf buf = Unpooled.wrappedBuffer(event.getData()); - String brand = ProtocolUtils.readString(buf); - boolean lunarclient = brand.startsWith("lunarclient:"); - - VelocityCore.getLogger().log(knownBrands.contains(brand) || lunarclient ? Level.INFO : Level.WARNING, () -> player.getUsername() + " joins with brand: " + brand); - if(lunarclient) - lunar.sendRestrictions(player); - if(brand.equals("badlion")) - badlion.sendRestrictions(player); - - PASS_THROUGH.handle(event); - } - private void steamWarBrand(PluginMessageEvent event) { Player player = (Player) event.getTarget(); String brand = Chatter.of(player).parseToLegacy("STEAMWAR_BRAND", "Velocity", player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(""), new String(event.getData(), 1, event.getData().length - 1)); diff --git a/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java b/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java index 74c18eb0..57cc3986 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java +++ b/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java @@ -28,25 +28,25 @@ public class Badlion { private final byte[] packet; - public Badlion() { //TODO check if working or (json) modsDisallowed wrapper necessary + public Badlion() { JsonObject disabled = new JsonObject(); disabled.addProperty("disabled", true); - JsonObject json = new JsonObject(); - json.add("Clear Glass", disabled); - json.add("ClearWater", disabled); - json.add("FOV Changer", disabled); - json.add("Hitboxes", disabled); - json.add("LevelHead", disabled); - json.add("MiniMap", disabled); - json.add("MLG Cobweb", disabled); - json.add("Replay", disabled); //TODO check if ReplayMod restrictions work - json.add("Schematica", disabled); - json.add("ToggleSneak", disabled); - json.add("ToggleSprint", disabled); - json.add("TNT Time", disabled); + JsonObject modsDisallowed = new JsonObject(); + modsDisallowed.add("Clear Glass", disabled); + modsDisallowed.add("ClearWater", disabled); + modsDisallowed.add("FOV Changer", disabled); + modsDisallowed.add("Hitboxes", disabled); + modsDisallowed.add("LevelHead", disabled); + modsDisallowed.add("MiniMap", disabled); + modsDisallowed.add("MLG Cobweb", disabled); + modsDisallowed.add("Replay", disabled); + modsDisallowed.add("Schematica", disabled); + modsDisallowed.add("ToggleSneak", disabled); + modsDisallowed.add("ToggleSprint", disabled); + modsDisallowed.add("TNT Time", disabled); - packet = json.toString().getBytes(); + packet = modsDisallowed.toString().getBytes(); } public void sendRestrictions(Player player) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java index 327adbac..417a0e5e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java @@ -20,13 +20,13 @@ package de.steamwar.velocitycore.network.handlers; import com.velocitypowered.api.proxy.Player; -import de.steamwar.velocitycore.ArenaMode; -import de.steamwar.velocitycore.VelocityCore; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.sql.SchematicType; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.UserElo; +import de.steamwar.velocitycore.ArenaMode; +import de.steamwar.velocitycore.VelocityCore; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -52,8 +52,15 @@ public class EloPlayerHandler extends PacketHandler { */ @Handler public void handle(FightEndsPacket fightEndsPacket) { - if (!ArenaMode.getBySchemType(SchematicType.fromDB(fightEndsPacket.getGameMode())).isRanked()) - return; + SchematicType schematicType = SchematicType.fromDB(fightEndsPacket.getGameMode()); + ArenaMode arenaMode; + if (schematicType == null) { + arenaMode = ArenaMode.getByInternal(fightEndsPacket.getGameMode()); + } else { + arenaMode = ArenaMode.getBySchemType(schematicType); + } + if (arenaMode == null) return; + if (!arenaMode.isRanked()) return; if (EloSchemHandler.publicVsPrivate(fightEndsPacket)) return; diff --git a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java index 68144738..49da0116 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java @@ -19,18 +19,21 @@ package de.steamwar.velocitycore.network.handlers; -import de.steamwar.velocitycore.ArenaMode; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.sql.SchemElo; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicType; +import de.steamwar.velocitycore.ArenaMode; public class EloSchemHandler extends PacketHandler { private static final int K = 20; public static boolean publicVsPrivate(FightEndsPacket packet) { + if (packet.getRedSchem() == -1 && packet.getBlueSchem() == -1) { + return false; + } SchematicNode blueSchem = SchematicNode.getSchematicNode(packet.getBlueSchem()); SchematicNode redSchem = SchematicNode.getSchematicNode(packet.getRedSchem()); return (blueSchem.getOwner() == 0) != (redSchem.getOwner() == 0); @@ -38,9 +41,10 @@ public class EloSchemHandler extends PacketHandler { @Handler public void handle(FightEndsPacket fightEndsPacket) { - if (!ArenaMode.getBySchemType(SchematicType.fromDB(fightEndsPacket.getGameMode())).isRanked()) { - return; - } + SchematicType type = SchematicType.fromDB(fightEndsPacket.getGameMode()); + if (type == null) return; + ArenaMode arenaMode = ArenaMode.getBySchemType(type); + if (!arenaMode.isRanked()) return; if (publicVsPrivate(fightEndsPacket)) return; diff --git a/VelocityCore/src/de/steamwar/velocitycore/tablist/Tablist.java b/VelocityCore/src/de/steamwar/velocitycore/tablist/Tablist.java index f8dc1783..315717a6 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/tablist/Tablist.java +++ b/VelocityCore/src/de/steamwar/velocitycore/tablist/Tablist.java @@ -43,6 +43,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import java.util.*; +import java.util.logging.Level; import java.util.stream.IntStream; @ChannelHandler.Sharable @@ -63,7 +64,6 @@ public class Tablist extends ChannelInboundHandlerAdapter { this.player = player; this.viewer = Chatter.of(player); this.directTabItems = Storage.directTabItems.computeIfAbsent(player, p -> new HashMap<>()); - injection(); } public void update(TablistPart global, int seconds) { @@ -139,7 +139,9 @@ public class Tablist extends ChannelInboundHandlerAdapter { synchronized (directTabItems) { directTabItems.clear(); } + } + public void onServerPostSwitch() { if(player.getProtocolVersion().greaterThan(ProtocolVersion.MINECRAFT_1_20)) { current.clear(); sendPacket(player, createTeamPacket); @@ -148,8 +150,14 @@ public class Tablist extends ChannelInboundHandlerAdapter { private void injection() { connection = (VelocityServerConnection) player.getCurrentServer().orElse(null); - if(connection == null) - return; + if(connection == null) { + connection = ((ConnectedPlayer) player).getConnectionInFlight(); + + if(connection == null) { + VelocityCore.getLogger().log(Level.WARNING, "Could not inject Tablist: %s".formatted(player)); + return; + } + } ChannelPipeline pipeline = connection.getConnection().getChannel().pipeline(); if(pipeline.get("steamwar-tablist") != null) //Catch unclean exit diff --git a/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java b/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java index 2b8b47bd..29cebb38 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java +++ b/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java @@ -21,8 +21,9 @@ package de.steamwar.velocitycore.tablist; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import de.steamwar.network.packets.common.FightInfoPacket; @@ -59,17 +60,19 @@ public class TablistManager extends BasicListener { } @Subscribe - public void onJoin(PostLoginEvent event) { + public void onServerConnection(ServerConnectedEvent event) { synchronized (tablists) { - tablists.put(event.getPlayer(), new Tablist(event.getPlayer())); + tablists.computeIfAbsent(event.getPlayer(), Tablist::new).onServerSwitch(); } - Tablist.sendPacket(event.getPlayer(), Tablist.createTeamPacket); + + if(event.getPlayer().getProtocolVersion().noGreaterThan(ProtocolVersion.MINECRAFT_1_20)) + Tablist.sendPacket(event.getPlayer(), Tablist.createTeamPacket); } @Subscribe - public void onServerConnection(ServerPostConnectEvent event) { + public void onServerPostConnection(ServerPostConnectEvent event) { synchronized (tablists) { - tablists.get(event.getPlayer()).onServerSwitch(); + tablists.get(event.getPlayer()).onServerPostSwitch(); } } @@ -90,14 +93,14 @@ public class TablistManager extends BasicListener { private void updateTablist() { List subservers = new ArrayList<>(); - for (RegisteredServer server : new ArrayList<>(VelocityCore.getProxy().getAllServers())){ - if(server.getPlayersConnected().isEmpty()) + for (RegisteredServer server : new ArrayList<>(VelocityCore.getProxy().getAllServers())) { + if (server.getPlayersConnected().isEmpty()) continue; Subserver subserver = Subserver.getSubserver(server.getServerInfo()); - if(fightInfos.containsKey(server)) + if (fightInfos.containsKey(server)) subservers.add(new TablistServer(server, fightInfos.get(server))); - else if(subserver == null || subserver.getType() != Servertype.BAUSERVER) + else if (subserver == null || subserver.getType() != Servertype.BAUSERVER) subservers.add(new TablistServer(server)); } subservers.add(new TablistBuild()); diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index b457102f..ff9b2587 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -27,4 +27,12 @@ sourceSets { srcDirs("src/") } } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.10") } \ No newline at end of file diff --git a/buildSrc/src/steamwar.kotlin.gradle b/buildSrc/src/steamwar.kotlin.gradle new file mode 100644 index 00000000..03b0a99d --- /dev/null +++ b/buildSrc/src/steamwar.kotlin.gradle @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +plugins { + id "org.jetbrains.kotlin.jvm" +} + +kotlin { + jvmToolchain(21) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.compileJava { + options.encoding "UTF-8" +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } + test { + java { + srcDirs("testsrc/") + } + resources { + srcDirs("testsrc/") + exclude("**/*.java", "**/*.kt") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 072ee29a..edd33ceb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -89,6 +89,7 @@ dependencyResolutionManagement { includeGroup("org.spigotmc") includeGroup("io.papermc.paper") includeGroup("com.velocitypowered") + includeGroup("net.md-5") } } mavenCentral() @@ -113,6 +114,7 @@ dependencyResolutionManagement { library("spigotapi", "org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT") library("spigotannotations", "org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT") library("paperapi", "io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT") + library("paperapi21", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") library("authlib", "com.mojang:authlib:1.5.25") library("datafixer", "com.mojang:datafixerupper:4.0.26") library("brigadier", "com.mojang:brigadier:1.0.18") @@ -174,7 +176,7 @@ include( "FightSystem:FightSystem_Standalone" ) -// include("KotlinCore") +include("KotlinCore") include("LobbySystem") @@ -214,3 +216,5 @@ include( "VelocityCore", "VelocityCore:Persistent" ) + +include("TNTLeague")