Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0ba5362e | ||
|
|
e6f73e3f05 | ||
|
|
c6ddf65c64 | ||
|
|
b23571906f |
No files matched your search
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discussions (Questions & Ideas)
|
||||
url: https://github.com/SunsetRQ/AntiSeedCracker/discussions
|
||||
url: https://github.com/SunsetRQ7/AntiSeedCracker/discussions
|
||||
about: Ask questions, share configs, or discuss ideas here before opening an issue.
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to AntiSeedCracker are documented here. Format loosely follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [3.5.0.2] - 2026-07-24
|
||||
|
||||
### Fixed
|
||||
- **Buried treasure map locations were never actually scrambled**: `MapSeedInterceptor`
|
||||
filtered map decorations by name, checking for `"TREASURE"`, `"PILLAGER"`, and
|
||||
`"BASTION"` — none of which are real vanilla map decoration type IDs (verified against
|
||||
`org.bukkit.map.MapCursor.Type` directly: no such constants exist, so those three checks
|
||||
never matched anything, ever). Buried treasure maps actually use `TARGET_X`, which was
|
||||
missing entirely. Buried treasure is one of this plugin's own named SeedCrackerX
|
||||
structure signals, so its map marker was leaking the real, unscrambled location on every
|
||||
treasure map handed to a player. Now correctly matches `TARGET_X`; the two other
|
||||
never-matching checks were removed.
|
||||
- **`end_cities`/`end_spikes` `modify_world: false` could permanently disable itself**:
|
||||
both `EndCityProtector` and `EndSpikeProtector` marked a chunk/world as "already
|
||||
processed" as soon as it was scanned, even when `modify_world` was off and nothing was
|
||||
actually changed. Turning `modify_world` on later via `/asc toggle` or a config edit
|
||||
would find that flag already set and skip the modification forever, for any chunk/world
|
||||
already loaded once while it was off. Both now only mark a chunk/world "done" when the
|
||||
modification actually ran (or, for End Cities, when a chunk provably has no End City at
|
||||
all and therefore never will need modifying).
|
||||
- **Redundant respawn-cycle polling in `EndSpikeProtector`**: all four dragon-respawn End
|
||||
Crystals fire their placement event independently, each previously starting its own
|
||||
300-attempt polling loop to detect when the respawn animation finished — four times the
|
||||
necessary scheduled work per dragon fight. Now guarded so only one poll loop runs per
|
||||
world at a time.
|
||||
- Replaced reflection-based scheduled-task cancellation (`getMethod("cancel").invoke(...)`,
|
||||
silently swallowing any failure) in `FoliaSchedulerUtil` with direct, typed
|
||||
`ScheduledTask#cancel()` calls — verified against the real Paper API class. Faster, and
|
||||
a genuine cancellation failure is no longer silently discarded.
|
||||
- Removed a pointless seconds→ticks→seconds round-trip conversion in the seed rotation
|
||||
interval config.
|
||||
- Simplified the End Spike pillar-angle formula to a plain decagon spacing expression;
|
||||
behaviorally identical (verified via the periodicity of `cos`/`sin`), just no longer
|
||||
needlessly obscure.
|
||||
- Trimmed default `config.yml` down to essential settings, with fuller explanations kept
|
||||
in the README instead of long inline comments.
|
||||
|
||||
## [3.5.0.1] - 2026-07-22
|
||||
|
||||
### Fixed
|
||||
- **Eye of Ender redirection didn't actually hold**: `EyeOfEnderProtector` only ever set the
|
||||
thrown eye's *velocity* toward the fake stronghold. Vanilla's `EnderSignal` entity is
|
||||
constructed with a target location pointing at the **real** stronghold before
|
||||
`ProjectileLaunchEvent` even fires, and re-accelerates toward that stored target every
|
||||
tick on its own — independent of whatever velocity a plugin sets once at launch. Without
|
||||
calling `EnderSignal#setTargetLocation`, the eye would curve back toward the true bearing
|
||||
within a second or two of flight, regardless of the initial jitter, leaking the real
|
||||
direction to anything tracking the eye's trajectory rather than just its launch frame.
|
||||
Now sets the target location (with a plausible, per-player-stable fake Y as well as
|
||||
x/z) in addition to velocity, on both the primary and Folia-retry code paths.
|
||||
- **Fake stronghold silently moved on every `/asc reload`/`/asc toggle`**: `initPlayer()`
|
||||
unconditionally re-rolled each online player's fake stronghold every time it ran, which
|
||||
includes on every reload and every `/asc toggle` (which reloads internally) — not just on
|
||||
join. An admin flipping one unrelated toggle would make a previously-consistent `/locate
|
||||
stronghold` or Eye of Ender answer jump to a new location with no world change, undermining
|
||||
the exact consistency guarantee 3.5.0 added `getOrAssignFakeStructureLocation` to provide.
|
||||
Now only assigns a fake stronghold the first time a player is seen; already-assigned
|
||||
players keep theirs across reloads. `SeedManager` gained `hasFakeStronghold`/
|
||||
`getFakeStrongholdY` to support this.
|
||||
- Verified with a clean `mvn clean package` on JDK 25 after both fixes.
|
||||
|
||||
## [3.5.0] - 2026-07-22
|
||||
|
||||
Live-tested end to end on a real Paper 26.1.2 server via RCON (repeated boots, every
|
||||
`/asc` subcommand, both direct and `/execute`-wrapped bypass attempts, config
|
||||
persistence across restarts). Several of the fixes below were only found because of
|
||||
that live testing, not static review.
|
||||
|
||||
### Fixed
|
||||
- **Build was broken as shipped**: `pom.xml` targeted Java 21 while the bundled
|
||||
`folia-api` 26.1.2 dependency ships Java 25 class files, so a clean checkout could
|
||||
not compile. Bumped to release 25 (JDK 25 confirmed installed and working).
|
||||
- **`/execute`-wrapping command-blocker bypass**: vanilla's `/execute ... run <command>`
|
||||
re-dispatches its trailing command internally without re-firing
|
||||
`PlayerCommandPreprocessEvent`, so `/execute run seed` (or nested chains, even with a
|
||||
dimension switch like `/execute in minecraft:the_end run seed`) sailed straight past
|
||||
the blocker. Added `CommandUtil`, which recursively resolves what an `/execute` chain
|
||||
ultimately dispatches to before the block check runs. Verified live via RCON.
|
||||
- **`/asc stats` bled into the next RCON command's response**: the command computed its
|
||||
result asynchronously and replied a tick later, after the RCON round-trip had already
|
||||
closed — the reply then attached itself to whatever command ran next over the same
|
||||
connection instead of `stats`. Found live via RCON testing. Now synchronous for
|
||||
console/RCON senders (async is kept for real players, to avoid blocking a tick on file
|
||||
I/O from chat).
|
||||
- **Windows console mojibake**: em-dashes and box-drawing characters in
|
||||
`logger.info/warning()` calls rendered as `�` on a real Windows console (confirmed
|
||||
live). Chat-facing messages (Adventure `Component`) were unaffected and left as-is;
|
||||
only raw JUL logger output was converted to plain ASCII.
|
||||
- **`/locate` answers were inconsistent on repeat queries**: non-stronghold structures
|
||||
got a brand-new random fake location on every call, letting a suspicious player detect
|
||||
the spoof just by re-running the command in place. Now cached per player+structure,
|
||||
same as strongholds already were.
|
||||
- **Treasure/explorer map icons visibly jittered**: fake decoration coordinates were
|
||||
redrawn from fresh randomness on every `MAP_DATA` packet, so the icon jumped around
|
||||
the map on every refresh instead of sitting at one (wrong) spot. Now derived
|
||||
deterministically from map ID + icon type + real position, so it's stable but still
|
||||
unpredictable to the player.
|
||||
- **Unbounded cache growth in `StructureReconMonitor`**: the per-chunk "does this chunk
|
||||
have a tracked structure" cache never evicted, growing for the entire server uptime.
|
||||
Now evicted on `ChunkUnloadEvent`, bounding it to currently-loaded chunks.
|
||||
- **`extra_blocked_commands` namespace-stripping bug**: the old colon-stripping logic
|
||||
stripped from the *first* colon anywhere in the string, not just a leading
|
||||
`namespace:` prefix, so a command with a namespaced argument later in the string
|
||||
could have its root command mangled. Fixed in the new shared `CommandUtil`.
|
||||
- Version strings were inconsistent across `pom.xml` (3.4.0, itself unreleased/broken),
|
||||
`plugin.yml` (3.3.1), and `README.md` (3.3.0); `plugin.yml`'s description still
|
||||
mentioned "plugin-API interception," a feature removed back in 3.3.0. All unified.
|
||||
|
||||
### Added
|
||||
- `/asc toggle <feature> [true|false]` — flip or explicitly set one protection layer
|
||||
live, persisted to `config.yml`, no full reload needed. Tab-completes feature names.
|
||||
- Granular permissions: `antiseedcracker.admin.status` (read-only) and
|
||||
`antiseedcracker.admin.manage` (reload/toggle), both covered by the existing
|
||||
`antiseedcracker.admin` umbrella so nothing breaks for existing setups.
|
||||
- `/asc help` — lists only the subcommands the sender actually has permission for.
|
||||
- **Command Probing Monitor**: logs (never blocks, same philosophy as the structure
|
||||
recon monitor) when a player triggers several blocked seed commands in a short
|
||||
window — a pattern consistent with a script/macro probing for a bypass.
|
||||
- `structure_recon_monitor.tracked_structures` is now configurable; defaults now
|
||||
include `buried_treasure`, which the plugin's own threat-model docs already named as
|
||||
a SeedCrackerX data source but which was missing from the actual tracked set.
|
||||
|
||||
### Researched
|
||||
- Surveyed SeedCrackerX's current (1.18+) technique set, LiquidBounce/Wurst/Meteor
|
||||
Client's public source and issue trackers (no seed-cracking integration or
|
||||
spoof-detection features found in any of them — ESP/X-ray modules only render chunk
|
||||
data already sent to the client), and competing plugins (`akshualy/AntiSeedCracker`,
|
||||
`zAntiCracker`, `DrexHD/SeedGuard`, `Earthcomputer/SecureSeed`). Findings folded into
|
||||
the README's Threat Model section, including an honest note that hashed-seed
|
||||
spoofing/rotation is a "raises the bar" layer, not a load-bearing one, against a
|
||||
well-informed attacker relying on structure-position data instead.
|
||||
- Documented, as a known limitation, that command-block- and data-pack-`/function`-
|
||||
issued commands never fire any Bukkit command-preprocess event at all — there is no
|
||||
supported API hook for either, so this can't be closed without NMS-level hacks the
|
||||
project deliberately avoids.
|
||||
|
||||
## [3.3.1] - 2026-07-12
|
||||
|
||||
- `pluginConfig`/`databaseManager` were plain fields reassigned by `/asc reload` on the
|
||||
main thread but read from PacketEvents Netty threads and the async scheduler with no
|
||||
happens-before edge; marked `volatile`.
|
||||
- `extra_blocked_commands` was never lowercased at load, so any mixed-case entry in
|
||||
`config.yml` silently failed to match.
|
||||
- `StructureReconMonitor` called `chunk.getStructures()` on every chunk crossing for
|
||||
every player with no cross-player caching; added a per-world chunk-level cache so
|
||||
each chunk's lookup happens once total. Added `ANCIENT_CITY` to the tracked set.
|
||||
- `DatabaseManager` opened and closed a file handle per audit event; now keeps a
|
||||
persistent writer, rotated only on date change.
|
||||
- Removed dead code left over from the removed `WorldSeedInterceptor`.
|
||||
- Verified live on Paper 26.1.2-74 and Folia 26.1.2-8, zero errors, real seed confirmed
|
||||
byte-for-byte intact on disk.
|
||||
|
||||
## [3.3.0] - 2026-07-11
|
||||
|
||||
- **Critical fix**: removed `WorldSeedInterceptor`, which patched the live NMS seed
|
||||
field once per second on every loaded world. That field is re-read fresh by vanilla
|
||||
chunk decoration (trees, ores, structure placement) for every chunk generated, not
|
||||
cached once at world load — any chunk decorated while it held a fake value generated
|
||||
from randomness instead of the real seed. This was the root cause of reported "seed
|
||||
changing disrupts chunk generation" issues.
|
||||
- Replaced it with `SeedIntegrityMonitor`, a read-only watchdog that only ever reads
|
||||
`world.getSeed()` and warns on drift, never writes to it.
|
||||
- `EndCityProtector` now scans only the structure's bounding box instead of the full
|
||||
chunk column.
|
||||
- Player-seed safety-net task interval reduced from 1s to 15s.
|
||||
- Researched LiquidBounce/SeedCrackerX mechanics and documented honestly that seed
|
||||
cracking via legitimately-visited structure coordinates is not a network exploit and
|
||||
cannot be prevented server-side.
|
||||
- Added `StructureReconMonitor` (audit-log only, never blocks) and
|
||||
`SlimeChunkObfuscator` (opt-in, off by default).
|
||||
- Documented that block/ore ESP (XRay, StorageESP) is out of scope — use Paper's
|
||||
native anti-xray instead.
|
||||
|
||||
## [3.2.0] - 2026-07-04
|
||||
|
||||
- Added bStats (plugin id 32378) with platform/protection charts.
|
||||
- Upgraded PacketEvents 2.12.2 → 2.13.0.
|
||||
- Configurable seed-blocked messages via `config.yml`.
|
||||
- `/asc reload` now applies database enable/disable without a restart.
|
||||
- Verified live: Paper 26.1.2-72 and Folia 26.1.2-8 boot with zero errors.
|
||||
|
||||
## [3.1.1] - 2026-06-13
|
||||
|
||||
- Fixed `NoClassDefFoundError: net/kyori/adventure/nbt/BinaryTag` on Folia/Paper by
|
||||
bundling `adventure-nbt` in the shaded jar (PacketEvents declared it as a compile
|
||||
dependency without bundling it).
|
||||
- Upgraded PacketEvents 2.12.0 → 2.12.2.
|
||||
|
||||
## [3.1.0] - 2026-06-13
|
||||
|
||||
- Initial release: professional multi-layer world seed protection for Paper, Purpur,
|
||||
and Folia.
|
||||
@@ -1,11 +1,11 @@
|
||||
# AntiSeedCracker
|
||||
|
||||
[](https://github.com/SunsetRQ/AntiSeedCracker/actions/workflows/build.yml)
|
||||
[](https://github.com/SunsetRQ/AntiSeedCracker/releases/latest)
|
||||
[](https://github.com/SunsetRQ7/AntiSeedCracker/actions/workflows/build.yml)
|
||||
[](https://github.com/SunsetRQ7/AntiSeedCracker/releases/latest)
|
||||
[](LICENSE)
|
||||
[](https://adoptium.net/)
|
||||
|
||||
**Version:** 3.5.0 | **Target:** Minecraft 26.1.2 | **Author:** SunsetRQ7_
|
||||
**Version:** 3.5.0.2 | **Target:** Minecraft 26.1.2 | **Author:** SunsetRQ7_
|
||||
|
||||
AntiSeedCracker is a professional, multi-layer world seed protection plugin for Paper, Purpur, and Folia. It blocks every known technique a player can use to recover your world seed, protecting your server from ESP clients, ghost clients, and seed-cracking tools such as SeedCrackerX.
|
||||
|
||||
@@ -51,7 +51,7 @@ Anonymous usage statistics are collected via [bStats](https://bstats.org/plugin/
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download `AntiSeedCracker-3.5.0.jar` from [Releases](../../releases/latest).
|
||||
1. Download `AntiSeedCracker-3.5.0.2.jar` from [Releases](../../releases/latest).
|
||||
2. Place it in your server's `plugins/` folder.
|
||||
3. Start or restart the server.
|
||||
4. Edit `plugins/AntiSeedCracker/config.yml` to your preference.
|
||||
@@ -237,7 +237,7 @@ export JAVA_HOME=/path/to/jdk-25
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
The shaded jar is produced at `target/AntiSeedCracker-3.5.0.jar`. PacketEvents 2.13.0 and bStats 3.2.1 are bundled and relocated under `me.sunsetrqsv.antiseedcracker.lib`.
|
||||
The shaded jar is produced at `target/AntiSeedCracker-3.5.0.2.jar`. PacketEvents 2.13.0 and bStats 3.2.1 are bundled and relocated under `me.sunsetrqsv.antiseedcracker.lib`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+32
-20
@@ -1,46 +1,58 @@
|
||||
# AntiSeedCracker
|
||||
|
||||
**Stop seed crackers. Silently. Permanently.**
|
||||
[](https://github.com/SunsetRQ7/AntiSeedCracker/actions/workflows/build.yml)
|
||||
[](https://github.com/SunsetRQ7/AntiSeedCracker/releases/latest)
|
||||
[](https://github.com/SunsetRQ7/AntiSeedCracker/blob/main/LICENSE)
|
||||
[](https://adoptium.net/)
|
||||
|
||||
AntiSeedCracker is a professional, multi-layer world seed protection plugin for **Paper**, **Purpur**, and **Folia**. It defeats every known technique that cheat clients, ghost clients, and tools like SeedCrackerX use to recover your world seed — without ever kicking, banning, or even alerting the player. Cheaters simply receive cryptographically random garbage and never know why their cracker fails.
|
||||
### Stop seed crackers. Silently. Permanently.
|
||||
|
||||
Your world seed is the master key to your entire map. Whoever recovers it can compute — instantly, from outside the game, with zero in-game effort — the exact location of every stronghold, ancient city, buried treasure, ore vein, and slime chunk on your server. **That's not a minor leak. That's wallhack-grade ESP for your whole world.**
|
||||
|
||||
AntiSeedCracker shuts every known door a cheat client, ghost client, or tool like SeedCrackerX can use to get it. **14 protection layers. Zero kicks. Zero bans.** Cheaters just quietly receive cryptographically random garbage and never find out why their cracker keeps failing.
|
||||
|
||||
---
|
||||
|
||||
## Why protect your seed?
|
||||
|
||||
Once a player knows your world seed, they can compute the exact location of every stronghold, ancient city, buried treasure, ore vein, and slime chunk on your server — from an external tool, with no in-game effort. Seed knowledge is wallhack-grade ESP for the entire map. AntiSeedCracker closes every leak.
|
||||
Once your seed is out, it doesn't matter how good your build-battle event or your economy plugin is — every hidden structure on the map is public information to anyone with a laptop. Seed-cracking tools don't need to hack your server or read your files: they reconstruct the seed purely from structure coordinates a player legitimately walked to. AntiSeedCracker can't make that impossible (nothing can — see **Honest limits** below), but it closes off every *other* way to leak it, and gives you visibility into the one it can't.
|
||||
|
||||
## Protection layers
|
||||
|
||||
- **Packet-level seed spoofing** — replaces the hashed seed in JOIN_GAME and RESPAWN packets with a per-player, cryptographically random fake before it ever leaves the server (Netty-level via bundled PacketEvents).
|
||||
**Network-level**
|
||||
- **Packet-level seed spoofing** — replaces the hashed seed in `JOIN_GAME` and `RESPAWN` packets with a per-player, cryptographically random fake before it ever leaves the server (Netty-level, via bundled PacketEvents).
|
||||
- **Dynamic seed rotation** — every player's fake seed rotates on a configurable interval, defeating crackers that accumulate data across sessions.
|
||||
- **Seed integrity monitor** — read-only watchdog that verifies the real generation seed never drifts. AntiSeedCracker does not, and will not, write to your world's actual generation seed — see "Safe for your world" below.
|
||||
- **Eye of Ender redirection** — thrown Eyes fly toward a per-player fake stronghold with randomized angular jitter, making triangulation useless.
|
||||
- **/locate spoofing** — returns convincing fake coordinates; stronghold queries stay consistent with Eye of Ender behavior.
|
||||
- **Seed command blocking** — /seed and friends are blocked for players, console, RCON, and stripped from tab-complete.
|
||||
- **Seed integrity monitor** — read-only watchdog confirming the real generation seed never drifts. AntiSeedCracker does not, and will not, write to your world's actual generation seed — see **Safe for your world** below.
|
||||
|
||||
**World & structure**
|
||||
- **Eye of Ender redirection** — thrown Eyes are redirected to a per-player fake stronghold for their entire flight path, with angular jitter, making triangulation useless.
|
||||
- **`/locate` spoofing** — returns convincing fake coordinates, stable per player and per structure so repeat queries can't be used to spot the fake.
|
||||
- **End spike randomization** — shuffles the bedrock cap heights of the ten End pillars, destroying the vanilla height fingerprint that uniquely identifies a seed.
|
||||
- **End City fingerprint removal** — replaces the distinctive magenta glass pattern in End Cities (scoped to the structure's bounding box, not the whole chunk).
|
||||
- **Treasure & explorer map scrambling** — structure icons on maps are randomized in the MAP_DATA packet.
|
||||
- **Structure reconnaissance monitor** — logs (never blocks) suspicious multi-structure visitation patterns matching known seed-cracking tool behavior; tracked structure list is fully configurable.
|
||||
- **Command probing monitor** — logs (never blocks) a player racking up several blocked seed commands in a short window, a pattern consistent with bypass-probing scripts.
|
||||
- **`/execute`-wrapping protection** — every blocked command is also checked against what an `/execute ... run ...` chain ultimately dispatches to, closing a well-known Bukkit command-blocker gap.
|
||||
- **Slime chunk obfuscation** (opt-in) — denies slime-chunk-sampling-based cracking at the cost of disabling slime farms where enabled.
|
||||
- **Audit log** — every blocked attempt is recorded to a lightweight flat-file log, with /asc stats summaries in-game.
|
||||
- **Treasure & explorer map scrambling** — buried treasure, mansion, monument, and other structure icons on maps are randomized in the `MAP_DATA` packet, stable per map so icons don't visibly jitter around.
|
||||
- **Slime chunk obfuscation** *(opt-in)* — denies slime-chunk-sampling-based cracking at the cost of disabling slime farms where enabled.
|
||||
|
||||
**Command & detection** *(all logging-only where noted — never punishes players)*
|
||||
- **Seed command blocking** — `/seed` and friends are blocked for players, console, and RCON, and stripped from tab-complete.
|
||||
- **`/execute`-wrapping protection** — every blocked command is also checked against what an `/execute ... run ...` chain ultimately dispatches to (recursively, for nested chains), closing a well-known Bukkit command-blocker gap.
|
||||
- **Structure reconnaissance monitor** — logs suspicious multi-structure visitation patterns matching known seed-cracking tool behavior; the tracked structure list is fully configurable.
|
||||
- **Command probing monitor** — logs a player racking up several blocked seed commands in a short window, a pattern consistent with a bypass-probing script rather than one curious `/seed`.
|
||||
- **Audit log** — every blocked attempt is recorded to a lightweight flat-file log, with `/asc stats` summaries in-game.
|
||||
|
||||
## Never punishes players
|
||||
|
||||
AntiSeedCracker follows a strict no-punishment philosophy: no kicks, no bans. Cheat tools silently fail while legitimate gameplay is completely unaffected.
|
||||
AntiSeedCracker follows a strict no-punishment philosophy: no kicks, no bans, ever. Cheat tools silently fail while legitimate gameplay is completely unaffected — nobody gets caught in the crossfire.
|
||||
|
||||
## Safe for your world
|
||||
|
||||
- The real seed is never read into logs, files, or network traffic, and is never rewritten by this plugin.
|
||||
- An earlier internal design continuously overwrote the live in-memory generation seed to spoof `World.getSeed()` for other plugins. That approach was removed in 3.3.0 after we found it could corrupt chunk decoration on affected servers — Minecraft re-reads that field fresh for every chunk it generates, not just once at world load. The plugin now only ever reads that value, never writes it.
|
||||
- Optional cosmetic changes (End spike caps, End City glass) are single-block, physics-free, run at most once per chunk, and can be disabled in the config.
|
||||
- Verified by live testing on Paper 26.1.2 and Folia 26.1.2: zero errors, and the real seed confirmed byte-for-byte intact on disk after extended runtime.
|
||||
- Verified by live testing on real Paper 26.1.2 and Folia 26.1.2 servers: zero errors, real seed confirmed byte-for-byte intact on disk after extended runtime.
|
||||
|
||||
## Honest limits
|
||||
|
||||
No plugin can make a seed truly uncrackable against tools like SeedCrackerX, which reconstruct it from the real coordinates of structures a player has legitimately explored — that's not a network exploit, and coordinates can't be hidden from a player standing at their own position. AntiSeedCracker closes every server-side leak we're aware of (packet spoofing, triangulation, command access, structure fingerprinting) and adds detection for cracking-pattern behavior, but exploration-based cracking is a limit of the genre, not something this plugin claims to solve.
|
||||
No plugin can make a seed truly uncrackable against tools like SeedCrackerX, which reconstruct it from the real coordinates of structures a player has legitimately explored — that's not a network exploit, and coordinates can't be hidden from a player standing at their own position. AntiSeedCracker closes every server-side leak we're aware of (packet spoofing, triangulation, command access, structure fingerprinting) and adds detection for cracking-pattern behavior, but exploration-based cracking is a limit of the genre, not something this plugin claims to solve. If a listing promises "100% uncrackable," be skeptical of it — not of us for saying so.
|
||||
|
||||
## Performance
|
||||
|
||||
@@ -59,9 +71,9 @@ Spigot/CraftBukkit and legacy Minecraft versions (1.16.5 – 25.x) are not suppo
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Drop `AntiSeedCracker-3.5.0.jar` into your `plugins/` folder.
|
||||
1. Drop `AntiSeedCracker-3.5.0.2.jar` into your `plugins/` folder.
|
||||
2. Restart the server. Protection is active immediately with sensible defaults.
|
||||
3. Optionally tune `plugins/AntiSeedCracker/config.yml` (every layer, world list, message, and interval is configurable) and apply with `/asc reload`.
|
||||
3. Optionally tune `plugins/AntiSeedCracker/config.yml` — every layer, world list, message, and interval is configurable — and apply with `/asc reload`, or flip a single feature live with `/asc toggle`.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -76,5 +88,5 @@ Permissions: `antiseedcracker.admin` (umbrella, default: op), or split as `antis
|
||||
|
||||
---
|
||||
|
||||
**Open source** under the MIT License: https://github.com/SunsetRQ/AntiSeedCracker
|
||||
**Open source** under the MIT License: https://github.com/SunsetRQ7/AntiSeedCracker
|
||||
Issues and feature requests welcome on GitHub. Anonymous usage metrics via bStats (opt-out supported).
|
||||
@@ -0,0 +1,98 @@
|
||||
## 3.5.0.2 — 2026-07-24
|
||||
|
||||
**Fixed**
|
||||
- Fixed buried treasure map locations never actually being scrambled: the treasure map protection layer was filtering map icons by the wrong internal name, so a real buried treasure map still pointed straight at the true, unmodified location. It now correctly detects and scrambles the treasure marker.
|
||||
- Fixed `end_cities`/`end_spikes` protection being able to permanently disable itself: if `modify_world` was left off for a world or chunk, turning it on later (including via `/asc toggle`) would silently do nothing for anything already loaded once while it was off. It now correctly applies the moment it's turned on.
|
||||
- Fixed the End obsidian pillar protection starting up to four times more background work than needed during a dragon respawn.
|
||||
- General code cleanup and minor performance tightening (task scheduling, config parsing) with no behavior changes.
|
||||
|
||||
---
|
||||
|
||||
## 3.5.0.1 — 2026-07-22
|
||||
|
||||
**Fixed**
|
||||
- Fixed Eye of Ender redirection not actually holding: thrown eyes had their initial velocity redirected toward the fake stronghold, but Minecraft's own homing behavior would curve them back toward the real stronghold's direction within a second or two of flight. The eye's target location is now redirected as well, so it flies toward the fake stronghold for its entire flight, not just its first moment in the air.
|
||||
- Fixed the fake stronghold location silently changing for online players on `/asc reload` or `/asc toggle`, which could make a previously consistent `/locate stronghold` or Eye of Ender answer jump to a new location with no world change. Each player now keeps the same fake stronghold for their whole session, exactly as intended.
|
||||
|
||||
---
|
||||
|
||||
## 3.5.0 — 2026-07-22
|
||||
|
||||
**Fixed**
|
||||
- Fixed a compiler/target mismatch that prevented a clean build from source (build now correctly targets Java 25, matching the bundled Folia API).
|
||||
- Fixed an `/execute`-wrapped bypass of the seed-command blocker: `/execute run seed` (including nested chains and dimension switches like `/execute in minecraft:the_end run seed`) previously slipped past the blocker because `/execute` re-dispatches its inner command internally. Blocked commands are now resolved recursively through `/execute` chains before the check runs.
|
||||
- Fixed `/asc stats` occasionally attaching its output to the next RCON command's response due to an async delay. Console/RCON senders now get a synchronous reply; player replies remain async to avoid blocking on file I/O.
|
||||
- Fixed console log lines (em-dashes, box-drawing characters) rendering as garbled text on Windows consoles.
|
||||
- Fixed `/locate` returning a brand-new fake location on every repeated query for the same structure, which could tip off a suspicious player. Results are now stable per player and structure.
|
||||
- Fixed treasure/explorer map icons visibly jumping to a new fake position on every map refresh. The fake position is now stable per map.
|
||||
- Fixed unbounded memory growth in the structure reconnaissance monitor's per-chunk cache — entries are now evicted when their chunk unloads.
|
||||
- Fixed a namespace-stripping bug in `extra_blocked_commands` matching that could mismatch commands containing a colon later in the argument string.
|
||||
- Synced version numbers across `pom.xml`, `plugin.yml`, and `README.md`, and removed a stale reference to a feature already removed back in 3.3.0.
|
||||
|
||||
**Added**
|
||||
- `/asc toggle <feature> [true|false]` — flip any protection layer on or off live, persisted to `config.yml`, no reload required.
|
||||
- New granular permissions: `antiseedcracker.admin.status` (read-only) and `antiseedcracker.admin.manage` (reload/toggle) — both still covered by the existing `antiseedcracker.admin` permission, so existing setups keep working unchanged.
|
||||
- `/asc help` — lists only the subcommands the sender actually has permission to use.
|
||||
- New Command Probing Monitor: logs (never blocks — same philosophy as every other detection layer in this plugin) when a player triggers several blocked seed commands in a short window, a pattern consistent with a script or macro probing for a bypass.
|
||||
- `structure_recon_monitor.tracked_structures` is now configurable, and now includes Buried Treasure by default.
|
||||
|
||||
---
|
||||
|
||||
## 3.3.1 — 2026-07-12
|
||||
|
||||
**Fixed**
|
||||
- Fixed a thread-visibility bug where `/asc reload` updated config/database state in a way that wasn't reliably visible to packet-handling and async threads.
|
||||
- Fixed `extra_blocked_commands` entries with mixed-case letters silently failing to match.
|
||||
- Fixed repeated, uncached structure lookups on every chunk crossing for every player — lookups are now cached per chunk.
|
||||
- Fixed the audit log opening and closing a file handle for every single event; it now keeps a persistent writer and only rotates on date change.
|
||||
- Added Ancient City to the tracked structure set.
|
||||
- Removed dead code left over from a previously removed feature.
|
||||
- Verified live on Paper 26.1.2-74 and Folia 26.1.2-8 with zero errors; real seed confirmed byte-for-byte intact on disk after extended runtime.
|
||||
|
||||
---
|
||||
|
||||
## 3.3.0 — 2026-07-11
|
||||
|
||||
**Fixed**
|
||||
- **Critical fix**: removed a feature that patched the live world seed in memory once per second. That value is re-read by vanilla chunk decoration (trees, ores, structure placement) fresh for every chunk generated, not just once at world load — so any chunk decorated while it held a fake value generated incorrectly instead of from the real seed. This was the root cause of reports of broken chunk generation.
|
||||
- Replaced it with a read-only Seed Integrity Monitor that only ever reads the world seed and warns if it drifts — it never writes to it.
|
||||
- End City protection now scans only the structure's bounding box instead of the entire chunk column, reducing overhead on large servers.
|
||||
- Reduced the player-seed safety-net task's interval from every 1 second to every 15 seconds.
|
||||
|
||||
**Added**
|
||||
- Structure Reconnaissance Monitor (audit-log only — never blocks or punishes players).
|
||||
- Optional Slime Chunk Obfuscation (off by default).
|
||||
|
||||
**Notes**
|
||||
- Ore/block ESP protection (X-ray, storage ESP) is intentionally out of scope for this plugin — use your server software's built-in anti-X-ray instead.
|
||||
|
||||
---
|
||||
|
||||
## 3.2.0 — 2026-07-04
|
||||
|
||||
**Added**
|
||||
- Anonymous usage metrics (bStats), with platform and protection-layer charts.
|
||||
- Configurable seed-blocked messages in `config.yml`.
|
||||
- `/asc reload` now applies database enable/disable without a restart.
|
||||
|
||||
**Changed**
|
||||
- Upgraded PacketEvents 2.12.2 → 2.13.0.
|
||||
|
||||
**Verified**
|
||||
- Live-tested with zero errors on Paper 26.1.2-72 and Folia 26.1.2-8.
|
||||
|
||||
---
|
||||
|
||||
## 3.1.1 — 2026-06-13
|
||||
|
||||
**Fixed**
|
||||
- Fixed a `NoClassDefFoundError` on Folia/Paper caused by a missing bundled dependency.
|
||||
|
||||
**Changed**
|
||||
- Upgraded PacketEvents 2.12.0 → 2.12.2.
|
||||
|
||||
---
|
||||
|
||||
## 3.1.0 — 2026-06-13
|
||||
|
||||
Initial release: professional multi-layer world seed protection for Paper, Purpur, and Folia.
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>me.sunsetrqsv</groupId>
|
||||
<artifactId>AntiSeedCracker</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<version>3.5.0.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>AntiSeedCracker</name>
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class PluginConfig {
|
||||
private final boolean interceptRespawn;
|
||||
|
||||
private final boolean seedRotationEnabled;
|
||||
private final long seedRotationIntervalTicks;
|
||||
private final long seedRotationIntervalSeconds;
|
||||
|
||||
private final boolean endSpikesEnabled;
|
||||
private final List<String> endSpikeWorlds;
|
||||
@@ -76,8 +76,8 @@ public final class PluginConfig {
|
||||
this.interceptRespawn = cfg.getBoolean("seed_obfuscation.intercept_respawn", true);
|
||||
|
||||
this.seedRotationEnabled = cfg.getBoolean("seed_rotation.enabled", true);
|
||||
long rawSecs = Math.max(30L, cfg.getLong("seed_rotation.interval_seconds", 60L));
|
||||
this.seedRotationIntervalTicks = rawSecs * 20L;
|
||||
this.seedRotationIntervalSeconds = Math.max(30L,
|
||||
cfg.getLong("seed_rotation.interval_seconds", 60L));
|
||||
|
||||
this.endSpikesEnabled = cfg.getBoolean("structure_protection.end_spikes.enabled", true);
|
||||
this.endSpikeWorlds = Collections.unmodifiableList(
|
||||
@@ -154,8 +154,8 @@ public final class PluginConfig {
|
||||
public boolean isInterceptLogin() { return interceptLogin; }
|
||||
public boolean isInterceptRespawn() { return interceptRespawn; }
|
||||
|
||||
public boolean isSeedRotationEnabled() { return seedRotationEnabled; }
|
||||
public long getSeedRotationIntervalTicks() { return seedRotationIntervalTicks; }
|
||||
public boolean isSeedRotationEnabled() { return seedRotationEnabled; }
|
||||
public long getSeedRotationIntervalSeconds() { return seedRotationIntervalSeconds; }
|
||||
|
||||
public boolean isEndSpikesEnabled() { return endSpikesEnabled; }
|
||||
public List<String> getEndSpikeWorlds() { return endSpikeWorlds; }
|
||||
|
||||
@@ -49,16 +49,25 @@ public final class EndCityProtector implements Listener {
|
||||
Collection<GeneratedStructure> structures =
|
||||
chunk.getStructures(Structure.END_CITY);
|
||||
|
||||
if (structures.isEmpty()) return;
|
||||
if (structures.isEmpty()) {
|
||||
// Nothing in this chunk ever needs checking again - structures don't
|
||||
// appear after generation, so this is permanently safe to skip.
|
||||
chunk.getPersistentDataContainer()
|
||||
.set(modifiedKey, PersistentDataType.BYTE, (byte) 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getPluginConfig().isEndCitiesModifyWorld()) {
|
||||
for (GeneratedStructure structure : structures) {
|
||||
replaceGlassInBounds(chunk, structure.getBoundingBox());
|
||||
}
|
||||
chunk.getPersistentDataContainer()
|
||||
.set(modifiedKey, PersistentDataType.BYTE, (byte) 1);
|
||||
}
|
||||
|
||||
chunk.getPersistentDataContainer()
|
||||
.set(modifiedKey, PersistentDataType.BYTE, (byte) 1);
|
||||
// else: an End City is here but modify_world is off - leave unmarked so a
|
||||
// future load re-checks this chunk in case modify_world gets enabled later.
|
||||
// Marking it "done" here without having done anything would permanently
|
||||
// skip this chunk even after the admin turns modify_world on.
|
||||
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[AntiSeedCracker] EndCityProtector scan error"
|
||||
|
||||
@@ -20,6 +20,9 @@ import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class EndSpikeProtector implements Listener {
|
||||
|
||||
@@ -31,7 +34,7 @@ public final class EndSpikeProtector implements Listener {
|
||||
static {
|
||||
PILLAR_ANGLES = new double[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
PILLAR_ANGLES[i] = 2.0 * (-Math.PI + Math.PI * 0.1 * i);
|
||||
PILLAR_ANGLES[i] = i * (2.0 * Math.PI / 10.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +43,10 @@ public final class EndSpikeProtector implements Listener {
|
||||
private final AntiSeedCrackerPlugin plugin;
|
||||
private final NamespacedKey modifiedKey;
|
||||
|
||||
/** Worlds currently running a respawn-cycle poll loop, to avoid starting one per crystal
|
||||
* (all 4 respawn crystals fire {@link EntityPlaceEvent} independently). */
|
||||
private final Set<UUID> pollingWorlds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public EndSpikeProtector(AntiSeedCrackerPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.modifiedKey = new NamespacedKey(plugin, "asc_spike_modified");
|
||||
@@ -62,11 +69,16 @@ public final class EndSpikeProtector implements Listener {
|
||||
if (event.getEntityType() != EntityType.END_CRYSTAL) return;
|
||||
World world = event.getEntity().getWorld();
|
||||
if (!shouldProcess(world)) return;
|
||||
if (!plugin.getPluginConfig().isEndSpikesModifyWorld()) return;
|
||||
|
||||
Block placedOn = event.getBlock();
|
||||
if (placedOn == null || placedOn.getType() != Material.BEDROCK) return;
|
||||
if (Math.abs(placedOn.getX()) > 3 || Math.abs(placedOn.getZ()) > 3) return;
|
||||
|
||||
// All 4 respawn crystals fire this event independently within the same tick or two;
|
||||
// without this guard each one would start its own redundant 300-attempt poll loop.
|
||||
if (!pollingWorlds.add(world.getUID())) return;
|
||||
|
||||
world.getPersistentDataContainer().set(modifiedKey, PersistentDataType.BOOLEAN, false);
|
||||
schedulePollForRespawnEnd(world, 0);
|
||||
}
|
||||
@@ -78,13 +90,16 @@ public final class EndSpikeProtector implements Listener {
|
||||
}
|
||||
|
||||
private void scheduleModification(World world) {
|
||||
// Checked before touching the PDC at all: if this flag were set while modify_world
|
||||
// was off, turning modify_world on later would find "already modified" already true
|
||||
// and permanently skip the shuffle for this world.
|
||||
if (!plugin.getPluginConfig().isEndSpikesModifyWorld()) return;
|
||||
|
||||
Boolean alreadyModified = world.getPersistentDataContainer()
|
||||
.getOrDefault(modifiedKey, PersistentDataType.BOOLEAN, false);
|
||||
if (alreadyModified) return;
|
||||
world.getPersistentDataContainer().set(modifiedKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
if (!plugin.getPluginConfig().isEndSpikesModifyWorld()) return;
|
||||
|
||||
int[] heights = Arrays.copyOf(VANILLA_HEIGHTS, VANILLA_HEIGHTS.length);
|
||||
for (int i = heights.length - 1; i > 0; i--) {
|
||||
int j = RNG.nextInt(i + 1);
|
||||
@@ -121,6 +136,7 @@ public final class EndSpikeProtector implements Listener {
|
||||
|
||||
private void schedulePollForRespawnEnd(World world, int attemptCount) {
|
||||
if (attemptCount > 300) {
|
||||
pollingWorlds.remove(world.getUID());
|
||||
scheduleModification(world);
|
||||
return;
|
||||
}
|
||||
@@ -133,6 +149,7 @@ public final class EndSpikeProtector implements Listener {
|
||||
if (summoning) {
|
||||
schedulePollForRespawnEnd(world, attemptCount + 1);
|
||||
} else {
|
||||
pollingWorlds.remove(world.getUID());
|
||||
scheduleModification(world);
|
||||
}
|
||||
}, 20L);
|
||||
|
||||
@@ -39,6 +39,7 @@ public final class EyeOfEnderProtector implements Listener {
|
||||
UUID shooterUUID = shooterPlayer != null ? shooterPlayer.getUniqueId() : null;
|
||||
|
||||
final int[] fakeTarget;
|
||||
final int targetY;
|
||||
if (shooterUUID != null) {
|
||||
int[] stored = plugin.getSeedManager().getFakeStronghold(shooterUUID);
|
||||
if (stored != null) {
|
||||
@@ -50,6 +51,7 @@ public final class EyeOfEnderProtector implements Listener {
|
||||
plugin.getPluginConfig().getFakeStrongholdMaxDist()
|
||||
);
|
||||
}
|
||||
targetY = plugin.getSeedManager().getFakeStrongholdY(shooterUUID, eye.getLocation().getBlockY());
|
||||
} else {
|
||||
int min = plugin.getPluginConfig().getFakeStrongholdMinDist();
|
||||
int max = plugin.getPluginConfig().getFakeStrongholdMaxDist();
|
||||
@@ -59,6 +61,9 @@ public final class EyeOfEnderProtector implements Listener {
|
||||
(int) Math.round(Math.cos(angle) * dist),
|
||||
(int) Math.round(Math.sin(angle) * dist)
|
||||
};
|
||||
int minY = world.getMinHeight() + 8;
|
||||
int maxY = Math.max(minY + 1, Math.min(world.getMaxHeight() - 8, minY + 72));
|
||||
targetY = minY + RNG.nextInt(maxY - minY);
|
||||
}
|
||||
|
||||
final int targetX = fakeTarget[0];
|
||||
@@ -90,6 +95,13 @@ public final class EyeOfEnderProtector implements Listener {
|
||||
jz * speed
|
||||
));
|
||||
|
||||
// Critical: vanilla EnderSignal re-accelerates toward its target location every
|
||||
// tick on its own, independent of whatever velocity we just set. That target was
|
||||
// already set to the REAL stronghold before this event ever fired (the entity is
|
||||
// constructed with it), so without this call the eye would curve back toward the
|
||||
// true bearing within a second or two regardless of the jitter above.
|
||||
eye.setTargetLocation(new Location(world, targetX + 0.5, targetY, targetZ + 0.5));
|
||||
|
||||
DatabaseManager db = plugin.getDatabaseManager();
|
||||
if (db != null && plugin.getPluginConfig().isDatabaseLogEvents()
|
||||
&& shooterPlayer != null) {
|
||||
@@ -120,6 +132,7 @@ public final class EyeOfEnderProtector implements Listener {
|
||||
eye.getVelocity().getY(),
|
||||
(ndx * sin + ndz * cos) * speed
|
||||
));
|
||||
eye.setTargetLocation(new Location(world, targetX + 0.5, targetY, targetZ + 0.5));
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,11 +54,19 @@ public final class PlayerSessionListener implements Listener {
|
||||
|
||||
public void initPlayer(Player player) {
|
||||
plugin.getSeedManager().assignSeed(player);
|
||||
plugin.getSeedManager().assignFakeStronghold(
|
||||
player,
|
||||
plugin.getPluginConfig().getFakeStrongholdMinDist(),
|
||||
plugin.getPluginConfig().getFakeStrongholdMaxDist()
|
||||
);
|
||||
|
||||
// Only assign on first sight of this player. initPlayer() also re-runs for every
|
||||
// already-online player on /asc reload (and /asc toggle, which reloads internally) -
|
||||
// unconditionally reassigning here would silently move an online player's fake
|
||||
// stronghold on every unrelated config change, defeating the very answer-consistency
|
||||
// /locate and Eye of Ender redirection depend on to not tip off a suspicious player.
|
||||
if (!plugin.getSeedManager().hasFakeStronghold(player.getUniqueId())) {
|
||||
plugin.getSeedManager().assignFakeStronghold(
|
||||
player,
|
||||
plugin.getPluginConfig().getFakeStrongholdMinDist(),
|
||||
plugin.getPluginConfig().getFakeStrongholdMaxDist()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
|
||||
@@ -68,17 +68,22 @@ public final class MapSeedInterceptor extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches against the real vanilla map decoration type IDs (verified against
|
||||
* {@code org.bukkit.map.MapCursor.Type}: no "treasure", "pillager", or "bastion" type
|
||||
* exists at all — those three never matched anything. Buried treasure maps actually use
|
||||
* {@code TARGET_X}, which was previously missing here, so buried treasure locations
|
||||
* (one of this plugin's own named SeedCrackerX signals) were never actually scrambled.
|
||||
*/
|
||||
private static boolean isStructureDecoration(MapDecorationType type) {
|
||||
try {
|
||||
String name = type.getName().toString().toUpperCase();
|
||||
return name.contains("MANSION")
|
||||
|| name.contains("MONUMENT")
|
||||
|| name.contains("TARGET_X")
|
||||
|| name.contains("RED_X")
|
||||
|| name.contains("TREASURE")
|
||||
|| name.contains("TRIAL_CHAMBERS")
|
||||
|| name.contains("VILLAGE")
|
||||
|| name.contains("PILLAGER")
|
||||
|| name.contains("BASTION")
|
||||
|| name.contains("JUNGLE_TEMPLE")
|
||||
|| name.contains("SWAMP_HUT");
|
||||
} catch (Exception ignored) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.sunsetrqsv.antiseedcracker.scheduler;
|
||||
|
||||
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
import me.sunsetrqsv.antiseedcracker.util.PlatformUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
@@ -15,13 +16,9 @@ public final class FoliaSchedulerUtil {
|
||||
public static CancelableTask scheduleAsyncRepeating(Plugin plugin, Runnable task,
|
||||
long initialDelayMs, long periodMs) {
|
||||
if (PlatformUtil.IS_PAPER) {
|
||||
Object handle = plugin.getServer().getAsyncScheduler()
|
||||
ScheduledTask handle = plugin.getServer().getAsyncScheduler()
|
||||
.runAtFixedRate(plugin, t -> task.run(), initialDelayMs, periodMs, TimeUnit.MILLISECONDS);
|
||||
return () -> {
|
||||
try {
|
||||
handle.getClass().getMethod("cancel").invoke(handle);
|
||||
} catch (Exception ignored) {}
|
||||
};
|
||||
return handle::cancel;
|
||||
}
|
||||
long delayTicks = Math.max(1L, initialDelayMs / 50L);
|
||||
long periodTicks = Math.max(1L, periodMs / 50L);
|
||||
@@ -113,13 +110,9 @@ public final class FoliaSchedulerUtil {
|
||||
long initialDelayTicks,
|
||||
long periodTicks) {
|
||||
if (PlatformUtil.IS_PAPER) {
|
||||
Object handle = plugin.getServer().getGlobalRegionScheduler()
|
||||
ScheduledTask handle = plugin.getServer().getGlobalRegionScheduler()
|
||||
.runAtFixedRate(plugin, t -> task.run(), initialDelayTicks, periodTicks);
|
||||
return () -> {
|
||||
try {
|
||||
handle.getClass().getMethod("cancel").invoke(handle);
|
||||
} catch (Exception ignored) {}
|
||||
};
|
||||
return handle::cancel;
|
||||
}
|
||||
org.bukkit.scheduler.BukkitTask bt = plugin.getServer().getScheduler()
|
||||
.runTaskTimer(plugin, task, initialDelayTicks, periodTicks);
|
||||
|
||||
@@ -47,19 +47,44 @@ public final class SeedManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a fake stronghold (x, y, z) to the player, replacing any previous one.
|
||||
* The y is a plausible stronghold-band height so {@link org.bukkit.entity.EnderSignal}
|
||||
* can be given a full 3D target location, not just a horizontal bearing.
|
||||
*/
|
||||
public int[] assignFakeStronghold(Player player, int minDist, int maxDist) {
|
||||
double angle = rng.nextDouble() * 2.0 * Math.PI;
|
||||
int dist = minDist + rng.nextInt(Math.max(1, maxDist - minDist + 1));
|
||||
int x = (int) Math.round(Math.cos(angle) * dist);
|
||||
int z = (int) Math.round(Math.sin(angle) * dist);
|
||||
fakeStrongholds.put(player.getUniqueId(), new long[]{x, z});
|
||||
|
||||
int minY = player.getWorld().getMinHeight() + 8;
|
||||
int maxY = Math.max(minY + 1, Math.min(player.getWorld().getMaxHeight() - 8, minY + 72));
|
||||
int y = minY + rng.nextInt(maxY - minY);
|
||||
|
||||
fakeStrongholds.put(player.getUniqueId(), new long[]{x, y, z});
|
||||
return new int[]{x, z};
|
||||
}
|
||||
|
||||
/** Horizontal (x, z) of the player's fake stronghold, or {@code null} if none assigned yet. */
|
||||
public int[] getFakeStronghold(UUID uuid) {
|
||||
long[] packed = fakeStrongholds.get(uuid);
|
||||
if (packed == null) return null;
|
||||
return new int[]{(int) packed[0], (int) packed[1]};
|
||||
return new int[]{(int) packed[0], (int) packed[2]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Y of the player's fake stronghold, for continuous Eye of Ender homing (see
|
||||
* {@code EyeOfEnderProtector}). Returns {@code fallback} if none has been assigned yet.
|
||||
*/
|
||||
public int getFakeStrongholdY(UUID uuid, int fallback) {
|
||||
long[] packed = fakeStrongholds.get(uuid);
|
||||
return packed != null ? (int) packed[1] : fallback;
|
||||
}
|
||||
|
||||
/** True if this player already has a fake stronghold assigned. */
|
||||
public boolean hasFakeStronghold(UUID uuid) {
|
||||
return fakeStrongholds.containsKey(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class SeedRotationTask implements Runnable {
|
||||
if (handle != null) {
|
||||
handle.cancel();
|
||||
}
|
||||
long periodMs = (plugin.getPluginConfig().getSeedRotationIntervalTicks() / 20L) * 1000L;
|
||||
long periodMs = plugin.getPluginConfig().getSeedRotationIntervalSeconds() * 1000L;
|
||||
handle = FoliaSchedulerUtil.scheduleAsyncRepeating(plugin, this, periodMs, periodMs);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,6 @@ structure_recon_monitor:
|
||||
enabled: true
|
||||
threshold: 6
|
||||
window_seconds: 900
|
||||
# Leave empty to use the built-in default list (shipwrecks, desert/jungle temples,
|
||||
# swamp huts, pillager outposts, ocean monuments, igloos, buried treasure, End
|
||||
# Cities, ancient cities — the structures SeedCrackerX-style tools accumulate bits
|
||||
# from). Deliberately excludes villages/mineshafts/ruined portals since those are
|
||||
# far too common to be a useful signal and would just generate noise.
|
||||
tracked_structures: []
|
||||
|
||||
eye_of_ender_protection:
|
||||
@@ -70,11 +65,6 @@ extra_blocked_commands:
|
||||
- "seedfind"
|
||||
- "worldseedinfo"
|
||||
|
||||
# Logs (never blocks — same non-punishing philosophy as structure_recon_monitor) when
|
||||
# a player triggers several blocked /seed-family commands in a short window: a pattern
|
||||
# consistent with a script or macro probing for a command-blocker bypass (trying many
|
||||
# command variants back to back) rather than a player who just typed /seed once out of
|
||||
# curiosity.
|
||||
command_probing_monitor:
|
||||
enabled: true
|
||||
threshold: 5
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: AntiSeedCracker
|
||||
version: '3.5.0'
|
||||
version: '3.5.0.2'
|
||||
main: me.sunsetrqsv.antiseedcracker.AntiSeedCrackerPlugin
|
||||
api-version: '26.1'
|
||||
folia-supported: true
|
||||
|
||||
Reference in new issue
Block a user