From 52c82a81e7c12722e9c644d526b227dcce373f06 Mon Sep 17 00:00:00 2001 From: Electroid Date: Sun, 9 Jul 2017 15:22:10 -0700 Subject: [PATCH] Add legacy CTF support --- .../java/tc/oc/pgm/flag/FlagManifest.java | 11 +- .../tc/oc/pgm/flag/LegacyFlagPlayerFacet.java | 150 ++++++++++++++++++ .../java/tc/oc/pgm/flag/state/Carried.java | 6 +- .../java/tc/oc/pgm/flag/state/Spawned.java | 3 +- PGM/src/main/java/tc/oc/pgm/match/Match.java | 17 +- .../main/java/tc/oc/pgm/match/MatchImpl.java | 8 - .../bukkit/inject/BukkitPlayerModule.java | 5 + .../tc/oc/commons/bukkit/util/NMSHacks.java | 83 +++++++++- 8 files changed, 265 insertions(+), 18 deletions(-) create mode 100644 PGM/src/main/java/tc/oc/pgm/flag/LegacyFlagPlayerFacet.java diff --git a/PGM/src/main/java/tc/oc/pgm/flag/FlagManifest.java b/PGM/src/main/java/tc/oc/pgm/flag/FlagManifest.java index 698bf28..101bfdf 100644 --- a/PGM/src/main/java/tc/oc/pgm/flag/FlagManifest.java +++ b/PGM/src/main/java/tc/oc/pgm/flag/FlagManifest.java @@ -2,10 +2,19 @@ package tc.oc.pgm.flag; import tc.oc.commons.core.inject.HybridManifest; import tc.oc.pgm.map.inject.MapBinders; +import tc.oc.pgm.match.MatchPlayerFacetBinder; +import tc.oc.pgm.match.inject.MatchBinders; + +public class FlagManifest extends HybridManifest implements MapBinders, MatchBinders { -public class FlagManifest extends HybridManifest implements MapBinders { @Override protected void configure() { rootParsers().addBinding().to(FlagParser.class); + + installPlayerModule(binder -> { + final MatchPlayerFacetBinder facets = new MatchPlayerFacetBinder(binder); + facets.register(LegacyFlagPlayerFacet.class); + }); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/flag/LegacyFlagPlayerFacet.java b/PGM/src/main/java/tc/oc/pgm/flag/LegacyFlagPlayerFacet.java new file mode 100644 index 0000000..8752dcd --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/flag/LegacyFlagPlayerFacet.java @@ -0,0 +1,150 @@ +package tc.oc.pgm.flag; + +import com.google.common.collect.ImmutableList; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import tc.oc.commons.bukkit.item.ItemBuilder; +import tc.oc.commons.bukkit.util.NMSHacks; +import tc.oc.commons.core.stream.Collectors; +import tc.oc.pgm.events.ListenerScope; +import tc.oc.pgm.flag.event.FlagStateChangeEvent; +import tc.oc.pgm.flag.state.Carried; +import tc.oc.pgm.flag.state.Spawned; +import tc.oc.pgm.match.Match; +import tc.oc.pgm.match.MatchPlayerFacet; +import tc.oc.pgm.match.MatchScope; +import tc.oc.pgm.match.Repeatable; +import tc.oc.time.Time; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static java.util.stream.IntStream.range; +import static tc.oc.minecraft.protocol.MinecraftVersion.*; + +@ListenerScope(MatchScope.LOADED) +public class LegacyFlagPlayerFacet implements MatchPlayerFacet, Listener { + + private final Match match; + private final Player bukkit; + private final Map beams; + + @Inject LegacyFlagPlayerFacet(Match match, Player bukkit) { + this.match = match; + this.bukkit = bukkit; + this.beams = new HashMap<>(); + } + + protected Stream flags() { + return (Stream) match.features().all(Flag.class); + } + + protected void trackFlag(Flag flag) { + if(lessThan(MINECRAFT_1_8, bukkit.getProtocolVersion())) { + beams.put(flag, beams.getOrDefault(flag, new Beam(flag))); + } + } + + protected void untrackFlag(Flag flag) { + if(beams.containsKey(flag)) { + beams.remove(flag).hide(); + } + } + + @Override + public void enable() { + flags().filter(flag -> flag.state() instanceof Spawned) + .forEach(this::trackFlag); + } + + @Override + public void disable() { + flags().forEach(this::untrackFlag); + beams.clear(); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onFlagStateChange(FlagStateChangeEvent event) { + Flag flag = event.getFlag(); + untrackFlag(flag); + if(event.getNewState() instanceof Spawned) { + trackFlag(flag); + } + } + + @Repeatable(interval = @Time(seconds = 1)) + public void onSecond() { + ImmutableList.copyOf(beams.values()).forEach(Beam::update); + } + + class Beam { + + final Flag flag; + final List segments; + + Beam(Flag flag) { + this.flag = flag; + this.segments = range(0, 32).mapToObj(i -> new NMSHacks.FakeZombie(match.getWorld(), true, true)) + .collect(Collectors.toImmutableList()); + show(); + } + + Optional carrier() { + return Optional.ofNullable(flag.state() instanceof Carried ? ((Carried) flag.state()).getCarrier().getBukkit() : null); + } + + Location location() { + Location location = flag.getLocation().get().clone(); + location.setPitch(0); + return location; + } + + ItemStack wool() { + return new ItemBuilder().material(Material.WOOL) + .enchant(Enchantment.DURABILITY, 1) + .color(flag.getDyeColor()) + .get(); + } + + void show() { + if(carrier().map(carrier -> carrier.equals(bukkit)).orElse(false)) return; + segments.forEach(segment -> { + segment.spawn(bukkit, location()); + segment.wear(bukkit, EquipmentSlot.HEAD, wool()); + }); + range(1, segments.size()).forEachOrdered(i -> { + segments.get(i - 1).ride(bukkit, segments.get(i).entity()); + }); + update(); + } + + void update() { + Optional carrier = carrier(); + NMSHacks.FakeZombie base = segments.get(0); + if(carrier.isPresent()) { + base.mount(bukkit, carrier.get()); + } else { + base.teleport(bukkit, location()); + } + } + + void hide() { + for(int i = segments.size() - 1; i >= 0; i--) { + segments.get(i).destroy(bukkit); + } + } + + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/flag/state/Carried.java b/PGM/src/main/java/tc/oc/pgm/flag/state/Carried.java index 34dd99b..10be505 100644 --- a/PGM/src/main/java/tc/oc/pgm/flag/state/Carried.java +++ b/PGM/src/main/java/tc/oc/pgm/flag/state/Carried.java @@ -61,6 +61,10 @@ public class Carried extends Spawned implements Missing { this.dropLocations.add(dropLocation); // Need an initial dropLocation in case the carrier never generates ones } + public MatchPlayer getCarrier() { + return carrier; + } + @Override public boolean isRecoverable() { return true; @@ -191,7 +195,7 @@ public class Carried extends Spawned implements Missing { @Override protected boolean canSeeParticles(Player player) { - return player != this.carrier.getBukkit(); + return super.canSeeParticles(player) && player != this.carrier.getBukkit(); } protected void dropFlag() { diff --git a/PGM/src/main/java/tc/oc/pgm/flag/state/Spawned.java b/PGM/src/main/java/tc/oc/pgm/flag/state/Spawned.java index 790e835..7faa3ca 100644 --- a/PGM/src/main/java/tc/oc/pgm/flag/state/Spawned.java +++ b/PGM/src/main/java/tc/oc/pgm/flag/state/Spawned.java @@ -5,6 +5,7 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import tc.oc.commons.bukkit.util.NMSHacks; +import tc.oc.minecraft.protocol.MinecraftVersion; import tc.oc.pgm.flag.Flag; import tc.oc.pgm.flag.Post; import tc.oc.pgm.flag.event.FlagCaptureEvent; @@ -67,7 +68,7 @@ public abstract class Spawned extends BaseState { } protected boolean canSeeParticles(Player player) { - return true; + return MinecraftVersion.atLeast(MinecraftVersion.MINECRAFT_1_8, player.getProtocolVersion()); } @Override diff --git a/PGM/src/main/java/tc/oc/pgm/match/Match.java b/PGM/src/main/java/tc/oc/pgm/match/Match.java index 6857bcf..22b9915 100644 --- a/PGM/src/main/java/tc/oc/pgm/match/Match.java +++ b/PGM/src/main/java/tc/oc/pgm/match/Match.java @@ -203,7 +203,22 @@ public interface Match extends Audience, IMatchQuery, Filterable, M * @see #registerEvents * @see #registerRepeatable */ - void registerEventsAndRepeatables(Object thing); + default void registerEventsAndRepeatables(Object thing) { + registerRepeatable(thing); + if(thing instanceof Listener) registerEvents((Listener) thing); + } + + /** + * Unregister {@link Repeatable} methods on the given object, and also + * unregister it for events if it is a {@link Listener}. + * + * @see #unregisterEvents + * @see #unregisterRepeatable + */ + default void unregisterEventsAndRepeatables(Object thing) { + unregisterRepeatable(thing); + if(thing instanceof Listener) unregisterEvents((Listener) thing); + } /** * Return the {@link MapModuleContext} that was used to load this match. diff --git a/PGM/src/main/java/tc/oc/pgm/match/MatchImpl.java b/PGM/src/main/java/tc/oc/pgm/match/MatchImpl.java index d3bfb00..a3c11fa 100644 --- a/PGM/src/main/java/tc/oc/pgm/match/MatchImpl.java +++ b/PGM/src/main/java/tc/oc/pgm/match/MatchImpl.java @@ -352,14 +352,6 @@ public class MatchImpl implements Match, ForwardingAudience { runningScheduler.unregisterRepeatables(object); } - @Override - public void registerEventsAndRepeatables(Object thing) { - registerRepeatable(thing); - if(thing instanceof Listener) { - registerEvents((Listener) thing); - } - } - // ----------------------------------- // ---- Modules/Features/Contexts ---- diff --git a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/inject/BukkitPlayerModule.java b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/inject/BukkitPlayerModule.java index 68780e3..0c22a8c 100644 --- a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/inject/BukkitPlayerModule.java +++ b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/inject/BukkitPlayerModule.java @@ -7,6 +7,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; import org.bukkit.entity.Player; import org.bukkit.inventory.PlayerInventory; +import tc.oc.minecraft.protocol.MinecraftVersion; /** * Binds various services provided through a {@link Player} (but does not bind {@link Player} itself) @@ -26,4 +27,8 @@ public class BukkitPlayerModule extends AbstractModule { @Provides UUID uuid(Player player) { return player.getUniqueId(); } + + @Provides MinecraftVersion version(Player player) { + return MinecraftVersion.byProtocol(player.getProtocolVersion()); + } } diff --git a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/util/NMSHacks.java b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/util/NMSHacks.java index 38a857d..5dc9bd3 100644 --- a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/util/NMSHacks.java +++ b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/util/NMSHacks.java @@ -22,6 +22,7 @@ import net.minecraft.server.DataWatcher; import net.minecraft.server.Enchantment; import net.minecraft.server.EntityArmorStand; import net.minecraft.server.EntityChicken; +import net.minecraft.server.EntityFallingBlock; import net.minecraft.server.EntityFireworks; import net.minecraft.server.EntityItem; import net.minecraft.server.EntityLiving; @@ -90,10 +91,10 @@ import org.bukkit.craftbukkit.util.Skins; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; +import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Firework; import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; -import org.bukkit.entity.Wither; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.material.MaterialData; import org.bukkit.plugin.Plugin; @@ -102,6 +103,9 @@ import org.bukkit.potion.PotionEffectTypeWrapper; import org.bukkit.registry.Key; import org.bukkit.util.Vector; import java.time.Duration; +import java.util.stream.IntStream; +import java.util.stream.Stream; + import tc.oc.commons.core.util.TimeUtils; import static com.google.common.base.Preconditions.checkArgument; @@ -114,7 +118,8 @@ public class NMSHacks { // There is no nice way to get at them. private static final Map, Integer> ENTITY_TYPE_IDS = ImmutableMap.of( org.bukkit.entity.Item.class, 2, - ArmorStand.class, 78 + ArmorStand.class, 78, + FallingBlock.class, 70 ); private static EntityTrackerEntry getTrackerEntry(net.minecraft.server.Entity nms) { @@ -272,8 +277,12 @@ public class NMSHacks { metadata); } + public static Packet setPassengerPacket(int vehicleId, IntStream riderIds) { + return new PacketPlayOutMount(vehicleId, riderIds.toArray()); + } + public static Packet setPassengerPacket(int vehicleId, int riderId) { - return new PacketPlayOutMount(vehicleId, riderId); + return setPassengerPacket(vehicleId, IntStream.of(riderId)); } public static Packet moveEntityRelativePacket(int entityId, Vector delta, boolean onGround) { @@ -292,6 +301,10 @@ public class NMSHacks { true); } + public static Packet entityVelocityPacket(int entityId, Vector velocity) { + return new PacketPlayOutEntityVelocity(entityId, velocity.getX(), velocity.getY(), velocity.getZ()); + } + private static Packet entityMetadataPacket(int entityId, net.minecraft.server.Entity nmsEntity, boolean complete) { return new PacketPlayOutEntityMetadata(entityId, nmsEntity.getDataWatcher(), complete); } @@ -304,8 +317,12 @@ public class NMSHacks { return entityMetadataPacket(nmsEntity.getId(), nmsEntity, complete); } + public static Packet entityEquipmentPacket(int entityId, EquipmentSlot slot, org.bukkit.inventory.ItemStack armor) { + return new PacketPlayOutEntityEquipment(entityId, EnumItemSlot.values()[slot.ordinal()], CraftItemStack.asNMSCopy(armor)); + } + public static Packet entityHelmetPacket(int entityId, org.bukkit.inventory.ItemStack helmet) { - return new PacketPlayOutEntityEquipment(entityId, EnumItemSlot.HEAD, CraftItemStack.asNMSCopy(helmet)); + return entityEquipmentPacket(entityId, EquipmentSlot.HEAD, helmet); } /** @@ -318,6 +335,8 @@ public class NMSHacks { public interface FakeEntity { int entityId(); + Entity entity(); + void spawn(Player viewer, Location location, Vector velocity); default void spawn(Player viewer, Location location) { @@ -336,8 +355,20 @@ public class NMSHacks { sendPacket(viewer, teleportEntityPacket(entityId(), location)); } + default void ride(Player viewer, Stream riders) { + sendPacket(viewer, setPassengerPacket(entityId(), riders.mapToInt(Entity::getEntityId))); + } + default void ride(Player viewer, Entity rider) { - sendPacket(viewer, setPassengerPacket(entityId(), rider.getEntityId())); + ride(viewer, Stream.of(rider)); + } + + default void mount(Player viewer, Entity vehicle) { + sendPacket(viewer, setPassengerPacket(vehicle.getEntityId(), entityId())); + } + + default void wear(Player viewer, EquipmentSlot slot, org.bukkit.inventory.ItemStack item) { + sendPacket(viewer, entityEquipmentPacket(entityId(), slot, item)); } } @@ -348,6 +379,11 @@ public class NMSHacks { this.entity = entity; } + @Override + public Entity entity() { + return entity.getBukkitEntity(); + } + @Override public int entityId() { return entity.getId(); @@ -385,6 +421,26 @@ public class NMSHacks { } } + public static class FakeFallingBlock extends FakeEntityImpl { + + private final MaterialData data; + + public FakeFallingBlock(World world, @Nullable MaterialData data) { + super(new EntityFallingBlock(((CraftWorld) world).getHandle())); + this.data = data != null ? data : new MaterialData(Material.SAND); + entity.setNoGravity(true); + entity.formBlock = false; + entity.dropItem = false; + entity.ticksLived = 1; + } + + @Override + public void spawn(Player viewer, Location location, Vector velocity) { + sendPacket(viewer, spawnEntityPacket(FallingBlock.class, data.getItemTypeId() + (data.getData() << 12), entityId(), entity.getUniqueID(), location, velocity)); + sendPacket(viewer, entityMetadataPacket(entity, true)); + } + } + private static class FakeLivingEntity extends FakeEntityImpl { protected FakeLivingEntity(T entity) { @@ -440,12 +496,22 @@ public class NMSHacks { } public static class FakeZombie extends FakeLivingEntity { + public FakeZombie(World world, boolean invisible) { + this(world, invisible, false); + } + + public FakeZombie(World world, boolean invisible, boolean baby) { super(new EntityZombie(((CraftWorld) world).getHandle())); entity.setInvisible(invisible); - entity.setNoAI(false); + entity.setBaby(baby); + entity.setNoAI(true); + entity.setNoGravity(true); + entity.setInvulnerable(true); + entity.setSilent(true); } + } public static class FakeChicken extends FakeLivingEntity { @@ -494,6 +560,11 @@ public class NMSHacks { this.uuid = uuid; } + @Override + public Entity entity() { + return prototype().getBukkitEntity(); + } + @Override public int entityId() { return entityId;