Add per-arena min/max spawnpoint distance settings.

Introduces two new settings for controlling how far away from or close
to a spawnpoint players must be in order for the spawnpoint to be valid
when spawning monsters.

This is a pretty big one. In ancient times, MobArena had no limit on how
close players had to be to spawnpoints for them to be valid. The result
was mobs spawning too far away from the players, frozen in place until
their target approached them or until another player attacked them. This
was clearly undesirable, so the 15-block max distance was introduced to
solve this problem. And it worked, hurray!

In the meantime, it also imposed a really cumbersome limitation on all
arena designs, since aesthetically (or strategically) placed spawnpoints
were seldom sufficient, and server owners have been needing to litter
their arena floors with spawnpoints to avoid warnings and to get their
expected monster behavior.

Modern versions of Minecraft no longer exhibit that "frozen in place"
behavior the hardcoded max distance set out to solve, and server owners
have been asking for configurability on this front for years. With this
commit, that distance is now per-arena configurable. The change has no
real impact on the performance of the plugin. It's worth noting that we
don't modify the pathfinding attribute `generic.followRange`. We might
want to revisit this in a future commit, but since it can _definitely_
affect performance, we should have some actual servers run a test build
with it before jumping on that wagon.

Having many spawnpoints might still be preferable to some, but it comes
with another problem of players standing right on top of a spawnpoint
when a monster spawns. To combat this, an additional setting to control
the _minimum_ distance that _all_ players must be from the spawnpoint
for it to be valid is introduced as well. This means it is possible to
have lots of spawnpoints for a less predictable session, but without the
risk of being instantly attacked when a new wave spawns. This change has
a theoretical performance impact on the plugin, because it's a lot more
brute force without the early returns of the old algorithm. However, if
the setting is left at 0, the old algorithm is used.

The only real downside to these changes is that it's more code and more
settings to maintain. It doesn't improve on the clusterfuck that is the
arena settings in the config-file - we're just making things worse. I do
think it's worth having, though, since the bigger revamps on the drawing
board are at best months away, and at worst, they'll never happen. When
or if the time comes, it's probably better to rethink certain aspects of
the plugin instead of trying to convert everything gracefully.

Closes #412
This commit is contained in:
Andreas Troelsen 2024-10-06 14:42:02 +02:00
parent 78f5dc0545
commit 00b59ed7c4
7 changed files with 64 additions and 10 deletions

View File

@ -14,6 +14,8 @@ These changes will (most likely) be included in the next version.
### Added
- MobArena now properly supports Vault economy providers registered after MobArena has started. This should make it possible to use custom economy providers that aren't built into Vault, such as those created with Denizen.
- New wave rewards section `tiers` allows for _non-stacking_ reward tiers for beating certain waves. This allows for configuring reward sets that get "upgraded" as the waves progress, e.g. by granting a full leather armor set for beating wave 15, but beating wave 20 _replaces_ that leather armor set with an iron armor set.
- New per-arena setting `spawnpoint-max-distance` can be used to tweak how close to any player a given spawnpoint must be to be considered valid when spawning monsters. This should help reduce the amount of spawnpoints required, especially for larger arenas.
- New per-arena setting `spawnpoint-min-distance` can be used to tweak how far away from all players a given spawnpoint must be to be considered valid when spawning monsters. This should help prevent monsters from spawning directly on top of players.
### Changed
- Recurrent waves can now be randomized. If two or more recurrent waves clash on wave number, frequency, _and_ priority, MobArena will now randomly pick between them. This should make it easier to create more varied and interesting wave setups without having to resort to only massively randomized default waves.

View File

@ -125,6 +125,8 @@ public class ArenaImpl implements Arena
private MASpawnThread spawnThread;
private SheepBouncer sheepBouncer;
private Map<Integer, ThingPicker> everyWaveMap, afterWaveMap, waveTiersMap;
private double spawnpointMinDistanceSquared;
private double spawnpointMaxDistanceSquared;
// Misc
private ArenaListener eventListener;
@ -209,6 +211,8 @@ public class ArenaImpl implements Arena
this.everyWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "every");
this.afterWaveMap = MAUtils.getArenaRewardMap(plugin, section, name, "after");
this.waveTiersMap = MAUtils.getArenaRewardMap(plugin, section, name, "tiers");
this.spawnpointMinDistanceSquared = Math.pow(settings.getDouble("spawnpoint-min-distance", 0), 2);
this.spawnpointMaxDistanceSquared = Math.pow(settings.getDouble("spawnpoint-max-distance", 15), 2);
// Misc
this.eventListener = new ArenaListener(this, plugin);
@ -382,6 +386,16 @@ public class ArenaImpl implements Arena
return spawnThread;
}
@Override
public double getSpawnpointMinDistanceSquared() {
return spawnpointMinDistanceSquared;
}
@Override
public double getSpawnpointMaxDistanceSquared() {
return spawnpointMaxDistanceSquared;
}
@Override
public WaveManager getWaveManager() {
return waveManager;

View File

@ -88,6 +88,7 @@ public class MAUtils
/* Iterate through the ArrayList, and update current and result every
* time a squared distance smaller than current is found. */
List<Player> players = new ArrayList<>(arena.getPlayersInArena());
double max = arena.getSpawnpointMaxDistanceSquared();
for (Player p : players) {
if (!arena.getWorld().equals(p.getWorld())) {
plugin.getLogger().info("Player '" + p.getName() + "' is not in the right world. Kicking...");
@ -97,7 +98,7 @@ public class MAUtils
}
double dist = distanceSquared(plugin, p, e.getLocation());
if (dist < current && dist < 256D) {
if (dist < current && dist < max) {
current = dist;
result = p;
}

View File

@ -82,6 +82,10 @@ public interface Arena
MASpawnThread getSpawnThread();
double getSpawnpointMinDistanceSquared();
double getSpawnpointMaxDistanceSquared();
WaveManager getWaveManager();
ArenaListener getEventListener();

View File

@ -648,9 +648,12 @@ public class ArenaRegion
}
// Find all the spawnpoints that cover the location
double min = arena.getSpawnpointMinDistanceSquared();
double max = arena.getSpawnpointMaxDistanceSquared();
Map<String,Location> map = new HashMap<>();
for (Map.Entry<String,Location> entry : spawnpoints.entrySet()) {
if (p.getLocation().distanceSquared(entry.getValue()) < MobArena.MIN_PLAYER_DISTANCE_SQUARED) {
double dist = p.getLocation().distanceSquared(entry.getValue());
if (min <= dist && dist <= max) {
map.put(entry.getKey(), entry.getValue());
}
}

View File

@ -27,14 +27,42 @@ public class WaveUtils
spawnpoints = arena.getRegion().getSpawnpointList();
}
// Loop through each one and check if any players are in range.
for (Location l : spawnpoints) {
for (Player p : players) {
if (MAUtils.distanceSquared(plugin, p, l) >= MobArena.MIN_PLAYER_DISTANCE_SQUARED) {
continue;
double min = arena.getSpawnpointMinDistanceSquared();
double max = arena.getSpawnpointMaxDistanceSquared();
if (min > 0) {
// If the min distance is greater than 0, we need to check the
// distance of every player, because one player within the max
// range is no longer enough to make a spawnpoint valid. If even
// a single player is too close, the spawnpoint is invalid.
for (Location l : spawnpoints) {
boolean valid = false;
for (Player p : players) {
double dist = MAUtils.distanceSquared(plugin, p, l);
if (dist < min) {
valid = false;
break;
}
if (dist <= max) {
valid = true;
}
}
if (valid) {
result.add(l);
}
}
} else {
// If the min distance is 0, the old "any player within range"
// approach is sufficient, because we can never invalidate a
// spawnpoint for being too close to a player.
for (Location l : spawnpoints) {
for (Player p : players) {
double dist = MAUtils.distanceSquared(plugin, p, l);
if (dist <= max) {
result.add(l);
break;
}
}
result.add(l);
break;
}
}
@ -70,7 +98,7 @@ public class WaveUtils
}
dist = p.getLocation().distanceSquared(e.getLocation());
if (dist < current && dist < MobArena.MIN_PLAYER_DISTANCE_SQUARED)
if (dist < current && dist < arena.getSpawnpointMaxDistanceSquared())
{
current = dist;
result = p;

View File

@ -27,6 +27,8 @@ first-wave-delay: 5
next-wave-delay: 0
wave-interval: 15
final-wave: 0
spawnpoint-min-distance: 0
spawnpoint-max-distance: 15
monster-limit: 100
monster-exp: false
keep-exp: false