spawnpoint-studio 0.3.4__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. {spawnpoint_studio-0.3.4/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.5}/PKG-INFO +8 -4
  2. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/README.md +7 -3
  3. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/build.py +3 -1
  4. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cli.py +12 -1
  5. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/demo.py +8 -4
  6. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/demo_touch.js +1 -1
  7. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/graph.py +2 -0
  8. spawnpoint_studio-0.3.5/compiler/spawnpoint/logo.py +167 -0
  9. spawnpoint_studio-0.3.5/compiler/spawnpoint/music.py +117 -0
  10. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/farm.py +38 -0
  11. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/gallery.py +1 -1
  12. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/assemble.py +24 -3
  13. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/farmgen.py +8 -2
  14. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5/compiler/spawnpoint_studio.egg-info}/PKG-INFO +8 -4
  15. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +5 -0
  16. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_farm.py +29 -0
  17. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/sunny_farm.json +107 -46
  18. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_days_component.gd +66 -3
  19. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_plot_component.gd +8 -1
  20. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_save_component.gd +18 -0
  21. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farmer_component.gd +24 -4
  22. spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/game_menu_component.gd +157 -0
  23. spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/shop_component.gd +109 -0
  24. spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/sprinkler_component.gd +58 -0
  25. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/manifest.json +168 -0
  26. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/game_state.gd +1 -0
  27. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/sfx.gd +30 -0
  28. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/pyproject.toml +1 -1
  29. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/LICENSE +0 -0
  30. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/MANIFEST.in +0 -0
  31. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/__init__.py +0 -0
  32. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/analyze.py +0 -0
  33. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/__init__.py +0 -0
  34. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/server.py +0 -0
  35. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/app.css +0 -0
  36. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/app.js +0 -0
  37. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/index.html +0 -0
  38. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/diagnostics.py +0 -0
  39. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/expr.py +0 -0
  40. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/gif.py +0 -0
  41. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/godot.py +0 -0
  42. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/level.py +0 -0
  43. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/llm_server.py +0 -0
  44. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/manifest.py +0 -0
  45. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/__init__.py +0 -0
  46. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/report.py +0 -0
  47. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/runner.py +0 -0
  48. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/tuner.py +0 -0
  49. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/project.py +0 -0
  50. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/py.typed +0 -0
  51. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/rules.py +0 -0
  52. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sfx.py +0 -0
  53. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/__init__.py +0 -0
  54. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/ai.py +0 -0
  55. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/cartoon.py +0 -0
  56. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/cartoon_monsters.py +0 -0
  57. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/chibi.py +0 -0
  58. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/contour.py +0 -0
  59. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/creatures.py +0 -0
  60. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/generate.py +0 -0
  61. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/importer.py +0 -0
  62. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/items.py +0 -0
  63. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/monsters.py +0 -0
  64. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/motion.py +0 -0
  65. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/palettes.py +0 -0
  66. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/resolve.py +0 -0
  67. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/scenery.py +0 -0
  68. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/spriteset.py +0 -0
  69. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/tiles.py +0 -0
  70. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/__init__.py +0 -0
  71. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/brief.py +0 -0
  72. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/creative.py +0 -0
  73. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/dataset.py +0 -0
  74. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/edits.py +0 -0
  75. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/genres.py +0 -0
  76. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/levelgen.py +0 -0
  77. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/llm.py +0 -0
  78. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/orchestrator.py +0 -0
  79. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/prompts.py +0 -0
  80. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/web.py +0 -0
  81. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/webcheck.py +0 -0
  82. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio_cli.py +0 -0
  83. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
  84. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
  85. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
  86. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
  87. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/conftest.py +0 -0
  88. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_ai_sprites.py +0 -0
  89. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_analyze.py +0 -0
  90. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_cockpit.py +0 -0
  91. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_demo.py +0 -0
  92. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_e2e.py +0 -0
  93. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_expr.py +0 -0
  94. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_genres.py +0 -0
  95. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_gif.py +0 -0
  96. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_godot_library.py +0 -0
  97. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_graph.py +0 -0
  98. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_level.py +0 -0
  99. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_live_llm.py +0 -0
  100. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_manifest.py +0 -0
  101. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_playtest.py +0 -0
  102. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_rules.py +0 -0
  103. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_sfx.py +0 -0
  104. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_sprites.py +0 -0
  105. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_studio.py +0 -0
  106. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_web.py +0 -0
  107. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/castle_defense.json +0 -0
  108. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/coin_hopper.json +0 -0
  109. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/dino_eggs.json +0 -0
  110. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/knight_quest.json +0 -0
  111. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/neon_arena.json +0 -0
  112. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/night_survivor.json +0 -0
  113. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/ninja_rescue.json +0 -0
  114. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/pixel_arena.json +0 -0
  115. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/pixel_hopper.json +0 -0
  116. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/star_run.json +0 -0
  117. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/aim_component.gd +0 -0
  118. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/animator_component.gd +0 -0
  119. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/base_component.gd +0 -0
  120. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/bubble_board_component.gd +0 -0
  121. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
  122. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
  123. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/crop_pest_component.gd +0 -0
  124. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/experience_component.gd +0 -0
  125. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_animal_component.gd +0 -0
  126. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/fishing_spot_component.gd +0 -0
  127. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/flight_path_component.gd +0 -0
  128. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/gate_component.gd +0 -0
  129. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/gatherable_component.gd +0 -0
  130. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/ground_patch_component.gd +0 -0
  131. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/health_bar_component.gd +0 -0
  132. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/health_component.gd +0 -0
  133. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
  134. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
  135. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/juice_component.gd +0 -0
  136. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
  137. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/market_component.gd +0 -0
  138. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/path_follower_component.gd +0 -0
  139. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
  140. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
  141. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
  142. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
  143. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/shmup_ship_component.gd +0 -0
  144. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/sleep_spot_component.gd +0 -0
  145. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
  146. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
  147. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tool_bench_component.gd +0 -0
  148. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
  149. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_brain_component.gd +0 -0
  150. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_builder_component.gd +0 -0
  151. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_slot_component.gd +0 -0
  152. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
  153. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/warp_component.gd +0 -0
  154. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/wave_spawner_component.gd +0 -0
  155. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
  156. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
  157. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
  158. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
  159. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
  160. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/fx.gd +0 -0
  161. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/hud.gd +0 -0
  162. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/level.gd +0 -0
  163. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/sp.gd +0 -0
  164. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/world.gd +0 -0
  165. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
  166. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
  167. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/build.gd +0 -0
  168. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/build.tscn +0 -0
  169. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/capture.gd +0 -0
  170. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/capture.tscn +0 -0
  171. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
  172. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
  173. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/overview.gd +0 -0
  174. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/overview.tscn +0 -0
  175. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest.gd +0 -0
  176. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
  177. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
  178. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/record.gd +0 -0
  179. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/record.tscn +0 -0
  180. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/smoke.gd +0 -0
  181. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
  182. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/setup.cfg +0 -0
  183. {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawnpoint-studio
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Turn a sentence into a juicy, playable Godot 4 game — locally, on a consumer GPU.
5
5
  Author: smoky1496
6
6
  License: MIT
@@ -22,6 +22,8 @@ Provides-Extra: dev
22
22
  Requires-Dist: pytest>=7; extra == "dev"
23
23
  Dynamic: license-file
24
24
 
25
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/logo.png" width="520" alt="Spawn Point: a sprout growing out of a glowing spawn pad, and the name in gold pixel letters"></p>
26
+
25
27
  # Spawn Point
26
28
 
27
29
  **Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
@@ -187,8 +189,10 @@ shipping bin, while crows, foxes, rabbits or mice go for your crops. Step throug
187
189
  inside (bed, fireplace, table, rug). Maps vary: the house on either side, the field and the pen swapping places, the
188
190
  pond moving around. The workbench by the house sells tool upgrades (a watering can and a hoe that work a
189
191
  3x3 patch, a bigger basket, a sharper axe, a better rod), and the farm saves itself: close the game and it carries on
190
- where you left it (a won or lost season, or R, starts a fresh one). On a phone, ACT does all of it and a tap on a seed in
191
- the bar picks it. The
192
+ where you left it (a won or lost season, or R, starts a fresh one). Some days it rains and the crops water themselves
193
+ (snow on a winter farm); a shop stall sells hens, cows, golden melon seeds and sprinklers that water their patch every
194
+ morning. A farm opens on a title screen (carry on, or N for a new farm), P pauses, and each season has its own little
195
+ chiptune, composed at build time. On a phone, ACT does all of it, a tap on a seed in the bar picks it, and II pauses. The
192
196
  hotbar shows your seeds and basket. You walk behind trees and the house (the scene is depth-sorted), and only an
193
197
  object's footprint blocks you. `spawnpoint overview <project>` saves a picture of the whole map after a short autopilot
194
198
  run, like the one above.
@@ -407,7 +411,7 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
407
411
  | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
408
412
  | Shooters | `FlightPath` (straight, wave, dive and hover patterns across the screen), `ShmupShip` (auto-fire, power-ups that add shots and spread), `HealthBar` (boss bars), and `WaveSpawner` gates (`autostart`, `endless`, random spread) |
409
413
  | Tower defense | `PathFollower` (walks the road to the base), `Base` (loses a life per invader that gets in), `WaveSpawner` ("8 x Goblin every 1.2; ..."), `TowerSlot`, `TowerBuilder` (click a pad, pay in gold), `TowerBrain` (targets the invader furthest along), `GroundPatch` (roads and paths) |
410
- | Farming | `FarmPlot` (soil that grows a crop only while watered, drawn from its growth stages), `Farmer` (till / plant / water / harvest, buy seeds, feed, collect, fish, chop, sleep, a basket and a tappable hotbar), `FarmAnimal` (wanders, needs feeding, gives eggs or milk, chicks grow up), `FarmDays` (a season of days with day and night), `SleepSpot` (a bed), `ToolBench` (tool upgrades), `FarmSave` (keeps the farm between plays), `Gatherable` (trees and rocks that break into wood and stone), `FishingSpot` (cast, bite, reel in), `Warp` (doorways, e.g. into the farmhouse), `Market` (a stall or a shipping bin that buys the basket), `CropPest` (crows that eat crops) |
414
+ | Farming | `FarmPlot` (soil that grows a crop only while watered, drawn from its growth stages), `Farmer` (till / plant / water / harvest, buy seeds, feed, collect, fish, chop, sleep, a basket and a tappable hotbar), `FarmAnimal` (wanders, needs feeding, gives eggs or milk, chicks grow up), `FarmDays` (a season of days with day and night), `SleepSpot` (a bed), `ToolBench` (tool upgrades), `FarmSave` (keeps the farm between plays), `Shop` and `Sprinkler` (buy animals, seeds and sprinklers), `GameMenu` (title screen, pause menu, music), `Gatherable` (trees and rocks that break into wood and stone), `FishingSpot` (cast, bite, reel in), `Warp` (doorways, e.g. into the farmhouse), `Market` (a stall or a shipping bin that buys the basket), `CropPest` (crows that eat crops) |
411
415
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
412
416
 
413
417
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -1,3 +1,5 @@
1
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/logo.png" width="520" alt="Spawn Point: a sprout growing out of a glowing spawn pad, and the name in gold pixel letters"></p>
2
+
1
3
  # Spawn Point
2
4
 
3
5
  **Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
@@ -163,8 +165,10 @@ shipping bin, while crows, foxes, rabbits or mice go for your crops. Step throug
163
165
  inside (bed, fireplace, table, rug). Maps vary: the house on either side, the field and the pen swapping places, the
164
166
  pond moving around. The workbench by the house sells tool upgrades (a watering can and a hoe that work a
165
167
  3x3 patch, a bigger basket, a sharper axe, a better rod), and the farm saves itself: close the game and it carries on
166
- where you left it (a won or lost season, or R, starts a fresh one). On a phone, ACT does all of it and a tap on a seed in
167
- the bar picks it. The
168
+ where you left it (a won or lost season, or R, starts a fresh one). Some days it rains and the crops water themselves
169
+ (snow on a winter farm); a shop stall sells hens, cows, golden melon seeds and sprinklers that water their patch every
170
+ morning. A farm opens on a title screen (carry on, or N for a new farm), P pauses, and each season has its own little
171
+ chiptune, composed at build time. On a phone, ACT does all of it, a tap on a seed in the bar picks it, and II pauses. The
168
172
  hotbar shows your seeds and basket. You walk behind trees and the house (the scene is depth-sorted), and only an
169
173
  object's footprint blocks you. `spawnpoint overview <project>` saves a picture of the whole map after a short autopilot
170
174
  run, like the one above.
@@ -383,7 +387,7 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
383
387
  | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
384
388
  | Shooters | `FlightPath` (straight, wave, dive and hover patterns across the screen), `ShmupShip` (auto-fire, power-ups that add shots and spread), `HealthBar` (boss bars), and `WaveSpawner` gates (`autostart`, `endless`, random spread) |
385
389
  | Tower defense | `PathFollower` (walks the road to the base), `Base` (loses a life per invader that gets in), `WaveSpawner` ("8 x Goblin every 1.2; ..."), `TowerSlot`, `TowerBuilder` (click a pad, pay in gold), `TowerBrain` (targets the invader furthest along), `GroundPatch` (roads and paths) |
386
- | Farming | `FarmPlot` (soil that grows a crop only while watered, drawn from its growth stages), `Farmer` (till / plant / water / harvest, buy seeds, feed, collect, fish, chop, sleep, a basket and a tappable hotbar), `FarmAnimal` (wanders, needs feeding, gives eggs or milk, chicks grow up), `FarmDays` (a season of days with day and night), `SleepSpot` (a bed), `ToolBench` (tool upgrades), `FarmSave` (keeps the farm between plays), `Gatherable` (trees and rocks that break into wood and stone), `FishingSpot` (cast, bite, reel in), `Warp` (doorways, e.g. into the farmhouse), `Market` (a stall or a shipping bin that buys the basket), `CropPest` (crows that eat crops) |
390
+ | Farming | `FarmPlot` (soil that grows a crop only while watered, drawn from its growth stages), `Farmer` (till / plant / water / harvest, buy seeds, feed, collect, fish, chop, sleep, a basket and a tappable hotbar), `FarmAnimal` (wanders, needs feeding, gives eggs or milk, chicks grow up), `FarmDays` (a season of days with day and night), `SleepSpot` (a bed), `ToolBench` (tool upgrades), `FarmSave` (keeps the farm between plays), `Shop` and `Sprinkler` (buy animals, seeds and sprinklers), `GameMenu` (title screen, pause menu, music), `Gatherable` (trees and rocks that break into wood and stone), `FishingSpot` (cast, bite, reel in), `Warp` (doorways, e.g. into the farmhouse), `Market` (a stall or a shipping bin that buys the basket), `CropPest` (crows that eat crops) |
387
391
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
388
392
 
389
393
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -9,7 +9,7 @@ from dataclasses import dataclass, field
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
- from . import godot, manifest as manifest_mod, project, sfx
12
+ from . import godot, manifest as manifest_mod, music, project, sfx
13
13
  from .analyze import Analysis, analyze
14
14
  from .diagnostics import Diagnostics
15
15
  from .manifest import Manifest
@@ -68,6 +68,8 @@ def build(data: Any, out_dir: Path, *, manifest: Manifest | None = None, smoke:
68
68
  project.write(out_dir, data, plan, analysis.rules_source, feature, analysis.sprites, analysis.tiles_image,
69
69
  analysis.scenery_images)
70
70
  sfx.write_all(out_dir / "sfx", seed=analysis.graph.settings.seed)
71
+ if analysis.graph.settings.music != "none":
72
+ music.write(out_dir / "sfx", analysis.graph.settings.music, analysis.graph.settings.seed)
71
73
  timings["write"] = time.perf_counter() - t
72
74
 
73
75
  log: list[str] = []
@@ -97,6 +97,9 @@ def main(argv: list[str] | None = None) -> int:
97
97
  gf.add_argument("--focus", default="", choices=["", "field", "animals", "fish", "house"],
98
98
  help="farms: show one activity")
99
99
 
100
+ lg = sub.add_parser("logo", help="write the Spawn Point pixel-art logo (icons, wordmark, favicon)")
101
+ lg.add_argument("-o", "--out", type=Path, default=Path("logo"))
102
+
100
103
  ov = sub.add_parser("overview", help="save one picture of a built project's whole map (needs a display)")
101
104
  ov.add_argument("project", type=Path)
102
105
  ov.add_argument("-o", "--out", type=Path, help="output file (default: <project>/overview.png)")
@@ -150,7 +153,7 @@ def main(argv: list[str] | None = None) -> int:
150
153
  return {"validate": _validate, "build": _build, "run": _run, "components": _components,
151
154
  "manifest": _manifest, "create": studio_cli.create, "tweak": studio_cli.tweak,
152
155
  "serve": studio_cli.serve, "dataset": studio_cli.dataset, "playtest": studio_cli.playtest, "studio": studio_cli.studio,
153
- "sprites": _sprites, "gif": _gif, "overview": _overview, "llm": _llm, "examples": _examples,
156
+ "sprites": _sprites, "gif": _gif, "overview": _overview, "logo": _logo, "llm": _llm, "examples": _examples,
154
157
  "demo": _demo}[args.cmd](args)
155
158
  except godot.GodotError as e:
156
159
  print(f"error: {e}", file=sys.stderr)
@@ -264,6 +267,14 @@ def _gif(args) -> int:
264
267
  return 0
265
268
 
266
269
 
270
+ def _logo(args) -> int:
271
+ from .logo import write
272
+
273
+ for path in write(args.out):
274
+ print(f"Wrote {path}")
275
+ return 0
276
+
277
+
267
278
  def _overview(args) -> int:
268
279
  from .gif import overview
269
280
 
@@ -55,9 +55,9 @@ SHOWCASE = [
55
55
  "Move: arrows / WASD | Dash: Shift | Pick an upgrade: 1 / 2 / 3 or click | Restart: R",
56
56
  {"pad": "dpad", "buttons": [["DASH", "ShiftLeft"]], "tap": "Move with the stick - you shoot on your own"}),
57
57
  ("sunny_farm.json", "farming",
58
- "A cozy island farm over three days: buy seeds, till and grow strawberries, carrots and pumpkins, feed the hens and the cow, fish in the pond, chop wood, sleep in the farmhouse, upgrade your tools, and sell it all at the shipping bin. Your farm is saved as you play.",
59
- "Move: arrows / WASD | Till, plant, water, harvest, feed, fish, chop, go in, sleep: Space / E | Seed: 1 / 2 / 3 or click it | Shoo crows: X | Restart: R",
60
- {"pad": "dpad", "buttons": [["ACT", "Space"], ["SHOO", "KeyX"]],
58
+ "A cozy island farm over three days: buy seeds, till and grow strawberries, carrots and pumpkins, feed the hens and the cow, fish in the pond, chop wood, sleep in the farmhouse, upgrade your tools, buy hens and sprinklers, and sell it all at the shipping bin. Rainy days water the crops; your farm is saved as you play.",
59
+ "Move: arrows / WASD | Till, plant, water, harvest, feed, fish, chop, shop, go in, sleep: Space / E | Seed: 1 / 2 / 3 or click it | Shoo crows: X | Pause: P | Restart: R",
60
+ {"pad": "dpad", "buttons": [["ACT", "Space"], ["SHOO", "KeyX"], ["II", "KeyP"]],
61
61
  "tap": "ACT works the field, feeds, fishes and opens doors; tap a seed in the bar to choose it"}),
62
62
  ("pixel_arena.json", "arena shooter",
63
63
  "A top-down arena: blast through robots and turrets, then reach the portal.",
@@ -127,6 +127,8 @@ def build_site(out: Path, games: list[Game] | None = None, screenshots: bool = T
127
127
  share_engine(out, target)
128
128
  log(f" {g.slug}: built (web export {seconds:.1f}s)")
129
129
  shutil.copy2(TOUCH_JS, out / "touch.js") # every game page loads it (before the browser check)
130
+ from . import logo
131
+ logo.write(out) # favicon.ico, logo.png and the icon sizes
130
132
  built = [g for g in games if (out / g.slug / "index.html").exists()]
131
133
  if screenshots:
132
134
  if webcheck.find_chrome() is None:
@@ -205,6 +207,8 @@ def index_html(games: list[Game]) -> str:
205
207
  <meta charset="utf-8">
206
208
  <meta name="viewport" content="width=device-width, initial-scale=1">
207
209
  <title>Spawn Point demos</title>
210
+ <link rel="icon" href="favicon.ico">
211
+ <meta property="og:image" content="logo.png">
208
212
  <meta name="description" content="Nine Godot 4 games built with Spawn Point: platformers, a sword fighter, tower defense, a space shooter, a bubble shooter, a survivor, a farm. Play in your browser.">
209
213
  <meta property="og:title" content="Spawn Point demos: play Godot games in your browser">
210
214
  <meta property="og:description" content="A Mario-style platformer, a ninja rescue, a dinosaur-egg shooter, a survivor and an arena, all built from Spawn Point components.">
@@ -243,7 +247,7 @@ footer {{ color:var(--muted); font-size:13px; padding-bottom:40px }}
243
247
  </head>
244
248
  <body>
245
249
  <header>
246
- <h1>Spawn<span>Point</span> demos</h1>
250
+ <h1 class="brand"><img src="logo-icon-64.png" width="48" height="48" alt="" style="image-rendering:pixelated;vertical-align:middle;margin-right:10px">Spawn<span>Point</span> demos</h1>
247
251
  <p class="lede">Nine games, nine ways to play, all built by Spawn Point from small JSON game graphs and a
248
252
  library of tested Godot 4 components: game feel, cartoon or pixel art and collisions come for free. Click a card to play in
249
253
  your browser, no install needed.</p>
@@ -8,7 +8,7 @@
8
8
  var forced = location.search.indexOf("touch") >= 0; // ?touch shows the controls on a desktop (testing)
9
9
  if (!(coarse || forced || "ontouchstart" in window) || location.search.indexOf("shot") >= 0) return;
10
10
 
11
- var KEYS = {Space: " ", ShiftLeft: "Shift", KeyX: "x", KeyR: "r", KeyE: "e", Digit1: "1", Digit2: "2", Digit3: "3",
11
+ var KEYS = {Space: " ", ShiftLeft: "Shift", KeyX: "x", KeyR: "r", KeyE: "e", Digit1: "1", Digit2: "2", Digit3: "3", KeyP: "p", KeyN: "n", KeyM: "m",
12
12
  ArrowLeft: "ArrowLeft",
13
13
  ArrowRight: "ArrowRight", ArrowUp: "ArrowUp", ArrowDown: "ArrowDown"};
14
14
  var held = {};
@@ -133,6 +133,8 @@ class Settings(_Model):
133
133
  # "~" water, "," tidy grass (fields), "_" sand. Used by farms; replaces the floor and scenery.
134
134
  terrain: list[str] | None = None
135
135
  season: Literal["spring", "autumn", "winter", "desert"] = "spring" # the terrain's colours
136
+ # Background music, composed at build time (see music.py): a season's tune, or none.
137
+ music: Literal["none", "spring", "autumn", "winter", "desert"] = "none"
136
138
  # Draw entities lower on screen in front of the ones above them (top-down games with tall trees and houses).
137
139
  y_sort: bool = False
138
140
  # Extra pictures for components (crop growth stages, item icons, soil): name -> what to draw.
@@ -0,0 +1,167 @@
1
+ """The Spawn Point logo, drawn as pixel art from code: a glowing spawn pad with a sprout growing out of it, and a
2
+ chunky pixel wordmark. `spawnpoint logo -o <dir>` writes the icon (several sizes), the wordmark and a favicon."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ from PIL import Image
10
+
11
+ RGBA = tuple[int, int, int, int]
12
+
13
+
14
+ def _c(h: str, a: int = 255) -> RGBA:
15
+ h = h.lstrip("#")
16
+ return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16), a
17
+
18
+
19
+ BG, BG_LIGHT, BG_EDGE = _c("#1d1a33"), _c("#2b2650"), _c("#0e0c1c")
20
+ PAD, PAD_LIGHT, PAD_DARK, GLOW = _c("#29adff"), _c("#9ee8ff"), _c("#1a6ab0"), _c("#29adff", 70)
21
+ LEAF, LEAF_LIGHT, LEAF_DARK, STEM = _c("#5cd84a"), _c("#a8f070"), _c("#2c8a3c"), _c("#3fa83a")
22
+ GOLD, GOLD_DARK, GOLD_LIGHT, INK = _c("#ffec27"), _c("#e8902a"), _c("#fff7a8"), _c("#2a1e2e")
23
+ SPARK = _c("#fffaf0")
24
+
25
+
26
+ def _blend(dst: np.ndarray, y: int, x: int, c: RGBA) -> None:
27
+ if 0 <= y < dst.shape[0] and 0 <= x < dst.shape[1]:
28
+ a = c[3] / 255.0
29
+ dst[y, x, :3] = (dst[y, x, :3] * (1 - a) + np.array(c[:3]) * a).astype(np.uint8)
30
+ dst[y, x, 3] = max(dst[y, x, 3], c[3])
31
+
32
+
33
+ def icon(size: int = 32) -> np.ndarray:
34
+ """The square app icon at its art size (32x32 art pixels; scale it up by whole numbers)."""
35
+ n = 32
36
+ img = np.zeros((n, n, 4), dtype=np.uint8)
37
+ # a rounded dark tile with a soft top light
38
+ for y in range(n):
39
+ for x in range(n):
40
+ corner = min(x, n - 1 - x) + min(y, n - 1 - y)
41
+ if corner < 3 and (min(x, n - 1 - x) < 2 and min(y, n - 1 - y) < 2):
42
+ if (min(x, n - 1 - x), min(y, n - 1 - y)) in ((0, 0), (0, 1), (1, 0)):
43
+ continue
44
+ img[y, x] = _c("#3a3366") if y < 6 else BG_LIGHT if y < 11 else _c("#24204a") if y < 16 else BG
45
+ edge = img[..., 3] > 0
46
+ p = np.pad(edge, 1)
47
+ ring = edge & ~(p[:-2, 1:-1] & p[2:, 1:-1] & p[1:-1, :-2] & p[1:-1, 2:])
48
+ img[ring] = BG_EDGE
49
+ # the beam of light rising from the pad
50
+ for y in range(6, 25):
51
+ for x in range(12, 20):
52
+ a = int(60 * (1 - abs(x - 15.5) / 4.5) * (y - 5) / 19)
53
+ if a > 0:
54
+ _blend(img, y, x, (158, 232, 255, a))
55
+ # the spawn pad: a glowing ellipse
56
+ for y in range(22, 29):
57
+ for x in range(5, 27):
58
+ d = ((x - 15.5) / 10.5) ** 2 + ((y - 25) / 3.2) ** 2
59
+ if d <= 1.0:
60
+ img[y, x] = PAD_DARK if d > 0.62 else PAD
61
+ elif d <= 1.35:
62
+ _blend(img, y, x, GLOW)
63
+ for x in range(9, 23): # the lit inner rim
64
+ _blend(img, 23, x, PAD_LIGHT)
65
+ for x in range(11, 21):
66
+ img[25, x] = PAD_LIGHT if (x % 3) else PAD
67
+ # the sprout: a stem and two round leaves, a bud on top
68
+ for y in range(12, 25):
69
+ img[y, 15] = STEM
70
+ img[y, 16] = LEAF_DARK
71
+ for cx, cy, rx, ry in ((11.5, 15.5, 4.2, 2.4), (20.5, 12.5, 4.2, 2.4)):
72
+ for y in range(int(cy - ry) - 1, int(cy + ry) + 2):
73
+ for x in range(int(cx - rx) - 1, int(cx + rx) + 2):
74
+ d = ((x + 0.5 - cx) / rx) ** 2 + ((y + 0.5 - cy) / ry) ** 2
75
+ if d <= 1.0:
76
+ img[y, x] = LEAF_DARK if y + 0.5 > cy + ry * 0.35 else LEAF
77
+ if d < 0.35 and y + 0.5 < cy:
78
+ img[y, x] = LEAF_LIGHT
79
+ for (y, x, col) in ((9, 15, LEAF), (9, 16, LEAF), (8, 15, LEAF_LIGHT), (10, 15, LEAF), (10, 16, LEAF_DARK),
80
+ (11, 15, STEM), (11, 16, LEAF_DARK)):
81
+ img[y, x] = col
82
+ sprout = (img[..., 1] > 150) & (img[..., 0] < 200) & (img[..., 2] < 140) # an outline round the green
83
+ q = np.pad(sprout, 1)
84
+ rim = (q[:-2, 1:-1] | q[2:, 1:-1] | q[1:-1, :-2] | q[1:-1, 2:]) & ~sprout & (np.arange(n)[:, None] < 22)
85
+ img[rim] = _c("#143a24")
86
+ # sparkles
87
+ for (y, x) in ((5, 7), (8, 25), (4, 21), (19, 6)):
88
+ img[y, x] = SPARK
89
+ for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
90
+ _blend(img, y + dy, x + dx, (255, 250, 240, 140))
91
+ return img
92
+
93
+
94
+ # A 5x7 pixel font for the wordmark (only the letters it needs, and a few spares).
95
+ GLYPHS = {
96
+ "S": ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
97
+ "P": ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
98
+ "A": ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
99
+ "W": ["10001", "10001", "10001", "10101", "10101", "10101", "01010"],
100
+ "N": ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
101
+ "O": ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
102
+ "I": ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
103
+ "T": ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
104
+ " ": ["00"] * 7,
105
+ }
106
+
107
+
108
+ def wordmark(text: str = "SPAWN POINT", scale: int = 3) -> np.ndarray:
109
+ """The name in chunky gold pixel letters with a dark outline and a drop shadow (art pixels)."""
110
+ glyphs = [GLYPHS.get(ch, GLYPHS[" "]) for ch in text.upper()]
111
+ w = sum((len(g[0]) + 1) * scale + 1 for g in glyphs) + 8
112
+ h = 7 * scale + 8
113
+ mask = np.zeros((h, w), dtype=bool)
114
+ shade = np.zeros((h, w), dtype=np.uint8) # 0 body, 1 top light, 2 bottom shadow
115
+ x_at = 3
116
+ for rows in glyphs:
117
+ for gy, row in enumerate(rows):
118
+ for gx, bit in enumerate(row):
119
+ if bit == "1": # one pixel wider than the grid: bold strokes
120
+ y0, x0 = 3 + gy * scale, x_at + gx * scale
121
+ mask[y0:y0 + scale, x0:x0 + scale + 1] = True
122
+ shade[y0:y0 + scale, x0:x0 + scale + 1] = 1 if gy < 2 else (2 if gy > 4 else 0)
123
+ x_at += (len(rows[0]) + 1) * scale + 1
124
+ img = np.zeros((h, w, 4), dtype=np.uint8)
125
+ shadow = np.zeros_like(mask)
126
+ shadow[2:, 2:] = mask[:-2, :-2]
127
+ p = np.pad(mask, 1)
128
+ outline = (p[:-2, 1:-1] | p[2:, 1:-1] | p[1:-1, :-2] | p[1:-1, 2:]) & ~mask
129
+ img[shadow & ~mask] = BG_EDGE
130
+ img[outline] = INK
131
+ img[mask] = GOLD
132
+ img[mask & (shade == 1)] = GOLD_LIGHT
133
+ img[mask & (shade == 2)] = GOLD_DARK
134
+ return img
135
+
136
+
137
+ def banner(scale: int = 3) -> np.ndarray:
138
+ """The icon and the wordmark side by side (art pixels, transparent background)."""
139
+ ic, wm = icon(), wordmark(scale=scale)
140
+ h = max(ic.shape[0], wm.shape[0])
141
+ out = np.zeros((h, ic.shape[1] + 6 + wm.shape[1], 4), dtype=np.uint8)
142
+ out[(h - ic.shape[0]) // 2:(h - ic.shape[0]) // 2 + ic.shape[0], :ic.shape[1]] = ic
143
+ y = (h - wm.shape[0]) // 2
144
+ out[y:y + wm.shape[0], ic.shape[1] + 6:] = wm
145
+ return out
146
+
147
+
148
+ def _up(a: np.ndarray, k: int) -> Image.Image:
149
+ return Image.fromarray(a).resize((a.shape[1] * k, a.shape[0] * k), Image.NEAREST)
150
+
151
+
152
+ def write(out_dir: Path) -> list[Path]:
153
+ """logo-icon-<size>.png (32..512), logo.png (icon + name), favicon.ico."""
154
+ out_dir.mkdir(parents=True, exist_ok=True)
155
+ written = []
156
+ ic = icon()
157
+ for k in (1, 2, 4, 8, 16):
158
+ path = out_dir / f"logo-icon-{32 * k}.png"
159
+ _up(ic, k).save(path)
160
+ written.append(path)
161
+ path = out_dir / "logo.png"
162
+ _up(banner(), 4).save(path)
163
+ written.append(path)
164
+ fav = out_dir / "favicon.ico"
165
+ _up(ic, 2).save(fav, sizes=[(16, 16), (32, 32), (64, 64)])
166
+ written.append(fav)
167
+ return written
@@ -0,0 +1,117 @@
1
+ """Background music: a short chiptune loop per farming season, composed from a seed (no audio files are read).
2
+
3
+ Eight bars over a four-chord progression: a square-wave melody that walks the chord tones, a triangle bass on the
4
+ roots, and a quiet arpeggio (bells in winter). The loop is written as res://sfx/music.wav and played on repeat.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import io
10
+ import random
11
+ import wave
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+
16
+ RATE = 22050 # music is fine at half rate, and half the size on the web
17
+
18
+ # season -> (tempo, root note (MIDI), scale steps, progression of chord roots as scale degrees, lead wave)
19
+ STYLES = {
20
+ "spring": (112, 60, (0, 2, 4, 5, 7, 9, 11), (0, 4, 5, 3), "square"), # C major, I-V-vi-IV
21
+ "autumn": (96, 57, (0, 2, 3, 5, 7, 8, 10), (0, 5, 2, 6), "square"), # A minor, i-VI-III-VII
22
+ "winter": (84, 62, (0, 2, 4, 5, 7, 9, 11), (0, 2, 3, 0), "sine"), # D major, slow bells, I-iii-IV-I
23
+ "desert": (100, 64, (0, 1, 4, 5, 7, 8, 10), (0, 1, 0, 4), "square"), # E phrygian dominant, i-bII-i-V
24
+ }
25
+ SEASONS = tuple(STYLES)
26
+
27
+
28
+ def _freq(midi: float) -> float:
29
+ return 440.0 * 2 ** ((midi - 69) / 12)
30
+
31
+
32
+ def _osc(kind: str, phase: np.ndarray) -> np.ndarray:
33
+ p = phase % 1.0
34
+ if kind == "square":
35
+ return np.where(p < 0.25, 1.0, -1.0) # a 25% pulse: the classic chiptune lead
36
+ if kind == "triangle":
37
+ return 4 * np.abs(p - 0.5) - 1
38
+ return np.sin(2 * np.pi * p)
39
+
40
+
41
+ def _note(buf: np.ndarray, start: float, length: float, midi: float, kind: str, volume: float,
42
+ decay: float = 0.0) -> None:
43
+ """Adds one note to `buf` (seconds), with a short attack and release (and an optional exponential decay)."""
44
+ s0, n = int(start * RATE), int(length * RATE)
45
+ n = min(n, len(buf) - s0)
46
+ if n <= 0:
47
+ return
48
+ i = np.arange(n)
49
+ attack, release = max(int(0.006 * RATE), 1), max(int(0.04 * RATE), 1)
50
+ env = np.minimum(np.minimum(1.0, i / attack), (n - i) / release)
51
+ if decay:
52
+ env = env * np.exp(-decay * i / RATE)
53
+ buf[s0:s0 + n] += volume * env * _osc(kind, _freq(midi) * i / RATE)
54
+
55
+
56
+ def compose(season: str = "spring", seed: int = 0) -> list[float]:
57
+ """Eight bars of music as samples in -1..1."""
58
+ tempo, root, scale, progression, lead = STYLES.get(season, STYLES["spring"])
59
+ rng = random.Random(f"music:{season}:{seed}")
60
+ beat = 60.0 / tempo
61
+ bars = 8
62
+ buf = np.zeros(int(bars * 4 * beat * RATE) + RATE // 4)
63
+
64
+ def degree(d: int, octave: int = 0) -> int:
65
+ return root + scale[d % len(scale)] + 12 * (d // len(scale) + octave)
66
+
67
+ melody_degree = 2
68
+ for bar in range(bars):
69
+ chord = progression[bar % len(progression)]
70
+ tones = [chord, chord + 2, chord + 4]
71
+ t0 = bar * 4 * beat
72
+ for b in range(4): # bass on the beat (root, and the fifth on beat three)
73
+ d = chord if b != 2 else chord + 4
74
+ _note(buf, t0 + b * beat, beat * 0.9, degree(d, -2), "triangle", 0.30)
75
+ for step in range(8): # an arpeggio on the eighths (bells in winter)
76
+ d = tones[step % 3]
77
+ if season == "winter":
78
+ if step % 2 == 0:
79
+ _note(buf, t0 + step * beat / 2, beat * 1.5, degree(d, 1), "sine", 0.10, decay=3.0)
80
+ else:
81
+ _note(buf, t0 + step * beat / 2, beat * 0.35, degree(d), "square", 0.05)
82
+ step = 0
83
+ while step < 8: # the melody: a walk that leans toward the chord's notes
84
+ length = rng.choice((1, 1, 2, 2, 3)) if season != "winter" else rng.choice((2, 2, 3, 4))
85
+ length = min(length, 8 - step)
86
+ if rng.random() < 0.22 and step > 0:
87
+ step += length
88
+ continue
89
+ target = rng.choice(tones) + 7
90
+ melody_degree += max(-2, min(2, target - melody_degree)) if rng.random() < 0.7 else rng.choice((-1, 1))
91
+ melody_degree = max(5, min(14, melody_degree))
92
+ _note(buf, t0 + step * beat / 2, length * beat / 2 * 0.92, degree(melody_degree), lead,
93
+ 0.16 if lead == "square" else 0.22, decay=1.5 if season == "winter" else 0.0)
94
+ step += length
95
+ peak = max(1e-6, float(np.abs(buf).max()))
96
+ return list(buf / peak * 0.6)
97
+
98
+
99
+ def to_wav_bytes(samples: list[float]) -> bytes:
100
+ out = io.BytesIO()
101
+ with wave.open(out, "wb") as w:
102
+ w.setnchannels(1)
103
+ w.setsampwidth(2)
104
+ w.setframerate(RATE)
105
+ w.writeframes((np.clip(np.asarray(samples), -1.0, 1.0) * 32767).astype("<i2").tobytes())
106
+ return out.getvalue()
107
+
108
+
109
+ def write(out_dir: Path, season: str, seed: int = 0) -> Path:
110
+ """Writes music.wav for `season` (skipped when it is already there for the same season and seed)."""
111
+ out_dir.mkdir(parents=True, exist_ok=True)
112
+ target, stamp = out_dir / "music.wav", out_dir / ".music.params"
113
+ key = f"{season}:{seed}"
114
+ if not (target.exists() and stamp.exists() and stamp.read_text() == key):
115
+ target.write_bytes(to_wav_bytes(compose(season, seed)))
116
+ stamp.write_text(key)
117
+ return target
@@ -726,6 +726,42 @@ def paint_workbench(g: Fig, color: str, anim: str, i: int) -> None:
726
726
  _out(g)
727
727
 
728
728
 
729
+ def paint_shop(g: Fig, color: str, anim: str, i: int) -> None:
730
+ d, b, l = _ramp(color)
731
+ g.ellipse(16, 31, 15, 1.8, _c("#2c5a30", 110))
732
+ g.rect(3, 10, 4, 30, WOOD_DARK) # posts
733
+ g.rect(27, 10, 28, 30, WOOD_DARK)
734
+ g.rect(1, 19, 30, 29, WOOD) # the counter
735
+ g.rect(1, 19, 30, 20, WOOD_LIGHT)
736
+ g.rect(1, 29, 30, 29, WOOD_DARK)
737
+ for k, x in enumerate(range(0, 32, 4)): # a striped awning with a scalloped edge
738
+ g.rect(x, 4, x + 3, 10, b if k % 2 == 0 else WHITE)
739
+ g.ellipse(x + 2, 11, 2, 1.6, b if k % 2 == 0 else WHITE)
740
+ g.rect(0, 3, 31, 4, d)
741
+ g.rect(5, 15, 9, 18, _c("#e8c25a")) # wares: a sack of seeds, eggs, a chick in a basket
742
+ g.rect(6, 14, 8, 14, _c("#c8a040"))
743
+ g.ellipse(14, 17, 2, 1.6, _c("#f8f0e0"))
744
+ g.ellipse(17.5, 17, 2, 1.6, _c("#f8f0e0"))
745
+ g.rect(21, 15, 26, 18, WOOD_LIGHT)
746
+ g.ellipse(23.5, 14, 1.8, 1.6, _c("#ffd84a"))
747
+ g.rect(10, 22, 21, 25, PLANK) # a sign on the counter
748
+ g.rect(12, 23, 19, 23, WOOD_DARK)
749
+ _out(g)
750
+
751
+
752
+ def paint_sprinkler(g: Fig, color: str, anim: str, i: int) -> None:
753
+ g.ellipse(8, 14, 4, 1.2, _c("#2c5a30", 110))
754
+ g.rect(7, 7, 8, 13, STONE_DARK)
755
+ g.rect(5, 12, 10, 13, STONE)
756
+ g.ellipse(7.5, 6, 3, 2, STONE)
757
+ g.ellipse(7, 5.5, 1.4, 0.8, STONE_LIGHT)
758
+ k = i % 4
759
+ for n, (dx, dy) in enumerate(((-5, -2), (5, -2), (-3, -4), (3, -4))): # little jets of water
760
+ if (n + k) % 2 == 0:
761
+ g.set(7.5 + dx, 5 + dy, _c("#7fd0ff"))
762
+ _out(g)
763
+
764
+
729
765
  def paint_sign(g: Fig, color: str, anim: str, i: int) -> None:
730
766
  g.rect(7, 8, 8, 15, WOOD_DARK)
731
767
  g.rect(1, 2, 14, 9, WOOD)
@@ -1397,6 +1433,8 @@ SPRITES: dict[str, tuple[tuple[int, int], dict[str, tuple[int, float, bool]], ob
1397
1433
  "barrel": ((16, 16), {"idle": (1, 1, False)}, paint_barrel, "#9a5e36"),
1398
1434
  "signpost": ((16, 16), {"idle": (1, 1, False)}, paint_sign, "#9a5e36"),
1399
1435
  "workbench": ((24, 18), {"idle": (1, 1, False)}, paint_workbench, "#9a5e36"),
1436
+ "shop": ((32, 32), {"idle": (1, 1, False)}, paint_shop, "#3a8ad0"),
1437
+ "sprinkler": ((16, 16), {"idle": (4, 6, True)}, paint_sprinkler, "#a8a8b8"),
1400
1438
  "flower_bush": ((16, 16), {"idle": (1, 1, False)}, paint_flower_bush, "#ff8fb4"),
1401
1439
  "fence_h": ((16, 16), {"idle": (1, 1, False)}, paint_fence_h, "#9a5e36"),
1402
1440
  "fence_v": ((16, 16), {"idle": (1, 1, False)}, paint_fence_v, "#9a5e36"),
@@ -43,7 +43,7 @@ CAST: list[tuple[str, str, str]] = [
43
43
  ("Farmer", "farmer", "#e8503a"), ("Maple", "tree_maple", "#d8402a"), ("SnowyPine", "tree_snowy_pine", "#2f7a5a"),
44
44
  ("BareTree", "tree_bare", "#9a5e36"), ("Palm", "tree_palm", "#4caf50"), ("Cactus", "cactus", "#5aa048"),
45
45
  ("Bed", "bed", "#4a7ad0"), ("Fireplace", "fireplace", "#c8643a"), ("Table", "table", "#9a5e36"),
46
- ("Workbench", "workbench", "#9a5e36"), ("Crow", "crow", "#3a3448"), ("Fox", "fox", "#e07a2a"), ("Rabbit", "rabbit", "#d8c8b8"), ("Mouse", "mouse", "#9a9aa8"),
46
+ ("Workbench", "workbench", "#9a5e36"), ("ShopStall", "shop", "#3a8ad0"), ("Sprinkler", "sprinkler", "#a8a8b8"), ("Crow", "crow", "#3a3448"), ("Fox", "fox", "#e07a2a"), ("Rabbit", "rabbit", "#d8c8b8"), ("Mouse", "mouse", "#9a9aa8"),
47
47
  ("Chair", "chair", "#9a5e36"), ("Dresser", "dresser", "#9a5e36"), ("PottedPlant", "potted_plant", "#5cb84a"),
48
48
  ]
49
49
 
@@ -112,7 +112,13 @@ FARM_CROP_COLORS = {"strawberry": "#e83a4a", "potato": "#c8986a", "carrot": "#ff
112
112
  "pumpkin": "#ff9a2a", "corn": "#ffd84a", "cabbage": "#8ad860", "wheat": "#e8c25a",
113
113
  "onion": "#f4ece0", "eggplant": "#8a4ac8", "blueberry": "#4a6ae8", "turnip": "#c84a9a"}
114
114
  FARM_DAYS = {"short": 2, "medium": 3, "long": 4} # days in a farming season
115
+ FARM_RAIN = {"spring": 0.35, "autumn": 0.4, "winter": 0.35, "desert": 0.05} # chance of a rainy (or snowy) day
115
116
  FARM_START_MONEY = 15 # enough for the first seeds
117
+ # For sale at the shop stall (key, name, price; see ShopComponent), and the rare crop it sells
118
+ # (name, colour, grow seconds, price).
119
+ FARM_SHOP = (("chicken", "Chicken", 40), ("cow", "Cow", 90), ("seeds", "Golden melon seeds", 60),
120
+ ("sprinkler", "Sprinkler", 55))
121
+ FARM_RARE_CROP = ("GoldenMelon", "#ffd84a", 30, 45)
116
122
  # Tool upgrades at the workbench: (key, name, price); see ToolBenchComponent.
117
123
  FARM_UPGRADES = (("basket", "Big basket", 30), ("can", "Watering can", 45), ("hoe", "Hoe", 35), ("axe", "Axe", 40),
118
124
  ("rod", "Fishing rod", 30))
@@ -141,6 +147,7 @@ FARM_OBJECTS: dict[str, tuple[str, dict]] = {
141
147
  "hay": ("Hay", {"base": (28, 14)}), "barrel": ("Barrel", {"base": (24, 14)}), "well": ("Well", {"base": (44, 28)}),
142
148
  "scarecrow": ("Scarecrow", {"base": (12, 10)}), "signpost": ("Signpost", {"base": (12, 8)}),
143
149
  "workbench": ("Workbench", {"base": (40, 14)}),
150
+ "shop": ("ShopStall", {"base": (56, 18)}), "sprinkler": ("WaterSprinkler", {"base": (12, 8)}),
144
151
  "bed": ("Bed", {"base": (28, 56)}), "fireplace": ("Fireplace", {"base": (60, 20)}), "table": ("Table", {"base": (56, 20)}),
145
152
  "chair": ("Chair", {"base": (20, 12)}), "dresser": ("Dresser", {"base": (44, 16)}),
146
153
  "potted_plant": ("PottedPlant", {"base": (16, 12)}),
@@ -381,6 +388,11 @@ class _Assembler:
381
388
  art[f"icon_{c}"] = {"kind": "crop_icon", "of": c, "color": color}
382
389
  crop_entries.append(f"{c}|{color}|{grow}|{price}|res://sprites/art_crop_{c}.png|res://sprites/art_icon_{c}.png")
383
390
  art["icon_hay"] = {"kind": "item_icon", "of": "hay"}
391
+ rare = FARM_RARE_CROP # sold at the shop stall
392
+ art[f"crop_{rare[0]}"] = {"kind": "crop", "of": rare[0], "color": rare[1]}
393
+ art[f"icon_{rare[0]}"] = {"kind": "crop_icon", "of": rare[0], "color": rare[1]}
394
+ rare_entry = (f"{rare[0]}|{rare[1]}|{rare[2]}|{rare[3]}|res://sprites/art_crop_{rare[0]}.png"
395
+ f"|res://sprites/art_icon_{rare[0]}.png")
384
396
  goods = []
385
397
  for item, price in FARM_GOODS:
386
398
  art[f"icon_{item.lower()}"] = {"kind": "item_icon", "of": item.lower()}
@@ -389,8 +401,11 @@ class _Assembler:
389
401
  "acceleration": 2200}, "PlayerInput": {},
390
402
  "Farmer": {"crops": ";".join(crop_entries), "goods": ";".join(goods), "reach": 46,
391
403
  "basket_size": 16, "buy_seeds": True},
392
- "FarmDays": {"days": days, "day_seconds": day_seconds},
393
- "FarmSave": {"slot": f"{farm_slug(b.title or 'farm')}-{self.seed}"}}
404
+ "FarmDays": {"days": days, "day_seconds": day_seconds, "weather_seed": self.seed,
405
+ "rain_chance": FARM_RAIN[season], "snow": season == "winter"},
406
+ "FarmSave": {"slot": f"{farm_slug(b.title or 'farm')}-{self.seed}"},
407
+ "GameMenu": {"title": b.title or "My Farm",
408
+ "subtitle": f"Earn ${target} in {days} days. Space works the farm, P pauses."}}
394
409
  if "dash" in b.player.abilities:
395
410
  comps["TopDownMovement"]["dash_speed"] = 600.0
396
411
  self.entities["Player"] = {"shape": {"type": "circle", "size": [18, 18], "color": pal["player"]},
@@ -433,6 +448,12 @@ class _Assembler:
433
448
  "components": {}}
434
449
  if kind == "bed":
435
450
  ent["components"]["SleepSpot"] = {}
451
+ if kind == "sprinkler": # waits hidden in the field until bought
452
+ ent["body"] = "area"
453
+ ent["components"]["Sprinkler"] = {"radius": 52}
454
+ if kind == "shop":
455
+ ent["components"]["Shop"] = {"offers": ";".join(f"{k}|{n}|{p}" for k, n, p in FARM_SHOP),
456
+ "rare_crop": rare_entry}
436
457
  if kind == "workbench":
437
458
  ent["components"]["ToolBench"] = {"offers": ";".join(f"{k}|{n}|{p}" for k, n, p in FARM_UPGRADES)}
438
459
  if "gather" in spec:
@@ -520,7 +541,7 @@ class _Assembler:
520
541
  "settings": {"tile_size": TILE, "lives": 1, "seed": self.seed, "zoom": 1.6,
521
542
  "background": "#3aa0e8" if fm.island else "#4f8a3a",
522
543
  "tile_style": "flat", "tiles": {"solid": {"color": "#00000000", "top": "#00000000"}},
523
- "terrain": fm.terrain, "season": season, "y_sort": True, "art": art},
544
+ "terrain": fm.terrain, "season": season, "music": season, "y_sort": True, "art": art},
524
545
  "entities": self.entities, "level": {"map": rows, "legend": legend}, "rules": self.rules}
525
546
 
526
547
  def _farm_target(self) -> int: