diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TakeCommand.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TakeCommand.java index 1d58170..68367a9 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TakeCommand.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TakeCommand.java @@ -27,13 +27,19 @@ public class TakeCommand implements Command { } String itemId = args.getFirst(); Room room = ctx.getPlayer().getCurrentRoom(); - Optional taken = room.removeItem(itemId); - if (taken.isEmpty()) { + Optional found = room.findItem(itemId); + if (found.isEmpty()) { ctx.getIo().write("There is no '" + itemId + "' here."); return; } - ctx.getPlayer().addItem(taken.get()); - ctx.getIo().write("You take the " + taken.get().getName() + "."); + Item item = found.get(); + if (item.isFixed()) { + ctx.getIo().write("The " + item.getName() + " is part of the room — you can't take it."); + return; + } + room.removeItem(itemId); + ctx.getPlayer().addItem(item); + ctx.getIo().write("You take the " + item.getName() + "."); } @Override diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ItemFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ItemFactory.java index a475c56..2df54ed 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ItemFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ItemFactory.java @@ -13,6 +13,7 @@ import thb.jeanluc.adventure.model.item.SwitchableItem; */ public final class ItemFactory { + /** Utility class; not instantiable. */ private ItemFactory() { } @@ -31,6 +32,7 @@ public final class ItemFactory { .name(dto.name()) .description(dto.description()) .light(Boolean.TRUE.equals(dto.light())) + .fixed(Boolean.TRUE.equals(dto.fixed())) .build(); case "readable" -> ReadableItem.builder() .id(dto.id()) @@ -38,6 +40,7 @@ public final class ItemFactory { .description(dto.description()) .readText(dto.readText()) .light(Boolean.TRUE.equals(dto.light())) + .fixed(Boolean.TRUE.equals(dto.fixed())) .build(); case "switchable" -> SwitchableItem.builder() .id(dto.id()) @@ -48,6 +51,7 @@ public final class ItemFactory { .offText(dto.offText()) .effects(thb.jeanluc.adventure.loader.dto.EffectDto.toModelList(dto.effects())) .light(Boolean.TRUE.equals(dto.light())) + .fixed(Boolean.TRUE.equals(dto.fixed())) .build(); default -> throw new WorldLoadException( "Unknown item type '" + dto.type() + "' on item '" + dto.id() + "'"); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ItemDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ItemDto.java index 3676590..430d349 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ItemDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ItemDto.java @@ -16,6 +16,7 @@ import java.util.List; * @param offText message printed when a switchable transitions to off * @param effects effects applied when a switchable transitions to on; nullable * @param light whether this item is a light source; nullable + * @param fixed whether this item is scenery that cannot be taken; nullable */ public record ItemDto( String type, @@ -27,11 +28,24 @@ public record ItemDto( String onText, String offText, List effects, - Boolean light + Boolean light, + Boolean fixed ) { - /** Backward-compatible constructor without effects/light. */ + /** + * Backward-compatible constructor without effects/light/fixed, which default to null. + * + * @param type discriminator: {@code plain | readable | switchable} + * @param id unique slug + * @param name display name + * @param description examine description + * @param readText text printed when a readable item is used + * @param initialState initial on/off state of a switchable item + * @param onText message printed when a switchable transitions to on + * @param offText message printed when a switchable transitions to off + */ public ItemDto(String type, String id, String name, String description, String readText, Boolean initialState, String onText, String offText) { - this(type, id, name, description, readText, initialState, onText, offText, null, null); + this(type, id, name, description, readText, initialState, onText, offText, + null, null, null); } } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/item/Item.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/item/Item.java index 0058aaf..5a1d6b4 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/item/Item.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/item/Item.java @@ -36,6 +36,13 @@ public abstract class Item { /** Whether this item can serve as a light source (when on, for switchables). */ protected final boolean light; + /** + * Whether this item is scenery bolted to its room and cannot be taken. + * Fixed items (the front door, the chapel shrine, the boiler generator) + * remain usable in place but must never enter the player's inventory. + */ + protected final boolean fixed; + /** * Executes the item's primary action. Side effects (text output, * mutation of player state) flow through the given context. diff --git a/Semesterprojekt/src/main/resources/world/items.yaml b/Semesterprojekt/src/main/resources/world/items.yaml index 64586e1..6fc2118 100644 --- a/Semesterprojekt/src/main/resources/world/items.yaml +++ b/Semesterprojekt/src/main/resources/world/items.yaml @@ -40,6 +40,7 @@ - type: plain id: valve name: Drainage Valve + fixed: true description: | A rusted drainage valve set into the cellar wall. Its wheel is missing — it is part of the house, not something you can carry off. @@ -52,6 +53,7 @@ - type: plain id: generator name: Generator + fixed: true description: | A rusty generator with an empty fuse socket. It is bolted to the floor — you will have to work on it where it stands. @@ -71,6 +73,7 @@ - type: plain id: shrine name: Stone Shrine + fixed: true description: | A small stone shrine with a shallow offering niche. It is mortared into the chapel floor — a place to leave something, not to take it. @@ -80,6 +83,7 @@ - type: switchable id: front_door name: Front Door + fixed: true description: The heavy front door. It would let you leave the manor for good. initialState: false onText: | @@ -113,6 +117,7 @@ - type: readable id: portrait name: Family Portrait + fixed: true description: A faded oil portrait above the dining-room hearth. readText: | A stern family poses before the manor. A small brass plate reads only: diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/FixedItemTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/FixedItemTest.java new file mode 100644 index 0000000..fb8c76a --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/FixedItemTest.java @@ -0,0 +1,39 @@ +package thb.jeanluc.adventure.command.impl; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.model.item.PlainItem; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenery items are usable in place but must never enter the inventory — + * otherwise the front door could be carried off and opened from any room. + */ +class FixedItemTest { + + @Test + void take_withFixedItem_leavesItInTheRoom() { + var w = new CommandTestSupport.World(); + var door = PlainItem.builder() + .id("door").name("Front Door").description("heavy").fixed(true).build(); + w.kitchen.addItem(door); + + new TakeCommand().execute(w.ctx, List.of("door")); + + assertThat(w.player.hasItem("door")).isFalse(); + assertThat(w.kitchen.findItem("door")).isPresent(); + assertThat(w.io.allOutput()).contains("part of the room"); + } + + @Test + void take_withOrdinaryItem_stillMovesItToInventory() { + var w = new CommandTestSupport.World(); + + new TakeCommand().execute(w.ctx, List.of("letter")); + + assertThat(w.player.hasItem("letter")).isTrue(); + assertThat(w.kitchen.findItem("letter")).isEmpty(); + } +}