Initial commit: SeedProtector plugin for Paper/Folia
This commit is contained in:
commit
2858dc8786
10 changed files with 368 additions and 0 deletions
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
build.log
|
||||||
|
.incremental/
|
||||||
|
.polyglot.build.properties
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Test Server Files
|
||||||
|
foila test/
|
||||||
|
logs/
|
||||||
|
plugins/
|
||||||
|
world/
|
||||||
|
world_nether/
|
||||||
|
world_the_end/
|
||||||
|
usercache.json
|
||||||
|
whitelist.json
|
||||||
|
ops.json
|
||||||
|
banned-ips.json
|
||||||
|
banned-players.json
|
||||||
|
server.properties
|
||||||
|
bukkit.yml
|
||||||
|
spigot.yml
|
||||||
|
paper-global.yml
|
||||||
|
eula.txt
|
||||||
|
help.yml
|
||||||
|
commands.yml
|
||||||
|
permissions.yml
|
||||||
|
version_history.json
|
||||||
|
cache/
|
||||||
|
libraries/
|
||||||
|
versions/
|
||||||
49
pom.xml
Normal file
49
pom.xml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://www.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>seedprotector</groupId>
|
||||||
|
<artifactId>SeedProtector</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
|
||||||
|
<name>Seed Protector</name>
|
||||||
|
<url>https://example.com</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.21-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.basedir}/src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>plugin.yml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
38
src/main/java/seedprotector/SeedProtector.java
Normal file
38
src/main/java/seedprotector/SeedProtector.java
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package seedprotector;
|
||||||
|
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.WorldInitEvent;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import seedprotector.listeners.StructureListener;
|
||||||
|
import seedprotector.managers.PluginManager;
|
||||||
|
import seedprotector.utils.ScramblePopulator;
|
||||||
|
|
||||||
|
public class SeedProtector extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
// Initialize managers
|
||||||
|
PluginManager.getInstance().initialize(this);
|
||||||
|
|
||||||
|
// Register structure listener
|
||||||
|
getServer().getPluginManager().registerEvents(new StructureListener(), this);
|
||||||
|
|
||||||
|
// Register world init listener to inject populator
|
||||||
|
getServer().getPluginManager().registerEvents(this, this);
|
||||||
|
|
||||||
|
getLogger().info("Seed Protector has been enabled!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onWorldInit(WorldInitEvent event) {
|
||||||
|
// Add the scramble populator to every world as it initializes
|
||||||
|
event.getWorld().getPopulators().add(new ScramblePopulator());
|
||||||
|
getLogger().info("Injected ScramblePopulator into world: " + event.getWorld().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
getLogger().info("Seed Protector has been disabled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/java/seedprotector/listeners/PlayerListener.java
Normal file
13
src/main/java/seedprotector/listeners/PlayerListener.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package seedprotector.listeners;
|
||||||
|
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
// Handle player join event
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/main/java/seedprotector/listeners/StructureListener.java
Normal file
49
src/main/java/seedprotector/listeners/StructureListener.java
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package seedprotector.listeners;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.AsyncStructureSpawnEvent;
|
||||||
|
import org.bukkit.generator.structure.Structure;
|
||||||
|
import seedprotector.managers.PluginManager;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class StructureListener implements Listener {
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onStructureSpawn(AsyncStructureSpawnEvent event) {
|
||||||
|
Structure structure = event.getStructure();
|
||||||
|
String structureKey = structure.getKey().toString();
|
||||||
|
PluginManager config = PluginManager.getInstance();
|
||||||
|
|
||||||
|
// 1. Check if structure is exempt
|
||||||
|
if (config.getExemptStructures().contains(structureKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if structure is included (if list is not empty)
|
||||||
|
if (!config.getIncludedStructures().isEmpty() && !config.getIncludedStructures().contains(structureKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Prevent /locate infinite loops by only cancelling during actual generation
|
||||||
|
// If the chunk is already loaded, it's likely a scan/locate command
|
||||||
|
if (event.getWorld().isChunkLoaded(event.getChunkX(), event.getChunkZ())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Deterministic cancellation based on salt and percentage
|
||||||
|
long salt = config.getSecretSalt();
|
||||||
|
int chunkX = event.getChunkX();
|
||||||
|
int chunkZ = event.getChunkZ();
|
||||||
|
|
||||||
|
long combined = salt ^ ((long) chunkX << 32) ^ chunkZ ^ structure.hashCode();
|
||||||
|
Random random = new Random(combined);
|
||||||
|
|
||||||
|
if (random.nextDouble() < config.getObscurePercentage()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
Bukkit.getLogger().info("[SeedProtector] Obscured " + structureKey + " at " + chunkX + "," + chunkZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/java/seedprotector/managers/PluginManager.java
Normal file
55
src/main/java/seedprotector/managers/PluginManager.java
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package seedprotector.managers;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class PluginManager {
|
||||||
|
private static PluginManager instance;
|
||||||
|
private long secretSalt;
|
||||||
|
private double obscurePercentage;
|
||||||
|
private final Set<String> exemptStructures = new HashSet<>();
|
||||||
|
private final Set<String> includedStructures = new HashSet<>();
|
||||||
|
|
||||||
|
public static PluginManager getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new PluginManager();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(Plugin plugin) {
|
||||||
|
File configFile = new File(plugin.getDataFolder(), "config.json");
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
plugin.getDataFolder().mkdirs();
|
||||||
|
try (InputStream in = plugin.getResource("config.json")) {
|
||||||
|
Files.copy(in, configFile.toPath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Could not save default config.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(configFile)) {
|
||||||
|
JsonObject json = new Gson().fromJson(reader, JsonObject.class);
|
||||||
|
this.secretSalt = json.get("secret_salt").getAsLong();
|
||||||
|
this.obscurePercentage = json.get("obscure_percentage").getAsDouble();
|
||||||
|
|
||||||
|
json.get("exempt_structures").getAsJsonArray().forEach(e -> exemptStructures.add(e.getAsString()));
|
||||||
|
json.get("included_structures").getAsJsonArray().forEach(e -> includedStructures.add(e.getAsString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Could not load config.json: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSecretSalt() { return secretSalt; }
|
||||||
|
public double getObscurePercentage() { return obscurePercentage; }
|
||||||
|
public Set<String> getExemptStructures() { return exemptStructures; }
|
||||||
|
public Set<String> getIncludedStructures() { return includedStructures; }
|
||||||
|
}
|
||||||
70
src/main/java/seedprotector/utils/ScramblePopulator.java
Normal file
70
src/main/java/seedprotector/utils/ScramblePopulator.java
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package seedprotector.utils;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.generator.BlockPopulator;
|
||||||
|
import org.bukkit.generator.LimitedRegion;
|
||||||
|
import org.bukkit.generator.WorldInfo;
|
||||||
|
import seedprotector.managers.PluginManager;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class ScramblePopulator extends BlockPopulator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, LimitedRegion limitedRegion) {
|
||||||
|
long salt = PluginManager.getInstance().getSecretSalt();
|
||||||
|
// Create a deterministic random for this chunk based on salt
|
||||||
|
long chunkSeed = salt ^ ((long) chunkX << 32) ^ chunkZ ^ worldInfo.getSeed();
|
||||||
|
Random chunkRandom = new Random(chunkSeed);
|
||||||
|
|
||||||
|
// Iterate through the chunk to find and "scramble" decorators
|
||||||
|
// We focus on Y levels where ores and flowers are common
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
int realX = (chunkX << 4) + x;
|
||||||
|
int realZ = (chunkZ << 4) + z;
|
||||||
|
|
||||||
|
for (int y = worldInfo.getMinHeight(); y < worldInfo.getMaxHeight(); y++) {
|
||||||
|
Material type = limitedRegion.getType(realX, y, realZ);
|
||||||
|
|
||||||
|
// Scramble Ores
|
||||||
|
if (isOre(type)) {
|
||||||
|
// 10% chance to move the ore slightly
|
||||||
|
if (chunkRandom.nextDouble() < 0.10) {
|
||||||
|
int offsetX = chunkRandom.nextInt(3) - 1; // -1, 0, 1
|
||||||
|
int offsetY = chunkRandom.nextInt(3) - 1;
|
||||||
|
int offsetZ = chunkRandom.nextInt(3) - 1;
|
||||||
|
|
||||||
|
int targetX = realX + offsetX;
|
||||||
|
int targetY = y + offsetY;
|
||||||
|
int targetZ = realZ + offsetZ;
|
||||||
|
|
||||||
|
if (limitedRegion.isInRegion(targetX, targetY, targetZ) &&
|
||||||
|
limitedRegion.getType(targetX, targetY, targetZ) == Material.STONE) {
|
||||||
|
limitedRegion.setType(realX, y, realZ, Material.STONE);
|
||||||
|
limitedRegion.setType(targetX, targetY, targetZ, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scramble Flowers/Vegetation on surface
|
||||||
|
if (isVegetation(type)) {
|
||||||
|
// 20% chance to remove or swap vegetation
|
||||||
|
if (chunkRandom.nextDouble() < 0.20) {
|
||||||
|
limitedRegion.setType(realX, y, realZ, Material.AIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOre(Material type) {
|
||||||
|
return type.name().endsWith("_ORE");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVegetation(Material type) {
|
||||||
|
return type == Material.TALL_GRASS || type == Material.POPPY ||
|
||||||
|
type == Material.DANDELION || type == Material.SUGAR_CANE;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/seedprotector/utils/Utils.java
Normal file
21
src/main/java/seedprotector/utils/Utils.java
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package seedprotector.utils;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
public static Component colorize(String msg) {
|
||||||
|
if (msg.contains("<") && msg.contains(">")) {
|
||||||
|
return MiniMessage.miniMessage().deserialize(msg);
|
||||||
|
}
|
||||||
|
return LegacyComponentSerializer.legacyAmpersand().deserialize(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String legacyColorize(String msg) {
|
||||||
|
return LegacyComponentSerializer.legacyAmpersand().serialize(
|
||||||
|
LegacyComponentSerializer.legacyAmpersand().deserialize(msg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/resources/config.json
Normal file
23
src/main/resources/config.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"secret_salt": 123456789,
|
||||||
|
"obscure_percentage": 0.5,
|
||||||
|
"exempt_structures": [
|
||||||
|
"minecraft:stronghold",
|
||||||
|
"minecraft:fortress",
|
||||||
|
"minecraft:end_city"
|
||||||
|
],
|
||||||
|
"included_structures": [
|
||||||
|
"minecraft:village_savanna",
|
||||||
|
"minecraft:village_desert",
|
||||||
|
"minecraft:village_plains",
|
||||||
|
"minecraft:village_snowy",
|
||||||
|
"minecraft:village_taiga",
|
||||||
|
"minecraft:desert_pyramid",
|
||||||
|
"minecraft:jungle_pyramid",
|
||||||
|
"minecraft:swamp_hut",
|
||||||
|
"minecraft:pillager_outpost",
|
||||||
|
"minecraft:ocean_monument",
|
||||||
|
"minecraft:shipwreck",
|
||||||
|
"minecraft:buried_treasure"
|
||||||
|
]
|
||||||
|
}
|
||||||
6
src/main/resources/plugin.yml
Normal file
6
src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
main: seedprotector.SeedProtector
|
||||||
|
version: 1.0
|
||||||
|
name: SeedProtector
|
||||||
|
author: bunchy7s
|
||||||
|
api-version: '1.21'
|
||||||
|
folia-supported: true
|
||||||
Loading…
Add table
Reference in a new issue