diff --git a/core/src/main/java/tc/oc/pgm/spawner/Spawner.java b/core/src/main/java/tc/oc/pgm/spawner/Spawner.java index 4da0c4fd15..9935e219e5 100644 --- a/core/src/main/java/tc/oc/pgm/spawner/Spawner.java +++ b/core/src/main/java/tc/oc/pgm/spawner/Spawner.java @@ -1,5 +1,6 @@ package tc.oc.pgm.spawner; +import java.util.Objects; import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.event.EventHandler; @@ -36,7 +37,6 @@ public class Spawner implements Listener, Tickable { public Spawner(SpawnerDefinition definition, Match match) { this.definition = definition; this.match = match; - this.lastTick = match.getTick().tick; this.players = new OnlinePlayerMapAdapter<>(PGM.get()); calculateDelay(); @@ -51,7 +51,6 @@ public void tick(Match match, Tick tick) { definition.spawnRegion.getRandom(match.getRandom()).toLocation(match.getWorld()); spawnable.spawn(location, match); match.getWorld().spigot().playEffect(location, Effect.FLAME, 0, 0, 0, 0.15f, 0, 0, 40, 64); - spawnedEntities = spawnedEntities + spawnable.getSpawnCount(); } calculateDelay(); @@ -82,47 +81,23 @@ private boolean canSpawn() { @EventHandler(priority = EventPriority.HIGHEST) public void onItemMerge(ItemMergeEvent event) { - boolean entityTracked = false; - boolean targetTracked = false; - if (event.getEntity().hasMetadata(METADATA_KEY)) { - entityTracked = true; - } - if (event.getTarget().hasMetadata(METADATA_KEY)) { - targetTracked = true; - } - - // Do nothing if neither item is from a PGM Spawner - if (!entityTracked && !targetTracked) { - return; - } - - // Cancel the merge if only 1 of the items is from a PGM Spawner - if ((entityTracked && !targetTracked) || (!entityTracked && targetTracked)) { - event.setCancelled(true); - return; - } - - int entitySpawnerID = -1; - int targetSpawnerID = -1; - if (event.getEntity().hasMetadata(METADATA_KEY)) { - entitySpawnerID = - MetadataUtils.getMetadata(event.getEntity(), METADATA_KEY, PGM.get()).asInt(); - } - if (event.getTarget().hasMetadata(METADATA_KEY)) { - targetSpawnerID = - MetadataUtils.getMetadata(event.getTarget(), METADATA_KEY, PGM.get()).asInt(); - } - // Cancel the merge if the items are from different PGM spawners - if (entitySpawnerID != targetSpawnerID) { - event.setCancelled(true); - return; + boolean entityTracked = event.getEntity().hasMetadata(METADATA_KEY); + boolean targetTracked = event.getTarget().hasMetadata(METADATA_KEY); + if (!entityTracked && !targetTracked) return; // None affected + if (entityTracked && targetTracked) { + String entitySpawnerId = + MetadataUtils.getMetadata(event.getEntity(), METADATA_KEY, PGM.get()).toString(); + String targetSpawnerId = + MetadataUtils.getMetadata(event.getTarget(), METADATA_KEY, PGM.get()).toString(); + if (entitySpawnerId.equals(targetSpawnerId)) return; // Same spawner, allow merge } + event.setCancelled(true); } private void handleEntityRemoveEvent(Metadatable metadatable, int amount) { if (metadatable.hasMetadata(METADATA_KEY)) { - if (MetadataUtils.getMetadata(metadatable, METADATA_KEY, PGM.get()).asInt() - == definition.numericID) { + if (Objects.equals( + MetadataUtils.getMetadata(metadatable, METADATA_KEY, PGM.get()), definition.getId())) { spawnedEntities -= amount; spawnedEntities = Math.max(0, spawnedEntities); } diff --git a/core/src/main/java/tc/oc/pgm/spawner/SpawnerDefinition.java b/core/src/main/java/tc/oc/pgm/spawner/SpawnerDefinition.java index b3345b913a..dbe811072f 100644 --- a/core/src/main/java/tc/oc/pgm/spawner/SpawnerDefinition.java +++ b/core/src/main/java/tc/oc/pgm/spawner/SpawnerDefinition.java @@ -2,23 +2,24 @@ import java.time.Duration; import java.util.List; -import tc.oc.pgm.api.feature.FeatureDefinition; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; import tc.oc.pgm.api.feature.FeatureInfo; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.region.Region; +import tc.oc.pgm.features.SelfIdentifyingFeatureDefinition; @FeatureInfo(name = "spawner") -public class SpawnerDefinition implements FeatureDefinition { - +public class SpawnerDefinition extends SelfIdentifyingFeatureDefinition { public final Region spawnRegion; public final Region playerRegion; public final int maxEntities; public final Duration minDelay, maxDelay, delay; public final List objects; public final Filter playerFilter; - public final int numericID; public SpawnerDefinition( + String id, List objects, Region spawnRegion, Region playerRegion, @@ -26,8 +27,8 @@ public SpawnerDefinition( Duration delay, Duration minDelay, Duration maxDelay, - int maxEntities, - int numericID) { + int maxEntities) { + super(id); this.spawnRegion = spawnRegion; this.playerRegion = playerRegion; this.maxEntities = maxEntities; @@ -36,6 +37,17 @@ public SpawnerDefinition( this.delay = delay; this.objects = objects; this.playerFilter = playerFilter; - this.numericID = numericID; + } + + @Override + protected String getDefaultId() { + return super.makeDefaultId(); + } + + public static String makeDefaultId(@Nullable String name, AtomicInteger serial) { + return "--" + + makeTypeName(SpawnerDefinition.class) + + "-" + + (name != null ? makeId(name) : serial.getAndIncrement()); } } diff --git a/core/src/main/java/tc/oc/pgm/spawner/SpawnerModule.java b/core/src/main/java/tc/oc/pgm/spawner/SpawnerModule.java index b20458a356..52366171db 100644 --- a/core/src/main/java/tc/oc/pgm/spawner/SpawnerModule.java +++ b/core/src/main/java/tc/oc/pgm/spawner/SpawnerModule.java @@ -5,8 +5,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionType; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; @@ -24,6 +27,8 @@ import tc.oc.pgm.regions.RegionModule; import tc.oc.pgm.regions.RegionParser; import tc.oc.pgm.spawner.objects.SpawnableItem; +import tc.oc.pgm.spawner.objects.SpawnablePotion; +import tc.oc.pgm.util.xml.InheritingElement; import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.XMLUtils; @@ -49,19 +54,22 @@ public SpawnerModule parse(MapFactory factory, Logger logger, Document doc) RegionParser regionParser = factory.getRegions(); KitParser kitParser = factory.getKits(); FilterParser filterParser = factory.getFilters(); + AtomicInteger postIdSerial = new AtomicInteger(1); - int numericID = 0; - for (Element element : + for (Element spawnerEl : XMLUtils.flattenElements(doc.getRootElement(), "spawners", "spawner")) { - Region spawnRegion = regionParser.parseRequiredRegionProperty(element, "spawn-region"); - Region playerRegion = regionParser.parseRequiredRegionProperty(element, "player-region"); - Attribute delayAttr = element.getAttribute("delay"); - Attribute minDelayAttr = element.getAttribute("min-delay"); - Attribute maxDelayAttr = element.getAttribute("max-delay"); + String id = spawnerEl.getAttributeValue("id"); + Region spawnRegion = regionParser.parseRequiredRegionProperty(spawnerEl, "spawn-region"); + Region playerRegion = regionParser.parseRequiredRegionProperty(spawnerEl, "player-region"); + Attribute delayAttr = spawnerEl.getAttribute("delay"); + Attribute minDelayAttr = spawnerEl.getAttribute("min-delay"); + Attribute maxDelayAttr = spawnerEl.getAttribute("max-delay"); + + if (id == null) id = SpawnerDefinition.makeDefaultId("spawner", postIdSerial); if ((minDelayAttr != null || maxDelayAttr != null) && delayAttr != null) { throw new InvalidXMLException( - "Attribute 'minDelay' and 'maxDelay' cannot be combined with 'delay'", element); + "Attribute 'minDelay' and 'maxDelay' cannot be combined with 'delay'", spawnerEl); } Duration delay = XMLUtils.parseDuration(delayAttr, Duration.ofSeconds(10)); @@ -69,26 +77,51 @@ public SpawnerModule parse(MapFactory factory, Logger logger, Document doc) Duration maxDelay = XMLUtils.parseDuration(maxDelayAttr, delay); if (maxDelay.compareTo(minDelay) <= 0 && minDelayAttr != null && maxDelayAttr != null) { - throw new InvalidXMLException("Max-delay must be longer than min-delay", element); + throw new InvalidXMLException("Max-delay must be longer than min-delay", spawnerEl); } int maxEntities = XMLUtils.parseNumber( - element.getAttribute("max-entities"), Integer.class, Integer.MAX_VALUE); + spawnerEl.getAttribute("max-entities"), Integer.class, Integer.MAX_VALUE); Filter playerFilter = - filterParser.parseFilterProperty(element, "filter", StaticFilter.ALLOW); + filterParser.parseFilterProperty(spawnerEl, "filter", StaticFilter.ALLOW); List objects = new ArrayList<>(); - for (Element spawnable : - XMLUtils.getChildren( - element, "item")) { // TODO Add more types of spawnables once entity parser is built - ItemStack stack = kitParser.parseItem(spawnable, false); - SpawnableItem item = new SpawnableItem(stack, numericID); + for (Element itemEl : XMLUtils.getChildren(spawnerEl, "item")) { + ItemStack stack = kitParser.parseItem(itemEl, false); + SpawnableItem item = new SpawnableItem(stack, id); objects.add(item); } + for (Element potionEl : XMLUtils.getChildren(spawnerEl, "potion")) { + ImmutableList.Builder effectsBuilder = ImmutableList.builder(); + for (Element potionChild : potionEl.getChildren("effect")) { + effectsBuilder.add(XMLUtils.parsePotionEffect(new InheritingElement(potionChild))); + } + ImmutableList effects = effectsBuilder.build(); + if (effects.isEmpty()) { + throw new InvalidXMLException("Expected child effects, but found none", spawnerEl); + } + int damageValue = 0; + if (potionEl.getAttribute("damage") != null) { + damageValue = XMLUtils.parseNumber(potionEl.getAttribute("damage"), Integer.class, 0); + } else { + for (PotionEffect potionEffect : effects) { + // PotionType lists "true" potions, PotionEffectType "potionEffect.getType()" lists + // all possible status effects (ie wither, blindness, etc) + // Use the first listed PotionType for potion color + if (PotionType.getByEffect(potionEffect.getType()) != null) { + damageValue = PotionType.getByEffect(potionEffect.getType()).getDamageValue(); + break; + } + } + } + objects.add(new SpawnablePotion(effects, damageValue, id)); + } + SpawnerDefinition spawnerDefinition = new SpawnerDefinition( + id, objects, spawnRegion, playerRegion, @@ -96,11 +129,9 @@ public SpawnerModule parse(MapFactory factory, Logger logger, Document doc) delay, minDelay, maxDelay, - maxEntities, - numericID); - factory.getFeatures().addFeature(element, spawnerDefinition); + maxEntities); + factory.getFeatures().addFeature(spawnerEl, spawnerDefinition); spawnerModule.spawnerDefinitions.add(spawnerDefinition); - numericID++; } return spawnerModule.spawnerDefinitions.isEmpty() ? null : spawnerModule; diff --git a/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnableItem.java b/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnableItem.java index ae5edd4f98..84c91aeeb6 100644 --- a/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnableItem.java +++ b/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnableItem.java @@ -11,18 +11,18 @@ public class SpawnableItem implements Spawnable { - private ItemStack stack; - private String METADATA_VALUE; + private final ItemStack stack; + private final String spawnerId; - public SpawnableItem(ItemStack stack, int spawnerID) { + public SpawnableItem(ItemStack stack, String spawnerId) { this.stack = stack; - this.METADATA_VALUE = Integer.toString(spawnerID); + this.spawnerId = spawnerId; } @Override public void spawn(Location location, Match match) { Item item = location.getWorld().dropItem(location, stack); - item.setMetadata(Spawner.METADATA_KEY, new FixedMetadataValue(PGM.get(), METADATA_VALUE)); + item.setMetadata(Spawner.METADATA_KEY, new FixedMetadataValue(PGM.get(), spawnerId)); } @Override diff --git a/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnablePotion.java b/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnablePotion.java new file mode 100644 index 0000000000..f59e5753bf --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/spawner/objects/SpawnablePotion.java @@ -0,0 +1,45 @@ +package tc.oc.pgm.spawner.objects; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.Potion; +import org.bukkit.potion.PotionEffect; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.spawner.Spawnable; +import tc.oc.pgm.spawner.Spawner; +import tc.oc.pgm.util.nms.NMSHacks; + +public class SpawnablePotion implements Spawnable { + private final ItemStack potionItem; + private final String spawnerId; + + public SpawnablePotion(List potion, int damageValue, String spawnerId) { + this.spawnerId = spawnerId; + // Potion "name" determines potion color + ItemStack potionItem = new Potion(damageValue).splash().toItemStack(1); + PotionMeta potionMeta = (PotionMeta) potionItem.getItemMeta(); + for (PotionEffect effect : potion) { + potionMeta.addCustomEffect(effect, false); + } + potionItem.setItemMeta(potionMeta); + this.potionItem = potionItem; + } + + @Override + public void spawn(Location location, Match match) { + NMSHacks.EntityPotion entityPotion = new NMSHacks.EntityPotion(location, potionItem); + entityPotion + .getBukkitEntity() + .setMetadata(Spawner.METADATA_KEY, new FixedMetadataValue(PGM.get(), spawnerId)); + entityPotion.spawn(); + } + + @Override + public int getSpawnCount() { + return potionItem.getAmount(); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java index a39064a648..f964c5c09a 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java @@ -1327,6 +1327,21 @@ protected Packet spawnPacket() { public void wear(Player viewer, int slot, ItemStack item) {} } + class EntityPotion extends net.minecraft.server.v1_8_R3.EntityPotion { + public EntityPotion(Location location, ItemStack potionItem) { + super( + ((CraftWorld) location.getWorld()).getHandle(), + location.getX(), + location.getY(), + location.getZ(), + CraftItemStack.asNMSCopy(potionItem)); + } + + public void spawn() { + world.addEntity(this); + } + } + static void setFireworksExpectedLifespan(Firework firework, int ticks) { ((CraftFirework) firework).getHandle().expectedLifespan = ticks; }