Tags
Creators
Details
1.10.0
Compatibility
Changes
What's New
Added
- Permission-aware tab completion for
/stormtrooperx. The first argument completes to only the subcommands the sender may actually run; foroptout/optin, the second argument completes to online player names — but only for admins holdingstormtrooperx.optout.others. Tab-completion pools are pre-sorted and computed once at startup. /stormtrooperx helplists only the commands the sender has permission to run, so a regular player no longer sees admin-only subcommands they can't use.- Idempotent
optoutand newoptin.optoutno longer toggles — it always sets opted-out (reporting "already opted out" instead of silently flipping).optinis the symmetric counterpart.toggleis retained for the flip behavior. - Admin opt-out management.
/stormtrooperx optout|optin <player>lets admins manage opt-out state for any online player, gated by the newstormtrooperx.optout.otherspermission. The target receives a chat notification of the change. stormtrooperx.adminpermission, required for/stormtrooperx reloadand grantingstormtrooperx.optout.othersvia permission children. The previous setup had no clean "give one player everything" knob.- Join-time opt-out reminder. When an opted-out player's state finishes loading from the database, they get a one-shot chat message confirming the status.
- PlaceholderAPI soft dependency. When PlaceholderAPI is present,
%stormtrooperx_optout%resolves totrue/falsefor the requesting player. The expansion persists across/papi reload. PAPI is a soft dependency only — the plugin loads and runs identically without it.
Changed
- Projectile math extracted. The speed-preserving direction perturbation now lives in a package-private
ProjectileNerf.perturb(Vector, double, Supplier<Vector>)with an injectable random source, making the pure-math invariants directly unit-testable without event mocking. Runtime behavior is unchanged. stormtrooperx.usedefault flipped fromoptotrueso non-op players can actually reach/stormtrooperx optout(itselfdefault: true). The old default made the public opt-out command unreachable for the audience it was meant for.- Ignores cancelled
EntityShootBowEvent.onBowShootnow declares@EventHandler(ignoreCancelled = true)— if an upstream plugin cancels the shoot event, we no longer perturb the (cancelled) projectile's velocity. - HikariCP MySQL pool sets the Connector/J performance properties from the HikariCP wiki:
useServerPrepStmts,useLocalSessionState,rewriteBatchedStatements,cacheResultSetMetadata,cacheServerConfiguration,elideSetAutoCommits,maintainTimeStats=false. No config change needed. - Hot-path micro-optimizations in
onBowShoot:lengthSquared()for the zero-velocity guard, cached projectile andisH2references, pattern-matchinginstanceof.
Fixed
- Folia regional-thread safety on
/stormtrooperx reload.entityConfigsanddebugare nowvolatile, andloadConfigurationpublishes a freshly-builtEnumMapin a single reference write instead of mutating the live map. The old code could expose a partially populated map to a regionalEntityShootBowEventthread during a reload.
Security
- CWE-117 log injection — release tags. Closed log-injection vectors in
UpdateChecker. GitHub release tags pass avalidateReleaseTagbarrier (^v?\d+(\.\d+){0,3}([-+][A-Za-z0-9.-]+)?$) before reaching any logger or version compare; malformed tags are dropped at the source. - CWE-117 log injection — sink-side allowlist. A new
sanitizeForLoghelper reduces logged version strings at the sink to[A-Za-z0-9._+-]via a negated-character-classreplaceAll— the form CodeQL recognizes as a sanitizer (a[\r\n]denylist does not satisfyjava/log-injection). - JDBC parameter smuggling.
DatabaseManagernow validates the MySQLpropertiesmap against an explicit allowlist (SAFE_MYSQL_PROPERTY_KEYS) and URL-encodes values. Unsupported keys throwIllegalArgumentExceptionatinitialize(). This closes a parameter-smuggling vector that could otherwise enable high-risk Connector/J flags (allowLoadLocalInfile,autoDeserialize,queryInterceptors, …) with RCE / file-read history. - Defense-in-depth Connector/J defaults. Even with the allowlist,
dataSourcePropertiesforcesallowLoadLocalInfile=false,allowUrlInLocalInfile=false,autoDeserialize=false, andallowPublicKeyRetrieval=falseat the HikariCP layer. - Chat-injection echo. Player names echoed by the offline branch of
/stormtrooperx optout|optin <player>are run through a newsanitizeNameForEcho(strips control chars and§, caps at 16 chars), closing a low-severity chat-injection vector on offline-mode servers. - AssertJ pinned to 3.27.7 (test-scope), clear of CVE-2026-24400 (GHSA-rqfh-9r24-8c9r — XXE in the unused
isXmlEqualTo).
Build
- CycloneDX SBOM. Added
cyclonedx-maven-pluginon theverifyphase;release.ymluploadsbom.xml/bom.jsonalongside the JAR. release.ymlactions pinned to commit SHAs (with# vX.Y.Ztrailers) instead of mutable major tags — the release job holdsid-token/attestations: writescope. Dependabot keeps the pins current.
Tests
- AssertJ added. Migrated assertions to fluent
assertThat(...)style for richer failure messages. Mockito 5.x + JUnit Jupiter 6.x stack is unchanged. - Branch-coverage gate. Jacoco now enforces
BRANCH COVEREDRATIO >= 0.50per package alongside the existing 0.60 line-coverage rule. Current: 72% line / 75% branch. - CI runs
mvn clean verifyinstead ofpackage, so the Jacoco gate (bound toverify) is enforced in CI — not just locally. - Shared test infrastructure. New
support/test package:TestSupport(reflective field/method access) andInlinePluginScheduler(shared inline scheduler), replacing duplicated boilerplate across test classes. - YAML fixtures under
src/test/resources/fixtures/(config-v2.yml,config-v3.yml);ConfigMigrationTestexercises real-load → migrate → assert instead of synthetic YamlConfiguration objects. - New
ProjectileNerfTestfor the extracted pure function — speed preservation, zero-velocity guard, mutation semantics, accuracy clamping. Plus expandedUpdateCheckerTestcoveringvalidateReleaseTagaccept/reject sets.
Dependencies
- Bumped
me.clip:placeholderapifrom2.11.6to2.12.2(providedscope, soft dependency — not shaded into the JAR).
Documentation
OptOutManager#isOptedOut,StormtrooperXExpansion, and the README PlaceholderAPI section all document the online-only semantics of%stormtrooperx_optout%: offline players resolve tofalseregardless of persisted state.DatabaseManager.initializeH2Javadoc now warns against enablingAUTO_SERVER=TRUE/ H2 server mode without revisiting the hardcodedsa/empty credentials.
Upgrade Notes
If upgrading from 1.9.x:
- Behavior change —
/stormtrooperx optout. Previously toggled; now it always sets opted-out. Use/stormtrooperx togglefor the old flip behavior. If you bind macros or scripts to the command, this is a one-line update. - Permission default change —
stormtrooperx.use. Default flipped fromoptotrueso theoptoutsubcommand (alwaysdefault: true) is reachable for the intended audience. Servers using a permissions plugin that explicitly grantedstormtrooperx.useto non-ops are unaffected; servers relying on the old default should explicitly denystormtrooperx.useto revert. - New permissions to wire up (admins):
stormtrooperx.admin(grantsreloadandoptout.othersvia children) andstormtrooperx.optout.others(grants admin opt-out commands for other players). Both default toop. - PlaceholderAPI is optional. To use
%stormtrooperx_optout%, install PlaceholderAPI; no config change required. - Config: No schema change.
config-version: 3continues to apply, no migration runs on upgrade.
Server compatibility: Unchanged — Spigot, Paper, Purpur, and Folia 1.20.4+, on Minecraft 1.18 through 26.1.x, Java 17+.
JAR verification: Built and attested via GitHub Actions with Sigstore. Verify with gh attestation verify StormtrooperX-1.10.0.jar --repo GooberCraft/StormtrooperX (requires gh 2.43.0+).
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:x4scZzGn:od9PgoEA"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:x4scZzGn:od9PgoEA"
}

