appearance.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { resolve } from "node:path";
  2. import { describe, expect, it } from "vitest";
  3. import {
  4. createEngine,
  5. type AnimalEntity,
  6. type BuildingPlacement,
  7. type GrowthSystem,
  8. type InventorySystem,
  9. type PlacementSystem,
  10. type PlotEntity,
  11. type PlantingSystem,
  12. } from "../src/index.js";
  13. const repoRoot = resolve(process.cwd(), "../..");
  14. const contentDir = resolve(repoRoot, "examples", "sample-game", "content");
  15. function engineSetup() {
  16. return createEngine({ contentDir });
  17. }
  18. function plantingOf(engine: ReturnType<typeof createEngine>): PlantingSystem {
  19. const planting = engine.systems.get<PlantingSystem>("planting");
  20. if (!planting) throw new Error("planting system not booted");
  21. return planting;
  22. }
  23. function growthOf(engine: ReturnType<typeof createEngine>): GrowthSystem {
  24. const growth = engine.systems.get<GrowthSystem>("growth");
  25. if (!growth) throw new Error("growth system not booted");
  26. return growth;
  27. }
  28. function placementOf(engine: ReturnType<typeof createEngine>): PlacementSystem {
  29. const placement = engine.systems.get<PlacementSystem>("placement");
  30. if (!placement) throw new Error("placement system not booted");
  31. return placement;
  32. }
  33. function inventoryOf(engine: ReturnType<typeof createEngine>): InventorySystem {
  34. const inv = engine.systems.get<InventorySystem>("inventory");
  35. if (!inv) throw new Error("inventory system not booted");
  36. return inv;
  37. }
  38. function sowParsnip(engine: ReturnType<typeof createEngine>): PlotEntity {
  39. inventoryOf(engine).give("parsnip-seed", 3);
  40. const planting = plantingOf(engine);
  41. planting.till(3, 3);
  42. return planting.sow(3, 3, "parsnip-seed");
  43. }
  44. describe("entity appearance (spriteRef) maintenance", () => {
  45. it("engine seeds the player entity with spriteRef 'player'", async () => {
  46. const engine = engineSetup();
  47. await engine.start();
  48. try {
  49. const player = engine.world.get("player") as { spriteRef?: string } | undefined;
  50. expect(player?.spriteRef).toBe("player");
  51. } finally {
  52. await engine.stop();
  53. }
  54. });
  55. it("PlantingSystem.sow sets plot.spriteRef to the crop's stage-0 spriteRef", async () => {
  56. const engine = engineSetup();
  57. await engine.start();
  58. try {
  59. const plot = sowParsnip(engine);
  60. expect(plot.spriteRef).toBe("parsnip-sprout");
  61. } finally {
  62. await engine.stop();
  63. }
  64. });
  65. it("GrowthSystem.forceStage updates plot.spriteRef to the forced stage's spriteRef", async () => {
  66. const engine = engineSetup();
  67. await engine.start();
  68. try {
  69. const plot = sowParsnip(engine);
  70. const grown = growthOf(engine).forceStage("plot:3:3", 2);
  71. expect(grown.spriteRef).toBe("parsnip-mature");
  72. } finally {
  73. await engine.stop();
  74. }
  75. });
  76. it("GrowthSystem day-boundary stage advance updates plot.spriteRef", async () => {
  77. const engine = engineSetup();
  78. await engine.start();
  79. try {
  80. const plot = sowParsnip(engine);
  81. expect(plot.spriteRef).toBe("parsnip-sprout");
  82. const growth = growthOf(engine);
  83. const planting = plantingOf(engine);
  84. planting.water(3, 3);
  85. growth.step(480);
  86. planting.water(3, 3);
  87. growth.step(480);
  88. const advanced = engine.world.get("plot:3:3") as PlotEntity;
  89. expect(advanced.stageIndex).toBe(1);
  90. expect(advanced.spriteRef).toBe("parsnip-grown");
  91. } finally {
  92. await engine.stop();
  93. }
  94. });
  95. it("PlantingSystem.harvest clears plot.spriteRef with the crop fields", async () => {
  96. const engine = engineSetup();
  97. await engine.start();
  98. try {
  99. const plot = sowParsnip(engine);
  100. growthOf(engine).forceStage("plot:3:3", 2);
  101. plantingOf(engine).harvest(3, 3);
  102. const cleared = engine.world.get("plot:3:3") as PlotEntity;
  103. expect(cleared.cropItemId).toBeUndefined();
  104. expect(cleared.spriteRef).toBeUndefined();
  105. } finally {
  106. await engine.stop();
  107. }
  108. });
  109. it("GrowthSystem.spawnAnimal sets animal.spriteRef to the animal's stage-0 spriteRef", async () => {
  110. const engine = engineSetup();
  111. await engine.start();
  112. try {
  113. const animal = growthOf(engine).spawnAnimal("chicken", 9, 3);
  114. expect(animal.spriteRef).toBe("chicken-chick");
  115. } finally {
  116. await engine.stop();
  117. }
  118. });
  119. it("GrowthSystem.forceAnimalStage updates animal.spriteRef", async () => {
  120. const engine = engineSetup();
  121. await engine.start();
  122. try {
  123. const animal = growthOf(engine).spawnAnimal("chicken", 9, 3);
  124. const laying = growthOf(engine).forceAnimalStage(animal.id, 2);
  125. expect(laying.spriteRef).toBe("chicken-laying");
  126. expect((engine.world.get(animal.id) as AnimalEntity).spriteRef).toBe("chicken-laying");
  127. } finally {
  128. await engine.stop();
  129. }
  130. });
  131. it("PlacementSystem.place copies the building def's spriteRef onto the entity", async () => {
  132. const engine = engineSetup();
  133. await engine.start();
  134. try {
  135. const coop = placementOf(engine).place("coop", 10, 4) as BuildingPlacement;
  136. expect(coop.spriteRef).toBe("coop");
  137. const fence = placementOf(engine).place("fence", 8, 8) as BuildingPlacement;
  138. expect(fence.spriteRef).toBe("fence");
  139. } finally {
  140. await engine.stop();
  141. }
  142. });
  143. it("spriteRef stays absent when the def has no spriteRef (fallback shape drawing)", async () => {
  144. const engine = engineSetup();
  145. await engine.start();
  146. try {
  147. engine.registry.register("item", { id: "plain-seed", name: "Plain Seed", kind: "seed", stackable: true });
  148. engine.registry.register("crop", {
  149. id: "plain",
  150. seedItem: "plain-seed",
  151. stages: [
  152. { name: "sprout", days: 1, waterRequired: false },
  153. { name: "grown", days: 1, waterRequired: false },
  154. ],
  155. seasons: ["spring"],
  156. drops: [{ itemId: "parsnip", qty: 1 }],
  157. });
  158. inventoryOf(engine).give("plain-seed", 1);
  159. const planting = plantingOf(engine);
  160. planting.till(6, 6);
  161. const plot = planting.sow(6, 6, "plain-seed");
  162. expect(plot.cropItemId).toBe("plain");
  163. expect(plot.spriteRef).toBeUndefined();
  164. } finally {
  165. await engine.stop();
  166. }
  167. });
  168. });