Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a793e8c69 | ||
|
|
3dfc1b71af | ||
|
|
f26b7d0f0d | ||
|
|
e7a5a14529 | ||
|
|
904faf1832 | ||
|
|
2e6fdb4e5b |
@@ -1,4 +1,5 @@
|
|||||||
.gradle/
|
.gradle/
|
||||||
|
.docker/
|
||||||
build/
|
build/
|
||||||
out/
|
out/
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# spigot-tree-feller agent entrypoint
|
||||||
|
|
||||||
|
The canonical stories, engineering guidance, and **all process documents** are in the private [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md).
|
||||||
|
|
||||||
|
Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/spigot-tree-feller/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/spigot-tree-feller/` stories. Also follow the parent workspace `AGENTS.md` when present.
|
||||||
|
|
||||||
|
For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-tree-feller/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access.
|
||||||
|
|
||||||
|
Development follows [Development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md): approved stories, failing tests, passing implementation, verification, then source/wiki commit and push. GitOps updates are committed locally **without pushing**; only [Do release](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/do-release.md) authorizes a reviewed GitOps push.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Tree Feller is a Spigot 26.2 plugin that lets Survival players earn animated automatic felling independently for each vanilla tree family.
|
Tree Feller is a Spigot 26.2 plugin that lets Survival players earn animated automatic felling independently for each vanilla tree family.
|
||||||
|
|
||||||
The approved behavior and implementation record are in the [OKF design bundle](design/index.md).
|
The approved behavior and implementation record are in the [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-tree-feller/index.md).
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -17,6 +17,40 @@ The approved behavior and implementation record are in the [OKF design bundle](d
|
|||||||
|
|
||||||
The plugin JAR is written to `build/libs/`.
|
The plugin JAR is written to `build/libs/`.
|
||||||
|
|
||||||
|
## Local Docker test server
|
||||||
|
|
||||||
|
Build the current plugin and start it on an isolated Spigot 26.2 server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/start-test-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect to `localhost:25565` as `WindMagi`, which the harness configures as a level-4 operator using its deterministic offline-mode UUID. The committed `docker/test-ops.json` fixture replaces local operator state on every startup so the offline UUID remains deterministic. The server uses offline mode for local convenience and binds only to the loopback interface.
|
||||||
|
|
||||||
|
The first startup may take several minutes while the image downloads and Spigot is prepared. Override the 15-minute wait or local RCON password when needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TREE_FELLER_START_TIMEOUT=1200 \
|
||||||
|
TREE_FELLER_RCON_PASSWORD='local-secret' \
|
||||||
|
./scripts/start-test-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Common lifecycle commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Follow server logs
|
||||||
|
docker compose -f compose.test.yml logs -f minecraft
|
||||||
|
|
||||||
|
# Stop while preserving the world and server configuration
|
||||||
|
docker compose -f compose.test.yml down
|
||||||
|
|
||||||
|
# Stop and permanently reset all generated local test state
|
||||||
|
docker compose -f compose.test.yml down
|
||||||
|
rm -rf .docker/minecraft
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-run the start script after code changes to rebuild the JAR and recreate the test container. Generated server state remains under `.docker/minecraft/` and is ignored by Git.
|
||||||
|
|
||||||
## Player commands
|
## Player commands
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -45,7 +79,7 @@ Administrative commands require `treefeller.admin`, granted to server operators
|
|||||||
- Crimson and warped fungi
|
- Crimson and warped fungi
|
||||||
- Giant red and brown mushrooms
|
- Giant red and brown mushrooms
|
||||||
|
|
||||||
Azalea-grown logs count as oak. Bamboo is not treated as a tree.
|
Azalea-grown logs count as oak. Unlock progress counts unstripped natural log and Nether stem materials even after the surrounding tree has been disrupted; wood, hyphae, stripped variants, and bamboo do not count. Player-placed qualifying materials are indistinguishable from generated materials in Spigot and therefore also count. Giant mushroom stems require enough cap context to identify their species.
|
||||||
|
|
||||||
## Configuration and state
|
## Configuration and state
|
||||||
|
|
||||||
@@ -57,7 +91,7 @@ Player preferences, administrative locks, progress, unlocks, and known names are
|
|||||||
|
|
||||||
Tree detection searches matching natural trunk blocks laterally and upward, never below the initiating chop, and requires corresponding foliage or caps. Search bounds prevent unbounded traversal. Leaves, caps, roots, vines, and decorations are not automatically broken.
|
Tree detection searches matching natural trunk blocks laterally and upward, never below the initiating chop, and requires corresponding foliage or caps. Search bounds prevent unbounded traversal. Leaves, caps, roots, vines, and decorations are not automatically broken.
|
||||||
|
|
||||||
Additional trunk blocks use Spigot's `Player.breakBlock` path so block-break cancellation, drops, experience, enchantments, and axe durability remain authoritative. Protection plugins should cancel `BlockBreakEvent` normally. A cancelled additional break stops the remaining felling.
|
Automatic felling supports Spigot 26.2 wooden, stone, copper, iron, golden, diamond, and netherite axes. Additional trunk blocks use Spigot's `Player.breakBlock` path so block-break cancellation, drops, experience, enchantments, and axe durability remain authoritative. Protection plugins should cancel `BlockBreakEvent` normally. A cancelled additional break stops the remaining felling.
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: tree-feller-test
|
||||||
|
|
||||||
|
services:
|
||||||
|
minecraft:
|
||||||
|
image: itzg/minecraft-server:java25
|
||||||
|
container_name: tree-feller-test
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:25565:25565"
|
||||||
|
environment:
|
||||||
|
EULA: "TRUE"
|
||||||
|
TYPE: SPIGOT
|
||||||
|
VERSION: "26.2"
|
||||||
|
MEMORY: 2G
|
||||||
|
ONLINE_MODE: "false"
|
||||||
|
OPS_FILE: /config/tree-feller-ops.json
|
||||||
|
OVERRIDE_OPS: "true"
|
||||||
|
ENABLE_RCON: "true"
|
||||||
|
RCON_PASSWORD: "${TREE_FELLER_RCON_PASSWORD:-tree-feller-local-test}"
|
||||||
|
VIEW_DISTANCE: "6"
|
||||||
|
SIMULATION_DISTANCE: "6"
|
||||||
|
SPAWN_PROTECTION: "0"
|
||||||
|
volumes:
|
||||||
|
- ./.docker/minecraft:/data
|
||||||
|
- ./build/libs/tree-feller-0.1.0-SNAPSHOT.jar:/plugins/TreeFeller.jar:ro
|
||||||
|
- ./docker/test-ops.json:/config/tree-feller-ops.json:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc-health"]
|
||||||
|
start_period: 15m
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 90
|
||||||
|
restart: "no"
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
okf_version: "0.1"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Tree Feller Design
|
|
||||||
|
|
||||||
This bundle documents Tree Feller, a Spigot 26.2 plugin through which players earn per-species automatic tree felling, watch trees break progressively, and safely undo their most recent felling.
|
|
||||||
|
|
||||||
## Explore
|
|
||||||
|
|
||||||
- [User stories](user-stories/index.md)
|
|
||||||
- [Design log](log.md)
|
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
# Tree Feller Design Log
|
|
||||||
|
|
||||||
## 2026-08-11
|
|
||||||
|
|
||||||
### US-009 build and release completed
|
|
||||||
|
|
||||||
- Added maintainer documentation for requirements, builds, commands, configuration, supported species, persistence, safety, compatibility, and releases.
|
|
||||||
- Verified workflow YAML, pull-request conventional-commit validation, development artifacts, semantic versioning, and Gitea release attachment configuration against `../spigot-getgud/`.
|
|
||||||
- Verified the complete strict test lifecycle and a release-version build with `./gradlew clean check jar -PreleaseVersion=1.2.3`, producing `build/libs/tree-feller-1.2.3.jar`.
|
|
||||||
- Verified every user story is done, the OKF bundle conforms, the repository has no remote, and no push or release was attempted.
|
|
||||||
|
|
||||||
### US-006 unlock announcements completed
|
|
||||||
|
|
||||||
- Verified that earned unlocks alone produce the configurable achievement title and safety guidance exactly once per earned cycle.
|
|
||||||
- Administrative grants now send a distinct online chat notification without presenting a mined-block achievement or title.
|
|
||||||
- Verified reset-and-reearn behavior, invalid-event suppression, administrative distinction, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-007 administration completed
|
|
||||||
|
|
||||||
- Added the dedicated permission-gated `/treefelleradmin` tree for player status, species grants and resets, global player felling locks, and persistent per-species thresholds.
|
|
||||||
- Added exact live-name, durable-alias, and UUID target resolution with ambiguity rejection and UUID authority.
|
|
||||||
- Added positional, permission-aware completion for roots, known players, properties, species identifiers, actions, and boolean values.
|
|
||||||
- Administrative mutations are idempotent, persist before reporting success, preserve unrelated state, and notify online targets without using earned-achievement titles.
|
|
||||||
- Administratively locked players continue manual progress, receive a configurable explanation when felling is suppressed, and retain their preference and unlocks.
|
|
||||||
- Verified grants, resets, locks, thresholds, identity safety, online messaging, autocomplete, authorization, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-006 unlock announcement checkpoint
|
|
||||||
|
|
||||||
- Added configurable, placeholder-aware titles, subtitles, timing, and chat guidance for newly earned species.
|
|
||||||
- Progress presentation now removes the completed boss bar before showing the one-time achievement and guidance about sneaking and undo.
|
|
||||||
- Verified earned and ordinary progress paths plus the complete build with `./gradlew clean check jar`.
|
|
||||||
- US-006 remains in progress until US-007 verifies distinct online messaging for administrative grants.
|
|
||||||
|
|
||||||
### US-005 safe undo completed
|
|
||||||
|
|
||||||
- Added one runtime-only latest-felling record per player with original world, coordinates, material, block-data string, and completion time.
|
|
||||||
- Added `/treefeller undo` behind the separate `treefeller.undo` permission and the configurable six-minute default window.
|
|
||||||
- Undo now preflights worlds, loaded chunks, empty target positions, exact block data, and aggregate inventory materials before making any change.
|
|
||||||
- Missing-material failures report every material and quantity; successful undo withdraws materials once, restores orientation-aware block data without physics, and consumes the record.
|
|
||||||
- Unexpected inventory or world failures roll back changed blocks and inventory where possible and preserve the record while logging suppressed recovery failures for administrators.
|
|
||||||
- Verified shortages, occupied locations, expiration, successful restoration, command reporting, permissions, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-003 animated tree felling completed
|
|
||||||
|
|
||||||
- Added eligibility-aware automatic felling for unlocked species with sneaking, saved preference, and administrative-lock bypasses.
|
|
||||||
- Added deterministic bottom-to-top scheduling at the configured interval, per-player and per-block overlap claims, and exact runtime snapshots of successfully removed blocks.
|
|
||||||
- Routed each additional trunk through `Player.breakBlock` so Spigot protection cancellation, drops, experience, enchantments, axe durability, and tool breakage remain authoritative.
|
|
||||||
- Felling now stops on cancellation, tool loss, state invalidation, logout, world unload, plugin disablement, changed blocks, or unloaded chunks and releases every runtime claim.
|
|
||||||
- Verified ordering, delay, overlap rejection, cancellation, eligibility, lifecycle safety, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-002 progress visibility completed
|
|
||||||
|
|
||||||
- Added `/treefeller unlocked` with every species' durable unlocked state or current count and live threshold.
|
|
||||||
- Added one configurable boss bar per player with species and numeric progress, live threshold evaluation, timeout replacement, and five-second default cleanup.
|
|
||||||
- Suppressed progress presentation for all ineligible events and removed it immediately when a species unlocks.
|
|
||||||
- Verified command output, player-only completion, boss-bar presentation and timeout, cleanup, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-004 personal controls completed
|
|
||||||
|
|
||||||
- Added `/treefeller enabled [on|off]` with durable, idempotent preference changes and administrative-override reporting.
|
|
||||||
- Added player-only usage and positional completion for `enabled`, `unlocked`, `undo`, and boolean values without exposing the administrative command tree.
|
|
||||||
- Registered configurable player messages and kept `treefeller.command` separate from `treefeller.admin`.
|
|
||||||
- Verified state preservation, reporting, autocomplete, metadata, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-001 species unlock progression completed
|
|
||||||
|
|
||||||
- Added stable taxonomy for all approved overworld trees, Nether fungi, and giant mushrooms while excluding bamboo and treating azalea logs as oak.
|
|
||||||
- Replaced the old recursive search with a deterministic, bounded, iterative scanner that follows connected trunk blocks laterally and upward but never downward and requires matching foliage or caps.
|
|
||||||
- Added Survival-and-axe eligibility, automatic-break suppression, durable one-point increments, saturating counters, permanent unlocks, and next-qualifying-block threshold evaluation.
|
|
||||||
- Registered progress handling through the Spigot block-break lifecycle and persisted every accepted update before notifying observers.
|
|
||||||
- Verified taxonomy, detection, tools, eligibility, progression, persistence, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### US-008 configuration and persistence completed
|
|
||||||
|
|
||||||
- Added validated settings for all supported species, search safety, animation, progress presentation, undo, titles, and messages.
|
|
||||||
- Added persistence-before-activation threshold changes and failure-safe active settings.
|
|
||||||
- Added UUID-keyed immutable player state with retained names, saturating progress, unlocks, preferences, locks, defensive reads, forward-field retention, and atomic YAML replacement.
|
|
||||||
- Invalid required configuration now disables partial plugin startup with a focused log message.
|
|
||||||
- Verified settings, persistence, corruption handling, atomic replacement, and the complete build with `./gradlew clean check jar`.
|
|
||||||
|
|
||||||
### Build foundation checkpoint
|
|
||||||
|
|
||||||
- Added the Java 17 Gradle project, Spigot 26.2 dependency, strict compilation, JUnit lifecycle, plugin metadata, wrapper, and Gitea CI and semantic-release workflows.
|
|
||||||
- Verified separate player and administrative command metadata through a failing-then-passing test.
|
|
||||||
- Verified the local foundation with `./gradlew clean check jar`; remote workflow and release criteria remain pending final delivery verification.
|
|
||||||
|
|
||||||
### Implementation started
|
|
||||||
|
|
||||||
- Began US-009 by establishing the test-first Gradle and plugin-metadata foundation.
|
|
||||||
- US-009 remains in progress until all stories and final delivery behavior have been verified.
|
|
||||||
|
|
||||||
### Initial tree-felling design
|
|
||||||
|
|
||||||
- Players earn automatic felling separately for each supported tree species by manually mining qualifying tree blocks in Survival with an axe.
|
|
||||||
- Each species defaults to a 100-block unlock threshold; lowered thresholds are evaluated on the player's next qualifying block, while earned unlocks are permanent unless an administrator resets them.
|
|
||||||
- Supported species cover Spigot 26.2 overworld trees, crimson and warped fungi, and giant red and brown mushrooms; bamboo is excluded.
|
|
||||||
- The prior `../yetanothertreefeller/` search strategy informs detection: connected trunk blocks are searched laterally and upward, never downward, and associated foliage or caps establish that the structure is a tree. The new implementation will use bounded iteration rather than unbounded recursion.
|
|
||||||
- Unlocked trees break from bottom to top, one trunk block every two ticks by default. Leaves and caps are not automatically removed.
|
|
||||||
- Sneaking bypasses automatic felling. Players may also persistently enable or disable it with `/treefeller enabled <on|off>`.
|
|
||||||
- The player's latest felling may be undone for six minutes by default. Undo preflights the complete operation, reclaims all required replacement materials from inventory, and reports shortages without making partial changes.
|
|
||||||
- Unlock progress is shown through `/treefeller unlocked` and a temporary boss bar. Unlocks produce a title and a chat explanation of sneaking and undo.
|
|
||||||
- Player commands use `/treefeller`; administration uses the separate `/treefelleradmin` command and `treefeller.admin` permission.
|
|
||||||
- Administrators can inspect players, grant or reset species, lock all automatic felling for a player, and persistently change species thresholds. Administrative locking does not stop manual progress.
|
|
||||||
- Build and release conventions follow `../spigot-getgud/`, including Spigot API `26.2-R0.1-SNAPSHOT`, Java 17, Gradle, strict compilation, tests, and Gitea automation.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Tree Feller User Stories
|
|
||||||
|
|
||||||
1. [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
2. [US-002: View tree progress](us-002-view-tree-progress.md)
|
|
||||||
3. [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
|
|
||||||
4. [US-004: Control personal tree felling](us-004-control-personal-tree-felling.md)
|
|
||||||
5. [US-005: Undo the last felled tree](us-005-undo-the-last-felled-tree.md)
|
|
||||||
6. [US-006: Announce tree unlocks](us-006-announce-tree-unlocks.md)
|
|
||||||
7. [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
8. [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
9. [US-009: Build and release Tree Feller](us-009-build-and-release-tree-feller.md)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-001: Earn tree-type unlocks"
|
|
||||||
description: Let players earn permanent automatic felling separately for each supported tree species.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-001: Earn tree-type unlocks
|
|
||||||
|
|
||||||
As a **survival player**, I want to unlock Tree Feller by practicing with each tree species so that automatic felling feels earned and species-specific.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] Progress is tracked independently for oak, spruce, birch, jungle, acacia, dark oak, mangrove, cherry, pale oak, crimson fungi, warped fungi, giant red mushrooms, and giant brown mushrooms as represented by Spigot 26.2 materials.
|
|
||||||
- [x] Azalea-grown oak logs contribute to oak progress, and bamboo does not contribute to any tree species.
|
|
||||||
- [x] A block contributes progress only when a player manually breaks it in Survival mode, with an axe, from a structure that passes Tree Feller's tree validation.
|
|
||||||
- [x] Creative-mode breaks, non-axe breaks, cancelled breaks, and blocks removed by automatic felling do not contribute progress.
|
|
||||||
- [x] Each qualifying manually mined block contributes exactly one point to its species.
|
|
||||||
- [x] Each species has an independently configurable unlock threshold that defaults to 100 blocks.
|
|
||||||
- [x] Reaching the active threshold permanently unlocks automatic felling for that species.
|
|
||||||
- [x] A threshold lowered below a player's saved progress grants the unlock when that player next mines a qualifying block of the species, not immediately when configuration changes.
|
|
||||||
- [x] Raising a threshold never removes an earned unlock.
|
|
||||||
- [x] A player whose automatic felling is disabled or administratively locked may continue earning progress through qualifying manual mining.
|
|
||||||
- [x] Progress and unlocks survive logout and server restart.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-002: View tree progress](us-002-view-tree-progress.md)
|
|
||||||
- [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
|
|
||||||
- [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-002: View tree progress"
|
|
||||||
description: Show players which species are unlocked and their progress toward the remaining unlocks.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-002: View tree progress
|
|
||||||
|
|
||||||
As a **player**, I want clear unlock and progress information so that I know which trees I can fell and what to work on next.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] `/treefeller unlocked` lists every supported species in a readable locked or unlocked state.
|
|
||||||
- [x] Each locked species includes the player's current qualifying-block count and active threshold.
|
|
||||||
- [x] Each unlocked species is clearly distinguished and is not presented as needing further progress.
|
|
||||||
- [x] Mining a qualifying block for a locked species displays or updates a boss bar with the species name and numeric progress toward its active threshold.
|
|
||||||
- [x] The boss bar reflects a changed threshold the next time qualifying progress is recorded.
|
|
||||||
- [x] The boss bar disappears after no qualifying block has been mined for five seconds by default.
|
|
||||||
- [x] Mining another qualifying block before timeout restarts the configured visibility period.
|
|
||||||
- [x] Boss-bar visibility duration, text, color, and style are configurable.
|
|
||||||
- [x] Progress feedback is not displayed for cancelled, ineligible, automatically felled, or already-unlocked blocks.
|
|
||||||
- [x] `/treefeller unlocked` and its autocomplete expose no administrative functionality.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
- [US-006: Announce tree unlocks](us-006-announce-tree-unlocks.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-003: Fell unlocked trees"
|
|
||||||
description: Safely and visibly break an unlocked tree's trunk from the mined block upward.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-003: Fell unlocked trees
|
|
||||||
|
|
||||||
As a **player with an unlocked species**, I want its trees to break progressively from the point I chop so that felling is useful, understandable, and visually satisfying.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] Automatic felling is considered only for a non-cancelled Survival-mode block break made with an axe against a species the player has unlocked.
|
|
||||||
- [x] Sneaking when the initiating block is broken always bypasses automatic felling and leaves the ordinary single-block break intact.
|
|
||||||
- [x] A disabled or administratively locked player receives the ordinary single-block break without automatic felling.
|
|
||||||
- [x] Tree detection follows connected blocks of the initiating trunk family laterally and upward, including diagonal branches, but never follows trunk blocks below the initiating block.
|
|
||||||
- [x] Detection requires foliage, wart blocks, or mushroom caps appropriate to the candidate species so that an unsupported log structure is not automatically felled.
|
|
||||||
- [x] Detection is iterative and bounded by configurable block and search-distance limits; reaching a safety limit aborts automatic felling without preventing the initiating ordinary break.
|
|
||||||
- [x] Neighbor visitation is deterministic and does not process a location more than once.
|
|
||||||
- [x] Only trunk, stem, or mushroom-stem blocks are felled; leaves, wart blocks, mushroom caps, roots, vines, and decorations remain for normal game behavior.
|
|
||||||
- [x] The initiating block is handled by the original break, and remaining discovered trunk blocks break bottom-to-top at a configurable delay of two server ticks per block by default.
|
|
||||||
- [x] Each additional block is checked through the applicable Bukkit block-break event path, and a cancellation prevents that block and any unsafe continuation from being broken.
|
|
||||||
- [x] Each successfully felled block produces drops and experience according to its block state, the active axe, enchantments, and the Spigot API rather than duplicating the initiating block's drops.
|
|
||||||
- [x] Axe durability, including Unbreaking behavior, is applied for every successfully felled block without double-charging the initiating break.
|
|
||||||
- [x] Felling stops safely before another block is processed when the axe breaks, is removed, or is no longer an eligible axe.
|
|
||||||
- [x] Logging out, plugin disablement, world unload, or another invalidated runtime condition cancels the remaining animation without breaking queued blocks.
|
|
||||||
- [x] A player cannot start overlapping automatic fellings that could double-break or double-drop the same blocks.
|
|
||||||
- [x] Only blocks actually removed by this felling are recorded for undo.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
- [US-004: Control personal tree felling](us-004-control-personal-tree-felling.md)
|
|
||||||
- [US-005: Undo the last felled tree](us-005-undo-the-last-felled-tree.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-004: Control personal tree felling"
|
|
||||||
description: Let players persistently enable or disable their own automatic tree felling.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-004: Control personal tree felling
|
|
||||||
|
|
||||||
As a **player**, I want to turn automatic felling on or off independently of my unlocks so that I remain in control of how I mine.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] `/treefeller enabled <on|off>` enables or disables automatic felling for the issuing player.
|
|
||||||
- [x] `/treefeller enabled` without a value reports the player's current preference and whether an administrative lock currently overrides it.
|
|
||||||
- [x] The preference defaults to enabled for a player with no saved value.
|
|
||||||
- [x] Changing the preference does not alter species progress or earned unlocks.
|
|
||||||
- [x] Repeating the currently saved value is idempotent and reports that no change was needed.
|
|
||||||
- [x] The player receives clear confirmation after a successful change.
|
|
||||||
- [x] The saved preference survives logout and server restart.
|
|
||||||
- [x] `/treefeller` provides concise usage for `enabled`, `unlocked`, and `undo` without advertising inaccessible administrative commands.
|
|
||||||
- [x] Position-aware autocomplete suggests player subcommands and valid `on` or `off` values.
|
|
||||||
- [x] Player commands use player permissions that are distinct from `treefeller.admin`; possession of player permissions does not grant `/treefelleradmin` access.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-002: View tree progress](us-002-view-tree-progress.md)
|
|
||||||
- [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
|
|
||||||
- [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-005: Undo the last felled tree"
|
|
||||||
description: Safely restore the player's most recent automatic felling without creating replacement materials.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-005: Undo the last felled tree
|
|
||||||
|
|
||||||
As a **player**, I want to undo my latest automatically felled tree so that I can recover from an accidental chop without duplicating its materials.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] `/treefeller undo` targets only the issuing player's most recent automatic felling.
|
|
||||||
- [x] An undo record contains only blocks successfully removed by the operation, including the initiating trunk block when its original state can be captured safely.
|
|
||||||
- [x] Each record retains the world, block coordinates, original block material, and original block data needed to restore orientation and other supported state.
|
|
||||||
- [x] Only one undo record is retained per player; a later felling replaces the earlier record after the later operation has removed at least one eligible tree block.
|
|
||||||
- [x] An undo remains available for six minutes after the felling by default, with a configurable duration.
|
|
||||||
- [x] Undo performs a complete preflight before changing the world or inventory.
|
|
||||||
- [x] Preflight requires every target world and chunk to be available and every target position to remain safely restorable; an occupied or otherwise unsafe position fails the whole undo.
|
|
||||||
- [x] Preflight calculates the exact replacement materials required to reconstruct all recorded trunk states and requires those aggregate materials in the issuing player's inventory.
|
|
||||||
- [x] If inventory is insufficient, no item or block changes occur and the error lists every missing material with its missing quantity.
|
|
||||||
- [x] On success, required materials are removed exactly once and all recorded blocks are restored as one logical operation.
|
|
||||||
- [x] If an unexpected restoration failure occurs after preflight, the implementation avoids a silent partial result and reports the recovery action needed to administrators.
|
|
||||||
- [x] A successful undo consumes the record so that it cannot be repeated.
|
|
||||||
- [x] An expired, absent, already-used, or invalid undo produces a clear message and makes no changes.
|
|
||||||
- [x] Undo restores trunk blocks only; it does not restore foliage, caps, roots, drops, experience, or axe durability.
|
|
||||||
- [x] Undo has a distinct player permission and never grants access to `/treefelleradmin`.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
|
|
||||||
- [US-004: Control personal tree felling](us-004-control-personal-tree-felling.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-006: Announce tree unlocks"
|
|
||||||
description: Celebrate each newly earned species and explain how to control or undo automatic felling.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-006: Announce tree unlocks
|
|
||||||
|
|
||||||
As a **player**, I want visible and actionable feedback when I unlock a species so that I notice the achievement and understand Tree Feller's safety controls.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] Earning a species unlock displays a configurable on-screen title and subtitle naming the species.
|
|
||||||
- [x] Earning a species unlock also sends a configurable chat message that explains that sneaking prevents automatic felling and names `/treefeller undo`.
|
|
||||||
- [x] Title text, subtitle text, fade-in time, display time, fade-out time, and chat text are configurable.
|
|
||||||
- [x] Messages support the project's chosen Spigot formatting convention and a documented species placeholder.
|
|
||||||
- [x] The boss bar for the newly unlocked species is removed when its unlock announcement is shown.
|
|
||||||
- [x] An earned species produces its unlock announcement exactly once unless an administrator later resets that species and the player earns it again.
|
|
||||||
- [x] An administrative grant clearly informs an online target that access was granted but does not falsely present it as a mined-block achievement.
|
|
||||||
- [x] Invalid or cancelled breaks never generate an unlock announcement.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
- [US-002: View tree progress](us-002-view-tree-progress.md)
|
|
||||||
- [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-007: Administer Tree Feller"
|
|
||||||
description: Give administrators separate, structured commands for player access, progress, and species thresholds.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-007: Administer Tree Feller
|
|
||||||
|
|
||||||
As a **server administrator**, I want a dedicated administrative command tree so that I can manage Tree Feller without granting ordinary players administrative authority.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] All administrative operations are rooted at `/treefelleradmin` rather than `/treefeller`.
|
|
||||||
- [x] Administrative commands require `treefeller.admin`, which server operators receive by default and ordinary player permissions never imply.
|
|
||||||
- [x] `/treefelleradmin player <name|uuid> status` reports identity, saved enabled preference, administrative lock, and each species' progress, threshold, and unlock state.
|
|
||||||
- [x] `/treefelleradmin player <name|uuid> tree <type> grant` grants that species permanently without altering unrelated species.
|
|
||||||
- [x] `/treefelleradmin player <name|uuid> tree <type> reset` removes that species' unlock and resets its progress to zero without altering unrelated species.
|
|
||||||
- [x] A reset player can earn the species again and receive its normal earned-unlock announcement.
|
|
||||||
- [x] `/treefelleradmin player <name|uuid> locked <on|off>` controls an override that prevents all automatic felling for the player without changing their preference, progress, or unlocks.
|
|
||||||
- [x] An administratively locked player may continue accruing qualifying manual progress and receives a clear explanation when automatic felling is suppressed.
|
|
||||||
- [x] `/treefelleradmin threshold <type> <blocks>` validates and persistently changes the named species' unlock threshold.
|
|
||||||
- [x] Lowering a threshold does not scan or immediately mutate all player records; each affected player unlocks on their next qualifying block of that species.
|
|
||||||
- [x] Raising a threshold never revokes existing unlocks.
|
|
||||||
- [x] Player targets resolve exact online names, previously known names, and UUIDs without confusing players who have used the same name.
|
|
||||||
- [x] Tree-type arguments use stable documented identifiers covering every supported species.
|
|
||||||
- [x] Autocomplete is permission-aware and suggests valid subcommands, known player targets, properties, tree types, actions, and values for the current argument position.
|
|
||||||
- [x] Every successful mutation reports exactly what changed to the administrator and, when online, the affected player.
|
|
||||||
- [x] Invalid or unauthorized requests make no partial state or configuration changes.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
- [US-004: Control personal tree felling](us-004-control-personal-tree-felling.md)
|
|
||||||
- [US-006: Announce tree unlocks](us-006-announce-tree-unlocks.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-008: Configure and persist Tree Feller"
|
|
||||||
description: Provide validated configuration and durable player state for predictable tree-felling behavior.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-008: Configure and persist Tree Feller
|
|
||||||
|
|
||||||
As a **server operator**, I want Tree Feller's behavior and player progression stored safely so that upgrades and restarts do not cause surprises or lost progress.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] Configuration defines supported species and their thresholds, each defaulting to 100 qualifying blocks.
|
|
||||||
- [x] Configuration defines an animation delay defaulting to two server ticks per felled block.
|
|
||||||
- [x] Configuration defines a progress boss-bar timeout defaulting to five seconds and an undo window defaulting to six minutes.
|
|
||||||
- [x] Configuration includes bounded tree-search limits, boss-bar presentation, title timing, and all player-facing messages.
|
|
||||||
- [x] Numeric settings reject zero, negative, overflowing, or operationally unsafe values using documented bounds.
|
|
||||||
- [x] Invalid required configuration prevents partial plugin initialization and produces a clear server log message identifying the setting.
|
|
||||||
- [x] Runtime threshold changes made through `/treefelleradmin` are persisted before becoming active and survive restart.
|
|
||||||
- [x] A failed runtime configuration write leaves the active threshold unchanged.
|
|
||||||
- [x] UUID-keyed player state persists latest known name, enabled preference, administrative lock, per-species progress, and earned unlocks.
|
|
||||||
- [x] Player identity resolution safely retains previously known names while UUID remains authoritative.
|
|
||||||
- [x] Player state is written atomically where supported so that a failed write does not replace valid state with a partial document.
|
|
||||||
- [x] Corrupt or invalid player records are handled defensively and cannot silently grant unlocks or administrative privileges.
|
|
||||||
- [x] Progress that exceeds a currently configured threshold is retained, and counters cannot overflow.
|
|
||||||
- [x] Unknown forward-compatible configuration and player-state fields are preserved where practical.
|
|
||||||
- [x] Undo records are runtime safety records and do not survive a server restart unless a later approved design explicitly adds durable undo.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-001: Earn tree-type unlocks](us-001-earn-tree-type-unlocks.md)
|
|
||||||
- [US-002: View tree progress](us-002-view-tree-progress.md)
|
|
||||||
- [US-003: Fell unlocked trees](us-003-fell-unlocked-trees.md)
|
|
||||||
- [US-005: Undo the last felled tree](us-005-undo-the-last-felled-tree.md)
|
|
||||||
- [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
- [US-009: Build and release Tree Feller](us-009-build-and-release-tree-feller.md)
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
type: User Story
|
|
||||||
title: "US-009: Build and release Tree Feller"
|
|
||||||
description: Give maintainers repeatable Spigot builds, automated verification, and versioned Gitea releases.
|
|
||||||
status: done
|
|
||||||
---
|
|
||||||
|
|
||||||
# US-009: Build and release Tree Feller
|
|
||||||
|
|
||||||
As a **plugin maintainer**, I want automated builds and releases modeled on `spigot-getgud` so that tested, correctly versioned Tree Feller artifacts can be distributed consistently.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- [x] The Gradle project compiles against Spigot API `26.2-R0.1-SNAPSHOT` using a Java 17 toolchain.
|
|
||||||
- [x] Compiler lint warnings fail the build.
|
|
||||||
- [x] Automated JUnit 5 tests run as part of the Gradle check lifecycle.
|
|
||||||
- [x] Pushes and pull requests build and test Tree Feller in Gitea Actions.
|
|
||||||
- [x] Pull requests validate conventional commit messages.
|
|
||||||
- [x] CI stores a development Tree Feller JAR as a workflow artifact.
|
|
||||||
- [x] Main-branch conventional commits drive semantic versioning.
|
|
||||||
- [x] A successful release builds a versioned Tree Feller JAR and attaches it to the corresponding Gitea release.
|
|
||||||
- [x] Build files, Gradle wrapper, workflows, release behavior, and Java dependency versions follow `../spigot-getgud/` where applicable while using Tree Feller names and identifiers.
|
|
||||||
- [x] Plugin metadata declares the player and administrative command trees with separate permissions.
|
|
||||||
- [x] The approved user-story bundle is committed before implementation begins.
|
|
||||||
- [x] Subsequent implementation follows test-driven development where practical and keeps story statuses and acceptance criteria synchronized with verified behavior.
|
|
||||||
- [x] No remote push is performed until the maintainer confirms that the Gitea repository and release token secret are ready.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [US-007: Administer Tree Feller](us-007-administer-tree-feller.md)
|
|
||||||
- [US-008: Configure and persist Tree Feller](us-008-configure-and-persist-tree-feller.md)
|
|
||||||
- [User-story catalog](index.md)
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"uuid": "6a3b6e9f-1a2f-380c-8a75-7a7ca6392c0e",
|
||||||
|
"name": "WindMagi",
|
||||||
|
"level": 4,
|
||||||
|
"bypassesPlayerLimit": false
|
||||||
|
}
|
||||||
|
]
|
||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
compose_file="$project_root/compose.test.yml"
|
||||||
|
wait_seconds="${TREE_FELLER_START_TIMEOUT:-900}"
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "Required command not found: docker" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo "Docker is not running or is not accessible." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
|
echo "Docker Compose v2 is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$project_root"
|
||||||
|
./gradlew clean check jar
|
||||||
|
mkdir -p .docker/minecraft
|
||||||
|
|
||||||
|
# Recreate the container so the image installs the newly built read-only plugin JAR.
|
||||||
|
docker compose -f "$compose_file" up -d --force-recreate
|
||||||
|
container_id="$(docker compose -f "$compose_file" ps -q minecraft)"
|
||||||
|
if [[ -z "$container_id" ]]; then
|
||||||
|
echo "The Tree Feller test container was not created." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Waiting up to ${wait_seconds}s for Spigot 26.2..."
|
||||||
|
deadline=$((SECONDS + wait_seconds))
|
||||||
|
while (( SECONDS < deadline )); do
|
||||||
|
container_state="$(docker inspect --format '{{.State.Status}}' "$container_id")"
|
||||||
|
health_state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
|
||||||
|
if [[ "$health_state" == "healthy" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [[ "$container_state" == "exited" || "$container_state" == "dead" ]]; then
|
||||||
|
docker compose -f "$compose_file" logs --tail=150 minecraft >&2
|
||||||
|
echo "The Minecraft server exited during startup." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
health_state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
|
||||||
|
if [[ "$health_state" != "healthy" ]]; then
|
||||||
|
docker compose -f "$compose_file" logs --tail=150 minecraft >&2
|
||||||
|
echo "Timed out waiting for Spigot 26.2 (status: $health_state)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
plugins="$(docker compose -f "$compose_file" exec -T minecraft rcon-cli plugins)"
|
||||||
|
if [[ "$plugins" != *"TreeFeller"* ]]; then
|
||||||
|
echo "$plugins" >&2
|
||||||
|
echo "TreeFeller was not reported by the running server." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Offline mode assigns a deterministic UUID that differs from the account's online UUID.
|
||||||
|
# Verify the exact identity instead of issuing `op WindMagi`, which can resolve online identity.
|
||||||
|
offline_uuid="6a3b6e9f-1a2f-380c-8a75-7a7ca6392c0e"
|
||||||
|
ops_json="$(docker compose -f "$compose_file" exec -T minecraft sh -lc 'cat /data/ops.json')"
|
||||||
|
if [[ "$ops_json" != *'"name": "WindMagi"'* || "$ops_json" != *"\"uuid\": \"${offline_uuid}\""* ]]; then
|
||||||
|
echo "$ops_json" >&2
|
||||||
|
echo "WindMagi's offline-mode UUID was not installed as an operator." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'MESSAGE'
|
||||||
|
Tree Feller test server is ready at localhost:25565.
|
||||||
|
Operator: WindMagi
|
||||||
|
View logs: docker compose -f compose.test.yml logs -f minecraft
|
||||||
|
Stop: docker compose -f compose.test.yml down
|
||||||
|
Reset: docker compose -f compose.test.yml down && rm -rf .docker/minecraft
|
||||||
|
MESSAGE
|
||||||
@@ -40,16 +40,19 @@ public final class TreeProgressListener implements Listener {
|
|||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if (event.isCancelled()
|
if (event.isCancelled()
|
||||||
|| player.getGameMode() != GameMode.SURVIVAL
|
|| player.getGameMode() != GameMode.SURVIVAL
|
||||||
|| !TreeTools.isAxe(player.getInventory().getItemInMainHand().getType())
|
|
||||||
|| automaticBreaks.isMarked(event.getBlock())) {
|
|| automaticBreaks.isMarked(event.getBlock())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Optional<TreeStructure> detected = detector.detect(event.getBlock());
|
Optional<TreeSpecies> classified = TreeTaxonomy.directSpecies(event.getBlock().getType());
|
||||||
if (detected.isEmpty()) {
|
if (classified.isEmpty()
|
||||||
|
&& event.getBlock().getType() == org.bukkit.Material.MUSHROOM_STEM) {
|
||||||
|
classified = detector.detect(event.getBlock()).map(TreeStructure::species);
|
||||||
|
}
|
||||||
|
if (classified.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeSpecies species = detected.orElseThrow().species();
|
TreeSpecies species = classified.orElseThrow();
|
||||||
PlayerTreeFellerState state = states.load(player.getUniqueId())
|
PlayerTreeFellerState state = states.load(player.getUniqueId())
|
||||||
.orElseGet(() -> PlayerTreeFellerState.initial(
|
.orElseGet(() -> PlayerTreeFellerState.initial(
|
||||||
player.getUniqueId(), player.getName()));
|
player.getUniqueId(), player.getName()));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public final class TreeTools {
|
|||||||
private static final Set<Material> AXES = EnumSet.of(
|
private static final Set<Material> AXES = EnumSet.of(
|
||||||
Material.WOODEN_AXE,
|
Material.WOODEN_AXE,
|
||||||
Material.STONE_AXE,
|
Material.STONE_AXE,
|
||||||
|
Material.COPPER_AXE,
|
||||||
Material.IRON_AXE,
|
Material.IRON_AXE,
|
||||||
Material.GOLDEN_AXE,
|
Material.GOLDEN_AXE,
|
||||||
Material.DIAMOND_AXE,
|
Material.DIAMOND_AXE,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class TreeFellingListenerTest {
|
|||||||
when(player.getName()).thenReturn("Player");
|
when(player.getName()).thenReturn("Player");
|
||||||
when(player.getGameMode()).thenReturn(GameMode.SURVIVAL);
|
when(player.getGameMode()).thenReturn(GameMode.SURVIVAL);
|
||||||
when(player.getInventory()).thenReturn(inventory);
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.IRON_AXE));
|
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.COPPER_AXE));
|
||||||
TreeStructure tree = new TreeStructure(
|
TreeStructure tree = new TreeStructure(
|
||||||
TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0), new BlockPoint(0, 1, 0)));
|
TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0), new BlockPoint(0, 1, 0)));
|
||||||
PlayerTreeFellerState eligible = PlayerTreeFellerState.initial(playerId, "Player")
|
PlayerTreeFellerState eligible = PlayerTreeFellerState.initial(playerId, "Player")
|
||||||
@@ -54,6 +54,34 @@ class TreeFellingListenerTest {
|
|||||||
verify(starter, org.mockito.Mockito.times(1)).start(any(), any(), any(), eq(2));
|
verify(starter, org.mockito.Mockito.times(1)).start(any(), any(), any(), eq(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aNonAxeCanEarnProgressButNeverStartsAutomaticFelling() {
|
||||||
|
UUID playerId = UUID.randomUUID();
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(player.getName()).thenReturn("Player");
|
||||||
|
when(player.getGameMode()).thenReturn(GameMode.SURVIVAL);
|
||||||
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
|
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.DIAMOND_PICKAXE));
|
||||||
|
PlayerStateStore states = mock(PlayerStateStore.class);
|
||||||
|
when(states.load(playerId)).thenReturn(Optional.of(
|
||||||
|
PlayerTreeFellerState.initial(playerId, "Player")
|
||||||
|
.withUnlocked(TreeSpecies.OAK, true)));
|
||||||
|
TreeFellingStarter starter = mock(TreeFellingStarter.class);
|
||||||
|
TreeFellingListener listener = new TreeFellingListener(
|
||||||
|
ignored -> Optional.of(new TreeStructure(
|
||||||
|
TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0)))),
|
||||||
|
states,
|
||||||
|
new AutomaticBreakRegistry(),
|
||||||
|
starter,
|
||||||
|
() -> 2);
|
||||||
|
|
||||||
|
listener.onBlockBreak(new BlockBreakEvent(mock(Block.class), player));
|
||||||
|
|
||||||
|
verify(starter, never()).start(any(), any(), any(), any(Integer.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void administrativeLockSuppressesFellingWithoutChangingTheOrdinaryBreak() {
|
void administrativeLockSuppressesFellingWithoutChangingTheOrdinaryBreak() {
|
||||||
UUID playerId = UUID.randomUUID();
|
UUID playerId = UUID.randomUUID();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
class TreeProgressListenerTest {
|
class TreeProgressListenerTest {
|
||||||
@Test
|
@Test
|
||||||
void recordsOnlyManualSurvivalAxeBreaksFromValidatedTrees() throws Exception {
|
void recordsNaturalTrunkMaterialAfterTheTreeStructureIsNoLongerIntact() throws Exception {
|
||||||
UUID playerId = UUID.randomUUID();
|
UUID playerId = UUID.randomUUID();
|
||||||
Player player = mock(Player.class);
|
Player player = mock(Player.class);
|
||||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||||
@@ -31,15 +31,15 @@ class TreeProgressListenerTest {
|
|||||||
when(player.getInventory()).thenReturn(inventory);
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.IRON_AXE));
|
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.IRON_AXE));
|
||||||
when(block.getWorld()).thenReturn(world);
|
when(block.getWorld()).thenReturn(world);
|
||||||
|
when(block.getType()).thenReturn(Material.OAK_LOG);
|
||||||
when(world.getUID()).thenReturn(UUID.randomUUID());
|
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||||
when(block.getX()).thenReturn(1);
|
when(block.getX()).thenReturn(1);
|
||||||
when(block.getY()).thenReturn(64);
|
when(block.getY()).thenReturn(64);
|
||||||
when(block.getZ()).thenReturn(2);
|
when(block.getZ()).thenReturn(2);
|
||||||
TreeStructure tree = new TreeStructure(TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0)));
|
|
||||||
InMemoryStateStore states = new InMemoryStateStore();
|
InMemoryStateStore states = new InMemoryStateStore();
|
||||||
AutomaticBreakRegistry automaticBreaks = new AutomaticBreakRegistry();
|
AutomaticBreakRegistry automaticBreaks = new AutomaticBreakRegistry();
|
||||||
TreeProgressListener listener = new TreeProgressListener(
|
TreeProgressListener listener = new TreeProgressListener(
|
||||||
ignored -> Optional.of(tree),
|
ignored -> Optional.empty(),
|
||||||
states,
|
states,
|
||||||
ignored -> 100,
|
ignored -> 100,
|
||||||
automaticBreaks,
|
automaticBreaks,
|
||||||
@@ -50,9 +50,12 @@ class TreeProgressListenerTest {
|
|||||||
automaticBreaks.mark(block);
|
automaticBreaks.mark(block);
|
||||||
listener.onBlockBreak(event);
|
listener.onBlockBreak(event);
|
||||||
automaticBreaks.unmark(block);
|
automaticBreaks.unmark(block);
|
||||||
|
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.AIR));
|
||||||
|
listener.onBlockBreak(event);
|
||||||
|
when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.DIAMOND_PICKAXE));
|
||||||
listener.onBlockBreak(event);
|
listener.onBlockBreak(event);
|
||||||
|
|
||||||
assertEquals(1, states.state.progress(TreeSpecies.OAK));
|
assertEquals(2, states.state.progress(TreeSpecies.OAK));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class InMemoryStateStore implements PlayerStateStore {
|
private static final class InMemoryStateStore implements PlayerStateStore {
|
||||||
|
|||||||
@@ -26,5 +26,8 @@ class TreeTaxonomyTest {
|
|||||||
expected.forEach((material, species) ->
|
expected.forEach((material, species) ->
|
||||||
assertEquals(species, TreeTaxonomy.directSpecies(material).orElseThrow()));
|
assertEquals(species, TreeTaxonomy.directSpecies(material).orElseThrow()));
|
||||||
assertTrue(TreeTaxonomy.directSpecies(Material.BAMBOO).isEmpty());
|
assertTrue(TreeTaxonomy.directSpecies(Material.BAMBOO).isEmpty());
|
||||||
|
assertTrue(TreeTaxonomy.directSpecies(Material.OAK_WOOD).isEmpty());
|
||||||
|
assertTrue(TreeTaxonomy.directSpecies(Material.STRIPPED_OAK_LOG).isEmpty());
|
||||||
|
assertTrue(TreeTaxonomy.directSpecies(Material.CRIMSON_HYPHAE).isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class TreeToolsTest {
|
|||||||
void acceptsAxesButNotPickaxesOrOtherItems() {
|
void acceptsAxesButNotPickaxesOrOtherItems() {
|
||||||
assertTrue(TreeTools.isAxe(Material.WOODEN_AXE));
|
assertTrue(TreeTools.isAxe(Material.WOODEN_AXE));
|
||||||
assertTrue(TreeTools.isAxe(Material.GOLDEN_AXE));
|
assertTrue(TreeTools.isAxe(Material.GOLDEN_AXE));
|
||||||
|
assertTrue(TreeTools.isAxe(Material.COPPER_AXE));
|
||||||
assertTrue(TreeTools.isAxe(Material.NETHERITE_AXE));
|
assertTrue(TreeTools.isAxe(Material.NETHERITE_AXE));
|
||||||
assertFalse(TreeTools.isAxe(Material.DIAMOND_PICKAXE));
|
assertFalse(TreeTools.isAxe(Material.DIAMOND_PICKAXE));
|
||||||
assertFalse(TreeTools.isAxe(Material.AIR));
|
assertFalse(TreeTools.isAxe(Material.AIR));
|
||||||
|
|||||||
Reference in New Issue
Block a user