From 69e10f31b8102e569db72cfcff8726f41186fb2a Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 15:58:26 +1000 Subject: [PATCH 01/16] feat: add pure-Java domain value types in model package with Lombok Introduce `gg.steve.mc.ap.model` top-level package containing core domain value types organized by concept sub-package (set, ability, combat, effect, notification, player). All types are immutable value carriers using Lombok @Value/@Builder with zero Bukkit/NMS/NBT imports. Types added: - model.set: PieceSlot enum, PurchaseResult enum - model.ability: AbilityType enum, BasicStats, HandItemSpec - model.combat: DamageContext, DamageModification - model.effect: EffectSpec, SoundSpec - model.notification: Notification - model.player: Wearer HandItemSpec carries the pure calculateFinalDamage math (mirroring HandSetData) and is added to the JaCoCo 100% core gate. All other types have construction/equality unit tests. Also adds: - Lombok 1.18.38 dependency (provided scope) with annotation processor - lombok.config enabling @Generated annotation for JaCoCo exclusion - AGENTS.md updated with model package conventions --- AGENTS.md | 5 + lombok.config | 2 + pom.xml | 14 ++ .../mc/ap/model/ability/AbilityType.java | 16 +++ .../steve/mc/ap/model/ability/BasicStats.java | 17 +++ .../mc/ap/model/ability/HandItemSpec.java | 24 ++++ .../mc/ap/model/combat/DamageContext.java | 16 +++ .../ap/model/combat/DamageModification.java | 14 ++ .../steve/mc/ap/model/effect/EffectSpec.java | 10 ++ .../steve/mc/ap/model/effect/SoundSpec.java | 10 ++ .../ap/model/notification/Notification.java | 15 +++ .../gg/steve/mc/ap/model/player/Wearer.java | 11 ++ .../gg/steve/mc/ap/model/set/PieceSlot.java | 9 ++ .../steve/mc/ap/model/set/PurchaseResult.java | 7 + .../mc/ap/model/ability/AbilityTypeTest.java | 21 +++ .../mc/ap/model/ability/BasicStatsTest.java | 57 +++++++++ .../mc/ap/model/ability/HandItemSpecTest.java | 120 ++++++++++++++++++ .../mc/ap/model/combat/DamageContextTest.java | 46 +++++++ .../model/combat/DamageModificationTest.java | 26 ++++ .../mc/ap/model/effect/EffectSpecTest.java | 27 ++++ .../mc/ap/model/effect/SoundSpecTest.java | 27 ++++ .../model/notification/NotificationTest.java | 61 +++++++++ .../steve/mc/ap/model/player/WearerTest.java | 31 +++++ .../steve/mc/ap/model/set/PieceSlotTest.java | 30 +++++ .../mc/ap/model/set/PurchaseResultTest.java | 21 +++ 25 files changed, 637 insertions(+) create mode 100644 lombok.config create mode 100644 src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java create mode 100644 src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java create mode 100644 src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java create mode 100644 src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java create mode 100644 src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java create mode 100644 src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java create mode 100644 src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java create mode 100644 src/main/java/gg/steve/mc/ap/model/notification/Notification.java create mode 100644 src/main/java/gg/steve/mc/ap/model/player/Wearer.java create mode 100644 src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java create mode 100644 src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java create mode 100644 src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/player/WearerTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java diff --git a/AGENTS.md b/AGENTS.md index 3635121..9d199a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,11 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Mockito on JDK 25: surefire must pass `-Dnet.bytebuddy.experimental=true` (configured in pom.xml ``). Without it, ByteBuddy fails to recognize the JDK 25 class file version. - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). +- Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. +- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. +- Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. +- JaCoCo core gate includes `gg.steve.mc.ap.model.ability.HandItemSpec` (has hand-written `calculateFinalDamage` logic). Pure Lombok data carriers are tested but not in the strict gate. +- No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. ## Maintaining this file diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..df71bb6 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/pom.xml b/pom.xml index 905db77..f4a79c7 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,13 @@ 3.14.0 ${maven.compiler.release} + + + org.projectlombok + lombok + 1.18.38 + + @@ -107,6 +114,7 @@ gg.steve.mc.ap.armor.Piece gg.steve.mc.ap.armor.SetType gg.steve.mc.ap.data.SetDataType + gg.steve.mc.ap.model.ability.HandItemSpec @@ -235,6 +243,12 @@ 1.5.21 provided + + org.projectlombok + lombok + 1.18.38 + provided + org.junit.jupiter diff --git a/src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java b/src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java new file mode 100644 index 0000000..55e5c66 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java @@ -0,0 +1,16 @@ +package gg.steve.mc.ap.model.ability; + +public enum AbilityType { + BASIC, + LIGHTNING, + WARP, + POTION, + FALL, + HUNGER, + TRAVELLER, + STUN, + HAND, + ENGINEER, + COLOR_WAY, + EXPERIENCE; +} diff --git a/src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java b/src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java new file mode 100644 index 0000000..fb03e37 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java @@ -0,0 +1,17 @@ +package gg.steve.mc.ap.model.ability; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class BasicStats { + double increase; + double reduction; + double knockback; + double health; + float walkSpeed; + float walkSpeedDefault; + float flySpeed; + float flySpeedDefault; +} diff --git a/src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java b/src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java new file mode 100644 index 0000000..68f2a0e --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java @@ -0,0 +1,24 @@ +package gg.steve.mc.ap.model.ability; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class HandItemSpec { + double increase; + boolean requireSet; + String damageCause; + + /** + * Pure damage calculation mirroring HandSetData.calculateFinalDamage. + * When setIncrease is -1, the set bonus is not applied. + */ + public double calculateFinalDamage(double damage, double setIncrease) { + if (setIncrease != -1) { + double set = setIncrease - 1; + return damage * (set + this.increase); + } + return damage * this.increase; + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java b/src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java new file mode 100644 index 0000000..df73d34 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java @@ -0,0 +1,16 @@ +package gg.steve.mc.ap.model.combat; + +import lombok.Builder; +import lombok.Value; + +import java.util.UUID; + +@Value +@Builder +public class DamageContext { + UUID attacker; + UUID target; + double baseDamage; + String cause; + boolean projectile; +} diff --git a/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java b/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java new file mode 100644 index 0000000..e212bb9 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java @@ -0,0 +1,14 @@ +package gg.steve.mc.ap.model.combat; + +import lombok.Value; + +/** + * Represents a modification to damage and knockback. + * A null DamageModification at call sites means "no change" - callers should use + * Optional to represent the absence of modification. + */ +@Value +public class DamageModification { + double newDamage; + double knockbackMultiplier; +} diff --git a/src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java new file mode 100644 index 0000000..f9ce4f7 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java @@ -0,0 +1,10 @@ +package gg.steve.mc.ap.model.effect; + +import lombok.Value; + +@Value +public class EffectSpec { + String type; + int duration; + int amplifier; +} diff --git a/src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java new file mode 100644 index 0000000..f876c62 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java @@ -0,0 +1,10 @@ +package gg.steve.mc.ap.model.effect; + +import lombok.Value; + +@Value +public class SoundSpec { + String name; + float volume; + float pitch; +} diff --git a/src/main/java/gg/steve/mc/ap/model/notification/Notification.java b/src/main/java/gg/steve/mc/ap/model/notification/Notification.java new file mode 100644 index 0000000..fb28914 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/notification/Notification.java @@ -0,0 +1,15 @@ +package gg.steve.mc.ap.model.notification; + +import gg.steve.mc.ap.model.effect.SoundSpec; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +public class Notification { + List messages; + SoundSpec sound; + List commands; +} diff --git a/src/main/java/gg/steve/mc/ap/model/player/Wearer.java b/src/main/java/gg/steve/mc/ap/model/player/Wearer.java new file mode 100644 index 0000000..8af5f43 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/player/Wearer.java @@ -0,0 +1,11 @@ +package gg.steve.mc.ap.model.player; + +import lombok.Value; + +import java.util.UUID; + +@Value +public class Wearer { + UUID playerId; + String setName; +} diff --git a/src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java b/src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java new file mode 100644 index 0000000..30050fc --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java @@ -0,0 +1,9 @@ +package gg.steve.mc.ap.model.set; + +public enum PieceSlot { + HELMET, + CHESTPLATE, + LEGGINGS, + BOOTS, + HAND; +} diff --git a/src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java b/src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java new file mode 100644 index 0000000..e010a03 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java @@ -0,0 +1,7 @@ +package gg.steve.mc.ap.model.set; + +public enum PurchaseResult { + SUCCESS, + INSUFFICIENT_FUNDS, + NO_PERMISSION; +} diff --git a/src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java b/src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java new file mode 100644 index 0000000..2149e5c --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java @@ -0,0 +1,21 @@ +package gg.steve.mc.ap.model.ability; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AbilityTypeTest { + + @Test + void allValuesPresent() { + AbilityType[] values = AbilityType.values(); + assertEquals(12, values.length); + } + + @Test + void valueOfRoundTrips() { + for (AbilityType type : AbilityType.values()) { + assertEquals(type, AbilityType.valueOf(type.name())); + } + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java b/src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java new file mode 100644 index 0000000..718a83b --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java @@ -0,0 +1,57 @@ +package gg.steve.mc.ap.model.ability; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BasicStatsTest { + + @Test + void builderCreatesExpectedValues() { + BasicStats stats = BasicStats.builder() + .increase(1.5) + .reduction(0.8) + .knockback(0.3) + .health(30.0) + .walkSpeed(0.25f) + .walkSpeedDefault(0.2f) + .flySpeed(0.15f) + .flySpeedDefault(0.1f) + .build(); + + assertEquals(1.5, stats.getIncrease()); + assertEquals(0.8, stats.getReduction()); + assertEquals(0.3, stats.getKnockback()); + assertEquals(30.0, stats.getHealth()); + assertEquals(0.25f, stats.getWalkSpeed()); + assertEquals(0.2f, stats.getWalkSpeedDefault()); + assertEquals(0.15f, stats.getFlySpeed()); + assertEquals(0.1f, stats.getFlySpeedDefault()); + } + + @Test + void equalsAndHashCode() { + BasicStats a = BasicStats.builder() + .increase(1.5).reduction(0.8).knockback(0.3).health(30.0) + .walkSpeed(0.25f).walkSpeedDefault(0.2f) + .flySpeed(0.15f).flySpeedDefault(0.1f).build(); + BasicStats b = BasicStats.builder() + .increase(1.5).reduction(0.8).knockback(0.3).health(30.0) + .walkSpeed(0.25f).walkSpeedDefault(0.2f) + .flySpeed(0.15f).flySpeedDefault(0.1f).build(); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + void disabledValuesUseNegativeOne() { + BasicStats stats = BasicStats.builder() + .increase(-1).reduction(-1).knockback(-1).health(-1) + .walkSpeed(-1f).walkSpeedDefault(-1f) + .flySpeed(-1f).flySpeedDefault(-1f).build(); + + assertEquals(-1, stats.getIncrease()); + assertEquals(-1, stats.getReduction()); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java b/src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java new file mode 100644 index 0000000..5e41549 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java @@ -0,0 +1,120 @@ +package gg.steve.mc.ap.model.ability; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class HandItemSpecTest { + + @Test + void builderCreatesExpectedValues() { + HandItemSpec spec = HandItemSpec.builder() + .increase(2.5) + .requireSet(true) + .damageCause("ENTITY_ATTACK") + .build(); + + assertEquals(2.5, spec.getIncrease()); + assertTrue(spec.isRequireSet()); + assertEquals("ENTITY_ATTACK", spec.getDamageCause()); + } + + @Test + void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { + HandItemSpec spec = HandItemSpec.builder() + .increase(1.5) + .requireSet(false) + .damageCause("ENTITY_ATTACK") + .build(); + + // setIncrease != -1: result = damage * (setIncrease - 1 + handIncrease) + // damage=10, setIncrease=2.0 -> 10 * (2.0 - 1 + 1.5) = 10 * 2.5 = 25.0 + assertEquals(25.0, spec.calculateFinalDamage(10.0, 2.0), 0.0001); + } + + @Test + void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { + HandItemSpec spec = HandItemSpec.builder() + .increase(1.5) + .requireSet(false) + .damageCause("ENTITY_ATTACK") + .build(); + + // setIncrease == -1: result = damage * handIncrease + // damage=10, setIncrease=-1 -> 10 * 1.5 = 15.0 + assertEquals(15.0, spec.calculateFinalDamage(10.0, -1), 0.0001); + } + + @ParameterizedTest + @CsvSource({ + "10.0, 1.5, 3.0, 35.0", // 10 * (3-1 + 1.5) = 10 * 3.5 + "5.0, 2.0, 1.0, 10.0", // 5 * (1-1 + 2) = 5 * 2 + "8.0, 1.0, 2.0, 16.0", // 8 * (2-1 + 1) = 8 * 2 + "0.0, 1.5, 2.0, 0.0", // 0 * anything = 0 + }) + void calculateFinalDamage_withSetIncrease_parametrized( + double damage, double increase, double setIncrease, double expected) { + HandItemSpec spec = HandItemSpec.builder() + .increase(increase) + .requireSet(false) + .damageCause("ENTITY_ATTACK") + .build(); + + assertEquals(expected, spec.calculateFinalDamage(damage, setIncrease), 0.0001); + } + + @ParameterizedTest + @CsvSource({ + "10.0, 1.5, 15.0", // 10 * 1.5 + "5.0, 2.0, 10.0", // 5 * 2 + "0.0, 3.0, 0.0", // 0 * anything = 0 + "8.0, 0.5, 4.0", // 8 * 0.5 + }) + void calculateFinalDamage_withoutSetIncrease_parametrized( + double damage, double increase, double expected) { + HandItemSpec spec = HandItemSpec.builder() + .increase(increase) + .requireSet(false) + .damageCause("ENTITY_ATTACK") + .build(); + + assertEquals(expected, spec.calculateFinalDamage(damage, -1), 0.0001); + } + + @Test + void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { + // Edge case: setIncrease=1 -> set part = 1-1=0, so result = damage * (0 + increase) = damage * increase + HandItemSpec spec = HandItemSpec.builder() + .increase(2.0) + .requireSet(true) + .damageCause("PROJECTILE") + .build(); + + assertEquals(20.0, spec.calculateFinalDamage(10.0, 1.0), 0.0001); + } + + @Test + void equalsAndHashCode() { + HandItemSpec a = HandItemSpec.builder() + .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + HandItemSpec b = HandItemSpec.builder() + .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + HandItemSpec c = HandItemSpec.builder() + .increase(2.0).requireSet(false).damageCause("PROJECTILE").build(); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } + + @Test + void toStringContainsFields() { + HandItemSpec spec = HandItemSpec.builder() + .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + String s = spec.toString(); + assertTrue(s.contains("1.5")); + assertTrue(s.contains("ENTITY_ATTACK")); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java b/src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java new file mode 100644 index 0000000..53fbf84 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java @@ -0,0 +1,46 @@ +package gg.steve.mc.ap.model.combat; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class DamageContextTest { + + @Test + void builderCreatesExpectedValues() { + UUID attacker = UUID.randomUUID(); + UUID target = UUID.randomUUID(); + + DamageContext ctx = DamageContext.builder() + .attacker(attacker) + .target(target) + .baseDamage(10.0) + .cause("ENTITY_ATTACK") + .projectile(false) + .build(); + + assertEquals(attacker, ctx.getAttacker()); + assertEquals(target, ctx.getTarget()); + assertEquals(10.0, ctx.getBaseDamage()); + assertEquals("ENTITY_ATTACK", ctx.getCause()); + assertFalse(ctx.isProjectile()); + } + + @Test + void equalsAndHashCode() { + UUID attacker = UUID.fromString("00000000-0000-0000-0000-000000000001"); + UUID target = UUID.fromString("00000000-0000-0000-0000-000000000002"); + + DamageContext a = DamageContext.builder() + .attacker(attacker).target(target) + .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); + DamageContext b = DamageContext.builder() + .attacker(attacker).target(target) + .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java b/src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java new file mode 100644 index 0000000..5af4a4f --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java @@ -0,0 +1,26 @@ +package gg.steve.mc.ap.model.combat; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DamageModificationTest { + + @Test + void constructsWithExpectedValues() { + DamageModification mod = new DamageModification(15.0, 0.5); + assertEquals(15.0, mod.getNewDamage()); + assertEquals(0.5, mod.getKnockbackMultiplier()); + } + + @Test + void equalsAndHashCode() { + DamageModification a = new DamageModification(10.0, 1.0); + DamageModification b = new DamageModification(10.0, 1.0); + DamageModification c = new DamageModification(10.0, 2.0); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java new file mode 100644 index 0000000..9c4c412 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java @@ -0,0 +1,27 @@ +package gg.steve.mc.ap.model.effect; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EffectSpecTest { + + @Test + void constructsWithExpectedValues() { + EffectSpec spec = new EffectSpec("SPEED", 200, 1); + assertEquals("SPEED", spec.getType()); + assertEquals(200, spec.getDuration()); + assertEquals(1, spec.getAmplifier()); + } + + @Test + void equalsAndHashCode() { + EffectSpec a = new EffectSpec("SPEED", 200, 1); + EffectSpec b = new EffectSpec("SPEED", 200, 1); + EffectSpec c = new EffectSpec("JUMP", 100, 2); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java new file mode 100644 index 0000000..5305e4b --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java @@ -0,0 +1,27 @@ +package gg.steve.mc.ap.model.effect; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SoundSpecTest { + + @Test + void constructsWithExpectedValues() { + SoundSpec spec = new SoundSpec("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); + assertEquals("ENTITY_PLAYER_LEVELUP", spec.getName()); + assertEquals(1.0f, spec.getVolume()); + assertEquals(0.5f, spec.getPitch()); + } + + @Test + void equalsAndHashCode() { + SoundSpec a = new SoundSpec("DING", 1.0f, 1.0f); + SoundSpec b = new SoundSpec("DING", 1.0f, 1.0f); + SoundSpec c = new SoundSpec("BOOM", 0.5f, 2.0f); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java b/src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java new file mode 100644 index 0000000..17e9795 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java @@ -0,0 +1,61 @@ +package gg.steve.mc.ap.model.notification; + +import gg.steve.mc.ap.model.effect.SoundSpec; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class NotificationTest { + + @Test + void builderCreatesExpectedValues() { + SoundSpec sound = new SoundSpec("DING", 1.0f, 1.0f); + List messages = Arrays.asList("Welcome!", "Enjoy your armor."); + List commands = Collections.singletonList("give %player% diamond 1"); + + Notification notification = Notification.builder() + .messages(messages) + .sound(sound) + .commands(commands) + .build(); + + assertEquals(messages, notification.getMessages()); + assertEquals(sound, notification.getSound()); + assertEquals(commands, notification.getCommands()); + } + + @Test + void equalsAndHashCode() { + SoundSpec sound = new SoundSpec("DING", 1.0f, 1.0f); + Notification a = Notification.builder() + .messages(Collections.singletonList("Hi")) + .sound(sound) + .commands(Collections.emptyList()) + .build(); + Notification b = Notification.builder() + .messages(Collections.singletonList("Hi")) + .sound(sound) + .commands(Collections.emptyList()) + .build(); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + void nullFieldsPermitted() { + Notification notification = Notification.builder() + .messages(null) + .sound(null) + .commands(null) + .build(); + + assertNull(notification.getMessages()); + assertNull(notification.getSound()); + assertNull(notification.getCommands()); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/player/WearerTest.java b/src/test/java/gg/steve/mc/ap/model/player/WearerTest.java new file mode 100644 index 0000000..cfcbee8 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/player/WearerTest.java @@ -0,0 +1,31 @@ +package gg.steve.mc.ap.model.player; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class WearerTest { + + @Test + void constructsWithExpectedValues() { + UUID id = UUID.randomUUID(); + Wearer wearer = new Wearer(id, "dragon"); + + assertEquals(id, wearer.getPlayerId()); + assertEquals("dragon", wearer.getSetName()); + } + + @Test + void equalsAndHashCode() { + UUID id = UUID.fromString("00000000-0000-0000-0000-000000000001"); + Wearer a = new Wearer(id, "dragon"); + Wearer b = new Wearer(id, "dragon"); + Wearer c = new Wearer(id, "knight"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java b/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java new file mode 100644 index 0000000..7e1f011 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java @@ -0,0 +1,30 @@ +package gg.steve.mc.ap.model.set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PieceSlotTest { + + @Test + void allValuesPresent() { + PieceSlot[] values = PieceSlot.values(); + assertEquals(5, values.length); + } + + @Test + void valueOfRoundTrips() { + for (PieceSlot slot : PieceSlot.values()) { + assertEquals(slot, PieceSlot.valueOf(slot.name())); + } + } + + @Test + void expectedSlots() { + assertNotNull(PieceSlot.valueOf("HELMET")); + assertNotNull(PieceSlot.valueOf("CHESTPLATE")); + assertNotNull(PieceSlot.valueOf("LEGGINGS")); + assertNotNull(PieceSlot.valueOf("BOOTS")); + assertNotNull(PieceSlot.valueOf("HAND")); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java b/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java new file mode 100644 index 0000000..ca803f7 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java @@ -0,0 +1,21 @@ +package gg.steve.mc.ap.model.set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PurchaseResultTest { + + @Test + void allValuesPresent() { + PurchaseResult[] values = PurchaseResult.values(); + assertEquals(3, values.length); + } + + @Test + void valueOfRoundTrips() { + for (PurchaseResult result : PurchaseResult.values()) { + assertEquals(result, PurchaseResult.valueOf(result.name())); + } + } +} From f0d9ffe9be46ec590d58e321c0595f294802dcf5 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 16:02:41 +1000 Subject: [PATCH 02/16] refactor: rename model types to fully-specific domain names Rename all domain value types to use explicit, unambiguous names per project naming conventions: - PieceSlot -> ArmorPieceSlot - PurchaseResult -> ArmorSetPurchaseResult - AbilityType -> ArmorSetAbilityType - BasicStats -> ArmorSetBasicStats - HandItemSpec -> ArmorHandItemSpec - DamageContext -> CombatDamageContext - DamageModification -> CombatDamageModification - EffectSpec -> PotionEffectSpec - SoundSpec -> NotificationSoundSpec - Notification -> ArmorSetNotification - Wearer -> ArmorSetWearer Updates JaCoCo core gate include and AGENTS.md accordingly. --- AGENTS.md | 2 +- pom.xml | 2 +- ...ndItemSpec.java => ArmorHandItemSpec.java} | 2 +- ...lityType.java => ArmorSetAbilityType.java} | 2 +- ...asicStats.java => ArmorSetBasicStats.java} | 2 +- ...eContext.java => CombatDamageContext.java} | 2 +- .../combat/CombatDamageModification.java | 14 +++++++ .../ap/model/combat/DamageModification.java | 14 ------- ...ndSpec.java => NotificationSoundSpec.java} | 2 +- ...{EffectSpec.java => PotionEffectSpec.java} | 2 +- ...ication.java => ArmorSetNotification.java} | 6 +-- .../{Wearer.java => ArmorSetWearer.java} | 2 +- .../{PieceSlot.java => ArmorPieceSlot.java} | 2 +- ...esult.java => ArmorSetPurchaseResult.java} | 2 +- ...ecTest.java => ArmorHandItemSpecTest.java} | 39 +++++++++---------- ...Test.java => ArmorSetAbilityTypeTest.java} | 8 ++-- ...sTest.java => ArmorSetBasicStatsTest.java} | 10 ++--- ...Test.java => CombatDamageContextTest.java} | 8 ++-- ...java => CombatDamageModificationTest.java} | 10 ++--- ...st.java => NotificationSoundSpecTest.java} | 10 ++--- ...pecTest.java => PotionEffectSpecTest.java} | 10 ++--- ...est.java => ArmorSetNotificationTest.java} | 16 ++++---- ...earerTest.java => ArmorSetWearerTest.java} | 10 ++--- .../mc/ap/model/set/ArmorPieceSlotTest.java | 30 ++++++++++++++ .../model/set/ArmorSetPurchaseResultTest.java | 21 ++++++++++ .../steve/mc/ap/model/set/PieceSlotTest.java | 30 -------------- .../mc/ap/model/set/PurchaseResultTest.java | 21 ---------- 27 files changed, 139 insertions(+), 140 deletions(-) rename src/main/java/gg/steve/mc/ap/model/ability/{HandItemSpec.java => ArmorHandItemSpec.java} (94%) rename src/main/java/gg/steve/mc/ap/model/ability/{AbilityType.java => ArmorSetAbilityType.java} (84%) rename src/main/java/gg/steve/mc/ap/model/ability/{BasicStats.java => ArmorSetBasicStats.java} (89%) rename src/main/java/gg/steve/mc/ap/model/combat/{DamageContext.java => CombatDamageContext.java} (86%) create mode 100644 src/main/java/gg/steve/mc/ap/model/combat/CombatDamageModification.java delete mode 100644 src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java rename src/main/java/gg/steve/mc/ap/model/effect/{SoundSpec.java => NotificationSoundSpec.java} (76%) rename src/main/java/gg/steve/mc/ap/model/effect/{EffectSpec.java => PotionEffectSpec.java} (79%) rename src/main/java/gg/steve/mc/ap/model/notification/{Notification.java => ArmorSetNotification.java} (59%) rename src/main/java/gg/steve/mc/ap/model/player/{Wearer.java => ArmorSetWearer.java} (81%) rename src/main/java/gg/steve/mc/ap/model/set/{PieceSlot.java => ArmorPieceSlot.java} (77%) rename src/main/java/gg/steve/mc/ap/model/set/{PurchaseResult.java => ArmorSetPurchaseResult.java} (71%) rename src/test/java/gg/steve/mc/ap/model/ability/{HandItemSpecTest.java => ArmorHandItemSpecTest.java} (75%) rename src/test/java/gg/steve/mc/ap/model/ability/{AbilityTypeTest.java => ArmorSetAbilityTypeTest.java} (52%) rename src/test/java/gg/steve/mc/ap/model/ability/{BasicStatsTest.java => ArmorSetBasicStatsTest.java} (86%) rename src/test/java/gg/steve/mc/ap/model/combat/{DamageContextTest.java => CombatDamageContextTest.java} (85%) rename src/test/java/gg/steve/mc/ap/model/combat/{DamageModificationTest.java => CombatDamageModificationTest.java} (56%) rename src/test/java/gg/steve/mc/ap/model/effect/{SoundSpecTest.java => NotificationSoundSpecTest.java} (56%) rename src/test/java/gg/steve/mc/ap/model/effect/{EffectSpecTest.java => PotionEffectSpecTest.java} (61%) rename src/test/java/gg/steve/mc/ap/model/notification/{NotificationTest.java => ArmorSetNotificationTest.java} (73%) rename src/test/java/gg/steve/mc/ap/model/player/{WearerTest.java => ArmorSetWearerTest.java} (67%) create mode 100644 src/test/java/gg/steve/mc/ap/model/set/ArmorPieceSlotTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResultTest.java delete mode 100644 src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java delete mode 100644 src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java diff --git a/AGENTS.md b/AGENTS.md index 9d199a6..9cfa67f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. - Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. -- JaCoCo core gate includes `gg.steve.mc.ap.model.ability.HandItemSpec` (has hand-written `calculateFinalDamage` logic). Pure Lombok data carriers are tested but not in the strict gate. +- JaCoCo core gate includes `gg.steve.mc.ap.model.ability.ArmorHandItemSpec` (has hand-written `calculateFinalDamage` logic). Pure Lombok data carriers are tested but not in the strict gate. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. ## Maintaining this file diff --git a/pom.xml b/pom.xml index f4a79c7..aa61b68 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ gg.steve.mc.ap.armor.Piece gg.steve.mc.ap.armor.SetType gg.steve.mc.ap.data.SetDataType - gg.steve.mc.ap.model.ability.HandItemSpec + gg.steve.mc.ap.model.ability.ArmorHandItemSpec diff --git a/src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java similarity index 94% rename from src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java rename to src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java index 68f2a0e..5026b38 100644 --- a/src/main/java/gg/steve/mc/ap/model/ability/HandItemSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java @@ -5,7 +5,7 @@ @Value @Builder -public class HandItemSpec { +public class ArmorHandItemSpec { double increase; boolean requireSet; String damageCause; diff --git a/src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java b/src/main/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityType.java similarity index 84% rename from src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java rename to src/main/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityType.java index 55e5c66..62ef7e2 100644 --- a/src/main/java/gg/steve/mc/ap/model/ability/AbilityType.java +++ b/src/main/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityType.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.ability; -public enum AbilityType { +public enum ArmorSetAbilityType { BASIC, LIGHTNING, WARP, diff --git a/src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java b/src/main/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStats.java similarity index 89% rename from src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java rename to src/main/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStats.java index fb03e37..2b658ad 100644 --- a/src/main/java/gg/steve/mc/ap/model/ability/BasicStats.java +++ b/src/main/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStats.java @@ -5,7 +5,7 @@ @Value @Builder -public class BasicStats { +public class ArmorSetBasicStats { double increase; double reduction; double knockback; diff --git a/src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java similarity index 86% rename from src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java rename to src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java index df73d34..1e42357 100644 --- a/src/main/java/gg/steve/mc/ap/model/combat/DamageContext.java +++ b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java @@ -7,7 +7,7 @@ @Value @Builder -public class DamageContext { +public class CombatDamageContext { UUID attacker; UUID target; double baseDamage; diff --git a/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageModification.java b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageModification.java new file mode 100644 index 0000000..bae2ecd --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageModification.java @@ -0,0 +1,14 @@ +package gg.steve.mc.ap.model.combat; + +import lombok.Value; + +/** + * Represents a modification to damage and knockback. + * A null CombatDamageModification at call sites means "no change" - callers should use + * Optional to represent the absence of modification. + */ +@Value +public class CombatDamageModification { + double newDamage; + double knockbackMultiplier; +} diff --git a/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java b/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java deleted file mode 100644 index e212bb9..0000000 --- a/src/main/java/gg/steve/mc/ap/model/combat/DamageModification.java +++ /dev/null @@ -1,14 +0,0 @@ -package gg.steve.mc.ap.model.combat; - -import lombok.Value; - -/** - * Represents a modification to damage and knockback. - * A null DamageModification at call sites means "no change" - callers should use - * Optional to represent the absence of modification. - */ -@Value -public class DamageModification { - double newDamage; - double knockbackMultiplier; -} diff --git a/src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java similarity index 76% rename from src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java rename to src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java index f876c62..05a0024 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/SoundSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java @@ -3,7 +3,7 @@ import lombok.Value; @Value -public class SoundSpec { +public class NotificationSoundSpec { String name; float volume; float pitch; diff --git a/src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java similarity index 79% rename from src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java rename to src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java index f9ce4f7..82cde76 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/EffectSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java @@ -3,7 +3,7 @@ import lombok.Value; @Value -public class EffectSpec { +public class PotionEffectSpec { String type; int duration; int amplifier; diff --git a/src/main/java/gg/steve/mc/ap/model/notification/Notification.java b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java similarity index 59% rename from src/main/java/gg/steve/mc/ap/model/notification/Notification.java rename to src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java index fb28914..c682a80 100644 --- a/src/main/java/gg/steve/mc/ap/model/notification/Notification.java +++ b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.notification; -import gg.steve.mc.ap.model.effect.SoundSpec; +import gg.steve.mc.ap.model.effect.NotificationSoundSpec; import lombok.Builder; import lombok.Value; @@ -8,8 +8,8 @@ @Value @Builder -public class Notification { +public class ArmorSetNotification { List messages; - SoundSpec sound; + NotificationSoundSpec sound; List commands; } diff --git a/src/main/java/gg/steve/mc/ap/model/player/Wearer.java b/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java similarity index 81% rename from src/main/java/gg/steve/mc/ap/model/player/Wearer.java rename to src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java index 8af5f43..e9a6d83 100644 --- a/src/main/java/gg/steve/mc/ap/model/player/Wearer.java +++ b/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java @@ -5,7 +5,7 @@ import java.util.UUID; @Value -public class Wearer { +public class ArmorSetWearer { UUID playerId; String setName; } diff --git a/src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java b/src/main/java/gg/steve/mc/ap/model/set/ArmorPieceSlot.java similarity index 77% rename from src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java rename to src/main/java/gg/steve/mc/ap/model/set/ArmorPieceSlot.java index 30050fc..e610647 100644 --- a/src/main/java/gg/steve/mc/ap/model/set/PieceSlot.java +++ b/src/main/java/gg/steve/mc/ap/model/set/ArmorPieceSlot.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.set; -public enum PieceSlot { +public enum ArmorPieceSlot { HELMET, CHESTPLATE, LEGGINGS, diff --git a/src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java b/src/main/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResult.java similarity index 71% rename from src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java rename to src/main/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResult.java index e010a03..b04af51 100644 --- a/src/main/java/gg/steve/mc/ap/model/set/PurchaseResult.java +++ b/src/main/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResult.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.set; -public enum PurchaseResult { +public enum ArmorSetPurchaseResult { SUCCESS, INSUFFICIENT_FUNDS, NO_PERMISSION; diff --git a/src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java similarity index 75% rename from src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java rename to src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java index 5e41549..22d283d 100644 --- a/src/test/java/gg/steve/mc/ap/model/ability/HandItemSpecTest.java +++ b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java @@ -6,11 +6,11 @@ import static org.junit.jupiter.api.Assertions.*; -class HandItemSpecTest { +class ArmorHandItemSpecTest { @Test void builderCreatesExpectedValues() { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(2.5) .requireSet(true) .damageCause("ENTITY_ATTACK") @@ -23,7 +23,7 @@ void builderCreatesExpectedValues() { @Test void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(1.5) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -36,7 +36,7 @@ void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { @Test void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(1.5) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -49,14 +49,14 @@ void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { @ParameterizedTest @CsvSource({ - "10.0, 1.5, 3.0, 35.0", // 10 * (3-1 + 1.5) = 10 * 3.5 - "5.0, 2.0, 1.0, 10.0", // 5 * (1-1 + 2) = 5 * 2 - "8.0, 1.0, 2.0, 16.0", // 8 * (2-1 + 1) = 8 * 2 - "0.0, 1.5, 2.0, 0.0", // 0 * anything = 0 + "10.0, 1.5, 3.0, 35.0", + "5.0, 2.0, 1.0, 10.0", + "8.0, 1.0, 2.0, 16.0", + "0.0, 1.5, 2.0, 0.0", }) void calculateFinalDamage_withSetIncrease_parametrized( double damage, double increase, double setIncrease, double expected) { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(increase) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -67,14 +67,14 @@ void calculateFinalDamage_withSetIncrease_parametrized( @ParameterizedTest @CsvSource({ - "10.0, 1.5, 15.0", // 10 * 1.5 - "5.0, 2.0, 10.0", // 5 * 2 - "0.0, 3.0, 0.0", // 0 * anything = 0 - "8.0, 0.5, 4.0", // 8 * 0.5 + "10.0, 1.5, 15.0", + "5.0, 2.0, 10.0", + "0.0, 3.0, 0.0", + "8.0, 0.5, 4.0", }) void calculateFinalDamage_withoutSetIncrease_parametrized( double damage, double increase, double expected) { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(increase) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -85,8 +85,7 @@ void calculateFinalDamage_withoutSetIncrease_parametrized( @Test void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { - // Edge case: setIncrease=1 -> set part = 1-1=0, so result = damage * (0 + increase) = damage * increase - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(2.0) .requireSet(true) .damageCause("PROJECTILE") @@ -97,11 +96,11 @@ void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { @Test void equalsAndHashCode() { - HandItemSpec a = HandItemSpec.builder() + ArmorHandItemSpec a = ArmorHandItemSpec.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); - HandItemSpec b = HandItemSpec.builder() + ArmorHandItemSpec b = ArmorHandItemSpec.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); - HandItemSpec c = HandItemSpec.builder() + ArmorHandItemSpec c = ArmorHandItemSpec.builder() .increase(2.0).requireSet(false).damageCause("PROJECTILE").build(); assertEquals(a, b); @@ -111,7 +110,7 @@ void equalsAndHashCode() { @Test void toStringContainsFields() { - HandItemSpec spec = HandItemSpec.builder() + ArmorHandItemSpec spec = ArmorHandItemSpec.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); String s = spec.toString(); assertTrue(s.contains("1.5")); diff --git a/src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java b/src/test/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityTypeTest.java similarity index 52% rename from src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java rename to src/test/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityTypeTest.java index 2149e5c..d508a05 100644 --- a/src/test/java/gg/steve/mc/ap/model/ability/AbilityTypeTest.java +++ b/src/test/java/gg/steve/mc/ap/model/ability/ArmorSetAbilityTypeTest.java @@ -4,18 +4,18 @@ import static org.junit.jupiter.api.Assertions.*; -class AbilityTypeTest { +class ArmorSetAbilityTypeTest { @Test void allValuesPresent() { - AbilityType[] values = AbilityType.values(); + ArmorSetAbilityType[] values = ArmorSetAbilityType.values(); assertEquals(12, values.length); } @Test void valueOfRoundTrips() { - for (AbilityType type : AbilityType.values()) { - assertEquals(type, AbilityType.valueOf(type.name())); + for (ArmorSetAbilityType type : ArmorSetAbilityType.values()) { + assertEquals(type, ArmorSetAbilityType.valueOf(type.name())); } } } diff --git a/src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java b/src/test/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStatsTest.java similarity index 86% rename from src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java rename to src/test/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStatsTest.java index 718a83b..aa97410 100644 --- a/src/test/java/gg/steve/mc/ap/model/ability/BasicStatsTest.java +++ b/src/test/java/gg/steve/mc/ap/model/ability/ArmorSetBasicStatsTest.java @@ -4,11 +4,11 @@ import static org.junit.jupiter.api.Assertions.*; -class BasicStatsTest { +class ArmorSetBasicStatsTest { @Test void builderCreatesExpectedValues() { - BasicStats stats = BasicStats.builder() + ArmorSetBasicStats stats = ArmorSetBasicStats.builder() .increase(1.5) .reduction(0.8) .knockback(0.3) @@ -31,11 +31,11 @@ void builderCreatesExpectedValues() { @Test void equalsAndHashCode() { - BasicStats a = BasicStats.builder() + ArmorSetBasicStats a = ArmorSetBasicStats.builder() .increase(1.5).reduction(0.8).knockback(0.3).health(30.0) .walkSpeed(0.25f).walkSpeedDefault(0.2f) .flySpeed(0.15f).flySpeedDefault(0.1f).build(); - BasicStats b = BasicStats.builder() + ArmorSetBasicStats b = ArmorSetBasicStats.builder() .increase(1.5).reduction(0.8).knockback(0.3).health(30.0) .walkSpeed(0.25f).walkSpeedDefault(0.2f) .flySpeed(0.15f).flySpeedDefault(0.1f).build(); @@ -46,7 +46,7 @@ void equalsAndHashCode() { @Test void disabledValuesUseNegativeOne() { - BasicStats stats = BasicStats.builder() + ArmorSetBasicStats stats = ArmorSetBasicStats.builder() .increase(-1).reduction(-1).knockback(-1).health(-1) .walkSpeed(-1f).walkSpeedDefault(-1f) .flySpeed(-1f).flySpeedDefault(-1f).build(); diff --git a/src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java similarity index 85% rename from src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java rename to src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java index 53fbf84..e4e30f8 100644 --- a/src/test/java/gg/steve/mc/ap/model/combat/DamageContextTest.java +++ b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java @@ -6,14 +6,14 @@ import static org.junit.jupiter.api.Assertions.*; -class DamageContextTest { +class CombatDamageContextTest { @Test void builderCreatesExpectedValues() { UUID attacker = UUID.randomUUID(); UUID target = UUID.randomUUID(); - DamageContext ctx = DamageContext.builder() + CombatDamageContext ctx = CombatDamageContext.builder() .attacker(attacker) .target(target) .baseDamage(10.0) @@ -33,10 +33,10 @@ void equalsAndHashCode() { UUID attacker = UUID.fromString("00000000-0000-0000-0000-000000000001"); UUID target = UUID.fromString("00000000-0000-0000-0000-000000000002"); - DamageContext a = DamageContext.builder() + CombatDamageContext a = CombatDamageContext.builder() .attacker(attacker).target(target) .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); - DamageContext b = DamageContext.builder() + CombatDamageContext b = CombatDamageContext.builder() .attacker(attacker).target(target) .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); diff --git a/src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageModificationTest.java similarity index 56% rename from src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java rename to src/test/java/gg/steve/mc/ap/model/combat/CombatDamageModificationTest.java index 5af4a4f..0dd866d 100644 --- a/src/test/java/gg/steve/mc/ap/model/combat/DamageModificationTest.java +++ b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageModificationTest.java @@ -4,20 +4,20 @@ import static org.junit.jupiter.api.Assertions.*; -class DamageModificationTest { +class CombatDamageModificationTest { @Test void constructsWithExpectedValues() { - DamageModification mod = new DamageModification(15.0, 0.5); + CombatDamageModification mod = new CombatDamageModification(15.0, 0.5); assertEquals(15.0, mod.getNewDamage()); assertEquals(0.5, mod.getKnockbackMultiplier()); } @Test void equalsAndHashCode() { - DamageModification a = new DamageModification(10.0, 1.0); - DamageModification b = new DamageModification(10.0, 1.0); - DamageModification c = new DamageModification(10.0, 2.0); + CombatDamageModification a = new CombatDamageModification(10.0, 1.0); + CombatDamageModification b = new CombatDamageModification(10.0, 1.0); + CombatDamageModification c = new CombatDamageModification(10.0, 2.0); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java similarity index 56% rename from src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java rename to src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java index 5305e4b..d5d1925 100644 --- a/src/test/java/gg/steve/mc/ap/model/effect/SoundSpecTest.java +++ b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java @@ -4,11 +4,11 @@ import static org.junit.jupiter.api.Assertions.*; -class SoundSpecTest { +class NotificationSoundSpecTest { @Test void constructsWithExpectedValues() { - SoundSpec spec = new SoundSpec("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); + NotificationSoundSpec spec = new NotificationSoundSpec("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); assertEquals("ENTITY_PLAYER_LEVELUP", spec.getName()); assertEquals(1.0f, spec.getVolume()); assertEquals(0.5f, spec.getPitch()); @@ -16,9 +16,9 @@ void constructsWithExpectedValues() { @Test void equalsAndHashCode() { - SoundSpec a = new SoundSpec("DING", 1.0f, 1.0f); - SoundSpec b = new SoundSpec("DING", 1.0f, 1.0f); - SoundSpec c = new SoundSpec("BOOM", 0.5f, 2.0f); + NotificationSoundSpec a = new NotificationSoundSpec("DING", 1.0f, 1.0f); + NotificationSoundSpec b = new NotificationSoundSpec("DING", 1.0f, 1.0f); + NotificationSoundSpec c = new NotificationSoundSpec("BOOM", 0.5f, 2.0f); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java similarity index 61% rename from src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java rename to src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java index 9c4c412..523d8e1 100644 --- a/src/test/java/gg/steve/mc/ap/model/effect/EffectSpecTest.java +++ b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java @@ -4,11 +4,11 @@ import static org.junit.jupiter.api.Assertions.*; -class EffectSpecTest { +class PotionEffectSpecTest { @Test void constructsWithExpectedValues() { - EffectSpec spec = new EffectSpec("SPEED", 200, 1); + PotionEffectSpec spec = new PotionEffectSpec("SPEED", 200, 1); assertEquals("SPEED", spec.getType()); assertEquals(200, spec.getDuration()); assertEquals(1, spec.getAmplifier()); @@ -16,9 +16,9 @@ void constructsWithExpectedValues() { @Test void equalsAndHashCode() { - EffectSpec a = new EffectSpec("SPEED", 200, 1); - EffectSpec b = new EffectSpec("SPEED", 200, 1); - EffectSpec c = new EffectSpec("JUMP", 100, 2); + PotionEffectSpec a = new PotionEffectSpec("SPEED", 200, 1); + PotionEffectSpec b = new PotionEffectSpec("SPEED", 200, 1); + PotionEffectSpec c = new PotionEffectSpec("JUMP", 100, 2); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java similarity index 73% rename from src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java rename to src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java index 17e9795..e8a0a2c 100644 --- a/src/test/java/gg/steve/mc/ap/model/notification/NotificationTest.java +++ b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.notification; -import gg.steve.mc.ap.model.effect.SoundSpec; +import gg.steve.mc.ap.model.effect.NotificationSoundSpec; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -9,15 +9,15 @@ import static org.junit.jupiter.api.Assertions.*; -class NotificationTest { +class ArmorSetNotificationTest { @Test void builderCreatesExpectedValues() { - SoundSpec sound = new SoundSpec("DING", 1.0f, 1.0f); + NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); List messages = Arrays.asList("Welcome!", "Enjoy your armor."); List commands = Collections.singletonList("give %player% diamond 1"); - Notification notification = Notification.builder() + ArmorSetNotification notification = ArmorSetNotification.builder() .messages(messages) .sound(sound) .commands(commands) @@ -30,13 +30,13 @@ void builderCreatesExpectedValues() { @Test void equalsAndHashCode() { - SoundSpec sound = new SoundSpec("DING", 1.0f, 1.0f); - Notification a = Notification.builder() + NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); + ArmorSetNotification a = ArmorSetNotification.builder() .messages(Collections.singletonList("Hi")) .sound(sound) .commands(Collections.emptyList()) .build(); - Notification b = Notification.builder() + ArmorSetNotification b = ArmorSetNotification.builder() .messages(Collections.singletonList("Hi")) .sound(sound) .commands(Collections.emptyList()) @@ -48,7 +48,7 @@ void equalsAndHashCode() { @Test void nullFieldsPermitted() { - Notification notification = Notification.builder() + ArmorSetNotification notification = ArmorSetNotification.builder() .messages(null) .sound(null) .commands(null) diff --git a/src/test/java/gg/steve/mc/ap/model/player/WearerTest.java b/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java similarity index 67% rename from src/test/java/gg/steve/mc/ap/model/player/WearerTest.java rename to src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java index cfcbee8..7b8f207 100644 --- a/src/test/java/gg/steve/mc/ap/model/player/WearerTest.java +++ b/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java @@ -6,12 +6,12 @@ import static org.junit.jupiter.api.Assertions.*; -class WearerTest { +class ArmorSetWearerTest { @Test void constructsWithExpectedValues() { UUID id = UUID.randomUUID(); - Wearer wearer = new Wearer(id, "dragon"); + ArmorSetWearer wearer = new ArmorSetWearer(id, "dragon"); assertEquals(id, wearer.getPlayerId()); assertEquals("dragon", wearer.getSetName()); @@ -20,9 +20,9 @@ void constructsWithExpectedValues() { @Test void equalsAndHashCode() { UUID id = UUID.fromString("00000000-0000-0000-0000-000000000001"); - Wearer a = new Wearer(id, "dragon"); - Wearer b = new Wearer(id, "dragon"); - Wearer c = new Wearer(id, "knight"); + ArmorSetWearer a = new ArmorSetWearer(id, "dragon"); + ArmorSetWearer b = new ArmorSetWearer(id, "dragon"); + ArmorSetWearer c = new ArmorSetWearer(id, "knight"); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/set/ArmorPieceSlotTest.java b/src/test/java/gg/steve/mc/ap/model/set/ArmorPieceSlotTest.java new file mode 100644 index 0000000..8cfc53b --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/set/ArmorPieceSlotTest.java @@ -0,0 +1,30 @@ +package gg.steve.mc.ap.model.set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ArmorPieceSlotTest { + + @Test + void allValuesPresent() { + ArmorPieceSlot[] values = ArmorPieceSlot.values(); + assertEquals(5, values.length); + } + + @Test + void valueOfRoundTrips() { + for (ArmorPieceSlot slot : ArmorPieceSlot.values()) { + assertEquals(slot, ArmorPieceSlot.valueOf(slot.name())); + } + } + + @Test + void expectedSlots() { + assertNotNull(ArmorPieceSlot.valueOf("HELMET")); + assertNotNull(ArmorPieceSlot.valueOf("CHESTPLATE")); + assertNotNull(ArmorPieceSlot.valueOf("LEGGINGS")); + assertNotNull(ArmorPieceSlot.valueOf("BOOTS")); + assertNotNull(ArmorPieceSlot.valueOf("HAND")); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResultTest.java b/src/test/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResultTest.java new file mode 100644 index 0000000..49d0165 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/set/ArmorSetPurchaseResultTest.java @@ -0,0 +1,21 @@ +package gg.steve.mc.ap.model.set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ArmorSetPurchaseResultTest { + + @Test + void allValuesPresent() { + ArmorSetPurchaseResult[] values = ArmorSetPurchaseResult.values(); + assertEquals(3, values.length); + } + + @Test + void valueOfRoundTrips() { + for (ArmorSetPurchaseResult result : ArmorSetPurchaseResult.values()) { + assertEquals(result, ArmorSetPurchaseResult.valueOf(result.name())); + } + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java b/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java deleted file mode 100644 index 7e1f011..0000000 --- a/src/test/java/gg/steve/mc/ap/model/set/PieceSlotTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package gg.steve.mc.ap.model.set; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class PieceSlotTest { - - @Test - void allValuesPresent() { - PieceSlot[] values = PieceSlot.values(); - assertEquals(5, values.length); - } - - @Test - void valueOfRoundTrips() { - for (PieceSlot slot : PieceSlot.values()) { - assertEquals(slot, PieceSlot.valueOf(slot.name())); - } - } - - @Test - void expectedSlots() { - assertNotNull(PieceSlot.valueOf("HELMET")); - assertNotNull(PieceSlot.valueOf("CHESTPLATE")); - assertNotNull(PieceSlot.valueOf("LEGGINGS")); - assertNotNull(PieceSlot.valueOf("BOOTS")); - assertNotNull(PieceSlot.valueOf("HAND")); - } -} diff --git a/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java b/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java deleted file mode 100644 index ca803f7..0000000 --- a/src/test/java/gg/steve/mc/ap/model/set/PurchaseResultTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package gg.steve.mc.ap.model.set; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class PurchaseResultTest { - - @Test - void allValuesPresent() { - PurchaseResult[] values = PurchaseResult.values(); - assertEquals(3, values.length); - } - - @Test - void valueOfRoundTrips() { - for (PurchaseResult result : PurchaseResult.values()) { - assertEquals(result, PurchaseResult.valueOf(result.name())); - } - } -} From 8611078ff809ec133239da9b69535e82c8737f58 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 16:12:41 +1000 Subject: [PATCH 03/16] no-mistakes(document): Add ArmorHandItemSpec to core-gate lists in README and AGENTS --- AGENTS.md | 3 +-- README.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9cfa67f..2f62c0e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Build with JDK 25 or newer - `maven-enforcer-plugin` (`requireJavaVersion [25,)`) fails `validate` on anything older. Emitted plugin bytecode is intentionally Java 8 (major version 52) via `8` in `pom.xml`; the target runtime is a Java 17+ Spigot server. Do not "modernize" the bytecode level. Build/verify: `JAVA_HOME= mvn -B clean verify`. - `de.tr7zw:functional-annotations` is pinned to `0.1-SNAPSHOT` because no released version has ever been published (CodeMC metadata lists only the SNAPSHOT). It is a real compile dependency (`de.tr7zw.annotations.FAUtil`, used by the `nbt` package), so it cannot be dropped. Revisit if upstream ever cuts a release. - Test harness: JUnit 5 + MockBukkit + Mockito + JaCoCo. `mvn verify` runs tests and produces `target/site/jacoco/` coverage report. JaCoCo excludes `**/nbt/**` (vendored) and `**/ArmorPlus.class` (bootstrap). -- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. +- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItemSpec`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. - JDK/bytecode split: main code compiles to Java 8 (`maven.compiler.release=8`), test code compiles to Java 17 (`maven.compiler.testRelease=17`). The `testRelease` property is picked up automatically by maven-compiler-plugin's default-testCompile execution - no custom execution block needed. Both compile under the same JDK 25 build. MockBukkit + paper-api are Java 17 bytecode, so tests cannot target 8. - MockBukkit limitation: the plugin cannot be loaded via `MockBukkit.load(ArmorPlus.class)` because the vendored NBT-API performs reflection at enable time that MockBukkit does not model. Use pure-logic unit tests or Mockito for testing pieces that interact with Bukkit APIs. - Mockito on JDK 25: surefire must pass `-Dnet.bytebuddy.experimental=true` (configured in pom.xml ``). Without it, ByteBuddy fails to recognize the JDK 25 class file version. @@ -14,7 +14,6 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. - Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. -- JaCoCo core gate includes `gg.steve.mc.ap.model.ability.ArmorHandItemSpec` (has hand-written `calculateFinalDamage` logic). Pure Lombok data carriers are tested but not in the strict gate. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. ## Maintaining this file diff --git a/README.md b/README.md index ac93a9d..67389ce 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For more information about the plugin, permissions, and commands please refer to ## Code Coverage JaCoCo runs on every CI build (`mvn verify`). -The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`); a coverage drop on any of these will fail the build. +The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItemSpec`); a coverage drop on any of these will fail the build. The HTML coverage report is uploaded as the **jacoco-report** artifact on each workflow run - download it from the [Actions tab](https://github.com/nbdSteve/ArmorPlus/actions/workflows/ci.yml). ## Soft Dependencies From 3643cce8b9e7929e4594536d0d232c982f9dc089 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 16:24:33 +1000 Subject: [PATCH 04/16] fix: make ArmorSetNotification lists genuinely immutable Use Lombok @Singular on messages and commands fields so the builder produces unmodifiable defensive copies at build() time. Callers retaining the original list can no longer mutate the notification post-construction. Tests added proving: - Mutating the source list after build does not affect stored value - getMessages()/getCommands() throw on modification attempts - Empty lists returned (not null) when no items added --- .../notification/ArmorSetNotification.java | 5 +- .../ArmorSetNotificationTest.java | 80 +++++++++++++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java index c682a80..6ba5edd 100644 --- a/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java +++ b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java @@ -2,6 +2,7 @@ import gg.steve.mc.ap.model.effect.NotificationSoundSpec; import lombok.Builder; +import lombok.Singular; import lombok.Value; import java.util.List; @@ -9,7 +10,7 @@ @Value @Builder public class ArmorSetNotification { - List messages; + @Singular List messages; NotificationSoundSpec sound; - List commands; + @Singular List commands; } diff --git a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java index e8a0a2c..943f000 100644 --- a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java +++ b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java @@ -3,8 +3,8 @@ import gg.steve.mc.ap.model.effect.NotificationSoundSpec; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -14,32 +14,29 @@ class ArmorSetNotificationTest { @Test void builderCreatesExpectedValues() { NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); - List messages = Arrays.asList("Welcome!", "Enjoy your armor."); - List commands = Collections.singletonList("give %player% diamond 1"); ArmorSetNotification notification = ArmorSetNotification.builder() - .messages(messages) + .message("Welcome!") + .message("Enjoy your armor.") .sound(sound) - .commands(commands) + .command("give %player% diamond 1") .build(); - assertEquals(messages, notification.getMessages()); + assertEquals(Arrays.asList("Welcome!", "Enjoy your armor."), notification.getMessages()); assertEquals(sound, notification.getSound()); - assertEquals(commands, notification.getCommands()); + assertEquals(Arrays.asList("give %player% diamond 1"), notification.getCommands()); } @Test void equalsAndHashCode() { NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); ArmorSetNotification a = ArmorSetNotification.builder() - .messages(Collections.singletonList("Hi")) + .message("Hi") .sound(sound) - .commands(Collections.emptyList()) .build(); ArmorSetNotification b = ArmorSetNotification.builder() - .messages(Collections.singletonList("Hi")) + .message("Hi") .sound(sound) - .commands(Collections.emptyList()) .build(); assertEquals(a, b); @@ -47,15 +44,64 @@ void equalsAndHashCode() { } @Test - void nullFieldsPermitted() { + void emptyListsWhenNoItemsAdded() { ArmorSetNotification notification = ArmorSetNotification.builder() - .messages(null) .sound(null) - .commands(null) .build(); - assertNull(notification.getMessages()); - assertNull(notification.getSound()); - assertNull(notification.getCommands()); + assertNotNull(notification.getMessages()); + assertTrue(notification.getMessages().isEmpty()); + assertNotNull(notification.getCommands()); + assertTrue(notification.getCommands().isEmpty()); + } + + @Test + void bulkMessagesMethodDefensivelyCopies() { + List original = new ArrayList<>(Arrays.asList("A", "B")); + + ArmorSetNotification notification = ArmorSetNotification.builder() + .messages(original) + .command("cmd") + .build(); + + original.add("C"); + + assertEquals(Arrays.asList("A", "B"), notification.getMessages()); + } + + @Test + void bulkCommandsMethodDefensivelyCopies() { + List original = new ArrayList<>(Arrays.asList("x", "y")); + + ArmorSetNotification notification = ArmorSetNotification.builder() + .commands(original) + .message("msg") + .build(); + + original.add("z"); + + assertEquals(Arrays.asList("x", "y"), notification.getCommands()); + } + + @Test + void getMessagesReturnsUnmodifiableList() { + ArmorSetNotification notification = ArmorSetNotification.builder() + .message("hello") + .command("cmd") + .build(); + + assertThrows(UnsupportedOperationException.class, + () -> notification.getMessages().add("sneaky")); + } + + @Test + void getCommandsReturnsUnmodifiableList() { + ArmorSetNotification notification = ArmorSetNotification.builder() + .message("msg") + .command("run") + .build(); + + assertThrows(UnsupportedOperationException.class, + () -> notification.getCommands().add("sneaky")); } } From dffc2df33a89b46dc69fe6d047dd8fc9b0ce010f Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 16:31:41 +1000 Subject: [PATCH 05/16] no-mistakes(document): Document @Singular immutability convention for model collection fields --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 2f62c0e..ef143d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. -- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. +- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. From 9fe350c808e83f4cb4844ed6cbebddf2528a9b68 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Thu, 9 Jul 2026 16:45:21 +1000 Subject: [PATCH 06/16] refactor: drop Spec suffix from model effect types Rename PotionEffectSpec -> PotionEffect and NotificationSoundSpec -> NotificationSound. The clean short name belongs to the domain model; any future adapter code referencing the Bukkit PotionEffect uses the fully-qualified org.bukkit.potion.PotionEffect at that boundary. --- ...nSoundSpec.java => NotificationSound.java} | 2 +- ...otionEffectSpec.java => PotionEffect.java} | 2 +- .../notification/ArmorSetNotification.java | 4 +-- .../effect/NotificationSoundSpecTest.java | 27 ------------------- .../model/effect/NotificationSoundTest.java | 27 +++++++++++++++++++ .../ap/model/effect/PotionEffectSpecTest.java | 27 ------------------- .../mc/ap/model/effect/PotionEffectTest.java | 27 +++++++++++++++++++ .../ArmorSetNotificationTest.java | 6 ++--- 8 files changed, 61 insertions(+), 61 deletions(-) rename src/main/java/gg/steve/mc/ap/model/effect/{NotificationSoundSpec.java => NotificationSound.java} (76%) rename src/main/java/gg/steve/mc/ap/model/effect/{PotionEffectSpec.java => PotionEffect.java} (79%) delete mode 100644 src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java delete mode 100644 src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java diff --git a/src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java similarity index 76% rename from src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java rename to src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java index 05a0024..49e50ef 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/NotificationSoundSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java @@ -3,7 +3,7 @@ import lombok.Value; @Value -public class NotificationSoundSpec { +public class NotificationSound { String name; float volume; float pitch; diff --git a/src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java similarity index 79% rename from src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java rename to src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java index 82cde76..2f2b655 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/PotionEffectSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java @@ -3,7 +3,7 @@ import lombok.Value; @Value -public class PotionEffectSpec { +public class PotionEffect { String type; int duration; int amplifier; diff --git a/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java index 6ba5edd..427cb81 100644 --- a/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java +++ b/src/main/java/gg/steve/mc/ap/model/notification/ArmorSetNotification.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.notification; -import gg.steve.mc.ap.model.effect.NotificationSoundSpec; +import gg.steve.mc.ap.model.effect.NotificationSound; import lombok.Builder; import lombok.Singular; import lombok.Value; @@ -11,6 +11,6 @@ @Builder public class ArmorSetNotification { @Singular List messages; - NotificationSoundSpec sound; + NotificationSound sound; @Singular List commands; } diff --git a/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java deleted file mode 100644 index d5d1925..0000000 --- a/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundSpecTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package gg.steve.mc.ap.model.effect; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class NotificationSoundSpecTest { - - @Test - void constructsWithExpectedValues() { - NotificationSoundSpec spec = new NotificationSoundSpec("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); - assertEquals("ENTITY_PLAYER_LEVELUP", spec.getName()); - assertEquals(1.0f, spec.getVolume()); - assertEquals(0.5f, spec.getPitch()); - } - - @Test - void equalsAndHashCode() { - NotificationSoundSpec a = new NotificationSoundSpec("DING", 1.0f, 1.0f); - NotificationSoundSpec b = new NotificationSoundSpec("DING", 1.0f, 1.0f); - NotificationSoundSpec c = new NotificationSoundSpec("BOOM", 0.5f, 2.0f); - - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - assertNotEquals(a, c); - } -} diff --git a/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java new file mode 100644 index 0000000..f367e21 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java @@ -0,0 +1,27 @@ +package gg.steve.mc.ap.model.effect; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NotificationSoundTest { + + @Test + void constructsWithExpectedValues() { + NotificationSound sound = new NotificationSound("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); + assertEquals("ENTITY_PLAYER_LEVELUP", sound.getName()); + assertEquals(1.0f, sound.getVolume()); + assertEquals(0.5f, sound.getPitch()); + } + + @Test + void equalsAndHashCode() { + NotificationSound a = new NotificationSound("DING", 1.0f, 1.0f); + NotificationSound b = new NotificationSound("DING", 1.0f, 1.0f); + NotificationSound c = new NotificationSound("BOOM", 0.5f, 2.0f); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java deleted file mode 100644 index 523d8e1..0000000 --- a/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectSpecTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package gg.steve.mc.ap.model.effect; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class PotionEffectSpecTest { - - @Test - void constructsWithExpectedValues() { - PotionEffectSpec spec = new PotionEffectSpec("SPEED", 200, 1); - assertEquals("SPEED", spec.getType()); - assertEquals(200, spec.getDuration()); - assertEquals(1, spec.getAmplifier()); - } - - @Test - void equalsAndHashCode() { - PotionEffectSpec a = new PotionEffectSpec("SPEED", 200, 1); - PotionEffectSpec b = new PotionEffectSpec("SPEED", 200, 1); - PotionEffectSpec c = new PotionEffectSpec("JUMP", 100, 2); - - assertEquals(a, b); - assertEquals(a.hashCode(), b.hashCode()); - assertNotEquals(a, c); - } -} diff --git a/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java new file mode 100644 index 0000000..76f895c --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java @@ -0,0 +1,27 @@ +package gg.steve.mc.ap.model.effect; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PotionEffectTest { + + @Test + void constructsWithExpectedValues() { + PotionEffect effect = new PotionEffect("SPEED", 200, 1); + assertEquals("SPEED", effect.getType()); + assertEquals(200, effect.getDuration()); + assertEquals(1, effect.getAmplifier()); + } + + @Test + void equalsAndHashCode() { + PotionEffect a = new PotionEffect("SPEED", 200, 1); + PotionEffect b = new PotionEffect("SPEED", 200, 1); + PotionEffect c = new PotionEffect("JUMP", 100, 2); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java index 943f000..1824c6d 100644 --- a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java +++ b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.notification; -import gg.steve.mc.ap.model.effect.NotificationSoundSpec; +import gg.steve.mc.ap.model.effect.NotificationSound; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -13,7 +13,7 @@ class ArmorSetNotificationTest { @Test void builderCreatesExpectedValues() { - NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); + NotificationSound sound = new NotificationSound("DING", 1.0f, 1.0f); ArmorSetNotification notification = ArmorSetNotification.builder() .message("Welcome!") @@ -29,7 +29,7 @@ void builderCreatesExpectedValues() { @Test void equalsAndHashCode() { - NotificationSoundSpec sound = new NotificationSoundSpec("DING", 1.0f, 1.0f); + NotificationSound sound = new NotificationSound("DING", 1.0f, 1.0f); ArmorSetNotification a = ArmorSetNotification.builder() .message("Hi") .sound(sound) From 025cd8c030912c0bf7dc1607e0d14fe23dbb523c Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:05:17 +1000 Subject: [PATCH 07/16] refactor: drop Spec suffix from ArmorHandItemSpec -> ArmorHandItem Consistent with earlier rename of effect types - the domain model uses clean names without the Spec suffix. Updates JaCoCo core gate, README, and AGENTS.md references. --- AGENTS.md | 2 +- README.md | 2 +- pom.xml | 2 +- ...orHandItemSpec.java => ArmorHandItem.java} | 2 +- ...emSpecTest.java => ArmorHandItemTest.java} | 40 +++++++++---------- 5 files changed, 24 insertions(+), 24 deletions(-) rename src/main/java/gg/steve/mc/ap/model/ability/{ArmorHandItemSpec.java => ArmorHandItem.java} (94%) rename src/test/java/gg/steve/mc/ap/model/ability/{ArmorHandItemSpecTest.java => ArmorHandItemTest.java} (72%) diff --git a/AGENTS.md b/AGENTS.md index ef143d4..59f1c00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Build with JDK 25 or newer - `maven-enforcer-plugin` (`requireJavaVersion [25,)`) fails `validate` on anything older. Emitted plugin bytecode is intentionally Java 8 (major version 52) via `8` in `pom.xml`; the target runtime is a Java 17+ Spigot server. Do not "modernize" the bytecode level. Build/verify: `JAVA_HOME= mvn -B clean verify`. - `de.tr7zw:functional-annotations` is pinned to `0.1-SNAPSHOT` because no released version has ever been published (CodeMC metadata lists only the SNAPSHOT). It is a real compile dependency (`de.tr7zw.annotations.FAUtil`, used by the `nbt` package), so it cannot be dropped. Revisit if upstream ever cuts a release. - Test harness: JUnit 5 + MockBukkit + Mockito + JaCoCo. `mvn verify` runs tests and produces `target/site/jacoco/` coverage report. JaCoCo excludes `**/nbt/**` (vendored) and `**/ArmorPlus.class` (bootstrap). -- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItemSpec`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. +- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. - JDK/bytecode split: main code compiles to Java 8 (`maven.compiler.release=8`), test code compiles to Java 17 (`maven.compiler.testRelease=17`). The `testRelease` property is picked up automatically by maven-compiler-plugin's default-testCompile execution - no custom execution block needed. Both compile under the same JDK 25 build. MockBukkit + paper-api are Java 17 bytecode, so tests cannot target 8. - MockBukkit limitation: the plugin cannot be loaded via `MockBukkit.load(ArmorPlus.class)` because the vendored NBT-API performs reflection at enable time that MockBukkit does not model. Use pure-logic unit tests or Mockito for testing pieces that interact with Bukkit APIs. - Mockito on JDK 25: surefire must pass `-Dnet.bytebuddy.experimental=true` (configured in pom.xml ``). Without it, ByteBuddy fails to recognize the JDK 25 class file version. diff --git a/README.md b/README.md index 67389ce..24f89b5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For more information about the plugin, permissions, and commands please refer to ## Code Coverage JaCoCo runs on every CI build (`mvn verify`). -The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItemSpec`); a coverage drop on any of these will fail the build. +The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`); a coverage drop on any of these will fail the build. The HTML coverage report is uploaded as the **jacoco-report** artifact on each workflow run - download it from the [Actions tab](https://github.com/nbdSteve/ArmorPlus/actions/workflows/ci.yml). ## Soft Dependencies diff --git a/pom.xml b/pom.xml index aa61b68..67536d2 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ gg.steve.mc.ap.armor.Piece gg.steve.mc.ap.armor.SetType gg.steve.mc.ap.data.SetDataType - gg.steve.mc.ap.model.ability.ArmorHandItemSpec + gg.steve.mc.ap.model.ability.ArmorHandItem diff --git a/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java similarity index 94% rename from src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java rename to src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java index 5026b38..c1a9bc6 100644 --- a/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpec.java +++ b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java @@ -5,7 +5,7 @@ @Value @Builder -public class ArmorHandItemSpec { +public class ArmorHandItem { double increase; boolean requireSet; String damageCause; diff --git a/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java similarity index 72% rename from src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java rename to src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java index 22d283d..5252a7d 100644 --- a/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemSpecTest.java +++ b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java @@ -6,24 +6,24 @@ import static org.junit.jupiter.api.Assertions.*; -class ArmorHandItemSpecTest { +class ArmorHandItemTest { @Test void builderCreatesExpectedValues() { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(2.5) .requireSet(true) .damageCause("ENTITY_ATTACK") .build(); - assertEquals(2.5, spec.getIncrease()); - assertTrue(spec.isRequireSet()); - assertEquals("ENTITY_ATTACK", spec.getDamageCause()); + assertEquals(2.5, item.getIncrease()); + assertTrue(item.isRequireSet()); + assertEquals("ENTITY_ATTACK", item.getDamageCause()); } @Test void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(1.5) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -31,12 +31,12 @@ void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { // setIncrease != -1: result = damage * (setIncrease - 1 + handIncrease) // damage=10, setIncrease=2.0 -> 10 * (2.0 - 1 + 1.5) = 10 * 2.5 = 25.0 - assertEquals(25.0, spec.calculateFinalDamage(10.0, 2.0), 0.0001); + assertEquals(25.0, item.calculateFinalDamage(10.0, 2.0), 0.0001); } @Test void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(1.5) .requireSet(false) .damageCause("ENTITY_ATTACK") @@ -44,7 +44,7 @@ void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { // setIncrease == -1: result = damage * handIncrease // damage=10, setIncrease=-1 -> 10 * 1.5 = 15.0 - assertEquals(15.0, spec.calculateFinalDamage(10.0, -1), 0.0001); + assertEquals(15.0, item.calculateFinalDamage(10.0, -1), 0.0001); } @ParameterizedTest @@ -56,13 +56,13 @@ void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { }) void calculateFinalDamage_withSetIncrease_parametrized( double damage, double increase, double setIncrease, double expected) { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(increase) .requireSet(false) .damageCause("ENTITY_ATTACK") .build(); - assertEquals(expected, spec.calculateFinalDamage(damage, setIncrease), 0.0001); + assertEquals(expected, item.calculateFinalDamage(damage, setIncrease), 0.0001); } @ParameterizedTest @@ -74,33 +74,33 @@ void calculateFinalDamage_withSetIncrease_parametrized( }) void calculateFinalDamage_withoutSetIncrease_parametrized( double damage, double increase, double expected) { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(increase) .requireSet(false) .damageCause("ENTITY_ATTACK") .build(); - assertEquals(expected, spec.calculateFinalDamage(damage, -1), 0.0001); + assertEquals(expected, item.calculateFinalDamage(damage, -1), 0.0001); } @Test void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(2.0) .requireSet(true) .damageCause("PROJECTILE") .build(); - assertEquals(20.0, spec.calculateFinalDamage(10.0, 1.0), 0.0001); + assertEquals(20.0, item.calculateFinalDamage(10.0, 1.0), 0.0001); } @Test void equalsAndHashCode() { - ArmorHandItemSpec a = ArmorHandItemSpec.builder() + ArmorHandItem a = ArmorHandItem.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); - ArmorHandItemSpec b = ArmorHandItemSpec.builder() + ArmorHandItem b = ArmorHandItem.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); - ArmorHandItemSpec c = ArmorHandItemSpec.builder() + ArmorHandItem c = ArmorHandItem.builder() .increase(2.0).requireSet(false).damageCause("PROJECTILE").build(); assertEquals(a, b); @@ -110,9 +110,9 @@ void equalsAndHashCode() { @Test void toStringContainsFields() { - ArmorHandItemSpec spec = ArmorHandItemSpec.builder() + ArmorHandItem item = ArmorHandItem.builder() .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); - String s = spec.toString(); + String s = item.toString(); assertTrue(s.contains("1.5")); assertTrue(s.contains("ENTITY_ATTACK")); } From 706fbf95e7d58cf584b18d98d47ddde429920945 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:17:56 +1000 Subject: [PATCH 08/16] feat: introduce typed identity wrappers and rename ArmorHandItemSpec Add model.id sub-package with typed string/UUID wrappers to eliminate primitive obsession for domain identity keys: - PlayerId (wraps UUID) - ArmorSetId (wraps String set name) - DamageCauseId (wraps String damage cause key) - PotionEffectId (wraps String effect type name) - SoundId (wraps String sound name key) Each provides a static of() factory. All model value types now use these typed IDs instead of raw String/UUID for identity fields. Free-text strings (messages, commands) remain unwrapped. Also renames ArmorHandItemSpec -> ArmorHandItem (drops Spec suffix consistent with prior effect type renames). --- .../mc/ap/model/ability/ArmorHandItem.java | 3 +- .../ap/model/combat/CombatDamageContext.java | 10 +++---- .../mc/ap/model/effect/NotificationSound.java | 3 +- .../mc/ap/model/effect/PotionEffect.java | 3 +- .../gg/steve/mc/ap/model/id/ArmorSetId.java | 12 ++++++++ .../steve/mc/ap/model/id/DamageCauseId.java | 12 ++++++++ .../gg/steve/mc/ap/model/id/PlayerId.java | 14 +++++++++ .../steve/mc/ap/model/id/PotionEffectId.java | 12 ++++++++ .../java/gg/steve/mc/ap/model/id/SoundId.java | 12 ++++++++ .../mc/ap/model/player/ArmorSetWearer.java | 8 ++--- .../ap/model/ability/ArmorHandItemTest.java | 30 +++++++++---------- .../model/combat/CombatDamageContextTest.java | 20 ++++++++----- .../model/effect/NotificationSoundTest.java | 11 +++---- .../mc/ap/model/effect/PotionEffectTest.java | 11 +++---- .../steve/mc/ap/model/id/ArmorSetIdTest.java | 25 ++++++++++++++++ .../mc/ap/model/id/DamageCauseIdTest.java | 25 ++++++++++++++++ .../gg/steve/mc/ap/model/id/PlayerIdTest.java | 29 ++++++++++++++++++ .../mc/ap/model/id/PotionEffectIdTest.java | 25 ++++++++++++++++ .../gg/steve/mc/ap/model/id/SoundIdTest.java | 25 ++++++++++++++++ .../ArmorSetNotificationTest.java | 5 ++-- .../ap/model/player/ArmorSetWearerTest.java | 17 ++++++----- 21 files changed, 258 insertions(+), 54 deletions(-) create mode 100644 src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java create mode 100644 src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java create mode 100644 src/main/java/gg/steve/mc/ap/model/id/PlayerId.java create mode 100644 src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java create mode 100644 src/main/java/gg/steve/mc/ap/model/id/SoundId.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java diff --git a/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java index c1a9bc6..b103012 100644 --- a/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java +++ b/src/main/java/gg/steve/mc/ap/model/ability/ArmorHandItem.java @@ -1,5 +1,6 @@ package gg.steve.mc.ap.model.ability; +import gg.steve.mc.ap.model.id.DamageCauseId; import lombok.Builder; import lombok.Value; @@ -8,7 +9,7 @@ public class ArmorHandItem { double increase; boolean requireSet; - String damageCause; + DamageCauseId damageCause; /** * Pure damage calculation mirroring HandSetData.calculateFinalDamage. diff --git a/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java index 1e42357..2415276 100644 --- a/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java +++ b/src/main/java/gg/steve/mc/ap/model/combat/CombatDamageContext.java @@ -1,16 +1,16 @@ package gg.steve.mc.ap.model.combat; +import gg.steve.mc.ap.model.id.DamageCauseId; +import gg.steve.mc.ap.model.id.PlayerId; import lombok.Builder; import lombok.Value; -import java.util.UUID; - @Value @Builder public class CombatDamageContext { - UUID attacker; - UUID target; + PlayerId attacker; + PlayerId target; double baseDamage; - String cause; + DamageCauseId cause; boolean projectile; } diff --git a/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java index 49e50ef..779784e 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/NotificationSound.java @@ -1,10 +1,11 @@ package gg.steve.mc.ap.model.effect; +import gg.steve.mc.ap.model.id.SoundId; import lombok.Value; @Value public class NotificationSound { - String name; + SoundId name; float volume; float pitch; } diff --git a/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java index 2f2b655..30eb24e 100644 --- a/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java +++ b/src/main/java/gg/steve/mc/ap/model/effect/PotionEffect.java @@ -1,10 +1,11 @@ package gg.steve.mc.ap.model.effect; +import gg.steve.mc.ap.model.id.PotionEffectId; import lombok.Value; @Value public class PotionEffect { - String type; + PotionEffectId type; int duration; int amplifier; } diff --git a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java new file mode 100644 index 0000000..45938e6 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java @@ -0,0 +1,12 @@ +package gg.steve.mc.ap.model.id; + +import lombok.Value; + +@Value +public class ArmorSetId { + String value; + + public static ArmorSetId of(String value) { + return new ArmorSetId(value); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java new file mode 100644 index 0000000..eff436b --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java @@ -0,0 +1,12 @@ +package gg.steve.mc.ap.model.id; + +import lombok.Value; + +@Value +public class DamageCauseId { + String value; + + public static DamageCauseId of(String value) { + return new DamageCauseId(value); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java new file mode 100644 index 0000000..fcbadf1 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java @@ -0,0 +1,14 @@ +package gg.steve.mc.ap.model.id; + +import lombok.Value; + +import java.util.UUID; + +@Value +public class PlayerId { + UUID value; + + public static PlayerId of(UUID value) { + return new PlayerId(value); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java new file mode 100644 index 0000000..606bf06 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java @@ -0,0 +1,12 @@ +package gg.steve.mc.ap.model.id; + +import lombok.Value; + +@Value +public class PotionEffectId { + String value; + + public static PotionEffectId of(String value) { + return new PotionEffectId(value); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java new file mode 100644 index 0000000..ee10296 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java @@ -0,0 +1,12 @@ +package gg.steve.mc.ap.model.id; + +import lombok.Value; + +@Value +public class SoundId { + String value; + + public static SoundId of(String value) { + return new SoundId(value); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java b/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java index e9a6d83..b8276b8 100644 --- a/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java +++ b/src/main/java/gg/steve/mc/ap/model/player/ArmorSetWearer.java @@ -1,11 +1,11 @@ package gg.steve.mc.ap.model.player; +import gg.steve.mc.ap.model.id.ArmorSetId; +import gg.steve.mc.ap.model.id.PlayerId; import lombok.Value; -import java.util.UUID; - @Value public class ArmorSetWearer { - UUID playerId; - String setName; + PlayerId playerId; + ArmorSetId setId; } diff --git a/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java index 5252a7d..f38fc93 100644 --- a/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java +++ b/src/test/java/gg/steve/mc/ap/model/ability/ArmorHandItemTest.java @@ -1,5 +1,6 @@ package gg.steve.mc.ap.model.ability; +import gg.steve.mc.ap.model.id.DamageCauseId; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -8,17 +9,20 @@ class ArmorHandItemTest { + private static final DamageCauseId ENTITY_ATTACK = DamageCauseId.of("ENTITY_ATTACK"); + private static final DamageCauseId PROJECTILE = DamageCauseId.of("PROJECTILE"); + @Test void builderCreatesExpectedValues() { ArmorHandItem item = ArmorHandItem.builder() .increase(2.5) .requireSet(true) - .damageCause("ENTITY_ATTACK") + .damageCause(ENTITY_ATTACK) .build(); assertEquals(2.5, item.getIncrease()); assertTrue(item.isRequireSet()); - assertEquals("ENTITY_ATTACK", item.getDamageCause()); + assertEquals(ENTITY_ATTACK, item.getDamageCause()); } @Test @@ -26,11 +30,9 @@ void calculateFinalDamage_withSetIncrease_combinesBothMultipliers() { ArmorHandItem item = ArmorHandItem.builder() .increase(1.5) .requireSet(false) - .damageCause("ENTITY_ATTACK") + .damageCause(ENTITY_ATTACK) .build(); - // setIncrease != -1: result = damage * (setIncrease - 1 + handIncrease) - // damage=10, setIncrease=2.0 -> 10 * (2.0 - 1 + 1.5) = 10 * 2.5 = 25.0 assertEquals(25.0, item.calculateFinalDamage(10.0, 2.0), 0.0001); } @@ -39,11 +41,9 @@ void calculateFinalDamage_withoutSetIncrease_usesHandMultiplierOnly() { ArmorHandItem item = ArmorHandItem.builder() .increase(1.5) .requireSet(false) - .damageCause("ENTITY_ATTACK") + .damageCause(ENTITY_ATTACK) .build(); - // setIncrease == -1: result = damage * handIncrease - // damage=10, setIncrease=-1 -> 10 * 1.5 = 15.0 assertEquals(15.0, item.calculateFinalDamage(10.0, -1), 0.0001); } @@ -59,7 +59,7 @@ void calculateFinalDamage_withSetIncrease_parametrized( ArmorHandItem item = ArmorHandItem.builder() .increase(increase) .requireSet(false) - .damageCause("ENTITY_ATTACK") + .damageCause(ENTITY_ATTACK) .build(); assertEquals(expected, item.calculateFinalDamage(damage, setIncrease), 0.0001); @@ -77,7 +77,7 @@ void calculateFinalDamage_withoutSetIncrease_parametrized( ArmorHandItem item = ArmorHandItem.builder() .increase(increase) .requireSet(false) - .damageCause("ENTITY_ATTACK") + .damageCause(ENTITY_ATTACK) .build(); assertEquals(expected, item.calculateFinalDamage(damage, -1), 0.0001); @@ -88,7 +88,7 @@ void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { ArmorHandItem item = ArmorHandItem.builder() .increase(2.0) .requireSet(true) - .damageCause("PROJECTILE") + .damageCause(PROJECTILE) .build(); assertEquals(20.0, item.calculateFinalDamage(10.0, 1.0), 0.0001); @@ -97,11 +97,11 @@ void calculateFinalDamage_setIncreaseOfOne_yieldsHandMultiplierOnly() { @Test void equalsAndHashCode() { ArmorHandItem a = ArmorHandItem.builder() - .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + .increase(1.5).requireSet(true).damageCause(ENTITY_ATTACK).build(); ArmorHandItem b = ArmorHandItem.builder() - .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + .increase(1.5).requireSet(true).damageCause(ENTITY_ATTACK).build(); ArmorHandItem c = ArmorHandItem.builder() - .increase(2.0).requireSet(false).damageCause("PROJECTILE").build(); + .increase(2.0).requireSet(false).damageCause(PROJECTILE).build(); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); @@ -111,7 +111,7 @@ void equalsAndHashCode() { @Test void toStringContainsFields() { ArmorHandItem item = ArmorHandItem.builder() - .increase(1.5).requireSet(true).damageCause("ENTITY_ATTACK").build(); + .increase(1.5).requireSet(true).damageCause(ENTITY_ATTACK).build(); String s = item.toString(); assertTrue(s.contains("1.5")); assertTrue(s.contains("ENTITY_ATTACK")); diff --git a/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java index e4e30f8..14ae6bf 100644 --- a/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java +++ b/src/test/java/gg/steve/mc/ap/model/combat/CombatDamageContextTest.java @@ -1,5 +1,7 @@ package gg.steve.mc.ap.model.combat; +import gg.steve.mc.ap.model.id.DamageCauseId; +import gg.steve.mc.ap.model.id.PlayerId; import org.junit.jupiter.api.Test; import java.util.UUID; @@ -10,35 +12,37 @@ class CombatDamageContextTest { @Test void builderCreatesExpectedValues() { - UUID attacker = UUID.randomUUID(); - UUID target = UUID.randomUUID(); + PlayerId attacker = PlayerId.of(UUID.randomUUID()); + PlayerId target = PlayerId.of(UUID.randomUUID()); + DamageCauseId cause = DamageCauseId.of("ENTITY_ATTACK"); CombatDamageContext ctx = CombatDamageContext.builder() .attacker(attacker) .target(target) .baseDamage(10.0) - .cause("ENTITY_ATTACK") + .cause(cause) .projectile(false) .build(); assertEquals(attacker, ctx.getAttacker()); assertEquals(target, ctx.getTarget()); assertEquals(10.0, ctx.getBaseDamage()); - assertEquals("ENTITY_ATTACK", ctx.getCause()); + assertEquals(cause, ctx.getCause()); assertFalse(ctx.isProjectile()); } @Test void equalsAndHashCode() { - UUID attacker = UUID.fromString("00000000-0000-0000-0000-000000000001"); - UUID target = UUID.fromString("00000000-0000-0000-0000-000000000002"); + PlayerId attacker = PlayerId.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); + PlayerId target = PlayerId.of(UUID.fromString("00000000-0000-0000-0000-000000000002")); + DamageCauseId cause = DamageCauseId.of("PROJECTILE"); CombatDamageContext a = CombatDamageContext.builder() .attacker(attacker).target(target) - .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); + .baseDamage(5.0).cause(cause).projectile(true).build(); CombatDamageContext b = CombatDamageContext.builder() .attacker(attacker).target(target) - .baseDamage(5.0).cause("PROJECTILE").projectile(true).build(); + .baseDamage(5.0).cause(cause).projectile(true).build(); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java index f367e21..0eaa139 100644 --- a/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java +++ b/src/test/java/gg/steve/mc/ap/model/effect/NotificationSoundTest.java @@ -1,5 +1,6 @@ package gg.steve.mc.ap.model.effect; +import gg.steve.mc.ap.model.id.SoundId; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -8,17 +9,17 @@ class NotificationSoundTest { @Test void constructsWithExpectedValues() { - NotificationSound sound = new NotificationSound("ENTITY_PLAYER_LEVELUP", 1.0f, 0.5f); - assertEquals("ENTITY_PLAYER_LEVELUP", sound.getName()); + NotificationSound sound = new NotificationSound(SoundId.of("ENTITY_PLAYER_LEVELUP"), 1.0f, 0.5f); + assertEquals(SoundId.of("ENTITY_PLAYER_LEVELUP"), sound.getName()); assertEquals(1.0f, sound.getVolume()); assertEquals(0.5f, sound.getPitch()); } @Test void equalsAndHashCode() { - NotificationSound a = new NotificationSound("DING", 1.0f, 1.0f); - NotificationSound b = new NotificationSound("DING", 1.0f, 1.0f); - NotificationSound c = new NotificationSound("BOOM", 0.5f, 2.0f); + NotificationSound a = new NotificationSound(SoundId.of("DING"), 1.0f, 1.0f); + NotificationSound b = new NotificationSound(SoundId.of("DING"), 1.0f, 1.0f); + NotificationSound c = new NotificationSound(SoundId.of("BOOM"), 0.5f, 2.0f); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java index 76f895c..0afe6bc 100644 --- a/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java +++ b/src/test/java/gg/steve/mc/ap/model/effect/PotionEffectTest.java @@ -1,5 +1,6 @@ package gg.steve.mc.ap.model.effect; +import gg.steve.mc.ap.model.id.PotionEffectId; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -8,17 +9,17 @@ class PotionEffectTest { @Test void constructsWithExpectedValues() { - PotionEffect effect = new PotionEffect("SPEED", 200, 1); - assertEquals("SPEED", effect.getType()); + PotionEffect effect = new PotionEffect(PotionEffectId.of("SPEED"), 200, 1); + assertEquals(PotionEffectId.of("SPEED"), effect.getType()); assertEquals(200, effect.getDuration()); assertEquals(1, effect.getAmplifier()); } @Test void equalsAndHashCode() { - PotionEffect a = new PotionEffect("SPEED", 200, 1); - PotionEffect b = new PotionEffect("SPEED", 200, 1); - PotionEffect c = new PotionEffect("JUMP", 100, 2); + PotionEffect a = new PotionEffect(PotionEffectId.of("SPEED"), 200, 1); + PotionEffect b = new PotionEffect(PotionEffectId.of("SPEED"), 200, 1); + PotionEffect c = new PotionEffect(PotionEffectId.of("JUMP"), 100, 2); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); diff --git a/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java new file mode 100644 index 0000000..5f401ad --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java @@ -0,0 +1,25 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ArmorSetIdTest { + + @Test + void ofCreatesExpectedValue() { + ArmorSetId id = ArmorSetId.of("dragon"); + assertEquals("dragon", id.getValue()); + } + + @Test + void equalsAndHashCode() { + ArmorSetId a = ArmorSetId.of("dragon"); + ArmorSetId b = ArmorSetId.of("dragon"); + ArmorSetId c = ArmorSetId.of("knight"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java new file mode 100644 index 0000000..ccff299 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java @@ -0,0 +1,25 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DamageCauseIdTest { + + @Test + void ofCreatesExpectedValue() { + DamageCauseId id = DamageCauseId.of("ENTITY_ATTACK"); + assertEquals("ENTITY_ATTACK", id.getValue()); + } + + @Test + void equalsAndHashCode() { + DamageCauseId a = DamageCauseId.of("ENTITY_ATTACK"); + DamageCauseId b = DamageCauseId.of("ENTITY_ATTACK"); + DamageCauseId c = DamageCauseId.of("PROJECTILE"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java new file mode 100644 index 0000000..c9d483f --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java @@ -0,0 +1,29 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class PlayerIdTest { + + @Test + void ofCreatesExpectedValue() { + UUID uuid = UUID.randomUUID(); + PlayerId id = PlayerId.of(uuid); + assertEquals(uuid, id.getValue()); + } + + @Test + void equalsAndHashCode() { + UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000001"); + PlayerId a = PlayerId.of(uuid); + PlayerId b = PlayerId.of(uuid); + PlayerId c = PlayerId.of(UUID.fromString("00000000-0000-0000-0000-000000000002")); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java new file mode 100644 index 0000000..c2d34cf --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java @@ -0,0 +1,25 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PotionEffectIdTest { + + @Test + void ofCreatesExpectedValue() { + PotionEffectId id = PotionEffectId.of("SPEED"); + assertEquals("SPEED", id.getValue()); + } + + @Test + void equalsAndHashCode() { + PotionEffectId a = PotionEffectId.of("SPEED"); + PotionEffectId b = PotionEffectId.of("SPEED"); + PotionEffectId c = PotionEffectId.of("JUMP"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java new file mode 100644 index 0000000..f5323e3 --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java @@ -0,0 +1,25 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SoundIdTest { + + @Test + void ofCreatesExpectedValue() { + SoundId id = SoundId.of("ENTITY_PLAYER_LEVELUP"); + assertEquals("ENTITY_PLAYER_LEVELUP", id.getValue()); + } + + @Test + void equalsAndHashCode() { + SoundId a = SoundId.of("DING"); + SoundId b = SoundId.of("DING"); + SoundId c = SoundId.of("BOOM"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertNotEquals(a, c); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java index 1824c6d..13e1b60 100644 --- a/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java +++ b/src/test/java/gg/steve/mc/ap/model/notification/ArmorSetNotificationTest.java @@ -1,6 +1,7 @@ package gg.steve.mc.ap.model.notification; import gg.steve.mc.ap.model.effect.NotificationSound; +import gg.steve.mc.ap.model.id.SoundId; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -13,7 +14,7 @@ class ArmorSetNotificationTest { @Test void builderCreatesExpectedValues() { - NotificationSound sound = new NotificationSound("DING", 1.0f, 1.0f); + NotificationSound sound = new NotificationSound(SoundId.of("DING"), 1.0f, 1.0f); ArmorSetNotification notification = ArmorSetNotification.builder() .message("Welcome!") @@ -29,7 +30,7 @@ void builderCreatesExpectedValues() { @Test void equalsAndHashCode() { - NotificationSound sound = new NotificationSound("DING", 1.0f, 1.0f); + NotificationSound sound = new NotificationSound(SoundId.of("DING"), 1.0f, 1.0f); ArmorSetNotification a = ArmorSetNotification.builder() .message("Hi") .sound(sound) diff --git a/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java b/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java index 7b8f207..856d661 100644 --- a/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java +++ b/src/test/java/gg/steve/mc/ap/model/player/ArmorSetWearerTest.java @@ -1,5 +1,7 @@ package gg.steve.mc.ap.model.player; +import gg.steve.mc.ap.model.id.ArmorSetId; +import gg.steve.mc.ap.model.id.PlayerId; import org.junit.jupiter.api.Test; import java.util.UUID; @@ -10,19 +12,20 @@ class ArmorSetWearerTest { @Test void constructsWithExpectedValues() { - UUID id = UUID.randomUUID(); - ArmorSetWearer wearer = new ArmorSetWearer(id, "dragon"); + PlayerId id = PlayerId.of(UUID.randomUUID()); + ArmorSetId setId = ArmorSetId.of("dragon"); + ArmorSetWearer wearer = new ArmorSetWearer(id, setId); assertEquals(id, wearer.getPlayerId()); - assertEquals("dragon", wearer.getSetName()); + assertEquals(setId, wearer.getSetId()); } @Test void equalsAndHashCode() { - UUID id = UUID.fromString("00000000-0000-0000-0000-000000000001"); - ArmorSetWearer a = new ArmorSetWearer(id, "dragon"); - ArmorSetWearer b = new ArmorSetWearer(id, "dragon"); - ArmorSetWearer c = new ArmorSetWearer(id, "knight"); + PlayerId id = PlayerId.of(UUID.fromString("00000000-0000-0000-0000-000000000001")); + ArmorSetWearer a = new ArmorSetWearer(id, ArmorSetId.of("dragon")); + ArmorSetWearer b = new ArmorSetWearer(id, ArmorSetId.of("dragon")); + ArmorSetWearer c = new ArmorSetWearer(id, ArmorSetId.of("knight")); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); From 63f3ee6d66880d6feec3623725b430759459ba57 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:26:44 +1000 Subject: [PATCH 09/16] no-mistakes(review): Add null-precondition to ID wrappers; document id sub-package --- AGENTS.md | 2 +- src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java | 4 +++- src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java | 4 +++- src/main/java/gg/steve/mc/ap/model/id/PlayerId.java | 3 ++- src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java | 4 +++- src/main/java/gg/steve/mc/ap/model/id/SoundId.java | 4 +++- src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java | 5 +++++ src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java | 5 +++++ src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java | 5 +++++ .../java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java | 5 +++++ src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java | 5 +++++ 11 files changed, 40 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 59f1c00..e102f50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Mockito on JDK 25: surefire must pass `-Dnet.bytebuddy.experimental=true` (configured in pom.xml ``). Without it, ByteBuddy fails to recognize the JDK 25 class file version. - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). -- Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. +- Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`, `id`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. - Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. diff --git a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java index 45938e6..199d012 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java @@ -2,11 +2,13 @@ import lombok.Value; +import java.util.Objects; + @Value public class ArmorSetId { String value; public static ArmorSetId of(String value) { - return new ArmorSetId(value); + return new ArmorSetId(Objects.requireNonNull(value, "value must not be null")); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java index eff436b..69261e0 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java @@ -2,11 +2,13 @@ import lombok.Value; +import java.util.Objects; + @Value public class DamageCauseId { String value; public static DamageCauseId of(String value) { - return new DamageCauseId(value); + return new DamageCauseId(Objects.requireNonNull(value, "value must not be null")); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java index fcbadf1..164ac1a 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java @@ -2,6 +2,7 @@ import lombok.Value; +import java.util.Objects; import java.util.UUID; @Value @@ -9,6 +10,6 @@ public class PlayerId { UUID value; public static PlayerId of(UUID value) { - return new PlayerId(value); + return new PlayerId(Objects.requireNonNull(value, "value must not be null")); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java index 606bf06..9d671b5 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java @@ -2,11 +2,13 @@ import lombok.Value; +import java.util.Objects; + @Value public class PotionEffectId { String value; public static PotionEffectId of(String value) { - return new PotionEffectId(value); + return new PotionEffectId(Objects.requireNonNull(value, "value must not be null")); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java index ee10296..dea7974 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java @@ -2,11 +2,13 @@ import lombok.Value; +import java.util.Objects; + @Value public class SoundId { String value; public static SoundId of(String value) { - return new SoundId(value); + return new SoundId(Objects.requireNonNull(value, "value must not be null")); } } diff --git a/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java index 5f401ad..a432b30 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java @@ -12,6 +12,11 @@ void ofCreatesExpectedValue() { assertEquals("dragon", id.getValue()); } + @Test + void ofNullThrows() { + assertThrows(NullPointerException.class, () -> ArmorSetId.of(null)); + } + @Test void equalsAndHashCode() { ArmorSetId a = ArmorSetId.of("dragon"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java index ccff299..e0577aa 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java @@ -12,6 +12,11 @@ void ofCreatesExpectedValue() { assertEquals("ENTITY_ATTACK", id.getValue()); } + @Test + void ofNullThrows() { + assertThrows(NullPointerException.class, () -> DamageCauseId.of(null)); + } + @Test void equalsAndHashCode() { DamageCauseId a = DamageCauseId.of("ENTITY_ATTACK"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java index c9d483f..b9f929e 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java @@ -15,6 +15,11 @@ void ofCreatesExpectedValue() { assertEquals(uuid, id.getValue()); } + @Test + void ofNullThrows() { + assertThrows(NullPointerException.class, () -> PlayerId.of(null)); + } + @Test void equalsAndHashCode() { UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000001"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java index c2d34cf..43082b5 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java @@ -12,6 +12,11 @@ void ofCreatesExpectedValue() { assertEquals("SPEED", id.getValue()); } + @Test + void ofNullThrows() { + assertThrows(NullPointerException.class, () -> PotionEffectId.of(null)); + } + @Test void equalsAndHashCode() { PotionEffectId a = PotionEffectId.of("SPEED"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java index f5323e3..e2caf81 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java @@ -12,6 +12,11 @@ void ofCreatesExpectedValue() { assertEquals("ENTITY_PLAYER_LEVELUP", id.getValue()); } + @Test + void ofNullThrows() { + assertThrows(NullPointerException.class, () -> SoundId.of(null)); + } + @Test void equalsAndHashCode() { SoundId a = SoundId.of("DING"); From 0918054a1f4f1c41dd3f02f51014185d92b9aed3 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:35:35 +1000 Subject: [PATCH 10/16] fix: enforce null safety on typed ID wrappers via @NonNull Add Lombok @NonNull to the value field in all typed ID wrappers so both the of() factory and the generated constructor reject null. This closes the bypass where new ArmorSetId(null) would silently succeed. The of() factory now simply delegates to the constructor (Lombok handles the null check). Update AGENTS.md to reference typed ID wrappers as the identity convention. --- AGENTS.md | 2 +- src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java | 7 +++---- src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java | 7 +++---- src/main/java/gg/steve/mc/ap/model/id/PlayerId.java | 6 +++--- src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java | 7 +++---- src/main/java/gg/steve/mc/ap/model/id/SoundId.java | 7 +++---- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e102f50..aaefb7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`, `id`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. -- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use string/UUID IDs as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. +- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use the typed ID wrappers in model.id (PlayerId, ArmorSetId, DamageCauseId, PotionEffectId, SoundId) as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. diff --git a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java index 199d012..07b0811 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java @@ -1,14 +1,13 @@ package gg.steve.mc.ap.model.id; +import lombok.NonNull; import lombok.Value; -import java.util.Objects; - @Value public class ArmorSetId { - String value; + @NonNull String value; public static ArmorSetId of(String value) { - return new ArmorSetId(Objects.requireNonNull(value, "value must not be null")); + return new ArmorSetId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java index 69261e0..afd857f 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java @@ -1,14 +1,13 @@ package gg.steve.mc.ap.model.id; +import lombok.NonNull; import lombok.Value; -import java.util.Objects; - @Value public class DamageCauseId { - String value; + @NonNull String value; public static DamageCauseId of(String value) { - return new DamageCauseId(Objects.requireNonNull(value, "value must not be null")); + return new DamageCauseId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java index 164ac1a..1b6d3bb 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java @@ -1,15 +1,15 @@ package gg.steve.mc.ap.model.id; +import lombok.NonNull; import lombok.Value; -import java.util.Objects; import java.util.UUID; @Value public class PlayerId { - UUID value; + @NonNull UUID value; public static PlayerId of(UUID value) { - return new PlayerId(Objects.requireNonNull(value, "value must not be null")); + return new PlayerId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java index 9d671b5..44dc882 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java @@ -1,14 +1,13 @@ package gg.steve.mc.ap.model.id; +import lombok.NonNull; import lombok.Value; -import java.util.Objects; - @Value public class PotionEffectId { - String value; + @NonNull String value; public static PotionEffectId of(String value) { - return new PotionEffectId(Objects.requireNonNull(value, "value must not be null")); + return new PotionEffectId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java index dea7974..29cc9ea 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java @@ -1,14 +1,13 @@ package gg.steve.mc.ap.model.id; +import lombok.NonNull; import lombok.Value; -import java.util.Objects; - @Value public class SoundId { - String value; + @NonNull String value; public static SoundId of(String value) { - return new SoundId(Objects.requireNonNull(value, "value must not be null")); + return new SoundId(value); } } From f1870431f8cc67bce5d6d5e3afc3537e7a152e47 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:48:09 +1000 Subject: [PATCH 11/16] no-mistakes(document): Document @NonNull null-safety convention for ID wrappers --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index aaefb7f..da24dab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`, `id`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. -- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use the typed ID wrappers in model.id (PlayerId, ArmorSetId, DamageCauseId, PotionEffectId, SoundId) as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. +- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use the typed ID wrappers in model.id (PlayerId, ArmorSetId, DamageCauseId, PotionEffectId, SoundId) as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. ID wrappers enforce null safety via `@NonNull` on the value field; both the generated constructor and the `of()` factory reject null at construction time. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. From 341947a792d1d56d75e75ddb601edb1d34d709db Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:59:18 +1000 Subject: [PATCH 12/16] refactor: introduce TypedString/StringId base for typed identifiers Replace per-wrapper Lombok @Value IDs with a shared base-class hierarchy using commons-lang3 for equals/hashCode/validation: - TypedString: abstract base with final String id field, null-rejecting constructor (Validate.notNull), EqualsBuilder/HashCodeBuilder, and a static toStrings() utility - StringId extends TypedString: adds Validate.notEmpty guard - ArmorSetId, DamageCauseId, PotionEffectId, SoundId extend StringId - PlayerId extends TypedString (stores UUID, exposes getValue()) Adds commons-lang3 3.17.0 as a compile dependency, shaded under gg.steve.mc.ap.lib.commons.lang3. TypedString and StringId added to the JaCoCo 100% core gate. --- build.log | 147 ++++++++++++++++++ pom.xml | 12 ++ .../gg/steve/mc/ap/model/id/ArmorSetId.java | 13 +- .../steve/mc/ap/model/id/DamageCauseId.java | 13 +- .../gg/steve/mc/ap/model/id/PlayerId.java | 16 +- .../steve/mc/ap/model/id/PotionEffectId.java | 13 +- .../java/gg/steve/mc/ap/model/id/SoundId.java | 13 +- .../gg/steve/mc/ap/model/id/StringId.java | 10 ++ .../gg/steve/mc/ap/model/id/TypedString.java | 41 +++++ .../steve/mc/ap/model/id/ArmorSetIdTest.java | 7 +- .../mc/ap/model/id/DamageCauseIdTest.java | 7 +- .../gg/steve/mc/ap/model/id/PlayerIdTest.java | 7 + .../mc/ap/model/id/PotionEffectIdTest.java | 7 +- .../gg/steve/mc/ap/model/id/SoundIdTest.java | 7 +- .../gg/steve/mc/ap/model/id/StringIdTest.java | 13 ++ .../steve/mc/ap/model/id/TypedStringTest.java | 71 +++++++++ 16 files changed, 347 insertions(+), 50 deletions(-) create mode 100644 build.log create mode 100644 src/main/java/gg/steve/mc/ap/model/id/StringId.java create mode 100644 src/main/java/gg/steve/mc/ap/model/id/TypedString.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/StringIdTest.java create mode 100644 src/test/java/gg/steve/mc/ap/model/id/TypedStringTest.java diff --git a/build.log b/build.log new file mode 100644 index 0000000..2880075 --- /dev/null +++ b/build.log @@ -0,0 +1,147 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] ----------------------< gg.steve.mc.ap:ArmorPlus >---------------------- +[INFO] Building ArmorPlus v2.3.6 +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- clean:3.2.0:clean (default-clean) @ ArmorPlus --- +[INFO] Deleting /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target +[INFO] +[INFO] --- enforcer:3.5.0:enforce (enforce-build-jdk) @ ArmorPlus --- +[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed +[INFO] +[INFO] --- jacoco:0.8.15:prepare-agent (prepare-agent) @ ArmorPlus --- +[INFO] argLine set to -javaagent:/Users/goodhill/.m2/repository/org/jacoco/org.jacoco.agent/0.8.15/org.jacoco.agent-0.8.15-runtime.jar=destfile=/Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec,excludes=**/nbt/**:**/ArmorPlus.class +[INFO] +[INFO] --- resources:3.4.0:resources (default-resources) @ ArmorPlus --- +[INFO] Copying 20 resources from src/main/resources to target/classes +[INFO] +[INFO] --- compiler:3.14.0:compile (default-compile) @ ArmorPlus --- +[INFO] Recompiling the module because of changed source code. +[INFO] Compiling 124 source files with javac [debug release 8] to target/classes +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit +WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +[WARNING] source value 8 is obsolete and will be removed in a future release +[WARNING] target value 8 is obsolete and will be removed in a future release +[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. +[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Some input files use or override a deprecated API. +[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Recompile with -Xlint:deprecation for details. +[INFO] +[INFO] --- resources:3.4.0:testResources (default-testResources) @ ArmorPlus --- +[INFO] skip non existing resourceDirectory /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/resources +[INFO] +[INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ ArmorPlus --- +[INFO] Recompiling the module because of changed dependency. +[INFO] Compiling 32 source files with javac [debug release 17] to target/test-classes +[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Some input files use or override a deprecated API. +[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Recompile with -Xlint:deprecation for details. +[INFO] +[INFO] --- surefire:3.5.6:test (default-test) @ ArmorPlus --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running gg.steve.mc.ap.message.MessageTypeTest +Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 +OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended +WARNING: A Java agent has been loaded dynamically (/Users/goodhill/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.7/byte-buddy-agent-1.17.7.jar) +WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning +WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information +WARNING: Dynamic loading of agents will be disallowed by default in a future release +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.987 s -- in gg.steve.mc.ap.message.MessageTypeTest +[INFO] Running gg.steve.mc.ap.message.CommandDebugTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in gg.steve.mc.ap.message.CommandDebugTest +[INFO] Running gg.steve.mc.ap.utils.PlayerDirectionUtilTest +[INFO] Tests run: 31, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.089 s -- in gg.steve.mc.ap.utils.PlayerDirectionUtilTest +[INFO] Running gg.steve.mc.ap.utils.ColorUtilTest +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.utils.ColorUtilTest +[INFO] Running gg.steve.mc.ap.armor.SetTypeTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.SetTypeTest +[INFO] Running gg.steve.mc.ap.armor.PieceTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.PieceTest +[INFO] Running gg.steve.mc.ap.armor.SetIsWearingTest +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.291 s -- in gg.steve.mc.ap.armor.SetIsWearingTest +[INFO] Running gg.steve.mc.ap.armor.SetStatusEffectsManagerTest +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.armor.SetStatusEffectsManagerTest +[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageModificationTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.combat.CombatDamageModificationTest +[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageContextTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 s -- in gg.steve.mc.ap.model.combat.CombatDamageContextTest +[INFO] Running gg.steve.mc.ap.model.notification.ArmorSetNotificationTest +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.model.notification.ArmorSetNotificationTest +[INFO] Running gg.steve.mc.ap.model.effect.NotificationSoundTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.NotificationSoundTest +[INFO] Running gg.steve.mc.ap.model.effect.PotionEffectTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.PotionEffectTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorHandItemTest +[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.016 s -- in gg.steve.mc.ap.model.ability.ArmorHandItemTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest +[INFO] Running gg.steve.mc.ap.model.id.TypedStringTest +[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in gg.steve.mc.ap.model.id.TypedStringTest +[INFO] Running gg.steve.mc.ap.model.id.StringIdTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.StringIdTest +[INFO] Running gg.steve.mc.ap.model.id.PlayerIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.PlayerIdTest +[INFO] Running gg.steve.mc.ap.model.id.SoundIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.SoundIdTest +[INFO] Running gg.steve.mc.ap.model.id.PotionEffectIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.PotionEffectIdTest +[INFO] Running gg.steve.mc.ap.model.id.DamageCauseIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.DamageCauseIdTest +[INFO] Running gg.steve.mc.ap.model.id.ArmorSetIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.ArmorSetIdTest +[INFO] Running gg.steve.mc.ap.model.set.ArmorPieceSlotTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.set.ArmorPieceSlotTest +[INFO] Running gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest +[INFO] Running gg.steve.mc.ap.model.player.ArmorSetWearerTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.player.ArmorSetWearerTest +[INFO] Running gg.steve.mc.ap.data.SetDataTypeTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.data.SetDataTypeTest +[INFO] Running gg.steve.mc.ap.data.types.HandSetDataTest +[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.057 s -- in gg.steve.mc.ap.data.types.HandSetDataTest +[INFO] Running gg.steve.mc.ap.data.types.ExperienceSetDataTest +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in gg.steve.mc.ap.data.types.ExperienceSetDataTest +[INFO] Running gg.steve.mc.ap.data.BasicSetDataTest +[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.081 s -- in gg.steve.mc.ap.data.BasicSetDataTest +[INFO] Running gg.steve.mc.ap.data.utils.WarpUtilTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.122 s -- in gg.steve.mc.ap.data.utils.WarpUtilTest +[INFO] Running gg.steve.mc.ap.player.SetPlayerManagerTest +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s -- in gg.steve.mc.ap.player.SetPlayerManagerTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 185, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] +[INFO] --- jar:3.5.0:jar (default-jar) @ ArmorPlus --- +[INFO] Building jar: /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6.jar +[INFO] +[INFO] --- shade:3.1.0:shade (default) @ ArmorPlus --- +[INFO] Including de.tr7zw:functional-annotations:jar:0.1-SNAPSHOT in the shaded jar. +[INFO] Including org.bstats:bstats-bukkit:jar:1.7 in the shaded jar. +[INFO] Including org.apache.commons:commons-lang3:jar:3.17.0 in the shaded jar. +[INFO] Replacing original artifact with shaded artifact. +[INFO] Replacing /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6.jar with /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6-shaded.jar +[INFO] +[INFO] --- jacoco:0.8.15:report (report) @ ArmorPlus --- +[INFO] Loading execution data file /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec +[INFO] Analyzed bundle 'ArmorPlus' with 73 classes +[INFO] +[INFO] --- jacoco:0.8.15:check (check-core) @ ArmorPlus --- +[INFO] Loading execution data file /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec +[INFO] Analyzed bundle 'ArmorPlus' with 73 classes +[INFO] All coverage checks have been met. +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 5.834 s +[INFO] Finished at: 2026-07-10T08:58:07+10:00 +[INFO] ------------------------------------------------------------------------ diff --git a/pom.xml b/pom.xml index 67536d2..3bd700a 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,8 @@ gg.steve.mc.ap.armor.SetType gg.steve.mc.ap.data.SetDataType gg.steve.mc.ap.model.ability.ArmorHandItem + gg.steve.mc.ap.model.id.TypedString + gg.steve.mc.ap.model.id.StringId @@ -145,6 +147,10 @@ gg.steve.mc.ap + + org.apache.commons.lang3 + gg.steve.mc.ap.lib.commons.lang3 + @@ -237,6 +243,12 @@ 1.7 compile + + org.apache.commons + commons-lang3 + 3.17.0 + compile + com.mojang authlib diff --git a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java index 07b0811..80439dd 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java @@ -1,13 +1,6 @@ package gg.steve.mc.ap.model.id; -import lombok.NonNull; -import lombok.Value; - -@Value -public class ArmorSetId { - @NonNull String value; - - public static ArmorSetId of(String value) { - return new ArmorSetId(value); - } +public class ArmorSetId extends StringId { + private ArmorSetId(String value) { super(value); } + public static ArmorSetId of(String value) { return new ArmorSetId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java index afd857f..6d2920d 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java @@ -1,13 +1,6 @@ package gg.steve.mc.ap.model.id; -import lombok.NonNull; -import lombok.Value; - -@Value -public class DamageCauseId { - @NonNull String value; - - public static DamageCauseId of(String value) { - return new DamageCauseId(value); - } +public class DamageCauseId extends StringId { + private DamageCauseId(String value) { super(value); } + public static DamageCauseId of(String value) { return new DamageCauseId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java index 1b6d3bb..926974b 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java @@ -1,15 +1,19 @@ package gg.steve.mc.ap.model.id; -import lombok.NonNull; -import lombok.Value; - import java.util.UUID; -@Value -public class PlayerId { - @NonNull UUID value; +public class PlayerId extends TypedString { + private final UUID uuid; + + private PlayerId(UUID value) { + super(value.toString()); + this.uuid = value; + } + + public UUID getValue() { return uuid; } public static PlayerId of(UUID value) { + if (value == null) throw new NullPointerException("value must not be null"); return new PlayerId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java index 44dc882..727ffa8 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java @@ -1,13 +1,6 @@ package gg.steve.mc.ap.model.id; -import lombok.NonNull; -import lombok.Value; - -@Value -public class PotionEffectId { - @NonNull String value; - - public static PotionEffectId of(String value) { - return new PotionEffectId(value); - } +public class PotionEffectId extends StringId { + private PotionEffectId(String value) { super(value); } + public static PotionEffectId of(String value) { return new PotionEffectId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java index 29cc9ea..6ee40b4 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java @@ -1,13 +1,6 @@ package gg.steve.mc.ap.model.id; -import lombok.NonNull; -import lombok.Value; - -@Value -public class SoundId { - @NonNull String value; - - public static SoundId of(String value) { - return new SoundId(value); - } +public class SoundId extends StringId { + private SoundId(String value) { super(value); } + public static SoundId of(String value) { return new SoundId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/StringId.java b/src/main/java/gg/steve/mc/ap/model/id/StringId.java new file mode 100644 index 0000000..b550245 --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/StringId.java @@ -0,0 +1,10 @@ +package gg.steve.mc.ap.model.id; + +import org.apache.commons.lang3.Validate; + +public abstract class StringId extends TypedString { + protected StringId(String id) { + super(id); + Validate.notEmpty(id); + } +} diff --git a/src/main/java/gg/steve/mc/ap/model/id/TypedString.java b/src/main/java/gg/steve/mc/ap/model/id/TypedString.java new file mode 100644 index 0000000..39473df --- /dev/null +++ b/src/main/java/gg/steve/mc/ap/model/id/TypedString.java @@ -0,0 +1,41 @@ +package gg.steve.mc.ap.model.id; + +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class TypedString { + private final String id; + + protected TypedString(String id) { + Validate.notNull(id); + this.id = id; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(id).toHashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (obj == this) return true; + if (obj.getClass() != getClass()) return false; + TypedString other = (TypedString) obj; + return new EqualsBuilder().append(id, other.id).isEquals(); + } + + @Override + public String toString() { + return id; + } + + public static List toStrings(Collection typedStrings) { + return typedStrings.stream().map(TypedString::toString).collect(Collectors.toList()); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java index a432b30..ec55c95 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/ArmorSetIdTest.java @@ -9,7 +9,7 @@ class ArmorSetIdTest { @Test void ofCreatesExpectedValue() { ArmorSetId id = ArmorSetId.of("dragon"); - assertEquals("dragon", id.getValue()); + assertEquals("dragon", id.toString()); } @Test @@ -17,6 +17,11 @@ void ofNullThrows() { assertThrows(NullPointerException.class, () -> ArmorSetId.of(null)); } + @Test + void ofEmptyThrows() { + assertThrows(IllegalArgumentException.class, () -> ArmorSetId.of("")); + } + @Test void equalsAndHashCode() { ArmorSetId a = ArmorSetId.of("dragon"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java index e0577aa..ab19d18 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/DamageCauseIdTest.java @@ -9,7 +9,7 @@ class DamageCauseIdTest { @Test void ofCreatesExpectedValue() { DamageCauseId id = DamageCauseId.of("ENTITY_ATTACK"); - assertEquals("ENTITY_ATTACK", id.getValue()); + assertEquals("ENTITY_ATTACK", id.toString()); } @Test @@ -17,6 +17,11 @@ void ofNullThrows() { assertThrows(NullPointerException.class, () -> DamageCauseId.of(null)); } + @Test + void ofEmptyThrows() { + assertThrows(IllegalArgumentException.class, () -> DamageCauseId.of("")); + } + @Test void equalsAndHashCode() { DamageCauseId a = DamageCauseId.of("ENTITY_ATTACK"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java index b9f929e..6eaf4c7 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/PlayerIdTest.java @@ -31,4 +31,11 @@ void equalsAndHashCode() { assertEquals(a.hashCode(), b.hashCode()); assertNotEquals(a, c); } + + @Test + void toStringReturnsUuidString() { + UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000001"); + PlayerId id = PlayerId.of(uuid); + assertEquals("00000000-0000-0000-0000-000000000001", id.toString()); + } } diff --git a/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java index 43082b5..b03bf1e 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/PotionEffectIdTest.java @@ -9,7 +9,7 @@ class PotionEffectIdTest { @Test void ofCreatesExpectedValue() { PotionEffectId id = PotionEffectId.of("SPEED"); - assertEquals("SPEED", id.getValue()); + assertEquals("SPEED", id.toString()); } @Test @@ -17,6 +17,11 @@ void ofNullThrows() { assertThrows(NullPointerException.class, () -> PotionEffectId.of(null)); } + @Test + void ofEmptyThrows() { + assertThrows(IllegalArgumentException.class, () -> PotionEffectId.of("")); + } + @Test void equalsAndHashCode() { PotionEffectId a = PotionEffectId.of("SPEED"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java index e2caf81..58afd15 100644 --- a/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java +++ b/src/test/java/gg/steve/mc/ap/model/id/SoundIdTest.java @@ -9,7 +9,7 @@ class SoundIdTest { @Test void ofCreatesExpectedValue() { SoundId id = SoundId.of("ENTITY_PLAYER_LEVELUP"); - assertEquals("ENTITY_PLAYER_LEVELUP", id.getValue()); + assertEquals("ENTITY_PLAYER_LEVELUP", id.toString()); } @Test @@ -17,6 +17,11 @@ void ofNullThrows() { assertThrows(NullPointerException.class, () -> SoundId.of(null)); } + @Test + void ofEmptyThrows() { + assertThrows(IllegalArgumentException.class, () -> SoundId.of("")); + } + @Test void equalsAndHashCode() { SoundId a = SoundId.of("DING"); diff --git a/src/test/java/gg/steve/mc/ap/model/id/StringIdTest.java b/src/test/java/gg/steve/mc/ap/model/id/StringIdTest.java new file mode 100644 index 0000000..6930a2d --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/StringIdTest.java @@ -0,0 +1,13 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class StringIdTest { + + @Test + void ofEmptyThrowsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ArmorSetId.of("")); + } +} diff --git a/src/test/java/gg/steve/mc/ap/model/id/TypedStringTest.java b/src/test/java/gg/steve/mc/ap/model/id/TypedStringTest.java new file mode 100644 index 0000000..119a1df --- /dev/null +++ b/src/test/java/gg/steve/mc/ap/model/id/TypedStringTest.java @@ -0,0 +1,71 @@ +package gg.steve.mc.ap.model.id; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class TypedStringTest { + + @Test + void differentSubtypesWithSameStringAreNotEqual() { + ArmorSetId armorSetId = ArmorSetId.of("SPEED"); + PotionEffectId potionEffectId = PotionEffectId.of("SPEED"); + + assertNotEquals(armorSetId, potionEffectId); + } + + @Test + void toStringsReturnsRawStrings() { + List ids = Arrays.asList( + ArmorSetId.of("dragon"), + ArmorSetId.of("knight") + ); + + List result = TypedString.toStrings(ids); + + assertEquals(Arrays.asList("dragon", "knight"), result); + } + + @Test + void toStringReturnsRawIdString() { + ArmorSetId id = ArmorSetId.of("dragon"); + assertEquals("dragon", id.toString()); + } + + @Test + void nullConstructorThrows() { + assertThrows(NullPointerException.class, () -> ArmorSetId.of(null)); + } + + @SuppressWarnings("EqualsWithItself") + @Test + void equalsNullReturnsFalse() { + ArmorSetId id = ArmorSetId.of("dragon"); + assertFalse(id.equals(null)); + } + + @SuppressWarnings("EqualsWithItself") + @Test + void equalsSelfReturnsTrue() { + ArmorSetId id = ArmorSetId.of("dragon"); + assertTrue(id.equals(id)); + } + + @Test + void sameTypeDifferentValueNotEqual() { + ArmorSetId a = ArmorSetId.of("dragon"); + ArmorSetId b = ArmorSetId.of("knight"); + assertNotEquals(a, b); + } + + @Test + void sameTypeSameValueEqual() { + ArmorSetId a = ArmorSetId.of("dragon"); + ArmorSetId b = ArmorSetId.of("dragon"); + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + } +} From d766053e154b64e9f7c7b339941c3e54d9bac15c Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 08:59:25 +1000 Subject: [PATCH 13/16] chore: remove accidental build log --- build.log | 147 ------------------------------------------------------ 1 file changed, 147 deletions(-) delete mode 100644 build.log diff --git a/build.log b/build.log deleted file mode 100644 index 2880075..0000000 --- a/build.log +++ /dev/null @@ -1,147 +0,0 @@ -[INFO] Scanning for projects... -[INFO] -[INFO] ----------------------< gg.steve.mc.ap:ArmorPlus >---------------------- -[INFO] Building ArmorPlus v2.3.6 -[INFO] from pom.xml -[INFO] --------------------------------[ jar ]--------------------------------- -[INFO] -[INFO] --- clean:3.2.0:clean (default-clean) @ ArmorPlus --- -[INFO] Deleting /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target -[INFO] -[INFO] --- enforcer:3.5.0:enforce (enforce-build-jdk) @ ArmorPlus --- -[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed -[INFO] -[INFO] --- jacoco:0.8.15:prepare-agent (prepare-agent) @ ArmorPlus --- -[INFO] argLine set to -javaagent:/Users/goodhill/.m2/repository/org/jacoco/org.jacoco.agent/0.8.15/org.jacoco.agent-0.8.15-runtime.jar=destfile=/Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec,excludes=**/nbt/**:**/ArmorPlus.class -[INFO] -[INFO] --- resources:3.4.0:resources (default-resources) @ ArmorPlus --- -[INFO] Copying 20 resources from src/main/resources to target/classes -[INFO] -[INFO] --- compiler:3.14.0:compile (default-compile) @ ArmorPlus --- -[INFO] Recompiling the module because of changed source code. -[INFO] Compiling 124 source files with javac [debug release 8] to target/classes -WARNING: A terminally deprecated method in sun.misc.Unsafe has been called -WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit -WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit -WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release -[WARNING] source value 8 is obsolete and will be removed in a future release -[WARNING] target value 8 is obsolete and will be removed in a future release -[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. -[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Some input files use or override a deprecated API. -[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Recompile with -Xlint:deprecation for details. -[INFO] -[INFO] --- resources:3.4.0:testResources (default-testResources) @ ArmorPlus --- -[INFO] skip non existing resourceDirectory /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/resources -[INFO] -[INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ ArmorPlus --- -[INFO] Recompiling the module because of changed dependency. -[INFO] Compiling 32 source files with javac [debug release 17] to target/test-classes -[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Some input files use or override a deprecated API. -[INFO] /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Recompile with -Xlint:deprecation for details. -[INFO] -[INFO] --- surefire:3.5.6:test (default-test) @ ArmorPlus --- -[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider -[INFO] -[INFO] ------------------------------------------------------- -[INFO] T E S T S -[INFO] ------------------------------------------------------- -[INFO] Running gg.steve.mc.ap.message.MessageTypeTest -Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 -OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended -WARNING: A Java agent has been loaded dynamically (/Users/goodhill/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.7/byte-buddy-agent-1.17.7.jar) -WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning -WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information -WARNING: Dynamic loading of agents will be disallowed by default in a future release -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.987 s -- in gg.steve.mc.ap.message.MessageTypeTest -[INFO] Running gg.steve.mc.ap.message.CommandDebugTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in gg.steve.mc.ap.message.CommandDebugTest -[INFO] Running gg.steve.mc.ap.utils.PlayerDirectionUtilTest -[INFO] Tests run: 31, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.089 s -- in gg.steve.mc.ap.utils.PlayerDirectionUtilTest -[INFO] Running gg.steve.mc.ap.utils.ColorUtilTest -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.utils.ColorUtilTest -[INFO] Running gg.steve.mc.ap.armor.SetTypeTest -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.SetTypeTest -[INFO] Running gg.steve.mc.ap.armor.PieceTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.PieceTest -[INFO] Running gg.steve.mc.ap.armor.SetIsWearingTest -[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.291 s -- in gg.steve.mc.ap.armor.SetIsWearingTest -[INFO] Running gg.steve.mc.ap.armor.SetStatusEffectsManagerTest -[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.armor.SetStatusEffectsManagerTest -[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageModificationTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.combat.CombatDamageModificationTest -[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageContextTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 s -- in gg.steve.mc.ap.model.combat.CombatDamageContextTest -[INFO] Running gg.steve.mc.ap.model.notification.ArmorSetNotificationTest -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.model.notification.ArmorSetNotificationTest -[INFO] Running gg.steve.mc.ap.model.effect.NotificationSoundTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.NotificationSoundTest -[INFO] Running gg.steve.mc.ap.model.effect.PotionEffectTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.PotionEffectTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorHandItemTest -[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.016 s -- in gg.steve.mc.ap.model.ability.ArmorHandItemTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest -[INFO] Running gg.steve.mc.ap.model.id.TypedStringTest -[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in gg.steve.mc.ap.model.id.TypedStringTest -[INFO] Running gg.steve.mc.ap.model.id.StringIdTest -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.StringIdTest -[INFO] Running gg.steve.mc.ap.model.id.PlayerIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.PlayerIdTest -[INFO] Running gg.steve.mc.ap.model.id.SoundIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.SoundIdTest -[INFO] Running gg.steve.mc.ap.model.id.PotionEffectIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.PotionEffectIdTest -[INFO] Running gg.steve.mc.ap.model.id.DamageCauseIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.DamageCauseIdTest -[INFO] Running gg.steve.mc.ap.model.id.ArmorSetIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.ArmorSetIdTest -[INFO] Running gg.steve.mc.ap.model.set.ArmorPieceSlotTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.set.ArmorPieceSlotTest -[INFO] Running gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest -[INFO] Running gg.steve.mc.ap.model.player.ArmorSetWearerTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.player.ArmorSetWearerTest -[INFO] Running gg.steve.mc.ap.data.SetDataTypeTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.data.SetDataTypeTest -[INFO] Running gg.steve.mc.ap.data.types.HandSetDataTest -[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.057 s -- in gg.steve.mc.ap.data.types.HandSetDataTest -[INFO] Running gg.steve.mc.ap.data.types.ExperienceSetDataTest -[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in gg.steve.mc.ap.data.types.ExperienceSetDataTest -[INFO] Running gg.steve.mc.ap.data.BasicSetDataTest -[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.081 s -- in gg.steve.mc.ap.data.BasicSetDataTest -[INFO] Running gg.steve.mc.ap.data.utils.WarpUtilTest -[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.122 s -- in gg.steve.mc.ap.data.utils.WarpUtilTest -[INFO] Running gg.steve.mc.ap.player.SetPlayerManagerTest -[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s -- in gg.steve.mc.ap.player.SetPlayerManagerTest -[INFO] -[INFO] Results: -[INFO] -[INFO] Tests run: 185, Failures: 0, Errors: 0, Skipped: 0 -[INFO] -[INFO] -[INFO] --- jar:3.5.0:jar (default-jar) @ ArmorPlus --- -[INFO] Building jar: /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6.jar -[INFO] -[INFO] --- shade:3.1.0:shade (default) @ ArmorPlus --- -[INFO] Including de.tr7zw:functional-annotations:jar:0.1-SNAPSHOT in the shaded jar. -[INFO] Including org.bstats:bstats-bukkit:jar:1.7 in the shaded jar. -[INFO] Including org.apache.commons:commons-lang3:jar:3.17.0 in the shaded jar. -[INFO] Replacing original artifact with shaded artifact. -[INFO] Replacing /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6.jar with /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/ArmorPlus-v2.3.6-shaded.jar -[INFO] -[INFO] --- jacoco:0.8.15:report (report) @ ArmorPlus --- -[INFO] Loading execution data file /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec -[INFO] Analyzed bundle 'ArmorPlus' with 73 classes -[INFO] -[INFO] --- jacoco:0.8.15:check (check-core) @ ArmorPlus --- -[INFO] Loading execution data file /Users/goodhill/.treehouse/ArmorPlus-e64159/1/ArmorPlus/target/jacoco.exec -[INFO] Analyzed bundle 'ArmorPlus' with 73 classes -[INFO] All coverage checks have been met. -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 5.834 s -[INFO] Finished at: 2026-07-10T08:58:07+10:00 -[INFO] ------------------------------------------------------------------------ From fbd5a1184e764277870cd648ff5a85deb269ec10 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 09:06:35 +1000 Subject: [PATCH 14/16] no-mistakes(review): Mark leaf ID classes final --- build.log | 146 ++++++++++++++++++ .../gg/steve/mc/ap/model/id/ArmorSetId.java | 2 +- .../steve/mc/ap/model/id/DamageCauseId.java | 2 +- .../gg/steve/mc/ap/model/id/PlayerId.java | 2 +- .../steve/mc/ap/model/id/PotionEffectId.java | 2 +- .../java/gg/steve/mc/ap/model/id/SoundId.java | 2 +- 6 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 build.log diff --git a/build.log b/build.log new file mode 100644 index 0000000..fa841e0 --- /dev/null +++ b/build.log @@ -0,0 +1,146 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] ----------------------< gg.steve.mc.ap:ArmorPlus >---------------------- +[INFO] Building ArmorPlus v2.3.6 +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- clean:3.2.0:clean (default-clean) @ ArmorPlus --- +[INFO] +[INFO] --- enforcer:3.5.0:enforce (enforce-build-jdk) @ ArmorPlus --- +[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed +[INFO] +[INFO] --- jacoco:0.8.15:prepare-agent (prepare-agent) @ ArmorPlus --- +[INFO] argLine set to -javaagent:/Users/goodhill/.m2/repository/org/jacoco/org.jacoco.agent/0.8.15/org.jacoco.agent-0.8.15-runtime.jar=destfile=/Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec,excludes=**/nbt/**:**/ArmorPlus.class +[INFO] +[INFO] --- resources:3.4.0:resources (default-resources) @ ArmorPlus --- +[INFO] Copying 20 resources from src/main/resources to target/classes +[INFO] +[INFO] --- compiler:3.14.0:compile (default-compile) @ ArmorPlus --- +[INFO] Recompiling the module because of changed source code. +[INFO] Compiling 124 source files with javac [debug release 8] to target/classes +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit +WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +[WARNING] source value 8 is obsolete and will be removed in a future release +[WARNING] target value 8 is obsolete and will be removed in a future release +[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. +[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Some input files use or override a deprecated API. +[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Recompile with -Xlint:deprecation for details. +[INFO] +[INFO] --- resources:3.4.0:testResources (default-testResources) @ ArmorPlus --- +[INFO] skip non existing resourceDirectory /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/resources +[INFO] +[INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ ArmorPlus --- +[INFO] Recompiling the module because of changed dependency. +[INFO] Compiling 32 source files with javac [debug release 17] to target/test-classes +[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Some input files use or override a deprecated API. +[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Recompile with -Xlint:deprecation for details. +[INFO] +[INFO] --- surefire:3.5.6:test (default-test) @ ArmorPlus --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running gg.steve.mc.ap.message.MessageTypeTest +Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 +OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended +WARNING: A Java agent has been loaded dynamically (/Users/goodhill/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.7/byte-buddy-agent-1.17.7.jar) +WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning +WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information +WARNING: Dynamic loading of agents will be disallowed by default in a future release +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.151 s -- in gg.steve.mc.ap.message.MessageTypeTest +[INFO] Running gg.steve.mc.ap.message.CommandDebugTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in gg.steve.mc.ap.message.CommandDebugTest +[INFO] Running gg.steve.mc.ap.utils.PlayerDirectionUtilTest +[INFO] Tests run: 31, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 s -- in gg.steve.mc.ap.utils.PlayerDirectionUtilTest +[INFO] Running gg.steve.mc.ap.utils.ColorUtilTest +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.utils.ColorUtilTest +[INFO] Running gg.steve.mc.ap.armor.SetTypeTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.SetTypeTest +[INFO] Running gg.steve.mc.ap.armor.PieceTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.armor.PieceTest +[INFO] Running gg.steve.mc.ap.armor.SetIsWearingTest +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.372 s -- in gg.steve.mc.ap.armor.SetIsWearingTest +[INFO] Running gg.steve.mc.ap.armor.SetStatusEffectsManagerTest +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in gg.steve.mc.ap.armor.SetStatusEffectsManagerTest +[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageModificationTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.combat.CombatDamageModificationTest +[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageContextTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in gg.steve.mc.ap.model.combat.CombatDamageContextTest +[INFO] Running gg.steve.mc.ap.model.notification.ArmorSetNotificationTest +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.model.notification.ArmorSetNotificationTest +[INFO] Running gg.steve.mc.ap.model.effect.NotificationSoundTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.NotificationSoundTest +[INFO] Running gg.steve.mc.ap.model.effect.PotionEffectTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.PotionEffectTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorHandItemTest +[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s -- in gg.steve.mc.ap.model.ability.ArmorHandItemTest +[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest +[INFO] Running gg.steve.mc.ap.model.id.TypedStringTest +[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.012 s -- in gg.steve.mc.ap.model.id.TypedStringTest +[INFO] Running gg.steve.mc.ap.model.id.StringIdTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.StringIdTest +[INFO] Running gg.steve.mc.ap.model.id.PlayerIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.PlayerIdTest +[INFO] Running gg.steve.mc.ap.model.id.SoundIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.id.SoundIdTest +[INFO] Running gg.steve.mc.ap.model.id.PotionEffectIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.PotionEffectIdTest +[INFO] Running gg.steve.mc.ap.model.id.DamageCauseIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.DamageCauseIdTest +[INFO] Running gg.steve.mc.ap.model.id.ArmorSetIdTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in gg.steve.mc.ap.model.id.ArmorSetIdTest +[INFO] Running gg.steve.mc.ap.model.set.ArmorPieceSlotTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.set.ArmorPieceSlotTest +[INFO] Running gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest +[INFO] Running gg.steve.mc.ap.model.player.ArmorSetWearerTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.player.ArmorSetWearerTest +[INFO] Running gg.steve.mc.ap.data.SetDataTypeTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.data.SetDataTypeTest +[INFO] Running gg.steve.mc.ap.data.types.HandSetDataTest +[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.067 s -- in gg.steve.mc.ap.data.types.HandSetDataTest +[INFO] Running gg.steve.mc.ap.data.types.ExperienceSetDataTest +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in gg.steve.mc.ap.data.types.ExperienceSetDataTest +[INFO] Running gg.steve.mc.ap.data.BasicSetDataTest +[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.092 s -- in gg.steve.mc.ap.data.BasicSetDataTest +[INFO] Running gg.steve.mc.ap.data.utils.WarpUtilTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.143 s -- in gg.steve.mc.ap.data.utils.WarpUtilTest +[INFO] Running gg.steve.mc.ap.player.SetPlayerManagerTest +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 s -- in gg.steve.mc.ap.player.SetPlayerManagerTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 185, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] +[INFO] --- jar:3.5.0:jar (default-jar) @ ArmorPlus --- +[INFO] Building jar: /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6.jar +[INFO] +[INFO] --- shade:3.1.0:shade (default) @ ArmorPlus --- +[INFO] Including de.tr7zw:functional-annotations:jar:0.1-SNAPSHOT in the shaded jar. +[INFO] Including org.bstats:bstats-bukkit:jar:1.7 in the shaded jar. +[INFO] Including org.apache.commons:commons-lang3:jar:3.17.0 in the shaded jar. +[INFO] Replacing original artifact with shaded artifact. +[INFO] Replacing /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6.jar with /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6-shaded.jar +[INFO] +[INFO] --- jacoco:0.8.15:report (report) @ ArmorPlus --- +[INFO] Loading execution data file /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec +[INFO] Analyzed bundle 'ArmorPlus' with 73 classes +[INFO] +[INFO] --- jacoco:0.8.15:check (check-core) @ ArmorPlus --- +[INFO] Loading execution data file /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec +[INFO] Analyzed bundle 'ArmorPlus' with 73 classes +[INFO] All coverage checks have been met. +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 6.880 s +[INFO] Finished at: 2026-07-10T09:06:26+10:00 +[INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java index 80439dd..f23f913 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/ArmorSetId.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.id; -public class ArmorSetId extends StringId { +public final class ArmorSetId extends StringId { private ArmorSetId(String value) { super(value); } public static ArmorSetId of(String value) { return new ArmorSetId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java index 6d2920d..0c243b4 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/DamageCauseId.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.id; -public class DamageCauseId extends StringId { +public final class DamageCauseId extends StringId { private DamageCauseId(String value) { super(value); } public static DamageCauseId of(String value) { return new DamageCauseId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java index 926974b..a1f9732 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PlayerId.java @@ -2,7 +2,7 @@ import java.util.UUID; -public class PlayerId extends TypedString { +public final class PlayerId extends TypedString { private final UUID uuid; private PlayerId(UUID value) { diff --git a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java index 727ffa8..be170c5 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/PotionEffectId.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.id; -public class PotionEffectId extends StringId { +public final class PotionEffectId extends StringId { private PotionEffectId(String value) { super(value); } public static PotionEffectId of(String value) { return new PotionEffectId(value); } } diff --git a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java index 6ee40b4..874e262 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/SoundId.java +++ b/src/main/java/gg/steve/mc/ap/model/id/SoundId.java @@ -1,6 +1,6 @@ package gg.steve.mc.ap.model.id; -public class SoundId extends StringId { +public final class SoundId extends StringId { private SoundId(String value) { super(value); } public static SoundId of(String value) { return new SoundId(value); } } From b650a7862e4432538638beafdfb4ebec6c9b6bb6 Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 09:22:49 +1000 Subject: [PATCH 15/16] no-mistakes(review): Delete build.log, gitignore it, update stale docs --- .gitignore | 3 +- AGENTS.md | 4 +- README.md | 2 +- build.log | 146 ----------------------------------------------------- 4 files changed, 5 insertions(+), 150 deletions(-) delete mode 100644 build.log diff --git a/.gitignore b/.gitignore index 316d19b..53ef314 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ ChangeLog.txt target/ repo/ out/ -META-INF/ \ No newline at end of file +META-INF/ +build.log diff --git a/AGENTS.md b/AGENTS.md index da24dab..1319f55 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,14 +5,14 @@ This file is the project's committed home for project-intrinsic agent knowledge: - Build with JDK 25 or newer - `maven-enforcer-plugin` (`requireJavaVersion [25,)`) fails `validate` on anything older. Emitted plugin bytecode is intentionally Java 8 (major version 52) via `8` in `pom.xml`; the target runtime is a Java 17+ Spigot server. Do not "modernize" the bytecode level. Build/verify: `JAVA_HOME= mvn -B clean verify`. - `de.tr7zw:functional-annotations` is pinned to `0.1-SNAPSHOT` because no released version has ever been published (CodeMC metadata lists only the SNAPSHOT). It is a real compile dependency (`de.tr7zw.annotations.FAUtil`, used by the `nbt` package), so it cannot be dropped. Revisit if upstream ever cuts a release. - Test harness: JUnit 5 + MockBukkit + Mockito + JaCoCo. `mvn verify` runs tests and produces `target/site/jacoco/` coverage report. JaCoCo excludes `**/nbt/**` (vendored) and `**/ArmorPlus.class` (bootstrap). -- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. +- Coverage gate (CORE set): JaCoCo `check-core` execution enforces 100% line AND branch on these pure-logic classes: `PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`, `model.id.TypedString`, `model.id.StringId`. Build fails if core coverage drops below 100%. Everything else remains report-only. Excluded from core: `LogUtil` (calls `ArmorPlus.get()` singleton), `nbt.NBTType` (vendored `**/nbt/**` exclusion). When modifying a core class, add tests in the same commit. - JDK/bytecode split: main code compiles to Java 8 (`maven.compiler.release=8`), test code compiles to Java 17 (`maven.compiler.testRelease=17`). The `testRelease` property is picked up automatically by maven-compiler-plugin's default-testCompile execution - no custom execution block needed. Both compile under the same JDK 25 build. MockBukkit + paper-api are Java 17 bytecode, so tests cannot target 8. - MockBukkit limitation: the plugin cannot be loaded via `MockBukkit.load(ArmorPlus.class)` because the vendored NBT-API performs reflection at enable time that MockBukkit does not model. Use pure-logic unit tests or Mockito for testing pieces that interact with Bukkit APIs. - Mockito on JDK 25: surefire must pass `-Dnet.bytebuddy.experimental=true` (configured in pom.xml ``). Without it, ByteBuddy fails to recognize the JDK 25 class file version. - Characterization test safety net (Phase 4A): `src/test/java/gg/steve/mc/ap/{data,player,armor,message}` contains characterization tests pinning current behavior of damage calc (`HandSetData.calculateFinalDamage`, `BasicSetData.onHit/onDamage`), wearer tracking (`SetPlayerManager`), warp safety (`WarpUtil.isSafe`), XP multiplier (`ExperienceSetData.onTargetDeath`), potion checks (`SetStatusEffectsManager.potionCheck`), set detection (`Set.isWearingSet/verifyPiece`), and messaging (`MessageType/CommandDebug`). Rearch phases 4B+ MUST keep these tests green - they prove behavior is preserved during extraction. Do NOT "fix" surprising behavior these tests pin; document it and defer to a separate bug-fix PR. - Integration-only (not netted): `Set.isWearingSet` full integration path (NBT reflection in real server), `SetPlayerManager.init()` (iterates Bukkit.getOnlinePlayers), GUI rendering, PAPI expansion, scheduler-dependent abilities (Fairy/ColorWay/Lightning/Engineer/Traveller tick logic). - Domain model package: `gg.steve.mc.ap.model` with sub-packages `set`, `ability`, `combat`, `effect`, `notification`, `player`, `id`. Contains pure-Java value types with ZERO Bukkit/NMS/NBT imports. Uses Lombok (`@Value`, `@Builder`) for boilerplate elimination. The `lombok.config` at repo root sets `lombok.addLombokGeneratedAnnotation=true` so JaCoCo auto-excludes generated code. -- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use the typed ID wrappers in model.id (PlayerId, ArmorSetId, DamageCauseId, PotionEffectId, SoundId) as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. ID wrappers enforce null safety via `@NonNull` on the value field; both the generated constructor and the `of()` factory reject null at construction time. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. +- Domain model conventions: No class may use the `Manager` suffix (use Registry, Service, Store, Factory, Source, Resolver, etc.). Use the typed ID wrappers in model.id (PlayerId, ArmorSetId, DamageCauseId, PotionEffectId, SoundId) as identity currency - never reference Bukkit Player/ItemStack/World in the model layer. ID wrappers extend the `TypedString`/`StringId` base hierarchy: `TypedString` (abstract) rejects null via commons-lang3 `Validate.notNull` in its constructor and implements `equals`/`hashCode` with `getClass()` checks (different subtypes wrapping the same string are never equal); `StringId` extends it adding `Validate.notEmpty`. Leaf classes are final with a private constructor and an `of()` factory; `PlayerId` extends `TypedString` directly, wrapping a `UUID`. Use `@Value` + `@Builder` for multi-field value types. Annotate `List`/`Set`/`Map` fields with `@Singular` so Lombok's builder produces genuinely unmodifiable defensive copies at `build()` time. - Lombok dependency: `org.projectlombok:lombok:1.18.38` (provided scope). Annotation processor configured in maven-compiler-plugin's ``. Works with JDK 25 and emits Java 8 bytecode. - No-Bukkit-in-model rule: verified by grep (no ArchUnit yet). The `model` package must have zero imports of `org.bukkit.*`, `net.minecraft.*`, `de.tr7zw.*`, or any plugin class outside `model`. diff --git a/README.md b/README.md index 24f89b5..a399c3b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For more information about the plugin, permissions, and commands please refer to ## Code Coverage JaCoCo runs on every CI build (`mvn verify`). -The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`); a coverage drop on any of these will fail the build. +The build enforces 100% line and branch coverage on the pure-logic core classes (`PlayerDirectionUtil`, `ColorUtil`, `Piece`, `SetType`, `SetDataType`, `ArmorHandItem`, `TypedString`, `StringId`); a coverage drop on any of these will fail the build. The HTML coverage report is uploaded as the **jacoco-report** artifact on each workflow run - download it from the [Actions tab](https://github.com/nbdSteve/ArmorPlus/actions/workflows/ci.yml). ## Soft Dependencies diff --git a/build.log b/build.log deleted file mode 100644 index fa841e0..0000000 --- a/build.log +++ /dev/null @@ -1,146 +0,0 @@ -[INFO] Scanning for projects... -[INFO] -[INFO] ----------------------< gg.steve.mc.ap:ArmorPlus >---------------------- -[INFO] Building ArmorPlus v2.3.6 -[INFO] from pom.xml -[INFO] --------------------------------[ jar ]--------------------------------- -[INFO] -[INFO] --- clean:3.2.0:clean (default-clean) @ ArmorPlus --- -[INFO] -[INFO] --- enforcer:3.5.0:enforce (enforce-build-jdk) @ ArmorPlus --- -[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed -[INFO] -[INFO] --- jacoco:0.8.15:prepare-agent (prepare-agent) @ ArmorPlus --- -[INFO] argLine set to -javaagent:/Users/goodhill/.m2/repository/org/jacoco/org.jacoco.agent/0.8.15/org.jacoco.agent-0.8.15-runtime.jar=destfile=/Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec,excludes=**/nbt/**:**/ArmorPlus.class -[INFO] -[INFO] --- resources:3.4.0:resources (default-resources) @ ArmorPlus --- -[INFO] Copying 20 resources from src/main/resources to target/classes -[INFO] -[INFO] --- compiler:3.14.0:compile (default-compile) @ ArmorPlus --- -[INFO] Recompiling the module because of changed source code. -[INFO] Compiling 124 source files with javac [debug release 8] to target/classes -WARNING: A terminally deprecated method in sun.misc.Unsafe has been called -WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit -WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit -WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release -[WARNING] source value 8 is obsolete and will be removed in a future release -[WARNING] target value 8 is obsolete and will be removed in a future release -[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. -[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Some input files use or override a deprecated API. -[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/main/java/gg/steve/mc/ap/data/BasicSetData.java: Recompile with -Xlint:deprecation for details. -[INFO] -[INFO] --- resources:3.4.0:testResources (default-testResources) @ ArmorPlus --- -[INFO] skip non existing resourceDirectory /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/resources -[INFO] -[INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ ArmorPlus --- -[INFO] Recompiling the module because of changed dependency. -[INFO] Compiling 32 source files with javac [debug release 17] to target/test-classes -[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Some input files use or override a deprecated API. -[INFO] /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/src/test/java/gg/steve/mc/ap/data/BasicSetDataTest.java: Recompile with -Xlint:deprecation for details. -[INFO] -[INFO] --- surefire:3.5.6:test (default-test) @ ArmorPlus --- -[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider -[INFO] -[INFO] ------------------------------------------------------- -[INFO] T E S T S -[INFO] ------------------------------------------------------- -[INFO] Running gg.steve.mc.ap.message.MessageTypeTest -Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 -OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended -WARNING: A Java agent has been loaded dynamically (/Users/goodhill/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.7/byte-buddy-agent-1.17.7.jar) -WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning -WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information -WARNING: Dynamic loading of agents will be disallowed by default in a future release -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.151 s -- in gg.steve.mc.ap.message.MessageTypeTest -[INFO] Running gg.steve.mc.ap.message.CommandDebugTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in gg.steve.mc.ap.message.CommandDebugTest -[INFO] Running gg.steve.mc.ap.utils.PlayerDirectionUtilTest -[INFO] Tests run: 31, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 s -- in gg.steve.mc.ap.utils.PlayerDirectionUtilTest -[INFO] Running gg.steve.mc.ap.utils.ColorUtilTest -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.utils.ColorUtilTest -[INFO] Running gg.steve.mc.ap.armor.SetTypeTest -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.armor.SetTypeTest -[INFO] Running gg.steve.mc.ap.armor.PieceTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.armor.PieceTest -[INFO] Running gg.steve.mc.ap.armor.SetIsWearingTest -[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.372 s -- in gg.steve.mc.ap.armor.SetIsWearingTest -[INFO] Running gg.steve.mc.ap.armor.SetStatusEffectsManagerTest -[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in gg.steve.mc.ap.armor.SetStatusEffectsManagerTest -[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageModificationTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.combat.CombatDamageModificationTest -[INFO] Running gg.steve.mc.ap.model.combat.CombatDamageContextTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in gg.steve.mc.ap.model.combat.CombatDamageContextTest -[INFO] Running gg.steve.mc.ap.model.notification.ArmorSetNotificationTest -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in gg.steve.mc.ap.model.notification.ArmorSetNotificationTest -[INFO] Running gg.steve.mc.ap.model.effect.NotificationSoundTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.NotificationSoundTest -[INFO] Running gg.steve.mc.ap.model.effect.PotionEffectTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.effect.PotionEffectTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.ability.ArmorSetAbilityTypeTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorHandItemTest -[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s -- in gg.steve.mc.ap.model.ability.ArmorHandItemTest -[INFO] Running gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.ability.ArmorSetBasicStatsTest -[INFO] Running gg.steve.mc.ap.model.id.TypedStringTest -[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.012 s -- in gg.steve.mc.ap.model.id.TypedStringTest -[INFO] Running gg.steve.mc.ap.model.id.StringIdTest -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.id.StringIdTest -[INFO] Running gg.steve.mc.ap.model.id.PlayerIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.PlayerIdTest -[INFO] Running gg.steve.mc.ap.model.id.SoundIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.id.SoundIdTest -[INFO] Running gg.steve.mc.ap.model.id.PotionEffectIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.PotionEffectIdTest -[INFO] Running gg.steve.mc.ap.model.id.DamageCauseIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in gg.steve.mc.ap.model.id.DamageCauseIdTest -[INFO] Running gg.steve.mc.ap.model.id.ArmorSetIdTest -[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in gg.steve.mc.ap.model.id.ArmorSetIdTest -[INFO] Running gg.steve.mc.ap.model.set.ArmorPieceSlotTest -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in gg.steve.mc.ap.model.set.ArmorPieceSlotTest -[INFO] Running gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.set.ArmorSetPurchaseResultTest -[INFO] Running gg.steve.mc.ap.model.player.ArmorSetWearerTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.model.player.ArmorSetWearerTest -[INFO] Running gg.steve.mc.ap.data.SetDataTypeTest -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in gg.steve.mc.ap.data.SetDataTypeTest -[INFO] Running gg.steve.mc.ap.data.types.HandSetDataTest -[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.067 s -- in gg.steve.mc.ap.data.types.HandSetDataTest -[INFO] Running gg.steve.mc.ap.data.types.ExperienceSetDataTest -[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in gg.steve.mc.ap.data.types.ExperienceSetDataTest -[INFO] Running gg.steve.mc.ap.data.BasicSetDataTest -[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.092 s -- in gg.steve.mc.ap.data.BasicSetDataTest -[INFO] Running gg.steve.mc.ap.data.utils.WarpUtilTest -[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.143 s -- in gg.steve.mc.ap.data.utils.WarpUtilTest -[INFO] Running gg.steve.mc.ap.player.SetPlayerManagerTest -[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 s -- in gg.steve.mc.ap.player.SetPlayerManagerTest -[INFO] -[INFO] Results: -[INFO] -[INFO] Tests run: 185, Failures: 0, Errors: 0, Skipped: 0 -[INFO] -[INFO] -[INFO] --- jar:3.5.0:jar (default-jar) @ ArmorPlus --- -[INFO] Building jar: /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6.jar -[INFO] -[INFO] --- shade:3.1.0:shade (default) @ ArmorPlus --- -[INFO] Including de.tr7zw:functional-annotations:jar:0.1-SNAPSHOT in the shaded jar. -[INFO] Including org.bstats:bstats-bukkit:jar:1.7 in the shaded jar. -[INFO] Including org.apache.commons:commons-lang3:jar:3.17.0 in the shaded jar. -[INFO] Replacing original artifact with shaded artifact. -[INFO] Replacing /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6.jar with /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/ArmorPlus-v2.3.6-shaded.jar -[INFO] -[INFO] --- jacoco:0.8.15:report (report) @ ArmorPlus --- -[INFO] Loading execution data file /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec -[INFO] Analyzed bundle 'ArmorPlus' with 73 classes -[INFO] -[INFO] --- jacoco:0.8.15:check (check-core) @ ArmorPlus --- -[INFO] Loading execution data file /Users/goodhill/.no-mistakes/worktrees/694674b8f9a9/01KX4HQBF37N8Y0RF9J8QPKDHZ/target/jacoco.exec -[INFO] Analyzed bundle 'ArmorPlus' with 73 classes -[INFO] All coverage checks have been met. -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.880 s -[INFO] Finished at: 2026-07-10T09:06:26+10:00 -[INFO] ------------------------------------------------------------------------ From 1cc252209c9cf75aa1d418ff52053cd299b6120e Mon Sep 17 00:00:00 2001 From: Stephen Goodhill Date: Fri, 10 Jul 2026 09:35:19 +1000 Subject: [PATCH 16/16] no-mistakes(lint): Fixed deprecated Validate.notNull; no remaining issues --- src/main/java/gg/steve/mc/ap/model/id/TypedString.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/gg/steve/mc/ap/model/id/TypedString.java b/src/main/java/gg/steve/mc/ap/model/id/TypedString.java index 39473df..fb5dbed 100644 --- a/src/main/java/gg/steve/mc/ap/model/id/TypedString.java +++ b/src/main/java/gg/steve/mc/ap/model/id/TypedString.java @@ -1,19 +1,18 @@ package gg.steve.mc.ap.model.id; -import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public abstract class TypedString { private final String id; protected TypedString(String id) { - Validate.notNull(id); - this.id = id; + this.id = Objects.requireNonNull(id, "id must not be null"); } @Override