This commit is contained in:
parent
50b02952c5
commit
5a6b2e2cac
23 changed files with 809 additions and 227 deletions
27
.local/AGENTS.md
Normal file
27
.local/AGENTS.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# 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.
|
||||
81
.local/ARCHITECTURE.md
Normal file
81
.local/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 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.
|
||||
13
.local/CODEX_PROMPTS.md
Normal file
13
.local/CODEX_PROMPTS.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# 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
Normal file
213
.local/SPEC.md
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
# 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
Normal file
132
.local/TODO.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# 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.
|
||||
13
.local/examples/bosses/raider_lord.json
Normal file
13
.local/examples/bosses/raider_lord.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
32
.local/examples/classes/warrior.json
Normal file
32
.local/examples/classes/warrior.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
30
.local/examples/progression.json
Normal file
30
.local/examples/progression.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
24
.local/examples/ranks.json
Normal file
24
.local/examples/ranks.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
30
.local/examples/themes/aquatic.json
Normal file
30
.local/examples/themes/aquatic.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
40
.local/examples/themes/cavern.json
Normal file
40
.local/examples/themes/cavern.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"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,25 +1,66 @@
|
|||
|
||||
Installation information
|
||||
=======
|
||||
# Arena of Shinuwa
|
||||
|
||||
This template repository can be directly cloned to get you started with a new
|
||||
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).
|
||||
Server-first PvE arena mod. Phase 0 provides only the bootstrap: mod loading,
|
||||
server configuration, logging and a test setup. No gameplay is implemented yet.
|
||||
|
||||
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.
|
||||
## Development versions
|
||||
|
||||
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.
|
||||
Versions retained from the repository:
|
||||
|
||||
Mapping Names:
|
||||
============
|
||||
By default, the MDK is configured to use the official mapping names from Mojang for methods and fields
|
||||
in the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this
|
||||
license. For the latest license text, refer to the mapping file itself, or the reference copy here:
|
||||
https://github.com/NeoForged/NeoForm/blob/main/Mojang.md
|
||||
| Component | Version |
|
||||
| --- | --- |
|
||||
| Minecraft | 1.21.1 |
|
||||
| NeoForge | 21.1.235 |
|
||||
| Java toolchain | 21 |
|
||||
| Gradle wrapper | 9.2.1 |
|
||||
| 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 |
|
||||
|
||||
Additional Resources:
|
||||
==========
|
||||
Community Documentation: https://docs.neoforged.net/
|
||||
NeoForged Discord: https://discord.neoforged.net/
|
||||
The local verification environment uses Java 21.0.8. Mojang mapping names are
|
||||
subject to [Mojang's mapping license](https://github.com/NeoForged/NeoForm/blob/main/Mojang.md).
|
||||
|
||||
## Layout
|
||||
|
||||
- `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.
|
||||
|
|
|
|||
14
build.gradle
14
build.gradle
|
|
@ -26,6 +26,7 @@ sourceSets.main.resources {
|
|||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// Add here additional repositories if required by some of the dependencies below.
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +105,10 @@ dependencies {
|
|||
// 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}"
|
||||
|
||||
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
|
||||
// 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}"
|
||||
|
|
@ -167,6 +172,15 @@ tasks.withType(JavaCompile).configureEach {
|
|||
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 {
|
||||
module {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
# Must match the String constant located in the main mod class annotated with @Mod.
|
||||
mod_id=examplemod
|
||||
mod_id=arena
|
||||
# The human-readable display name for the mod.
|
||||
mod_name=Example Mod
|
||||
mod_name=Arena of Shinuwa
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=All Rights Reserved
|
||||
# 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.
|
||||
# This should match the base package used for the mod sources.
|
||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
mod_group_id=com.example.examplemod
|
||||
mod_group_id=com.shinuwa.arena
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
30
src/main/java/com/shinuwa/arena/ArenaMod.java
Normal file
30
src/main/java/com/shinuwa/arena/ArenaMod.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/shinuwa/arena/config/ArenaConfig.java
Normal file
19
src/main/java/com/shinuwa/arena/config/ArenaConfig.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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() {}
|
||||
}
|
||||
5
src/main/java/com/shinuwa/arena/package-info.java
Normal file
5
src/main/java/com/shinuwa/arena/package-info.java
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* 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
|
||||
|
||||
# A file name (in the root of the mod JAR) containing a logo for display
|
||||
#logoFile="examplemod.png" #optional
|
||||
#logoFile="arena.png" #optional
|
||||
|
||||
# A text field displayed in the mod UI
|
||||
#credits="" #optional
|
||||
|
|
@ -46,7 +46,7 @@ displayName="${mod_name}" #mandatory
|
|||
|
||||
# The description text for the mod (multi line!) (#mandatory)
|
||||
description='''
|
||||
Example mod description.
|
||||
Server-first PvE arena mod. Currently provides the bootstrap foundation only.
|
||||
'''
|
||||
|
||||
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
41
src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java
Normal file
41
src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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