fix(combinations): guard null response; reject self-combination at load

- Combinations.tryUse: only write response when non-null and non-blank,
  preventing NPE/IllegalArgument for effects-only recipes.
- CombinationFactory.fromDto: throw WorldLoadException when a == b,
  since item singletons make a self-combination impossible at runtime.
- Tests: +effectsOnlyRecipeAppliesEffectsWithoutProduceOrResponse
  (CombinationsTest 7→8), +rejectsSelfCombination
  (CombinationFactoryTest 4→5).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 17:28:32 +02:00
parent 89962b4b60
commit e7b9891771
4 changed files with 29 additions and 1 deletions

View File

@@ -123,6 +123,20 @@ class CombinationsTest {
assertThat(ctx.getPlayer().hasItem("matches")).isTrue(); // not consumed
}
@Test
void effectsOnlyRecipeAppliesEffectsWithoutProduceOrResponse() {
Combination effectsOnly = new Combination("matches", "shovel",
java.util.List.of(), java.util.List.of(), null,
java.util.List.of(new Effect(Effect.Type.SET_FLAG, "rite_done")),
null, null);
GameContext ctx = ctx(effectsOnly);
give(ctx, "matches");
give(ctx, "shovel");
Combinations.tryUse(ctx, "matches", "shovel");
assertThat(ctx.getState().isSet("rite_done")).isTrue();
assertThat(out(ctx)).doesNotContain("null");
}
@Test
void metRequiresAppliesEffectsAndProduces() {
Combination gated = new Combination("matches", "torch",

View File

@@ -58,4 +58,12 @@ class CombinationFactoryTest {
.isInstanceOf(WorldLoadException.class)
.hasMessageContaining("wraith");
}
@Test
void rejectsSelfCombination() {
CombinationDto dto = new CombinationDto("torch", "torch", null, null, null, null, "x", null);
assertThatThrownBy(() -> CombinationFactory.fromDto(dto, items()))
.isInstanceOf(WorldLoadException.class)
.hasMessageContaining("itself");
}
}