Compare commits
No commits in common. "5a6b2e2cac7ffd730850a674a4e91d34dbab729c" and "168bd197e90f95429b2cb4ea085e20b4d1703ad4" have entirely different histories.
5a6b2e2cac
...
168bd197e9
23 changed files with 229 additions and 811 deletions
|
|
@ -1,27 +0,0 @@
|
||||||
# AGENTS.md — Arena Mod
|
|
||||||
|
|
||||||
## Mission
|
|
||||||
Build a server-first NeoForge 1.21.1 PvE arena mod. The mod must remain usable without Cobblemon, EasyNPC, Create, or any boss mod.
|
|
||||||
|
|
||||||
## Working rules
|
|
||||||
1. `SPEC.md` is the functional source of truth. `ARCHITECTURE.md` is the technical source of truth.
|
|
||||||
2. Work only on the current unchecked phase in `TODO.md`, unless explicitly asked otherwise.
|
|
||||||
3. Inspect the existing project and dependency versions before coding. Never invent an API.
|
|
||||||
4. For optional integrations (Cobblemon/EasyNPC/third-party entities), verify the actual installed API/source before writing integration code.
|
|
||||||
5. Keep optional integrations isolated; absence of an optional mod MUST NOT prevent startup.
|
|
||||||
6. Prefer Minecraft/NeoForge registries and resource IDs (`namespace:path`) over hard-coded vanilla lists.
|
|
||||||
7. Content must be data-driven where specified. Invalid content disables only the affected definition and produces a useful diagnostic; it must not crash the server.
|
|
||||||
8. Inventory safety is critical. Never clear a player's original inventory until its recovery snapshot has been durably persisted.
|
|
||||||
9. Avoid unrelated refactors. Keep commits/changes scoped and easy to review.
|
|
||||||
10. Compile/test after each phase. Fix compilation errors before declaring a phase complete.
|
|
||||||
11. Add focused tests where practical, especially for pure logic (wave budgeting, rank thresholds, validation, state transitions).
|
|
||||||
12. If implementation must diverge from these docs, document the reason in `ARCHITECTURE.md` before proceeding.
|
|
||||||
13. Update `TODO.md` only for work actually completed.
|
|
||||||
|
|
||||||
## Definition of done for a phase
|
|
||||||
- Project compiles.
|
|
||||||
- Existing tests still pass.
|
|
||||||
- New behavior has a minimal test or reproducible manual test procedure.
|
|
||||||
- No optional dependency became mandatory.
|
|
||||||
- No known inventory-loss/duplication path was introduced.
|
|
||||||
- `TODO.md` reflects reality.
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
# Architecture — NeoForge 1.21.1 Arena
|
|
||||||
|
|
||||||
This document fixes responsibilities, not exact Java package names.
|
|
||||||
|
|
||||||
## Modules/components
|
|
||||||
### ArenaManager
|
|
||||||
Owns registered arena definitions/controllers and active sessions. V1 may enforce one active arena, but APIs should accept arena/session IDs.
|
|
||||||
|
|
||||||
### ArenaDefinition / ArenaControllerBlockEntity
|
|
||||||
Persistent spatial configuration: relative bounds, locations, tagged spawn points, water baseline/max, presentation/config metadata. Controller is the coordinate origin.
|
|
||||||
|
|
||||||
### ArenaSession
|
|
||||||
Runtime state machine. Holds immutable starting roster/config snapshot plus mutable participant states, wave state, lives, tracked entities, environment transaction and reward progress.
|
|
||||||
|
|
||||||
### ParticipantState
|
|
||||||
UUID, class, ready/eliminated status, remaining arena kit state, reputation progress, recovery reference.
|
|
||||||
|
|
||||||
### InventoryRecoveryService
|
|
||||||
Durable, idempotent recovery snapshots. Owns save-before-clear, restore-on-exit/login, unfinished-session recovery and anti-duplication state transitions. Keep this isolated and heavily tested.
|
|
||||||
|
|
||||||
### ContentRepository
|
|
||||||
Loads/reloads/validates JSON definitions: classes, ranks, themes, mob variants, bosses, progression/balance config. Registry-aware validation produces diagnostics and disabled-definition lists.
|
|
||||||
|
|
||||||
### ItemStackCodec / CaptureService
|
|
||||||
Uses Minecraft's supported ItemStack serialization/data-component mechanisms for target version. In-game capture normalizes durability only, preserves stack count and other components, then writes canonical JSON.
|
|
||||||
|
|
||||||
### WaveDirector
|
|
||||||
Pure-ish deterministic logic where possible. Inputs: wave index, target budget, eligible themes, recent theme history, player scaling, RNG seed. Output: planned composition. Does not directly spawn entities.
|
|
||||||
|
|
||||||
### SpawnService
|
|
||||||
Turns planned entries into entities at validated tagged spawn points. Supports equipment, attributes, scale, vehicles/passengers and session tagging.
|
|
||||||
|
|
||||||
### ArenaIsolationService
|
|
||||||
Event-driven boundary/protection rules: entry/exit, teleport containment, block break/place, explosions, endermen, targeting, projectile crossing, loot/XP, interaction rules.
|
|
||||||
|
|
||||||
### EnvironmentService
|
|
||||||
Transactional temporary water modifications and restoration. Persist enough recovery information to undo interrupted flooding.
|
|
||||||
|
|
||||||
### Reward/ReputationService
|
|
||||||
UUID reputation/ranks, wave validation, victory currency, admin configuration.
|
|
||||||
|
|
||||||
### Presentation/UI
|
|
||||||
Titles/action bar, intuitive registration/class/ready screens or interactions. Player commands are not required. Admin commands remain separate.
|
|
||||||
|
|
||||||
### Optional integrations
|
|
||||||
Separate compatibility adapters loaded only when the mod is present:
|
|
||||||
- Cobblemon: block/allow Pokémon actions according to arena rule.
|
|
||||||
- EasyNPC: optional hooks/commands/API bridge if verified and useful.
|
|
||||||
No Create dependency.
|
|
||||||
|
|
||||||
## Persistence
|
|
||||||
Use NeoForge/Minecraft-supported persistent data facilities appropriate to 1.21.1. Do not invent ad-hoc unsafe serialization when native codecs/registries are available.
|
|
||||||
|
|
||||||
Persist at least:
|
|
||||||
- controller/arena definition;
|
|
||||||
- reputation/ranks data as appropriate;
|
|
||||||
- inventory recovery records;
|
|
||||||
- enough active-session recovery marker to detect an interrupted session;
|
|
||||||
- temporary environment restoration data while modifications exist.
|
|
||||||
|
|
||||||
## State machine invariants
|
|
||||||
- Only registered participants may enter participant states.
|
|
||||||
- Inventory snapshot success precedes inventory clearing.
|
|
||||||
- Starting roster, average reputation tier, wave count and mode are frozen at countdown start.
|
|
||||||
- Session-created entities carry session identity.
|
|
||||||
- Cleanup is idempotent: running it twice must not duplicate rewards/items or damage the world.
|
|
||||||
- Reward payout is idempotent.
|
|
||||||
- Recovery is safe after abrupt server termination.
|
|
||||||
|
|
||||||
## Data-driven compatibility
|
|
||||||
Use ResourceLocations/registries for Item, EntityType, Attribute, enchantment/data component references supported by the target APIs. A definition may reference another mod without compile-time linkage.
|
|
||||||
|
|
||||||
## Networking
|
|
||||||
Keep authority server-side. Client packets are requests/UI actions only; server validates class unlocks, readiness, arena membership, admin permissions and configuration changes.
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
- Do not scan the entire arena every tick.
|
|
||||||
- Track session entities directly.
|
|
||||||
- Boundary checks should be event-driven where possible; low-frequency sanity checks may repair missed crossings.
|
|
||||||
- Water changes should be batched/throttled.
|
|
||||||
- Spawn validation should inspect only candidate locations/entity dimensions.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Minimal Codex prompts
|
|
||||||
|
|
||||||
## First run
|
|
||||||
Read `AGENTS.md`, `SPEC.md`, `ARCHITECTURE.md`, and `TODO.md`. Inspect the repository and implement **Phase 0 only**. Do not implement later phases. Compile/test, fix errors, then update `TODO.md` only for work actually completed.
|
|
||||||
|
|
||||||
## Subsequent run
|
|
||||||
Implement the next unchecked phase in `TODO.md`. Follow `AGENTS.md`; read only the relevant sections of `SPEC.md` and `ARCHITECTURE.md`, then inspect existing code before editing. Do not implement later phases. Compile/test, fix errors, and update `TODO.md` accurately.
|
|
||||||
|
|
||||||
## Bug-fix run
|
|
||||||
Investigate this bug against `SPEC.md` and `ARCHITECTURE.md`: <BUG>. Reproduce or identify the failing path first. Make the smallest safe fix, add a regression test when practical, compile/test, and do not refactor unrelated code.
|
|
||||||
|
|
||||||
## Integration run
|
|
||||||
Implement <INTEGRATION> only after inspecting the exact installed dependency/API version. Do not guess API names. Keep the integration optional and verify the mod still starts without that dependency.
|
|
||||||
213
.local/SPEC.md
213
.local/SPEC.md
|
|
@ -1,213 +0,0 @@
|
||||||
# Functional Specification — PvE Arena
|
|
||||||
|
|
||||||
Target: Minecraft Java 1.21.1, NeoForge. Server-first multiplayer PvE arena, initially designed for up to ~4 simultaneous participants.
|
|
||||||
|
|
||||||
## 1. Core principles
|
|
||||||
- One arena is required for V1, but core code SHOULD avoid an irreversible singleton design.
|
|
||||||
- A placed **Arena Controller** is the spatial reference for the arena.
|
|
||||||
- Arena geometry is builder-owned. The mod manages combat/session rules, not arena construction.
|
|
||||||
- Coordinates for arena points are stored relative to the controller.
|
|
||||||
- Player-facing flow MUST be intuitive and MUST NOT require player commands.
|
|
||||||
- Admin commands/tools MAY be used for setup and content editing.
|
|
||||||
- Vanilla and modded items/entities are referenced by registry IDs. Unknown IDs invalidate only the affected definition.
|
|
||||||
|
|
||||||
## 2. Arena Controller and bounds
|
|
||||||
The controller stores/configures:
|
|
||||||
- asymmetric 3D bounding box (`x-`, `x+`, `y-`, `y+`, `z-`, `z+`);
|
|
||||||
- lobby/hall location;
|
|
||||||
- participant entry/ready location(s);
|
|
||||||
- respawn location;
|
|
||||||
- exit location;
|
|
||||||
- boss spawn;
|
|
||||||
- tagged mob spawn points (`GROUND`, `WATER`, optional future tags);
|
|
||||||
- water baseline/max level;
|
|
||||||
- optional environment/redstone hooks.
|
|
||||||
|
|
||||||
Admin MUST be able to visualize bounds and configured points using temporary client-visible effects/particles. Bounds MUST be adjustable without rebuilding the arena.
|
|
||||||
|
|
||||||
Spawn validation MUST reject points that cannot fit the entity's actual dimensions (including scale), are obstructed, lack appropriate support/fluid, or are dangerously close to a participant. The director retries another compatible point.
|
|
||||||
|
|
||||||
## 3. Session lifecycle
|
|
||||||
Nominal states:
|
|
||||||
`IDLE -> REGISTRATION -> CLASS_SELECTION -> READY -> COUNTDOWN -> WAVES -> BOSS -> VICTORY -> CLEANUP -> IDLE`
|
|
||||||
|
|
||||||
Exceptional endings:
|
|
||||||
`DEFEAT`, `ABORTED`, `SERVER_RECOVERY` -> `CLEANUP`.
|
|
||||||
|
|
||||||
Flow:
|
|
||||||
1. Player registers.
|
|
||||||
2. Original player state is durably snapshotted.
|
|
||||||
3. Only after successful persistence, arena-controlled inventory/state is applied.
|
|
||||||
4. Player enters hall and chooses an unlocked class.
|
|
||||||
5. Player enters ready area and marks Ready.
|
|
||||||
6. When all registered participants are ready, registration closes and countdown starts.
|
|
||||||
7. Waves run.
|
|
||||||
8. Final boss encounter runs.
|
|
||||||
9. Victory/defeat/abort cleans all arena entities/environment and restores players.
|
|
||||||
|
|
||||||
Late joining after countdown is forbidden.
|
|
||||||
|
|
||||||
## 4. Inventory safety
|
|
||||||
This is a hard requirement.
|
|
||||||
- Persist recovery data BEFORE clearing/changing the player's original inventory.
|
|
||||||
- Snapshot at minimum: main inventory, hotbar, armor, offhand; include other player state only if implementation deliberately modifies it.
|
|
||||||
- On normal exit/elimination: remove arena kit, restore original snapshot, then mark recovery complete.
|
|
||||||
- On disconnect/crash: remove player from active session. If restoration cannot happen while offline, restore automatically on next login.
|
|
||||||
- On server restart with an unfinished session: cancel/recover the session. Online participants are moved outside and restored; offline participants are restored on next login.
|
|
||||||
- A failed snapshot MUST prevent arena entry and MUST NOT clear inventory.
|
|
||||||
- Restoration logic MUST be designed to avoid both loss and duplication.
|
|
||||||
|
|
||||||
## 5. Classes
|
|
||||||
Classes are JSON-defined and include:
|
|
||||||
- ID/display name;
|
|
||||||
- required reputation rank;
|
|
||||||
- armor slots, offhand and inventory;
|
|
||||||
- optional descriptive metadata.
|
|
||||||
|
|
||||||
Item definitions MUST support full `ItemStack` data/components so vanilla or modded items can retain enchantments, custom name/lore, dyes, banner/shield patterns, armor trims, attributes and other registered data components.
|
|
||||||
|
|
||||||
Admin editing:
|
|
||||||
- `addItemFromHand` (or equivalent) copies the full held stack into the class JSON.
|
|
||||||
- Captured stack quantity MUST equal held quantity for stackable items.
|
|
||||||
- Captured damage/durability MUST be normalized to fully repaired.
|
|
||||||
- Capture MUST NOT consume the admin's held item.
|
|
||||||
- Armor/offhand slot capture commands SHOULD exist.
|
|
||||||
- JSON remains the source of truth; in-game editing updates the JSON and revalidates/reloads the class.
|
|
||||||
- `/arena class give <class> [player]` gives a copy of the complete class kit for testing/display (e.g. armor racks), without registering a session.
|
|
||||||
|
|
||||||
Class choice locks when the player becomes Ready. Multiple players MAY choose the same class unless later configured otherwise.
|
|
||||||
|
|
||||||
On participant death with lives remaining:
|
|
||||||
- arena kit does not drop;
|
|
||||||
- consumed items are NOT restocked;
|
|
||||||
- durability/remaining stack counts are preserved;
|
|
||||||
- player respawns in the arena respawn zone with the same remaining kit state.
|
|
||||||
|
|
||||||
## 6. Reputation and ranks
|
|
||||||
- Reputation is permanent, per-player progression keyed by UUID.
|
|
||||||
- Currency is separate and spendable.
|
|
||||||
- Ranks are JSON-defined thresholds, e.g. Recruit/Fighter/Veteran/Champion.
|
|
||||||
- Classes SHOULD require a rank ID rather than duplicate numeric thresholds.
|
|
||||||
- Average reputation/rank of the starting group determines the session's configured wave-count tier. This is frozen at session start and never recalculated after disconnect/elimination.
|
|
||||||
- Reputation is earned per validated wave.
|
|
||||||
- In clear-to-progress mode, wave N validates when its required mobs are defeated.
|
|
||||||
- In timed-pressure mode, wave N validates when wave N+2 is reached; victory validates any remaining pending waves.
|
|
||||||
- Eliminated players keep reputation already earned.
|
|
||||||
|
|
||||||
## 7. Lives and modes
|
|
||||||
Keep "mode" separate from wave power/budget.
|
|
||||||
|
|
||||||
Default modes:
|
|
||||||
### Clear-to-progress (easy-style)
|
|
||||||
- Lives are individual.
|
|
||||||
- Next wave begins after all required arena mobs for current wave are defeated.
|
|
||||||
- Wave validation occurs on clear.
|
|
||||||
|
|
||||||
### Timed-pressure (hard-style)
|
|
||||||
- Lives are shared by the team.
|
|
||||||
- Each wave starts after a configured timer even if previous-wave enemies remain.
|
|
||||||
- Wave N reputation validates when N+2 is reached.
|
|
||||||
|
|
||||||
Exact life counts/timers/multipliers are configurable balancing values.
|
|
||||||
|
|
||||||
When a player has no applicable life remaining, they are eliminated:
|
|
||||||
- removed from session participation;
|
|
||||||
- teleported outside;
|
|
||||||
- original inventory restored.
|
|
||||||
Remaining participants continue. Defeat occurs when no active participants remain (subject to mode rules).
|
|
||||||
|
|
||||||
## 8. Wave Director
|
|
||||||
Wave difficulty is budget/score-driven, not encoded as `easy/hard` on themes.
|
|
||||||
|
|
||||||
Each theme JSON defines:
|
|
||||||
- ID/display name;
|
|
||||||
- selection weight;
|
|
||||||
- mob/variant pool;
|
|
||||||
- each entry: cost, weight, optional min/max, compatible spawn tags;
|
|
||||||
- optional environmental requirement such as water level.
|
|
||||||
|
|
||||||
For each wave:
|
|
||||||
1. Determine target score/budget from wave progression, player-count scaling and configurable session scaling.
|
|
||||||
2. Select a valid theme.
|
|
||||||
3. A theme used recently is excluded for the next 5 waves by default (`themeCooldown`, configurable).
|
|
||||||
4. If too few themes exist, gracefully relax cooldown using least-recently-used fallback.
|
|
||||||
5. Compose mobs until near target budget while respecting weights/min/max.
|
|
||||||
6. Spawn progressively through validated compatible spawn points.
|
|
||||||
|
|
||||||
Player-count scaling SHOULD primarily increase mob budget/count. Health scaling MAY increase modestly and MUST be separately configurable; avoid turning normal mobs into excessive HP sponges.
|
|
||||||
|
|
||||||
At wave start, participants receive a Title:
|
|
||||||
- title: `Vague <N>`
|
|
||||||
- subtitle: theme display name.
|
|
||||||
Action bar MAY show useful mode-specific status (remaining enemies or next-wave timer).
|
|
||||||
|
|
||||||
## 9. Mob variants, equipment, mounts
|
|
||||||
Mob definitions MAY include equipment/full item stack components and configurable registered attributes such as max health, attack damage, armor, movement and `minecraft:scale`.
|
|
||||||
|
|
||||||
Composite spawns MUST support vehicles/passengers (e.g. spider jockey, mounted skeleton). Treat a composite as one director selection with one total cost. Limit nesting depth.
|
|
||||||
|
|
||||||
Third-party registered entity IDs MAY be used without a compile-time dependency. Missing entity/item/attribute IDs invalidate the affected definition with diagnostics, not server startup.
|
|
||||||
|
|
||||||
## 10. Bosses
|
|
||||||
- Wither MAY be the default boss but is not mandatory.
|
|
||||||
- Boss definitions are data-driven and MAY reference vanilla or modded registered entities.
|
|
||||||
- Pseudo-bosses MAY apply name, scale, attributes, equipment, boss bar and optional adds/minions.
|
|
||||||
- Third-party boss compatibility is best-effort; arena containment and cleanup still apply.
|
|
||||||
- Boss rewards scale with completed session/wave tier.
|
|
||||||
- Optional boss mods must never become required dependencies.
|
|
||||||
|
|
||||||
## 11. Arena isolation and protection
|
|
||||||
During an active session, the arena bounding box is a logical sealed boundary.
|
|
||||||
- Participants cannot leave except through arena-controlled teleport/exit.
|
|
||||||
- Non-participant players cannot enter.
|
|
||||||
- Arena mobs cannot leave; outside mobs cannot enter.
|
|
||||||
- Projectiles/effects must not cross the boundary in either direction where technically interceptable; unsafe crossing projectiles should be cancelled/removed.
|
|
||||||
- Arena mobs SHOULD NOT target non-participants.
|
|
||||||
- Endermen cannot move arena blocks and cannot teleport outside.
|
|
||||||
- Explosions can retain combat effects but MUST NOT destroy arena blocks.
|
|
||||||
- Participants cannot place or break arena blocks.
|
|
||||||
- Existing arena interactions remain usable where safe: ladders, slime blocks, doors/trapdoors/buttons, wind charges, etc.
|
|
||||||
- Arena mobs SHOULD drop no normal loot/XP unless explicitly configured.
|
|
||||||
- All session-spawned entities (mobs, mounts, passengers, minions, relevant projectiles) are tagged/tracked by session and cleaned on end/recovery.
|
|
||||||
- Spectator stands may exist immediately outside the bounding box; spectators must not influence combat.
|
|
||||||
|
|
||||||
## 12. Water environment
|
|
||||||
No dynamic obstacle system in V1. Builders own obstacles.
|
|
||||||
|
|
||||||
Themes MAY request temporary arena flooding by 0..N block layers above a configured water baseline.
|
|
||||||
- Only safe/replaceable positions may be temporarily filled.
|
|
||||||
- Never overwrite permanent arena geometry blindly.
|
|
||||||
- Track every modified position needed for exact restoration.
|
|
||||||
- Water MAY rise/fall progressively for presentation.
|
|
||||||
- Restore on theme transition as designed, session end, abort and recovery.
|
|
||||||
- Recovery MUST handle server restart without leaving permanent unintended flooding.
|
|
||||||
- `WATER` spawn points become eligible when appropriate.
|
|
||||||
|
|
||||||
## 13. Cobblemon and other integrations
|
|
||||||
Cobblemon support is OPTIONAL.
|
|
||||||
- If absent, arena works normally.
|
|
||||||
- If present, arena rules can allow or block Pokémon use/sending/battling during a session.
|
|
||||||
- Integration code must be isolated and written only against verified APIs for the installed target version.
|
|
||||||
|
|
||||||
EasyNPC is OPTIONAL presentation/integration only. Core registration/readiness/session behavior must work without it.
|
|
||||||
Create is NOT a dependency. Builders may use Create/redstone around the arena independently.
|
|
||||||
|
|
||||||
## 14. Rewards and forfeit
|
|
||||||
- Currency reward is equal per qualifying starting participant; do not create a pot that grows for survivors when teammates are eliminated.
|
|
||||||
- Default: currency is awarded on final victory; amount depends on completed wave/session tier and configurable scaling.
|
|
||||||
- Reward item/currency can be configured in-game from a held ItemStack and persisted in configuration.
|
|
||||||
- Reputation is progression-based as described above.
|
|
||||||
- Collective forfeit is available through an in-chat vote among active participants.
|
|
||||||
- Default vote: absolute majority; solo = immediate. Duration/config is configurable.
|
|
||||||
- Successful forfeit -> ABORTED -> cleanup, exit and inventory restoration; no victory currency.
|
|
||||||
|
|
||||||
## 15. Validation philosophy
|
|
||||||
Provide useful startup/reload/admin diagnostics for:
|
|
||||||
- missing registry IDs;
|
|
||||||
- malformed class/theme/boss definitions;
|
|
||||||
- impossible min/max/cost values;
|
|
||||||
- missing required arena points;
|
|
||||||
- invalid bounds/spawn points;
|
|
||||||
- incompatible boss size warning.
|
|
||||||
|
|
||||||
Invalid content should be disabled locally whenever safe instead of crashing the server.
|
|
||||||
132
.local/TODO.md
132
.local/TODO.md
|
|
@ -1,132 +0,0 @@
|
||||||
# Implementation Plan
|
|
||||||
|
|
||||||
Do one phase at a time. Keep the project compiling after every phase.
|
|
||||||
|
|
||||||
- [x] **Phase 0 — Bootstrap**
|
|
||||||
- Confirm exact NeoForge/Minecraft/Java versions and mappings from the project.
|
|
||||||
- Create minimal mod entrypoint/config/logging.
|
|
||||||
- Establish packages and basic test setup.
|
|
||||||
- No gameplay yet.
|
|
||||||
- Completed: `arena` entrypoint, server config/startup logging, `com.shinuwa.arena`
|
|
||||||
and `config` packages; removed template gameplay/client examples.
|
|
||||||
- Versions confirmed: Minecraft 1.21.1, NeoForge 21.1.235, Java 21
|
|
||||||
(local JDK 21.0.8), NeoGradle 7.1.38, Gradle 9.2.1,
|
|
||||||
NeoForm 1.21.1-20240808.144430, Parchment 1.21.1 / 2024.11.17.
|
|
||||||
- Validation: `./gradlew build test --offline` succeeded; 3 JUnit 5.11.4
|
|
||||||
configuration tests passed. Rebuilt and inspected `arena-1.0.0.jar`:
|
|
||||||
correct metadata, no template content, only Minecraft/NeoForge dependencies.
|
|
||||||
- README documents build instructions and dedicated/integrated server smoke
|
|
||||||
tests. Full server/client startup remains a manual check, not executed here.
|
|
||||||
|
|
||||||
- [ ] **Phase 1 — Data model + content loading**
|
|
||||||
- Resource IDs, ranks, class definitions, themes, mob variants, bosses, balance/progression.
|
|
||||||
- JSON loading/reload and validation diagnostics.
|
|
||||||
- Add example built-in/default data.
|
|
||||||
- Unit-test pure validators where practical.
|
|
||||||
|
|
||||||
- [ ] **Phase 2 — Arena Controller + admin setup**
|
|
||||||
- Controller block/block entity.
|
|
||||||
- Relative asymmetric bounding box.
|
|
||||||
- Configurator/admin commands for bounds and points.
|
|
||||||
- Particle visualization of bounds/points.
|
|
||||||
- Tagged spawn points and validation diagnostics.
|
|
||||||
- Export/import arena definition if cleanly supportable.
|
|
||||||
|
|
||||||
- [ ] **Phase 3 — Inventory recovery foundation**
|
|
||||||
- Durable snapshot-before-clear transaction.
|
|
||||||
- Restore and idempotency.
|
|
||||||
- Restore-on-login for disconnected/offline players.
|
|
||||||
- Interrupted-session recovery marker.
|
|
||||||
- Test failure paths before adding full sessions.
|
|
||||||
|
|
||||||
- [ ] **Phase 4 — Session state machine**
|
|
||||||
- Registration, class selection, ready, countdown, active, victory/defeat/abort, cleanup.
|
|
||||||
- Starting roster/config freeze.
|
|
||||||
- Disconnect = remove participant + pending restoration.
|
|
||||||
- Server restart = cancel/recover.
|
|
||||||
- No waves yet; use simple admin-driven state transitions for tests.
|
|
||||||
|
|
||||||
- [ ] **Phase 5 — Classes + reputation**
|
|
||||||
- Class unlock by JSON rank.
|
|
||||||
- Full kit application.
|
|
||||||
- Death handling: no drops, preserve remaining kit state, respawn if life remains.
|
|
||||||
- Elimination restores original inventory.
|
|
||||||
- `addItemFromHand`/slot capture writes JSON, preserves components/count, repairs durability.
|
|
||||||
- `/arena class give`.
|
|
||||||
- Reputation persistence/ranks.
|
|
||||||
|
|
||||||
- [ ] **Phase 6 — Wave Director**
|
|
||||||
- Budget-driven composition.
|
|
||||||
- Theme weights, 5-wave default cooldown + graceful fallback.
|
|
||||||
- Entry cost/weight/min/max.
|
|
||||||
- Player-count budget scaling and modest configurable health scaling.
|
|
||||||
- Deterministic tests with seeded RNG.
|
|
||||||
- Wave Title/subtitle.
|
|
||||||
|
|
||||||
- [ ] **Phase 7 — Spawning + advanced mob variants**
|
|
||||||
- Spawn-point validation.
|
|
||||||
- Equipment/components/attributes/scale.
|
|
||||||
- Vehicles/passengers with nesting limit.
|
|
||||||
- Session entity tagging/tracking.
|
|
||||||
- Modded registry entity support without compile dependency.
|
|
||||||
|
|
||||||
- [ ] **Phase 8 — Modes, lives, progression**
|
|
||||||
- Clear-to-progress individual lives.
|
|
||||||
- Timed-pressure shared lives.
|
|
||||||
- N+2 reputation validation for timed mode.
|
|
||||||
- Wave count tier from frozen group average reputation.
|
|
||||||
- Action bar status.
|
|
||||||
- Defeat conditions.
|
|
||||||
|
|
||||||
- [ ] **Phase 9 — Arena isolation**
|
|
||||||
- Block break/place protection.
|
|
||||||
- Explosion terrain protection.
|
|
||||||
- Entity/player boundary entry/exit.
|
|
||||||
- Enderman restrictions.
|
|
||||||
- Projectile crossing.
|
|
||||||
- Targeting isolation.
|
|
||||||
- Loot/XP suppression.
|
|
||||||
- Cleanup of all tracked arena entities.
|
|
||||||
- Verify spectator stands outside bounds cannot influence session.
|
|
||||||
|
|
||||||
- [ ] **Phase 10 — Boss system + rewards**
|
|
||||||
- Data-driven vanilla/modded boss EntityType.
|
|
||||||
- Pseudo-boss attributes/scale/equipment/boss bar/adds.
|
|
||||||
- Victory detection.
|
|
||||||
- Equal per-player victory currency, scaled by completed tier.
|
|
||||||
- Held-item currency configuration.
|
|
||||||
- Idempotent payout.
|
|
||||||
|
|
||||||
- [ ] **Phase 11 — Water environment**
|
|
||||||
- Configurable baseline/max.
|
|
||||||
- Safe temporary fill for requested layers.
|
|
||||||
- Progressive batched rise/fall.
|
|
||||||
- Exact transactional restoration.
|
|
||||||
- Crash/restart recovery.
|
|
||||||
- WATER spawn integration.
|
|
||||||
|
|
||||||
- [ ] **Phase 12 — Player UX + forfeit**
|
|
||||||
- Intuitive registration/class/ready UX; no player commands required.
|
|
||||||
- Participant/class/ready recap.
|
|
||||||
- In-chat forfeit vote and timeout.
|
|
||||||
- Polish titles/action bar/errors.
|
|
||||||
- Accessibility/readability pass.
|
|
||||||
|
|
||||||
- [ ] **Phase 13 — Optional Cobblemon integration**
|
|
||||||
- Verify actual target Cobblemon API first.
|
|
||||||
- Optional adapter only.
|
|
||||||
- Configurable allow/block Pokémon use/battles during arena session.
|
|
||||||
- Test startup with Cobblemon absent and present.
|
|
||||||
|
|
||||||
- [ ] **Phase 14 — Optional EasyNPC bridge**
|
|
||||||
- Only if useful and actual API is verified.
|
|
||||||
- NPC may initiate/open arena services but must not own core logic.
|
|
||||||
- Test startup with EasyNPC absent.
|
|
||||||
|
|
||||||
- [ ] **Phase 15 — Hardening/release**
|
|
||||||
- Multiplayer tests (1–4 players).
|
|
||||||
- Disconnect/reconnect, kill server mid-session, abort, repeated cleanup.
|
|
||||||
- Duplication/loss tests.
|
|
||||||
- Invalid JSON/mod removed between restarts.
|
|
||||||
- Performance profiling with high mob counts.
|
|
||||||
- Admin documentation and example configs.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"id": "raider_lord",
|
|
||||||
"display_name": "Seigneur des Pillards",
|
|
||||||
"entity": "minecraft:ravager",
|
|
||||||
"spawn_tag": "BOSS",
|
|
||||||
"attributes": {
|
|
||||||
"minecraft:scale": 1.25,
|
|
||||||
"minecraft:max_health": 180.0,
|
|
||||||
"minecraft:armor": 12.0,
|
|
||||||
"minecraft:attack_damage": 16.0
|
|
||||||
},
|
|
||||||
"boss_bar": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"id": "warrior",
|
|
||||||
"display_name": "Guerrier",
|
|
||||||
"required_rank": "recruit",
|
|
||||||
"equipment": {
|
|
||||||
"head": {
|
|
||||||
"item": "minecraft:iron_helmet"
|
|
||||||
},
|
|
||||||
"chest": {
|
|
||||||
"item": "minecraft:iron_chestplate"
|
|
||||||
},
|
|
||||||
"legs": {
|
|
||||||
"item": "minecraft:iron_leggings"
|
|
||||||
},
|
|
||||||
"feet": {
|
|
||||||
"item": "minecraft:iron_boots"
|
|
||||||
},
|
|
||||||
"offhand": {
|
|
||||||
"item": "minecraft:shield"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inventory": [
|
|
||||||
{
|
|
||||||
"item": "minecraft:iron_sword",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "minecraft:cooked_beef",
|
|
||||||
"count": 16
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"theme_cooldown": 5,
|
|
||||||
"party_scaling": {
|
|
||||||
"1": 1.0,
|
|
||||||
"2": 1.75,
|
|
||||||
"3": 2.5,
|
|
||||||
"4": 3.25
|
|
||||||
},
|
|
||||||
"health_scaling": {
|
|
||||||
"1": 1.0,
|
|
||||||
"2": 1.1,
|
|
||||||
"3": 1.2,
|
|
||||||
"4": 1.3
|
|
||||||
},
|
|
||||||
"reputation_wave_tiers": [
|
|
||||||
{
|
|
||||||
"min_average_reputation": 0,
|
|
||||||
"wave_count": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_average_reputation": 500,
|
|
||||||
"wave_count": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_average_reputation": 1500,
|
|
||||||
"wave_count": 20
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"note": "Illustrative balancing values; tune during playtesting."
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"ranks": [
|
|
||||||
{
|
|
||||||
"id": "recruit",
|
|
||||||
"display_name": "Recrue",
|
|
||||||
"min_reputation": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fighter",
|
|
||||||
"display_name": "Combattant",
|
|
||||||
"min_reputation": 250
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "veteran",
|
|
||||||
"display_name": "Vétéran",
|
|
||||||
"min_reputation": 750
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "champion",
|
|
||||||
"display_name": "Champion",
|
|
||||||
"min_reputation": 2000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"id": "aquatic",
|
|
||||||
"display_name": "Les profondeurs",
|
|
||||||
"weight": 6,
|
|
||||||
"environment": {
|
|
||||||
"water_level": 3
|
|
||||||
},
|
|
||||||
"mobs": [
|
|
||||||
{
|
|
||||||
"entity": "minecraft:drowned",
|
|
||||||
"cost": 2.0,
|
|
||||||
"weight": 10,
|
|
||||||
"min": 2,
|
|
||||||
"max": 18,
|
|
||||||
"spawn_tags": [
|
|
||||||
"WATER"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": "minecraft:guardian",
|
|
||||||
"cost": 5.0,
|
|
||||||
"weight": 3,
|
|
||||||
"min": 0,
|
|
||||||
"max": 4,
|
|
||||||
"spawn_tags": [
|
|
||||||
"WATER"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"id": "cavern",
|
|
||||||
"display_name": "Monstres des cavernes",
|
|
||||||
"weight": 10,
|
|
||||||
"environment": {
|
|
||||||
"water_level": 0
|
|
||||||
},
|
|
||||||
"mobs": [
|
|
||||||
{
|
|
||||||
"entity": "minecraft:zombie",
|
|
||||||
"cost": 1.0,
|
|
||||||
"weight": 10,
|
|
||||||
"min": 0,
|
|
||||||
"max": 20,
|
|
||||||
"spawn_tags": [
|
|
||||||
"GROUND"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": "minecraft:spider",
|
|
||||||
"cost": 1.5,
|
|
||||||
"weight": 8,
|
|
||||||
"min": 0,
|
|
||||||
"max": 12,
|
|
||||||
"spawn_tags": [
|
|
||||||
"GROUND"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": "minecraft:cave_spider",
|
|
||||||
"cost": 2.5,
|
|
||||||
"weight": 4,
|
|
||||||
"min": 0,
|
|
||||||
"max": 5,
|
|
||||||
"spawn_tags": [
|
|
||||||
"GROUND"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
79
README.md
79
README.md
|
|
@ -1,66 +1,25 @@
|
||||||
|
|
||||||
# Arena of Shinuwa
|
Installation information
|
||||||
|
=======
|
||||||
|
|
||||||
Server-first PvE arena mod. Phase 0 provides only the bootstrap: mod loading,
|
This template repository can be directly cloned to get you started with a new
|
||||||
server configuration, logging and a test setup. No gameplay is implemented yet.
|
mod. Simply create a new repository cloned from this one, by following the
|
||||||
|
instructions provided by [GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).
|
||||||
|
|
||||||
## Development versions
|
Once you have your clone, simply open the repository in the IDE of your choice. The usual recommendation for an IDE is either IntelliJ IDEA or Eclipse.
|
||||||
|
|
||||||
Versions retained from the repository:
|
If at any point you are missing libraries in your IDE, or you've run into problems you can
|
||||||
|
run `gradlew --refresh-dependencies` to refresh the local cache. `gradlew clean` to reset everything
|
||||||
|
{this does not affect your code} and then start the process again.
|
||||||
|
|
||||||
| Component | Version |
|
Mapping Names:
|
||||||
| --- | --- |
|
============
|
||||||
| Minecraft | 1.21.1 |
|
By default, the MDK is configured to use the official mapping names from Mojang for methods and fields
|
||||||
| NeoForge | 21.1.235 |
|
in the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this
|
||||||
| Java toolchain | 21 |
|
license. For the latest license text, refer to the mapping file itself, or the reference copy here:
|
||||||
| Gradle wrapper | 9.2.1 |
|
https://github.com/NeoForged/NeoForm/blob/main/Mojang.md
|
||||||
| NeoGradle userdev | 7.1.38 |
|
|
||||||
| NeoForm / official Mojang mappings | 1.21.1-20240808.144430 |
|
|
||||||
| Parchment mappings | 1.21.1 / 2024.11.17 |
|
|
||||||
| JUnit Jupiter | 5.11.4 |
|
|
||||||
|
|
||||||
The local verification environment uses Java 21.0.8. Mojang mapping names are
|
Additional Resources:
|
||||||
subject to [Mojang's mapping license](https://github.com/NeoForged/NeoForm/blob/main/Mojang.md).
|
==========
|
||||||
|
Community Documentation: https://docs.neoforged.net/
|
||||||
## Layout
|
NeoForged Discord: https://discord.neoforged.net/
|
||||||
|
|
||||||
- `src/main/java/com/shinuwa/arena`: common mod entrypoint and logging.
|
|
||||||
- `src/main/java/com/shinuwa/arena/config`: NeoForge server configuration.
|
|
||||||
- `src/main/resources/META-INF/neoforge.mods.toml`: mod metadata.
|
|
||||||
- `src/test/java/com/shinuwa/arena`: JUnit tests.
|
|
||||||
- `.local`: specification, architecture, phased checklist and reference examples.
|
|
||||||
|
|
||||||
Mod ID: `arena`. Only Minecraft and NeoForge are required. Cobblemon, EasyNPC,
|
|
||||||
Create and boss mods are not dependencies. Further component packages will be
|
|
||||||
introduced with their implementation phases in `.local/TODO.md`.
|
|
||||||
|
|
||||||
## Build and test
|
|
||||||
|
|
||||||
Use JDK 21 and the checked-in wrapper:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./gradlew build test
|
|
||||||
```
|
|
||||||
|
|
||||||
The mod JAR is written to `build/libs/arena-1.0.0.jar`. Tests check configuration
|
|
||||||
defaults, invalid-value correction and preservation of an explicit opt-out.
|
|
||||||
The existing GitHub Actions build runs the Gradle `build` checks as well.
|
|
||||||
|
|
||||||
## Manual bootstrap smoke test
|
|
||||||
|
|
||||||
1. Run `./gradlew runServer` with no optional mods installed. On first launch,
|
|
||||||
review Minecraft's EULA and, if you agree, accept it in `run/server/eula.txt`,
|
|
||||||
then run the command again.
|
|
||||||
2. Wait for the server's `Done` message. Confirm the log contains
|
|
||||||
`Arena of Shinuwa initialized` and `Arena bootstrap ready on Minecraft 1.21.1`.
|
|
||||||
3. Confirm `run/server/world/serverconfig/arena-server.toml` contains
|
|
||||||
`logServerStartup = true`. Stop the server using `stop`.
|
|
||||||
4. Set that option to `false` and restart. The initialization log should remain,
|
|
||||||
and the `Arena bootstrap ready` message should be absent. Stop the server.
|
|
||||||
5. Run `./gradlew runClient`, then create a single-player world to repeat the
|
|
||||||
startup check on an integrated server. Its config is under that world's
|
|
||||||
`serverconfig` directory. No example blocks, items or creative tab should
|
|
||||||
appear under this mod.
|
|
||||||
|
|
||||||
These are manual procedures; passing compilation/unit tests alone does not
|
|
||||||
verify a graphical client or a complete server startup.
|
|
||||||
|
|
|
||||||
16
build.gradle
16
build.gradle
|
|
@ -1,7 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'net.neoforged.gradle.userdev' version '7.1.38'
|
id 'net.neoforged.gradle.userdev' version '7.1.26'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('wrapper', Wrapper).configure {
|
tasks.named('wrapper', Wrapper).configure {
|
||||||
|
|
@ -26,7 +26,6 @@ sourceSets.main.resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
|
||||||
// Add here additional repositories if required by some of the dependencies below.
|
// Add here additional repositories if required by some of the dependencies below.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,10 +104,6 @@ dependencies {
|
||||||
// For all intends and purposes: You can treat this dependency as if it is a normal library you would use.
|
// For all intends and purposes: You can treat this dependency as if it is a normal library you would use.
|
||||||
implementation "net.neoforged:neoforge:${neo_version}"
|
implementation "net.neoforged:neoforge:${neo_version}"
|
||||||
|
|
||||||
testImplementation platform('org.junit:junit-bom:5.11.4')
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
||||||
|
|
||||||
// Example optional mod dependency with JEI
|
// Example optional mod dependency with JEI
|
||||||
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
|
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
|
||||||
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
|
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
|
||||||
|
|
@ -172,15 +167,6 @@ tasks.withType(JavaCompile).configureEach {
|
||||||
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test', Test).configure {
|
|
||||||
useJUnitPlatform()
|
|
||||||
// Keep NeoForge/Log4j test output inside the ignored build directory.
|
|
||||||
workingDir = layout.buildDirectory.dir('test-work').get().asFile
|
|
||||||
doFirst {
|
|
||||||
workingDir.mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
|
// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
|
||||||
idea {
|
idea {
|
||||||
module {
|
module {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ minecraft_version=1.21.1
|
||||||
# as they do not follow standard versioning conventions.
|
# as they do not follow standard versioning conventions.
|
||||||
minecraft_version_range=[1.21.1]
|
minecraft_version_range=[1.21.1]
|
||||||
# The Neo version must agree with the Minecraft version to get a valid artifact
|
# The Neo version must agree with the Minecraft version to get a valid artifact
|
||||||
neo_version=21.1.235
|
neo_version=21.1.228
|
||||||
# The loader version range can only use the major version of FML as bounds
|
# The loader version range can only use the major version of FML as bounds
|
||||||
loader_version_range=[1,)
|
loader_version_range=[1,)
|
||||||
|
|
||||||
|
|
@ -26,9 +26,9 @@ loader_version_range=[1,)
|
||||||
|
|
||||||
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
|
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
|
||||||
# Must match the String constant located in the main mod class annotated with @Mod.
|
# Must match the String constant located in the main mod class annotated with @Mod.
|
||||||
mod_id=arena
|
mod_id=examplemod
|
||||||
# The human-readable display name for the mod.
|
# The human-readable display name for the mod.
|
||||||
mod_name=Arena of Shinuwa
|
mod_name=Example Mod
|
||||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||||
mod_license=All Rights Reserved
|
mod_license=All Rights Reserved
|
||||||
# The mod version. See https://semver.org/
|
# The mod version. See https://semver.org/
|
||||||
|
|
@ -36,4 +36,4 @@ mod_version=1.0.0
|
||||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||||
# This should match the base package used for the mod sources.
|
# This should match the base package used for the mod sources.
|
||||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||||
mod_group_id=com.shinuwa.arena
|
mod_group_id=com.example.examplemod
|
||||||
|
|
|
||||||
42
src/main/java/com/example/examplemod/Config.java
Normal file
42
src/main/java/com/example/examplemod/Config.java
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.example.examplemod;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
|
import net.neoforged.fml.event.config.ModConfigEvent;
|
||||||
|
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||||
|
|
||||||
|
// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
|
||||||
|
// Demonstrates how to use Neo's config APIs
|
||||||
|
public class Config {
|
||||||
|
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();
|
||||||
|
|
||||||
|
public static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
|
||||||
|
.comment("Whether to log the dirt block on common setup")
|
||||||
|
.define("logDirtBlock", true);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER
|
||||||
|
.comment("A magic number")
|
||||||
|
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
public static final ModConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER
|
||||||
|
.comment("What you want the introduction message to be for the magic number")
|
||||||
|
.define("magicNumberIntroduction", "The magic number is... ");
|
||||||
|
|
||||||
|
// a list of strings that are treated as resource locations for items
|
||||||
|
public static final ModConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER
|
||||||
|
.comment("A list of items to log on common setup.")
|
||||||
|
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), () -> "", Config::validateItemName);
|
||||||
|
|
||||||
|
static final ModConfigSpec SPEC = BUILDER.build();
|
||||||
|
|
||||||
|
private static boolean validateItemName(final Object obj) {
|
||||||
|
return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(ResourceLocation.parse(itemName));
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/main/java/com/example/examplemod/ExampleMod.java
Normal file
117
src/main/java/com/example/examplemod/ExampleMod.java
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.example.examplemod;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import com.mojang.logging.LogUtils;
|
||||||
|
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.food.FoodProperties;
|
||||||
|
import net.minecraft.world.item.BlockItem;
|
||||||
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.material.MapColor;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent;
|
||||||
|
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredBlock;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredItem;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
// The value here should match an entry in the META-INF/neoforge.mods.toml file
|
||||||
|
@Mod(ExampleMod.MODID)
|
||||||
|
public class ExampleMod {
|
||||||
|
// Define mod id in a common place for everything to reference
|
||||||
|
public static final String MODID = "examplemod";
|
||||||
|
// Directly reference a slf4j logger
|
||||||
|
public static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
// Create a Deferred Register to hold Blocks which will all be registered under the "examplemod" namespace
|
||||||
|
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MODID);
|
||||||
|
// Create a Deferred Register to hold Items which will all be registered under the "examplemod" namespace
|
||||||
|
public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MODID);
|
||||||
|
// Create a Deferred Register to hold CreativeModeTabs which will all be registered under the "examplemod" namespace
|
||||||
|
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
|
||||||
|
|
||||||
|
// Creates a new Block with the id "examplemod:example_block", combining the namespace and path
|
||||||
|
public static final DeferredBlock<Block> EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock("example_block", BlockBehaviour.Properties.of().mapColor(MapColor.STONE));
|
||||||
|
// Creates a new BlockItem with the id "examplemod:example_block", combining the namespace and path
|
||||||
|
public static final DeferredItem<BlockItem> EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", EXAMPLE_BLOCK);
|
||||||
|
|
||||||
|
// Creates a new food item with the id "examplemod:example_id", nutrition 1 and saturation 2
|
||||||
|
public static final DeferredItem<Item> EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item", new Item.Properties().food(new FoodProperties.Builder()
|
||||||
|
.alwaysEdible().nutrition(1).saturationModifier(2f).build()));
|
||||||
|
|
||||||
|
// Creates a creative tab with the id "examplemod:example_tab" for the example item, that is placed after the combat tab
|
||||||
|
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder()
|
||||||
|
.title(Component.translatable("itemGroup.examplemod")) //The language key for the title of your CreativeModeTab
|
||||||
|
.withTabsBefore(CreativeModeTabs.COMBAT)
|
||||||
|
.icon(() -> EXAMPLE_ITEM.get().getDefaultInstance())
|
||||||
|
.displayItems((parameters, output) -> {
|
||||||
|
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
|
||||||
|
}).build());
|
||||||
|
|
||||||
|
// The constructor for the mod class is the first code that is run when your mod is loaded.
|
||||||
|
// FML will recognize some parameter types like IEventBus or ModContainer and pass them in automatically.
|
||||||
|
public ExampleMod(IEventBus modEventBus, ModContainer modContainer) {
|
||||||
|
// Register the commonSetup method for modloading
|
||||||
|
modEventBus.addListener(this::commonSetup);
|
||||||
|
|
||||||
|
// Register the Deferred Register to the mod event bus so blocks get registered
|
||||||
|
BLOCKS.register(modEventBus);
|
||||||
|
// Register the Deferred Register to the mod event bus so items get registered
|
||||||
|
ITEMS.register(modEventBus);
|
||||||
|
// Register the Deferred Register to the mod event bus so tabs get registered
|
||||||
|
CREATIVE_MODE_TABS.register(modEventBus);
|
||||||
|
|
||||||
|
// Register ourselves for server and other game events we are interested in.
|
||||||
|
// Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events.
|
||||||
|
// Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below.
|
||||||
|
NeoForge.EVENT_BUS.register(this);
|
||||||
|
|
||||||
|
// Register the item to a creative tab
|
||||||
|
modEventBus.addListener(this::addCreative);
|
||||||
|
|
||||||
|
// Register our mod's ModConfigSpec so that FML can create and load the config file for us
|
||||||
|
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commonSetup(FMLCommonSetupEvent event) {
|
||||||
|
// Some common setup code
|
||||||
|
LOGGER.info("HELLO FROM COMMON SETUP");
|
||||||
|
|
||||||
|
if (Config.LOG_DIRT_BLOCK.getAsBoolean()) {
|
||||||
|
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt());
|
||||||
|
|
||||||
|
Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the example block item to the building blocks tab
|
||||||
|
private void addCreative(BuildCreativeModeTabContentsEvent event) {
|
||||||
|
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) {
|
||||||
|
event.accept(EXAMPLE_BLOCK_ITEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use SubscribeEvent and let the Event Bus discover methods to call
|
||||||
|
@SubscribeEvent
|
||||||
|
public void onServerStarting(ServerStartingEvent event) {
|
||||||
|
// Do something when the server starts
|
||||||
|
LOGGER.info("HELLO from server starting");
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/com/example/examplemod/ExampleModClient.java
Normal file
31
src/main/java/com/example/examplemod/ExampleModClient.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.example.examplemod;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
|
||||||
|
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
|
||||||
|
|
||||||
|
// This class will not load on dedicated servers. Accessing client side code from here is safe.
|
||||||
|
@Mod(value = ExampleMod.MODID, dist = Dist.CLIENT)
|
||||||
|
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
|
||||||
|
@EventBusSubscriber(modid = ExampleMod.MODID, value = Dist.CLIENT)
|
||||||
|
public class ExampleModClient {
|
||||||
|
public ExampleModClient(ModContainer container) {
|
||||||
|
// Allows NeoForge to create a config screen for this mod's configs.
|
||||||
|
// The config screen is accessed by going to the Mods screen > clicking on your mod > clicking on config.
|
||||||
|
// Do not forget to add translations for your config options to the en_us.json file.
|
||||||
|
container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
|
// Some client setup code
|
||||||
|
ExampleMod.LOGGER.info("HELLO FROM CLIENT SETUP");
|
||||||
|
ExampleMod.LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package com.shinuwa.arena;
|
|
||||||
|
|
||||||
import com.mojang.logging.LogUtils;
|
|
||||||
import com.shinuwa.arena.config.ArenaConfig;
|
|
||||||
import net.neoforged.fml.ModContainer;
|
|
||||||
import net.neoforged.fml.common.Mod;
|
|
||||||
import net.neoforged.fml.config.ModConfig;
|
|
||||||
import net.neoforged.neoforge.common.NeoForge;
|
|
||||||
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
/** Common bootstrap, safe to load on both dedicated and integrated servers. */
|
|
||||||
@Mod(ArenaMod.MOD_ID)
|
|
||||||
public final class ArenaMod {
|
|
||||||
public static final String MOD_ID = "arena";
|
|
||||||
public static final Logger LOGGER = LogUtils.getLogger();
|
|
||||||
|
|
||||||
public ArenaMod(ModContainer container) {
|
|
||||||
container.registerConfig(ModConfig.Type.SERVER, ArenaConfig.SPEC);
|
|
||||||
NeoForge.EVENT_BUS.addListener(this::onServerStarted);
|
|
||||||
LOGGER.info("Arena of Shinuwa initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onServerStarted(ServerStartedEvent event) {
|
|
||||||
// SERVER configuration is loaded by this lifecycle stage.
|
|
||||||
if (ArenaConfig.LOG_SERVER_STARTUP.getAsBoolean()) {
|
|
||||||
LOGGER.info("Arena bootstrap ready on Minecraft {}", event.getServer().getServerVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.shinuwa.arena.config;
|
|
||||||
|
|
||||||
import net.neoforged.neoforge.common.ModConfigSpec;
|
|
||||||
|
|
||||||
/** World-specific server configuration; gameplay settings belong to later phases. */
|
|
||||||
public final class ArenaConfig {
|
|
||||||
public static final ModConfigSpec SPEC;
|
|
||||||
public static final ModConfigSpec.BooleanValue LOG_SERVER_STARTUP;
|
|
||||||
|
|
||||||
static {
|
|
||||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
|
||||||
LOG_SERVER_STARTUP = builder
|
|
||||||
.comment("Log when the arena bootstrap is ready after the server starts.")
|
|
||||||
.define("logServerStartup", true);
|
|
||||||
SPEC = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArenaConfig() {}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/**
|
|
||||||
* Server-first arena mod bootstrap. Gameplay components will be introduced in
|
|
||||||
* their corresponding phases, following .local/ARCHITECTURE.md.
|
|
||||||
*/
|
|
||||||
package com.shinuwa.arena;
|
|
||||||
|
|
@ -36,7 +36,7 @@ displayName="${mod_name}" #mandatory
|
||||||
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
||||||
|
|
||||||
# A file name (in the root of the mod JAR) containing a logo for display
|
# A file name (in the root of the mod JAR) containing a logo for display
|
||||||
#logoFile="arena.png" #optional
|
#logoFile="examplemod.png" #optional
|
||||||
|
|
||||||
# A text field displayed in the mod UI
|
# A text field displayed in the mod UI
|
||||||
#credits="" #optional
|
#credits="" #optional
|
||||||
|
|
@ -46,7 +46,7 @@ displayName="${mod_name}" #mandatory
|
||||||
|
|
||||||
# The description text for the mod (multi line!) (#mandatory)
|
# The description text for the mod (multi line!) (#mandatory)
|
||||||
description='''
|
description='''
|
||||||
Server-first PvE arena mod. Currently provides the bootstrap foundation only.
|
Example mod description.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
|
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
|
||||||
|
|
|
||||||
13
src/main/resources/assets/examplemod/lang/en_us.json
Normal file
13
src/main/resources/assets/examplemod/lang/en_us.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"itemGroup.examplemod": "Example Mod Tab",
|
||||||
|
"block.examplemod.example_block": "Example Block",
|
||||||
|
"item.examplemod.example_item": "Example Item",
|
||||||
|
|
||||||
|
"examplemod.configuration.title": "Example Mod Configs",
|
||||||
|
"examplemod.configuration.section.examplemod.common.toml": "Example Mod Configs",
|
||||||
|
"examplemod.configuration.section.examplemod.common.toml.title": "Example Mod Configs",
|
||||||
|
"examplemod.configuration.items": "Item List",
|
||||||
|
"examplemod.configuration.logDirtBlock": "Log Dirt Block",
|
||||||
|
"examplemod.configuration.magicNumberIntroduction": "Magic Number Text",
|
||||||
|
"examplemod.configuration.magicNumber": "Magic Number"
|
||||||
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package com.shinuwa.arena.config;
|
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class ArenaConfigTest {
|
|
||||||
@Test
|
|
||||||
void missingConfigurationGetsValidDefaults() {
|
|
||||||
CommentedConfig config = CommentedConfig.inMemory();
|
|
||||||
|
|
||||||
ArenaConfig.SPEC.correct(config);
|
|
||||||
|
|
||||||
assertEquals(Boolean.TRUE, config.get("logServerStartup"));
|
|
||||||
assertTrue(ArenaConfig.SPEC.isCorrect(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void disabledStartupLoggingSurvivesCorrection() {
|
|
||||||
CommentedConfig config = CommentedConfig.inMemory();
|
|
||||||
config.set("logServerStartup", false);
|
|
||||||
|
|
||||||
ArenaConfig.SPEC.correct(config);
|
|
||||||
|
|
||||||
assertEquals(Boolean.FALSE, config.get("logServerStartup"));
|
|
||||||
assertTrue(ArenaConfig.SPEC.isCorrect(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void invalidStartupLoggingValueIsRepaired() {
|
|
||||||
CommentedConfig config = CommentedConfig.inMemory();
|
|
||||||
config.set("logServerStartup", 42);
|
|
||||||
|
|
||||||
ArenaConfig.SPEC.correct(config);
|
|
||||||
|
|
||||||
assertEquals(Boolean.TRUE, config.get("logServerStartup"));
|
|
||||||
assertTrue(ArenaConfig.SPEC.isCorrect(config));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue