Skip to content

Commit 48d7de9

Browse files
committed
implement new entity limit logic
1 parent 70c2701 commit 48d7de9

File tree

3 files changed

+175
-123
lines changed

3 files changed

+175
-123
lines changed

AnarchyExploitFixesFolia/src/main/java/me/xginko/aef/modules/chunklimits/CustomEntityLimit.java

Lines changed: 97 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
77
import me.xginko.aef.modules.AEFModule;
88
import me.xginko.aef.utils.ChunkUtil;
9+
import me.xginko.aef.utils.EntityUtil;
910
import me.xginko.aef.utils.LocationUtil;
1011
import org.bukkit.Chunk;
11-
import org.bukkit.Material;
1212
import org.bukkit.World;
1313
import org.bukkit.entity.Entity;
1414
import org.bukkit.entity.EntityType;
@@ -17,27 +17,28 @@
1717
import org.bukkit.event.HandlerList;
1818
import org.bukkit.event.Listener;
1919
import org.bukkit.event.entity.EntitySpawnEvent;
20-
import org.bukkit.event.world.ChunkUnloadEvent;
20+
import org.bukkit.event.world.EntitiesLoadEvent;
21+
import org.bukkit.event.world.EntitiesUnloadEvent;
2122

2223
import java.util.EnumMap;
24+
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Objects;
2427
import java.util.TreeMap;
25-
import java.util.concurrent.atomic.AtomicInteger;
2628
import java.util.function.Consumer;
2729

2830
public class CustomEntityLimit extends AEFModule implements Consumer<ScheduledTask>, Listener {
2931

3032
private final Map<EntityType, Integer> entityLimits = new EnumMap<>(EntityType.class);
3133
private final long checkPeriod, minChunkAge;
32-
private final boolean logIsEnabled, enableChunkAgeSkip, forceLoadEntities;
34+
private final boolean enableChunkAgeSkip, forceLoadEntities;
3335

3436
private ScheduledTask scheduledTask;
3537

3638
public CustomEntityLimit() {
3739
super("chunk-limits.entity-limits.custom-limit", false, """
3840
Limit specific entity types per chunk.\s
3941
Read over the defaults at least once before enabling.""");
40-
this.logIsEnabled = config.getBoolean(configPath + ".log-removals", true);
4142
this.checkPeriod = config.getInt(configPath + ".check-period-in-ticks", 1200, """
4243
Check all loaded chunks every x ticks.""");
4344
this.enableChunkAgeSkip = config.getBoolean(configPath + ".chunk-age-skip.enable", true);
@@ -137,14 +138,15 @@ public CustomEntityLimit() {
137138
https://jd.papermc.io/paper/1.20.6/org/bukkit/entity/EntityType.html\s
138139
Make sure your minecraft version is matching as well.""");
139140
for (String configuredEntity : section.getKeys(false)) {
141+
String configuredLimit = section.getString(configuredEntity);
140142
try {
141-
Integer maxAmountPerChunk = Integer.parseInt(section.getString(configuredEntity));
142-
EntityType limitedEntity = EntityType.valueOf(configuredEntity);
143-
entityLimits.put(limitedEntity, maxAmountPerChunk);
144-
} catch (NumberFormatException e) {
143+
entityLimits.put(
144+
EntityType.valueOf(configuredEntity),
145+
Integer.parseInt(Objects.requireNonNull(configuredLimit)));
146+
} catch (NumberFormatException | NullPointerException e) {
145147
notRecognized(Integer.class, configuredEntity);
146148
} catch (IllegalArgumentException e) {
147-
notRecognized(Material.class, configuredEntity);
149+
notRecognized(EntityType.class, configuredEntity);
148150
}
149151
}
150152
}
@@ -162,94 +164,118 @@ public void disable() {
162164
if (scheduledTask != null) scheduledTask.cancel();
163165
}
164166

165-
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
166-
private void onChunkUnload(ChunkUnloadEvent event) {
167-
if (ChunkUtil.isRetrievalUnsafe(event.getChunk())) return;
167+
private void enforceEntityLimit(Map<EntityType, List<Entity>> entitiesByTypes) {
168+
for (Map.Entry<EntityType, List<Entity>> entry : entitiesByTypes.entrySet()) {
169+
if (!entityLimits.containsKey(entry.getKey())) {
170+
continue;
171+
}
168172

169-
for (Map.Entry<EntityType, Integer> limit : entityLimits.entrySet()) {
170-
final int maxAllowedPerChunk = limit.getValue();
173+
int entityLimit = entityLimits.get(entry.getKey());
171174

172-
AtomicInteger count = new AtomicInteger();
175+
if (entry.getValue().size() <= entityLimit) {
176+
continue;
177+
}
173178

174-
for (Entity entity : event.getChunk().getEntities()) {
175-
entity.getScheduler().execute(plugin, () -> {
176-
if (entity.getType() != limit.getKey()) return;
177-
if (count.incrementAndGet() <= maxAllowedPerChunk) return;
179+
List<Entity> entitiesToRemove = entry.getValue().subList(entityLimit, entry.getValue().size() - 1);
178180

181+
for (int i = 1; i < entitiesToRemove.size(); i++) {
182+
Entity entity = entitiesToRemove.get(i);
183+
int index = i;
184+
entity.getScheduler().execute(plugin, () -> {
179185
entity.remove();
180-
if (logIsEnabled) info("Removed entity " + entity.getType() + " at " +
181-
LocationUtil.toString(entity.getLocation()) + " because reached limit of " + maxAllowedPerChunk);
186+
logger().info("Removed {} at {} ({}/{} limit={})",
187+
entity.getType(),
188+
LocationUtil.toString(entity.getLocation()),
189+
index,
190+
entitiesToRemove.size(),
191+
entityLimit);
182192
}, null, 1L);
183193
}
184-
}
185-
}
186194

187-
// Abort spawning process when chunk limit is reached
188-
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
189-
private void onEntitySpawn(PreCreatureSpawnEvent event) {
190-
if (!entityLimits.containsKey(event.getType())) return;
195+
entitiesToRemove.clear();
196+
entry.getValue().clear();
197+
}
191198

192-
final int maxAllowedPerChunk = entityLimits.get(event.getType());
193-
int entityCount = 1; // Count entity that spawned in this event
199+
entitiesByTypes.clear();
200+
}
194201

195-
for (Entity entity : event.getSpawnLocation().getChunk().getEntities()) {
196-
if (entity.getType() != event.getType()) continue;
202+
@Override
203+
public void accept(ScheduledTask task) {
204+
for (World world : plugin.getServer().getWorlds()) {
205+
for (Chunk chunk : world.getLoadedChunks()) {
206+
if (ChunkUtil.isRetrievalUnsafe(chunk)) {
207+
continue;
208+
}
197209

198-
entityCount++;
210+
plugin.getServer().getRegionScheduler().execute(plugin, world, chunk.getX(), chunk.getZ(), () -> {
211+
if (!forceLoadEntities && !chunk.isEntitiesLoaded()) return;
212+
if (enableChunkAgeSkip && chunk.getInhabitedTime() < minChunkAge) return;
199213

200-
if (entityCount > maxAllowedPerChunk) {
201-
event.setCancelled(true);
202-
event.setShouldAbortSpawn(true);
214+
enforceEntityLimit(EntityUtil.getEntityTypeMap(chunk.getEntities()));
215+
});
203216
}
204217
}
205218
}
206219

207220
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
208-
private void onSpawn(EntitySpawnEvent event) {
209-
if (!entityLimits.containsKey(event.getEntityType())) return;
221+
private void onEntitiesLoad(EntitiesLoadEvent event) {
222+
if (ChunkUtil.isRetrievalUnsafe(event.getChunk())) return;
210223

211-
final int maxAllowedPerChunk = entityLimits.get(event.getEntityType());
212-
int entityCount = 1; // Count entity that spawned in this event
224+
enforceEntityLimit(EntityUtil.getEntityTypeMap(event.getEntities()));
225+
}
213226

214-
for (Entity entity : event.getEntity().getChunk().getEntities()) {
215-
if (entity.getType() != event.getEntityType()) continue;
227+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
228+
private void onEntitiesUnload(EntitiesUnloadEvent event) {
229+
if (ChunkUtil.isRetrievalUnsafe(event.getChunk())) return;
230+
231+
enforceEntityLimit(EntityUtil.getEntityTypeMap(event.getEntities()));
232+
}
233+
234+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
235+
private void onPreCreatureSpawn(PreCreatureSpawnEvent event) {
236+
if (!entityLimits.containsKey(event.getType())) return;
237+
238+
int entityLimit = entityLimits.get(event.getType());
239+
int entityCount = 0;
216240

217-
entityCount++;
218-
if (entityCount <= maxAllowedPerChunk) continue;
241+
for (Entity entity : event.getSpawnLocation().getChunk().getEntities()) {
242+
if (entity.getType() == event.getType()) {
243+
entityCount++;
244+
}
245+
}
219246

220-
entity.remove();
221-
if (logIsEnabled) info("Removed entity " + entity.getType() + " at " +
222-
LocationUtil.toString(entity.getLocation()) + " because reached limit of " + maxAllowedPerChunk);
247+
if (entityCount + 1 > entityLimit) {
248+
event.setCancelled(true);
249+
logger().info("Cancelled {} of {} at {} => count={}, limit={}",
250+
event.getEventName(),
251+
event.getType(),
252+
LocationUtil.toString(event.getSpawnLocation()),
253+
entityCount,
254+
entityLimit);
223255
}
224256
}
225257

226-
@Override
227-
public void accept(ScheduledTask task) {
228-
for (World world : plugin.getServer().getWorlds()) {
229-
for (Chunk chunk : world.getLoadedChunks()) {
230-
if (ChunkUtil.isRetrievalUnsafe(chunk)) continue;
258+
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
259+
private void onEntitySpawn(EntitySpawnEvent event) {
260+
if (!entityLimits.containsKey(event.getEntityType())) return;
231261

232-
plugin.getServer().getRegionScheduler().execute(plugin, world, chunk.getX(), chunk.getZ(), () -> {
233-
if (!forceLoadEntities && !chunk.isEntitiesLoaded()) return;
234-
if (enableChunkAgeSkip && chunk.getInhabitedTime() < minChunkAge) return;
262+
int entityLimit = entityLimits.get(event.getEntityType());
263+
int entityCount = 0;
235264

236-
Entity[] chunkEntities = chunk.getEntities();
237-
238-
for (Map.Entry<EntityType, Integer> limit : entityLimits.entrySet()) {
239-
AtomicInteger count = new AtomicInteger();
240-
for (Entity entity : chunkEntities) {
241-
entity.getScheduler().execute(plugin, () -> {
242-
if (entity.getType() != limit.getKey()) return;
243-
if (count.incrementAndGet() <= limit.getValue()) return;
244-
245-
entity.remove();
246-
if (logIsEnabled) info("Removed entity " + entity.getType() + " at " +
247-
LocationUtil.toString(entity.getLocation()) + " because reached limit of " + limit.getValue());
248-
}, null, 1L);
249-
}
250-
}
251-
});
265+
for (Entity entity : event.getLocation().getChunk().getEntities()) {
266+
if (entity.getType() == event.getEntityType()) {
267+
entityCount++;
252268
}
253269
}
270+
271+
if (entityCount + 1 > entityLimit) {
272+
event.setCancelled(true);
273+
logger().info("Cancelled {} of {} at {} => count={}, limit={}",
274+
event.getEventName(),
275+
event.getEntityType(),
276+
LocationUtil.toString(event.getLocation()),
277+
entityCount,
278+
entityLimit);
279+
}
254280
}
255281
}

0 commit comments

Comments
 (0)