From 5a6b2e2cac7ffd730850a674a4e91d34dbab729c Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Fri, 18 Sep 2026 15:03:09 +0200 Subject: [PATCH] Initial commit --- .local/AGENTS.md | 27 +++ .local/ARCHITECTURE.md | 81 +++++++ .local/CODEX_PROMPTS.md | 13 ++ .local/SPEC.md | 213 ++++++++++++++++++ .local/TODO.md | 132 +++++++++++ .local/examples/bosses/raider_lord.json | 13 ++ .local/examples/classes/warrior.json | 32 +++ .local/examples/progression.json | 30 +++ .local/examples/ranks.json | 24 ++ .local/examples/themes/aquatic.json | 30 +++ .local/examples/themes/cavern.json | 40 ++++ README.md | 79 +++++-- build.gradle | 14 ++ gradle.properties | 6 +- .../java/com/example/examplemod/Config.java | 42 ---- .../com/example/examplemod/ExampleMod.java | 117 ---------- .../example/examplemod/ExampleModClient.java | 31 --- src/main/java/com/shinuwa/arena/ArenaMod.java | 30 +++ .../com/shinuwa/arena/config/ArenaConfig.java | 19 ++ .../java/com/shinuwa/arena/package-info.java | 5 + .../resources/META-INF/neoforge.mods.toml | 4 +- .../assets/examplemod/lang/en_us.json | 13 -- .../shinuwa/arena/config/ArenaConfigTest.java | 41 ++++ 23 files changed, 809 insertions(+), 227 deletions(-) create mode 100644 .local/AGENTS.md create mode 100644 .local/ARCHITECTURE.md create mode 100644 .local/CODEX_PROMPTS.md create mode 100644 .local/SPEC.md create mode 100644 .local/TODO.md create mode 100644 .local/examples/bosses/raider_lord.json create mode 100644 .local/examples/classes/warrior.json create mode 100644 .local/examples/progression.json create mode 100644 .local/examples/ranks.json create mode 100644 .local/examples/themes/aquatic.json create mode 100644 .local/examples/themes/cavern.json delete mode 100644 src/main/java/com/example/examplemod/Config.java delete mode 100644 src/main/java/com/example/examplemod/ExampleMod.java delete mode 100644 src/main/java/com/example/examplemod/ExampleModClient.java create mode 100644 src/main/java/com/shinuwa/arena/ArenaMod.java create mode 100644 src/main/java/com/shinuwa/arena/config/ArenaConfig.java create mode 100644 src/main/java/com/shinuwa/arena/package-info.java delete mode 100644 src/main/resources/assets/examplemod/lang/en_us.json create mode 100644 src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java diff --git a/.local/AGENTS.md b/.local/AGENTS.md new file mode 100644 index 0000000..fac533a --- /dev/null +++ b/.local/AGENTS.md @@ -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. diff --git a/.local/ARCHITECTURE.md b/.local/ARCHITECTURE.md new file mode 100644 index 0000000..7ad47ea --- /dev/null +++ b/.local/ARCHITECTURE.md @@ -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. diff --git a/.local/CODEX_PROMPTS.md b/.local/CODEX_PROMPTS.md new file mode 100644 index 0000000..14c08ea --- /dev/null +++ b/.local/CODEX_PROMPTS.md @@ -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`: . 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 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. diff --git a/.local/SPEC.md b/.local/SPEC.md new file mode 100644 index 0000000..6cfdede --- /dev/null +++ b/.local/SPEC.md @@ -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 [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 ` +- 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. diff --git a/.local/TODO.md b/.local/TODO.md new file mode 100644 index 0000000..c6b16ec --- /dev/null +++ b/.local/TODO.md @@ -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. diff --git a/.local/examples/bosses/raider_lord.json b/.local/examples/bosses/raider_lord.json new file mode 100644 index 0000000..7576b10 --- /dev/null +++ b/.local/examples/bosses/raider_lord.json @@ -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 +} diff --git a/.local/examples/classes/warrior.json b/.local/examples/classes/warrior.json new file mode 100644 index 0000000..8e2b6e9 --- /dev/null +++ b/.local/examples/classes/warrior.json @@ -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 + } + ] +} diff --git a/.local/examples/progression.json b/.local/examples/progression.json new file mode 100644 index 0000000..3d1836d --- /dev/null +++ b/.local/examples/progression.json @@ -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." +} diff --git a/.local/examples/ranks.json b/.local/examples/ranks.json new file mode 100644 index 0000000..0c7a828 --- /dev/null +++ b/.local/examples/ranks.json @@ -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 + } + ] +} diff --git a/.local/examples/themes/aquatic.json b/.local/examples/themes/aquatic.json new file mode 100644 index 0000000..15e6e86 --- /dev/null +++ b/.local/examples/themes/aquatic.json @@ -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" + ] + } + ] +} diff --git a/.local/examples/themes/cavern.json b/.local/examples/themes/cavern.json new file mode 100644 index 0000000..e6d4e05 --- /dev/null +++ b/.local/examples/themes/cavern.json @@ -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" + ] + } + ] +} diff --git a/README.md b/README.md index b421122..e9269bc 100644 --- a/README.md +++ b/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. diff --git a/build.gradle b/build.gradle index b6c535c..6cc3ab4 100644 --- a/build.gradle +++ b/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 { diff --git a/gradle.properties b/gradle.properties index 643c2f4..80be1ba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/com/example/examplemod/Config.java b/src/main/java/com/example/examplemod/Config.java deleted file mode 100644 index 358706c..0000000 --- a/src/main/java/com/example/examplemod/Config.java +++ /dev/null @@ -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 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> 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)); - } -} diff --git a/src/main/java/com/example/examplemod/ExampleMod.java b/src/main/java/com/example/examplemod/ExampleMod.java deleted file mode 100644 index db1e6de..0000000 --- a/src/main/java/com/example/examplemod/ExampleMod.java +++ /dev/null @@ -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 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 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 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 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 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"); - } -} diff --git a/src/main/java/com/example/examplemod/ExampleModClient.java b/src/main/java/com/example/examplemod/ExampleModClient.java deleted file mode 100644 index bf9b4c6..0000000 --- a/src/main/java/com/example/examplemod/ExampleModClient.java +++ /dev/null @@ -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()); - } -} diff --git a/src/main/java/com/shinuwa/arena/ArenaMod.java b/src/main/java/com/shinuwa/arena/ArenaMod.java new file mode 100644 index 0000000..c6f02c3 --- /dev/null +++ b/src/main/java/com/shinuwa/arena/ArenaMod.java @@ -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()); + } + } +} diff --git a/src/main/java/com/shinuwa/arena/config/ArenaConfig.java b/src/main/java/com/shinuwa/arena/config/ArenaConfig.java new file mode 100644 index 0000000..3b77cbc --- /dev/null +++ b/src/main/java/com/shinuwa/arena/config/ArenaConfig.java @@ -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() {} +} diff --git a/src/main/java/com/shinuwa/arena/package-info.java b/src/main/java/com/shinuwa/arena/package-info.java new file mode 100644 index 0000000..d908618 --- /dev/null +++ b/src/main/java/com/shinuwa/arena/package-info.java @@ -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; diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index cc5e2d2..ed5cede 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -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. diff --git a/src/main/resources/assets/examplemod/lang/en_us.json b/src/main/resources/assets/examplemod/lang/en_us.json deleted file mode 100644 index 4f93bf6..0000000 --- a/src/main/resources/assets/examplemod/lang/en_us.json +++ /dev/null @@ -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" -} diff --git a/src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java b/src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java new file mode 100644 index 0000000..be28f47 --- /dev/null +++ b/src/test/java/com/shinuwa/arena/config/ArenaConfigTest.java @@ -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)); + } +}