* It will make the laser visible for nearby players and start the countdown to the final duration. + *
* Once finished, it will destroy the laser and execute all runnables passed with {@link Laser#executeEnd}.
*
* @param plugin plugin used to start the task
@@ -139,7 +142,7 @@ public synchronized void cancel() throws IllegalStateException {
}
/**
- * Stops this laser.
+ * Stops this laser.
*
* This will destroy the laser for every player and start execute all runnables passed with {@link Laser#executeEnd}
*/
@@ -148,10 +151,21 @@ public void stop() {
main.cancel();
}
+ /**
+ * Gets laser status.
+ *
+ * @return true
if the laser is currently running
+ * (i.e. {@link #start} has been called and the duration is not over)
+ */
public boolean isStarted() {
return main != null;
}
+ /**
+ * Gets laser type.
+ *
+ * @return LaserType enum constant of this laser
+ */
public abstract LaserType getLaserType();
/**
@@ -170,10 +184,20 @@ public boolean isStarted() {
*/
public abstract void moveEnd(Location location) throws ReflectiveOperationException;
+ /**
+ * Gets the start location of the laser.
+ *
+ * @return where exactly is the start position of the laser located
+ */
public Location getStart() {
return start;
}
+ /**
+ * Gets the end location of the laser.
+ *
+ * @return where exactly is the end position of the laser located
+ */
public Location getEnd() {
return end;
}
@@ -229,11 +253,13 @@ public void run() {
}
protected void moveFakeEntity(Location location, int entityId, Object fakeEntity) throws ReflectiveOperationException {
+ if (fakeEntity != null) Packets.moveFakeEntity(fakeEntity, location);
+ if (main == null) return;
+
Object packet;
if (fakeEntity == null) {
packet = Packets.createPacketMoveEntity(location, entityId);
} else {
- Packets.moveFakeEntity(fakeEntity, location);
packet = Packets.createPacketMoveEntity(fakeEntity);
}
for (Player p : show) {
@@ -257,8 +283,8 @@ public static class GuardianLaser extends Laser {
private Object createGuardianPacket;
private Object createSquidPacket;
- private final Object teamCreatePacket;
- private final Object[] destroyPackets;
+ private Object teamCreatePacket;
+ private Object[] destroyPackets;
private Object metadataPacketGuardian;
private Object metadataPacketSquid;
private Object fakeGuardianDataWatcher;
@@ -275,6 +301,9 @@ public static class GuardianLaser extends Laser {
protected LivingEntity endEntity;
+ private Location correctStart;
+ private Location correctEnd;
+
/**
* Creates a new Guardian Laser instance
*
@@ -283,8 +312,10 @@ public static class GuardianLaser extends Laser {
* @param duration Duration of laser in seconds (-1 if infinite)
* @param distance Distance where laser will be visible (-1 if infinite)
* @throws ReflectiveOperationException if a reflection exception occurred during Laser creation
+ * @see Laser#start(Plugin) to start the laser
* @see #durationInTicks() to make the duration in ticks
* @see #executeEnd(Runnable) to add Runnable-s to execute when the laser will stop
+ * @see #GuardianLaser(Location, LivingEntity, int, int) to create a laser which follows an entity
*/
public GuardianLaser(Location start, Location end, int duration, int distance) throws ReflectiveOperationException {
super(start, end, duration, distance);
@@ -294,9 +325,7 @@ public GuardianLaser(Location start, Location end, int duration, int distance) t
targetID = squidID;
targetUUID = squidUUID;
- initGuardian();
- teamCreatePacket = Packets.createPacketTeamCreate("noclip" + teamID.getAndIncrement(), squidUUID, guardianUUID);
- destroyPackets = Packets.createPacketsRemoveEntities(squidID, guardianID);
+ initLaser();
}
/**
@@ -307,8 +336,10 @@ public GuardianLaser(Location start, Location end, int duration, int distance) t
* @param duration Duration of laser in seconds (-1 if infinite)
* @param distance Distance where laser will be visible (-1 if infinite)
* @throws ReflectiveOperationException if a reflection exception occurred during Laser creation
+ * @see Laser#start(Plugin) to start the laser
* @see #durationInTicks() to make the duration in ticks
* @see #executeEnd(Runnable) to add Runnable-s to execute when the laser will stop
+ * @see #GuardianLaser(Location, Location, int, int) to create a laser with a specific end location
*/
public GuardianLaser(Location start, LivingEntity endEntity, int duration, int distance) throws ReflectiveOperationException {
super(start, endEntity.getLocation(), duration, distance);
@@ -316,41 +347,65 @@ public GuardianLaser(Location start, LivingEntity endEntity, int duration, int d
targetID = endEntity.getEntityId();
targetUUID = endEntity.getUniqueId();
- initGuardian();
- teamCreatePacket = Packets.createPacketTeamCreate("noclip" + teamID.getAndIncrement(), squidUUID, guardianUUID);
- destroyPackets = Packets.createPacketsRemoveEntities(squidID, guardianID);
+ initLaser();
}
- private void initGuardian() throws ReflectiveOperationException {
+ private void initLaser() throws ReflectiveOperationException {
fakeGuardianDataWatcher = Packets.createFakeDataWatcher();
Packets.initGuardianWatcher(fakeGuardianDataWatcher, targetID);
- if (Packets.version < 17) {
- guardian = null;
- createGuardianPacket = Packets.createPacketEntitySpawnLiving(start, Packets.mappings.getGuardianID(), guardianUUID, guardianID);
- } else {
- guardian = Packets.createGuardian(start, guardianUUID, guardianID);
- createGuardianPacket = Packets.createPacketEntitySpawnLiving(guardian);
+ if (Packets.version >= 17) {
+ guardian = Packets.createGuardian(getCorrectStart(), guardianUUID, guardianID);
}
metadataPacketGuardian = Packets.createPacketMetadata(guardianID, fakeGuardianDataWatcher);
+
+ teamCreatePacket = Packets.createPacketTeamCreate("noclip" + teamID.getAndIncrement(), squidUUID, guardianUUID);
+ destroyPackets = Packets.createPacketsRemoveEntities(squidID, guardianID);
}
private void initSquid() throws ReflectiveOperationException {
- if (Packets.version < 17) {
- squid = null;
- createSquidPacket = Packets.createPacketEntitySpawnLiving(end, Packets.mappings.getSquidID(), squidUUID, squidID);
- } else {
- squid = Packets.createSquid(end, squidUUID, squidID);
- createSquidPacket = Packets.createPacketEntitySpawnLiving(squid);
+ if (Packets.version >= 17) {
+ squid = Packets.createSquid(getCorrectEnd(), squidUUID, squidID);
}
metadataPacketSquid = Packets.createPacketMetadata(squidID, Packets.fakeSquidWatcher);
Packets.setDirtyWatcher(Packets.fakeSquidWatcher);
}
+ private Object getGuardianSpawnPacket() throws ReflectiveOperationException {
+ if (createGuardianPacket == null) {
+ if (Packets.version < 17) {
+ createGuardianPacket = Packets.createPacketEntitySpawnLiving(getCorrectStart(), Packets.mappings.getGuardianID(), guardianUUID, guardianID);
+ } else {
+ createGuardianPacket = Packets.createPacketEntitySpawnLiving(guardian);
+ }
+ }
+ return createGuardianPacket;
+ }
+
+ private Object getSquidSpawnPacket() throws ReflectiveOperationException {
+ if (createSquidPacket == null) {
+ if (Packets.version < 17) {
+ createSquidPacket = Packets.createPacketEntitySpawnLiving(getCorrectEnd(), Packets.mappings.getSquidID(), squidUUID, squidID);
+ } else {
+ createSquidPacket = Packets.createPacketEntitySpawnLiving(squid);
+ }
+ }
+ return createSquidPacket;
+ }
+
@Override
public LaserType getLaserType() {
return LaserType.GUARDIAN;
}
+ /**
+ * Makes the laser follow an entity (moving end location).
+ *
+ * This is done client-side by making the fake guardian follow the existing entity. + * Hence, there is no consuming of server resources. + * + * @param entity living entity the laser will follow + * @throws ReflectiveOperationException if a reflection operation fails + */ public void attachEndEntity(LivingEntity entity) throws ReflectiveOperationException { if (entity.getWorld() != start.getWorld()) throw new IllegalArgumentException("Attached entity is not in the same world as the laser."); @@ -365,6 +420,7 @@ public Entity getEndEntity() { private void setTargetEntity(UUID uuid, int id) throws ReflectiveOperationException { targetUUID = uuid; targetID = id; + fakeGuardianDataWatcher = Packets.createFakeDataWatcher(); Packets.initGuardianWatcher(fakeGuardianDataWatcher, targetID); metadataPacketGuardian = Packets.createPacketMetadata(guardianID, fakeGuardianDataWatcher); @@ -378,6 +434,26 @@ public Location getEnd() { return endEntity == null ? end : endEntity.getLocation(); } + protected Location getCorrectStart() { + if (correctStart == null) { + correctStart = start.clone(); + correctStart.subtract(0, 0.5, 0); + } + return correctStart; + } + + protected Location getCorrectEnd() { + if (correctEnd == null) { + correctEnd = end.clone(); + correctEnd.subtract(0, 0.5, 0); + + Vector corrective = correctEnd.toVector().subtract(getCorrectStart().toVector()).normalize(); + correctEnd.subtract(corrective); + + } + return correctEnd; + } + @Override protected boolean isCloseEnough(Player player) { return player == endEntity || super.isCloseEnough(player); @@ -385,8 +461,18 @@ protected boolean isCloseEnough(Player player) { @Override protected void sendStartPackets(Player p, boolean hasSeen) throws ReflectiveOperationException { - Packets.sendPackets(p, createGuardianPacket, createSquidPacket); - Packets.sendPackets(p, metadataPacketGuardian, metadataPacketSquid); + if (squid == null) { + Packets.sendPackets(p, + getGuardianSpawnPacket(), + metadataPacketGuardian); + } else { + Packets.sendPackets(p, + getGuardianSpawnPacket(), + getSquidSpawnPacket(), + metadataPacketGuardian, + metadataPacketSquid); + } + if (!hasSeen) Packets.sendPackets(p, teamCreatePacket); } @@ -398,24 +484,31 @@ protected void sendDestroyPackets(Player p) throws ReflectiveOperationException @Override public void moveStart(Location location) throws ReflectiveOperationException { this.start = location; - initGuardian(); - if (main != null) { - moveFakeEntity(start, guardianID, guardian); + correctStart = null; + + createGuardianPacket = null; // will force re-generation of spawn packet + moveFakeEntity(getCorrectStart(), guardianID, guardian); + + if (squid != null) { + correctEnd = null; + createSquidPacket = null; + moveFakeEntity(getCorrectEnd(), squidID, squid); } } @Override public void moveEnd(Location location) throws ReflectiveOperationException { this.end = location; - initSquid(); - if (main != null) { - if (squid == null) { - for (Player p : show) { - Packets.sendPackets(p, createSquidPacket, metadataPacketSquid); - } - } else { - moveFakeEntity(end, squidID, squid); + createSquidPacket = null; // will force re-generation of spawn packet + correctEnd = null; + + if (squid == null) { + initSquid(); + for (Player p : show) { + Packets.sendPackets(p, getSquidSpawnPacket(), metadataPacketSquid); } + } else { + moveFakeEntity(getCorrectEnd(), squidID, squid); } if (targetUUID != squidUUID) { endEntity = null; @@ -424,7 +517,7 @@ public void moveEnd(Location location) throws ReflectiveOperationException { } /** - * Asks viewers' clients to change the color of this Laser + * Asks viewers' clients to change the color of this laser * * @throws ReflectiveOperationException */ @@ -441,10 +534,10 @@ public static class CrystalLaser extends Laser { private Object createCrystalPacket; private Object metadataPacketCrystal; private final Object[] destroyPackets; - private Object fakeCrystalDataWatcher; + private final Object fakeCrystalDataWatcher; private final Object crystal; - private final int crystalID; + private final int crystalID = Packets.generateEID(); /** * Creates a new Ender Crystal Laser instance @@ -454,6 +547,7 @@ public static class CrystalLaser extends Laser { * @param duration Duration of laser in seconds (-1 if infinite) * @param distance Distance where laser will be visible (-1 if infinite) * @throws ReflectiveOperationException if a reflection exception occurred during Laser creation + * @see #start(Plugin) to start the laser * @see #durationInTicks() to make the duration in ticks * @see #executeEnd(Runnable) to add Runnable-s to execute when the laser will stop */ @@ -464,26 +558,23 @@ public CrystalLaser(Location start, Location end, int duration, int distance) th Packets.setCrystalWatcher(fakeCrystalDataWatcher, end); if (Packets.version < 17) { crystal = null; - createCrystalPacket = Packets.createPacketEntitySpawnNormal(start, Packets.crystalID, Packets.crystalType); } else { - crystal = Packets.createCrystal(start); - createCrystalPacket = Packets.createPacketEntitySpawnNormal(crystal); + crystal = Packets.createCrystal(start, UUID.randomUUID(), crystalID); } - crystalID = (int) Packets.getField(createCrystalPacket, Packets.version < 17 ? "a" : "c"); metadataPacketCrystal = Packets.createPacketMetadata(crystalID, fakeCrystalDataWatcher); destroyPackets = Packets.createPacketsRemoveEntities(crystalID); } - protected void refreshPackets() throws ReflectiveOperationException { - fakeCrystalDataWatcher = Packets.createFakeDataWatcher(); - Packets.setCrystalWatcher(fakeCrystalDataWatcher, end); - if (Packets.version < 17) { - createCrystalPacket = Packets.createPacketEntitySpawnNormal(start, Packets.crystalID, Packets.crystalType); - } else { - createCrystalPacket = Packets.createPacketEntitySpawnNormal(crystal); + private Object getCrystalSpawnPacket() throws ReflectiveOperationException { + if (createCrystalPacket == null) { + if (Packets.version < 17) { + createCrystalPacket = Packets.createPacketEntitySpawnNormal(start, Packets.crystalID, Packets.crystalType, crystalID); + } else { + createCrystalPacket = Packets.createPacketEntitySpawnNormal(crystal); + } } - metadataPacketCrystal = Packets.createPacketMetadata(crystalID, fakeCrystalDataWatcher); + return createCrystalPacket; } @Override @@ -493,7 +584,7 @@ public LaserType getLaserType() { @Override protected void sendStartPackets(Player p, boolean hasSeen) throws ReflectiveOperationException { - Packets.sendPackets(p, createCrystalPacket); + Packets.sendPackets(p, getCrystalSpawnPacket()); Packets.sendPackets(p, metadataPacketCrystal); } @@ -505,14 +596,13 @@ protected void sendDestroyPackets(Player p) throws ReflectiveOperationException @Override public void moveStart(Location location) throws ReflectiveOperationException { this.start = location; - refreshPackets(); - if (main != null) moveFakeEntity(start, crystalID, crystal); + createCrystalPacket = null; // will force re-generation of spawn packet + moveFakeEntity(start, crystalID, crystal); } @Override public void moveEnd(Location location) throws ReflectiveOperationException { this.end = location; - refreshPackets(); if (main != null) { Packets.setCrystalWatcher(fakeCrystalDataWatcher, location); metadataPacketCrystal = Packets.createPacketMetadata(crystalID, fakeCrystalDataWatcher); @@ -525,7 +615,20 @@ public void moveEnd(Location location) throws ReflectiveOperationException { } public enum LaserType { - GUARDIAN, ENDER_CRYSTAL; + /** + * Represents a laser from a Guardian entity. + *
+ * It can be pointed to precise locations and + * can track entities smoothly using {@link GuardianLaser#attachEndEntity(LivingEntity)} + */ + GUARDIAN, + + /** + * Represents a laser from an Ender Crystal entity. + *
+ * Start and end locations are automatically rounded to integers (block locations).
+ */
+ ENDER_CRYSTAL;
/**
* Creates a new Laser instance, {@link GuardianLaser} or {@link CrystalLaser} depending on this enum value.
@@ -535,8 +638,9 @@ public enum LaserType {
* @param duration Duration of laser in seconds (-1 if infinite)
* @param distance Distance where laser will be visible
* @throws ReflectiveOperationException if a reflection exception occurred during Laser creation
- * @see #durationInTicks() to make the duration in ticks
- * @see #executeEnd(Runnable) to add Runnable-s to execute when the laser will stop
+ * @see Laser#start(Plugin) to start the laser
+ * @see Laser#durationInTicks() to make the duration in ticks
+ * @see Laser#executeEnd(Runnable) to add Runnable-s to execute when the laser will stop
*/
public Laser create(Location start, Location end, int duration, int distance) throws ReflectiveOperationException {
switch (this) {
@@ -565,15 +669,19 @@ static int generateEID() {
private static final int crystalID = 51; // pre-1.13
- private static Class> entityTypesClass;
private static Object crystalType;
private static Object squidType;
private static Object guardianType;
+ private static Constructor> crystalConstructor;
+ private static Constructor> squidConstructor;
+ private static Constructor> guardianConstructor;
+
private static Object watcherObject1; // invisilibity
private static Object watcherObject2; // spikes
private static Object watcherObject3; // attack id
private static Object watcherObject4; // crystal target
+ private static Object watcherObject5; // crystal base plate
private static Constructor> watcherConstructor;
private static Method watcherSet;
@@ -630,6 +738,7 @@ public void log(LogRecord logRecord) {
versions = Bukkit.getBukkitVersion().split("-R")[0].split("\\.");
versionMinor = versions.length <= 2 ? 0 : Integer.parseInt(versions[2]);
} else versionMinor = Integer.parseInt(versions[2].substring(1)); // 1.X.Y
+ logger.info("Found server version 1." + version + "." + versionMinor);
mappings = ProtocolMappings.getMappings(version);
if (mappings == null) {
@@ -638,12 +747,16 @@ public void log(LogRecord logRecord) {
}
logger.info("Loaded mappings " + mappings.name());
+ Class> entityTypesClass = getNMSClass("world.entity", "EntityTypes");
Class> entityClass = getNMSClass("world.entity", "Entity");
- entityTypesClass = getNMSClass("world.entity", "EntityTypes");
+ Class> crystalClass = getNMSClass("world.entity.boss.enderdragon", "EntityEnderCrystal");
+ Class> squidClass = getNMSClass("world.entity.animal", "EntitySquid");
+ Class> guardianClass = getNMSClass("world.entity.monster", "EntityGuardian");
watcherObject1 = getField(entityClass, mappings.getWatcherFlags(), null);
- watcherObject2 = getField(getNMSClass("world.entity.monster", "EntityGuardian"), mappings.getWatcherSpikes(), null);
- watcherObject3 = getField(getNMSClass("world.entity.monster", "EntityGuardian"), mappings.getWatcherTargetEntity(), null);
- watcherObject4 = getField(getNMSClass("world.entity.boss.enderdragon", "EntityEnderCrystal"), mappings.getWatcherTargetLocation(), null);
+ watcherObject2 = getField(guardianClass, mappings.getWatcherSpikes(), null);
+ watcherObject3 = getField(guardianClass, mappings.getWatcherTargetEntity(), null);
+ watcherObject4 = getField(crystalClass, mappings.getWatcherTargetLocation(), null);
+ watcherObject5 = getField(crystalClass, mappings.getWatcherBasePlate(), null);
if (version >= 13) {
crystalType = entityTypesClass.getDeclaredField(mappings.getCrystalTypeName()).get(null);
@@ -673,8 +786,15 @@ public void log(LogRecord logRecord) {
blockPositionConstructor = getNMSClass("core", "BlockPosition").getConstructor(double.class, double.class, double.class);
nmsWorld = Class.forName(cpack + "CraftWorld").getDeclaredMethod("getHandle").invoke(Bukkit.getWorlds().get(0));
- Object[] entityConstructorParams = version < 14 ? new Object[]{nmsWorld} : new Object[]{getNMSClass("world.entity", "EntityTypes").getDeclaredField(version < 17 ? "SQUID" : "aJ").get(null), nmsWorld};
- fakeSquid = getNMSClass("world.entity.animal", "EntitySquid").getDeclaredConstructors()[0].newInstance(entityConstructorParams);
+
+ squidConstructor = squidClass.getDeclaredConstructors()[0];
+ if (version >= 17) {
+ guardianConstructor = guardianClass.getDeclaredConstructors()[0];
+ crystalConstructor = crystalClass.getDeclaredConstructor(nmsWorld.getClass().getSuperclass(), double.class, double.class, double.class);
+ }
+
+ Object[] entityConstructorParams = version < 14 ? new Object[]{nmsWorld} : new Object[]{entityTypesClass.getDeclaredField(version < 17 ? "SQUID" : "aJ").get(null), nmsWorld};
+ fakeSquid = squidConstructor.newInstance(entityConstructorParams);
fakeSquidWatcher = createFakeDataWatcher();
tryWatcherSet(fakeSquidWatcher, watcherObject1, (byte) 32);
@@ -729,21 +849,23 @@ public static void setDirtyWatcher(Object watcher) throws ReflectiveOperationExc
}
public static Object createSquid(Location location, UUID uuid, int id) throws ReflectiveOperationException {
- Object entity = getNMSClass("world.entity.animal", "EntitySquid").getDeclaredConstructors()[0].newInstance(squidType, nmsWorld);
+ Object entity = squidConstructor.newInstance(squidType, nmsWorld);
setEntityIDs(entity, uuid, id);
moveFakeEntity(entity, location);
return entity;
}
public static Object createGuardian(Location location, UUID uuid, int id) throws ReflectiveOperationException {
- Object entity = getNMSClass("world.entity.monster", "EntityGuardian").getDeclaredConstructors()[0].newInstance(guardianType, nmsWorld);
+ Object entity = guardianConstructor.newInstance(guardianType, nmsWorld);
setEntityIDs(entity, uuid, id);
moveFakeEntity(entity, location);
return entity;
}
- public static Object createCrystal(Location location) throws ReflectiveOperationException {
- return getNMSClass("world.entity.boss.enderdragon", "EntityEnderCrystal").getDeclaredConstructor(nmsWorld.getClass().getSuperclass(), double.class, double.class, double.class).newInstance(nmsWorld, location.getX(), location.getY(), location.getZ());
+ public static Object createCrystal(Location location, UUID uuid, int id) throws ReflectiveOperationException {
+ Object entity = crystalConstructor.newInstance(nmsWorld, location.getX(), location.getY(), location.getZ());
+ setEntityIDs(entity, uuid, id);
+ return entity;
}
public static Object createPacketEntitySpawnLiving(Location location, int typeID, UUID uuid, int id) throws ReflectiveOperationException {
@@ -760,9 +882,9 @@ public static Object createPacketEntitySpawnLiving(Location location, int typeID
return packet;
}
- public static Object createPacketEntitySpawnNormal(Location location, int typeID, Object type) throws ReflectiveOperationException {
+ public static Object createPacketEntitySpawnNormal(Location location, int typeID, Object type, int id) throws ReflectiveOperationException {
Object packet = packetSpawnNormal.newInstance();
- setField(packet, "a", generateEID());
+ setField(packet, "a", id);
setField(packet, "b", UUID.randomUUID());
setField(packet, "c", location.getX());
setField(packet, "d", location.getY());
@@ -783,13 +905,14 @@ public static Object createPacketEntitySpawnNormal(Object entity) throws Reflect
public static void initGuardianWatcher(Object watcher, int targetId) throws ReflectiveOperationException {
tryWatcherSet(watcher, watcherObject1, (byte) 32);
- tryWatcherSet(watcher, watcherObject2, false);
+ tryWatcherSet(watcher, watcherObject2, Boolean.FALSE);
tryWatcherSet(watcher, watcherObject3, targetId);
}
public static void setCrystalWatcher(Object watcher, Location target) throws ReflectiveOperationException {
Object blockPosition = blockPositionConstructor.newInstance(target.getX(), target.getY(), target.getZ());
tryWatcherSet(watcher, watcherObject4, version < 13 ? com.google.common.base.Optional.of(blockPosition) : Optional.of(blockPosition));
+ tryWatcherSet(watcher, watcherObject5, Boolean.FALSE);
}
public static Object[] createPacketsRemoveEntities(int... entitiesId) throws ReflectiveOperationException {
@@ -883,12 +1006,6 @@ private static Object getField(Class> clazz, String name, Object instance) thr
return field.get(instance);
}
- private static Object getField(Object instance, String name) throws ReflectiveOperationException {
- Field field = instance.getClass().getDeclaredField(name);
- field.setAccessible(true);
- return field.get(instance);
- }
-
private static Class> getNMSClass(String package17, String className) throws ClassNotFoundException {
return Class.forName((version < 17 ? npack : "net.minecraft." + package17) + "." + className);
}
@@ -899,27 +1016,32 @@ public interface ReflectiveConsumer