From 31d593fc3c8e27be3b90940719572f2f1799d31e Mon Sep 17 00:00:00 2001 From: Pablo Herrera Date: Sun, 5 Jan 2025 18:23:40 +0100 Subject: [PATCH] Implement cuboid variable! Signed-off-by: Pablo Herrera --- .../module/exception/ModuleLoadException.java | 6 +- .../java/tc/oc/pgm/api/region/Region.java | 100 ++++++---- .../oc/pgm/api/region/RegionDefinition.java | 28 ++- .../oc/pgm/blockdrops/BlockDropsRuleSet.java | 9 +- .../pgm/controlpoint/RegionPlayerTracker.java | 8 +- .../java/tc/oc/pgm/core/CoreMatchModule.java | 12 +- .../tc/oc/pgm/destroyable/Destroyable.java | 2 +- core/src/main/java/tc/oc/pgm/flag/Flag.java | 4 +- .../java/tc/oc/pgm/map/MapFactoryImpl.java | 2 +- .../java/tc/oc/pgm/payload/PayloadRegion.java | 2 +- .../tc/oc/pgm/portals/PortalExitRegion.java | 8 +- .../java/tc/oc/pgm/regions/BlockRegion.java | 2 +- .../main/java/tc/oc/pgm/regions/Bounds.java | 7 +- .../java/tc/oc/pgm/regions/CircleRegion.java | 2 +- .../java/tc/oc/pgm/regions/Complement.java | 20 +- .../java/tc/oc/pgm/regions/CuboidRegion.java | 28 ++- .../tc/oc/pgm/regions/CylindricalRegion.java | 2 +- .../java/tc/oc/pgm/regions/EmptyRegion.java | 2 +- .../tc/oc/pgm/regions/EverywhereRegion.java | 2 +- .../tc/oc/pgm/regions/FiniteBlockRegion.java | 18 +- .../tc/oc/pgm/regions/HalfspaceRegion.java | 2 +- .../java/tc/oc/pgm/regions/Intersect.java | 24 ++- .../tc/oc/pgm/regions/MirroredRegion.java | 12 ++ .../tc/oc/pgm/regions/NegativeRegion.java | 19 +- .../java/tc/oc/pgm/regions/PointRegion.java | 2 +- .../tc/oc/pgm/regions/RectangleRegion.java | 2 +- .../java/tc/oc/pgm/regions/RegionContext.java | 21 +-- .../tc/oc/pgm/regions/RegionMatchModule.java | 7 +- .../java/tc/oc/pgm/regions/RegionParser.java | 14 +- .../java/tc/oc/pgm/regions/ResizedRegion.java | 3 +- .../java/tc/oc/pgm/regions/SectorRegion.java | 2 +- .../java/tc/oc/pgm/regions/SphereRegion.java | 5 +- .../tc/oc/pgm/regions/StaticValidation.java | 17 ++ .../tc/oc/pgm/regions/TransformedRegion.java | 17 +- .../tc/oc/pgm/regions/TranslatedRegion.java | 6 + .../main/java/tc/oc/pgm/regions/Union.java | 24 ++- .../tc/oc/pgm/regions/XMLRegionReference.java | 52 ++++-- .../java/tc/oc/pgm/renewable/Renewable.java | 28 +-- .../tc/oc/pgm/score/ScoreMatchModule.java | 5 +- .../tc/oc/pgm/snapshot/WorldSnapshot.java | 3 +- .../tc/oc/pgm/util/xml/XMLFluentParser.java | 2 +- .../tc/oc/pgm/variables/VariableParser.java | 18 +- .../pgm/variables/types/CuboidVariable.java | 171 ++++++++++++++++++ .../java/tc/oc/pgm/wool/WoolMatchModule.java | 9 +- .../main/java/tc/oc/pgm/wool/WoolModule.java | 44 ++--- .../tc/oc/pgm/util/block/BlockVectors.java | 4 + 46 files changed, 569 insertions(+), 208 deletions(-) create mode 100644 core/src/main/java/tc/oc/pgm/regions/StaticValidation.java create mode 100644 core/src/main/java/tc/oc/pgm/variables/types/CuboidVariable.java diff --git a/core/src/main/java/tc/oc/pgm/api/module/exception/ModuleLoadException.java b/core/src/main/java/tc/oc/pgm/api/module/exception/ModuleLoadException.java index bb9ffc8b71..7326f14ea9 100644 --- a/core/src/main/java/tc/oc/pgm/api/module/exception/ModuleLoadException.java +++ b/core/src/main/java/tc/oc/pgm/api/module/exception/ModuleLoadException.java @@ -9,7 +9,7 @@ public class ModuleLoadException extends RuntimeException { private final @Nullable Class key; public ModuleLoadException(Class key, String message, Throwable cause) { - super(message, cause); + super(getFullMessage(message, key), cause); this.key = key; } @@ -17,8 +17,8 @@ public ModuleLoadException(Class key, String message, Throwabl return key; } - public String getFullMessage() { - return getMessage() + (key != null ? " @ " + key.getSimpleName() : ""); + public static String getFullMessage(String message, Class key) { + return message + (key != null ? " @ " + key.getSimpleName() : ""); } public ModuleLoadException(Class key, String message) { diff --git a/core/src/main/java/tc/oc/pgm/api/region/Region.java b/core/src/main/java/tc/oc/pgm/api/region/Region.java index 9a2c54b356..8016f64a50 100644 --- a/core/src/main/java/tc/oc/pgm/api/region/Region.java +++ b/core/src/main/java/tc/oc/pgm/api/region/Region.java @@ -15,29 +15,55 @@ import org.bukkit.event.Event; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; +import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.filter.query.LocationQuery; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.filters.matcher.TypedFilter; import tc.oc.pgm.regions.Bounds; -import tc.oc.pgm.util.StreamUtils; import tc.oc.pgm.util.block.BlockVectors; import tc.oc.pgm.util.chunk.ChunkVector; import tc.oc.pgm.util.event.PlayerCoarseMoveEvent; /** Represents an arbitrary region in a Bukkit world. */ public interface Region extends TypedFilter { - /** Test if the region contains the given point */ - boolean contains(Vector point); + /** Static regions are ones which do not change depending on the world/match */ + interface Static extends Region { + /** Test if the region contains the given point */ + boolean contains(Vector point); + + /** Test if the region contains the given point */ + default boolean contains(Location point) { + return this.contains(point.toVector()); + } - /** Test if the region contains the given point */ - default boolean contains(Location point) { - return this.contains(point.toVector()); - } + /** Test if the region contains the center of the given block */ + default boolean contains(BlockVector blockPos) { + return this.contains((Vector) BlockVectors.center(blockPos)); + } - /** Test if the region contains the center of the given block */ - default boolean contains(BlockVector blockPos) { - return this.contains((Vector) BlockVectors.center(blockPos)); + /** + * Iterate over all the blocks inside this region. + * + * @throws UnsupportedOperationException if the region's blocks are not enumerable + */ + default Iterator getBlockVectorIterator() { + return Iterators.filter(this.getBounds().getBlockIterator(), this::contains); + } + + default Iterable getBlockVectors() { + return this::getBlockVectorIterator; + } + + @Override + default Static getStatic() { + if (isStatic()) return this; + throw new UnsupportedOperationException("Can't get static for non-static region"); + } } + /** Test if the region contains the given point */ + boolean contains(Location point); + /** Test if the region contains the center of the given block */ default boolean contains(Block block) { return this.contains(BlockVectors.center(block)); @@ -50,7 +76,7 @@ default boolean contains(BlockState block) { /** Test if the region contains the given entity */ default boolean contains(Entity entity) { - return this.contains(entity.getLocation().toVector()); + return this.contains(entity.getLocation()); } /** Test if the region contains the queried location */ @@ -63,26 +89,37 @@ default boolean enters(Location from, Location to) { return !this.contains(from) && this.contains(to); } - /** Test if moving from the first point to the second crosses into the region */ - default boolean enters(Vector from, Vector to) { - return !this.contains(from) && this.contains(to); - } - /** Test if moving from the first point to the second crosses out of the region */ default boolean exits(Location from, Location to) { return this.contains(from) && !this.contains(to); } - /** Test if moving from the first point to the second crosses out of the region */ - default boolean exits(Vector from, Vector to) { - return this.contains(from) && !this.contains(to); - } - /** Can this region generate evenly distributed random points? */ default boolean canGetRandom() { return false; } + default boolean isStatic() { + return false; + } + + default Static getStatic() { + if (this.isStatic() && this instanceof Static s) return s; + throw new UnsupportedOperationException("Can't get static for non-static region"); + } + + Static getStaticImpl(Match match); + + default Static getStatic(Match match) { + if (isStatic()) return getStatic(); + return getStaticImpl(match); + } + + default Static getStatic(World world) { + if (isStatic()) return getStatic(); + return getStaticImpl(PGM.get().getMatchManager().getMatch(world)); + } + /** * Gets a random point contained within this region. * @@ -126,26 +163,9 @@ default boolean matches(LocationQuery query) { return contains(query); } - /** - * Iterate over all the blocks inside this region. - * - * @throws UnsupportedOperationException if the region's blocks are not enumerable - */ - default Iterator getBlockVectorIterator() { - return Iterators.filter(this.getBounds().getBlockIterator(), this::contains); - } - - default Iterable getBlockVectors() { - return this::getBlockVectorIterator; - } - - default Stream getBlockPositions() { - return StreamUtils.of(getBlockVectorIterator()); - } - default Iterable getBlocks(World world) { - return () -> - Iterators.transform(getBlockVectorIterator(), pos -> BlockVectors.blockAt(world, pos)); + return () -> Iterators.transform( + getStatic(world).getBlockVectorIterator(), pos -> BlockVectors.blockAt(world, pos)); } default Stream getChunkPositions() { diff --git a/core/src/main/java/tc/oc/pgm/api/region/RegionDefinition.java b/core/src/main/java/tc/oc/pgm/api/region/RegionDefinition.java index 24988b71ba..f396b5022d 100644 --- a/core/src/main/java/tc/oc/pgm/api/region/RegionDefinition.java +++ b/core/src/main/java/tc/oc/pgm/api/region/RegionDefinition.java @@ -1,6 +1,32 @@ package tc.oc.pgm.api.region; +import org.bukkit.World; import tc.oc.pgm.api.filter.FilterDefinition; +import tc.oc.pgm.api.match.Match; /** A {@link tc.oc.pgm.api.region.Region} that is not a reference */ -public interface RegionDefinition extends FilterDefinition, Region {} +public interface RegionDefinition extends FilterDefinition, Region { + interface Static extends RegionDefinition, Region.Static {} + + interface HardStatic extends RegionDefinition.Static { + @Override + default boolean isStatic() { + return true; + } + + @Override + default RegionDefinition.Static getStatic(Match match) { + return this; + } + + @Override + default RegionDefinition.Static getStatic(World world) { + return this; + } + + @Override + default RegionDefinition.Static getStaticImpl(Match match) { + return this; + } + } +} diff --git a/core/src/main/java/tc/oc/pgm/blockdrops/BlockDropsRuleSet.java b/core/src/main/java/tc/oc/pgm/blockdrops/BlockDropsRuleSet.java index 81f5c979e2..d96c6ad598 100644 --- a/core/src/main/java/tc/oc/pgm/blockdrops/BlockDropsRuleSet.java +++ b/core/src/main/java/tc/oc/pgm/blockdrops/BlockDropsRuleSet.java @@ -17,7 +17,9 @@ import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.event.BlockTransformEvent; import tc.oc.pgm.api.filter.query.Query; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.player.ParticipantState; +import tc.oc.pgm.api.region.Region; import tc.oc.pgm.filters.matcher.StaticFilter; import tc.oc.pgm.filters.query.Queries; import tc.oc.pgm.kits.Kit; @@ -46,7 +48,7 @@ public ImmutableList getRules() { } /** Return the subset of rules that may act on the given region */ - public BlockDropsRuleSet subsetAffecting(FiniteBlockRegion region) { + public BlockDropsRuleSet subsetAffecting(Match match, FiniteBlockRegion region) { ImmutableList.Builder subset = ImmutableList.builder(); for (BlockDropsRule rule : this.rules) { if (rule.region == null) { @@ -54,10 +56,11 @@ public BlockDropsRuleSet subsetAffecting(FiniteBlockRegion region) { continue; } - if (Bounds.disjoint(rule.region.getBounds(), region.getBounds())) continue; + Region.Static staticReg = region.getStatic(match); + if (Bounds.disjoint(staticReg.getBounds(), region.getBounds())) continue; for (BlockVector block : region.getBlockVectors()) { - if (rule.region.contains(block)) { + if (staticReg.contains(block)) { subset.add(rule); break; } diff --git a/core/src/main/java/tc/oc/pgm/controlpoint/RegionPlayerTracker.java b/core/src/main/java/tc/oc/pgm/controlpoint/RegionPlayerTracker.java index 370cb14dbc..e575079baf 100644 --- a/core/src/main/java/tc/oc/pgm/controlpoint/RegionPlayerTracker.java +++ b/core/src/main/java/tc/oc/pgm/controlpoint/RegionPlayerTracker.java @@ -23,9 +23,9 @@ public class RegionPlayerTracker implements Listener { private final Set players = Sets.newHashSet(); // The region to check against - private Region region; + private Region.Static region; // A static filter players must match when entering - private @Nullable Filter staticFilter; + private final @Nullable Filter staticFilter; public RegionPlayerTracker(Match match, Region region) { this(match, region, null); @@ -33,7 +33,7 @@ public RegionPlayerTracker(Match match, Region region) { public RegionPlayerTracker(Match match, Region region, @Nullable Filter staticFilter) { this.match = match; - this.region = region; + this.region = region.getStatic(match); this.staticFilter = staticFilter; } @@ -41,7 +41,7 @@ public Set getPlayers() { return this.players; } - public void setRegion(Region region) { + public void setRegion(Region.Static region) { this.region = region; for (MatchPlayer player : match.getPlayers()) { handlePlayerMove(player.getBukkit(), player.getLocation().toVector()); diff --git a/core/src/main/java/tc/oc/pgm/core/CoreMatchModule.java b/core/src/main/java/tc/oc/pgm/core/CoreMatchModule.java index 9f43d6dbd8..6e6877ebb8 100644 --- a/core/src/main/java/tc/oc/pgm/core/CoreMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/core/CoreMatchModule.java @@ -16,7 +16,6 @@ import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.util.Vector; import tc.oc.pgm.api.event.BlockTransformEvent; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; @@ -64,9 +63,10 @@ public void leakCheck(final BlockTransformEvent event) { if (event.getWorld() != this.match.getWorld()) return; if (Materials.isLava(event.getNewState().getType())) { - Vector blockVector = BlockVectors.center(event.getNewState()).toVector(); + var blockVector = BlockVectors.center(event.getNewState()); // Vector ensuring it's inside leak region if it's above - Vector minVector = blockVector.clone().setY(0.5); + var minVector = blockVector.clone(); + minVector.setY(0.5); for (Core core : this.cores) { if (core.hasLeaked() || !core.getLeakRegion().contains(minVector)) continue; @@ -90,7 +90,7 @@ public void breakCheck(final BlockTransformEvent event) { if (event.getWorld() != this.match.getWorld()) return; ParticipantState player = ParticipantBlockTransformEvent.getPlayerState(event); - Vector blockVector = BlockVectors.center(event.getNewState()).toVector(); + var blockVector = BlockVectors.center(event.getNewState()); for (Core core : this.cores) { if (!core.hasLeaked() && core.getCasingRegion().contains(blockVector)) { @@ -134,7 +134,7 @@ public void damageCheck(BlockDamageEvent event) { Block block = event.getBlock(); if (block.getWorld() != this.match.getWorld()) return; MatchPlayer player = this.match.getPlayer(event.getPlayer()); - Vector center = BlockVectors.center(block).toVector(); + var center = BlockVectors.center(block); for (Core core : this.cores) { if (!core.hasLeaked() @@ -150,7 +150,7 @@ public void damageCheck(BlockDamageEvent event) { public void lavaProtection(final BlockTransformEvent event) { if (event.getWorld() != this.match.getWorld()) return; - Vector blockVector = BlockVectors.center(event.getNewState()).toVector(); + var blockVector = BlockVectors.center(event.getNewState()); for (Core core : this.cores) { if (core.getLavaRegion().contains(blockVector)) { event.setCancelled(true); diff --git a/core/src/main/java/tc/oc/pgm/destroyable/Destroyable.java b/core/src/main/java/tc/oc/pgm/destroyable/Destroyable.java index 2ff797e0d3..5862a58eae 100644 --- a/core/src/main/java/tc/oc/pgm/destroyable/Destroyable.java +++ b/core/src/main/java/tc/oc/pgm/destroyable/Destroyable.java @@ -120,7 +120,7 @@ public Destroyable(DestroyableFactory definition, Match match) { BlockDropsMatchModule bdmm = match.getModule(BlockDropsMatchModule.class); if (bdmm != null) { - this.blockDropsRuleSet = bdmm.getRuleSet().subsetAffecting(this.blockRegion); + this.blockDropsRuleSet = bdmm.getRuleSet().subsetAffecting(match, this.blockRegion); } this.recalculateHealth(); diff --git a/core/src/main/java/tc/oc/pgm/flag/Flag.java b/core/src/main/java/tc/oc/pgm/flag/Flag.java index ebdd01032d..fa8c26b759 100644 --- a/core/src/main/java/tc/oc/pgm/flag/Flag.java +++ b/core/src/main/java/tc/oc/pgm/flag/Flag.java @@ -108,8 +108,8 @@ protected Flag(Match match, FlagDefinition definition, ImmutableSet toBanner(pos.toLocation(match.getWorld()).getBlock())) + banner = StreamUtils.of(point.getRegion().getBlocks(match.getWorld())) + .map(Flag::toBanner) .filter(Objects::nonNull) .findFirst() .orElse(null); diff --git a/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java b/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java index dc7a068db4..d03f95731d 100644 --- a/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java @@ -104,7 +104,7 @@ public MapContext load() throws MapException { } catch (InvalidXMLException e) { throw new MapException(source, info, e.getMessage(), e); } catch (ModuleLoadException e) { - throw new MapException(source, info, e.getFullMessage(), e); + throw new MapException(source, info, e.getMessage(), e); } catch (JDOMParseException e) { // Set base uri so when error is displayed it shows what XML caused the issue Document d = e.getPartialDocument(); diff --git a/core/src/main/java/tc/oc/pgm/payload/PayloadRegion.java b/core/src/main/java/tc/oc/pgm/payload/PayloadRegion.java index dfddc4376a..c50b03242c 100644 --- a/core/src/main/java/tc/oc/pgm/payload/PayloadRegion.java +++ b/core/src/main/java/tc/oc/pgm/payload/PayloadRegion.java @@ -8,7 +8,7 @@ import tc.oc.pgm.regions.Bounds; /** This is a region that is not immutable. The origin point of the cylinder can move. */ -public class PayloadRegion implements RegionDefinition { +public class PayloadRegion implements RegionDefinition.HardStatic { private final Supplier base; private final double radius; diff --git a/core/src/main/java/tc/oc/pgm/portals/PortalExitRegion.java b/core/src/main/java/tc/oc/pgm/portals/PortalExitRegion.java index 4ec871bc60..752ef3da1a 100644 --- a/core/src/main/java/tc/oc/pgm/portals/PortalExitRegion.java +++ b/core/src/main/java/tc/oc/pgm/portals/PortalExitRegion.java @@ -1,12 +1,13 @@ package tc.oc.pgm.portals; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.regions.TransformedRegion; public class PortalExitRegion extends TransformedRegion { - PortalTransform portalTransform; + private final PortalTransform portalTransform; private final PortalTransform inverseTransform; public PortalExitRegion(Region entranceRegion, PortalTransform portalTransform) { @@ -24,4 +25,9 @@ protected Vector transform(Vector point) { protected Vector untransform(Vector point) { return inverseTransform.apply(point); } + + @Override + public Region.Static getStaticImpl(Match match) { + return new PortalExitRegion(region.getStatic(match), portalTransform); + } } diff --git a/core/src/main/java/tc/oc/pgm/regions/BlockRegion.java b/core/src/main/java/tc/oc/pgm/regions/BlockRegion.java index 67922cfbea..697c838b77 100644 --- a/core/src/main/java/tc/oc/pgm/regions/BlockRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/BlockRegion.java @@ -4,7 +4,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class BlockRegion implements RegionDefinition { +public class BlockRegion implements RegionDefinition.HardStatic { protected final Vector location; public BlockRegion(Vector block) { diff --git a/core/src/main/java/tc/oc/pgm/regions/Bounds.java b/core/src/main/java/tc/oc/pgm/regions/Bounds.java index c42eab414b..aac070623b 100644 --- a/core/src/main/java/tc/oc/pgm/regions/Bounds.java +++ b/core/src/main/java/tc/oc/pgm/regions/Bounds.java @@ -237,12 +237,7 @@ public Iterator getBlockIterator() { } public Iterable getBlocks() { - return new Iterable() { - @Override - public Iterator iterator() { - return getBlockIterator(); - } - }; + return this::getBlockIterator; } @Override diff --git a/core/src/main/java/tc/oc/pgm/regions/CircleRegion.java b/core/src/main/java/tc/oc/pgm/regions/CircleRegion.java index 0cb6b37ba5..d2eca51861 100644 --- a/core/src/main/java/tc/oc/pgm/regions/CircleRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/CircleRegion.java @@ -3,7 +3,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class CircleRegion implements RegionDefinition { +public class CircleRegion implements RegionDefinition.HardStatic { protected final double x; protected final double z; protected final double radius; diff --git a/core/src/main/java/tc/oc/pgm/regions/Complement.java b/core/src/main/java/tc/oc/pgm/regions/Complement.java index 31d4d2b72e..7adc8223bb 100644 --- a/core/src/main/java/tc/oc/pgm/regions/Complement.java +++ b/core/src/main/java/tc/oc/pgm/regions/Complement.java @@ -1,10 +1,12 @@ package tc.oc.pgm.regions; +import org.bukkit.Location; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; -public class Complement implements RegionDefinition { +public class Complement implements RegionDefinition.Static { private final Region original; private final Region subtracted; @@ -15,6 +17,12 @@ public Complement(Region original, Region... subtracted) { @Override public boolean contains(Vector point) { + return this.original.getStatic().contains(point) + && !this.subtracted.getStatic().contains(point); + } + + @Override + public boolean contains(Location point) { return this.original.contains(point) && !this.subtracted.contains(point); } @@ -23,6 +31,16 @@ public boolean isBlockBounded() { return this.original.isBlockBounded(); } + @Override + public Region.Static getStaticImpl(Match match) { + return new Complement(original.getStatic(match), subtracted.getStatic(match)); + } + + @Override + public boolean isStatic() { + return original.isStatic() && subtracted.isStatic(); + } + @Override public boolean isEmpty() { return this.original.isEmpty(); diff --git a/core/src/main/java/tc/oc/pgm/regions/CuboidRegion.java b/core/src/main/java/tc/oc/pgm/regions/CuboidRegion.java index 4fd94d228c..6db5a45ebd 100644 --- a/core/src/main/java/tc/oc/pgm/regions/CuboidRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/CuboidRegion.java @@ -4,11 +4,15 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class CuboidRegion implements RegionDefinition { - private final Bounds bounds; +public class CuboidRegion implements RegionDefinition.HardStatic { + protected final Bounds bounds; public CuboidRegion(Vector pos1, Vector pos2) { - this.bounds = new Bounds(Vector.getMinimum(pos1, pos2), Vector.getMaximum(pos1, pos2)); + this(new Bounds(Vector.getMinimum(pos1, pos2), Vector.getMaximum(pos1, pos2))); + } + + private CuboidRegion(Bounds bounds) { + this.bounds = bounds; } @Override @@ -47,6 +51,24 @@ private double randomRange(Random random, double min, double max) { return (max - min) * random.nextDouble() + min; } + public Mutable asMutableCopy() { + return new Mutable(this.bounds.clone()); + } + + public static class Mutable extends CuboidRegion { + public Mutable(Bounds bounds) { + super(bounds); + } + + public Vector getMutableMin() { + return bounds.min; + } + + public Vector getMutableMax() { + return bounds.max; + } + } + @Override public String toString() { return "CuboidRegion{min=[" diff --git a/core/src/main/java/tc/oc/pgm/regions/CylindricalRegion.java b/core/src/main/java/tc/oc/pgm/regions/CylindricalRegion.java index 4e220e5c29..a41648db29 100644 --- a/core/src/main/java/tc/oc/pgm/regions/CylindricalRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/CylindricalRegion.java @@ -6,7 +6,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class CylindricalRegion implements RegionDefinition { +public class CylindricalRegion implements RegionDefinition.HardStatic { private final Vector base; private final double radius; private final double radiusSq; diff --git a/core/src/main/java/tc/oc/pgm/regions/EmptyRegion.java b/core/src/main/java/tc/oc/pgm/regions/EmptyRegion.java index 37b701d32e..e41ffe13ce 100644 --- a/core/src/main/java/tc/oc/pgm/regions/EmptyRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/EmptyRegion.java @@ -3,7 +3,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class EmptyRegion implements RegionDefinition { +public class EmptyRegion implements RegionDefinition.HardStatic { public static final EmptyRegion INSTANCE = new EmptyRegion(); private EmptyRegion() {} diff --git a/core/src/main/java/tc/oc/pgm/regions/EverywhereRegion.java b/core/src/main/java/tc/oc/pgm/regions/EverywhereRegion.java index 874972f892..77efe70c58 100644 --- a/core/src/main/java/tc/oc/pgm/regions/EverywhereRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/EverywhereRegion.java @@ -4,7 +4,7 @@ import tc.oc.pgm.api.region.RegionDefinition; /** Wherever you go, here you are */ -public class EverywhereRegion implements RegionDefinition { +public class EverywhereRegion implements RegionDefinition.HardStatic { public static final EverywhereRegion INSTANCE = new EverywhereRegion(); private EverywhereRegion() {} diff --git a/core/src/main/java/tc/oc/pgm/regions/FiniteBlockRegion.java b/core/src/main/java/tc/oc/pgm/regions/FiniteBlockRegion.java index fbc1ed8561..75afef795d 100644 --- a/core/src/main/java/tc/oc/pgm/regions/FiniteBlockRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/FiniteBlockRegion.java @@ -8,7 +8,6 @@ import java.util.Random; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.util.BlockVector; @@ -19,6 +18,7 @@ import tc.oc.pgm.api.region.RegionDefinition; import tc.oc.pgm.filters.matcher.block.MaterialFilter; import tc.oc.pgm.filters.query.BlockQuery; +import tc.oc.pgm.util.StreamUtils; import tc.oc.pgm.util.Version; import tc.oc.pgm.util.block.BlockVectorSet; import tc.oc.pgm.util.block.BlockVectors; @@ -28,7 +28,7 @@ * Region represented by a list of single blocks. This will check if a point is inside the block at * all. */ -public class FiniteBlockRegion implements RegionDefinition { +public class FiniteBlockRegion implements RegionDefinition.HardStatic { private final BlockVectorSet positions; private final Bounds bounds; @@ -92,11 +92,6 @@ public Iterable getBlockVectors() { return positions; } - @Override - public Stream getBlockPositions() { - return positions.stream(); - } - public int getBlockVolume() { return positions.size(); } @@ -126,10 +121,9 @@ public static FiniteBlockRegion fromWorld( region = new CuboidRegion(bounds.getMin(), bounds.getMax().add(new Vector(1, 1, 1))); } - return new FiniteBlockRegion( - region - .getBlockPositions() - .filter(pos -> filter.test(BlockVectors.blockAt(world, pos))) - .collect(Collectors.toCollection(BlockVectorSet::new))); + return new FiniteBlockRegion(StreamUtils.of(region.getBlocks(world)) + .filter(filter) + .map(BlockVectors::position) + .collect(Collectors.toCollection(BlockVectorSet::new))); } } diff --git a/core/src/main/java/tc/oc/pgm/regions/HalfspaceRegion.java b/core/src/main/java/tc/oc/pgm/regions/HalfspaceRegion.java index 0265d72057..0f7b265418 100644 --- a/core/src/main/java/tc/oc/pgm/regions/HalfspaceRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/HalfspaceRegion.java @@ -3,7 +3,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class HalfspaceRegion implements RegionDefinition { +public class HalfspaceRegion implements RegionDefinition.HardStatic { private final Vector normal; // unit normal private final double offset; // parameter of the plane equation diff --git a/core/src/main/java/tc/oc/pgm/regions/Intersect.java b/core/src/main/java/tc/oc/pgm/regions/Intersect.java index d7cc3e8629..99cc131af9 100644 --- a/core/src/main/java/tc/oc/pgm/regions/Intersect.java +++ b/core/src/main/java/tc/oc/pgm/regions/Intersect.java @@ -1,10 +1,11 @@ package tc.oc.pgm.regions; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; -public class Intersect implements RegionDefinition { +public class Intersect implements RegionDefinition.Static { private final Region[] regions; public Intersect(Region... regions) { @@ -14,7 +15,7 @@ public Intersect(Region... regions) { @Override public boolean contains(Vector point) { for (Region region : this.regions) { - if (!region.contains(point)) { + if (!region.getStatic().contains(point)) { return false; } } @@ -31,6 +32,16 @@ public boolean isBlockBounded() { return false; } + @Override + public boolean isStatic() { + for (Region region : this.regions) { + if (!region.isStatic()) { + return false; + } + } + return true; + } + @Override public boolean isEmpty() { for (Region region : this.regions) { @@ -41,6 +52,15 @@ public boolean isEmpty() { return false; } + @Override + public Region.Static getStaticImpl(Match match) { + Region[] regions = new Region[this.regions.length]; + for (int i = 0; i < this.regions.length; i++) { + regions[i] = this.regions[i].getStatic(match); + } + return new Intersect(regions); + } + @Override public Bounds getBounds() { Bounds bounds = Bounds.unbounded(); diff --git a/core/src/main/java/tc/oc/pgm/regions/MirroredRegion.java b/core/src/main/java/tc/oc/pgm/regions/MirroredRegion.java index cc5db3d990..f7e66c0c2a 100644 --- a/core/src/main/java/tc/oc/pgm/regions/MirroredRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/MirroredRegion.java @@ -1,6 +1,7 @@ package tc.oc.pgm.regions; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; public class MirroredRegion extends TransformedRegion { @@ -19,6 +20,12 @@ public MirroredRegion(Region region, Vector origin, Vector normal) { this.offset = this.normal.dot(origin); } + private MirroredRegion(Region region, Vector normal, double offset) { + super(region); + this.normal = normal; + this.offset = offset; + } + @Override protected Vector transform(Vector point) { // FYI, reflection is 2x the projection of the point on the normal @@ -30,4 +37,9 @@ protected Vector transform(Vector point) { protected Vector untransform(Vector point) { return this.transform(point); } + + @Override + public Region.Static getStaticImpl(Match match) { + return new MirroredRegion(region.getStatic(match), normal, offset); + } } diff --git a/core/src/main/java/tc/oc/pgm/regions/NegativeRegion.java b/core/src/main/java/tc/oc/pgm/regions/NegativeRegion.java index c94174a8ed..58314fedba 100644 --- a/core/src/main/java/tc/oc/pgm/regions/NegativeRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/NegativeRegion.java @@ -1,10 +1,12 @@ package tc.oc.pgm.regions; +import org.bukkit.Location; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; -public class NegativeRegion implements RegionDefinition { +public class NegativeRegion implements RegionDefinition.Static { protected final Region region; public NegativeRegion(Region region) { @@ -13,9 +15,24 @@ public NegativeRegion(Region region) { @Override public boolean contains(Vector point) { + return !this.region.getStatic().contains(point); + } + + @Override + public boolean contains(Location point) { return !this.region.contains(point); } + @Override + public boolean isStatic() { + return region.isStatic(); + } + + @Override + public Region.Static getStaticImpl(Match match) { + return new NegativeRegion(region.getStatic(match)); + } + @Override public Bounds getBounds() { throw new UnsupportedOperationException("NegativeRegion is unbounded"); diff --git a/core/src/main/java/tc/oc/pgm/regions/PointRegion.java b/core/src/main/java/tc/oc/pgm/regions/PointRegion.java index ddd0519a0b..dc01df847c 100644 --- a/core/src/main/java/tc/oc/pgm/regions/PointRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/PointRegion.java @@ -4,7 +4,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class PointRegion implements RegionDefinition { +public class PointRegion implements RegionDefinition.HardStatic { private final Vector position; diff --git a/core/src/main/java/tc/oc/pgm/regions/RectangleRegion.java b/core/src/main/java/tc/oc/pgm/regions/RectangleRegion.java index 221d61eef3..9385262e5b 100644 --- a/core/src/main/java/tc/oc/pgm/regions/RectangleRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/RectangleRegion.java @@ -3,7 +3,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class RectangleRegion implements RegionDefinition { +public class RectangleRegion implements RegionDefinition.HardStatic { protected final double minX; protected final double minZ; protected final double maxX; diff --git a/core/src/main/java/tc/oc/pgm/regions/RegionContext.java b/core/src/main/java/tc/oc/pgm/regions/RegionContext.java index dc16fa77af..8d92f8b2ff 100644 --- a/core/src/main/java/tc/oc/pgm/regions/RegionContext.java +++ b/core/src/main/java/tc/oc/pgm/regions/RegionContext.java @@ -1,8 +1,5 @@ package tc.oc.pgm.regions; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.util.Vector; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.util.collection.ContextStore; @@ -12,20 +9,4 @@ *

The RegionManager correlates regions with names so they can be looked up and resolved at a * later time. */ -public class RegionContext extends ContextStore { - /** - * Gets all regions that contain the given point. - * - * @param point Point to check against. - * @return Regions where region.contains(point) == true - */ - public List getContaining(Vector point) { - List result = new ArrayList(); - for (Region region : this.store.values()) { - if (region.contains(point)) { - result.add(region); - } - } - return result; - } -} +public class RegionContext extends ContextStore {} diff --git a/core/src/main/java/tc/oc/pgm/regions/RegionMatchModule.java b/core/src/main/java/tc/oc/pgm/regions/RegionMatchModule.java index 5eecb23e61..5ef1ff2c25 100644 --- a/core/src/main/java/tc/oc/pgm/regions/RegionMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/regions/RegionMatchModule.java @@ -27,7 +27,6 @@ import org.bukkit.event.player.PlayerBucketEmptyEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.event.BlockTransformEvent; import tc.oc.pgm.api.filter.Filter.QueryResponse; @@ -135,8 +134,8 @@ public void applyEffects(final PlayerCoarseMoveEvent event) { MatchPlayer player = this.match.getPlayer(event.getPlayer()); if (player == null) return; - Vector from = event.getBlockFrom().toVector(); - Vector to = event.getBlockTo().toVector(); + var from = event.getBlockFrom(); + var to = event.getBlockTo(); Query query = new tc.oc.pgm.filters.query.PlayerQuery(event, player); for (RegionFilterApplication rfa : this.rfaContext.get(RFAScope.EFFECT)) { @@ -170,7 +169,7 @@ public void applyEffects(final PlayerCoarseMoveEvent event) { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void checkBlockTransform(final BlockTransformEvent event) { - Vector pos = BlockVectors.center(event.getNewState()).toVector(); + var pos = BlockVectors.center(event.getNewState()); ParticipantState actor = this.getActor(event); BlockState againstBlock = null; diff --git a/core/src/main/java/tc/oc/pgm/regions/RegionParser.java b/core/src/main/java/tc/oc/pgm/regions/RegionParser.java index 6153d24110..ce2ad45b2e 100644 --- a/core/src/main/java/tc/oc/pgm/regions/RegionParser.java +++ b/core/src/main/java/tc/oc/pgm/regions/RegionParser.java @@ -140,14 +140,11 @@ protected RegionDefinition parseHalves(Element el, double dir) throws InvalidXML if (y != null) halves.add(new HalfspaceRegion(new Vector(0, y, 0), new Vector(0, dir, 0))); if (z != null) halves.add(new HalfspaceRegion(new Vector(0, 0, z), new Vector(0, 0, dir))); - switch (halves.size()) { - case 0: - throw new InvalidXMLException("Expected at least one of x, y, or z attributes", el); - case 1: - return halves.get(0); - default: - return new Intersect((Region[]) halves.toArray()); - } + return switch (halves.size()) { + case 0 -> throw new InvalidXMLException("Expected at least one of x, y, or z attributes", el); + case 1 -> halves.getFirst(); + default -> new Intersect(halves.toArray(Region[]::new)); + }; } @MethodParser("below") @@ -301,6 +298,7 @@ public ResizedRegion parseResize(Element el) throws InvalidXMLException { Vector max = parser.vector(el, "max").attr().required(); boolean relative = parser.parseBool(el, "relative").attr().orFalse(); validate(child, BlockBoundedValidation.INSTANCE, new Node(el)); + validate(child, StaticValidation.INSTANCE, new Node(el)); return new ResizedRegion(child, min, max, relative); } diff --git a/core/src/main/java/tc/oc/pgm/regions/ResizedRegion.java b/core/src/main/java/tc/oc/pgm/regions/ResizedRegion.java index f2d7fb81e4..0fcdec01c4 100644 --- a/core/src/main/java/tc/oc/pgm/regions/ResizedRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/ResizedRegion.java @@ -2,9 +2,10 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.Region; +import tc.oc.pgm.api.region.RegionDefinition; import tc.oc.pgm.util.math.TransformMatrix; -public class ResizedRegion extends TransformedRegion { +public class ResizedRegion extends TransformedRegion implements RegionDefinition.HardStatic { private final Vector min, max; private final boolean relative; private TransformMatrix matrix; diff --git a/core/src/main/java/tc/oc/pgm/regions/SectorRegion.java b/core/src/main/java/tc/oc/pgm/regions/SectorRegion.java index 4555191eab..f59e2446a3 100644 --- a/core/src/main/java/tc/oc/pgm/regions/SectorRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/SectorRegion.java @@ -3,7 +3,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class SectorRegion implements RegionDefinition { +public class SectorRegion implements RegionDefinition.HardStatic { protected final double x, z; protected final double startAngle; protected final double endAngle; diff --git a/core/src/main/java/tc/oc/pgm/regions/SphereRegion.java b/core/src/main/java/tc/oc/pgm/regions/SphereRegion.java index 21fb587a05..7a622f4d38 100644 --- a/core/src/main/java/tc/oc/pgm/regions/SphereRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/SphereRegion.java @@ -5,7 +5,7 @@ import org.bukkit.util.Vector; import tc.oc.pgm.api.region.RegionDefinition; -public class SphereRegion implements RegionDefinition { +public class SphereRegion implements RegionDefinition.HardStatic { protected final Vector origin; protected final double radius; protected final double radiusSq; @@ -39,7 +39,8 @@ public boolean isBlockBounded() { @Override public Bounds getBounds() { Vector diagonal = new Vector(this.radius, this.radius, this.radius); - return new Bounds(this.origin.clone().subtract(diagonal), this.origin.clone().add(diagonal)); + return new Bounds( + this.origin.clone().subtract(diagonal), this.origin.clone().add(diagonal)); } @Override diff --git a/core/src/main/java/tc/oc/pgm/regions/StaticValidation.java b/core/src/main/java/tc/oc/pgm/regions/StaticValidation.java new file mode 100644 index 0000000000..42c6ca0747 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/regions/StaticValidation.java @@ -0,0 +1,17 @@ +package tc.oc.pgm.regions; + +import tc.oc.pgm.api.feature.FeatureValidation; +import tc.oc.pgm.api.region.RegionDefinition; +import tc.oc.pgm.util.xml.InvalidXMLException; +import tc.oc.pgm.util.xml.Node; + +public class StaticValidation implements FeatureValidation { + public static final StaticValidation INSTANCE = new StaticValidation(); + + @Override + public void validate(RegionDefinition definition, Node node) throws InvalidXMLException { + if (!definition.isStatic()) { + throw new InvalidXMLException("Cannot use non-static region here", node); + } + } +} diff --git a/core/src/main/java/tc/oc/pgm/regions/TransformedRegion.java b/core/src/main/java/tc/oc/pgm/regions/TransformedRegion.java index 4f20a6f576..bf36d5dfb0 100644 --- a/core/src/main/java/tc/oc/pgm/regions/TransformedRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/TransformedRegion.java @@ -1,12 +1,13 @@ package tc.oc.pgm.regions; import java.util.Random; +import org.bukkit.Location; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; -public abstract class TransformedRegion implements RegionDefinition { +public abstract class TransformedRegion implements RegionDefinition.Static { protected final Region region; protected @Nullable Bounds bounds; @@ -20,6 +21,11 @@ public boolean isBlockBounded() { return this.region.isBlockBounded(); } + @Override + public boolean isStatic() { + return region.isStatic(); + } + @Override public boolean isEmpty() { return this.region.isEmpty(); @@ -48,7 +54,14 @@ protected Bounds getTransformedBounds() { @Override public boolean contains(Vector point) { - return this.region.contains(this.untransform(point)); + return this.region.getStatic().contains(this.untransform(point)); + } + + @Override + public boolean contains(Location point) { + if (isStatic()) return contains(point.toVector()); + var vec = untransform(point.toVector()); + return this.region.contains(new Location(point.getWorld(), vec.getX(), vec.getY(), vec.getZ())); } @Override diff --git a/core/src/main/java/tc/oc/pgm/regions/TranslatedRegion.java b/core/src/main/java/tc/oc/pgm/regions/TranslatedRegion.java index 5fb99de12e..879de5ea09 100644 --- a/core/src/main/java/tc/oc/pgm/regions/TranslatedRegion.java +++ b/core/src/main/java/tc/oc/pgm/regions/TranslatedRegion.java @@ -1,6 +1,7 @@ package tc.oc.pgm.regions; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; /** Region adaptor that applies a translation. */ @@ -30,4 +31,9 @@ protected Vector untransform(Vector point) { protected Bounds getTransformedBounds() { return this.region.getBounds().translate(this.offset); } + + @Override + public Region.Static getStaticImpl(Match match) { + return new TranslatedRegion(region.getStatic(match), offset); + } } diff --git a/core/src/main/java/tc/oc/pgm/regions/Union.java b/core/src/main/java/tc/oc/pgm/regions/Union.java index a96d1beadd..5f16fbc1fb 100644 --- a/core/src/main/java/tc/oc/pgm/regions/Union.java +++ b/core/src/main/java/tc/oc/pgm/regions/Union.java @@ -1,10 +1,11 @@ package tc.oc.pgm.regions; import org.bukkit.util.Vector; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; -public class Union implements RegionDefinition { +public class Union implements RegionDefinition.Static { private final Region[] regions; public Union(Region... regions) { @@ -24,7 +25,7 @@ public Region[] getRegions() { @Override public boolean contains(Vector point) { for (Region region : this.regions) { - if (region.contains(point)) { + if (region.getStatic().contains(point)) { return true; } } @@ -41,6 +42,16 @@ public boolean isBlockBounded() { return true; } + @Override + public boolean isStatic() { + for (Region region : this.regions) { + if (!region.isStatic()) { + return false; + } + } + return true; + } + @Override public boolean isEmpty() { for (Region region : this.regions) { @@ -51,6 +62,15 @@ public boolean isEmpty() { return true; } + @Override + public Region.Static getStaticImpl(Match match) { + Region[] regions = new Region[this.regions.length]; + for (int i = 0; i < this.regions.length; i++) { + regions[i] = this.regions[i].getStatic(match); + } + return new Union(regions); + } + @Override public Bounds getBounds() { Bounds bounds = Bounds.empty(); diff --git a/core/src/main/java/tc/oc/pgm/regions/XMLRegionReference.java b/core/src/main/java/tc/oc/pgm/regions/XMLRegionReference.java index ccb66cf34c..1c04fbe0d4 100644 --- a/core/src/main/java/tc/oc/pgm/regions/XMLRegionReference.java +++ b/core/src/main/java/tc/oc/pgm/regions/XMLRegionReference.java @@ -14,6 +14,7 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.filter.query.LocationQuery; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.region.Region; import tc.oc.pgm.api.region.RegionDefinition; import tc.oc.pgm.features.FeatureDefinitionContext; @@ -21,7 +22,8 @@ import tc.oc.pgm.util.chunk.ChunkVector; import tc.oc.pgm.util.xml.Node; -public class XMLRegionReference extends XMLFeatureReference implements Region { +public class XMLRegionReference extends XMLFeatureReference + implements Region.Static { public XMLRegionReference( FeatureDefinitionContext context, @@ -33,7 +35,7 @@ public XMLRegionReference( @Override public boolean contains(Vector point) { - return get().contains(point); + return getStatic().contains(point); } @Override @@ -43,7 +45,7 @@ public boolean contains(Location point) { @Override public boolean contains(BlockVector pos) { - return get().contains(pos); + return getStatic().contains(pos); } @Override @@ -71,21 +73,11 @@ public boolean enters(Location from, Location to) { return get().enters(from, to); } - @Override - public boolean enters(Vector from, Vector to) { - return get().enters(from, to); - } - @Override public boolean exits(Location from, Location to) { return get().exits(from, to); } - @Override - public boolean exits(Vector from, Vector to) { - return get().exits(from, to); - } - @Override public boolean canGetRandom() { return get().canGetRandom(); @@ -101,6 +93,31 @@ public boolean isBlockBounded() { return get().isBlockBounded(); } + @Override + public boolean isStatic() { + return get().isStatic(); + } + + @Override + public Region.Static getStatic() { + return get().getStatic(); + } + + @Override + public Region.Static getStaticImpl(Match match) { + return get().getStaticImpl(match); + } + + @Override + public Region.Static getStatic(Match match) { + return get().getStatic(match); + } + + @Override + public Region.Static getStatic(World world) { + return get().getStatic(world); + } + @Override public Bounds getBounds() { return get().getBounds(); @@ -113,12 +130,12 @@ public boolean isEmpty() { @Override public Iterator getBlockVectorIterator() { - return get().getBlockVectorIterator(); + return getStatic().getBlockVectorIterator(); } @Override public Iterable getBlockVectors() { - return get().getBlockVectors(); + return getStatic().getBlockVectors(); } @Override @@ -131,11 +148,6 @@ public boolean matches(LocationQuery query) { return get().matches(query); } - @Override - public Stream getBlockPositions() { - return get().getBlockPositions(); - } - @Override public Iterable getBlocks(World world) { return get().getBlocks(world); diff --git a/core/src/main/java/tc/oc/pgm/renewable/Renewable.java b/core/src/main/java/tc/oc/pgm/renewable/Renewable.java index 4805e5f709..e4eb297cc5 100644 --- a/core/src/main/java/tc/oc/pgm/renewable/Renewable.java +++ b/core/src/main/java/tc/oc/pgm/renewable/Renewable.java @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableRangeMap; import com.google.common.collect.Range; -import com.google.common.collect.RangeMap; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -82,8 +81,9 @@ SnapshotMatchModule snapshot() { return snapshotMatchModule; } - boolean isOriginalRenewable(BlockVector pos) { - if (!definition.region.contains(pos)) return false; + boolean isOriginalRenewable(Block block) { + if (!definition.region.contains(block)) return false; + BlockVector pos = BlockVectors.position(block); Filter.QueryResponse response = renewableCache.get(pos); if (response == null) { response = definition.renewableBlocks.query(new BlockQuery(snapshot().getOriginalBlock(pos))); @@ -91,8 +91,9 @@ boolean isOriginalRenewable(BlockVector pos) { return response.isAllowed(); } - boolean isOriginalShuffleable(BlockVector pos) { - if (!definition.region.contains(pos)) return false; + boolean isOriginalShuffleable(Block block) { + if (!definition.region.contains(block)) return false; + BlockVector pos = BlockVectors.position(block); Filter.QueryResponse response = shuffleableCache.get(pos); if (response == null) { response = @@ -113,7 +114,7 @@ public void onBlockChange(BlockTransformEvent event) { } } - if (isOriginalShuffleable(BlockVectors.position(newState))) { + if (isOriginalShuffleable(event.getBlock())) { if (definition.shuffleableBlocks.query(new BlockQuery(oldState)).isAllowed()) { shuffleableMaterialDeficit.increment(oldState, 1); } @@ -158,17 +159,17 @@ void updateRenewablePool(BlockState block) { boolean isNew(BlockState currentState) { // If original block does not match renewable rule, block is new - BlockVector pos = BlockVectors.position(currentState); - if (!isOriginalRenewable(pos)) return true; + if (!isOriginalRenewable(currentState.getBlock())) return true; // If original and current world are both shuffleable, block is new MaterialData currentMaterial = MaterialData.block(currentState); - if (isOriginalShuffleable(pos) + if (isOriginalShuffleable(currentState.getBlock()) && definition.shuffleableBlocks.query(new BlockQuery(currentState)).isAllowed()) return true; // If current world matches original, block is new - if (currentMaterial.equals(snapshot().getOriginalMaterial(pos))) return true; + if (currentMaterial.equals(snapshot().getOriginalMaterial(BlockVectors.position(currentState)))) + return true; // Otherwise, block is not new (can be renewed) return false; @@ -225,8 +226,7 @@ BlockMaterialData sampleShuffledMaterial(BlockVector pos) { } BlockMaterialData chooseShuffledMaterial() { - ImmutableRangeMap.Builder weightsBuilder = - ImmutableRangeMap.builder(); + var weightsBuilder = ImmutableRangeMap.builder(); double sum = 0d; for (BlockMaterialData material : shuffleableMaterialDeficit.materials()) { double weight = shuffleableMaterialDeficit.get(material); @@ -235,13 +235,13 @@ BlockMaterialData chooseShuffledMaterial() { sum += weight; } } - RangeMap weights = weightsBuilder.build(); + var weights = weightsBuilder.build(); return weights.get(match.getRandom().nextDouble() * sum); } boolean renew(BlockVector pos) { BlockMaterialData material; - if (isOriginalShuffleable(pos)) { + if (isOriginalShuffleable(BlockVectors.blockAt(match.getWorld(), pos))) { // If position is shuffled, first try to find a nearby shuffleable block to swap with. // This helps to make shuffling less predictable when the world deficit is small or // out of proportion to the original distribution of world. diff --git a/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java b/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java index 71f0975659..0fec2f6310 100644 --- a/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/score/ScoreMatchModule.java @@ -22,7 +22,6 @@ import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.filter.Filter; @@ -212,8 +211,8 @@ public void playerEnterBox(PlayerCoarseMoveEvent event) { MatchPlayer player = this.match.getPlayer(event.getPlayer()); if (player == null || !player.canInteract() || player.getBukkit().isDead()) return; - Vector from = event.getBlockFrom().toVector(); - Vector to = event.getBlockTo().toVector(); + var from = event.getBlockFrom(); + var to = event.getBlockTo(); for (ScoreBox box : this.scoreBoxes) { if (box.getRegion().enters(from, to) && box.canScore(player)) { diff --git a/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java b/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java index e897b81aa7..6d702ac2af 100644 --- a/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java +++ b/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java @@ -97,6 +97,7 @@ public void removeBlocks(Region region, BlockVector offset, boolean update) { * @param region the region to get block states from */ public Iterable getMaterials(Region region) { - return () -> MaterialData.iterator(chunkSnapshots, region.getBlockVectorIterator()); + return () -> + MaterialData.iterator(chunkSnapshots, region.getStatic(world).getBlockVectorIterator()); } } diff --git a/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java b/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java index acd207a777..929a8aac89 100644 --- a/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java +++ b/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java @@ -52,7 +52,6 @@ public void init() { this.filters = factory.getFilters(); this.regions = factory.getRegions(); this.kits = factory.getKits(); - this.variables = factory.needModule(VariablesModule.class); } public BoolBuilder parseBool(Element el, String... prop) { @@ -171,6 +170,7 @@ public ActionParser getActionParser() { public > Builder, ?> formula( Class clazz, Element el, String... prop) { + if (variables == null) this.variables = factory.needModule(VariablesModule.class); return new Builder.Generic<>(el, prop) { @Override protected Formula parse(Node node) { diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java index 78532e523d..985ec26c08 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java @@ -2,6 +2,7 @@ import com.google.common.collect.Range; import java.lang.reflect.Method; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; import org.jdom2.Element; @@ -10,6 +11,7 @@ import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.party.Party; import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.features.FeatureDefinitionContext; import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.teams.TeamFactory; import tc.oc.pgm.util.MethodParser; @@ -18,6 +20,7 @@ import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; import tc.oc.pgm.variables.types.ArrayVariable; +import tc.oc.pgm.variables.types.CuboidVariable; import tc.oc.pgm.variables.types.DummyVariable; import tc.oc.pgm.variables.types.LivesVariable; import tc.oc.pgm.variables.types.MaxBuildVariable; @@ -28,7 +31,7 @@ public class VariableParser { // The limitation is due to them being used in exp4j formulas for. - public static final Pattern VARIABLE_ID = Pattern.compile("[A-Za-z_]\\w*"); + public static final Pattern VARIABLE_ID = Pattern.compile("[A-Za-z_][\\w.]*"); private final MapFactory factory; private final Map methodParsers; @@ -40,7 +43,7 @@ public VariableParser(MapFactory factory) { public Variable parse(Element el) throws InvalidXMLException { String id = Node.fromRequiredAttr(el, "id").getValue(); - if (!VARIABLE_ID.matcher(id).matches()) + if (!VARIABLE_ID.matcher(id).matches() || id.contains(".")) throw new InvalidXMLException( "Variable IDs must start with a letter or underscore and can only include letters, digits or underscores.", el); @@ -117,4 +120,15 @@ public Variable parsePlayerLocation(Element el) throws InvalidXMLEx XMLUtils.parseEnum(Node.fromAttr(el, "component"), PlayerLocationVariable.Component.class); return PlayerLocationVariable.INSTANCES.get(component); } + + @MethodParser("cuboid") + public Variable parseCuboid(Element el) throws InvalidXMLException { + String baseId = FeatureDefinitionContext.parseId(el); + var variable = new CuboidVariable(factory.getRegions().parseCuboid(el)); + for (CuboidVariable.Component component : CuboidVariable.Component.values()) { + var subId = baseId + "." + component.name().toLowerCase(Locale.ROOT); + factory.getFeatures().addFeature(el, subId, variable.getComponent(component)); + } + return variable; + } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/CuboidVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/CuboidVariable.java new file mode 100644 index 0000000000..1e3b5b1e96 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/CuboidVariable.java @@ -0,0 +1,171 @@ +package tc.oc.pgm.variables.types; + +import java.util.function.Function; +import java.util.function.ObjDoubleConsumer; +import java.util.function.ToDoubleFunction; +import java.util.logging.Level; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.util.Vector; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.api.region.Region; +import tc.oc.pgm.api.region.RegionDefinition; +import tc.oc.pgm.features.StateHolder; +import tc.oc.pgm.filters.FilterMatchModule; +import tc.oc.pgm.filters.Filterable; +import tc.oc.pgm.regions.Bounds; +import tc.oc.pgm.regions.CuboidRegion; +import tc.oc.pgm.variables.Variable; +import tc.oc.pgm.variables.VariablesMatchModule; + +public class CuboidVariable extends AbstractVariable + implements Variable.Indexed, StateHolder, RegionDefinition { + private static final Component[] COMPONENTS = Component.values(); + private final CuboidRegion cuboid; + + private Cached lastLookup = null; + + public CuboidVariable(CuboidRegion cuboid) { + super(Match.class); + this.cuboid = cuboid; + } + + @Override + public void load(Match match) { + match.getFeatureContext().registerState(this, cuboid.asMutableCopy()); + } + + @Override + public boolean isDynamic() { + return false; + } + + record Cached(CuboidRegion.Mutable cuboid, String world) {} + + private CuboidRegion.Mutable getCuboid(World world) { + var cached = lastLookup; + if (cached == null || !cached.world.equals(world.getName())) { + var match = PGM.get().getMatchManager().getMatch(world); + if (match == null) throw new IllegalStateException("Couldn't find match"); + cached = lastLookup = new Cached(match.state(this), world.getName()); + } + return cached.cuboid; + } + + public int checkBounds(int idx, Filterable obj) { + if (idx < 0 || idx >= COMPONENTS.length) { + String id = obj.moduleRequire(VariablesMatchModule.class).getId(this); + PGM.get() + .getGameLogger() + .log(Level.SEVERE, String.format("Index %d out of bounds for cuboid %s", idx, id)); + return 0; + } + return idx; + } + + @Override + public double getValue(Filterable obj, int idx) { + return getComponent(obj, COMPONENTS[checkBounds(idx, obj)]); + } + + @Override + public void setValue(Filterable obj, int idx, double value) { + setComponent(obj, COMPONENTS[checkBounds(idx, obj)], value); + + // For performance reasons, let's avoid launching an event for every variable change + obj.moduleRequire(FilterMatchModule.class).invalidate(obj); + } + + @Override + public int size() { + return COMPONENTS.length; + } + + @Override + protected double getValueImpl(Match obj) { + throw new UnsupportedOperationException(); + } + + @Override + protected void setValueImpl(Match obj, double value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(Location point) { + return getCuboid(point.getWorld()).contains(point.toVector()); + } + + @Override + public Region.Static getStaticImpl(Match match) { + return match.state(this); + } + + @Override + public Bounds getBounds() { + return cuboid.getBounds(); + } + + @Override + public boolean isBlockBounded() { + return cuboid.isBlockBounded(); + } + + public enum Component { + MIN_X, + MIN_Y, + MIN_Z, + MAX_X, + MAX_Y, + MAX_Z; + private final Function vector = name().startsWith("MIN") + ? CuboidRegion.Mutable::getMutableMin + : CuboidRegion.Mutable::getMutableMax; + private final ToDoubleFunction getter = + switch (name().charAt(4)) { + case 'X' -> Vector::getX; + case 'Y' -> Vector::getY; + case 'Z' -> Vector::getZ; + default -> throw new IllegalStateException("Unexpected value: " + name()); + }; + private final ObjDoubleConsumer setter = + switch (name().charAt(4)) { + case 'X' -> Vector::setX; + case 'Y' -> Vector::setY; + case 'Z' -> Vector::setZ; + default -> throw new IllegalStateException("Unexpected value: " + name()); + }; + } + + public double getComponent(Filterable match, Component component) { + return component.getter.applyAsDouble(component.vector.apply(match.state(this))); + } + + public void setComponent(Filterable match, Component component, double value) { + component.setter.accept(component.vector.apply(match.state(this)), value); + } + + public Variable getComponent(Component component) { + return new ComponentVariable(component); + } + + class ComponentVariable extends AbstractVariable { + private final Component component; + + public ComponentVariable(Component component) { + super(Match.class); + this.component = component; + } + + @Override + protected double getValueImpl(Match obj) { + return getComponent(obj, component); + } + + @Override + protected void setValueImpl(Match obj, double value) { + setComponent(obj, component, value); + } + } +} diff --git a/core/src/main/java/tc/oc/pgm/wool/WoolMatchModule.java b/core/src/main/java/tc/oc/pgm/wool/WoolMatchModule.java index 1943096463..3061514415 100644 --- a/core/src/main/java/tc/oc/pgm/wool/WoolMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/wool/WoolMatchModule.java @@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit; import net.kyori.adventure.text.Component; import org.bukkit.Material; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -23,7 +24,6 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; -import org.bukkit.util.Vector; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.event.BlockTransformEvent; import tc.oc.pgm.api.match.Match; @@ -37,7 +37,6 @@ import tc.oc.pgm.goals.events.GoalCompleteEvent; import tc.oc.pgm.goals.events.GoalStatusChangeEvent; import tc.oc.pgm.teams.Team; -import tc.oc.pgm.util.block.BlockVectors; @ListenerScope(MatchScope.RUNNING) public class WoolMatchModule implements MatchModule, Listener { @@ -165,7 +164,7 @@ public void placementCheck(final BlockTransformEvent event) { if (this.match.getWorld() != event.getWorld()) return; Entry woolEntry = - this.findMonumentWool(BlockVectors.center(event.getNewState()).toVector()); + this.findMonumentWool(event.getNewState().getBlock()); if (woolEntry == null) return; MonumentWool wool = woolEntry.getValue(); @@ -227,9 +226,9 @@ public void handleWoolCrafting(PrepareItemCraftEvent event) { } } - private Entry findMonumentWool(Vector point) { + private Entry findMonumentWool(Block block) { for (Entry woolEntry : this.wools.entries()) { - if (woolEntry.getValue().getDefinition().getPlacementRegion().contains(point)) { + if (woolEntry.getValue().getDefinition().getPlacementRegion().contains(block)) { return woolEntry; } } diff --git a/core/src/main/java/tc/oc/pgm/wool/WoolModule.java b/core/src/main/java/tc/oc/pgm/wool/WoolModule.java index df21c445bf..4a4cc6ec15 100644 --- a/core/src/main/java/tc/oc/pgm/wool/WoolModule.java +++ b/core/src/main/java/tc/oc/pgm/wool/WoolModule.java @@ -98,45 +98,37 @@ public WoolModule parse(MapFactory factory, Logger logger, Document doc) ShowOptions options = ShowOptions.parse(factory.getFilters(), woolEl); Boolean required = XMLUtils.parseBoolean(woolEl.getAttribute("required"), null); - ProximityMetric woolProximityMetric = - ProximityMetric.parse( - woolEl, "wool", new ProximityMetric(ProximityMetric.Type.CLOSEST_KILL, false)); - ProximityMetric monumentProximityMetric = - ProximityMetric.parse( - woolEl, "monument", new ProximityMetric(ProximityMetric.Type.CLOSEST_BLOCK, false)); + ProximityMetric woolProximityMetric = ProximityMetric.parse( + woolEl, "wool", new ProximityMetric(ProximityMetric.Type.CLOSEST_KILL, false)); + ProximityMetric monumentProximityMetric = ProximityMetric.parse( + woolEl, "monument", new ProximityMetric(ProximityMetric.Type.CLOSEST_BLOCK, false)); Vector location; if (factory.getProto().isOlderThan(MapProtos.WOOL_LOCATIONS)) { // The default location is at infinity, so players/blocks are always an infinite distance // from it - location = - new Vector( - Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + location = new Vector( + Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); } else { location = XMLUtils.parseVector(XMLUtils.getRequiredAttribute(woolEl, "location")); } - MonumentWoolFactory wool = - new MonumentWoolFactory( - id, - required, - options, - team, - woolProximityMetric, - monumentProximityMetric, - color, - location, - placement, - craftable); + MonumentWoolFactory wool = new MonumentWoolFactory( + id, + required, + options, + team, + woolProximityMetric, + monumentProximityMetric, + color, + location, + placement, + craftable); factory.getFeatures().addFeature(woolEl, wool); woolFactories.put(team, wool); } - if (woolFactories.size() > 0) { - return new WoolModule(woolFactories); - } else { - return null; - } + return woolFactories.isEmpty() ? null : new WoolModule(woolFactories); } } } diff --git a/util/src/main/java/tc/oc/pgm/util/block/BlockVectors.java b/util/src/main/java/tc/oc/pgm/util/block/BlockVectors.java index 007ef76987..c4f3d9479f 100644 --- a/util/src/main/java/tc/oc/pgm/util/block/BlockVectors.java +++ b/util/src/main/java/tc/oc/pgm/util/block/BlockVectors.java @@ -32,6 +32,10 @@ public interface BlockVectors { Materials.LILY_PAD, parse(Material::valueOf, "CAKE_BLOCK", "CAKE")); + static BlockVector position(Block block) { + return new BlockVector(block.getX(), block.getY(), block.getZ()); + } + static BlockVector position(BlockState block) { return new BlockVector(block.getX(), block.getY(), block.getZ()); }