Vibe code a forcefield renderer

This commit is contained in:
Logan Saso
2025-10-04 21:30:39 -07:00
commit c56e854dff
16 changed files with 977 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
package loganintech.regionforcefield;
import loganintech.regionforcefield.forcefield.ForcefieldRenderer;
import loganintech.regionforcefield.region.RegionPermissionChecker;
import loganintech.regionforcefield.task.ForcefieldUpdateTask;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
/**
* Main plugin class for RegionForcefield.
* Displays particle forcefields around WorldGuard regions that players cannot enter.
*/
public final class RegionForcefieldPlugin extends JavaPlugin {
private RegionPermissionChecker permissionChecker;
private ForcefieldRenderer forcefieldRenderer;
private ForcefieldUpdateTask updateTask;
@Override
public void onEnable() {
// Save default config
saveDefaultConfig();
// Initialize components
this.permissionChecker = new RegionPermissionChecker();
this.forcefieldRenderer = new ForcefieldRenderer(this);
// Start the periodic update task
this.updateTask = new ForcefieldUpdateTask(this, permissionChecker, forcefieldRenderer);
long updateInterval = getConfig().getLong("update-interval-ticks", 20L);
updateTask.runTaskTimer(this, 0L, updateInterval);
getLogger().info("RegionForcefield has been enabled!");
}
@Override
public void onDisable() {
// Cancel the update task
if (updateTask != null) {
updateTask.cancel();
}
getLogger().info("RegionForcefield has been disabled!");
}
/**
* Gets the region permission checker.
*
* @return the permission checker
*/
@NotNull
public RegionPermissionChecker getPermissionChecker() {
return permissionChecker;
}
/**
* Gets the forcefield renderer.
*
* @return the forcefield renderer
*/
@NotNull
public ForcefieldRenderer getForcefieldRenderer() {
return forcefieldRenderer;
}
}

View File

@@ -0,0 +1,208 @@
package loganintech.regionforcefield.forcefield;
import com.sk89q.worldedit.math.BlockVector2;
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 org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Renders particle forcefields around protected regions.
*/
public class ForcefieldRenderer {
private final Plugin plugin;
private final double particleSpacing;
private final Particle.DustOptions dustOptions;
/**
* Creates a new forcefield renderer.
*
* @param plugin the plugin instance
*/
public ForcefieldRenderer(@NotNull Plugin plugin) {
this.plugin = plugin;
this.particleSpacing = plugin.getConfig().getDouble("particle-spacing", 0.5);
// Get color from config or use default (cyan)
int red = plugin.getConfig().getInt("particle-color.red", 0);
int green = plugin.getConfig().getInt("particle-color.green", 255);
int blue = plugin.getConfig().getInt("particle-color.blue", 255);
float size = (float) plugin.getConfig().getDouble("particle-size", 1.0);
this.dustOptions = new Particle.DustOptions(Color.fromRGB(red, green, blue), size);
}
/**
* Renders a forcefield around a region for a specific player.
*
* @param player the player to show the forcefield to
* @param region the region to render
* @param world the world the region is in
*/
public void renderForcefield(@NotNull Player player, @NotNull ProtectedRegion region, @NotNull World world) {
if (region instanceof ProtectedCuboidRegion) {
renderCuboidForcefield(player, (ProtectedCuboidRegion) region, world);
} else if (region instanceof ProtectedPolygonalRegion) {
renderPolygonalForcefield(player, (ProtectedPolygonalRegion) region, world);
} else {
// For other region types, fall back to rendering a bounding box
renderBoundingBoxForcefield(player, region, world);
}
}
/**
* Renders a forcefield for a cuboid region.
*/
private void renderCuboidForcefield(@NotNull Player player, @NotNull ProtectedCuboidRegion region, @NotNull World world) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
// Render vertical edges
renderVerticalEdges(player, world, min, max);
// Render horizontal edges at top and bottom
renderHorizontalEdges(player, world, min, max);
// Optionally render faces (walls)
if (plugin.getConfig().getBoolean("render-walls", true)) {
renderWalls(player, world, min, max);
}
}
/**
* Renders a forcefield for a polygonal region.
*/
private void renderPolygonalForcefield(@NotNull Player player, @NotNull ProtectedPolygonalRegion region, @NotNull World world) {
List<BlockVector2> points = region.getPoints();
int minY = region.getMinimumPoint().y();
int maxY = region.getMaximumPoint().y();
// Render vertical walls between each pair of points
for (int i = 0; i < points.size(); i++) {
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);
}
}
/**
* Renders a bounding box forcefield for unsupported region types.
*/
private void renderBoundingBoxForcefield(@NotNull Player player, @NotNull ProtectedRegion region, @NotNull World world) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
renderVerticalEdges(player, world, min, max);
renderHorizontalEdges(player, world, min, max);
}
/**
* Renders the vertical edges of a cuboid.
*/
private void renderVerticalEdges(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
// 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());
}
/**
* Renders the horizontal edges of a cuboid.
*/
private void renderHorizontalEdges(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
// 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());
// 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());
}
/**
* Renders the walls (faces) of a cuboid.
*/
private void renderWalls(@NotNull Player player, @NotNull World world, @NotNull BlockVector3 min, @NotNull BlockVector3 max) {
// North wall (min Z)
renderVerticalWall(player, world, min.x(), min.z(), max.x(), min.z(), min.y(), max.y());
// South wall (max Z)
renderVerticalWall(player, world, min.x(), max.z(), max.x(), max.z(), min.y(), max.y());
// West wall (min X)
renderVerticalWall(player, world, min.x(), min.z(), min.x(), max.z(), min.y(), max.y());
// East wall (max X)
renderVerticalWall(player, world, max.x(), min.z(), max.x(), max.z(), min.y(), max.y());
}
/**
* Renders a vertical wall between two points.
*/
private void renderVerticalWall(@NotNull Player player, @NotNull World world,
double x1, double z1, double x2, double z2,
double minY, double maxY) {
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);
for (int i = 0; i <= horizontalSteps; i++) {
double t = horizontalSteps > 0 ? (double) i / horizontalSteps : 0;
double x = x1 + (x2 - x1) * t;
double z = z1 + (z2 - z1) * t;
for (int j = 0; j <= verticalSteps; j++) {
double y = minY + (maxY - minY) * ((double) j / verticalSteps);
spawnParticle(player, world, x, y, z);
}
}
}
/**
* Renders a line of particles between two points.
*/
private void renderLine(@NotNull Player player, @NotNull World world,
double x1, double y1, double z1,
double x2, double y2, double z2) {
double distance = Math.sqrt(
Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2) +
Math.pow(z2 - z1, 2)
);
int steps = (int) Math.ceil(distance / particleSpacing);
for (int i = 0; i <= steps; i++) {
double t = steps > 0 ? (double) i / steps : 0;
double x = x1 + (x2 - x1) * t;
double y = y1 + (y2 - y1) * t;
double z = z1 + (z2 - z1) * t;
spawnParticle(player, world, x, y, z);
}
}
/**
* Spawns a single particle at the specified location for a player.
*/
private void spawnParticle(@NotNull Player player, @NotNull World world, double x, double y, double z) {
Location location = new Location(world, x, y, z);
player.spawnParticle(Particle.DUST, location, 1, 0, 0, 0, 0, dustOptions);
}
}

View File

@@ -0,0 +1,74 @@
package loganintech.regionforcefield.region;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.Flags;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* Checks whether players have permission to enter WorldGuard regions.
*/
public class RegionPermissionChecker {
/**
* Gets all regions in a world that the specified player cannot enter.
*
* @param player the player to check
* @param world the world to check regions in
* @return a set of regions the player cannot enter
*/
@NotNull
public Set<ProtectedRegion> getBlockedRegions(@NotNull Player player, @NotNull World world) {
Set<ProtectedRegion> blockedRegions = new HashSet<>();
// Get the region manager for this world
RegionManager regionManager = WorldGuard.getInstance()
.getPlatform()
.getRegionContainer()
.get(BukkitAdapter.adapt(world));
if (regionManager == null) {
return blockedRegions;
}
// Convert Bukkit player to WorldGuard LocalPlayer
LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player);
// Check each region
for (ProtectedRegion region : regionManager.getRegions().values()) {
if (!canEnterRegion(localPlayer, region)) {
blockedRegions.add(region);
}
}
return blockedRegions;
}
/**
* Checks if a player can enter a specific region.
*
* @param player the player to check
* @param region the region to check
* @return true if the player can enter, false otherwise
*/
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 the player is a member or owner of the region
return region.isMember(player) || region.isOwner(player);
}
// If ENTRY is not explicitly denied, the player can enter
return true;
}
}

View File

@@ -0,0 +1,82 @@
package loganintech.regionforcefield.task;
import loganintech.regionforcefield.RegionForcefieldPlugin;
import loganintech.regionforcefield.forcefield.ForcefieldRenderer;
import loganintech.regionforcefield.region.RegionPermissionChecker;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* Periodic task that updates and renders forcefields for all online players.
*/
public class ForcefieldUpdateTask extends BukkitRunnable {
private final RegionForcefieldPlugin plugin;
private final RegionPermissionChecker permissionChecker;
private final ForcefieldRenderer forcefieldRenderer;
private final int maxRenderDistance;
/**
* Creates a new forcefield update task.
*
* @param plugin the plugin instance
* @param permissionChecker the permission checker
* @param forcefieldRenderer the forcefield renderer
*/
public ForcefieldUpdateTask(@NotNull RegionForcefieldPlugin plugin,
@NotNull RegionPermissionChecker permissionChecker,
@NotNull ForcefieldRenderer forcefieldRenderer) {
this.plugin = plugin;
this.permissionChecker = permissionChecker;
this.forcefieldRenderer = forcefieldRenderer;
this.maxRenderDistance = plugin.getConfig().getInt("max-render-distance", 100);
}
@Override
public void run() {
// Iterate through all online players
for (Player player : plugin.getServer().getOnlinePlayers()) {
// Get all regions the player cannot enter in their current world
Set<ProtectedRegion> blockedRegions = permissionChecker.getBlockedRegions(player, player.getWorld());
// Render forcefields for nearby blocked regions
for (ProtectedRegion region : blockedRegions) {
if (isRegionNearPlayer(player, region)) {
forcefieldRenderer.renderForcefield(player, region, player.getWorld());
}
}
}
}
/**
* Checks if a region is near enough to a player to render.
*
* @param player the player
* @param region the region
* @return true if the region should be rendered for this player
*/
private boolean isRegionNearPlayer(@NotNull Player player, @NotNull ProtectedRegion region) {
// Get player's position
double playerX = player.getLocation().getX();
double playerY = player.getLocation().getY();
double playerZ = player.getLocation().getZ();
// Get region's center (approximate)
double regionCenterX = (region.getMinimumPoint().x() + region.getMaximumPoint().x()) / 2.0;
double regionCenterY = (region.getMinimumPoint().y() + region.getMaximumPoint().y()) / 2.0;
double regionCenterZ = (region.getMinimumPoint().z() + region.getMaximumPoint().z()) / 2.0;
// Calculate distance
double distance = Math.sqrt(
Math.pow(playerX - regionCenterX, 2) +
Math.pow(playerY - regionCenterY, 2) +
Math.pow(playerZ - regionCenterZ, 2)
);
return distance <= maxRenderDistance;
}
}

View File

@@ -0,0 +1,25 @@
# RegionForcefield Configuration
# How often to update forcefields (in ticks, 20 ticks = 1 second)
update-interval-ticks: 20
# Maximum distance (in blocks) at which forcefields will be rendered
# Reducing this can improve performance on servers with many regions
max-render-distance: 100
# Distance between particles (in blocks)
# Smaller values = more particles = more detailed forcefields but more performance intensive
particle-spacing: 0.5
# Whether to render the walls (faces) of regions, or just the edges
# Setting to false will only render the outlines/edges
render-walls: true
# Particle color (RGB values from 0-255)
particle-color:
red: 0
green: 255
blue: 255
# Particle size (recommended range: 0.5 to 2.0)
particle-size: 1.0

View File

@@ -0,0 +1,8 @@
name: RegionForcefield
version: ${version}
main: loganintech.regionforcefield.RegionForcefieldPlugin
api-version: '1.21'
depend: [WorldGuard]
author: loganintech
description: Renders visible forcefields around WorldGuard regions that players cannot enter
website: https://github.com/loganintech