Added more

This commit is contained in:
Logan Saso
2025-10-04 21:53:50 -07:00
parent ffb44ca4bd
commit f2516906d1
6 changed files with 220 additions and 46 deletions

View File

@@ -5,10 +5,14 @@ A Minecraft plugin that renders visible particle forcefields around WorldGuard r
## Features
- Automatically detects WorldGuard regions with `entry deny` flag
- Renders particle forcefields only visible to players who cannot enter
- Renders **visible glass pane barriers** and particle effects for blocked regions
- Only shows forcefields to players who **actually cannot enter** (respects bypass permissions and ops)
- Glass panes only placed where there's currently air (doesn't cover existing blocks)
- Configurable particle color, size, spacing, and render distance
- Configurable block material (glass panes, barriers, etc.)
- Supports cuboid and polygonal region types
- Performance-optimized with distance-based rendering
- Automatic cleanup when players move away or disconnect
- Clean, readable, and well-documented code
## Requirements
@@ -85,14 +89,27 @@ particle-color:
# Particle size (0.5-2.0 recommended)
particle-size: 1.0
# Block rendering settings
# Whether to render actual blocks (glass panes) in addition to particles
render-blocks: true
# Distance between blocks (in blocks)
block-spacing: 1.0
# Block material to use (e.g., PURPLE_STAINED_GLASS_PANE, BARRIER, GLASS)
block-material: PURPLE_STAINED_GLASS_PANE
```
## Performance Tips
- Reduce `max-render-distance` for servers with many regions
- Increase `particle-spacing` to reduce particle count
- Increase `block-spacing` to reduce block count
- Set `render-blocks: false` to disable glass panes and only use particles
- Set `render-walls: false` to only show edges
- Increase `update-interval-ticks` if you don't need real-time updates
- Use `BARRIER` blocks instead of glass panes (less visible but lighter)
## Troubleshooting
@@ -102,8 +119,10 @@ particle-size: 1.0
2. Check the server console for debug messages
3. Use `/forcefield info` to see if regions are being detected
4. Verify the region has `entry deny` set: `/rg info <region>`
5. Check you're not a member/owner of the region
5. **Check you can't actually enter** - ops and members won't see forcefields
6. Ensure you're within render distance of the region (default: 100 blocks)
7. Try setting `render-blocks: true` if you only see particles
8. Check that locations aren't already occupied by blocks
### Plugin won't load

View File

@@ -2,6 +2,7 @@ package loganintech.regionforcefield;
import loganintech.regionforcefield.command.ForcefieldCommand;
import loganintech.regionforcefield.forcefield.ForcefieldRenderer;
import loganintech.regionforcefield.listener.PlayerListener;
import loganintech.regionforcefield.region.RegionPermissionChecker;
import loganintech.regionforcefield.task.ForcefieldUpdateTask;
import org.bukkit.command.PluginCommand;
@@ -35,6 +36,9 @@ public final class RegionForcefieldPlugin extends JavaPlugin {
this.permissionChecker = new RegionPermissionChecker(this);
this.forcefieldRenderer = new ForcefieldRenderer(this);
// Register listeners
getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
// Register commands
ForcefieldCommand commandExecutor = new ForcefieldCommand(this);
PluginCommand command = getCommand("forcefield");

View File

@@ -5,15 +5,19 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import loganintech.regionforcefield.RegionForcefieldPlugin;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import loganintech.regionforcefield.RegionForcefieldPlugin;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Renders particle forcefields around protected regions.
@@ -23,6 +27,8 @@ public class ForcefieldRenderer {
private final RegionForcefieldPlugin plugin;
private final double particleSpacing;
private final Particle.DustOptions dustOptions;
private final PlayerBlockTracker blockTracker;
private final BlockData glassBlockData;
/**
* Creates a new forcefield renderer.
@@ -32,6 +38,7 @@ public class ForcefieldRenderer {
public ForcefieldRenderer(@NotNull RegionForcefieldPlugin plugin) {
this.plugin = plugin;
this.particleSpacing = plugin.getConfig().getDouble("particle-spacing", 0.5);
this.blockTracker = new PlayerBlockTracker();
// Get color from config or use default (purple)
int red = plugin.getConfig().getInt("particle-color.red", 147);
@@ -40,6 +47,27 @@ public class ForcefieldRenderer {
float size = (float) plugin.getConfig().getDouble("particle-size", 1.0);
this.dustOptions = new Particle.DustOptions(Color.fromRGB(red, green, blue), size);
// Get block material from config or use purple stained glass pane
String materialName = plugin.getConfig().getString("block-material", "PURPLE_STAINED_GLASS_PANE");
Material material;
try {
material = Material.valueOf(materialName.toUpperCase());
} catch (IllegalArgumentException e) {
plugin.getLogger().warning("Invalid block material '" + materialName + "', using PURPLE_STAINED_GLASS_PANE");
material = Material.PURPLE_STAINED_GLASS_PANE;
}
this.glassBlockData = material.createBlockData();
}
/**
* Gets the block tracker for managing fake blocks.
*
* @return the block tracker
*/
@NotNull
public PlayerBlockTracker getBlockTracker() {
return blockTracker;
}
/**
@@ -48,49 +76,59 @@ public class ForcefieldRenderer {
* @param player the player to show the forcefield to
* @param region the region to render
* @param world the world the region is in
* @return set of block locations that were rendered
*/
public void renderForcefield(@NotNull Player player, @NotNull ProtectedRegion region, @NotNull World world) {
@NotNull
public Set<Location> renderForcefield(@NotNull Player player, @NotNull ProtectedRegion region, @NotNull World world) {
Set<Location> newBlocks = new HashSet<>();
try {
plugin.debug("Rendering forcefield for region " + region.getId() + " to player " + player.getName());
if (region instanceof ProtectedCuboidRegion) {
renderCuboidForcefield(player, (ProtectedCuboidRegion) region, world);
renderCuboidForcefield(player, (ProtectedCuboidRegion) region, world, newBlocks);
} else if (region instanceof ProtectedPolygonalRegion) {
renderPolygonalForcefield(player, (ProtectedPolygonalRegion) region, world);
renderPolygonalForcefield(player, (ProtectedPolygonalRegion) region, world, newBlocks);
} else {
// For other region types, fall back to rendering a bounding box
plugin.debug("Using bounding box for region type: " + region.getClass().getSimpleName());
renderBoundingBoxForcefield(player, region, world);
renderBoundingBoxForcefield(player, region, world, newBlocks);
}
plugin.debug("Rendered " + newBlocks.size() + " blocks for region " + region.getId());
} catch (Exception e) {
plugin.getLogger().warning("Error rendering forcefield for region " + region.getId() + ": " + e.getMessage());
e.printStackTrace();
}
return newBlocks;
}
/**
* Renders a forcefield for a cuboid region.
*/
private void renderCuboidForcefield(@NotNull Player player, @NotNull ProtectedCuboidRegion region, @NotNull World world) {
private void renderCuboidForcefield(@NotNull Player player, @NotNull ProtectedCuboidRegion region,
@NotNull World world, @NotNull Set<Location> blocks) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
// Render vertical edges
renderVerticalEdges(player, world, min, max);
renderVerticalEdges(player, world, min, max, blocks);
// Render horizontal edges at top and bottom
renderHorizontalEdges(player, world, min, max);
renderHorizontalEdges(player, world, min, max, blocks);
// Optionally render faces (walls)
if (plugin.getConfig().getBoolean("render-walls", true)) {
renderWalls(player, world, min, max);
renderWalls(player, world, min, max, blocks);
}
}
/**
* Renders a forcefield for a polygonal region.
*/
private void renderPolygonalForcefield(@NotNull Player player, @NotNull ProtectedPolygonalRegion region, @NotNull World world) {
private void renderPolygonalForcefield(@NotNull Player player, @NotNull ProtectedPolygonalRegion region,
@NotNull World world, @NotNull Set<Location> blocks) {
List<BlockVector2> points = region.getPoints();
int minY = region.getMinimumPoint().y();
int maxY = region.getMaximumPoint().y();
@@ -100,64 +138,71 @@ public class ForcefieldRenderer {
BlockVector2 point1 = points.get(i);
BlockVector2 point2 = points.get((i + 1) % points.size());
renderVerticalWall(player, world, point1.x(), point1.z(), point2.x(), point2.z(), minY, maxY);
renderVerticalWall(player, world, point1.x(), point1.z(), point2.x(), point2.z(), minY, maxY, blocks);
}
}
/**
* Renders a bounding box forcefield for unsupported region types.
*/
private void renderBoundingBoxForcefield(@NotNull Player player, @NotNull ProtectedRegion region, @NotNull World world) {
private void renderBoundingBoxForcefield(@NotNull Player player, @NotNull ProtectedRegion region,
@NotNull World world, @NotNull Set<Location> blocks) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
renderVerticalEdges(player, world, min, max);
renderHorizontalEdges(player, world, min, max);
renderVerticalEdges(player, world, min, max, blocks);
renderHorizontalEdges(player, world, min, max, blocks);
}
/**
* Renders the vertical edges of a cuboid.
*/
private void renderVerticalEdges(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
private void renderVerticalEdges(@NotNull Player player, @NotNull World world,
@NotNull BlockVector3 min, @NotNull BlockVector3 max,
@NotNull Set<Location> blocks) {
// Four vertical edges
renderLine(player, world, min.x(), min.y(), min.z(), min.x(), max.y(), min.z());
renderLine(player, world, max.x(), min.y(), min.z(), max.x(), max.y(), min.z());
renderLine(player, world, min.x(), min.y(), max.z(), min.x(), max.y(), max.z());
renderLine(player, world, max.x(), min.y(), max.z(), max.x(), max.y(), max.z());
renderLine(player, world, min.x(), min.y(), min.z(), min.x(), max.y(), min.z(), blocks);
renderLine(player, world, max.x(), min.y(), min.z(), max.x(), max.y(), min.z(), blocks);
renderLine(player, world, min.x(), min.y(), max.z(), min.x(), max.y(), max.z(), blocks);
renderLine(player, world, max.x(), min.y(), max.z(), max.x(), max.y(), max.z(), blocks);
}
/**
* Renders the horizontal edges of a cuboid.
*/
private void renderHorizontalEdges(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
private void renderHorizontalEdges(@NotNull Player player, @NotNull World world,
@NotNull BlockVector3 min, @NotNull BlockVector3 max,
@NotNull Set<Location> blocks) {
// Bottom edges
renderLine(player, world, min.x(), min.y(), min.z(), max.x(), min.y(), min.z());
renderLine(player, world, min.x(), min.y(), max.z(), max.x(), min.y(), max.z());
renderLine(player, world, min.x(), min.y(), min.z(), min.x(), min.y(), max.z());
renderLine(player, world, max.x(), min.y(), min.z(), max.x(), min.y(), max.z());
renderLine(player, world, min.x(), min.y(), min.z(), max.x(), min.y(), min.z(), blocks);
renderLine(player, world, min.x(), min.y(), max.z(), max.x(), min.y(), max.z(), blocks);
renderLine(player, world, min.x(), min.y(), min.z(), min.x(), min.y(), max.z(), blocks);
renderLine(player, world, max.x(), min.y(), min.z(), max.x(), min.y(), max.z(), blocks);
// Top edges
renderLine(player, world, min.x(), max.y(), min.z(), max.x(), max.y(), min.z());
renderLine(player, world, min.x(), max.y(), max.z(), max.x(), max.y(), max.z());
renderLine(player, world, min.x(), max.y(), min.z(), min.x(), max.y(), max.z());
renderLine(player, world, max.x(), max.y(), min.z(), max.x(), max.y(), max.z());
renderLine(player, world, min.x(), max.y(), min.z(), max.x(), max.y(), min.z(), blocks);
renderLine(player, world, min.x(), max.y(), max.z(), max.x(), max.y(), max.z(), blocks);
renderLine(player, world, min.x(), max.y(), min.z(), min.x(), max.y(), max.z(), blocks);
renderLine(player, world, max.x(), max.y(), min.z(), max.x(), max.y(), max.z(), blocks);
}
/**
* Renders the walls (faces) of a cuboid.
*/
private void renderWalls(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
private void renderWalls(@NotNull Player player, @NotNull World world,
@NotNull BlockVector3 min, @NotNull BlockVector3 max,
@NotNull Set<Location> blocks) {
// North wall (min Z)
renderVerticalWall(player, world, min.x(), min.z(), max.x(), min.z(), min.y(), max.y());
renderVerticalWall(player, world, min.x(), min.z(), max.x(), min.z(), min.y(), max.y(), blocks);
// South wall (max Z)
renderVerticalWall(player, world, min.x(), max.z(), max.x(), max.z(), min.y(), max.y());
renderVerticalWall(player, world, min.x(), max.z(), max.x(), max.z(), min.y(), max.y(), blocks);
// West wall (min X)
renderVerticalWall(player, world, min.x(), min.z(), min.x(), max.z(), min.y(), max.y());
renderVerticalWall(player, world, min.x(), min.z(), min.x(), max.z(), min.y(), max.y(), blocks);
// East wall (max X)
renderVerticalWall(player, world, max.x(), min.z(), max.x(), max.z(), min.y(), max.y());
renderVerticalWall(player, world, max.x(), min.z(), max.x(), max.z(), min.y(), max.y(), blocks);
}
/**
@@ -165,10 +210,11 @@ public class ForcefieldRenderer {
*/
private void renderVerticalWall(@NotNull Player player, @NotNull World world,
double x1, double z1, double x2, double z2,
double minY, double maxY) {
double minY, double maxY, @NotNull Set<Location> blocks) {
double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(z2 - z1, 2));
int horizontalSteps = (int) Math.ceil(distance / particleSpacing);
int verticalSteps = (int) Math.ceil((maxY - minY) / particleSpacing);
double blockSpacing = plugin.getConfig().getDouble("block-spacing", 1.0);
for (int i = 0; i <= horizontalSteps; i++) {
double t = horizontalSteps > 0 ? (double) i / horizontalSteps : 0;
@@ -178,6 +224,12 @@ public class ForcefieldRenderer {
for (int j = 0; j <= verticalSteps; j++) {
double y = minY + (maxY - minY) * ((double) j / verticalSteps);
spawnParticle(player, world, x, y, z);
// Place blocks at intervals
if (i % ((int) Math.max(1, blockSpacing / particleSpacing)) == 0 &&
j % ((int) Math.max(1, blockSpacing / particleSpacing)) == 0) {
placeBlock(player, world, x, y, z, blocks);
}
}
}
}
@@ -187,7 +239,8 @@ public class ForcefieldRenderer {
*/
private void renderLine(@NotNull Player player, @NotNull World world,
double x1, double y1, double z1,
double x2, double y2, double z2) {
double x2, double y2, double z2,
@NotNull Set<Location> blocks) {
double distance = Math.sqrt(
Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2) +
@@ -195,6 +248,7 @@ public class ForcefieldRenderer {
);
int steps = (int) Math.ceil(distance / particleSpacing);
double blockSpacing = plugin.getConfig().getDouble("block-spacing", 1.0);
for (int i = 0; i <= steps; i++) {
double t = steps > 0 ? (double) i / steps : 0;
@@ -203,6 +257,11 @@ public class ForcefieldRenderer {
double z = z1 + (z2 - z1) * t;
spawnParticle(player, world, x, y, z);
// Place blocks at intervals
if (i % ((int) Math.max(1, blockSpacing / particleSpacing)) == 0) {
placeBlock(player, world, x, y, z, blocks);
}
}
}
@@ -213,4 +272,63 @@ public class ForcefieldRenderer {
Location location = new Location(world, x, y, z);
player.spawnParticle(Particle.DUST, location, 1, 0, 0, 0, 0, dustOptions);
}
/**
* Places a fake block at the specified location if it's air.
*
* @param player the player to send the block to
* @param world the world
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param blocks the set to add this block location to
*/
private void placeBlock(@NotNull Player player, @NotNull World world,
double x, double y, double z, @NotNull Set<Location> blocks) {
if (!plugin.getConfig().getBoolean("render-blocks", true)) {
return;
}
Location location = new Location(world, Math.floor(x), Math.floor(y), Math.floor(z));
// Only place blocks where there's currently air
if (location.getBlock().getType() == Material.AIR) {
player.sendBlockChange(location, glassBlockData);
blocks.add(location);
}
}
/**
* Clears all fake blocks for a player by restoring the real blocks.
*
* @param player the player
*/
public void clearBlocks(@NotNull Player player) {
Set<Location> blocks = blockTracker.getBlocks(player);
for (Location location : blocks) {
// Send the real block data back to the player
player.sendBlockChange(location, location.getBlock().getBlockData());
}
blockTracker.clearPlayer(player);
}
/**
* Updates blocks for a player based on new blocks that should be visible.
*
* @param player the player
* @param newBlocks the new set of blocks to show
*/
public void updateBlocks(@NotNull Player player, @NotNull Set<Location> newBlocks) {
Set<Location> oldBlocks = blockTracker.getBlocks(player);
// Remove blocks that are no longer needed
for (Location location : oldBlocks) {
if (!newBlocks.contains(location)) {
player.sendBlockChange(location, location.getBlock().getBlockData());
}
}
// Update the tracker
blockTracker.setBlocks(player, newBlocks);
}
}

View File

@@ -83,21 +83,32 @@ public class RegionPermissionChecker {
}
/**
* Checks if a player can enter a specific region.
* Checks if a player can actually enter a region.
* Takes into account entry deny flag, bypass permissions, and member/owner status.
*
* @param player the player to check
* @param region the region to check
* @return true if the player can enter, false otherwise
* @return true if the player CAN enter (no forcefield), false if blocked (show forcefield)
*/
private boolean canEnterRegion(@NotNull LocalPlayer player, @NotNull ProtectedRegion region) {
// Check the ENTRY flag
// If ENTRY is set to DENY, the player cannot enter unless they have bypass permissions
if (region.getFlag(Flags.ENTRY) == com.sk89q.worldguard.protection.flags.StateFlag.State.DENY) {
// Check if player has bypass permission (includes ops)
if (player.hasPermission("worldguard.region.bypass." + region.getId()) ||
player.hasPermission("worldguard.region.bypass.*")) {
return true; // Can enter (has bypass), no forcefield
}
// Check if the player is a member or owner of the region
return region.isMember(player) || region.isOwner(player);
if (region.isMember(player) || region.isOwner(player)) {
return true; // Can enter (is member/owner), no forcefield
}
// Player cannot enter, show forcefield
return false;
}
// If ENTRY is not explicitly denied, the player can enter
// If ENTRY is not explicitly denied, player can enter
return true;
}
}

View File

@@ -4,10 +4,12 @@ import loganintech.regionforcefield.RegionForcefieldPlugin;
import loganintech.regionforcefield.forcefield.ForcefieldRenderer;
import loganintech.regionforcefield.region.RegionPermissionChecker;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
@@ -41,24 +43,30 @@ public class ForcefieldUpdateTask extends BukkitRunnable {
try {
// Iterate through all online players
for (Player player : plugin.getServer().getOnlinePlayers()) {
// Get all regions the player cannot enter in their current world
// Get all regions the player should see forcefields for
Set<ProtectedRegion> blockedRegions = permissionChecker.getBlockedRegions(player, player.getWorld());
if (!blockedRegions.isEmpty()) {
plugin.debug("Processing " + blockedRegions.size() + " blocked regions for " + player.getName());
}
// Render forcefields for nearby blocked regions
// Collect all blocks that should be rendered for this player
Set<Location> allBlocks = new HashSet<>();
int rendered = 0;
for (ProtectedRegion region : blockedRegions) {
if (isRegionNearPlayer(player, region)) {
forcefieldRenderer.renderForcefield(player, region, player.getWorld());
Set<Location> regionBlocks = forcefieldRenderer.renderForcefield(player, region, player.getWorld());
allBlocks.addAll(regionBlocks);
rendered++;
}
}
// Update the player's blocks (remove old ones, keep new ones)
forcefieldRenderer.updateBlocks(player, allBlocks);
if (rendered > 0) {
plugin.debug("Rendered " + rendered + " forcefields for " + player.getName());
plugin.debug("Rendered " + rendered + " forcefields (" + allBlocks.size() + " blocks) for " + player.getName());
}
}
} catch (Exception e) {

View File

@@ -26,3 +26,17 @@ particle-color:
# Particle size (recommended range: 0.5 to 2.0)
particle-size: 1.0
# Block rendering settings
# Whether to render actual blocks (glass panes) in addition to particles
render-blocks: true
# Distance between blocks (in blocks)
# Higher values = fewer blocks = better performance
# Must be >= particle-spacing
block-spacing: 1.0
# Block material to use for forcefields
# Examples: PURPLE_STAINED_GLASS_PANE, BARRIER, GLASS, LIGHT_BLUE_STAINED_GLASS
# See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html for all options
block-material: PURPLE_STAINED_GLASS_PANE