Initial commit
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Shinuwa 2026-09-18 15:03:09 +02:00
parent 50b02952c5
commit 5a6b2e2cac
23 changed files with 809 additions and 227 deletions

27
.local/AGENTS.md Normal file
View 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
View 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
View 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
View 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
View 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.

View 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
}

View 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
}
]
}

View 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."
}

View 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
}
]
}

View 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"
]
}
]
}

View 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"
]
}
]
}

View file

@ -1,25 +1,66 @@
Installation information # Arena of Shinuwa
=======
This template repository can be directly cloned to get you started with a new Server-first PvE arena mod. Phase 0 provides only the bootstrap: mod loading,
mod. Simply create a new repository cloned from this one, by following the server configuration, logging and a test setup. No gameplay is implemented yet.
instructions provided by [GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).
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 Versions retained from the repository:
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.
Mapping Names: | Component | Version |
============ | --- | --- |
By default, the MDK is configured to use the official mapping names from Mojang for methods and fields | Minecraft | 1.21.1 |
in the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this | NeoForge | 21.1.235 |
license. For the latest license text, refer to the mapping file itself, or the reference copy here: | Java toolchain | 21 |
https://github.com/NeoForged/NeoForm/blob/main/Mojang.md | 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: 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).
Community Documentation: https://docs.neoforged.net/
NeoForged Discord: https://discord.neoforged.net/ ## 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.

View file

@ -26,6 +26,7 @@ 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.
} }
@ -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. // 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}"
@ -167,6 +172,15 @@ 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 {

View file

@ -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=examplemod mod_id=arena
# The human-readable display name for the mod. # 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. # 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.example.examplemod mod_group_id=com.shinuwa.arena

View file

@ -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));
}
}

View file

@ -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");
}
}

View file

@ -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());
}
}

View 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());
}
}
}

View 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() {}
}

View 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;

View file

@ -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="examplemod.png" #optional #logoFile="arena.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='''
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. # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.

View file

@ -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"
}

View 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));
}
}