diff --git a/docs/changelog_v3.3.x.md b/docs/changelog_v3.3.x.md index e4433476f..8ea8e2eb7 100644 --- a/docs/changelog_v3.3.x.md +++ b/docs/changelog_v3.3.x.md @@ -14,7 +14,11 @@ These change logs represent the work that has been going on within prison. -# 3.3.0-alpha.18d 2024-09-05 +# 3.3.0-alpha.18d 2024-09-06 + + +* **AutoFeatures: PrisonEnchants (Pulsi's): Made changes to handle explosion events that happen outside of the mine, but there are some blocks that are within the mine.** +This prevents PrisonEnchants, or bukkit, from breaking the blocks within the mine. * **Auto Features: Added support for 9 new Blocking combinations.** diff --git a/prison-core/src/main/java/tech/mcprison/prison/util/Bounds.java b/prison-core/src/main/java/tech/mcprison/prison/util/Bounds.java index 707f2b3bf..39de171b9 100644 --- a/prison-core/src/main/java/tech/mcprison/prison/util/Bounds.java +++ b/prison-core/src/main/java/tech/mcprison/prison/util/Bounds.java @@ -478,12 +478,21 @@ public Location getCenter() { return center; } + + + public double getRadius() { + double radius = getDistance3d() / 2.0; + return radius; + } + - @Override public String toString() { + @Override + public String toString() { return "Bounds{" + "min=" + min.toCoordinates() + ", max=" + max.toCoordinates() + '}'; } - @Override public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (this == o) { return true; } diff --git a/prison-spigot/src/main/java/tech/mcprison/prison/spigot/autofeatures/events/AutoManagerPrisonEnchants.java b/prison-spigot/src/main/java/tech/mcprison/prison/spigot/autofeatures/events/AutoManagerPrisonEnchants.java index b91aff240..b7f97eaf9 100644 --- a/prison-spigot/src/main/java/tech/mcprison/prison/spigot/autofeatures/events/AutoManagerPrisonEnchants.java +++ b/prison-spigot/src/main/java/tech/mcprison/prison/spigot/autofeatures/events/AutoManagerPrisonEnchants.java @@ -1,7 +1,6 @@ package tech.mcprison.prison.spigot.autofeatures.events; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; import org.bukkit.Bukkit; @@ -315,31 +314,31 @@ else if ( getPeApiVersion() == PEExplosionEventVersion.undefined ) { return results; } - private List getBlocks( PEExplosionEvent event ) { - List results = new ArrayList<>(); - - if ( getPeApiVersion() == null ) { - getPEPluginVersion(); - } - - if ( getPeApiVersion() == PEExplosionEventVersion.pev1_0_0 ) { - - results.addAll( event.getExplodedBlocks() ); - } - else if ( getPeApiVersion() == PEExplosionEventVersion.pev2_0_0 ) { - - results.addAll( event.getBlocks().subList(1, event.getBlocks().size())); - } - else if ( getPeApiVersion() == PEExplosionEventVersion.pev2_2_1 ) { - - results.addAll( event.getBlocks() ); - } - else if ( getPeApiVersion() == PEExplosionEventVersion.undefined ) { - Output.get().logWarn( "AutoManager: Pulsi_'s PrisonEnchants api version is &6undefined&3!" ); - } - - return results; - } +// private List getBlocks( PEExplosionEvent event ) { +// List results = new ArrayList<>(); +// +// if ( getPeApiVersion() == null ) { +// getPEPluginVersion(); +// } +// +// if ( getPeApiVersion() == PEExplosionEventVersion.pev1_0_0 ) { +// +// results.addAll( event.getExplodedBlocks() ); +// } +// else if ( getPeApiVersion() == PEExplosionEventVersion.pev2_0_0 ) { +// +// results.addAll( event.getBlocks().subList(1, event.getBlocks().size())); +// } +// else if ( getPeApiVersion() == PEExplosionEventVersion.pev2_2_1 ) { +// +// results.addAll( event.getBlocks() ); +// } +// else if ( getPeApiVersion() == PEExplosionEventVersion.undefined ) { +// Output.get().logWarn( "AutoManager: Pulsi_'s PrisonEnchants api version is &6undefined&3!" ); +// } +// +// return results; +// } private void createListener(BlockBreakPriority bbPriority) { @@ -483,6 +482,31 @@ public void handlePEExplosionEvent( PEExplosionEvent e, BlockBreakPriority bbPri // or if the targetBlock has been set to ignore all block events which // means the block has already been processed. + + if ( e.getBlocks().size() == 0 ) { + // Nothing to process: + return ; + } + + + // Remove all invalid blocks: + // The original collection in the event will be updated... + int blocksBefore = e.getBlocks().size(); + + // verified blocks: + List vBlocks = removeAllInvalidBlocks( e.getPlayer(), e.getBlocks(), bbPriority, true ); + int blocksAfter = vBlocks.size(); + + Output.get().logInfo( "&6 #### PEExplosionEvent: &7removeAllInvalidBlocks:&3 before: %d after: %d", + blocksBefore, blocksAfter ); + + + if ( vBlocks.size() == 0 ) { + // No blocks are within prison mines... ignore this event. + return; + } + + // NOTE: support for v1.0, v2.2, and v2.2.1 has different block structures: Block bBlock = getBlock( e ); @@ -490,17 +514,35 @@ public void handlePEExplosionEvent( PEExplosionEvent e, BlockBreakPriority bbPri e.getPlayer(), bBlock, bbPriority, true ); + // The primary block is not in the mine, or they don't have access to it, so ignore event: if ( eventResults.isIgnoreEvent() ) { - return; + + // But if vBlocks.size() > 0, the try the first block in that list to see if it will work: + if ( vBlocks.size() > 0 ) { + bBlock = vBlocks.remove( 0 ); + + eventResults = ignoreMinesBlockBreakEvent( e, + e.getPlayer(), bBlock, + bbPriority, true ); + + if ( eventResults.isIgnoreEvent() ) { + return; + } + } + else { + + return; + } } StringBuilder debugInfo = new StringBuilder(); debugInfo.append( String.format( "&6### ** handlePEEExplosionEvent (Pulsi) ** ###&3 " + - "(event: &6PEExplosionEvent&3, config: %s, priority: %s, canceled: %s) ", + "(event: &6PEExplosionEvent&3, config: %s, priority: %s, %scanceled: %s) ", bbPriority.name(), bbPriority.getBukkitEventPriority().name(), + (e.getEventName() == null ? "" : "EventName: " + e.getEventName()), (e.isCancelled() ? "TRUE " : "FALSE") ) ); @@ -540,9 +582,11 @@ public void handlePEExplosionEvent( PEExplosionEvent e, BlockBreakPriority bbPri return; } - List blocks = getBlocks( e ); +// List blocks = getBlocks( e ); - pmEvent.setUnprocessedRawBlocks( blocks ); + // vBlocks have been verified to be within a mine. There may be restrictions that prevent them + // from being used, but they passed the first check. + pmEvent.setUnprocessedRawBlocks( vBlocks ); // Check to see if the blockConverter's EventTrigger should have diff --git a/prison-spigot/src/main/java/tech/mcprison/prison/spigot/block/OnBlockBreakMines.java b/prison-spigot/src/main/java/tech/mcprison/prison/spigot/block/OnBlockBreakMines.java index 378683f87..06252e9a5 100644 --- a/prison-spigot/src/main/java/tech/mcprison/prison/spigot/block/OnBlockBreakMines.java +++ b/prison-spigot/src/main/java/tech/mcprison/prison/spigot/block/OnBlockBreakMines.java @@ -5,6 +5,7 @@ import java.util.TreeMap; import java.util.UUID; +import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -24,11 +25,12 @@ import tech.mcprison.prison.output.Output; import tech.mcprison.prison.spigot.SpigotUtil; import tech.mcprison.prison.spigot.api.PrisonMinesBlockBreakEvent; +import tech.mcprison.prison.spigot.game.SpigotLocation; import tech.mcprison.prison.spigot.game.SpigotPlayer; import tech.mcprison.prison.spigot.utils.BlockUtils; import tech.mcprison.prison.tasks.PrisonCommandTaskData; -import tech.mcprison.prison.tasks.PrisonCommandTasks; import tech.mcprison.prison.tasks.PrisonCommandTaskData.TaskMode; +import tech.mcprison.prison.tasks.PrisonCommandTasks; public class OnBlockBreakMines extends OnBlockBreakEventCoreMessages @@ -274,6 +276,203 @@ public Mine findMine( UUID playerUUID, SpigotBlock sBlock, List altBlocks // return eventResults.isIgnoreEvent(); // } + + public List removeAllInvalidBlocks( Player player, + List blocks, + BlockBreakPriority bbPriority, + boolean ignoreBlockReuse ) { + + List goodBlocks = new ArrayList<>(); + + if ( blocks.size() == 0 || getPrisonMineManager() == null ) { + // Mines are not enabled, so exit with request to ignore event: + return goodBlocks; + } + + + + // searching for mines is expensive because it's has to check each and every block + // against all possible mines. + + // So first find the "center" of the block list by taking the averages of all blocks's x, y, and z coordinates: + Location locAvgBukkit = getCenterLocationOfBlocks(blocks, player); + + + // next we need to find the greatest distance from the average: + double locAvgRadius = getGreatestDistanceFromLocation(blocks, locAvgBukkit); + + + SpigotLocation locAvg = new SpigotLocation( locAvgBukkit ); + + + // Next we need to find all mines that are within the max distance of the mine, + // to ensure we don't miss the outer corners, take the mine's distance to a corner + // and mult by 1.3 which will potentially include mines that are outside of the + // list of blocks: + List minesShortList = getAllMinesWithinTheRadialDistance( locAvg, locAvgRadius ); + + + + // If no mines are found, then obviously no blocks will be within the mines: + if ( minesShortList.size() == 0 ) { + return goodBlocks; + } + + +// SpigotPlayer sPlayer = new SpigotPlayer( player ); + + + // All blocks must be not be null, and the blocks cannot be identified as an + // unbreakable block, which is usually part of an explosion event such as a + // block decay. + Mine lastMine = null; + + for (Block block : blocks) { + + SpigotBlock sBlock = SpigotBlock.getSpigotBlock( block ); + + if ( block != null && !BlockUtils.getInstance().isUnbreakable( sBlock ) ) { + + // If a block is in the same mine as the 'lastMine', then add it to the + // goodBlocks list. + if ( lastMine != null && lastMine.isInMine(sBlock) ) { + goodBlocks.add(block); + } + + // else if lastMine is null or the block is not in the lastMine, then + // need to check all mines in the short list to see if the block is in + // mine. If it is, then capture it in the goodBlocks list. + else { + + for (Mine mine : minesShortList) { + if ( mine.isInMineExact(locAvg) ) { + lastMine = mine; + + goodBlocks.add(block); + break; + } + } + } + + } + } + +// // Purge all blocks in the parameter List: +// blocks.clear(); +// +// // Add only the goodBlocks back to the parameter's variable: +// blocks.addAll( goodBlocks ); + + return goodBlocks; + } + + + /** + *

This function will take a list of blocks, and calculate the average location, + * which will be the center of the group of blocks. The group of blocks can be + * any shape, and it will still find the center. + *

+ * + *

The way this function works, is that it adds all x's, all y's, and all z's. + * Then divides all these values by the number of blocks that were summed together. + * Basically finding the common vector or everything. + *

+ * + * @param blocks + * @param player + * @return + */ + private Location getCenterLocationOfBlocks(List blocks, Player player) { + long xT = 0; + long yT = 0; + long zT = 0; + int count = 0; + for (Block block : blocks) { + xT += block.getX(); + yT += block.getY(); + zT += block.getZ(); + count++; + } + + double xAvg = (double) xT / count; + double yAvg = (double) yT / count; + double zAvg = (double) zT / count; + Location locAvgBukkit = new Location( player.getWorld(), xAvg, yAvg, zAvg ); + return locAvgBukkit; + } + + + /** + *

This function, given the a location, which should be the center of all blocks + * (See function `getCenterLocationOfBlocks()`), will return the greatest distance + * of all of the included blocks. This will be the outer radius of all the blocks, + * based upon the 'centerLocation'. + *

+ * + * @param blocks + * @param locAvgBukkit + * @return + */ + private double getGreatestDistanceFromLocation(List blocks, Location centerLocation ) { + double locAvgRadius = 0; + for (Block block : blocks) { + double d = centerLocation.distance( block.getLocation() ); + if ( d > locAvgRadius ) { + locAvgRadius = d; + } + } + return locAvgRadius; + } + + /** + *

This function, given a location, and the radius of a sphere around that location, find all + * mines that that has at least one block that will fall within the range of this the location's + * sphere. To ensure a mine is not missed by a block or two, the 'maxPossibleDistance' has + * four added to it. This will help ensure that more mines are included in this list, even if it + * may miss the farthest block by a couple of blocks. It's far better to be more inclusive, than + * to exclude too many. + *

+ * + *

This function basically behaves as if we have two points and spheres around each point. The + * spheres would be the radius from each location, to form a sphere for each location. Between the + * two spheres, we only need to use in our calculations, the closest point on each sphere, such that + * the two chosen points (one of each sphere) are the closest to each other, out of the whole surface + * of those two spheres. Basically, these two points, also happen to be on the line that is formed + * from one location to the other location. And the two points on that line, which represents the + * spheres surface, would be consider intersecting with each other if the distance between the points + * is less than zero. If they don't intersect then the distance will be greater than zero. + *

+ * + *

So basically, the total distance should be less than the two radiuses added together (plus 4.0) + * which indicates there is a probable chance a block may be within that mine. All mines that + * meet this requirement is returned in a List. + *

+ * + * @param locAvg + * @param locAvgRadius + * @return + */ + private List getAllMinesWithinTheRadialDistance( SpigotLocation location, double locationRadius ) { + List minesShortList = new ArrayList<>(); + for (Mine m : getPrisonMineManager().getMines() ) { + if ( !m.isVirtual() ) { + + double mineRadius = m.getBounds().getRadius(); + double maxPossibleDistance = mineRadius + locationRadius + 4; + + double distanceFromMine = m.getBounds().getDistance3d( location ); + + + if ( distanceFromMine <= maxPossibleDistance ) { + minesShortList.add( m ); + } + } + } + return minesShortList; + } + + + /** *

If the event is canceled, it still needs to be processed because of the MONITOR events: @@ -357,6 +556,7 @@ else if ( !mine.hasMiningAccess(sPlayer) ) { results.setResultsReason( EventResultsReasons .results_passed__access_priority__player_has_access ); + // Not sure why that was here? This looks like they should have access?] results.setIgnoreEvent( true ); }