diff --git a/pom.xml b/pom.xml index 6304993..fd2d135 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.betamc tsunami - 1.0.7 + 1.0.8 jar @@ -13,13 +13,6 @@ UTF-8 - - - Alpine Cloud - https://lib.alpn.cloud/alpine-public/ - - - net.sf.jopt-simple @@ -66,9 +59,9 @@ 1.7 - dev.tomwmth.configlib - configlib-yaml - 4.6.0 + org.spongepowered + configurate-yaml + 4.2.0 com.google.guava @@ -108,12 +101,12 @@ - clean install + clean package org.apache.maven.plugins maven-source-plugin - 3.3.1 + 3.4.0 attach-sources @@ -126,7 +119,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.5.0 @@ -164,7 +157,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.1 package @@ -182,6 +175,7 @@ *:* META-INF/*.RSA + META-INF/*.DSA META-INF/*.SF @@ -193,7 +187,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.15.0 1.8 1.8 diff --git a/src/main/java/com/legacyminecraft/poseidon/PoseidonStatisticsAgent.java b/src/main/java/com/legacyminecraft/poseidon/PoseidonStatisticsAgent.java deleted file mode 100644 index 275048b..0000000 --- a/src/main/java/com/legacyminecraft/poseidon/PoseidonStatisticsAgent.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.legacyminecraft.poseidon; - -import net.minecraft.server.MinecraftServer; -import org.bukkit.craftbukkit.CraftServer; -import org.json.simple.JSONObject; - -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Random; - -public class PoseidonStatisticsAgent { - //Default details - private int protocolID = 1; - public final String postTo = "https://poseidon.johnymuffin.com/statistics.php"; - //Unique Details - private String uniqueID; - private final String sessionID; - private final String version; - private final String branch; - private final Long startTime; - private Object syncLock = new Object(); - - public PoseidonStatisticsAgent(MinecraftServer server, CraftServer craftServer) { - //This really shouldn't be needed, but it runs once, whats the harm? - synchronized (syncLock) { - this.startTime = (System.currentTimeMillis() / 1000L); - this.uniqueID = PoseidonConfig.getInstance().getString("settings.statistics.key"); - //Create temp value - Random rnd = new Random(); - this.sessionID = String.valueOf(100000 + rnd.nextInt(900000)); - this.version = craftServer.getPoseidonVersion(); - this.branch = craftServer.getPoseidonReleaseType(); - } - - PoseidonStatisticsSender poseidonStatisticsSender = new PoseidonStatisticsSender(); - poseidonStatisticsSender.start(); - - } - - public JSONObject getPing() { - synchronized (syncLock) { - JSONObject ping = new JSONObject(); - ping.put("protocol", protocolID); - ping.put("uniqueID", uniqueID); - ping.put("sessionID", sessionID); - ping.put("version", version); - ping.put("branch", branch); - int uptime = (int) ((System.currentTimeMillis() / 1000L) - startTime); - ping.put("uptime", uptime); - return ping; - } - } - - public class PoseidonStatisticsSender extends Thread { - public volatile boolean errored = false; - - public void run() { - while (true && !this.isInterrupted()) { - HttpURLConnection connection = null; - try { - System.out.println("Submitting Project Poseidon Statistics."); - final JSONObject ping = getPing(); - URL url = new URL(postTo); - //Create Connection - connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setUseCaches(false); - connection.setDoInput(true); - connection.setDoOutput(true); - //Write Body - OutputStream stream = connection.getOutputStream(); - stream.write(ping.toJSONString().getBytes()); - stream.flush(); - stream.close(); - //Get Response - String response = String.valueOf(new InputStreamReader(connection.getInputStream())); - connection.disconnect(); - errored = false; - } catch (Exception exception) { - if (!errored) { - System.out.println("Failed to submit statistics for Project Poseidon. " + exception + " : " + exception.getMessage() + "."); - } - errored = true; - } finally { - if (connection != null) { - connection.disconnect(); - connection = null; - } - try { - Thread.sleep(300000L); - } catch (InterruptedException exception) { - System.out.println("Project Poseidon statistics thread has been closed."); - break; - } - } - } - } - - } - -} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index dcc30f5..6201664 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -1,6 +1,7 @@ package net.minecraft.server; import com.legacyminecraft.poseidon.PoseidonConfig; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import java.util.*; @@ -25,6 +26,7 @@ public class Chunk { public boolean q; public long r; private final int[] chunkSections; // Tsunami + public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami public Chunk(World world, int i, int j) { this.tileEntities = new HashMap(); diff --git a/src/main/java/net/minecraft/server/ChunkLoader.java b/src/main/java/net/minecraft/server/ChunkLoader.java index acb3518..38107c7 100644 --- a/src/main/java/net/minecraft/server/ChunkLoader.java +++ b/src/main/java/net/minecraft/server/ChunkLoader.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; + import java.io.*; import java.util.Iterator; @@ -155,6 +157,8 @@ public static void a(Chunk chunk, World world, NBTTagCompound nbttagcompound) { } nbttagcompound.a("TileEntities", (NBTBase) nbttaglist1); + + nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, chunk.container.asCompound()); // Tsunami - PersistentDataContainer API } public static Chunk a(World world, NBTTagCompound nbttagcompound) { @@ -210,6 +214,11 @@ public static Chunk a(World world, NBTTagCompound nbttagcompound) { } } + // Tsunami start - PersistentDataContainer API + CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY)); + container.copyTo(chunk.container, true); + // Tsunami end + return chunk; } diff --git a/src/main/java/net/minecraft/server/ConsoleLogFormatter.java b/src/main/java/net/minecraft/server/ConsoleLogFormatter.java index 72d4612..ee793d7 100644 --- a/src/main/java/net/minecraft/server/ConsoleLogFormatter.java +++ b/src/main/java/net/minecraft/server/ConsoleLogFormatter.java @@ -42,7 +42,13 @@ public String format(LogRecord logrecord) { String message = ANSI_PATTERN.matcher(formattedMessage).replaceAll(""); stringbuilder.append(message); // Tsunami end - stringbuilder.append('\n'); + + // Tsunami start - fix double newlines on newer Java versions + if (stringbuilder.charAt(stringbuilder.length() - 1) != '\n') { + stringbuilder.append('\n'); + } + // Tsunami end + Throwable throwable = logrecord.getThrown(); if (throwable != null) { diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index cab02a9..1d6e1e7 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -4,6 +4,7 @@ import org.bukkit.block.BlockFace; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.metadata.NBTMetadataConvert; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Vehicle; import org.bukkit.event.entity.EntityCombustEvent; @@ -101,6 +102,7 @@ public synchronized void setSeed(long seed) { public boolean bK; public boolean airBorne; public UUID uniqueId = UUID.randomUUID(); // CraftBukkit + public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami public final Map metadataStore = new HashMap<>(); // Tsunami public Entity(World world) { @@ -928,6 +930,7 @@ public void d(NBTTagCompound nbttagcompound) { nbttagcompound.setLong("UUIDMost", this.uniqueId.getMostSignificantBits()); // CraftBukkit end this.b(nbttagcompound); + nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore)); // Tsunami } @@ -1015,6 +1018,11 @@ public void e(NBTTagCompound nbttagcompound) { } // CraftBukkit end + // Tsunami - PersistentDataContainer API + CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY)); + container.copyTo(this.container, true); + // Tsunami end + // Tsunami start NBTTagCompound metadata = nbttagcompound.k("CustomMetadata"); this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata)); diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java index 9c6a90f..eeb9f1c 100644 --- a/src/main/java/net/minecraft/server/EntityArrow.java +++ b/src/main/java/net/minecraft/server/EntityArrow.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.Projectile; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -161,48 +163,57 @@ public void m_() { float f2; if (movingobjectposition != null) { - // CraftBukkit start - ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity()); - this.world.getServer().getPluginManager().callEvent(phe); - // CraftBukkit end if (movingobjectposition.entity != null) { - // CraftBukkit start - boolean stick; - if (entity instanceof EntityLiving) { - org.bukkit.Server server = this.world.getServer(); - - // TODO decide if we should create DamageCause.ARROW, DamageCause.PROJECTILE - // or leave as DamageCause.ENTITY_ATTACK - org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); - Projectile projectile = (Projectile) this.getBukkitEntity(); - // TODO deal with arrows being fired from a non-entity - - EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 4); - server.getPluginManager().callEvent(event); - this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle(); - - if (event.isCancelled()) { - stick = !projectile.doesBounce(); + // Tsunami start - improve ProjectileHitEvent + ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity()); + this.world.getServer().getPluginManager().callEvent(phe); + if (!phe.isCancelled()) { + // Tsunami end + + // CraftBukkit start + boolean stick; + if (entity instanceof EntityLiving) { + org.bukkit.Server server = this.world.getServer(); + + // TODO decide if we should create DamageCause.ARROW, DamageCause.PROJECTILE + // or leave as DamageCause.ENTITY_ATTACK + org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); + Projectile projectile = (Projectile) this.getBukkitEntity(); + // TODO deal with arrows being fired from a non-entity + + EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 4); + server.getPluginManager().callEvent(event); + this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle(); + + if (event.isCancelled()) { + stick = !projectile.doesBounce(); + } else { + // this function returns if the arrow should stick in or not, i.e. !bounce + stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + } } else { - // this function returns if the arrow should stick in or not, i.e. !bounce - stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + stick = movingobjectposition.entity.damageEntity(this.shooter, 4); + } + if (stick) { + // CraftBukkit end + this.world.makeSound(this, "random.drr", 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F)); + this.die(); + } else { + this.motX *= -0.10000000149011612D; + this.motY *= -0.10000000149011612D; + this.motZ *= -0.10000000149011612D; + this.yaw += 180.0F; + this.lastYaw += 180.0F; + this.k = 0; } - } else { - stick = movingobjectposition.entity.damageEntity(this.shooter, 4); - } - if (stick) { - // CraftBukkit end - this.world.makeSound(this, "random.drr", 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F)); - this.die(); - } else { - this.motX *= -0.10000000149011612D; - this.motY *= -0.10000000149011612D; - this.motZ *= -0.10000000149011612D; - this.yaw += 180.0F; - this.lastYaw += 180.0F; - this.k = 0; } } else { + // Tsunami start - improve ProjectileHitEvent + org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d); + BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face); + ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face); + this.world.getServer().getPluginManager().callEvent(phe); + // Tsunami end this.d = movingobjectposition.b; this.e = movingobjectposition.c; this.f = movingobjectposition.d; diff --git a/src/main/java/net/minecraft/server/EntityEgg.java b/src/main/java/net/minecraft/server/EntityEgg.java index d80da64..27b130b 100644 --- a/src/main/java/net/minecraft/server/EntityEgg.java +++ b/src/main/java/net/minecraft/server/EntityEgg.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.entity.CreatureType; import org.bukkit.entity.Projectile; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; @@ -148,123 +150,138 @@ public void m_() { } if (movingobjectposition != null) { - // CraftBukkit start - ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity()); - this.world.getServer().getPluginManager().callEvent(phe); + ProjectileHitEvent phe; + // CraftBukkit start if (movingobjectposition.entity != null) { - boolean stick; - if (movingobjectposition.entity instanceof EntityLiving) { - org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); - Projectile projectile = (Projectile) this.getBukkitEntity(); - - // TODO @see EntityArrow#162 - EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0); - this.world.getServer().getPluginManager().callEvent(event); - - if (event.isCancelled()) { - stick = !projectile.doesBounce(); + // Tsunami start - improve ProjectileHitEvent + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity()); + this.world.getServer().getPluginManager().callEvent(phe); + if (!phe.isCancelled()) { + // Tsunami end + + boolean stick; + if (movingobjectposition.entity instanceof EntityLiving) { + org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); + Projectile projectile = (Projectile) this.getBukkitEntity(); + + // TODO @see EntityArrow#162 + EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0); + this.world.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + stick = !projectile.doesBounce(); + } else { + // this function returns if the egg should stick in or not, i.e. !bounce + stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + } } else { - // this function returns if the egg should stick in or not, i.e. !bounce - stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + stick = movingobjectposition.entity.damageEntity(this.thrower, 0); } - } else { - stick = movingobjectposition.entity.damageEntity(this.thrower, 0); - } - if (stick) { - ; // Original code does nothing *yet* + if (stick) { + ; // Original code does nothing *yet* + } } + } else { + // Tsunami start - improve ProjectileHitEvent + org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d); + BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face); + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face); + this.world.getServer().getPluginManager().callEvent(phe); + // Tsunami end } - boolean hatching = !this.world.isStatic && this.random.nextInt(8) == 0; - int numHatching = (this.random.nextInt(32) == 0) ? 4 : 1; - if (!hatching) { - numHatching = 0; - } + if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami + boolean hatching = !this.world.isStatic && this.random.nextInt(8) == 0; + int numHatching = (this.random.nextInt(32) == 0) ? 4 : 1; + if (!hatching) { + numHatching = 0; + } - CreatureType hatchingType = CreatureType.CHICKEN; + CreatureType hatchingType = CreatureType.CHICKEN; - if (this.thrower instanceof EntityPlayer) { - org.bukkit.entity.Player player = (this.thrower == null) ? null : (org.bukkit.entity.Player) this.thrower.getBukkitEntity(); + if (this.thrower instanceof EntityPlayer) { + org.bukkit.entity.Player player = (this.thrower == null) ? null : (org.bukkit.entity.Player) this.thrower.getBukkitEntity(); - PlayerEggThrowEvent event = new PlayerEggThrowEvent(player, (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) numHatching, hatchingType); - this.world.getServer().getPluginManager().callEvent(event); + PlayerEggThrowEvent event = new PlayerEggThrowEvent(player, (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) numHatching, hatchingType); + this.world.getServer().getPluginManager().callEvent(event); - hatching = event.isHatching(); - numHatching = event.getNumHatches(); - hatchingType = event.getHatchType(); - } + hatching = event.isHatching(); + numHatching = event.getNumHatches(); + hatchingType = event.getHatchType(); + } - if (hatching) { - for (int k = 0; k < numHatching; k++) { - Entity entity = null; - switch (hatchingType) { - case CHICKEN: - entity = new EntityChicken(this.world); - break; - case COW: - entity = new EntityCow(this.world); - break; - case CREEPER: - entity = new EntityCreeper(this.world); - break; - case GHAST: - entity = new EntityGhast(this.world); - break; - case GIANT: - entity = new EntityGiantZombie(this.world); - break; - case PIG: - entity = new EntityPig(this.world); - break; - case PIG_ZOMBIE: - entity = new EntityPigZombie(this.world); - break; - case SHEEP: - entity = new EntitySheep(this.world); - break; - case SKELETON: - entity = new EntitySkeleton(this.world); - break; - case SPIDER: - entity = new EntitySpider(this.world); - break; - case ZOMBIE: - entity = new EntityZombie(this.world); - break; - case SQUID: - entity = new EntitySquid(this.world); - break; - case SLIME: - entity = new EntitySlime(this.world); - break; - case WOLF: - entity = new EntityWolf(this.world); - break; - case MONSTER: - entity = new EntityMonster(this.world); - break; - default: - entity = new EntityChicken(this.world); - break; - } + if (hatching) { + for (int k = 0; k < numHatching; k++) { + Entity entity = null; + switch (hatchingType) { + case CHICKEN: + entity = new EntityChicken(this.world); + break; + case COW: + entity = new EntityCow(this.world); + break; + case CREEPER: + entity = new EntityCreeper(this.world); + break; + case GHAST: + entity = new EntityGhast(this.world); + break; + case GIANT: + entity = new EntityGiantZombie(this.world); + break; + case PIG: + entity = new EntityPig(this.world); + break; + case PIG_ZOMBIE: + entity = new EntityPigZombie(this.world); + break; + case SHEEP: + entity = new EntitySheep(this.world); + break; + case SKELETON: + entity = new EntitySkeleton(this.world); + break; + case SPIDER: + entity = new EntitySpider(this.world); + break; + case ZOMBIE: + entity = new EntityZombie(this.world); + break; + case SQUID: + entity = new EntitySquid(this.world); + break; + case SLIME: + entity = new EntitySlime(this.world); + break; + case WOLF: + entity = new EntityWolf(this.world); + break; + case MONSTER: + entity = new EntityMonster(this.world); + break; + default: + entity = new EntityChicken(this.world); + break; + } - // The world we're spawning in accepts this creature - boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal; - if ((isAnimal && this.world.allowAnimals) || (!isAnimal && this.world.allowMonsters)) { - entity.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F); - this.world.addEntity(entity, SpawnReason.EGG); + // The world we're spawning in accepts this creature + boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal; + if ((isAnimal && this.world.allowAnimals) || (!isAnimal && this.world.allowMonsters)) { + entity.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F); + this.world.addEntity(entity, SpawnReason.EGG); + } + // CraftBukkit end } - // CraftBukkit end } - } - for (int l = 0; l < 8; ++l) { - this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D); - } + for (int l = 0; l < 8; ++l) { + this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D); + } - this.die(); + this.die(); + } } this.locX += this.motX; diff --git a/src/main/java/net/minecraft/server/EntityFireball.java b/src/main/java/net/minecraft/server/EntityFireball.java index 4e36ab6..aa69f7f 100644 --- a/src/main/java/net/minecraft/server/EntityFireball.java +++ b/src/main/java/net/minecraft/server/EntityFireball.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.Explosive; @@ -126,13 +128,16 @@ public void m_() { } if (movingobjectposition != null) { + ProjectileHitEvent phe; + // CraftBukkit start - ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity()); - this.world.getServer().getPluginManager().callEvent(phe); - // CraftBukkit end - if (!this.world.isStatic) { - // CraftBukkit start - if (movingobjectposition.entity != null) { + if (movingobjectposition.entity != null) { + // Tsunami start - improve ProjectileHitEvent + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity()); + this.world.getServer().getPluginManager().callEvent(phe); + if (!phe.isCancelled()) { + // Tsunami end + boolean stick; if (movingobjectposition.entity instanceof EntityLiving) { org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); @@ -157,7 +162,16 @@ public void m_() { ; } } + } else { + // Tsunami start - improve ProjectileHitEvent + org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d); + BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face); + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face); + this.world.getServer().getPluginManager().callEvent(phe); + // Tsunami end + } + if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami ExplosionPrimeEvent event = new ExplosionPrimeEvent((Explosive) CraftEntity.getEntity(this.world.getServer(), this)); this.world.getServer().getPluginManager().callEvent(event); @@ -166,9 +180,9 @@ public void m_() { this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire()); } // CraftBukkit end - } - this.die(); + this.die(); + } } this.locX += this.motX; diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 15af116..9d6b78f 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -6,6 +6,7 @@ import com.projectposeidon.api.PoseidonUUID; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import org.betamc.tsunami.Tsunami; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.inventory.CraftItemStack; @@ -248,7 +249,7 @@ public void a(boolean flag) { if (flag && !this.chunkCoordIntPairQueue.isEmpty()) { // Tsunami start - improve chunk sending WorldServer worldserver = this.getWorldServer(); - while (!this.chunkCoordIntPairQueue.isEmpty()) { + for (int count = 0; !this.chunkCoordIntPairQueue.isEmpty() && count < Tsunami.config().networking().maxChunkPacketsPerTick(); count++) { long coordPair = this.chunkCoordIntPairQueue.removeLong(0); Chunk chunk = worldserver.chunkProviderServer.getChunkAt(LongHash.msw(coordPair), LongHash.lsw(coordPair)); diff --git a/src/main/java/net/minecraft/server/EntitySnowball.java b/src/main/java/net/minecraft/server/EntitySnowball.java index fb3da4e..719e0c0 100644 --- a/src/main/java/net/minecraft/server/EntitySnowball.java +++ b/src/main/java/net/minecraft/server/EntitySnowball.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.Projectile; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -146,41 +148,55 @@ public void m_() { } if (movingobjectposition != null) { - // CraftBukkit start - ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity()); - this.world.getServer().getPluginManager().callEvent(phe); + ProjectileHitEvent phe; + // CraftBukkit start if (movingobjectposition.entity != null) { - boolean stick; - if (movingobjectposition.entity instanceof EntityLiving) { - org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); - Projectile projectile = (Projectile) this.getBukkitEntity(); - - // TODO @see EntityArrow#162 - EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0); - this.world.getServer().getPluginManager().callEvent(event); - this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle(); - - if (event.isCancelled()) { - stick = !projectile.doesBounce(); + // Tsunami start - improve ProjectileHitEvent + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity()); + this.world.getServer().getPluginManager().callEvent(phe); + if (!phe.isCancelled()) { + // Tsunami end + + boolean stick; + if (movingobjectposition.entity instanceof EntityLiving) { + org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity(); + Projectile projectile = (Projectile) this.getBukkitEntity(); + + // TODO @see EntityArrow#162 + EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0); + this.world.getServer().getPluginManager().callEvent(event); + this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle(); + + if (event.isCancelled()) { + stick = !projectile.doesBounce(); + } else { + // this function returns if the snowball should stick in or not, i.e. !bounce + stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + } } else { - // this function returns if the snowball should stick in or not, i.e. !bounce - stick = movingobjectposition.entity.damageEntity(this, event.getDamage()); + stick = movingobjectposition.entity.damageEntity(this.shooter, 0); + } + if (stick) { + ; } - } else { - stick = movingobjectposition.entity.damageEntity(this.shooter, 0); - } - if (stick) { - ; } + // CraftBukkit end + } else { + // Tsunami start - improve ProjectileHitEvent + org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d); + BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face); + phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face); + this.world.getServer().getPluginManager().callEvent(phe); + // Tsunami end } - // CraftBukkit end - for (int k = 0; k < 8; ++k) { - this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D); + if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami + for (int k = 0; k < 8; ++k) { + this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D); + } + this.die(); } - - this.die(); } this.locX += this.motX; diff --git a/src/main/java/net/minecraft/server/ItemDoor.java b/src/main/java/net/minecraft/server/ItemDoor.java index 73af3e8..73f85bb 100644 --- a/src/main/java/net/minecraft/server/ItemDoor.java +++ b/src/main/java/net/minecraft/server/ItemDoor.java @@ -18,8 +18,6 @@ public boolean a(ItemStack itemstack, EntityHuman entityhuman, World world, int if (l != 1) { return false; } else { - int clickedX = i, clickedY = j, clickedZ = k; // CraftBukkit - ++j; Block block; @@ -70,26 +68,22 @@ public boolean a(ItemStack itemstack, EntityHuman entityhuman, World world, int } CraftBlockState blockState = CraftBlockState.getBlockState(world, i, j, k); // CraftBukkit - world.suppressPhysics = true; world.setTypeIdAndData(i, j, k, block.id, i1); - // CraftBukkit start - bed - world.suppressPhysics = false; - world.applyPhysics(i, j, k, Block.REDSTONE_WIRE.id); - BlockPlaceEvent event = CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockState, clickedX, clickedY, clickedZ, block); + // CraftBukkit start + BlockPlaceEvent event = CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockState, i, j, k, block); if (event.isCancelled() || !event.canBuild()) { event.getBlockPlaced().setTypeIdAndData(blockState.getTypeId(), blockState.getRawData(), false); return false; } - - world.suppressPhysics = true; // CraftBukkit end + world.setTypeIdAndData(i, j + 1, k, block.id, i1 + 8); world.suppressPhysics = false; - // world.applyPhysics(i, j, k, block.id); // CraftBukkit - moved up - world.applyPhysics(i, j + 1, k, Block.REDSTONE_WIRE.id); + world.applyPhysics(i, j, k, block.id); + world.applyPhysics(i, j + 1, k, block.id); --itemstack.count; return true; } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 4a8bd90..89da65f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -37,8 +37,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; @@ -47,6 +47,7 @@ public class MinecraftServer implements Runnable, ICommandListener { public static Logger log = Logger.getLogger("Minecraft"); private static final long NANOS_PER_TICK = 50_000_000; // Tsunami public static HashMap trackerList = new HashMap(); + private Thread primaryThread; // Tsunami public NetworkListenThread networkListenThread; public PropertyManager propertyManager; // public WorldServer[] worldServer; // CraftBukkit - removed! @@ -61,6 +62,7 @@ public class MinecraftServer implements Runnable, ICommandListener { private List r = new ArrayList(); private List s = Collections.synchronizedList(new ArrayList()); private final Queue remoteCommands = new LinkedBlockingQueue<>(); // Tsunami + private final Queue taskQueue = new ConcurrentLinkedQueue<>(); // Tsunami // public EntityTracker[] tracker = new EntityTracker[2]; // CraftBukkit - removed! public boolean onlineMode; public boolean spawnAnimals; @@ -90,6 +92,12 @@ public MinecraftServer(OptionSet options) { // CraftBukkit - adds argument Optio this.options = options; Runtime.getRuntime().addShutdownHook(this.shutdownHook); // CraftBukkit end + + // Tsunami - keep reference to primary thread + Thread primaryThread = new ThreadServerApplication("Server thread", this); + this.primaryThread = primaryThread; + primaryThread.start(); + // Tsunami end } private boolean init() throws UnknownHostException { // CraftBukkit - added throws UnknownHostException @@ -493,38 +501,36 @@ public void a() { public void run() { try { if (this.init()) { - long i = System.nanoTime(); // Tsunami - System.nanoTime() - - for (long j = 0L; this.isRunning;) { - long k = System.nanoTime(); // Tsunami - System.nanoTime() - long l = k - i; + // Tsunami start - improve tick loop + long nextTickTime = System.nanoTime(); - if (l > NANOS_PER_TICK * 40L) { - // Tsunami - improve message - log.warning("Can't keep up! Did the system time change, or is the server overloaded? Running " + l / 1_000_000 + "ms behind, skipping " + l / NANOS_PER_TICK + " tick(s)"); - l = NANOS_PER_TICK * 40L; - } + while (this.isRunning) { + long behind = System.nanoTime() - nextTickTime; - if (l < 0L) { - log.warning("Time ran backwards! Did the system time change?"); - l = 0L; + if (behind > NANOS_PER_TICK * 40L) { + long skipTicks = behind / NANOS_PER_TICK; + log.warning("Can't keep up! Did the system time change, or is the server overloaded? Running " + behind / 1_000_000 + "ms behind, skipping " + skipTicks + " tick(s)"); + nextTickTime += skipTicks * NANOS_PER_TICK; + } else { + nextTickTime += NANOS_PER_TICK; } - j += l; - i = k; - if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit - this.h(); - j = 0L; - } else { - while (j > NANOS_PER_TICK) { // Tsunami - MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit - getWatchdog().tickUpdate(); // Project Poseidon - j -= NANOS_PER_TICK; // Tsunami - this.h(); + MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit + getWatchdog().tickUpdate(); // Project Poseidon + this.h(); + + Runnable task; + while (System.nanoTime() < nextTickTime) { + task = taskQueue.poll(); + if (task != null) { + try { + task.run(); + } catch (Throwable t) { + log.log(Level.SEVERE, "Error executing queued task", t); + } } } - - LockSupport.parkNanos(NANOS_PER_TICK - j); // Tsunami - use LockSupport.parkNanos() instead of Thread.sleep() + // Tsunami end } } else { while (this.isRunning) { @@ -598,6 +604,18 @@ private void h() { Vec3D.a(); ++this.ticks; + // Tsunami start + Runnable task; + int count = this.taskQueue.size(); + while (count-- > 0 && (task = this.taskQueue.poll()) != null) { + try { + task.run(); + } catch (Throwable t) { + log.log(Level.SEVERE, "Error executing queued task", t); + } + } + // Tsunami end + ((CraftScheduler) this.server.getScheduler()).mainThreadHeartbeat(this.ticks); // CraftBukkit //Project Poseidon Start - Tick Update @@ -661,15 +679,19 @@ private void h() { log.log(Level.INFO, "Saving worlds"); for (int w = 0; w < this.worlds.size(); w++) { WorldServer worldserver = this.worlds.get(w); - worldserver.w(); - worldserver.chunkProviderServer.lastAutoSave = worldserver.worldData.f(); + if (worldserver.chunkProviderServer.canSave()) { + worldserver.w(); + worldserver.chunkProviderServer.lastAutoSave = worldserver.worldData.f(); + } } this.serverConfigurationManager.savePlayers(); } for (int w = 0; w < this.worlds.size(); w++) { WorldServer worldserver = this.worlds.get(w); - worldserver.chunkProviderServer.saveChunks(false, null); + if (worldserver.chunkProviderServer.canSave()) { + worldserver.chunkProviderServer.saveChunks(false, null); + } } // Tsunami end @@ -684,6 +706,12 @@ private void h() { } } + // Tsunami start + public void scheduleTask(Runnable task) { + this.taskQueue.add(task); + } + // Tsunami end + public void issueCommand(String s, ICommandListener icommandlistener) { this.s.add(new ServerCommand(s, icommandlistener)); } @@ -748,12 +776,18 @@ public static void main(final OptionSet options) { // CraftBukkit - replaces mai // CraftBukkit - remove gui - (new ThreadServerApplication("Server thread", minecraftserver)).start(); + //(new ThreadServerApplication("Server thread", minecraftserver)).start(); // Tsunami - moved to MinecraftServer constructor } catch (Exception exception) { log.log(Level.SEVERE, "Failed to start the minecraft server", exception); } } + // Tsunami start + public boolean isPrimaryThread() { + return Thread.currentThread() == this.primaryThread; + } + // Tsunami end + public File a(String s) { return new File(s); } diff --git a/src/main/java/net/minecraft/server/NBTTagList.java b/src/main/java/net/minecraft/server/NBTTagList.java index 53fc22f..2e275a8 100644 --- a/src/main/java/net/minecraft/server/NBTTagList.java +++ b/src/main/java/net/minecraft/server/NBTTagList.java @@ -8,7 +8,7 @@ public class NBTTagList extends NBTBase { - private List a = new ArrayList(); + public List a = new ArrayList(); // Tsunami - private -> public private byte b; public NBTTagList() {} diff --git a/src/main/java/net/minecraft/server/NetHandler.java b/src/main/java/net/minecraft/server/NetHandler.java index db70d65..20460f8 100644 --- a/src/main/java/net/minecraft/server/NetHandler.java +++ b/src/main/java/net/minecraft/server/NetHandler.java @@ -6,6 +6,16 @@ public NetHandler() {} public abstract boolean c(); + // Tsunami start - rewrite networking code + public abstract void a(); + + public abstract boolean disconnected(); + + public abstract void disconnect(String s); + + public abstract NetworkManager getNetManager(); + // Tsunami end + public void a(Packet51MapChunk packet51mapchunk) {} public void a(Packet packet) {} @@ -211,4 +221,10 @@ public void a(Packet131 packet131) { public void a(Packet61 packet61) { this.a((Packet) packet61); } + + // Tsunami start - backport plugin messaging + public void a(Packet250PluginMessage packet250pluginmessage) { + this.a((Packet) packet250pluginmessage); + } + // Tsunami end } diff --git a/src/main/java/net/minecraft/server/NetLoginHandler.java b/src/main/java/net/minecraft/server/NetLoginHandler.java index 47455e6..be473be 100644 --- a/src/main/java/net/minecraft/server/NetLoginHandler.java +++ b/src/main/java/net/minecraft/server/NetLoginHandler.java @@ -6,6 +6,7 @@ import com.legacyminecraft.poseidon.PoseidonConfig; import com.projectposeidon.johnymuffin.LoginProcessHandler; import org.betamc.tsunami.Tsunami; +import org.betamc.tsunami.network.NetworkUtil; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.craftbukkit.CraftServer; @@ -27,7 +28,7 @@ public class NetLoginHandler extends NetHandler { public static Logger a = Logger.getLogger("Minecraft"); private static Random d = new Random(); - public NetworkManager networkManager; + public final NetworkManager networkManager; // Tsunami - final public boolean c = false; private MinecraftServer server; private int f = 0; @@ -51,8 +52,8 @@ public NetLoginHandler(MinecraftServer minecraftserver, Socket socket, String s) this.server = minecraftserver; this.networkManager = new NetworkManager(socket, s, this); this.networkManager.f = 0; - this.msgKickShutdown = PoseidonConfig.getInstance().getConfigString("message.kick.shutdown"); + this.networkManager.startThreads(); // Tsunami } // CraftBukkit start @@ -74,10 +75,17 @@ public void a() { } } + // Tsunami start + public boolean disconnected() { + return this.c; + } + // Tsunami end + public void disconnect(String s) { try { a.info("Disconnecting " + this.b() + ": " + s); - this.networkManager.queue(new Packet255KickDisconnect(s)); + String kickReason = s.substring(0, Math.min(s.length(), 100)); // Tsunami - truncate to 100 characters + this.networkManager.queue(new Packet255KickDisconnect(kickReason)); this.networkManager.d(); this.c = true; } catch (Exception exception) { @@ -85,6 +93,12 @@ public void disconnect(String s) { } } + // Tsunami start + public NetworkManager getNetManager() { + return this.networkManager; + } + // Tsunami end + public void a(Packet2Handshake packet2handshake) { if (this.server.onlineMode) { this.serverId = Long.toHexString(d.nextLong()); @@ -99,6 +113,8 @@ public void a(Packet0KeepAlive packet0KeepAlive) { } public void a(Packet1Login packet1login) { + NetworkUtil.ensureOnMainThread(packet1login, this, this.server); // Tsunami + if (receivedLoginPacket) { this.disconnect("Multiple login packets received."); return; @@ -245,7 +261,6 @@ private void doPongResponse(long time) throws IOException { this.networkManager.d(); this.networkManager.socket.close(); - this.server.networkListenThread.b(this); this.c = true; } // Tsunami end @@ -269,6 +284,7 @@ public void b(Packet1Login packet1login) { //Poseidon End netserverhandler.sendPacket(new Packet1Login("", entityplayer.id, worldserver.getSeed(), (byte) worldserver.worldProvider.dimension)); netserverhandler.sendPacket(new Packet6SpawnPosition(chunkcoordinates.x, chunkcoordinates.y, chunkcoordinates.z)); + netserverhandler.getPlayer().sendSupportedChannels(); // Tsunami this.server.serverConfigurationManager.a(entityplayer, worldserver); // this.server.serverConfigurationManager.sendAll(new Packet3Chat("\u00A7e" + entityplayer.name + " joined the game.")); // CraftBukkit - message moved to join event this.server.serverConfigurationManager.c(entityplayer); diff --git a/src/main/java/net/minecraft/server/NetServerHandler.java b/src/main/java/net/minecraft/server/NetServerHandler.java index e23393f..f81a6f0 100644 --- a/src/main/java/net/minecraft/server/NetServerHandler.java +++ b/src/main/java/net/minecraft/server/NetServerHandler.java @@ -5,6 +5,7 @@ import com.projectposeidon.ConnectionType; import com.legacyminecraft.poseidon.PoseidonConfig; import org.betamc.tsunami.Tsunami; +import org.betamc.tsunami.network.NetworkUtil; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; @@ -22,6 +23,7 @@ import org.bukkit.event.packet.PacketReceivedEvent; import org.bukkit.event.player.*; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -147,6 +149,12 @@ public void a() { // Tsunami end } + // Tsunami start + public boolean disconnected() { + return this.disconnected; + } + // Tsunami end + public void disconnect(String s) { if (disconnected) return; // Poseidon: Kick/Disconnect spam fix @@ -165,7 +173,8 @@ public void disconnect(String s) { // CraftBukkit end this.player.B(); - this.sendPacket(new Packet255KickDisconnect(s)); + String kickReason = s.substring(0, Math.min(s.length(), 100)); // Tsunami - truncate to 100 characters + this.sendPacket(new Packet255KickDisconnect(kickReason)); this.networkManager.d(); // CraftBukkit start @@ -179,7 +188,15 @@ public void disconnect(String s) { this.disconnected = true; } + // Tsunami start + public NetworkManager getNetManager() { + return this.networkManager; + } + // Tsunami end + public void a(Packet27 packet27) { + NetworkUtil.ensureOnMainThread(packet27, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet27); server.getPluginManager().callEvent(event); @@ -190,6 +207,8 @@ public void a(Packet27 packet27) { } public void a(Packet10Flying packet10flying) { + NetworkUtil.ensureOnMainThread(packet10flying, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet10flying); server.getPluginManager().callEvent(pevent); @@ -519,6 +538,8 @@ public void teleport(Location dest) { } public void a(Packet14BlockDig packet14blockdig) { + NetworkUtil.ensureOnMainThread(packet14blockdig, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet14blockdig); server.getPluginManager().callEvent(event); @@ -609,6 +630,8 @@ public void a(Packet14BlockDig packet14blockdig) { } public void a(Packet15Place packet15place) { + NetworkUtil.ensureOnMainThread(packet15place, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet15place); server.getPluginManager().callEvent(pevent); @@ -794,6 +817,8 @@ public void sendPacket(Packet packet) { } public void a(Packet16BlockItemSwitch packet16blockitemswitch) { + NetworkUtil.ensureOnMainThread(packet16blockitemswitch, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet16blockitemswitch); server.getPluginManager().callEvent(pevent); @@ -816,6 +841,8 @@ public void a(Packet16BlockItemSwitch packet16blockitemswitch) { } public void a(Packet3Chat packet3chat) { + NetworkUtil.ensureOnMainThread(packet3chat, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet3chat); server.getPluginManager().callEvent(event); @@ -941,6 +968,8 @@ private void handleCommand(String s) { } public void a(Packet18ArmAnimation packet18armanimation) { + NetworkUtil.ensureOnMainThread(packet18armanimation, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet18armanimation); server.getPluginManager().callEvent(pevent); @@ -985,6 +1014,8 @@ public void a(Packet18ArmAnimation packet18armanimation) { } public void a(Packet19EntityAction packet19entityaction) { + NetworkUtil.ensureOnMainThread(packet19entityaction, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet19entityaction); server.getPluginManager().callEvent(pevent); @@ -1041,6 +1072,8 @@ public String getName() { } public void a(Packet7UseEntity packet7useentity) { + NetworkUtil.ensureOnMainThread(packet7useentity, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet7useentity); server.getPluginManager().callEvent(pevent); @@ -1088,6 +1121,8 @@ public void a(Packet7UseEntity packet7useentity) { } public void a(Packet9Respawn packet9respawn) { + NetworkUtil.ensureOnMainThread(packet9respawn, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet9respawn); server.getPluginManager().callEvent(event); @@ -1102,12 +1137,16 @@ public void a(Packet9Respawn packet9respawn) { } public void a(Packet101CloseWindow packet101closewindow) { + NetworkUtil.ensureOnMainThread(packet101closewindow, this, this.minecraftServer); // Tsunami + if (this.player.dead) return; // CraftBukkit this.player.A(); } public void a(Packet102WindowClick packet102windowclick) { + NetworkUtil.ensureOnMainThread(packet102windowclick, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet102windowclick); server.getPluginManager().callEvent(event); @@ -1147,6 +1186,8 @@ public void a(Packet106Transaction packet106transaction) { this.ping = (this.ping * 3 + time) / 4; this.pingTimestamp = -1; return; + } else { + NetworkUtil.ensureOnMainThread(packet106transaction, this, this.minecraftServer); } // Tsunami end @@ -1166,6 +1207,8 @@ public void a(Packet106Transaction packet106transaction) { } public void a(Packet130UpdateSign packet130updatesign) { + NetworkUtil.ensureOnMainThread(packet130updatesign, this, this.minecraftServer); // Tsunami + // poseidon PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet130updatesign); server.getPluginManager().callEvent(pevent); @@ -1237,6 +1280,26 @@ public void a(Packet130UpdateSign packet130updatesign) { } } + // Tsunami start - backport plugin messaging + public void a(Packet250PluginMessage packet250pluginmessage) { + NetworkUtil.ensureOnMainThread(packet250pluginmessage, this, this.minecraftServer); + + if (packet250pluginmessage.channel.equals("REGISTER")) { + String channels = new String(packet250pluginmessage.message, StandardCharsets.UTF_8); + for (String channel : channels.split("\0")) { + getPlayer().addChannel(channel); + } + } else if (packet250pluginmessage.channel.equals("UNREGISTER")) { + String channels = new String(packet250pluginmessage.message, StandardCharsets.UTF_8); + for (String channel : channels.split("\0")) { + getPlayer().removeChannel(channel); + } + } else { + this.server.getMessenger().dispatchIncomingMessage(getPlayer(), packet250pluginmessage.channel, packet250pluginmessage.message); + } + } + // Tsunami end + public boolean c() { return true; } diff --git a/src/main/java/net/minecraft/server/NetworkListenThread.java b/src/main/java/net/minecraft/server/NetworkListenThread.java index 3e16232..b0dd731 100644 --- a/src/main/java/net/minecraft/server/NetworkListenThread.java +++ b/src/main/java/net/minecraft/server/NetworkListenThread.java @@ -3,9 +3,8 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,8 +15,11 @@ public class NetworkListenThread { private Thread e; public volatile boolean b = false; private int f = 0; - private List g = Collections.synchronizedList(new ArrayList()); // Tsunami - synchronized list - private ArrayList h = new ArrayList(); + // Tsunami start - rewrite networking code + //private List g = new ArrayList(); + //private ArrayList h = new ArrayList(); + private final List connections = new CopyOnWriteArrayList<>(); + // Tsunami end public MinecraftServer c; public NetworkListenThread(MinecraftServer minecraftserver, InetAddress inetaddress, int i) throws IOException { @@ -29,68 +31,41 @@ public NetworkListenThread(MinecraftServer minecraftserver, InetAddress inetaddr this.e.start(); } - // Tsunami start - public void b(NetLoginHandler netloginhandler) { - this.g.remove(netloginhandler); - } - // Tsunami end - public void a(NetServerHandler netserverhandler) { - this.h.add(netserverhandler); + addConnection(netserverhandler); // Tsunami } private void a(NetLoginHandler netloginhandler) { - if (netloginhandler == null) { - throw new IllegalArgumentException("Got null pendingconnection!"); + addConnection(netloginhandler); // Tsunami + } + + // Tsunami start - rewrite networking code + public void addConnection(NetHandler netHandler) { + if (netHandler == null) { + throw new IllegalArgumentException("Got null connection!"); } else { - this.g.add(netloginhandler); + this.connections.add(netHandler); } } + // Tsunami end public void a() { - int i; - - synchronized (this.g) { // Tsunami - wrap in synchronized block - for (i = 0; i < this.g.size(); ++i) { - NetLoginHandler netloginhandler = (NetLoginHandler) this.g.get(i); - - try { - netloginhandler.a(); - } catch (Exception exception) { - if (netloginhandler == null) { - a.log(Level.WARNING, "Looks like someone tried to crash the server, stopped their attempt."); - this.g.remove(i); - return; - } else { - netloginhandler.disconnect("Internal server error"); - a.log(Level.WARNING, "Failed to handle packet: " + exception, exception); - } - } - - if (netloginhandler.c) { - this.g.remove(i--); - } - - netloginhandler.networkManager.a(); - } - } - - for (i = 0; i < this.h.size(); ++i) { - NetServerHandler netserverhandler = (NetServerHandler) this.h.get(i); - + // Tsunami start - rewrite networking code + for (NetHandler netHandler : this.connections) { try { - netserverhandler.a(); - } catch (Exception exception1) { - a.log(Level.WARNING, "Failed to handle packet: " + exception1, exception1); - netserverhandler.disconnect("Internal server error"); + netHandler.a(); + } catch (Exception e) { + a.log(Level.WARNING, "Failed to tick connection: ", e); + netHandler.disconnect("Internal server error"); } - if (netserverhandler.disconnected) { - this.h.remove(i--); + if (netHandler.disconnected()) { + this.connections.remove(netHandler); } - netserverhandler.networkManager.a(); + netHandler.getNetManager().a(); } + // Tsunami end } static ServerSocket a(NetworkListenThread networklistenthread) { diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java index bcbe924..630308d 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -3,6 +3,7 @@ import com.legacyminecraft.poseidon.PoseidonConfig; import com.legacyminecraft.poseidon.event.PlayerReceivePacketEvent; import org.betamc.tsunami.Tsunami; +import org.betamc.tsunami.network.PacketScheduledException; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -14,6 +15,8 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; public class NetworkManager { @@ -26,7 +29,9 @@ public class NetworkManager { public DataInputStream input; // Tsunami - private -> public public DataOutputStream output; // Tsunami - private -> public private boolean l = true; - private Queue m = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue + //private List m = Collections.synchronizedList(new ArrayList()); // Tsunami + private final AtomicLong readPackets = new AtomicLong(0L); // Tsunami + private long lastReadPackets = 0L; // Tsunami private Queue highPriorityQueue = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue private Queue lowPriorityQueue = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue private NetHandler p; @@ -85,9 +90,14 @@ public NetworkManager(Socket socket, String s, NetHandler nethandler) { // CraftBukkit end */ this.s = new NetworkReaderThread(this, s + " read thread"); this.r = new NetworkWriterThread(this, s + " write thread"); + } + + // Tsunami start - rewrite networking code + void startThreads() { this.s.start(); this.r.start(); } + // Tsunami end //Project Poseidon Start public void setSocketAddress(SocketAddress socketAddress) { @@ -202,8 +212,9 @@ private boolean g() { int i = packet.b(); aint[i] += packet.a() + 1; - this.m.add(packet); flag = true; + + handleReadPacket(packet); // Tsunami } else { this.a("disconnect.endOfStream", new Object[0]); } @@ -218,6 +229,29 @@ private boolean g() { } } + // Tsunami start - rewrite networking code + private void handleReadPacket(Packet packet) { + this.readPackets.incrementAndGet(); + + NetHandler netHandler = this.p; + if (this.firePacketEvents && netHandler instanceof NetServerHandler) { + PlayerReceivePacketEvent event = new PlayerReceivePacketEvent(((NetServerHandler) netHandler).player.name, packet); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + packet = event.getPacket(); + } + + if (packet == null) return; + try { + packet.a(netHandler); + } catch (PacketScheduledException e) { + } catch (Exception e) { + MinecraftServer.log.log(Level.WARNING, "Failed to handle packet: ", e); + netHandler.disconnect("Internal server error"); + } + } + // Tsunami end + private void a(Exception exception) { exception.printStackTrace(); this.a("disconnect.genericReason", new Object[]{"Internal exception: " + exception.toString()}); @@ -255,12 +289,17 @@ public void a(String s, Object... aobject) { } public void b() { - boolean fast = PoseidonConfig.getInstance().getBoolean("settings.faster-packets.enabled", true); - if (this.x.get() > (fast ? 2097152 : 1048576)) { + // Tsunami start + long readPackets = this.readPackets.get(); + long newPackets = readPackets - this.lastReadPackets; + this.lastReadPackets = readPackets; + // Tsunami end + + if (this.x.get() > 2097152) { // Tsunami this.a("disconnect.overflow", new Object[0]); } - if (this.m.isEmpty()) { + if (newPackets == 0) { // Tsunami if (this.w++ == 1200) { this.a("disconnect.timeout", new Object[0]); } @@ -268,11 +307,9 @@ public void b() { this.w = 0; } - int i = (fast ? 1000 : 100); - - //Poseidon - Packet spam detection + // Poseidon start - Packet spam detection if (spamDetection) { - if (this.m.size() > threshold) { + if (newPackets > threshold) { // Tsunami String playerUsername = "Unknown"; if (this.p instanceof NetServerHandler) { playerUsername = ((NetServerHandler) this.p).player.name; @@ -280,39 +317,15 @@ public void b() { } else { this.a("disconnect.spam", new Object[0]); } - System.out.println("[Poseidon] Player " + playerUsername + " has been kicked for packet spamming. The queue size was " + this.m.size() + " and the threshold was " + threshold + "."); + System.out.println("[Poseidon] Player " + playerUsername + " has been kicked for packet spamming. The queue size was " + newPackets + " and the threshold was " + threshold + "."); } } + // Poseidon end -// if(this.m.size() > 1000) { -// String playerUsername = "Unknown"; -// if (this.p instanceof NetServerHandler) { -// System.out.println("The packet queue size is " + this.m.size() + " for player " + ((NetServerHandler) this.p).player.name + "."); -// } -// } - - Packet packet; - while ((packet = this.m.poll()) != null && i-- >= 0) { // Tsunami - poll - //Poseidon Start - Packet Receive Event - if (firePacketEvents && this.p instanceof NetServerHandler) { - PlayerReceivePacketEvent event = new PlayerReceivePacketEvent(((NetServerHandler) this.p).player.name, packet); - Bukkit.getPluginManager().callEvent(event); - packet = event.getPacket(); - if (!event.isCancelled()) { - packet.a(this.p); - } - - } else { - packet.a(this.p); - } - - //Poseidon End - -// packet.a(this.p); - } + // Tsunami - moved packet handling to handleReadPacket() this.a(); - if (this.t && this.m.isEmpty()) { + if (this.t && newPackets == 0) { // Tsunami this.p.a(this.u, this.v); } } diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java index f32344b..a081e5d 100644 --- a/src/main/java/net/minecraft/server/Packet.java +++ b/src/main/java/net/minecraft/server/Packet.java @@ -300,6 +300,7 @@ public static void writeUTF8(String value, OutputStream output) throws IOExcepti a(130, true, true, Packet130UpdateSign.class); a(131, true, false, Packet131.class); a(200, true, false, Packet200Statistic.class); + a(250, true, true, Packet250PluginMessage.class); // Tsunami a(255, true, true, Packet255KickDisconnect.class); packetClassToIdMap.put(ArtificialPacket53BlockChange.class, 53); //Poseidon - Artificial Block Change Packet e = new HashMap(); diff --git a/src/main/java/net/minecraft/server/Packet250PluginMessage.java b/src/main/java/net/minecraft/server/Packet250PluginMessage.java new file mode 100644 index 0000000..e5b234c --- /dev/null +++ b/src/main/java/net/minecraft/server/Packet250PluginMessage.java @@ -0,0 +1,55 @@ +package net.minecraft.server; + +import org.bukkit.plugin.messaging.Messenger; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class Packet250PluginMessage extends Packet { + + public String channel; + public byte[] message; + + public Packet250PluginMessage(String channel, byte[] message) { + this.channel = channel; + this.message = message; + } + + public Packet250PluginMessage() { + } + + private static byte[] readMessage(DataInputStream in) throws IOException { + short length = in.readShort(); + if (length > Messenger.MAX_MESSAGE_SIZE) { + throw new IOException("Received message length larger than maximum " + Messenger.MAX_MESSAGE_SIZE); + } + byte[] message = new byte[length]; + in.readFully(message); + return message; + } + + private static void writeMessage(byte[] message, DataOutputStream out) throws IOException { + out.writeShort(message.length); + out.write(message); + } + + public void a(DataInputStream in) throws IOException { + this.channel = a(in, Messenger.MAX_CHANNEL_SIZE); + this.message = readMessage(in); + } + + public void a(DataOutputStream out) throws IOException { + a(this.channel, out); + writeMessage(this.message, out); + } + + public void a(NetHandler nethandler) { + nethandler.a(this); + } + + public int a() { + return 2 + this.channel.length() * 2 + 2 + this.message.length; + } + +} diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 182b53c..4d618d7 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -1,6 +1,7 @@ package net.minecraft.server; import org.bukkit.craftbukkit.metadata.NBTMetadataConvert; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.metadata.MetadataValue; import java.util.HashMap; @@ -15,6 +16,7 @@ public class TileEntity { public int y; public int z; protected boolean h; + public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami public final Map metadataStore = new HashMap<>(); // Tsunami public TileEntity() {} @@ -32,6 +34,12 @@ public void a(NBTTagCompound nbttagcompound) { this.x = nbttagcompound.e("x"); this.y = nbttagcompound.e("y"); this.z = nbttagcompound.e("z"); + + // Tsunami start - PersistentDataContainer API + CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY)); + container.copyTo(this.container, true); + // Tsunami end + // Tsunami start NBTTagCompound metadata = nbttagcompound.k("CustomMetadata"); this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata)); @@ -48,6 +56,7 @@ public void b(NBTTagCompound nbttagcompound) { nbttagcompound.a("x", this.x); nbttagcompound.a("y", this.y); nbttagcompound.a("z", this.z); + nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore)); // Tsunami } } diff --git a/src/main/java/net/minecraft/server/WorldData.java b/src/main/java/net/minecraft/server/WorldData.java index d6af2b3..b2dc734 100644 --- a/src/main/java/net/minecraft/server/WorldData.java +++ b/src/main/java/net/minecraft/server/WorldData.java @@ -1,6 +1,7 @@ package net.minecraft.server; import org.bukkit.craftbukkit.metadata.NBTMetadataConvert; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.metadata.MetadataValue; import java.util.HashMap; @@ -26,6 +27,7 @@ public class WorldData { private int m; private boolean n; private int o; + public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami public final Map metadataStore = new HashMap<>(); // Tsunami public WorldData(NBTTagCompound nbttagcompound) { @@ -48,6 +50,12 @@ public WorldData(NBTTagCompound nbttagcompound) { this.h = nbttagcompound.k("Player"); this.i = this.h.e("Dimension"); } + + // Tsunami start - PersistentDataContainer API + CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY)); + container.copyTo(this.container, true); + // Tsunami end + // Tsunami start NBTTagCompound metadata = nbttagcompound.k("CustomMetadata"); this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata)); @@ -123,6 +131,7 @@ private void a(NBTTagCompound nbttagcompound, NBTTagCompound nbttagcompound1) { if (nbttagcompound1 != null) { nbttagcompound.a("Player", nbttagcompound1); } + nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore)); } diff --git a/src/main/java/org/betamc/tsunami/TsunamiConfig.java b/src/main/java/org/betamc/tsunami/TsunamiConfig.java index fc09648..ddbc59e 100644 --- a/src/main/java/org/betamc/tsunami/TsunamiConfig.java +++ b/src/main/java/org/betamc/tsunami/TsunamiConfig.java @@ -1,36 +1,42 @@ package org.betamc.tsunami; -import de.exlll.configlib.Comment; -import de.exlll.configlib.Configuration; -import de.exlll.configlib.NameFormatters; -import de.exlll.configlib.YamlConfigurations; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.yaml.NodeStyle; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; -import java.nio.charset.StandardCharsets; import java.nio.file.Paths; -@Configuration +@ConfigSerializable public class TsunamiConfig { private static TsunamiConfig instance; - private Console console = new Console(); - private Logging logging = new Logging(); - private Networking networking = new Networking(); - private Profiles profiles = new Profiles(); - private Rcon rcon = new Rcon(); - private ServerListPing serverListPing = new ServerListPing(); - private Anticheat anticheat = new Anticheat(); - private World world = new World(); + private Console console; + private Logging logging; + private Networking networking; + private Profiles profiles; + private Rcon rcon; + private ServerListPing serverListPing; + private Anticheat anticheat; + private World world; public static TsunamiConfig getInstance() { - if (instance == null) { - instance = YamlConfigurations.update(Paths.get("tsunami.yml"), TsunamiConfig.class, builder -> builder - .charset(StandardCharsets.UTF_8) - .setNameFormatter(NameFormatters.LOWER_KEBAB_CASE) - .inputNulls(false) - .outputNulls(false)); - } - return instance; + if (instance != null) return instance; + + YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(Paths.get("tsunami.yml")) + .indent(2) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + try { + instance = loader.load().get(TsunamiConfig.class); + loader.save(loader.createNode().set(instance)); + return instance; + } catch (ConfigurateException e) { + throw new RuntimeException(e); + } } private TsunamiConfig() { @@ -68,16 +74,10 @@ public World world() { return world; } - @Configuration + @ConfigSerializable public static class Console { - @Comment({ - "The prompt which will show in console.", - "Color codes (§[0-9a-f]) can be used here." - }) private String prompt = "> "; - @Comment("If warning messages should be highlighted.") private boolean highlightWarnings = true; - @Comment("If error messages should be highlighted.") private boolean highlightErrors = true; public String prompt() { @@ -93,9 +93,8 @@ public boolean highlightErrors() { } } - @Configuration + @ConfigSerializable public static class Logging { - @Comment("If attempts to issue unknown commands should be logged.") private boolean logUnknownCommands = false; public boolean logUnknownCommands() { @@ -103,38 +102,27 @@ public boolean logUnknownCommands() { } } - @Configuration + @ConfigSerializable public static class Networking { - @Comment({ - "The deflate compression level used to compress chunk packets.", - "Acceptable values are [-1..9]." - }) private int chunkPacketCompressionLevel = 6; + private int maxChunkPacketsPerTick = 10; public int chunkPacketCompressionLevel() { return Math.min(Math.max(chunkPacketCompressionLevel, -1), 9); } + + public int maxChunkPacketsPerTick() { + return Math.max(maxChunkPacketsPerTick, 1); + } } - @Configuration + @ConfigSerializable public static class Profiles { - @Comment({ - "The HTTP method used to fetch player profiles.", - "Acceptable values are POST and GET." - }) private FetchMethod fetchMethod = FetchMethod.POST; - @Comment("The URL which should be used for POST requests.") private String postUrl = "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname"; - @Comment("The URL which should be used for GET requests. {username} will be replaced by the actual name.") private String getUrl = "https://api.minecraftservices.com/minecraft/profile/lookup/name/{username}"; - @Comment("If the name of a player with an online profile is required to exactly match the name returned by the API.") private boolean verifyUsernameCasing = false; - @Comment({ - "Specifies in which cases offline profiles should be created for players.", - "Acceptable values are NEVER, WHEN_CRACKED and ALWAYS." - }) private CreateOfflineProfiles createOfflineProfiles = CreateOfflineProfiles.NEVER; - @Comment("After how many days online profiles should be refetched.") private int refetchAfterDays = 30; public FetchMethod fetchMethod() { @@ -173,16 +161,10 @@ public enum CreateOfflineProfiles { } } - @Configuration + @ConfigSerializable public static class Rcon { - @Comment({ - "If the remote console protocol should be enabled.", - "Please note that RCON is not encrypted and should not be used in a production environment." - }) private boolean enabled = false; - @Comment("The port used to listen for RCON connections.") private int port = 25575; - @Comment("The password required to establish an RCON connection.") private String password = ""; public boolean enabled() { @@ -198,16 +180,10 @@ public String password() { } } - @Configuration + @ConfigSerializable public static class ServerListPing { - @Comment("If the 1.7+ query protocol should be enabled.") private boolean enabled = false; - @Comment({ - "The MOTD which should be included in the query response.", - "Color codes (§[0-9a-f]) can be used here." - }) private String motd = "A Minecraft Server"; - @Comment("If the names of connected players should be included in the query response.") private boolean showPlayerNames = true; public boolean enabled() { @@ -223,11 +199,11 @@ public boolean showPlayerNames() { } } - @Configuration + @ConfigSerializable public static class Anticheat { - private FlagQuickMovement flagQuickMovement = new FlagQuickMovement(); - private FlagWrongMovement flagWrongMovement = new FlagWrongMovement(); - private FlagFlight flagFlight = new FlagFlight(); + private FlagQuickMovement flagQuickMovement; + private FlagWrongMovement flagWrongMovement; + private FlagFlight flagFlight; public FlagQuickMovement flagQuickMovement() { return flagQuickMovement; @@ -241,19 +217,10 @@ public FlagFlight flagFlight() { return flagFlight; } - @Configuration + @ConfigSerializable public static class FlagQuickMovement { - @Comment({ - "If too quick movement should be flagged.", - "Players with the permission 'tsunami.anticheat.quick-movement.bypass' are exempt from being flagged." - }) private boolean enabled = true; - @Comment("The distance threshold for quick movement to be flagged.") private double threshold = 100.0; - @Comment({ - "If flagged players should be teleported back to their previous location.", - "If this is disabled, players will be kicked instead." - }) private boolean teleportBack = false; public boolean enabled() { @@ -269,16 +236,10 @@ public boolean teleportBack() { } } - @Configuration + @ConfigSerializable public static class FlagWrongMovement { - @Comment({ - "If wrong movement should be flagged.", - "Players with the permission 'tsunami.anticheat.wrong-movement.bypass' are exempt from being flagged." - }) private boolean enabled = true; - @Comment("The distance threshold for wrong movement to be flagged.") private double threshold = 0.0625; - @Comment("If flagged players should be teleported back to their previous location.") private boolean teleportBack = true; public boolean enabled() { @@ -294,12 +255,8 @@ public boolean teleportBack() { } } - @Configuration + @ConfigSerializable public static class FlagFlight { - @Comment({ - "After how many ticks players should be kicked for flying.", - "Players with the permission 'tsunami.anticheat.flight.bypass' are exempt from being flagged." - }) private int kickAfter = 80; public int kickAfter() { @@ -308,17 +265,13 @@ public int kickAfter() { } } - @Configuration + @ConfigSerializable public static class World { - private AsyncChunkLoading asyncChunkLoading = new AsyncChunkLoading(); - @Comment("The interval in ticks in which world data should be auto-saved.") + private AsyncChunkLoading asyncChunkLoading; private int autoSaveInterval = 6000; - @Comment("The maximum amount of chunks to auto-save per tick.") private int maxAutoSaveChunksPerTick = 24; - private MobCaps mobCaps = new MobCaps(); - @Comment("If mob caps should be enforced on a per-player basis instead of globally.") + private MobCaps mobCaps; private boolean perPlayerMobSpawning = false; - @Comment("If dropped items should merge if they are of the same type.") private boolean mergeDroppedItems = false; public AsyncChunkLoading asyncChunkLoading() { @@ -345,11 +298,9 @@ public boolean mergeDroppedItems() { return mergeDroppedItems; } - @Configuration + @ConfigSerializable public static class AsyncChunkLoading { - @Comment("If chunks should be loaded from disk asynchronously.") private boolean enabled = false; - @Comment("The amount of threads to use for loading chunks.") private int threads = 3; public boolean enabled() { @@ -361,13 +312,10 @@ public int threads() { } } - @Configuration + @ConfigSerializable public static class MobCaps { - @Comment("The mob cap for hostile mobs.") private int monsters = 70; - @Comment("The mob cap for passive mobs.") private int animals = 15; - @Comment("The mob cap for water mobs (squids).") private int waterMobs = 5; public int monsters() { diff --git a/src/main/java/org/betamc/tsunami/network/NetworkUtil.java b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java new file mode 100644 index 0000000..7bbe874 --- /dev/null +++ b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java @@ -0,0 +1,25 @@ +package org.betamc.tsunami.network; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.NetHandler; +import net.minecraft.server.Packet; + +import java.util.logging.Level; + +public class NetworkUtil { + + public static void ensureOnMainThread(Packet packet, NetHandler netHandler, MinecraftServer server) { + if (!server.isPrimaryThread()) { + server.scheduleTask(() -> { + try { + packet.a(netHandler); + } catch (Exception e) { + MinecraftServer.log.log(Level.WARNING, "Failed to handle packet: ", e); + netHandler.disconnect("Internal server error"); + } + }); + throw PacketScheduledException.INSTANCE; + } + } + +} diff --git a/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java b/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java new file mode 100644 index 0000000..cf7a2e9 --- /dev/null +++ b/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java @@ -0,0 +1,6 @@ +package org.betamc.tsunami.network; + +public final class PacketScheduledException extends RuntimeException { + + public static final PacketScheduledException INSTANCE = new PacketScheduledException(); +} diff --git a/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java b/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java index 18f3e70..9750fdd 100644 --- a/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java +++ b/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java @@ -1,6 +1,6 @@ package org.betamc.tsunami.world; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import net.minecraft.server.BiomeBase; import net.minecraft.server.BiomeMeta; import net.minecraft.server.Block; @@ -22,13 +22,13 @@ public class LocalCreatureSpawner { private static final LocalMobCapCalculator calculator = new LocalMobCapCalculator(); - private static final LongOpenHashSet chunks = new LongOpenHashSet(); + private static final IntOpenHashSet creatureTypePerChunk = new IntOpenHashSet(); private LocalCreatureSpawner() { } public static void spawnCreatures(World world, boolean spawnMonsters, boolean spawnAnimals) { - chunks.clear(); + creatureTypePerChunk.clear(); calculator.prepare(world); calculator.forEachEntry((player, chunks) -> spawnForPlayer(world, player, chunks, spawnMonsters, spawnAnimals)); } @@ -43,7 +43,7 @@ private static void spawnForPlayer(World world, EntityPlayer player, List } private static void spawnCreatureTypeForChunk(EnumCreatureType creatureType, World world, Chunk chunk) { - if (!chunks.add(LongHash.toLong(chunk.x, chunk.z))) return; + if (!creatureTypePerChunk.add(getKey(creatureType, chunk.x, chunk.z))) return; int x = (chunk.x << 4) + world.random.nextInt(16); int y = world.random.nextInt(128); int z = (chunk.z << 4) + world.random.nextInt(16); @@ -88,6 +88,13 @@ private static void spawnCreatureTypeForChunk(EnumCreatureType creatureType, Wor } } + private static int getKey(EnumCreatureType creatureType, int x, int z) { + int key = creatureType.ordinal(); + key = 31 * key + x; + key = 31 * key + z; + return key; + } + private static int getTypeId(Chunk chunk, int x, int y, int z) { if (y >= 0 && y < 128) { return chunk.getTypeId(x & 15, y, z & 15); diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index a2b312a..e91db1f 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -8,8 +8,10 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.Recipe; import org.bukkit.map.MapView; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; import org.bukkit.scheduler.BukkitScheduler; import java.util.List; @@ -122,6 +124,12 @@ public static BukkitScheduler getScheduler() { return server.getScheduler(); } + // Tsunami start - backport plugin messaging + public static Messenger getMessenger() { + return server.getMessenger(); + } + // Tsunami end + public static ServicesManager getServicesManager() { return server.getServicesManager(); } @@ -162,6 +170,12 @@ public static World getWorld(UUID uid) { return server.getWorld(uid); } + // Tsunami start - PersistentDataContainer API + public static PersistentDataContainer createPersistentDataContainer() { + return server.createPersistentDataContainer(); + } + // Tsunami end + public static MapView getMap(short id) { return server.getMap(id); } @@ -261,4 +275,11 @@ public static Set getWhitelistedPlayers() { public static void reloadWhitelist() { server.reloadWhitelist(); } + + // Tsunami start + public static boolean isPrimaryThread() { + return server.isPrimaryThread(); + } + // Tsunami end + } diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java index f45a2c9..41bfcae 100644 --- a/src/main/java/org/bukkit/Chunk.java +++ b/src/main/java/org/bukkit/Chunk.java @@ -3,11 +3,12 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.entity.Entity; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a chunk of blocks */ -public interface Chunk { +public interface Chunk extends PersistentDataHolder { // Tsunami - extends PersistentDataHolder /** * Gets the X-coordinate of this chunk diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java index 3f62950..349ecb7 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -7,8 +7,11 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.Recipe; import org.bukkit.map.MapView; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.scheduler.BukkitScheduler; import java.util.List; @@ -20,7 +23,7 @@ /** * Represents a server implementation */ -public interface Server { +public interface Server extends PluginMessageRecipient { // Tsunami - extends PluginMessageRecipient /** @@ -221,6 +224,15 @@ public interface Server { */ public BukkitScheduler getScheduler(); + // Tsunami start - backport plugin messaging + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return Messenger responsible for this server. + */ + public Messenger getMessenger(); + // Tsunami end + /** * Gets a services manager * @@ -316,6 +328,15 @@ public interface Server { * @return World with the given Unique ID, or null if none exists. */ public World getWorld(UUID uid); + + // Tsunami start - PersistentDataContainer API + /** + * Creates a new empty {@link PersistentDataContainer}. + * + * @return a new {@link PersistentDataContainer} + */ + public PersistentDataContainer createPersistentDataContainer(); + // Tsunami end /** * Gets the map from the given item ID. @@ -468,4 +489,15 @@ public interface Server { */ public Set getBannedPlayers(); + // Tsunami start + /** + * Checks the current thread against the expected primary thread for the + * server. + * + * @return true if the current thread matches the expected primary thread, + * false otherwise + */ + public boolean isPrimaryThread(); + // Tsunami end + } diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java index 0fbe9ac..88d2300 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -8,6 +8,7 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; import org.bukkit.util.Vector; import java.util.HashMap; @@ -19,7 +20,7 @@ /** * Represents a world, which may contain entities, chunks and blocks */ -public interface World extends Metadatable { // Tsunami - extends Metadatable +public interface World extends PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Gets the {@link Block} at the given coordinates diff --git a/src/main/java/org/bukkit/block/Chest.java b/src/main/java/org/bukkit/block/Chest.java index a963ec0..861d5f9 100644 --- a/src/main/java/org/bukkit/block/Chest.java +++ b/src/main/java/org/bukkit/block/Chest.java @@ -1,10 +1,11 @@ package org.bukkit.block; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a chest. * * @author sk89q */ -public interface Chest extends BlockState, ContainerBlock, Metadatable {} // Tsunami - extends Metadatable +public interface Chest extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable {} // Tsunami - extends PersistentDataHolder, Metadatable diff --git a/src/main/java/org/bukkit/block/CreatureSpawner.java b/src/main/java/org/bukkit/block/CreatureSpawner.java index ff1875b..41b9b6a 100644 --- a/src/main/java/org/bukkit/block/CreatureSpawner.java +++ b/src/main/java/org/bukkit/block/CreatureSpawner.java @@ -2,6 +2,7 @@ import org.bukkit.entity.CreatureType; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a creature spawner. @@ -9,7 +10,7 @@ * @author sk89q * @author Cogito */ -public interface CreatureSpawner extends BlockState, Metadatable { // Tsunami - extends Metadatable +public interface CreatureSpawner extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Get the spawner's creature type. diff --git a/src/main/java/org/bukkit/block/Dispenser.java b/src/main/java/org/bukkit/block/Dispenser.java index ba95845..22ac22b 100644 --- a/src/main/java/org/bukkit/block/Dispenser.java +++ b/src/main/java/org/bukkit/block/Dispenser.java @@ -1,13 +1,14 @@ package org.bukkit.block; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a dispenser. * * @author sk89q */ -public interface Dispenser extends BlockState, ContainerBlock, Metadatable { // Tsunami - extends Metadatable +public interface Dispenser extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Attempts to dispense the contents of this block
diff --git a/src/main/java/org/bukkit/block/Furnace.java b/src/main/java/org/bukkit/block/Furnace.java index ebc8670..0f10e32 100644 --- a/src/main/java/org/bukkit/block/Furnace.java +++ b/src/main/java/org/bukkit/block/Furnace.java @@ -1,13 +1,14 @@ package org.bukkit.block; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a furnace. * * @author sk89q */ -public interface Furnace extends BlockState, ContainerBlock, Metadatable { // Tsunami - extends Metadatable +public interface Furnace extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Get burn time. diff --git a/src/main/java/org/bukkit/block/NoteBlock.java b/src/main/java/org/bukkit/block/NoteBlock.java index 5f8c4ce..b085c09 100644 --- a/src/main/java/org/bukkit/block/NoteBlock.java +++ b/src/main/java/org/bukkit/block/NoteBlock.java @@ -3,11 +3,12 @@ import org.bukkit.Instrument; import org.bukkit.Note; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents a note. */ -public interface NoteBlock extends BlockState, Metadatable { // Tsunami - extends Metadatable +public interface NoteBlock extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Gets the note. diff --git a/src/main/java/org/bukkit/block/Sign.java b/src/main/java/org/bukkit/block/Sign.java index 7857fdc..65833be 100644 --- a/src/main/java/org/bukkit/block/Sign.java +++ b/src/main/java/org/bukkit/block/Sign.java @@ -1,11 +1,12 @@ package org.bukkit.block; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; /** * Represents either a SignPost or a WallSign */ -public interface Sign extends BlockState, Metadatable { // Tsunami - extends Metadatable +public interface Sign extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Gets all the lines of text currently on this sign. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index 30015ab..502b53c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -12,6 +12,7 @@ import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.entity.Entity; +import org.bukkit.persistence.PersistentDataContainer; import java.lang.ref.WeakReference; import java.util.concurrent.ConcurrentMap; @@ -223,4 +224,11 @@ public static ChunkSnapshot getEmptyChunkSnapshot(int x, int z, CraftWorld world } return new EmptyChunkSnapshot(x, z, world.getName(), world.getFullTime(), biome, biomeTemp, biomeRain); } + + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return getHandle().container; + } + // Tsunami end + } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 9405c35..78d6aed 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -21,6 +21,7 @@ import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; import org.bukkit.craftbukkit.map.CraftMapView; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.scheduler.CraftScheduler; import org.bukkit.entity.Player; import org.bukkit.event.world.WorldInitEvent; @@ -33,8 +34,11 @@ import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.permissions.Permission; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.*; import org.bukkit.plugin.java.JavaPluginLoader; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitWorker; import org.bukkit.util.config.Configuration; @@ -63,6 +67,7 @@ public final class CraftServer implements Server { private final String gameVersion = "b1.7.3"; private final ServicesManager servicesManager = new SimpleServicesManager(); private final BukkitScheduler scheduler; + private final Messenger messenger = new StandardMessenger(); // Tsunami private final SimpleCommandMap commandMap = new SimpleCommandMap(this); private final PluginManager pluginManager; protected final MinecraftServer console; @@ -71,7 +76,6 @@ public final class CraftServer implements Server { private final Configuration configuration; private final Yaml yaml = new Yaml(new SafeConstructor()); private boolean shuttingdown = false; - private final List hiddenCommands = new ArrayList<>(); //Project Poseidon - Create variable public CraftServer(MinecraftServer console, ServerConfigurationManager server) { this.console = console; @@ -347,6 +351,12 @@ public BukkitScheduler getScheduler() { return scheduler; } + // Tsunami start - backport plugin messaging + public Messenger getMessenger() { + return messenger; + } + // Tsunami end + public ServicesManager getServicesManager() { return servicesManager; } @@ -381,6 +391,23 @@ public boolean dispatchCommand(CommandSender sender, String commandLine) { return false; } + // Tsunami start - backport plugin messaging + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + StandardMessenger.validatePluginMessage(getMessenger(), source, channel, message); + for (Player player : getOnlinePlayers()) { + player.sendPluginMessage(source, channel, message); + } + } + + public Set getListeningPluginChannels() { + Set result = new HashSet<>(); + for (Player player : getOnlinePlayers()) { + result.addAll(player.getListeningPluginChannels()); + } + return result; + } + // Tsunami end + public void reload() { loadConfig(); PropertyManager config = new PropertyManager(console.options); @@ -641,6 +668,12 @@ public void addWorld(World world) { worlds.put(world.getName().toLowerCase(), world); } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer createPersistentDataContainer() { + return new CraftPersistentDataContainer(); + } + // Tsunami end + public Logger getLogger() { return MinecraftServer.log; } @@ -862,6 +895,12 @@ public boolean isShuttingdown() { return shuttingdown; } + // Tsunami start + public boolean isPrimaryThread() { + return console.isPrimaryThread(); + } + // Tsunami end + public void setShuttingdown(boolean shuttingdown) { this.shuttingdown = shuttingdown; } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 9fdcc15..0ebeb34 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -20,6 +20,7 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; @@ -849,6 +850,12 @@ public void setKeepSpawnInMemory(boolean keepLoaded) { } } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return getHandle().worldData.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java index a4f4abf..d11265b 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java @@ -8,6 +8,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.inventory.Inventory; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftChest extends CraftBlockState implements Chest { @@ -36,6 +37,12 @@ public boolean update(boolean force) { return result; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.chest.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java index c552712..83e012e 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java @@ -7,6 +7,7 @@ import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.CreatureType; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftCreatureSpawner extends CraftBlockState implements CreatureSpawner { @@ -49,6 +50,12 @@ public void setDelay(int delay) { spawner.spawnDelay = delay; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.spawner.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java index aa02257..a41b69b 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java @@ -10,6 +10,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.inventory.Inventory; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import java.util.Random; @@ -55,6 +56,12 @@ public boolean update(boolean force) { return result; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.dispenser.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java index caafaf3..86b311c 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java @@ -8,6 +8,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.inventory.Inventory; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftFurnace extends CraftBlockState implements Furnace { @@ -52,6 +53,12 @@ public void setCookTime(short cookTime) { furnace.cookTime = cookTime; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.furnace.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java index 8fae74c..0cf6b1b 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java @@ -9,6 +9,7 @@ import org.bukkit.block.NoteBlock; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftNoteBlock extends CraftBlockState implements NoteBlock { @@ -77,6 +78,12 @@ public boolean play(Instrument instrument, Note note) { } } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.note.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java index 85174e7..cad29fc 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java @@ -6,6 +6,7 @@ import org.bukkit.block.Sign; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; public class CraftSign extends CraftBlockState implements Sign { @@ -42,6 +43,12 @@ public boolean update(boolean force) { return result; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return this.sign.container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index e59e79c..0334ffc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -11,6 +11,7 @@ import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; @@ -282,6 +283,12 @@ private static CraftPlayer getPlayer(EntityPlayer entity) { return result; } + // Tsunami start - PersistentDataContainer API + public PersistentDataContainer getPersistentDataContainer() { + return getHandle().container; + } + // Tsunami end + // Tsunami start public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) { Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null"); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 6f5688f..5813d6e 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import com.google.common.collect.ImmutableSet; import com.projectposeidon.ConnectionType; import net.minecraft.server.*; import org.bukkit.Achievement; @@ -11,17 +12,26 @@ import org.bukkit.craftbukkit.map.CraftMapView; import org.bukkit.craftbukkit.map.RenderData; import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRegisterChannelEvent; import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerUnregisterChannelEvent; import org.bukkit.map.MapView; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.StandardMessenger; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; import java.util.UUID; +import java.util.logging.Level; public class CraftPlayer extends CraftHumanEntity implements Player { private Set hiddenPlayers = new HashSet(); + private final Set channels = new HashSet<>(); // Tsunami public CraftPlayer(CraftServer server, EntityPlayer entity) { super(server, entity); @@ -107,6 +117,49 @@ public void sendMessage(String message) { this.sendRawMessage(message); } + // Tsunami start - backport plugin messaging + public void addChannel(String channel) { + if (this.channels.add(channel)) { + this.server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); + } + } + + public void removeChannel(String channel) { + if (this.channels.remove(channel)) { + this.server.getPluginManager().callEvent(new PlayerUnregisterChannelEvent(this, channel)); + } + } + + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + StandardMessenger.validatePluginMessage(this.server.getMessenger(), source, channel, message); + if (this.channels.contains(channel)) { + getHandle().netServerHandler.sendPacket(new Packet250PluginMessage(channel, message)); + } + } + + public Set getListeningPluginChannels() { + return ImmutableSet.copyOf(this.channels); + } + + public void sendSupportedChannels() { + Set listening = this.server.getMessenger().getIncomingChannels(); + if (!listening.isEmpty()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + for (String channel : listening) { + try { + out.write(channel.getBytes(StandardCharsets.UTF_8)); + out.write((byte) 0); + } catch (IOException e) { + MinecraftServer.log.log(Level.SEVERE, "Failed to send plugin channel REGISTER to " + getName(), e); + } + } + + getHandle().netServerHandler.sendPacket(new Packet250PluginMessage("REGISTER", out.toByteArray())); + } + } + // Tsunami end + public String getDisplayName() { return getHandle().displayName; } diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java new file mode 100644 index 0000000..492000f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java @@ -0,0 +1,110 @@ +package org.bukkit.craftbukkit.persistence; + +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagCompound; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class CraftPersistentDataContainer implements PersistentDataContainer { + + public static final String TAG_KEY = "PersistentDataContainer"; + private static final PersistentDataTypeRegistry REGISTRY = new PersistentDataTypeRegistry(); + + private final NBTTagCompound compound; + + public CraftPersistentDataContainer() { + this.compound = new NBTTagCompound(); + } + + public CraftPersistentDataContainer(NBTTagCompound compound) { + this.compound = compound; + } + + @Override + public void set(String key, PersistentDataType type, C value) { + Objects.requireNonNull(key, "key must not be null"); + Objects.requireNonNull(type, "type must not be null"); + Objects.requireNonNull(value, "value must not be null"); + + NBTBase tag = REGISTRY.getOrCreateAdapter(type).serialize(type.toPrimitive(value)); + this.compound.a(key, tag); + } + + @Override + public void remove(String key) { + Objects.requireNonNull(key, "key must not be null"); + + this.compound.a.remove(key); + } + + @Override + public boolean has(String key, PersistentDataType type) { + Objects.requireNonNull(key, "key must not be null"); + Objects.requireNonNull(type, "type must not be null"); + + NBTBase tag = (NBTBase) this.compound.a.get(key); + return tag != null && REGISTRY.getOrCreateAdapter(type).matches(tag); + } + + @Override + public boolean has(String key) { + Objects.requireNonNull(key, "key must not be null"); + + return this.compound.a.containsKey(key); + } + + @Override + public C get(String key, PersistentDataType type) { + Objects.requireNonNull(key, "key must not be null"); + Objects.requireNonNull(type, "type must not be null"); + + NBTBase tag = (NBTBase) this.compound.a.get(key); + if (tag == null) { + return null; + } + P primitive = REGISTRY.getOrCreateAdapter(type).deserialize(tag); + return type.fromPrimitive(primitive); + } + + @Override + public C getOrDefault(String key, PersistentDataType type, C defaultValue) { + Objects.requireNonNull(key, "key must not be null"); + Objects.requireNonNull(type, "type must not be null"); + Objects.requireNonNull(defaultValue, "defaultValue must not be null"); + + C value = get(key, type); + return value != null ? value : defaultValue; + } + + @Override + public Set getKeys() { + return Collections.unmodifiableSet(new HashSet<>(this.compound.a.keySet())); + } + + @Override + public boolean isEmpty() { + return this.compound.a.isEmpty(); + } + + @Override + public void copyTo(PersistentDataContainer other, boolean replace) { + Objects.requireNonNull(other, "other must not be null"); + + CraftPersistentDataContainer target = (CraftPersistentDataContainer) other; + if (replace) { + target.asCompound().a.putAll(this.compound.a); + } else { + this.compound.a.forEach(target.asCompound().a::putIfAbsent); + } + } + + public NBTTagCompound asCompound() { + return this.compound; + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java new file mode 100644 index 0000000..e8d8569 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java @@ -0,0 +1,97 @@ +package org.bukkit.craftbukkit.persistence; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagByte; +import net.minecraft.server.NBTTagByteArray; +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagDouble; +import net.minecraft.server.NBTTagFloat; +import net.minecraft.server.NBTTagInt; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagLong; +import net.minecraft.server.NBTTagShort; +import net.minecraft.server.NBTTagString; +import org.bukkit.persistence.ListPersistentDataType; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class PersistentDataTypeRegistry { + + private final Map, PrimitiveToTagAdapter> adapters = new HashMap<>(); + + public synchronized PrimitiveToTagAdapter getOrCreateAdapter(PersistentDataType type) { + PrimitiveToTagAdapter adapter = (PrimitiveToTagAdapter) this.adapters.get(type); + if (adapter == null) { + adapter = createAdapter(type); + this.adapters.put(type, adapter); + } + return adapter; + } + + private

PrimitiveToTagAdapter createAdapter(PersistentDataType type) { + if (Byte.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagByte::new, tag -> tag.a, tag -> tag instanceof NBTTagByte + ); + } else if (Short.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagShort::new, tag -> tag.a, tag -> tag instanceof NBTTagShort + ); + } else if (Integer.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagInt::new, tag -> tag.a, tag -> tag instanceof NBTTagInt + ); + } else if (Long.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagLong::new, tag -> tag.a, tag -> tag instanceof NBTTagLong + ); + } else if (Float.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagFloat::new, tag -> tag.a, tag -> tag instanceof NBTTagFloat + ); + } else if (Double.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagDouble::new, tag -> tag.a, tag -> tag instanceof NBTTagDouble + ); + } else if (String.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagString::new, tag -> tag.a, tag -> tag instanceof NBTTagString + ); + } else if (byte[].class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + NBTTagByteArray::new, tag -> tag.a, tag -> tag instanceof NBTTagByteArray + ); + } else if (PersistentDataContainer.class.equals(type.getPrimitiveType())) { + return new PrimitiveToTagAdapter( + CraftPersistentDataContainer::asCompound, CraftPersistentDataContainer::new, tag -> tag instanceof NBTTagCompound + ); + } else if (List.class.equals(type.getPrimitiveType())) { + Preconditions.checkArgument(type instanceof ListPersistentDataType, "type must be a ListPersistentDataType"); + ListPersistentDataType listType = (ListPersistentDataType) type; + PrimitiveToTagAdapter elementAdapter = getOrCreateAdapter(listType.getElementType()); + + return new PrimitiveToTagAdapter( + list -> { + NBTTagList tag = new NBTTagList(); + list.forEach(p -> tag.a(elementAdapter.serialize(p))); + return tag; + }, + tag -> Lists.transform(tag.a, e -> { + Preconditions.checkState(elementAdapter.matches((NBTBase) e)); + return elementAdapter.deserialize((NBTBase) e); + }), + tag -> tag instanceof NBTTagList + ); + } else { + throw new IllegalArgumentException("illegal primitive type " + type.getClass().getName()); + } + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java b/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java new file mode 100644 index 0000000..966b126 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit.persistence; + +import net.minecraft.server.NBTBase; + +import java.util.function.Function; +import java.util.function.Predicate; + +public class PrimitiveToTagAdapter { + + private final Function serializer; + private final Function deserializer; + private final Predicate matcher; + + public PrimitiveToTagAdapter(Function serializer, Function deserializer, Predicate matcher) { + this.serializer = serializer; + this.deserializer = deserializer; + this.matcher = matcher; + } + + public T serialize(P primitive) { + return this.serializer.apply(primitive); + } + + public P deserialize(T tag) { + return this.deserializer.apply(tag); + } + + public boolean matches(T tag) { + return this.matcher.test(tag); + } + +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java index ffb01f3..1afdf01 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java +++ b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java @@ -52,7 +52,7 @@ public String format(LogRecord record) { builder.append(record.getLevel().getLocalizedName().toUpperCase()); builder.append("] "); builder.append(formattedMessage); - builder.append('\n'); + // Tsunami - removed builder.append('\n'); if (ex != null) { StringWriter writer = new StringWriter(); diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java index c307f5e..9793609 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -5,6 +5,7 @@ import org.bukkit.World; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.metadata.Metadatable; +import org.bukkit.persistence.PersistentDataHolder; import org.bukkit.util.Vector; import java.util.List; @@ -13,7 +14,7 @@ /** * Represents a base entity in the world */ -public interface Entity extends Metadatable { // Tsunami - extends Metadatable +public interface Entity extends PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable /** * Gets the entity's current position diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java index 9e3e591..da3061a 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -5,6 +5,7 @@ import org.bukkit.*; import org.bukkit.command.CommandSender; import org.bukkit.map.MapView; +import org.bukkit.plugin.messaging.PluginMessageRecipient; import java.net.InetSocketAddress; import java.util.UUID; @@ -12,7 +13,7 @@ /** * Represents a player, connected or not */ -public interface Player extends HumanEntity, CommandSender, OfflinePlayer { +public interface Player extends HumanEntity, CommandSender, OfflinePlayer, PluginMessageRecipient { // Tsunami - extends PluginMessageRecipient /** * Gets the "friendly" name to display of this player. This may include color. *

diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java index 3d73d80..3c3607d 100644 --- a/src/main/java/org/bukkit/event/Event.java +++ b/src/main/java/org/bukkit/event/Event.java @@ -337,6 +337,20 @@ public enum Type { * @see org.bukkit.event.player.PlayerItemDamageEvent */ PLAYER_ITEM_DAMAGE(Category.PLAYER), + // Tsunami start - backport plugin messaging + /** + * Called when a player registers for a plugin channel + * + * @see org.bukkit.event.player.PlayerRegisterChannelEvent + */ + PLAYER_REGISTER_CHANNEL(Category.PLAYER), + /** + * Called when a player unregisters for a plugin channel + * + * @see org.bukkit.event.player.PlayerUnregisterChannelEvent + */ + PLAYER_UNREGISTER_CHANNEL(Category.PLAYER), + // Tsunami end /** * BLOCK EVENTS diff --git a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java index b1e21f9..57267fe 100644 --- a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java +++ b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java @@ -1,14 +1,84 @@ package org.bukkit.event.entity; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; /** - * Called when a projectile hits an object + * Called when a projectile hits a block or an entity */ -public class ProjectileHitEvent extends EntityEvent { +public class ProjectileHitEvent extends EntityEvent implements Cancellable { // Tsunami - implements Cancellable - public ProjectileHitEvent(Projectile projectile) { + // Tsunami start - improve ProjectileHitEvent + private final Projectile projectile; + private final Entity hitEntity; + private final Block hitBlock; + private final BlockFace hitFace; + private boolean cancelled = false; + + public ProjectileHitEvent(Projectile projectile, Entity hitEntity) { + super(Type.PROJECTILE_HIT, projectile); + this.projectile = projectile; + this.hitEntity = hitEntity; + this.hitBlock = null; + this.hitFace = null; + } + + public ProjectileHitEvent(Projectile projectile, Block hitBlock, BlockFace hitFace) { super(Type.PROJECTILE_HIT, projectile); + this.projectile = projectile; + this.hitBlock = hitBlock; + this.hitFace = hitFace; + this.hitEntity = null; + } + + /** + * Gets the projectile involved in this event + * + * @return the projectile + */ + public Projectile getProjectile() { + return this.projectile; + } + + /** + * Gets the entity that was hit, if it was an entity that was hit + * + * @return hit entity or else {@code null} + */ + public Entity getHitEntity() { + return this.hitEntity; + } + + /** + * Gets the block that was hit, if it was a block that was hit + * + * @return hit block or else {@code null} + */ + public Block getHitBlock() { + return this.hitBlock; + } + + /** + * Gets the block face that was hit, if it was a block that was hit + * + * @return hit face or else {@code null} + */ + public BlockFace getHitBlockFace() { + return this.hitFace; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; } + // Tsunami end } diff --git a/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java new file mode 100644 index 0000000..1036e5e --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java @@ -0,0 +1,20 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; + +/** + * This event is called after a player registers or unregisters a new plugin + * channel. + */ +public abstract class PlayerChannelEvent extends PlayerEvent { + private final String channel; + + public PlayerChannelEvent(Type type, Player player, String channel) { + super(type, player); + this.channel = channel; + } + + public final String getChannel() { + return channel; + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/player/PlayerListener.java b/src/main/java/org/bukkit/event/player/PlayerListener.java index b7fce6c..ee90895 100644 --- a/src/main/java/org/bukkit/event/player/PlayerListener.java +++ b/src/main/java/org/bukkit/event/player/PlayerListener.java @@ -205,4 +205,20 @@ public void onPlayerFish(PlayerFishEvent event) {} * @param event Relevant event details */ public void onPlayerItemDamage(PlayerItemDamageEvent event) {} + + // Tsunami start - backport plugin messaging + /** + * Called when a player registers for a plugin channel + * + * @param event Relevant event details + */ + public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) {} + + /** + * Called when a player unregisters for a plugin channel + * + * @param event Relevant event details + */ + public void onPlayerUnregisterChannel(PlayerUnregisterChannelEvent event) {} + // Tsunami end } diff --git a/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java new file mode 100644 index 0000000..26a8275 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java @@ -0,0 +1,13 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; + +/** + * This is called immediately after a player registers for a plugin channel. + */ +public class PlayerRegisterChannelEvent extends PlayerChannelEvent { + + public PlayerRegisterChannelEvent(Player player, String channel) { + super(Type.PLAYER_REGISTER_CHANNEL, player, channel); + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java index 727041c..062716b 100644 --- a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java +++ b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java @@ -22,7 +22,8 @@ public PlayerTeleportEvent(final Event.Type type, Player player, Location from, //Poseidon - Start private void blockCrossDimensionDupe() { - if (this.getFrom().getWorld() != this.getTo().getWorld()) { + // Tsunami - fix NPE + if (this.getTo() != null && this.getFrom().getWorld() != this.getTo().getWorld()) { EntityPlayer entity = ((CraftPlayer) this.getPlayer()).getHandle(); if (entity.activeContainer == entity.defaultContainer) return; diff --git a/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java new file mode 100644 index 0000000..5ec9029 --- /dev/null +++ b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java @@ -0,0 +1,13 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; + +/** + * This is called immediately after a player unregisters for a plugin channel. + */ +public class PlayerUnregisterChannelEvent extends PlayerChannelEvent { + + public PlayerUnregisterChannelEvent(Player player, String channel) { + super(Type.PLAYER_UNREGISTER_CHANNEL, player, channel); + } +} \ No newline at end of file diff --git a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java index 719fac1..2df84be 100644 --- a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#BYTE + */ +@Deprecated public class ByteMetadataValue extends MetadataValue { public ByteMetadataValue(byte value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java index ddb67ae..bcadeec 100644 --- a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#DOUBLE + */ +@Deprecated public class DoubleMetadataValue extends MetadataValue { public DoubleMetadataValue(double value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java index 428d4af..dd2e6b0 100644 --- a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#FLOAT + */ +@Deprecated public class FloatMetadataValue extends MetadataValue { public FloatMetadataValue(float value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/IntMetadataValue.java b/src/main/java/org/bukkit/metadata/IntMetadataValue.java index 9738544..a51f06e 100644 --- a/src/main/java/org/bukkit/metadata/IntMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/IntMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#INTEGER + */ +@Deprecated public class IntMetadataValue extends MetadataValue { public IntMetadataValue(int value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/LongMetadataValue.java b/src/main/java/org/bukkit/metadata/LongMetadataValue.java index cc3a685..b4e80e3 100644 --- a/src/main/java/org/bukkit/metadata/LongMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/LongMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#LONG + */ +@Deprecated public class LongMetadataValue extends MetadataValue { public LongMetadataValue(long value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java index d85d566..7be3bda 100644 --- a/src/main/java/org/bukkit/metadata/MetadataValue.java +++ b/src/main/java/org/bukkit/metadata/MetadataValue.java @@ -2,7 +2,11 @@ /** * Represents a metadata value of a {@link Metadatable} object + * + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType */ +@Deprecated public abstract class MetadataValue { private final Object value; diff --git a/src/main/java/org/bukkit/metadata/Metadatable.java b/src/main/java/org/bukkit/metadata/Metadatable.java index 23c2be4..26d5e1e 100644 --- a/src/main/java/org/bukkit/metadata/Metadatable.java +++ b/src/main/java/org/bukkit/metadata/Metadatable.java @@ -4,7 +4,11 @@ /** * Represents an object that can provide metadata about itself + * + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataContainer */ +@Deprecated public interface Metadatable { /** @@ -13,7 +17,9 @@ public interface Metadatable { * @param owningPlugin the plugin owning the metadata * @param key the unique identifier for the metadata * @param value the metadata value + * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#set(String, org.bukkit.persistence.PersistentDataType, Object)} */ + @Deprecated void setMetadata(Plugin owningPlugin, String key, MetadataValue value); /** @@ -21,7 +27,9 @@ public interface Metadatable { * * @param owningPlugin the plugin owning the metadata * @param key the unique identifier for the metadata + * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#remove(String)} */ + @Deprecated void removeMetadata(Plugin owningPlugin, String key); /** @@ -30,7 +38,9 @@ public interface Metadatable { * @param owningPlugin the plugin owning the metadata * @param key the unique identifier for the metadata * @return the metadata value, or null if it does not exist + * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#get(String, org.bukkit.persistence.PersistentDataType)} */ + @Deprecated MetadataValue getMetadata(Plugin owningPlugin, String key); /** @@ -39,7 +49,9 @@ public interface Metadatable { * @param owningPlugin the plugin owning the metadata * @param key the unique identifier for the metadata * @return true if the metadata exists, false if not + * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#has(String)} */ + @Deprecated boolean hasMetadata(Plugin owningPlugin, String key); } diff --git a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java index 440207e..1dc5d4d 100644 --- a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#SHORT + */ +@Deprecated public class ShortMetadataValue extends MetadataValue { public ShortMetadataValue(short value) { super(value); diff --git a/src/main/java/org/bukkit/metadata/StringMetadataValue.java b/src/main/java/org/bukkit/metadata/StringMetadataValue.java index 4f19d83..2fef59a 100644 --- a/src/main/java/org/bukkit/metadata/StringMetadataValue.java +++ b/src/main/java/org/bukkit/metadata/StringMetadataValue.java @@ -1,5 +1,10 @@ package org.bukkit.metadata; +/** + * @deprecated This API has been superseded by {@link org.bukkit.persistence}. + * @see org.bukkit.persistence.PersistentDataType#STRING + */ +@Deprecated public class StringMetadataValue extends MetadataValue { public StringMetadataValue(String value) { super(value); diff --git a/src/main/java/org/bukkit/persistence/ListPersistentDataType.java b/src/main/java/org/bukkit/persistence/ListPersistentDataType.java new file mode 100644 index 0000000..eccd851 --- /dev/null +++ b/src/main/java/org/bukkit/persistence/ListPersistentDataType.java @@ -0,0 +1,85 @@ +package org.bukkit.persistence; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a data type which is used to convert a list of complex values + * to a list of primitive values, and vice versa. This is used by {@link PersistentDataContainer} + * for storage and retrieval of values of type {@link List}. + *

+ * Allowed primitive types are {@code Byte}, {@code Short}, {@code Integer}, + * {@code Long}, {@code Float}, {@code Double}, {@code String}, {@code byte[]} + * and {@code PersistentDataContainer}. + * + * @see PersistentDataContainer + * @param

the primitive element type + * @param the complex element type + */ +public interface ListPersistentDataType extends PersistentDataType, List> { + + ListPersistentDataType BYTE = listTypeFrom(PersistentDataType.BYTE); + ListPersistentDataType SHORT = listTypeFrom(PersistentDataType.SHORT); + ListPersistentDataType INTEGER = listTypeFrom(PersistentDataType.INTEGER); + ListPersistentDataType LONG = listTypeFrom(PersistentDataType.LONG); + ListPersistentDataType FLOAT = listTypeFrom(PersistentDataType.FLOAT); + ListPersistentDataType DOUBLE = listTypeFrom(PersistentDataType.DOUBLE); + ListPersistentDataType BOOLEAN = listTypeFrom(PersistentDataType.BOOLEAN); + ListPersistentDataType CHARACTER = listTypeFrom(PersistentDataType.CHARACTER); + ListPersistentDataType STRING = listTypeFrom(PersistentDataType.STRING); + ListPersistentDataType BYTE_ARRAY = listTypeFrom(PersistentDataType.BYTE_ARRAY); + ListPersistentDataType DATA_CONTAINER = listTypeFrom(PersistentDataType.DATA_CONTAINER); + + /** + * Returns the data type which elements of a list of this type conform to. + * + * @return the element data type + */ + PersistentDataType getElementType(); + + /** + * Creates a {@link ListPersistentDataType} from the specified element data type. + * + * @param type the element data type + * @return a new list data type + */ + static ListPersistentDataType listTypeFrom(PersistentDataType type) { + return new ListPersistentDataTypeImpl<>(type); + } + + final class ListPersistentDataTypeImpl implements ListPersistentDataType { + private final PersistentDataType elementType; + + ListPersistentDataTypeImpl(PersistentDataType elementType) { + this.elementType = elementType; + } + + @Override + public Class> getPrimitiveType() { + return (Class>) (Object) List.class; + } + + @Override + public Class> getComplexType() { + return (Class>) (Object) List.class; + } + + @Override + public List

toPrimitive(List complex) { + return complex.stream().map(this.elementType::toPrimitive).collect(Collectors.toList()); + } + + @Override + public List fromPrimitive(List

primitive) { + return Lists.transform(primitive, this.elementType::fromPrimitive); + } + + @Override + public PersistentDataType getElementType() { + return this.elementType; + } + } + +} diff --git a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java new file mode 100644 index 0000000..cdc91a2 --- /dev/null +++ b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java @@ -0,0 +1,104 @@ +package org.bukkit.persistence; + +import java.util.Set; + +/** + * Represents a container which stores persistent data using key-value mappings. + *

+ * A container can store simple values, but it can also store lists of values + * and even other containers, enabling complex and nested data structures + * to be stored. + *

+ * A distinction is made between primitive and complex types: when storing a + * value, a {@link PersistentDataType} must be provided in order to convert + * the complex value into a primitive value. When retrieving a value, + * the primitive value is converted back to a complex value. + * + * @see PersistentDataType + */ +public interface PersistentDataContainer { + + /** + * Stores a new key-value mapping in this container, or replaces the value + * if a mapping with the specified key is already present. + * + * @param key the unique key of the mapping + * @param type the {@link PersistentDataType} used to convert + * the complex value to a primitive value + * @param value the complex value + */ + void set(String key, PersistentDataType type, C value); + + /** + * Removes a key-value mapping from this container if it is present. + * + * @param key the unique key of the mapping to remove + */ + void remove(String key); + + /** + * Tests if a key-value mapping with the specified key and a value conforming + * to the {@link PersistentDataType} is present in this container. + * + * @param key the unique key of the mapping to test for + * @param type the {@link PersistentDataType} which the value should conform to + * @return {@code true} if a key-value mapping with this key and whose value + * conforms to the type exists + */ + boolean has(String key, PersistentDataType type); + + /** + * Tests if a key-value mapping with the specified key is present in this container. + * + * @param key the unique key of the mapping to test for + * @return {@code true} if a key-value mapping with this key exists + */ + boolean has(String key); + + /** + * Retrieves a value from a key-value mapping present in this container. + * + * @param key the unique key of the mapping + * @param type the {@link PersistentDataType} used to convert + * the primitive value to a complex value + * @return the value associated with this key, or {@code null} + * if no mapping with this key exists + */ + C get(String key, PersistentDataType type); + + /** + * Retrieves a value from a key-value mapping present in this container, + * or returns the specified default value if no such mapping exists. + * + * @param key the unique key of the mapping + * @param type the {@link PersistentDataType} used to convert + * the primitive value to a complex value + * @param defaultValue the value to return if the mapping is not present + * @return the value associated with this key, or {@code defaultValue} + * if no mapping with this key exists + */ + C getOrDefault(String key, PersistentDataType type, C defaultValue); + + /** + * Returns a copy of the keys of all mappings present in this container. + * + * @return the keys of all mappings in this container + */ + Set getKeys(); + + /** + * Tests if this container holds no key-value mappings. + * + * @return {@code true} if no mappings are present in this container + */ + boolean isEmpty(); + + /** + * Copies all key-value mappings present in this container to another container. + * + * @param other the container to copy this container's mappings to + * @param replace if mappings from this container should replace mappings + * which are already present in the other container + */ + void copyTo(PersistentDataContainer other, boolean replace); +} diff --git a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java new file mode 100644 index 0000000..85aee7c --- /dev/null +++ b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java @@ -0,0 +1,17 @@ +package org.bukkit.persistence; + +/** + * Represents an object which is able to store persistent data. + * + * @see PersistentDataContainer + */ +public interface PersistentDataHolder { + + /** + * Returns the {@link PersistentDataContainer} which holds all persistent data + * stored by this object. + * + * @return this object's {@link PersistentDataContainer} + */ + PersistentDataContainer getPersistentDataContainer(); +} diff --git a/src/main/java/org/bukkit/persistence/PersistentDataType.java b/src/main/java/org/bukkit/persistence/PersistentDataType.java new file mode 100644 index 0000000..25e0c1f --- /dev/null +++ b/src/main/java/org/bukkit/persistence/PersistentDataType.java @@ -0,0 +1,134 @@ +package org.bukkit.persistence; + +/** + * Represents a data type which is used to convert a complex value to a + * primitive value, and vice versa. This is used by {@link PersistentDataContainer} + * for storage and retrieval of values. + *

+ * Allowed primitive types are {@code Byte}, {@code Short}, {@code Integer}, + * {@code Long}, {@code Float}, {@code Double}, {@code String}, {@code byte[]} + * and {@code PersistentDataContainer}. + * + * @see PersistentDataContainer + * @param

the primitive type + * @param the complex type + */ +public interface PersistentDataType { + + PersistentDataType BYTE = new PrimitivePersistentDataType<>(Byte.class); + PersistentDataType SHORT = new PrimitivePersistentDataType<>(Short.class); + PersistentDataType INTEGER = new PrimitivePersistentDataType<>(Integer.class); + PersistentDataType LONG = new PrimitivePersistentDataType<>(Long.class); + PersistentDataType FLOAT = new PrimitivePersistentDataType<>(Float.class); + PersistentDataType DOUBLE = new PrimitivePersistentDataType<>(Double.class); + PersistentDataType BOOLEAN = new BooleanPersistentDataType(); + PersistentDataType CHARACTER = new CharacterPersistentDataType(); + PersistentDataType STRING = new PrimitivePersistentDataType<>(String.class); + PersistentDataType BYTE_ARRAY = new PrimitivePersistentDataType<>(byte[].class); + PersistentDataType DATA_CONTAINER = new PrimitivePersistentDataType<>(PersistentDataContainer.class); + + /** + * Returns the primitive type of a value of this data type. + * + * @return the primitive type + */ + Class

getPrimitiveType(); + + /** + * Returns the complex type of a value of this data type. + * + * @return the complex type + */ + Class getComplexType(); + + /** + * Converts the given complex value to a primitive value. + * + * @param complex the complex value + * @return the primitive value + */ + P toPrimitive(C complex); + + /** + * Converts the given primitive value to a complex value. + * + * @param primitive the primitive value + * @return the complex value + */ + C fromPrimitive(P primitive); + + final class PrimitivePersistentDataType

implements PersistentDataType { + private final Class

primitiveType; + + PrimitivePersistentDataType(Class

primitiveType) { + this.primitiveType = primitiveType; + } + + @Override + public Class

getPrimitiveType() { + return this.primitiveType; + } + + @Override + public Class

getComplexType() { + return this.primitiveType; + } + + @Override + public P toPrimitive(P complex) { + return complex; + } + + @Override + public P fromPrimitive(P primitive) { + return primitive; + } + } + + final class BooleanPersistentDataType implements PersistentDataType { + + @Override + public Class getPrimitiveType() { + return Byte.class; + } + + @Override + public Class getComplexType() { + return Boolean.class; + } + + @Override + public Byte toPrimitive(Boolean complex) { + return (byte) (complex ? 1 : 0); + } + + @Override + public Boolean fromPrimitive(Byte primitive) { + return primitive != 0; + } + } + + final class CharacterPersistentDataType implements PersistentDataType { + + @Override + public Class getPrimitiveType() { + return Short.class; + } + + @Override + public Class getComplexType() { + return Character.class; + } + + @Override + public Short toPrimitive(Character complex) { + return (short) complex.charValue(); + } + + @Override + public Character fromPrimitive(Short primitive) { + return (char) primitive.shortValue(); + } + } + +} diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index 6ff0e70..896bd1c 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -620,11 +620,31 @@ public void execute(Listener listener, Event event) } }; case PLAYER_CHANGED_WORLD: - return new EventExecutor() { - public void execute(Listener listener, Event event) { + return new EventExecutor() + { + public void execute(Listener listener, Event event) + { ((PlayerListener) listener).onPlayerChangedWorld((PlayerChangedWorldEvent) event); } }; + // Tsunami start - backport plugin messaging + case PLAYER_REGISTER_CHANNEL: + return new EventExecutor() + { + public void execute(Listener listener, Event event) + { + ((PlayerListener) listener).onPlayerRegisterChannel((PlayerRegisterChannelEvent) event); + } + }; + case PLAYER_UNREGISTER_CHANNEL: + return new EventExecutor() + { + public void execute(Listener listener, Event event) + { + ((PlayerListener) listener).onPlayerUnregisterChannel((PlayerUnregisterChannelEvent) event); + } + }; + // Tsunami end // Block Events case BLOCK_PHYSICS: diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java new file mode 100644 index 0000000..2ee5403 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java @@ -0,0 +1,11 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Channel is too long. + */ +public class ChannelNameTooLongException extends RuntimeException { + + public ChannelNameTooLongException(String channel) { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars (attempted " + channel.length() + " - '" + channel + "."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java new file mode 100644 index 0000000..56f2e2a --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java @@ -0,0 +1,11 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin attempts to send a message on an unregistered channel. + */ +public class ChannelNotRegisteredException extends RuntimeException { + + public ChannelNotRegisteredException(String channel) { + super("Attempted to send a plugin message through an unregistered channel ('" + channel + "'."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java new file mode 100644 index 0000000..93765fa --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java @@ -0,0 +1,19 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Message is sent that is too large to be sent. + */ +public class MessageTooLargeException extends RuntimeException { + + public MessageTooLargeException(byte[] message) { + this(message.length); + } + + public MessageTooLargeException(int length) { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes (tried to send one that is " + length + " bytes long)."); + } + + public MessageTooLargeException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/src/main/java/org/bukkit/plugin/messaging/Messenger.java new file mode 100644 index 0000000..8d4480b --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/Messenger.java @@ -0,0 +1,201 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * A class responsible for managing the registrations of plugin channels and their + * listeners. + */ +public interface Messenger { + /** + * Represents the largest size that an individual Plugin Message may be. + */ + public static final int MAX_MESSAGE_SIZE = 32766; + + /** + * Represents the largest size that a Plugin Channel may be. + */ + public static final int MAX_CHANNEL_SIZE = 64; + + /** + * Checks if the specified channel is a reserved name. + * + * @param channel Channel name to check. + * @return True if the channel is reserved, otherwise false. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public boolean isReservedChannel(String channel); + + /** + * Registers the specific plugin to the requested outgoing plugin channel, allowing it + * to send messages through that channel to any clients. + * + * @param plugin Plugin that wishes to send messages through the channel. + * @param channel Channel to register. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void registerOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from the requested outgoing plugin channel, no longer + * allowing it to send messages through that channel to any clients. + * + * @param plugin Plugin that no longer wishes to send messages through the channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from all outgoing plugin channels, no longer allowing + * it to send any plugin messages. + * + * @param plugin Plugin that no longer wishes to send plugin messages. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin); + + /** + * Registers the specific plugin for listening on the requested incoming plugin channel, + * allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to register to this channel. + * @param channel Channel to register. + * @param listener Listener to receive messages on. + * @returns The resulting registration that was made as a result of this method. + * @throws IllegalArgumentException Thrown if plugin, channel or listener is null, or the listener is already registered for this channel. + */ + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin's listener from listening on the requested incoming plugin channel, + * no longer allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @param listener Listener to stop receiving messages on. + * @throws IllegalArgumentException Thrown if plugin, channel or listener is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin from listening on the requested incoming plugin channel, + * no longer allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from listening on all plugin channels through all listeners. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin); + + /** + * Gets a set containing all the outgoing plugin channels. + * + * @return List of all registered outgoing plugin channels. + */ + public Set getOutgoingChannels(); + + /** + * Gets a set containing all the outgoing plugin channels that the specified plugin is registered to. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered outgoing plugin channels that a plugin is registered to. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getOutgoingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channels. + * + * @return List of all registered incoming plugin channels. + */ + public Set getIncomingChannels(); + + /** + * Gets a set containing all the incoming plugin channels that the specified plugin is registered for. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered incoming plugin channels that the plugin is registered for. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getIncomingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations that the specified plugin has. + * + * @param plugin Plugin to retrieve registrations for. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set getIncomingChannelRegistrations(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations that are on the requested channel. + * + * @param channel Channel to retrieve registrations for. + * @return List of all registrations that are on the channel. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public Set getIncomingChannelRegistrations(String channel); + + /** + * Gets a set containing all the incoming plugin channel registrations that the specified plugin has + * on the requested channel. + * + * @param plugin Plugin to retrieve registrations for. + * @param channel Channel to filter registrations by. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public Set getIncomingChannelRegistrations(Plugin plugin, String channel); + + /** + * Checks if the specified plugin message listener registration is valid. + *

+ * A registration is considered valid if it has not be unregistered and that the plugin + * is still enabled. + * + * @param registration Registration to check. + * @return True if the registration is valid, otherwise false. + */ + public boolean isRegistrationValid(PluginMessageListenerRegistration registration); + + /** + * Checks if the specified plugin has registered to receive incoming messages through the requested + * channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isIncomingChannelRegistered(Plugin plugin, String channel); + + /** + * Checks if the specified plugin has registered to send outgoing messages through the requested + * channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel); + + /** + * Dispatches the specified incoming message to any registered listeners. + * + * @param source Source of the message. + * @param channel Channel that the message was sent by. + * @param message Raw payload of the message. + */ + public void dispatchIncomingMessage(Player source, String channel, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java new file mode 100644 index 0000000..0e197a6 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java @@ -0,0 +1,19 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; + +/** + * A listener for a specific Plugin Channel, which will receive notifications of messages sent + * from a client. + */ +public interface PluginMessageListener { + /** + * A method that will be thrown when a {@link PluginMessageSource} sends a plugin + * message on a registered channel. + * + * @param channel Channel that the message was sent through. + * @param player Source of the message. + * @param message The raw message that was sent. + */ + public void onPluginMessageReceived(String channel, Player player, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java new file mode 100644 index 0000000..850ba5e --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java @@ -0,0 +1,103 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.plugin.Plugin; + +/** + * Contains information about a {@link Plugin}s registration to a plugin channel. + */ +public final class PluginMessageListenerRegistration { + private final Messenger messenger; + private final Plugin plugin; + private final String channel; + private final PluginMessageListener listener; + + public PluginMessageListenerRegistration(Messenger messenger, Plugin plugin, String channel, PluginMessageListener listener) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null!"); + } + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null!"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null!"); + } + + this.messenger = messenger; + this.plugin = plugin; + this.channel = channel; + this.listener = listener; + } + + /** + * Gets the plugin channel that this registration is about. + * + * @return Plugin channel. + */ + public String getChannel() { + return channel; + } + + /** + * Gets the registered listener described by this registration. + * + * @return Registered listener. + */ + public PluginMessageListener getListener() { + return listener; + } + + /** + * Gets the plugin that this registration is for. + * + * @return Registered plugin. + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Checks if this registration is still valid. + * + * @return True if this registration is still valid, otherwise false. + */ + public boolean isValid() { + return messenger.isRegistrationValid(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PluginMessageListenerRegistration other = (PluginMessageListenerRegistration) obj; + if (this.messenger != other.messenger && (this.messenger == null || !this.messenger.equals(other.messenger))) { + return false; + } + if (this.plugin != other.plugin && (this.plugin == null || !this.plugin.equals(other.plugin))) { + return false; + } + if ((this.channel == null) ? (other.channel != null) : !this.channel.equals(other.channel)) { + return false; + } + if (this.listener != other.listener && (this.listener == null || !this.listener.equals(other.listener))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + (this.messenger != null ? this.messenger.hashCode() : 0); + hash = 53 * hash + (this.plugin != null ? this.plugin.hashCode() : 0); + hash = 53 * hash + (this.channel != null ? this.channel.hashCode() : 0); + hash = 53 * hash + (this.listener != null ? this.listener.hashCode() : 0); + return hash; + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java new file mode 100644 index 0000000..6383166 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java @@ -0,0 +1,32 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.plugin.Plugin; + +/** + * Represents a possible recipient for a Plugin Message. + */ +public interface PluginMessageRecipient { + /** + * Sends this recipient a Plugin Message on the specified outgoing channel. + *

+ * The message may not be larger than {@link Messenger#MAX_MESSAGE_SIZE} bytes, and the plugin must be registered to send + * messages on the specified channel. + * + * @param source The plugin that sent this message. + * @param channel The channel to send this message on. + * @param message The raw message to send. + * @throws IllegalArgumentException Thrown if the source plugin is disabled. + * @throws IllegalArgumentException Thrown if source, channel or message is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin. + */ + public void sendPluginMessage(Plugin source, String channel, byte[] message); + + /** + * Gets a set containing all the Plugin Channels that this client is listening on. + * + * @return Set containing all the channels that this client may accept. + */ + public Set getListeningPluginChannels(); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java new file mode 100644 index 0000000..2f6fafa --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java @@ -0,0 +1,11 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a plugin attempts to register for a reserved channel (such as "REGISTER") + */ +public class ReservedChannelException extends RuntimeException { + + public ReservedChannelException(String name) { + super("Attempted to register for a reserved channel name ('" + name + "')"); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java new file mode 100644 index 0000000..e90f2e1 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java @@ -0,0 +1,476 @@ +package org.bukkit.plugin.messaging; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Standard implementation to {@link Messenger} + */ +public class StandardMessenger implements Messenger { + private final Map> incomingByChannel = new HashMap>(); + private final Map> incomingByPlugin = new HashMap>(); + private final Map> outgoingByChannel = new HashMap>(); + private final Map> outgoingByPlugin = new HashMap>(); + private final Object incomingLock = new Object(); + private final Object outgoingLock = new Object(); + + private void addToOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins == null) { + plugins = new HashSet(); + outgoingByChannel.put(channel, plugins); + } + + if (channels == null) { + channels = new HashSet(); + outgoingByPlugin.put(plugin, channels); + } + + plugins.add(plugin); + channels.add(channel); + } + } + + private void removeFromOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins != null) { + plugins.remove(plugin); + + if (plugins.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + + if (channels != null) { + channels.remove(channel); + + if (channels.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + } + } + + private void removeFromOutgoing(Plugin plugin) { + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + String[] toRemove = channels.toArray(new String[0]); + + outgoingByPlugin.remove(plugin); + + for (String channel : toRemove) { + removeFromOutgoing(plugin, channel); + } + } + } + } + + private void addToIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByChannel.put(registration.getChannel(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByPlugin.put(registration.getPlugin(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + } + } + + private void removeFromIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByChannel.remove(registration.getChannel()); + } + } + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByPlugin.remove(registration.getPlugin()); + } + } + } + } + + private void removeFromIncoming(Plugin plugin, String channel) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]); + + for (PluginMessageListenerRegistration registration : toRemove) { + if (registration.getChannel().equals(channel)) { + removeFromIncoming(registration); + } + } + } + } + } + + private void removeFromIncoming(Plugin plugin) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]); + + incomingByPlugin.remove(plugin); + + for (PluginMessageListenerRegistration registration : toRemove) { + removeFromIncoming(registration); + } + } + } + } + + public boolean isReservedChannel(String channel) { + validateChannel(channel); + + return channel.equals("REGISTER") || channel.equals("UNREGISTER"); + } + + public void registerOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + + addToOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromOutgoing(plugin); + } + + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + + PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener); + + addToIncoming(result); + + return result; + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener)); + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(plugin, channel); + } + + public void unregisterIncomingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromIncoming(plugin); + } + + public Set getOutgoingChannels() { + synchronized (outgoingLock) { + Set keys = outgoingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set getOutgoingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return ImmutableSet.copyOf(channels); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannels() { + synchronized (incomingLock) { + Set keys = incomingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set getIncomingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + builder.add(registration.getChannel()); + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(String channel) { + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(channel); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set getIncomingChannelRegistrations(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + builder.add(registration); + } + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public boolean isRegistrationValid(PluginMessageListenerRegistration registration) { + if (registration == null) { + throw new IllegalArgumentException("Registration cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + return registrations.contains(registration); + } + + return false; + } + } + + public boolean isIncomingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + return true; + } + } + } + + return false; + } + } + + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return channels.contains(channel); + } + + return false; + } + } + + public void dispatchIncomingMessage(Player source, String channel, byte[] message) { + if (source == null) { + throw new IllegalArgumentException("Player source cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + validateChannel(channel); + + Set registrations = getIncomingChannelRegistrations(channel); + + for (PluginMessageListenerRegistration registration : registrations) { + registration.getListener().onPluginMessageReceived(channel, source, message); + } + } + + /** + * Validates a Plugin Channel name. + * + * @param channel Channel name to validate. + */ + public static void validateChannel(String channel) { + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null"); + } + if (channel.length() > Messenger.MAX_CHANNEL_SIZE) { + throw new ChannelNameTooLongException(channel); + } + } + + /** + * Validates the input of a Plugin Message, ensuring the arguments are all valid. + * + * @param messenger Messenger to use for validation. + * @param source Source plugin of the Message. + * @param channel Plugin Channel to send the message by. + * @param message Raw message payload to send. + * @throws IllegalArgumentException Thrown if the source plugin is disabled. + * @throws IllegalArgumentException Thrown if source, channel or message is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNameTooLongException Thrown if the channel name is too long. + * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin. + */ + public static void validatePluginMessage(Messenger messenger, Plugin source, String channel, byte[] message) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null"); + } + if (source == null) { + throw new IllegalArgumentException("Plugin source cannot be null"); + } + if (!source.isEnabled()) { + throw new IllegalArgumentException("Plugin must be enabled to send messages"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + if (!messenger.isOutgoingChannelRegistered(source, channel)) { + throw new ChannelNotRegisteredException(channel); + } + if (message.length > Messenger.MAX_MESSAGE_SIZE) { + throw new MessageTooLargeException(message); + } + validateChannel(channel); + } +}