Skip to content

Commit 3befe24

Browse files
committed
improve verbosity of blocklimits
1 parent 6476dfa commit 3befe24

File tree

4 files changed

+239
-73
lines changed

4 files changed

+239
-73
lines changed

AnarchyExploitFixesFolia/src/main/java/me/xginko/aef/config/Translation.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ public class Translation {
1818
public final Component no_permission, invalid_syntax, failed_argument_parse, chat_commandwhitelist_badcommand,
1919
misc_joinMessage, misc_leaveMessage, misc_MaskedKickMessage,
2020
misc_enabledConnectionMsgs, misc_disabledConnectionMsgs, misc_enabledFirstJoinMsgs, misc_disabledFirstJoinMsgs,
21-
preventions_witherSpawningDisabledInRadius,elytra_disablePacketElytraFly,
21+
preventions_witherSpawningDisabledInRadius,
22+
chunklimits_block_limit_exceeded,
23+
elytra_disablePacketElytraFly,
2224
elytra_global_YouAreFlyingIn, elytra_global_New, elytra_global_New_UpperCase, elytra_global_Old,
2325
elytra_global_Old_UpperCase, elytra_global_Speed, elytra_global_DisabledLowTPS, elytra_global_TooFastLowTPS,
2426
elytra_global_TooFastChunkInfo, elytra_disable_timer, elytra_global_TooFast, elytra_global_DisabledHere,
@@ -81,12 +83,17 @@ public Translation(Locale locale) throws Exception {
8183
List.of("<gray>%player% joined the game for the first time. They are %players_num% to join."));
8284
this.misc_MaskedKickMessage = getTranslation("kicks.masked-kick-message",
8385
"<gold>Disconnected");
86+
8487
// Lag Preventions
8588
this.lagpreventions_stopSpammingLevers = getTranslation("redstone.stop-spamming-levers",
8689
"<red>Stop spamming levers.");
8790
// Disable Wither Spawning at Spawn
8891
this.preventions_witherSpawningDisabledInRadius = getTranslation("withers.disabled-at-spawn",
8992
"<dark_red>Wither spawning is disabled in a radius of %radius% blocks around spawn.");
93+
94+
this.chunklimits_block_limit_exceeded = getTranslation("chunk-limits.block-limit.block-limit-reached",
95+
"<dark_red>You can't place more %block% in this chunk. Existing: %existing% Limit: %limit%.");
96+
9097
/*
9198
Elytra
9299
*/

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

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,40 @@
44
import com.github.benmanes.caffeine.cache.Cache;
55
import com.github.benmanes.caffeine.cache.Caffeine;
66
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
7+
import me.xginko.aef.AnarchyExploitFixes;
78
import me.xginko.aef.modules.AEFModule;
9+
import me.xginko.aef.utils.LocationUtil;
10+
import net.kyori.adventure.text.TextReplacementConfig;
811
import org.bukkit.Chunk;
12+
import org.bukkit.Location;
913
import org.bukkit.Material;
1014
import org.bukkit.event.EventHandler;
1115
import org.bukkit.event.EventPriority;
1216
import org.bukkit.event.HandlerList;
1317
import org.bukkit.event.Listener;
18+
import org.bukkit.event.block.Action;
1419
import org.bukkit.event.block.BlockPlaceEvent;
1520
import org.bukkit.event.player.PlayerInteractEvent;
21+
import org.jetbrains.annotations.NotNull;
1622

1723
import java.time.Duration;
1824
import java.util.EnumMap;
1925
import java.util.Map;
26+
import java.util.Objects;
2027
import java.util.TreeMap;
2128

2229
public class BlockLimit extends AEFModule implements Listener {
2330

2431
private final Map<Material, Integer> blockLimits = new EnumMap<>(Material.class);
2532
private final long materialCountCacheMillis;
33+
private final boolean notifyPlayer;
2634

27-
private Cache<Chunk, Cache<Material, Boolean>> chunkMaterialCache;
35+
private Cache<@NotNull Chunk, Cache<@NotNull Material, Integer>> chunkMaterialCountCache;
2836

2937
public BlockLimit() {
3038
super("chunk-limits.block-limit", false);
39+
this.notifyPlayer = config.getBoolean(configPath + ".notify-player", true,
40+
"Send a message to the player when they cant place a block due to chunk limit.");
3141
this.materialCountCacheMillis = config.getLong(configPath + ".material-count-cache-millis", 1000,
3242
"Recommended to not go higher than 5000ms.");
3343

@@ -135,14 +145,15 @@ public BlockLimit() {
135145
}
136146
}
137147

138-
ConfigSection section = config.getConfigSection(configPath + ".max-blocks-per-chunk", compatible,
148+
ConfigSection limitsSection = config.getConfigSection(configPath + ".max-blocks-per-chunk", compatible,
139149
"Attempt to prevent ChunkBan / Client FPS Lag");
140-
for (String configuredMaterial : section.getKeys(false)) {
150+
for (String configuredMaterial : limitsSection.getKeys(false)) {
151+
String configuredMaxAmount = limitsSection.getString(configuredMaterial);
141152
try {
142-
Material blockMaterial = Material.valueOf(configuredMaterial);
143-
Integer maxAmountPerChunk = Integer.parseInt(section.getString(configuredMaterial));
144-
this.blockLimits.put(blockMaterial, maxAmountPerChunk);
145-
} catch (NumberFormatException e) {
153+
this.blockLimits.put(
154+
Material.valueOf(configuredMaterial),
155+
Integer.parseInt(Objects.requireNonNull(configuredMaxAmount)));
156+
} catch (NumberFormatException | NullPointerException e) {
146157
notRecognized(Integer.class, configuredMaterial);
147158
} catch (IllegalArgumentException e) {
148159
notRecognized(Material.class, configuredMaterial);
@@ -152,70 +163,149 @@ public BlockLimit() {
152163

153164
@Override
154165
public void enable() {
155-
chunkMaterialCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(1)).build();
166+
chunkMaterialCountCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(1)).build();
156167
plugin.getServer().getPluginManager().registerEvents(this, plugin);
157168
}
158169

159170
@Override
160171
public void disable() {
161172
HandlerList.unregisterAll(this);
162-
if (chunkMaterialCache != null) {
163-
for (Map.Entry<Chunk, Cache<Material, Boolean>> entry : chunkMaterialCache.asMap().entrySet()) {
173+
if (chunkMaterialCountCache != null) {
174+
for (Map.Entry<@NotNull Chunk, Cache<@NotNull Material, Integer>> entry : chunkMaterialCountCache.asMap().entrySet()) {
164175
entry.getValue().invalidateAll();
165176
entry.getValue().cleanUp();
166177
}
167-
chunkMaterialCache.invalidateAll();
168-
chunkMaterialCache.cleanUp();
169-
chunkMaterialCache = null;
178+
chunkMaterialCountCache.invalidateAll();
179+
chunkMaterialCountCache.cleanUp();
180+
chunkMaterialCountCache = null;
170181
}
171182
}
172183

173184
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
174185
private void onBlockPlace(BlockPlaceEvent event) {
175-
if (blockLimits.containsKey(event.getBlock().getType())
176-
&& exceedsPerChunkLimit(event.getBlock().getType(), event.getBlock().getChunk())) {
177-
event.setCancelled(true);
186+
if (!blockLimits.containsKey(event.getBlock().getType())) {
187+
return;
178188
}
189+
190+
int materialCount = getMaterialCountForChunk(event.getBlock().getType(), event.getBlock().getChunk());
191+
int limit = blockLimits.get(event.getBlock().getType());
192+
193+
if (materialCount <= limit) {
194+
logger().debug("Player '{}' may place block {} at location {} => count={} limit={}",
195+
event.getPlayer().getName(),
196+
event.getBlock().getType(),
197+
LocationUtil.toString(event.getBlock().getLocation()),
198+
materialCount,
199+
limit);
200+
return;
201+
}
202+
203+
event.setCancelled(true);
204+
205+
if (notifyPlayer) event.getPlayer().sendMessage(
206+
AnarchyExploitFixes.translation(event.getPlayer()).chunklimits_block_limit_exceeded
207+
.replaceText(TextReplacementConfig.builder()
208+
.matchLiteral("%block%")
209+
.replacement(event.getBlock().getType().name())
210+
.build())
211+
.replaceText(TextReplacementConfig.builder()
212+
.matchLiteral("%existing%")
213+
.replacement(Integer.toString(materialCount))
214+
.build())
215+
.replaceText(TextReplacementConfig.builder()
216+
.matchLiteral("%limit%")
217+
.replacement(Integer.toString(limit))
218+
.build()));
219+
220+
logger().info("Cancelled placement of {} for player '{}' at location {} => count={} limit={}",
221+
event.getBlock().getType(),
222+
event.getPlayer().getName(),
223+
LocationUtil.toString(event.getBlock().getLocation()),
224+
materialCount,
225+
blockLimits.get(event.getBlock().getType()));
179226
}
180227

228+
@SuppressWarnings("deprecation")
181229
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
182230
private void onPlayerInteract(PlayerInteractEvent event) {
183-
if (event.getAction().isLeftClick()) return;
231+
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) return;
232+
233+
if (!blockLimits.containsKey(event.getMaterial())) {
234+
return;
235+
}
236+
237+
// Ignore interactions like for ex. players wanting to flick a lever
238+
if (event.getMaterial().isInteractable() && !event.getPlayer().isSneaking()) {
239+
return; // Let BlockPlaceEvent deal with this
240+
}
241+
242+
Location placeLocation = event.getClickedBlock().getRelative(event.getBlockFace()).getLocation();
243+
int materialCount = getMaterialCountForChunk(event.getMaterial(), placeLocation.getChunk());
244+
int limit = blockLimits.get(event.getMaterial());
184245

185-
if (blockLimits.containsKey(event.getMaterial())
186-
&& exceedsPerChunkLimit(event.getMaterial(), event.getPlayer().getChunk())) {
187-
event.setCancelled(true);
246+
if (materialCount <= limit) {
247+
logger().debug("Player '{}' may place block {} at location {} => count={} limit={}",
248+
event.getPlayer().getName(),
249+
event.getMaterial(),
250+
LocationUtil.toString(placeLocation),
251+
materialCount,
252+
limit);
253+
return;
188254
}
255+
256+
event.setCancelled(true);
257+
258+
if (notifyPlayer) event.getPlayer().sendMessage(
259+
AnarchyExploitFixes.translation(event.getPlayer()).chunklimits_block_limit_exceeded
260+
.replaceText(TextReplacementConfig.builder()
261+
.matchLiteral("%block%")
262+
.replacement(event.getMaterial().name())
263+
.build())
264+
.replaceText(TextReplacementConfig.builder()
265+
.matchLiteral("%existing%")
266+
.replacement(Integer.toString(materialCount))
267+
.build())
268+
.replaceText(TextReplacementConfig.builder()
269+
.matchLiteral("%limit%")
270+
.replacement(Integer.toString(limit))
271+
.build()));
272+
273+
logger().info("Cancelled placement of {} for player '{}' at location {} => count={} limit={}",
274+
event.getMaterial(),
275+
event.getPlayer().getName(),
276+
LocationUtil.toString(placeLocation),
277+
materialCount,
278+
blockLimits.get(event.getMaterial()));
189279
}
190280

191-
private boolean exceedsPerChunkLimit(Material blockType, Chunk chunk) {
192-
final int limit = blockLimits.get(blockType);
193-
final int minY = chunk.getWorld().getMinHeight();
194-
final int maxY = chunk.getWorld().getMaxHeight();
281+
private int getMaterialCountForChunk(Material blockType, Chunk chunk) {
282+
Cache<@NotNull Material, Integer> countCache = chunkMaterialCountCache.getIfPresent(chunk);
195283

196-
Cache<Material, Boolean> exceededCache = chunkMaterialCache.getIfPresent(chunk);
197-
if (exceededCache == null) {
198-
exceededCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(materialCountCacheMillis)).build();
284+
if (countCache == null) {
285+
countCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(materialCountCacheMillis)).build();
199286
}
200287

201-
Boolean exceeded = exceededCache.get(blockType, material -> {
288+
Integer cachedMaterialCount = countCache.getIfPresent(blockType);
289+
290+
if (cachedMaterialCount == null) {
291+
int minY = chunk.getWorld().getMinHeight();
292+
int maxY = chunk.getWorld().getMaxHeight();
202293
int count = 0;
294+
203295
for (int x = 0; x < 16; x++) {
204296
for (int z = 0; z < 16; z++) {
205297
for (int y = minY; y < maxY; y++) {
206-
if (chunk.getBlock(x, y, z).getType() == material) {
298+
if (chunk.getBlock(x, y, z).getType() == blockType) {
207299
count++;
208-
if (count > limit) {
209-
return true;
210-
}
211300
}
212301
}
213302
}
214303
}
215-
return false;
216-
});
217304

218-
chunkMaterialCache.put(chunk, exceededCache);
219-
return exceeded;
305+
cachedMaterialCount = count;
306+
countCache.put(blockType, count);
307+
}
308+
309+
return cachedMaterialCount;
220310
}
221311
}

AnarchyExploitFixesLegacy/src/main/java/me/xginko/aef/config/Translation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class Translation {
1919
public final String no_permission, invalid_syntax, failed_argument_parse, chat_CommandWhitelist_BadCommand,
2020
misc_joinMessage, misc_leaveMessage, misc_MaskedKickMessage, withers_SpawningDisabledInRadius,
2121
misc_enabledConnectionMsgs, misc_disabledConnectionMsgs, misc_enabledFirstJoinMsgs, misc_disabledFirstJoinMsgs,
22+
chunklimits_block_limit_exceeded,
2223
elytra_disablePacketElytraFly, elytra_global_YouAreFlyingIn, elytra_global_New, elytra_global_New_Color, elytra_global_Old,
2324
elytra_global_Old_Color, elytra_global_Speed, elytra_global_DisabledLowTPS, elytra_global_TooFastLowTPS,
2425
elytra_global_TooFastChunkInfo, elytra_global_TooFast, elytra_global_DisabledHere, elytra_global_Chunks,
@@ -81,6 +82,10 @@ public Translation(Locale locale) throws Exception {
8182
Collections.singletonList("&6%player% joined the game for the first time. They are %players_num% to join."));
8283
this.misc_MaskedKickMessage = getTranslation("kicks.masked-kick-message",
8384
"&6Disconnected");
85+
86+
this.chunklimits_block_limit_exceeded = getTranslation("chunk-limits.block-limit.block-limit-reached",
87+
"&4You can't place more %block% in this chunk. Existing: %existing% Limit: %limit%.");
88+
8489
// Withers
8590
this.withers_SpawningDisabledInRadius = getTranslation("withers.disabled-at-spawn",
8691
"&4Wither spawning is disabled in a radius of %radius% blocks around spawn.");

0 commit comments

Comments
 (0)