Skip to content

Commit 14bceed

Browse files
authored
Merge pull request #2743 from BentoBoxWorld/develop
Release 3.8.1
2 parents 582be1b + 2728c64 commit 14bceed

File tree

6 files changed

+193
-83
lines changed

6 files changed

+193
-83
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
<!-- Do not change unless you want different name for local builds. -->
7676
<build.number>-LOCAL</build.number>
7777
<!-- This allows to change between versions. -->
78-
<build.version>3.8.0</build.version>
78+
<build.version>3.8.1</build.version>
7979
<sonar.organization>bentobox-world</sonar.organization>
8080
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
8181
<server.jars>${project.basedir}/lib</server.jars>

src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public class BlueprintClipboard {
5858
/**
5959
* Used to filter out hidden DisplayEntity armor stands when copying
6060
*/
61-
private NamespacedKey key;
6261
private @Nullable Blueprint blueprint;
6362
private @Nullable Location pos1;
6463
private @Nullable Location pos2;
@@ -84,7 +83,6 @@ public class BlueprintClipboard {
8483
public BlueprintClipboard(@NonNull Blueprint blueprint) {
8584
this();
8685
this.blueprint = blueprint;
87-
this.key = new NamespacedKey(BentoBox.getInstance(), "associatedDisplayEntity");
8886
}
8987

9088
public BlueprintClipboard() {
@@ -165,6 +163,7 @@ private void copyAsync(World world, User user, List<Vector> vectorsToCopy, int s
165163
return;
166164
}
167165
copying = true;
166+
NamespacedKey key = new NamespacedKey(BentoBox.getInstance(), "associatedDisplayEntity");
168167
vectorsToCopy.stream().skip(index).limit(speed).forEach(v -> {
169168
List<Entity> ents = world.getEntities().stream()
170169
.filter(Objects::nonNull)

src/main/java/world/bentobox/bentobox/managers/island/DefaultNewIslandLocationStrategy.java

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,136 +15,192 @@
1515
import world.bentobox.bentobox.util.Util;
1616

1717
/**
18-
* The default strategy for generating locations for island
18+
* The default strategy for generating locations for new islands.
19+
* This strategy finds the next available island spot by searching in an outward square spiral pattern
20+
* from the last known island location or the world's starting coordinates.
21+
* <p>
22+
* If you wish to create an alternative strategy, you must implement the {@link NewIslandLocationStrategy} interface.
23+
* Your implementation should be robust and consider the following:
24+
* <ul>
25+
* <li><b>Performance:</b> Finding a location can be a frequent operation. Your algorithm should be efficient.</li>
26+
* <li><b>Non-empty worlds:</b> The world may already contain structures. Your strategy should be able to
27+
* navigate around these or register them as "occupied" to avoid placing islands on top of them. This implementation
28+
* uses a tolerance limit ({@link #MAX_UNOWNED_ISLANDS}) to prevent infinite loops in heavily modified worlds.</li>
29+
* <li><b>Island distance:</b> Respect the island distance setting from the configuration to prevent islands from overlapping.</li>
30+
* <li><b>Concurrency:</b> While island creation is typically synchronized, consider any potential race conditions if your
31+
* strategy involves asynchronous operations.</li>
32+
* </ul>
33+
* The methods in this class, like {@link #isIsland(Location)}, can be useful helpers for custom implementations.
34+
* </p>
35+
*
1936
* @author tastybento, leonardochaia
2037
* @since 1.8.0
21-
*
2238
*/
2339
public class DefaultNewIslandLocationStrategy implements NewIslandLocationStrategy {
2440

2541
/**
26-
* The amount times to tolerate island check returning blocks without known
27-
* island.
42+
* The maximum number of times to tolerate finding non-BentoBox blocks in a potential island spot
43+
* before giving up. This acts as a safeguard against infinite loops when searching for a free location
44+
* in a world that was not generated by BentoBox or has been heavily modified.
2845
*/
2946
protected static final Integer MAX_UNOWNED_ISLANDS = 20;
3047

48+
/**
49+
* Represents the result of checking a potential island location.
50+
*/
3151
protected enum Result {
32-
ISLAND_FOUND, BLOCKS_IN_AREA, FREE
52+
/**
53+
* A BentoBox island already exists at this location.
54+
*/
55+
ISLAND_FOUND,
56+
/**
57+
* No BentoBox island exists, but there are other blocks in the area.
58+
* This spot is considered occupied.
59+
*/
60+
BLOCKS_IN_AREA,
61+
/**
62+
* The location is free and suitable for a new island.
63+
*/
64+
FREE
3365
}
3466

3567
protected final BentoBox plugin = BentoBox.getInstance();
3668

3769
@Override
3870
public Location getNextLocation(World world) {
71+
// Get the last known island location from the database.
3972
Location last = plugin.getIslands().getLast(world);
4073
if (last == null) {
74+
// If no island has been created yet, start from the configured offset.
4175
last = new Location(world,
4276
(double) plugin.getIWM().getIslandXOffset(world) + plugin.getIWM().getIslandStartX(world),
4377
plugin.getIWM().getIslandHeight(world),
4478
(double) plugin.getIWM().getIslandZOffset(world) + plugin.getIWM().getIslandStartZ(world));
4579
}
46-
// Find a free spot
80+
// Find a free spot by spiraling outwards.
4781
Map<Result, Integer> result = new EnumMap<>(Result.class);
48-
// Check center
82+
// Check the starting location.
4983
Result r = isIsland(last);
84+
// Loop until a FREE spot is found or we hit the tolerance limit for unowned islands.
5085
while (!r.equals(Result.FREE) && result.getOrDefault(Result.BLOCKS_IN_AREA, 0) < MAX_UNOWNED_ISLANDS) {
86+
// Move to the next location in the spiral.
5187
nextGridLocation(last);
88+
// Count the result of the previous check.
5289
result.put(r, result.getOrDefault(r, 0) + 1);
90+
// Check the new location.
5391
r = isIsland(last);
5492
}
5593

5694
if (!r.equals(Result.FREE)) {
57-
// We could not find a free spot within the limit required. It's likely this
58-
// world is not empty
95+
// We could not find a free spot within the required limit.
96+
// This likely means the world is not empty or has many existing structures.
5997
plugin.logError("Could not find a free spot for islands! Is this world empty?");
6098
plugin.logError("Blocks around center locations: " + result.getOrDefault(Result.BLOCKS_IN_AREA, 0) + " max "
6199
+ MAX_UNOWNED_ISLANDS);
62100
plugin.logError("Known islands: " + result.getOrDefault(Result.ISLAND_FOUND, 0) + " max unlimited.");
63101
return null;
64102
}
103+
// A free spot was found. Save it as the last location for the next search.
65104
plugin.getIslands().setLast(last);
66105
return last;
67106
}
68107

69108
/**
70-
* Checks if there is an island or blocks at this location
109+
* Checks if a given location is free for a new island.
110+
* It checks for existing BentoBox islands, islands pending deletion, and any other blocks in the area.
71111
*
72-
* @param location - the location
73-
* @return Result enum indicated what was found or not found
112+
* @param location The center location of the potential island spot.
113+
* @return {@link Result} enum indicating what was found.
74114
*/
75115
protected Result isIsland(Location location) {
76-
// Quick check
116+
// Quick check using the island grid cache.
77117
if (plugin.getIslands().isIslandAt(location)) {
78118
return Result.ISLAND_FOUND;
79119
}
80120

81121
World world = location.getWorld();
82122

83-
// Check 4 corners
123+
// Check the four corners of the island protection area to be more thorough.
84124
int dist = plugin.getIWM().getIslandDistance(location.getWorld());
85125
Set<Location> locs = new HashSet<>();
86126
locs.add(location);
87127

128+
// Define the corners of the island's bounding box.
88129
locs.add(new Location(world, location.getX() - dist, 0, location.getZ() - dist));
89130
locs.add(new Location(world, location.getX() - dist, 0, location.getZ() + dist - 1));
90131
locs.add(new Location(world, location.getX() + dist - 1, 0, location.getZ() - dist));
91132
locs.add(new Location(world, location.getX() + dist - 1, 0, location.getZ() + dist - 1));
92133

93134
boolean generated = false;
94135
for (Location l : locs) {
136+
// Check if an island exists or is being deleted at any of the check points.
95137
if (plugin.getIslands().getIslandAt(l).isPresent() || plugin.getIslandDeletionManager().inDeletion(l)) {
96138
return Result.ISLAND_FOUND;
97139
}
140+
// Check if the chunk is generated. An ungenerated chunk is considered free.
98141
if (Util.isChunkGenerated(l)) generated = true;
99142
}
100-
// If chunk has not been generated yet, then it's not occupied
143+
// If no chunks in the area have been generated yet, then it's definitely not occupied.
101144
if (!generated) {
102145
return Result.FREE;
103146
}
104-
// Block check
147+
// If configured, check for any non-air/non-water blocks in the immediate vicinity of the center.
148+
// This is to detect pre-existing structures in imported worlds.
105149
if (plugin.getIWM().isCheckForBlocks(world)
106150
&& !plugin.getIWM().isUseOwnGenerator(world)
107151
&& Arrays.stream(BlockFace.values()).anyMatch(bf ->
108152
!location.getBlock().getRelative(bf).isEmpty()
109153
&& !location.getBlock().getRelative(bf).getType().equals(Material.WATER))) {
110-
// Block found
154+
// A block was found. Create a temporary, reserved island object at this location
155+
// to mark it as occupied in the database and prevent it from being checked again.
111156
plugin.getIslands().createIsland(location);
112157
return Result.BLOCKS_IN_AREA;
113158
}
159+
// The location is free.
114160
return Result.FREE;
115161
}
116162

117163
/**
118-
* Finds the next free island spot based off the last known island Uses
119-
* island_distance setting from the config file Builds up in a grid fashion
164+
* Calculates the next island location in an outward square spiral pattern.
165+
* This method modifies the provided {@link Location} object in-place.
166+
* The algorithm works by moving along the edges of increasingly larger squares
167+
* centered at the origin (0,0). The distance 'd' determines the step size.
120168
*
121-
* @param lastIsland - last island location
122-
* @return Location of next free island
169+
* @param lastIsland The location of the last island, which will be modified to the next location.
170+
* @return The same Location object, now set to the next grid position.
123171
*/
124172
private Location nextGridLocation(final Location lastIsland) {
125173
int x = lastIsland.getBlockX();
126174
int z = lastIsland.getBlockZ();
175+
// The distance between island centers is twice the protection distance.
127176
int d = plugin.getIWM().getIslandDistance(lastIsland.getWorld()) * 2;
128177
if (x < z) {
129178
if (-1 * x < z) {
179+
// Move right (positive X)
130180
lastIsland.setX(lastIsland.getX() + d);
131181
return lastIsland;
132182
}
183+
// Move up (positive Z)
133184
lastIsland.setZ(lastIsland.getZ() + d);
134185
return lastIsland;
135186
}
136187
if (x > z) {
137188
if (-1 * x >= z) {
189+
// Move left (negative X)
138190
lastIsland.setX(lastIsland.getX() - d);
139191
return lastIsland;
140192
}
193+
// Move down (negative Z)
141194
lastIsland.setZ(lastIsland.getZ() - d);
142195
return lastIsland;
143196
}
197+
// This condition handles the corner case where x == z.
144198
if (x <= 0) {
199+
// Move up (positive Z) from the bottom-left corner.
145200
lastIsland.setZ(lastIsland.getZ() + d);
146201
return lastIsland;
147202
}
203+
// Move down (negative Z) from the top-right corner.
148204
lastIsland.setZ(lastIsland.getZ() - d);
149205
return lastIsland;
150206
}

0 commit comments

Comments
 (0)