Compare commits

...
25 Commits
Author SHA1 Message Date
Maddy Miller 63e206713b Skip testMoveTo while session is uninitialized. Fixes #2325 2026-11-04 23:19:30 +10:00
Maddy Miller 488d94d640 Back to SNAPSHOT for continued development 2026-11-03 20:27:47 +10:00
Maddy Miller da30a99320 Release 7.0.21 2026-11-03 20:27:26 +10:00
Maddy Miller a4e15295c2 Skip printing error message if a known Folia detection failure 2026-11-03 20:12:52 +10:00
Maddy Miller 1c98e6d5a1 Resolve usages of deprecated for removal methods 2026-11-03 19:24:40 +10:00
Maddy Miller d9a8ca5042 Cleanup allocations by session caching code (#2325)
* Cleanup allocations by session caching code

* Even further cleanup, no weak references are needed

* back to asMap

* Keep World for the test itself, cache string for key
2026-11-03 19:17:42 +10:00
byPixelTV 90acadb235 Fix cross-region block access and replaced block checks (#2321) 2026-11-03 00:29:19 +10:00
Almost 5c0c7b4781 Add the explosion trigger player as the cause when creeper's prime for explosion (#2322)
* Keep creeper target as cause after the target dies

* Track creeper target on explosion prime instead of target event

Move the tracking to an ExplosionPrimeEvent handler in
WorldGuardEntityListener, as requested in review. The existing creeper
cleanup in onEntityExplode clears it.

* Move creeper target tracking into existing explosion prime handler
2026-11-02 20:01:47 +10:00
5e8c7634b0 Delegate to WorldEdit's Folia check (#2319)
* Do not check for PaperMC Folia brand

* Bump WorldEdit and delegate Folia check

* Ditch PaperLib check and use direct call

---------

Co-authored-by: iLemon <alex@lemongaming.ltd>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:50:40 +10:00
Daniel3032 1dea00d293 Ensure straw beds are treated as beds 2026-11-01 13:46:02 +10:00
Mykhailo Alipa cb77add3c9 Protect cushion placement through existing item handling (#2318)
* Protect cushion placement in claimed regions

* Use entity placement events for all player-placed entities

* Minimize the entity placement diff

* Handle cushions through the existing item placement path

* Add a cushion material helper matching the boat check
2026-10-22 20:13:02 +10:00
Maddy Miller 9545cce43a Back to SNAPSHOT for continued development 2026-10-17 16:42:41 +10:00
Maddy Miller 0b6c3f1045 Release 7.0.20 2026-10-17 16:41:25 +10:00
Maddy Miller 3f9b7cf4e0 Add cushions to ENTITY_ITEMS 2026-10-17 16:13:38 +10:00
HPnetwork fdbdb91816 Avoid NPE when clearing a region flag (#2307)
* Fix region flag clear feedback

* Use unset feedback when state flag parsing returns null
2026-10-16 21:33:07 +10:00
BaiChuan a670fc70f9 Guard Folia creature cause expansion by region ownership (#2312) 2026-10-16 20:52:49 +10:00
Maddy Miller 408845ad09 Add materials map for 26.3 blocks/items 2026-10-16 20:44:07 +10:00
CatTeaA 96d18c4efc Fix PlayerBucketEmptyEvent to use correct position check (#2313)
* Fix PlayerBucketEmptyEvent to use correct position check

* Considering that cauldrons trigger PlayerBucketEmptyEvent
2026-10-16 09:55:21 +10:00
nivcoo a468153cd8 feat. add entity break event handling and update Paper API version to 26.3 (#2315)
* feat. add entity break event handling and update Paper API version to 26.3

* improv. add braces to handle Hanging entities
2026-10-15 20:27:30 +10:00
Maddy Miller 4181ebfe36 Fixed Poplar boats causing an error, and harden against future issues (#2309) 2026-09-23 19:29:35 +10:00
Maddy Miller a6d64dcc79 Back to SNAPSHOT for continued development 2026-09-18 22:09:41 +10:00
Maddy Miller f395a1675b Release 7.0.19 2026-09-18 22:09:17 +10:00
Brokkonaut 0aeb9d8d54 Fix lock flag restoration after session reinitialization (#2304) 2026-09-18 21:18:27 +10:00
Rasmus Kjær Damgaard 7c280a1348 Cancel blocked natural spawns before the entity is created (#2300)
* Cancel blocked natural spawns before the entity is created

The mob-spawning and deny-spawn checks in WorldGuardEntityListener fire
on CreatureSpawnEvent, at the very end of the spawn pipeline. By that
point the server has picked a spawn position, run the placement checks,
constructed the mob and run finalizeSpawn, and the cancelled mob is
thrown away. Since a cancelled spawn never counts toward the mob cap,
the natural spawner keeps retrying the same area at full rate, so
regions that deny mob spawning become permanent spawn attempt hotspots
that pay entity construction over and over for nothing.

On Paper servers the same checks can run in PreCreatureSpawnEvent,
before the entity exists. Cancelling there also ends the remaining
attempts for the chunk in that spawn cycle, so the wasted work is gone
almost entirely. Measured on a flat test world with a region denying
mob-spawning over the whole spawn range and the mob cap kept empty, the
attempt rate around a single player collapsed from roughly 75000
attempts per second to roughly 60 per second, with no entities
constructed at all.

The new listener mirrors the natural spawn conditions from
onCreatureSpawn exactly: activity halt, block-creature-spawn,
mob-spawning and deny-spawn region flags, and block-ground-slimes. All
other spawn reasons keep going through the existing CreatureSpawnEvent
checks unchanged, and the listener is only registered when the Paper
event class is present.

* Move the pre spawn handler into the existing Paper inner listener

Review feedback: platform specific code lives in the Paper inner
classes of the main listeners, and WorldGuardEntityListener already has
one for EntityZapEvent, so the handler moves in there and the separate
listener class is gone.

* Match the HIGH priority of the other protection handlers

* Extract the shared spawn checks into a method used by both handlers

The pre spawn handler duplicated the tail of onCreatureSpawn. Both now
call handleCreatureSpawn, which takes the event as a Cancellable and
the location, entity type and spawn reason as parameters, following
the same shape as handlePigZap. The entity specific checks (plugin
spawning, armor stands, tamed animals) stay in onCreatureSpawn since
no entity exists yet at pre spawn time, and the activity halt check
stays in each handler so its ordering relative to those checks is
unchanged.

* Pass cfg and wcfg into the shared spawn checks

Both handlers already have them, so the shared method no longer does a
second lookup.

* Correct the rationale on the pre spawn handler

The comment claimed that cancelling ends the chunk's remaining spawn
attempts. It does not. Only setShouldAbortSpawn(true) makes the spawner
return early; a plain cancel falls through to the next candidate position
exactly like a failed placement check.

What the handler does buy is stated accurately instead: the placement
checks, the entity construction and finalizeSpawn are skipped for a spawn
that was going to be refused.

Two consequences a server operator should know are now named. The region
query runs for every candidate position rather than every constructed mob,
because Paper fires the event before the vanilla suitability checks. And on
Paper with per-player-mob-spawns enabled, every cancelled pre spawn is
charged to a per player mob backoff counter, so a large denied region can
suppress spawning in neighbouring chunks that allow mobs.

* Add a config option for the pre spawn handler

regions.use-pre-creature-spawn-event, default true, matching how
regions.use-creature-spawn-event already gates the CreatureSpawnEvent
path. Turning it off leaves the existing handler doing all the work, so
a server that sees a regression can revert without downgrading.
2026-08-11 21:21:44 +10:00
Vladimir Turov c90c483862 Add Cause tracking for SulfurCube (#2295) 2026-08-02 18:20:44 +10:00
31 changed files with 292 additions and 118 deletions

No files matched your search

+25
View File
@@ -1,5 +1,30 @@
# Changelog
## 7.0.21
* Improved protection checks for placing cushions.
* Fixed straw beds not being treated as beds for certain flags.
* Fixed cases where creepers could lose tracking of players who triggered them to explode.
* Improved caching to ensure unloaded worlds are not kept around in memory longer than necessary.
* Improved Folia detection to better detect Folia forks.
* Fixed errors on Folia caused by complex cross-world block interactions.
## 7.0.20
* Require 26.3+.
* Fixed poplar boats causing errors in some situations.
* Support Paper's new EntityBreakEvent (used for cushion destruction) in protection.
* Improved handling of buckets on waterlogged blocks when sneaking.
* Fixed a Folia error caused by entity actions occurring across Folia region boundaries.
* Improved command feedback when setting a state flag to "none".
## 7.0.19
* Require 26.2+.
* Improved handling for TNT inside of sulfur cubes.
* Improve performance of mob spawn prevention on Paper servers in some situations.
* Improved handling of certain flags when `/wg flushstates` is run, or in certain login situations.
## 7.0.18
* Add lava-harden flag for cobblestone/stone/obsidian/basalt forming.
+1 -1
View File
@@ -1,4 +1,4 @@
group=com.sk89q.worldguard
version=7.0.19-SNAPSHOT
version=7.0.22-SNAPSHOT
org.gradle.parallel=true
+2 -2
View File
@@ -1,5 +1,5 @@
[versions]
worldedit = "7.4.4"
worldedit = "7.4.6"
crankcase = "0.1.0"
# Minimum versions we apply to make dependencies support newer Java
@@ -27,7 +27,7 @@ autoService = "com.google.auto.service:auto-service:1.1.1"
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
jetbrains-annotations = "org.jetbrains:annotations:24.1.0"
paperApi = "io.papermc.paper:paper-api:26.2.build.+"
paperApi = "io.papermc.paper:paper-api:26.3.build.+"
paperLib = "io.papermc:paperlib:1.0.8"
dummypermscompat = "com.sk89q:dummypermscompat:1.10"
@@ -91,7 +91,7 @@ public class BukkitWorldConfiguration extends YamlWorldConfiguration {
plugin.createDefaultConfiguration(configFile, "config_world.yml");
plugin.createDefaultConfiguration(blacklistFile, "blacklist.txt");
config = new YAMLProcessor(configFile, true, YAMLFormat.EXTENDED);
config = new YAMLProcessor(configFile.toPath(), true, YAMLFormat.EXTENDED);
loadConfiguration();
if (summaryOnStart) {
@@ -298,7 +298,7 @@ public class BukkitWorldConfiguration extends YamlWorldConfiguration {
explosionFlagCancellation = getBoolean("regions.explosion-flags-block-entity-damage", true);
highFreqFlags = getBoolean("regions.high-frequency-flags", false);
checkLiquidFlow = getBoolean("regions.protect-against-liquid-flow", false);
regionWand = convertLegacyItem(getString("regions.wand", ItemTypes.LEATHER.getId()));
regionWand = convertLegacyItem(getString("regions.wand", ItemTypes.LEATHER.id()));
maxClaimVolume = getInt("regions.max-claim-volume", 30000);
claimOnlyInsideExistingRegions = getBoolean("regions.claim-only-inside-existing-regions", false);
setParentOnClaim = getString("regions.set-parent-on-claim", "");
@@ -559,15 +559,14 @@ public class WorldGuardPlugin extends JavaPlugin {
private final LazyReference<Boolean> folia = LazyReference.from(() -> {
try {
// Folia is Paper-based, so this is a good first check.
if (PaperLib.isPaper()) {
return ServerBuildInfo.buildInfo().isBrandCompatible(net.kyori.adventure.key.Key.key("papermc", "folia"));
}
} catch (Throwable t) {
return getWorldEdit().isFolia(); // Delegate check to WorldEdit
} catch (NoSuchMethodError _) {
// Ignore, this likely means an outdated version.
LOGGER.warn("Failed to check if server is running Folia, as WorldEdit's Folia support is missing.");
} catch (Throwable t) {
// Unknown error, log it and return false.
LOGGER.warn("Failed to check if server is running Folia", t);
}
return false;
});
@@ -40,6 +40,7 @@ import org.bukkit.entity.Firework;
import org.bukkit.entity.LightningStrike;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.SulfurCube;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Vehicle;
@@ -117,7 +118,7 @@ public final class Cause {
return false;
}
if (object instanceof TNTPrimed || object instanceof Vehicle) {
if (object instanceof TNTPrimed || object instanceof Vehicle || object instanceof SulfurCube) {
if (!PaperLib.isPaper()) {
return false;
}
@@ -178,7 +179,8 @@ public final class Cause {
@Nullable
public Block getFirstBlock() {
for (Object object : causes) {
if (object instanceof Block b) {
if (object instanceof Block b && (!WorldGuardPlugin.inst().isFolia()
|| Bukkit.isOwnedByCurrentRegion(b.getLocation()))) {
return b;
}
}
@@ -330,10 +332,16 @@ public final class Cause {
}
} else if (o instanceof Creeper c) {
indirect = true;
addAll(c.getTarget(), c.getIgniter());
if (!WorldGuardPlugin.inst().isFolia()
|| Bukkit.isOwnedByCurrentRegion(c)) {
addAll(c.getTarget(), c.getIgniter());
}
} else if (o instanceof Creature c) {
indirect = true;
addAll(c.getTarget());
if (!WorldGuardPlugin.inst().isFolia()
|| Bukkit.isOwnedByCurrentRegion(c)) {
addAll(c.getTarget());
}
} else if (o instanceof BlockProjectileSource) {
addAll(((BlockProjectileSource) o).getBlock());
} else if (o instanceof LightningStrike && PaperLib.isPaper() &&
@@ -48,6 +48,8 @@ import com.sk89q.worldguard.bukkit.util.PaperInterop;
import com.sk89q.worldguard.config.WorldConfiguration;
import com.sk89q.worldguard.protection.flags.Flags;
import io.papermc.lib.PaperLib;
import io.papermc.paper.event.entity.EntityBreakByEntityEvent;
import io.papermc.paper.event.entity.EntityBreakEvent;
import io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent;
import io.papermc.paper.event.player.PlayerOpenSignEvent;
import org.bukkit.Bukkit;
@@ -77,12 +79,14 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Firework;
import org.bukkit.entity.FishHook;
import org.bukkit.entity.Hanging;
import org.bukkit.entity.Item;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.bukkit.entity.Painting;
import org.bukkit.entity.Player;
import org.bukkit.entity.SulfurCube;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.WindCharge;
@@ -242,7 +246,7 @@ public class EventAbstractionListener extends AbstractListener {
BlockState previousState = event.getBlockReplacedState();
// Some blocks, like tall grass and fire, get replaced
if (previousState.getType() != Material.AIR && previousState.getType() != event.getBlockReplacedState().getType()) {
if (previousState.getType() != Material.AIR && previousState.getType() != event.getBlockPlaced().getType()) {
Events.fireToCancel(event, new BreakBlockEvent(event, create(event.getPlayer()), previousState.getLocation(), previousState.getType()));
}
@@ -667,8 +671,13 @@ public class EventAbstractionListener extends AbstractListener {
Player player = event.getPlayer();
Block blockClicked = event.getBlockClicked();
Block blockAffected;
if (blockClicked.getBlockData() instanceof Waterlogged) {
boolean canPlaceInside = false;
// Can be placed inside blocks only when not sneaking
if (!player.isSneaking()) {
canPlaceInside = (event.getBucket() != Material.LAVA_BUCKET && blockClicked.getBlockData() instanceof Waterlogged)
|| (Materials.isCauldron(blockClicked.getType()) && Materials.isCauldronBucketType(event.getBucket()));
}
if (canPlaceInside) {
blockAffected = blockClicked;
} else {
blockAffected = blockClicked.getRelative(event.getBlockFace());
@@ -895,7 +904,18 @@ public class EventAbstractionListener extends AbstractListener {
if (matchingItem != null && hasInteractBypass(world, matchingItem)) {
useEntityEvent.setAllowed(true);
}
Events.fireToCancel(event, useEntityEvent);
if (!Events.fireToCancel(event, useEntityEvent)) {
/* Sulfur cube doesn't have API for interaction/ignition source tracking */
if(entity instanceof SulfurCube) {
/* Remove player in case of using shears */
if(item.getType() == Material.SHEARS) {
Cause.untrackParentCause(entity);
/* Save player overwise */
} else {
Cause.trackParentCause(entity, player);
}
}
}
}
@EventHandler(ignoreCancelled = true)
@@ -917,6 +937,8 @@ public class EventAbstractionListener extends AbstractListener {
eventToFire.getRelevantFlags().add(Flags.FIREWORK_DAMAGE);
} else if (damager instanceof Creeper) {
eventToFire.getRelevantFlags().add(Flags.CREEPER_EXPLOSION);
} else if (damager instanceof SulfurCube) {
eventToFire.getRelevantFlags().add(Flags.TNT);
}
if (Events.fireToCancel(event, eventToFire)) {
if (damager instanceof Tameable && damager instanceof Mob) {
@@ -1244,7 +1266,17 @@ public class EventAbstractionListener extends AbstractListener {
// Handle created boats
if (item != null && Materials.isBoat(item.getType())) {
Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), Materials.getRelatedEntity(item.getType())));
EntityType entityType = Materials.getRelatedEntity(item.getType());
if (entityType == null) {
entityType = EntityType.OAK_BOAT;
}
Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), entityType));
return;
}
// Handle created cushions
if (item != null && Materials.isCushion(item.getType())) {
Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.CUSHION));
return;
}
@@ -1362,6 +1394,26 @@ public class EventAbstractionListener extends AbstractListener {
public void onEntityKnockbackByEntity(EntityPushedByEntityAttackEvent event) {
handleKnockback(event, event.getPushedBy());
}
@EventHandler(ignoreCancelled = true)
public void onEntityBreak(EntityBreakEvent event) {
if (event.getEntity() instanceof Hanging) {
return;
}
if (event instanceof EntityBreakByEntityEvent entityEvent) {
Entity remover = entityEvent.getRemover();
Events.fireToCancel(event, new DestroyEntityEvent(event, create(remover), event.getEntity()));
if (event.isCancelled() && remover instanceof Player player) {
playDenyEffect(player, event.getEntity().getLocation());
}
} else if (event.getCause() == EntityBreakEvent.RemoveCause.EXPLOSION) {
DestroyEntityEvent destroyEntityEvent = new DestroyEntityEvent(event, Cause.unknown(), event.getEntity());
destroyEntityEvent.getRelevantFlags().add(Flags.OTHER_EXPLOSION);
Events.fireToCancel(event, destroyEntityEvent);
}
}
}
@SuppressWarnings("removal")
@@ -57,6 +57,7 @@ import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Item;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.entity.SulfurCube;
import org.bukkit.entity.Tameable;
import org.bukkit.event.Event;
import org.bukkit.event.Event.Result;
@@ -222,7 +223,7 @@ public class RegionProtectionListener extends AbstractListener {
String what;
/* TNT */
if (event.getCause().find(EntityType.TNT, EntityType.TNT_MINECART) != null) {
if (event.getCause().find(EntityType.TNT, EntityType.TNT_MINECART, EntityType.SULFUR_CUBE) != null) {
canBreak = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.BLOCK_BREAK, Flags.TNT));
what = "use dynamite";
@@ -154,7 +154,7 @@ public class WorldGuardBlockListener extends AbstractListener {
Material targetId = blockTo.getType();
if ((isAir || isWater) &&
wcfg.preventWaterDamage.contains(BukkitAdapter.asBlockType(targetId).getId())) {
wcfg.preventWaterDamage.contains(BukkitAdapter.asBlockType(targetId).id())) {
event.setCancelled(true);
return;
}
@@ -163,7 +163,7 @@ public class WorldGuardBlockListener extends AbstractListener {
if (!wcfg.allowedLavaSpreadOver.isEmpty() && isLava) {
Material targetId = blockTo.getRelative(0, -1, 0).getType();
if (!wcfg.allowedLavaSpreadOver.contains(BukkitAdapter.asBlockType(targetId).getId())) {
if (!wcfg.allowedLavaSpreadOver.contains(BukkitAdapter.asBlockType(targetId).id())) {
event.setCancelled(true);
return;
}
@@ -233,11 +233,11 @@ public class WorldGuardBlockListener extends AbstractListener {
int y = block.getY();
int z = block.getZ();
if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y - 1, z).getType()).getId())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x + 1, y, z).getType()).getId())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x - 1, y, z).getType()).getId())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z - 1).getType()).getId())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z + 1).getType()).getId())) {
if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y - 1, z).getType()).id())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x + 1, y, z).getType()).id())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x - 1, y, z).getType()).id())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z - 1).getType()).id())
|| wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z + 1).getType()).id())) {
event.setCancelled(true);
return;
}
@@ -303,7 +303,7 @@ public class WorldGuardBlockListener extends AbstractListener {
if (!wcfg.disableFireSpreadBlocks.isEmpty()) {
Block block = event.getBlock();
if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(block.getType()).getId())) {
if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(block.getType()).id())) {
event.setCancelled(true);
checkAndDestroyFireAround(block.getWorld(), block.getX(), block.getY(), block.getZ());
return;
@@ -511,7 +511,7 @@ public class WorldGuardBlockListener extends AbstractListener {
if (!wcfg.allowedSnowFallOver.isEmpty()) {
Material targetId = event.getBlock().getRelative(0, -1, 0).getType();
if (!wcfg.allowedSnowFallOver.contains(BukkitAdapter.asBlockType(targetId).getId())) {
if (!wcfg.allowedSnowFallOver.contains(BukkitAdapter.asBlockType(targetId).id())) {
event.setCancelled(true);
return;
}
@@ -20,6 +20,7 @@
package com.sk89q.worldguard.bukkit.listener;
import com.destroystokyo.paper.event.entity.EntityZapEvent;
import com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.LocalPlayer;
@@ -61,6 +62,7 @@ import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.SulfurCube;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.WindCharge;
@@ -487,7 +489,7 @@ public class WorldGuardEntityListener extends AbstractListener {
event.blockList().clear();
return;
}
} else if (ent instanceof TNTPrimed || ent instanceof ExplosiveMinecart) {
} else if (ent instanceof TNTPrimed || ent instanceof ExplosiveMinecart || ent instanceof SulfurCube) {
if (wcfg.blockTNTExplosions) {
event.setCancelled(true);
return;
@@ -614,8 +616,12 @@ public class WorldGuardEntityListener extends AbstractListener {
event.setCancelled(true);
return;
}
if (ent instanceof Creeper creeper && creeper.getTarget() instanceof Player player) {
Cause.trackParentCause(creeper, player);
}
} else if (event.getEntityType() == EntityType.TNT
|| event.getEntityType() == EntityType.TNT_MINECART) {
|| event.getEntityType() == EntityType.TNT_MINECART
|| event.getEntityType() == EntityType.SULFUR_CUBE) {
if (wcfg.blockTNTExplosions) {
event.setCancelled(true);
return;
@@ -655,8 +661,11 @@ public class WorldGuardEntityListener extends AbstractListener {
return;
}
EntityType entityType = event.getEntityType();
handleCreatureSpawn(event, event.getLocation(), event.getEntityType(), event.getSpawnReason(), cfg, wcfg);
}
private static void handleCreatureSpawn(Cancellable event, Location location, EntityType entityType, SpawnReason spawnReason,
ConfigurationManager cfg, WorldConfiguration wcfg) {
com.sk89q.worldedit.world.entity.EntityType weEntityType = BukkitAdapter.adapt(entityType);
if (weEntityType != null && wcfg.blockCreatureSpawn.contains(weEntityType)) {
@@ -664,11 +673,9 @@ public class WorldGuardEntityListener extends AbstractListener {
return;
}
Location eventLoc = event.getLocation();
if (wcfg.useRegions && cfg.useRegionsCreatureSpawnEvent) {
ApplicableRegionSet set =
WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(eventLoc));
WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(location));
if (!set.testState(null, Flags.MOB_SPAWNING)) {
event.setCancelled(true);
@@ -683,8 +690,8 @@ public class WorldGuardEntityListener extends AbstractListener {
}
if (wcfg.blockGroundSlimes && entityType == EntityType.SLIME
&& eventLoc.getY() >= 60
&& event.getSpawnReason() == SpawnReason.NATURAL) {
&& location.getY() >= 60
&& spawnReason == SpawnReason.NATURAL) {
event.setCancelled(true);
return;
}
@@ -905,6 +912,53 @@ public class WorldGuardEntityListener extends AbstractListener {
handlePigZap(event.getEntity(), event);
}
}
/**
* Applies the natural spawn checks from {@link WorldGuardEntityListener#onCreatureSpawn} before the
* server constructs the entity. The CreatureSpawnEvent checks fire at the very
* end of the spawn pipeline, after the position was picked, the placement
* checks ran and the mob was constructed and finalized, so a region that denies
* mob spawning pays for a mob to be built and thrown away on every attempt.
* Cancelling here skips the placement checks, the construction and
* finalizeSpawn for a spawn that was going to be refused anyway.
*
* Cancelling does not end the chunk's remaining attempts. Only
* setShouldAbortSpawn(true) makes the spawner return early; a plain cancel
* falls through to the next candidate position exactly like a failed
* placement check.
*
* Note that Paper fires this event for every candidate position, before the
* light, block and collision checks, so the region query below runs
* considerably more often than the CreatureSpawnEvent one did. On Paper with
* per-player-mob-spawns enabled, which is the default, every cancelled pre
* spawn is also charged to a per player mob backoff counter that is added to
* the mob cap of every player within tick view distance and bleeds off one per
* spawn cycle, so a large denied region can suppress spawning in neighbouring
* chunks that allow mobs.
*
* Only NATURAL spawns are handled here; every other spawn reason keeps going
* through the CreatureSpawnEvent checks unchanged.
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPreCreatureSpawn(PreCreatureSpawnEvent event) {
if (event.getReason() != SpawnReason.NATURAL) {
return;
}
ConfigurationManager cfg = getConfig();
if (!cfg.useRegionsPreCreatureSpawnEvent) {
return;
}
if (cfg.activityHaltToggle) {
event.setCancelled(true);
return;
}
Location spawnLoc = event.getSpawnLocation();
handleCreatureSpawn(event, spawnLoc, event.getType(), event.getReason(), cfg, getWorldConfig(spawnLoc.getWorld()));
}
}
private static class SpigotListener implements Listener {
@@ -82,7 +82,7 @@ public class WorldGuardWeatherListener extends AbstractListener {
if (targetId == Material.AIR) {
targetId = target.getRelative(BlockFace.DOWN).getType();
}
if (wcfg.disallowedLightningBlocks.contains(BukkitAdapter.asBlockType(targetId).getId())) {
if (wcfg.disallowedLightningBlocks.contains(BukkitAdapter.asBlockType(targetId).id())) {
event.setCancelled(true);
}
}
@@ -27,7 +27,7 @@ import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
public class EventDebounce<K> {
@@ -36,7 +36,7 @@ public class EventDebounce<K> {
public EventDebounce(int debounceTime) {
cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(debounceTime, TimeUnit.MILLISECONDS)
.expireAfterWrite(Duration.ofMillis(debounceTime))
.concurrencyLevel(2)
.build(new CacheLoader<K, Entry>() {
@Override
@@ -27,7 +27,7 @@ import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
public class AbstractEventDebounce<K> {
@@ -36,7 +36,7 @@ public class AbstractEventDebounce<K> {
AbstractEventDebounce(int debounceTime) {
cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(debounceTime, TimeUnit.MILLISECONDS)
.expireAfterWrite(Duration.ofMillis(debounceTime))
.concurrencyLevel(2)
.build(new CacheLoader<K, Entry>() {
@Override
@@ -43,8 +43,8 @@ import java.util.function.Consumer;
public class BukkitSessionManager extends AbstractSessionManager implements Runnable, Listener {
/**
* Re-initialize handlers and clear "last position," "last state," etc.
* information for all players.
* Restore handler-managed state, re-initialize handlers, and clear
* "last position," "last state," etc. information for all players.
*/
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -72,9 +72,14 @@ public class BukkitSessionManager extends AbstractSessionManager implements Runn
@EventHandler
public void onPlayerProcess(ProcessPlayerEvent event) {
// Pre-load a session
// Create a new session or re-initialize a cached one.
LocalPlayer player = WorldGuardPlugin.inst().wrapPlayer(event.getPlayer());
get(player).initialize(player);
Session session = getIfPresent(player);
if (session == null) {
get(player); // this also initializes the new session
} else {
session.resetState(player); // old sessions need explicit re-initialization
}
}
@Override
@@ -43,6 +43,7 @@ import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.SpectralArrow;
import org.bukkit.entity.Steerable;
import org.bukkit.entity.SulfurCube;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Vehicle;
@@ -78,7 +79,9 @@ public final class Entities {
* @return true if TNT based
*/
public static boolean isTNTBased(Entity entity) {
return entity instanceof TNTPrimed || entity instanceof ExplosiveMinecart;
return entity instanceof TNTPrimed
|| entity instanceof ExplosiveMinecart
|| entity instanceof SulfurCube sulfurCube && sulfurCube.canExplode();
}
/**
@@ -265,6 +268,7 @@ public final class Entities {
case Wither wither -> Flags.WITHER_DAMAGE;
case Creeper creeper -> Flags.CREEPER_EXPLOSION;
case TNTPrimed tnt -> Flags.TNT;
case SulfurCube sulfurCube -> Flags.TNT;
case ExplosiveMinecart minecart -> Flags.TNT;
case EnderDragon dragon -> Flags.ENDERDRAGON_BLOCK_DAMAGE;
case null, default -> Flags.OTHER_EXPLOSION;
@@ -80,15 +80,15 @@ public final class Materials {
ENTITY_ITEMS.put(EntityType.EGG, Material.EGG);
ENTITY_ITEMS.put(EntityType.ARMOR_STAND, Material.ARMOR_STAND);
ENTITY_ITEMS.put(EntityType.END_CRYSTAL, Material.END_CRYSTAL);
ENTITY_ITEMS.put(EntityType.CUSHION, Material.WHITE_CUSHION);
for (String wood : new String[]{"OAK", "SPRUCE", "BIRCH", "JUNGLE", "ACACIA", "DARK_OAK", "MANGROVE", "CHERRY", "PALE_OAK"}) {
String regular = wood + "_BOAT";
String chest = wood + "_CHEST_BOAT";
ENTITY_ITEMS.put(EntityType.valueOf(regular), Material.getMaterial(regular));
ENTITY_ITEMS.put(EntityType.valueOf(chest), Material.getMaterial(chest));
}
ENTITY_ITEMS.put(EntityType.BAMBOO_RAFT, Material.BAMBOO_RAFT);
ENTITY_ITEMS.put(EntityType.BAMBOO_CHEST_RAFT, Material.BAMBOO_CHEST_RAFT);
// All boat items should have an entity equivalent, so we can use the tag to map them out.
Tag.ITEMS_BOATS.getValues().forEach(mat -> {
EntityType entityEquivalent = Registry.ENTITY_TYPE.get(mat.getKey());
if (entityEquivalent != null) {
ENTITY_ITEMS.put(entityEquivalent, mat);
}
});
// preset some tags to a default value, override some of them:
putMaterialTag(Tag.DOORS, MODIFIED_ON_RIGHT);
@@ -489,6 +489,7 @@ public final class Materials {
MATERIAL_FLAGS.put(Material.SUGAR, 0);
MATERIAL_FLAGS.put(Material.COOKIE, 0);
MATERIAL_FLAGS.put(Material.MAP, 0);
putMaterialTag(Tag.ITEMS_CLONABLE_MAPS, 0);
MATERIAL_FLAGS.put(Material.SHEARS, 0);
MATERIAL_FLAGS.put(Material.MELON_SLICE, 0);
MATERIAL_FLAGS.put(Material.PUMPKIN_SEEDS, 0);
@@ -602,6 +603,7 @@ public final class Materials {
MATERIAL_FLAGS.put(Material.TRIDENT, 0);
MATERIAL_FLAGS.put(Material.TROPICAL_FISH, 0);
MATERIAL_FLAGS.put(Material.TROPICAL_FISH_BUCKET, 0);
MATERIAL_FLAGS.put(Material.SULFUR_CUBE_BUCKET, 0);
MATERIAL_FLAGS.put(Material.CREEPER_BANNER_PATTERN, 0);
MATERIAL_FLAGS.put(Material.FLOWER_BANNER_PATTERN, 0);
@@ -747,6 +749,7 @@ public final class Materials {
MATERIAL_FLAGS.put(Material.MOSS_BLOCK, 0);
MATERIAL_FLAGS.put(Material.HANGING_ROOTS, 0);
MATERIAL_FLAGS.put(Material.POINTED_DRIPSTONE, 0);
MATERIAL_FLAGS.put(Material.DRIPSTONE_BLOCK, 0);
// 1.19
MATERIAL_FLAGS.put(Material.MUD, 0);
@@ -948,6 +951,12 @@ public final class Materials {
MATERIAL_FLAGS.put(Material.CHISELED_SULFUR, 0);
putMaterialTag(Tag.SPELEOTHEMS, 0);
// 26.3
MATERIAL_FLAGS.put(Material.RED_SHRUB, 0);
MATERIAL_FLAGS.put(Material.STRAW_BED, MODIFIED_ON_RIGHT);
MATERIAL_FLAGS.put(Material.SHELF_MUSHROOM, 0);
putMaterialTag(Tag.ITEMS_CUSHIONS, 0);
Stream.concat(Stream.concat(
Tag.CORAL_BLOCKS.getValues().stream(),
Tag.CORALS.getValues().stream()),
@@ -1058,6 +1067,7 @@ public final class Materials {
return switch (type) {
case LAVA_BUCKET -> Material.LAVA;
case WATER_BUCKET -> Material.WATER;
case POWDER_SNOW_BUCKET -> Material.POWDER_SNOW;
default -> Material.WATER;
};
}
@@ -1171,6 +1181,16 @@ public final class Materials {
return Tag.ITEMS_BOATS.isTagged(material);
}
/**
* Test whether the given material is a Cushion.
*
* @param material the material
* @return true if a Cushion item
*/
public static boolean isCushion(Material material) {
return Tag.ITEMS_CUSHIONS.isTagged(material);
}
/**
* Test whether the given material is a Shulker Box.
*
@@ -1307,7 +1327,7 @@ public final class Materials {
}
public static boolean isBed(Material material) {
return Tag.BEDS.isTagged(material);
return Tag.BEDS.isTagged(material) || material == Material.STRAW_BED;
}
public static boolean isAnvil(Material material) {
@@ -1584,4 +1604,19 @@ public final class Materials {
public static boolean isSculkGrowth(Material mat) {
return mat == Material.SCULK || mat == Material.SCULK_VEIN;
}
public static boolean isCauldron(Material mat) {
return switch (mat) {
case CAULDRON, LAVA_CAULDRON, POWDER_SNOW_CAULDRON, WATER_CAULDRON -> true;
default -> false;
};
}
// Other buckets containing mobs cannot be placed inside cauldrons
public static boolean isCauldronBucketType(Material mat) {
return switch (mat) {
case WATER_BUCKET, LAVA_BUCKET, POWDER_SNOW_BUCKET -> true;
default -> false;
};
}
}
@@ -2,5 +2,5 @@ name: WorldGuard
main: com.sk89q.worldguard.bukkit.WorldGuardPlugin
version: "${internalVersion}"
depend: [WorldEdit]
api-version: "26.2"
api-version: "26.3"
folia-supported: true
@@ -37,9 +37,9 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -53,7 +53,7 @@ public class Blacklist {
private boolean useAsWhitelist;
private LoadingCache<String, TrackedEvent> repeatingEventCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(30, TimeUnit.SECONDS)
.expireAfterAccess(Duration.ofSeconds(30))
.build(CacheLoader.from(TrackedEvent::new));
public Blacklist(boolean useAsWhitelist) {
@@ -31,7 +31,7 @@ public class BlockMatcher implements TargetMatcher {
@Override
public String getMatchedTypeId() {
return this.type.getId();
return this.type.id();
}
@Override
@@ -31,7 +31,7 @@ public class ItemBlockMatcher implements TargetMatcher {
@Override
public String getMatchedTypeId() {
return this.type.getId();
return this.type.id();
}
@Override
@@ -31,7 +31,7 @@ public class ItemMatcher implements TargetMatcher {
@Override
public String getMatchedTypeId() {
return this.type.getId();
return this.type.id();
}
@Override
@@ -597,11 +597,14 @@ public final class RegionCommands extends RegionCommandsBase {
if (value != null) {
// Set the flag if [value] was given even if [-g group] was given as well
try {
value = setFlag(existing, foundFlag, sender, value).toString();
Object parsedValue = setFlag(existing, foundFlag, sender, value);
value = parsedValue == null ? null : parsedValue.toString();
} catch (InvalidFlagFormat e) {
throw new CommandException(e.getMessage());
}
}
if (value != null) {
if (!args.hasFlag('h')) {
sender.print("Region flag " + foundFlag.getName() + " set on '" + regionId + "' to '" + value + "'.");
}
@@ -65,6 +65,7 @@ public abstract class ConfigurationManager {
"#\r\n";
public boolean useRegionsCreatureSpawnEvent;
public boolean useRegionsPreCreatureSpawnEvent;
public boolean activityHaltToggle = false;
public boolean useGodPermission;
public boolean useGodGroup;
@@ -212,13 +212,13 @@ public abstract class WorldConfiguration {
}
ItemType legacyItem = LegacyMapper.getInstance().getItemFromLegacy(id, data);
if (legacyItem != null) {
return legacyItem.getId();
return legacyItem.id();
}
} catch (NumberFormatException ignored) {
}
final ItemType itemType = ItemTypes.get(legacy);
if (itemType != null) {
return itemType.getId();
return itemType.id();
}
return null;
@@ -242,13 +242,13 @@ public abstract class WorldConfiguration {
}
BlockState legacyBlock = LegacyMapper.getInstance().getBlockFromLegacy(id, data);
if (legacyBlock != null) {
return legacyBlock.getBlockType().getId();
return legacyBlock.getBlockType().id();
}
} catch (NumberFormatException ignored) {
}
final BlockType blockType = BlockTypes.get(legacy);
if (blockType != null) {
return blockType.getId();
return blockType.id();
}
return null;
@@ -44,7 +44,7 @@ public abstract class YamlConfigurationManager extends ConfigurationManager {
public void load() {
copyDefaults();
config = new YAMLProcessor(new File(getDataFolder(), "config.yml"), true, YAMLFormat.EXTENDED);
config = new YAMLProcessor(new File(getDataFolder(), "config.yml").toPath(), true, YAMLFormat.EXTENDED);
try {
config.load();
} catch (IOException e) {
@@ -56,6 +56,7 @@ public abstract class YamlConfigurationManager extends ConfigurationManager {
migrateRegionsToUuid = config.getBoolean("regions.uuid-migration.perform-on-next-start", true);
keepUnresolvedNames = config.getBoolean("regions.uuid-migration.keep-names-that-lack-uuids", true);
useRegionsCreatureSpawnEvent = config.getBoolean("regions.use-creature-spawn-event", true);
useRegionsPreCreatureSpawnEvent = config.getBoolean("regions.use-pre-creature-spawn-event", false);
disableDefaultBypass = config.getBoolean("regions.disable-bypass-by-default", false);
announceBypassStatus = config.getBoolean("regions.announce-bypass-status", false);
@@ -333,7 +333,7 @@ public class YamlRegionFile implements RegionDatabase {
*/
private YAMLProcessor createYamlProcessor(File file) {
checkNotNull(file);
return new YAMLProcessor(file, false, YAMLFormat.COMPACT);
return new YAMLProcessor(file.toPath(), false, YAMLFormat.COMPACT);
}
/**
@@ -21,8 +21,6 @@ package com.sk89q.worldguard.session;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.WorldGuard;
@@ -43,12 +41,11 @@ import com.sk89q.worldguard.session.handler.WaterBreathing;
import com.sk89q.worldguard.session.handler.WeatherLockFlag;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.time.Duration;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.logging.Level;
@@ -59,21 +56,19 @@ public abstract class AbstractSessionManager implements SessionManager {
public static final int RUN_DELAY = 20;
public static final long SESSION_LIFETIME = 10;
private static final BiPredicate<World, LocalPlayer> BYPASS_PERMISSION_TEST = (world, player) -> {
return player.hasPermission("worldguard.region.bypass." + world.getName());
};
private static final BiPredicate<World, LocalPlayer> BYPASS_PERMISSION_TEST = (world, player) -> player.hasPermission("worldguard.region.bypass." + world.getName());
private final LoadingCache<WorldPlayerTuple, Boolean> bypassCache = CacheBuilder.newBuilder()
private final Cache<BypassCacheKey, Boolean> bypassCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(CacheLoader.from(tuple -> BYPASS_PERMISSION_TEST.test(tuple.getWorld(), tuple.getPlayer())));
.expireAfterWrite(Duration.ofSeconds(2))
.build();
private final Cache<CacheKey, Session> sessions = CacheBuilder.newBuilder()
.expireAfterAccess(SESSION_LIFETIME, TimeUnit.MINUTES)
private final Cache<UUID, Session> sessions = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(SESSION_LIFETIME))
.build();
private boolean hasCustom = false;
private List<Handler.Factory<? extends Handler>> handlers = new LinkedList<>();
private final List<Handler.Factory<? extends Handler>> handlers = new LinkedList<>();
private static final List<Handler.Factory<? extends Handler>> defaultHandlers = new LinkedList<>();
@@ -146,13 +141,14 @@ public abstract class AbstractSessionManager implements SessionManager {
return BYPASS_PERMISSION_TEST.test(world, player);
}
return bypassCache.getUnchecked(new WorldPlayerTuple(world, player));
var bypassCacheKey = new BypassCacheKey(world.getName(), player.getUniqueId());
return bypassCache.asMap().computeIfAbsent(bypassCacheKey, (_) -> BYPASS_PERMISSION_TEST.test(world, player));
}
@Override
public void resetState(LocalPlayer player) {
checkNotNull(player, "player");
@Nullable Session session = getIfPresentInternal(new CacheKey(player));
@Nullable Session session = getIfPresentInternal(player);
if (session != null) {
session.resetState(player);
}
@@ -161,13 +157,13 @@ public abstract class AbstractSessionManager implements SessionManager {
@Override
@Nullable
public Session getIfPresent(LocalPlayer player) {
return getIfPresentInternal(new CacheKey(player));
return getIfPresentInternal(player);
}
private Session getIfPresentInternal(CacheKey cacheKey) {
@Nullable Session session = sessions.getIfPresent(cacheKey);
private Session getIfPresentInternal(LocalPlayer player) {
@Nullable Session session = sessions.getIfPresent(player.getUniqueId());
if (session != null) {
session.ensureInitialized(cacheKey.playerRef.get(), this::initializeSession);
session.ensureInitialized(player, this::initializeSession);
return session;
}
return null;
@@ -175,7 +171,7 @@ public abstract class AbstractSessionManager implements SessionManager {
@Override
public Session get(LocalPlayer player) {
Session session = sessions.asMap().computeIfAbsent(new CacheKey(player), (k) -> new Session(this));
Session session = sessions.asMap().computeIfAbsent(player.getUniqueId(), (_) -> new Session(this));
session.ensureInitialized(player, this::initializeSession);
return session;
}
@@ -192,27 +188,6 @@ public abstract class AbstractSessionManager implements SessionManager {
session.initialize(player);
}
protected static final class CacheKey {
final WeakReference<LocalPlayer> playerRef;
final UUID uuid;
CacheKey(LocalPlayer player) {
playerRef = new WeakReference<>(player);
uuid = player.getUniqueId();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
return uuid.equals(cacheKey.uuid);
}
@Override
public int hashCode() {
return uuid.hashCode();
}
record BypassCacheKey(String worldName, UUID playerUUID) {
}
}
@@ -143,6 +143,9 @@ public class Session {
for (Handler handler : handlers.values()) {
handler.uninitialize(player, location, set);
}
lastValid = null;
lastRegionSet = null;
}
/**
@@ -161,11 +164,12 @@ public class Session {
}
/**
* Re-initialize the session.
* Restore handler-managed state and then re-initialize the session.
*
* @param player The player
*/
public void resetState(LocalPlayer player) {
uninitialize(player);
initialize(player);
needRefresh.set(true);
}
@@ -223,6 +227,12 @@ public class Session {
*/
@Nullable
public Location testMoveTo(LocalPlayer player, Location to, MoveType moveType, boolean forced) {
if (lastValid == null) {
// If lastValid is null, the session has not yet been initialized. This shouldn't happen, but theoretically
// can due to Bukkit event orderings.
return null;
}
RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery();
if (!forced && needRefresh.getAndSet(false)) {
@@ -41,14 +41,14 @@ public interface SessionManager {
boolean hasBypass(LocalPlayer player, World world);
/**
* Re-initialize handlers and clear "last position," "last state," etc.
* information for all players.
* Restore handler-managed state, re-initialize handlers, and clear
* "last position," "last state," etc. information for all players.
*/
void resetAllStates();
/**
* Re-initialize handlers and clear "last position," "last state," etc.
* information.
* Restore handler-managed state, re-initialize handlers, and clear
* "last position," "last state," etc. information.
*
* @param player The player
*/
@@ -22,6 +22,7 @@ package com.sk89q.worldguard.session;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldguard.LocalPlayer;
@Deprecated(forRemoval = true)
public class WorldPlayerTuple {
private final World world;
@@ -31,7 +31,7 @@ public class ApplicableRegionsReport extends DataReport {
public ApplicableRegionsReport(LocalPlayer player) {
super("Applicable regions");
BlockVector3 position = player.getBlockIn().toVector().toBlockPoint();
BlockVector3 position = player.getBlockLocation().toVector().toBlockPoint();
append("Location", player.getWorld().getName() + " @ " + position);
RegionManager mgr = WorldGuard.getInstance().getPlatform().getRegionContainer().get(player.getWorld());
if (mgr == null) {