spawnpoint-studio 0.3.2__tar.gz → 0.3.3__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 (165) hide show
  1. {spawnpoint_studio-0.3.2/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.3}/PKG-INFO +12 -9
  2. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/README.md +11 -8
  3. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cli.py +9 -0
  4. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/cartoon.py +3 -0
  5. spawnpoint_studio-0.3.3/compiler/spawnpoint/sprites/cartoon_monsters.py +152 -0
  6. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/gallery.py +41 -0
  7. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/generate.py +12 -2
  8. spawnpoint_studio-0.3.3/compiler/spawnpoint/sprites/monsters.py +220 -0
  9. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/assemble.py +16 -8
  10. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/prompts.py +2 -1
  11. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3/compiler/spawnpoint_studio.egg-info}/PKG-INFO +12 -9
  12. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +2 -0
  13. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_sprites.py +21 -3
  14. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_studio.py +2 -1
  15. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/castle_defense.json +2 -1
  16. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/coin_hopper.json +3 -2
  17. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/dino_eggs.json +2 -1
  18. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/knight_quest.json +4 -2
  19. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/neon_arena.json +3 -2
  20. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/night_survivor.json +3 -2
  21. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/ninja_rescue.json +3 -2
  22. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/pixel_arena.json +3 -2
  23. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/pixel_hopper.json +3 -2
  24. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/star_run.json +2 -1
  25. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/examples/sunny_farm.json +3 -2
  26. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/pyproject.toml +1 -1
  27. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/LICENSE +0 -0
  28. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/MANIFEST.in +0 -0
  29. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/__init__.py +0 -0
  30. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/analyze.py +0 -0
  31. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/build.py +0 -0
  32. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/__init__.py +0 -0
  33. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/server.py +0 -0
  34. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/app.css +0 -0
  35. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/app.js +0 -0
  36. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/cockpit/static/index.html +0 -0
  37. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/demo.py +0 -0
  38. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/demo_touch.js +0 -0
  39. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/diagnostics.py +0 -0
  40. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/expr.py +0 -0
  41. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/gif.py +0 -0
  42. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/godot.py +0 -0
  43. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/graph.py +0 -0
  44. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/level.py +0 -0
  45. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/llm_server.py +0 -0
  46. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/manifest.py +0 -0
  47. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/__init__.py +0 -0
  48. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/report.py +0 -0
  49. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/runner.py +0 -0
  50. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/playtest/tuner.py +0 -0
  51. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/project.py +0 -0
  52. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/py.typed +0 -0
  53. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/rules.py +0 -0
  54. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sfx.py +0 -0
  55. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/__init__.py +0 -0
  56. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/ai.py +0 -0
  57. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/chibi.py +0 -0
  58. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/contour.py +0 -0
  59. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/creatures.py +0 -0
  60. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/importer.py +0 -0
  61. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/items.py +0 -0
  62. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/motion.py +0 -0
  63. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/palettes.py +0 -0
  64. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/resolve.py +0 -0
  65. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/scenery.py +0 -0
  66. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/spriteset.py +0 -0
  67. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/sprites/tiles.py +0 -0
  68. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/__init__.py +0 -0
  69. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/brief.py +0 -0
  70. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/creative.py +0 -0
  71. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/dataset.py +0 -0
  72. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/edits.py +0 -0
  73. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/genres.py +0 -0
  74. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/levelgen.py +0 -0
  75. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/llm.py +0 -0
  76. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/orchestrator.py +0 -0
  77. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/web.py +0 -0
  78. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio/webcheck.py +0 -0
  79. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint/studio_cli.py +0 -0
  80. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
  81. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
  82. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
  83. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
  84. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/conftest.py +0 -0
  85. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_ai_sprites.py +0 -0
  86. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_analyze.py +0 -0
  87. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_cockpit.py +0 -0
  88. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_demo.py +0 -0
  89. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_e2e.py +0 -0
  90. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_expr.py +0 -0
  91. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_genres.py +0 -0
  92. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_gif.py +0 -0
  93. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_godot_library.py +0 -0
  94. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_graph.py +0 -0
  95. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_level.py +0 -0
  96. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_live_llm.py +0 -0
  97. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_manifest.py +0 -0
  98. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_playtest.py +0 -0
  99. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_rules.py +0 -0
  100. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_sfx.py +0 -0
  101. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/compiler/tests/test_web.py +0 -0
  102. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/aim_component.gd +0 -0
  103. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/animator_component.gd +0 -0
  104. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/base_component.gd +0 -0
  105. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/bubble_board_component.gd +0 -0
  106. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
  107. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
  108. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/crop_pest_component.gd +0 -0
  109. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/experience_component.gd +0 -0
  110. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/farm_plot_component.gd +0 -0
  111. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/farmer_component.gd +0 -0
  112. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/flight_path_component.gd +0 -0
  113. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/gate_component.gd +0 -0
  114. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/ground_patch_component.gd +0 -0
  115. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/health_bar_component.gd +0 -0
  116. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/health_component.gd +0 -0
  117. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
  118. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
  119. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/juice_component.gd +0 -0
  120. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
  121. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/market_component.gd +0 -0
  122. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/path_follower_component.gd +0 -0
  123. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
  124. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
  125. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
  126. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
  127. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/shmup_ship_component.gd +0 -0
  128. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
  129. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
  130. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
  131. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_brain_component.gd +0 -0
  132. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_builder_component.gd +0 -0
  133. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/tower_slot_component.gd +0 -0
  134. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
  135. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/wave_spawner_component.gd +0 -0
  136. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
  137. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
  138. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/manifest.json +0 -0
  139. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
  140. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
  141. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
  142. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/fx.gd +0 -0
  143. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
  144. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/hud.gd +0 -0
  145. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/level.gd +0 -0
  146. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
  147. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/sp.gd +0 -0
  148. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/runtime/world.gd +0 -0
  149. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
  150. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
  151. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/build.gd +0 -0
  152. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/build.tscn +0 -0
  153. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/capture.gd +0 -0
  154. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/capture.tscn +0 -0
  155. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
  156. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
  157. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest.gd +0 -0
  158. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
  159. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
  160. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/record.gd +0 -0
  161. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/record.tscn +0 -0
  162. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/smoke.gd +0 -0
  163. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
  164. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/setup.cfg +0 -0
  165. {spawnpoint_studio-0.3.2 → spawnpoint_studio-0.3.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawnpoint-studio
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
34
34
 
35
35
  <p align="center">
36
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a cartoon-style sword platformer">
36
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a sword platformer">
37
37
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/castle-defense.gif" width="48%" alt="Castle Defense: a tower defense game">
38
38
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/ninja-rescue.gif" width="48%" alt="Ninja Rescue: a platformer where a ninja frees his mother">
39
39
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/dino-eggs.gif" width="48%" alt="Dino Eggs: a bubble shooter with dinosaur eggs">
@@ -42,10 +42,10 @@ Dynamic: license-file
42
42
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/star-run.gif" width="48%" alt="Star Run: a side-scrolling shooter with power-ups and a boss">
43
43
  </p>
44
44
  <p align="center"><sub>Seven of the demo games, each one a small JSON graph in <code>examples/</code> built from the
45
- same component library: generated cartoon or pixel art, collision traced from the sprites, juice on by default.
45
+ same component library: generated pixel art, collision traced from the sprites, juice on by default.
46
46
  Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
47
47
 
48
- <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a cartoon
48
+ <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
49
49
  knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
50
50
  game, an arena shooter and a side-scrolling space shooter. No install needed, and
51
51
  they work on phones too (touch controls).</p>
@@ -169,10 +169,12 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
169
169
 
170
170
  ## Sprites: generated or your own
171
171
 
172
- Layer 2 replaces placeholder shapes with **procedural sprites** — no assets, no GPU — in one of two art styles:
173
- **pixel art** (the default) or **cartoon** (`"art_style": "cartoon"` in the graph settings, or "cartoon style" in
174
- the prompt): smooth shaded sprites with a sticker outline, rounded tiles, and a parallax sky with hills, clouds and
175
- trees. The
172
+ Layer 2 replaces placeholder shapes with **procedural pixel art** — no assets, no GPU: chibi people with big heads, monsters with faces (a biting plant, a mushroom, a snail, a flame spirit, a reaper, an alien, a dragon, a ghost...), items, props, themed tiles and a parallax sky. Spawn Point makes pixel art games only. (A graph can still ask for the smooth `"art_style": "cartoon"` look by hand, but games made from a prompt are always pixel art.)
173
+
174
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-heroes.gif" width="48%" alt="Animated heroes: knight, wizard, ranger, viking, king, princess, ninja..."> <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-monsters.gif" width="48%" alt="Animated monsters: slime, frog, biting plant, mushroom, snail, flame..."></p>
175
+ <p align="center"><sub>Every character and monster is drawn by plain Python from its name (<code>spawnpoint sprites parade --set heroes|monsters|items</code>).</sub></p>
176
+
177
+ The
176
178
  kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
177
179
  and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
178
180
  Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
@@ -204,7 +206,8 @@ spawnpoint sprites import hero.png --grid 32x32 --anim idle=0-3 --anim run=4-9
204
206
  spawnpoint sprites import sheet.png --rows idle,run,jump # auto-sliced by transparent gutters
205
207
  spawnpoint sprites import kenney_frames/ # idle_0.png, walk_1.png, ...
206
208
  spawnpoint sprites preview assets/sprites/Knight.sprite.json # contact sheet + collision outline
207
- spawnpoint sprites gallery --style cartoon -o gallery.png # every kind on one sheet, for review
209
+ spawnpoint sprites gallery --style fine -o gallery.png # every kind on one sheet, for review
210
+ spawnpoint sprites parade --set monsters -o monsters.gif # an animated sheet of characters, monsters or items
208
211
  ```
209
212
 
210
213
  Names pick the art: `Knight` gets a helmet and shield, `SkeletonArcher` a skull and a bow (and shoots
@@ -9,7 +9,7 @@
9
9
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
10
10
 
11
11
  <p align="center">
12
- <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a cartoon-style sword platformer">
12
+ <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/knight-quest.gif" width="48%" alt="Knight's Quest: a sword platformer">
13
13
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/castle-defense.gif" width="48%" alt="Castle Defense: a tower defense game">
14
14
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/ninja-rescue.gif" width="48%" alt="Ninja Rescue: a platformer where a ninja frees his mother">
15
15
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/dino-eggs.gif" width="48%" alt="Dino Eggs: a bubble shooter with dinosaur eggs">
@@ -18,10 +18,10 @@
18
18
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/star-run.gif" width="48%" alt="Star Run: a side-scrolling shooter with power-ups and a boss">
19
19
  </p>
20
20
  <p align="center"><sub>Seven of the demo games, each one a small JSON graph in <code>examples/</code> built from the
21
- same component library: generated cartoon or pixel art, collision traced from the sprites, juice on by default.
21
+ same component library: generated pixel art, collision traced from the sprites, juice on by default.
22
22
  Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
23
23
 
24
- <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a cartoon
24
+ <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a
25
25
  knight with a sword, tower defense, a ninja rescue, a dinosaur-egg bubble shooter, a survivor with level-ups, a farming
26
26
  game, an arena shooter and a side-scrolling space shooter. No install needed, and
27
27
  they work on phones too (touch controls).</p>
@@ -145,10 +145,12 @@ use `--no-web` to skip and play natively with `spawnpoint run`.
145
145
 
146
146
  ## Sprites: generated or your own
147
147
 
148
- Layer 2 replaces placeholder shapes with **procedural sprites** — no assets, no GPU — in one of two art styles:
149
- **pixel art** (the default) or **cartoon** (`"art_style": "cartoon"` in the graph settings, or "cartoon style" in
150
- the prompt): smooth shaded sprites with a sticker outline, rounded tiles, and a parallax sky with hills, clouds and
151
- trees. The
148
+ Layer 2 replaces placeholder shapes with **procedural pixel art** — no assets, no GPU: chibi people with big heads, monsters with faces (a biting plant, a mushroom, a snail, a flame spirit, a reaper, an alien, a dragon, a ghost...), items, props, themed tiles and a parallax sky. Spawn Point makes pixel art games only. (A graph can still ask for the smooth `"art_style": "cartoon"` look by hand, but games made from a prompt are always pixel art.)
149
+
150
+ <p align="center"><img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-heroes.gif" width="48%" alt="Animated heroes: knight, wizard, ranger, viking, king, princess, ninja..."> <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/parade-monsters.gif" width="48%" alt="Animated monsters: slime, frog, biting plant, mushroom, snail, flame..."></p>
151
+ <p align="center"><sub>Every character and monster is drawn by plain Python from its name (<code>spawnpoint sprites parade --set heroes|monsters|items</code>).</sub></p>
152
+
153
+ The
152
154
  kind follows the entity (`Goblin` → humanoid, `FireBat` → flyer, `LaserBot` → robot, `Crystal` → gem…)
153
155
  and details follow the name (`Ninja` → mask, `Fox` → ears and tail, `Spider` → legs, a sword-wielder holds a sword).
154
156
  Every sprite has idle / run / jump / fall / hurt animations driven automatically by an `Animator` component, plus
@@ -180,7 +182,8 @@ spawnpoint sprites import hero.png --grid 32x32 --anim idle=0-3 --anim run=4-9
180
182
  spawnpoint sprites import sheet.png --rows idle,run,jump # auto-sliced by transparent gutters
181
183
  spawnpoint sprites import kenney_frames/ # idle_0.png, walk_1.png, ...
182
184
  spawnpoint sprites preview assets/sprites/Knight.sprite.json # contact sheet + collision outline
183
- spawnpoint sprites gallery --style cartoon -o gallery.png # every kind on one sheet, for review
185
+ spawnpoint sprites gallery --style fine -o gallery.png # every kind on one sheet, for review
186
+ spawnpoint sprites parade --set monsters -o monsters.gif # an animated sheet of characters, monsters or items
184
187
  ```
185
188
 
186
189
  Names pick the art: `Knight` gets a helmet and shield, `SkeletonArcher` a skull and a bow (and shoots
@@ -124,6 +124,9 @@ def main(argv: list[str] | None = None) -> int:
124
124
  sga = sps.add_parser("gallery", help="render one contact sheet of every procedural kind, for review")
125
125
  sga.add_argument("--style", choices=["pixel", "fine", "cartoon"], default="fine")
126
126
  sga.add_argument("-o", "--out", type=Path, default=Path("sprite-gallery.png"))
127
+ spa = sps.add_parser("parade", help="render an animated sheet (GIF) of characters, monsters or items")
128
+ spa.add_argument("--set", dest="which", choices=["heroes", "monsters", "items"], default="heroes")
129
+ spa.add_argument("-o", "--out", type=Path, default=Path("sprite-parade.gif"))
127
130
 
128
131
  ds = sub.add_parser("dataset", help="recorded fine-tuning data")
129
132
  ds.add_argument("action", choices=["stats", "path"])
@@ -275,6 +278,12 @@ def _sprites(args) -> int:
275
278
  for k, spec in gen.KINDS.items():
276
279
  print(f"{k:<8} {spec.size[0]}x{spec.size[1]} px animations: {', '.join(spec.anims)}")
277
280
  return 0
281
+ if args.sprites_cmd == "parade":
282
+ from .sprites.gallery import parade
283
+ frames = parade(args.which)
284
+ frames[0].save(args.out, save_all=True, append_images=frames[1:], duration=100, loop=0, optimize=True)
285
+ print(f"Wrote {args.out}")
286
+ return 0
278
287
  if args.sprites_cmd == "gallery":
279
288
  from .sprites.gallery import gallery
280
289
  gallery(args.style).save(args.out)
@@ -972,6 +972,9 @@ PAINTERS = {"hero": draw_hero, "slime": draw_blob, "hopper": draw_blob, "bug": d
972
972
  "beast": draw_beast, "spider": draw_spider, "dragon": draw_dragon, "ghost": draw_ghost, "snake": draw_snake,
973
973
  "key": draw_key, "potion": draw_potion, "scroll": draw_scroll, "bomb": draw_bomb, "chest": draw_chest,
974
974
  "arrow": draw_arrow, "tree": draw_tree, "bush": draw_bush, "rock": draw_rock}
975
+ from . import cartoon_monsters # noqa: E402 (needs Painter and the helpers above)
976
+
977
+ PAINTERS.update(cartoon_monsters.PAINTERS)
975
978
  OUTLINE = {"bullet": 0.8, "slash": 0.8, "coin": 1.0, "gem": 1.0, "star": 1.1, "heart": 1.1, "spikes": 1.0, "orb": 1.0,
976
979
  "key": 1.0, "potion": 1.0, "scroll": 1.0, "bomb": 1.0, "arrow": 0.8}
977
980
 
@@ -0,0 +1,152 @@
1
+ """Cartoon twins of the monsters in monsters.py: the same characters in the smooth vector style."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+
7
+ from .cartoon import EYE, WHITE, Painter, Pose, _hexrgb, _shade
8
+ from . import monsters
9
+
10
+ LEAF = _hexrgb(monsters.LEAF)
11
+ MOUTH = _hexrgb(monsters.MOUTH)
12
+ TONGUE = _hexrgb(monsters.TONGUE)
13
+ STEM = _hexrgb(monsters.STEM)
14
+ CLOAK = _hexrgb(monsters.CLOAK)
15
+ BONE = _hexrgb(monsters.BONE)
16
+ FLAME_MID = _hexrgb(monsters.FLAME_MID)
17
+ FLAME_CORE = _hexrgb(monsters.FLAME_CORE)
18
+ SNAIL_BODY = _hexrgb(monsters.SNAIL_BODY)
19
+ WOOD = _hexrgb(monsters.WOOD)
20
+ STEEL = _hexrgb(monsters.STEEL)
21
+ GLOW = (255, 90, 90)
22
+
23
+
24
+ def _top_half(cx: float, cy: float, rx: float, ry: float, n: int = 24) -> list[tuple[float, float]]:
25
+ return [(cx + rx * math.cos(math.pi + math.pi * k / n), cy + ry * math.sin(math.pi + math.pi * k / n)) for k in range(n + 1)]
26
+
27
+
28
+ def draw_plant(P: Painter, p: Pose) -> None:
29
+ c = P.p
30
+ k = p.i % 4
31
+ sw = (0, 2, 0, -2)[k]
32
+ hurt = p.anim == "hurt"
33
+ gape = 2 if hurt else {"idle": (6, 10, 14, 8)[k], "run": (10, 4, 10, 4)[k]}.get(p.anim, 8)
34
+ P.line([(28, 62), (28 + sw, 36)], 9, LEAF, top=False)
35
+ P.ellipse(14 - sw, 52, 12, 5.5, LEAF)
36
+ P.ellipse(42 + sw, 52, 12, 5.5, LEAF)
37
+ P.ellipse(28 + sw, 22, 24, 20, c.base, shine=True)
38
+ for x, y, r in ((14, 15, 3.4), (30, 8, 3.8), (43, 15, 3.0)):
39
+ P.ellipse(x + sw, y, r, r, WHITE, shade=False, top=True)
40
+ my = 25
41
+ P.ellipse(28 + sw, my, 17, gape / 2 + 2.4, MOUTH, shade=False, top=True)
42
+ if gape > 6:
43
+ P.ellipse(28 + sw, my + gape / 2, 9, max(2, gape / 3), TONGUE, shade=False, top=True)
44
+ for x in range(14, 40, 6):
45
+ P.poly([(x + sw, my - gape / 2 - 2), (x + 3 + sw, my - gape / 2 + 3.5), (x + 6 + sw, my - gape / 2 - 2)], WHITE, shade=False, top=True)
46
+ P.poly([(x + 2 + sw, my + gape / 2 + 3), (x + 5 + sw, my + gape / 2 - 3.5), (x + 8 + sw, my + gape / 2 + 3)], WHITE, shade=False, top=True)
47
+
48
+
49
+ def draw_mushroom(P: Painter, p: Pose) -> None:
50
+ c = P.p
51
+ k = p.i % 4
52
+ bob = {"idle": (0, 2)[p.i % 2], "run": (0, 2, 0, 2)[k]}.get(p.anim, 0)
53
+ hurt = p.anim == "hurt"
54
+ foot = (0, 2, 0, -2)[k] if p.anim == "run" else 0
55
+ P.ellipse(18 + foot, 51, 6.5, 4, _shade(c.base, 0.5))
56
+ P.ellipse(38 - foot, 51, 6.5, 4, _shade(c.base, 0.5))
57
+ P.rrect(20, 30 + bob, 37, 51, 6, STEM)
58
+ P.poly(_top_half(28, 32 + bob, 26, 20) + [(2, 33 + bob), (54, 33 + bob)], c.base, top=True)
59
+ P.ellipse(20, 17 + bob, 11, 6, c.light, shade=False)
60
+ for x, y, r in ((14, 19, 4.4), (30, 11, 4.8), (42, 20, 4), (24, 26, 2.8)):
61
+ P.ellipse(x, y + bob, r, r, WHITE, shade=False, top=True)
62
+ if hurt:
63
+ P.eyes([(22, 38 + bob), (33, 38 + bob)], r=3.2, hurt=True)
64
+ else:
65
+ P.eyes([(22, 38 + bob), (33, 38 + bob)], r=3.2)
66
+ P.line([(26, 45 + bob), (30, 45 + bob)], 1.6, EYE)
67
+
68
+
69
+ def draw_reaper(P: Painter, p: Pose) -> None:
70
+ c = P.p
71
+ k = p.i % 4
72
+ bob = (0, 2, 2, 0)[k] if p.anim in ("idle", "run") else 0
73
+ P.line([(48, 12 + bob), (48, 60 + bob)], 4, WOOD, top=False)
74
+ P.poly([(48, 6 + bob), (28, 4 + bob), (16, 14 + bob), (28, 10 + bob), (48, 16 + bob)], STEEL, top=True)
75
+ P.poly([(14, 34 + bob), (40, 34 + bob), (48, 62 + bob), (6, 62 + bob)], CLOAK)
76
+ P.ellipse(28, 21 + bob, 19, 18, CLOAK, shine=True)
77
+ P.ellipse(26, 22 + bob, 13, 13, _shade(CLOAK, 0.35), shade=False, top=True)
78
+ P.ellipse(26, 24 + bob, 9.4, 9.8, BONE, shade=False, top=True)
79
+ for ex in (21, 29):
80
+ P.ellipse(ex, 23 + bob, 2.8, 3.4, EYE, shade=False, top=True)
81
+ if p.anim != "hurt":
82
+ P.ellipse(ex, 23 + bob, 1.1, 1.1, GLOW, shade=False, top=True)
83
+ P.line([(22, 31 + bob), (30, 31 + bob)], 1.6, EYE)
84
+ P.ellipse(42, 40 + bob, 4.6, 4.6, BONE, shade=False, top=True)
85
+
86
+
87
+ def draw_flame(P: Painter, p: Pose) -> None:
88
+ c = P.p
89
+ k = p.i % 4
90
+ f = (0, 2, 0, -2)[k]
91
+ bob = (0, 2)[p.i % 2] if p.anim == "idle" else 0
92
+ P.poly([(24 + f, 0 + bob), (34, 16 + bob), (44, 28), (42, 42), (34, 52), (14, 52), (6, 42), (4, 28), (14, 16 + bob)], c.base, top=True)
93
+ P.poly([(6 - f, 18 + bob), (12, 28), (6, 36)], c.base)
94
+ P.poly([(42 + f, 16 + bob), (38, 28), (44, 36)], c.base)
95
+ P.poly([(24 - f, 16 + bob), (34, 30), (36, 42), (28, 50), (18, 50), (12, 42), (14, 30)], FLAME_MID, shade=False, top=True)
96
+ P.poly([(24, 28), (30, 38), (28, 48), (20, 48), (18, 38)], FLAME_CORE, shade=False, top=True)
97
+ for ex in (15, 29):
98
+ P.ellipse(ex + 2, 33 + bob, 3.4, 3.4, WHITE, shade=False, top=True)
99
+ P.ellipse(ex + 3, 34 + bob, 1.7, 1.9, EYE, shade=False, top=True)
100
+ if p.anim != "hurt":
101
+ P.line([(12, 26 + bob), (20, 30 + bob)], 1.8, EYE)
102
+ P.line([(36, 26 + bob), (28, 30 + bob)], 1.8, EYE)
103
+ P.line([(18, 43), (24, 44), (30, 43)], 2.0, EYE)
104
+
105
+
106
+ def draw_snail(P: Painter, p: Pose) -> None:
107
+ c = P.p
108
+ k = p.i % 4
109
+ st = (0, 2, 4, 2)[k] if p.anim == "run" else 0
110
+ bob = (0, 2)[p.i % 2] if p.anim == "idle" else 0
111
+ P.ellipse(30 + st / 2, 40, 26 + st / 2, 8.4, SNAIL_BODY)
112
+ P.ellipse(54 + st, 34 + bob, 9, 9, SNAIL_BODY, shine=True)
113
+ for sx in (50, 58):
114
+ x = sx + st
115
+ P.line([(x, 28 + bob), (x - 2, 14 + bob)], 4, SNAIL_BODY, top=False)
116
+ P.ellipse(x - 2, 12 + bob, 4.6, 4.6, WHITE, shade=False, top=True)
117
+ P.ellipse(x - 1, 12 + bob, 2.2, 2.6, EYE, shade=False, top=True)
118
+ P.line([(56 + st, 40 + bob), (60 + st, 40 + bob)], 1.6, EYE)
119
+ P.ellipse(24, 24 + bob, 20, 19, c.base, shine=True)
120
+ for r, col in ((16, _shade(c.base, 0.6)), (13, c.base), (9.6, _shade(c.base, 0.6)), (6.4, c.base), (3, _shade(c.base, 0.6))):
121
+ P.ellipse(24, 24 + bob, r, r, col, shade=False, top=True)
122
+
123
+
124
+ def draw_alien(P: Painter, p: Pose) -> None:
125
+ c = P.p
126
+ k = p.i % 4
127
+ bob = {"idle": (0, 2)[p.i % 2], "run": (0, 2, 0, 2)[k]}.get(p.anim, 0)
128
+ sw = ((-4, 4), (0, 0), (4, -4), (0, 0))[k] if p.anim == "run" else (0, 0)
129
+ hurt = p.anim == "hurt"
130
+ P.line([(24, 8 + bob), (18, 0 + bob)], 2.4, c.shadow)
131
+ P.line([(32, 8 + bob), (38, 0 + bob)], 2.4, c.shadow)
132
+ P.ellipse(18, 1 + bob, 3, 3, c.light, shade=False, top=True)
133
+ P.ellipse(38, 1 + bob, 3, 3, c.light, shade=False, top=True)
134
+ P.rrect(18 + sw[0], 52, 24 + sw[0], 62, 2.4, c.shadow)
135
+ P.rrect(30 + sw[1], 52, 36 + sw[1], 62, 2.4, c.base)
136
+ P.ellipse(27, 48 + bob, 12, 10, c.base, shine=True)
137
+ P.line([(14, 44 + bob), (6, 48 + bob)], 4.4, c.base, top=False)
138
+ P.line([(40, 44 + bob), (48, 48 + bob)], 4.4, c.shadow, top=False)
139
+ P.ellipse(28, 24 + bob, 23, 20, c.base, shine=True)
140
+ for ex in (17, 39):
141
+ if hurt:
142
+ P.line([(ex - 4, 20 + bob), (ex + 4, 28 + bob)], 2, EYE)
143
+ P.line([(ex + 4, 20 + bob), (ex - 4, 28 + bob)], 2, EYE)
144
+ else:
145
+ sgn = -1 if ex < 28 else 1
146
+ P.poly([(ex - 6.4, 20 + bob), (ex + 6.4, 23 + bob), (ex + 3 * sgn, 34 + bob), (ex - 3 * sgn, 34 + bob)], EYE, shade=False, top=True)
147
+ P.ellipse(ex - 2, 25 + bob, 1.6, 1.6, WHITE, shade=False, top=True)
148
+ P.line([(24, 37 + bob), (32, 37 + bob)], 1.8, _shade(c.base, 0.4))
149
+
150
+
151
+ PAINTERS = {"plant": draw_plant, "mushroom": draw_mushroom, "reaper": draw_reaper, "flame": draw_flame,
152
+ "snail": draw_snail, "alien": draw_alien}
@@ -22,6 +22,8 @@ CAST: list[tuple[str, str, str]] = [
22
22
  ("Robot", "robot", "#83769c"), ("Turret", "turret", "#83769c"), ("Beetle", "bug", "#ab5236"),
23
23
  ("Wolf", "beast", "#8a8a9a"), ("Spider", "spider", "#5f574f"), ("Dragon", "dragon", "#ff004d"),
24
24
  ("Ghost", "ghost", "#c2c3c7"), ("Snake", "snake", "#3a8a40"),
25
+ ("BitingPlant", "plant", "#ff004d"), ("Mushroom", "mushroom", "#e8403a"), ("Reaper", "reaper", "#7e2553"),
26
+ ("Flame", "flame", "#ff6a1a"), ("Snail", "snail", "#b06ad8"), ("Alien", "alien", "#6fd24a"),
25
27
  ("Coin", "coin", "#ffcc40"), ("Star", "star", "#ffec27"), ("Gem", "gem", "#29adff"),
26
28
  ("Heart", "heart", "#ff004d"), ("Orb", "orb", "#83769c"), ("GoldKey", "key", "#ffcc40"),
27
29
  ("HealthPotion", "potion", "#e04858"), ("MagicScroll", "scroll", "#c03040"), ("Bomb", "bomb", "#333344"),
@@ -65,3 +67,42 @@ def gallery(style: str = "fine", columns: int = 8, cast: list[tuple[str, str, st
65
67
  else y + (CELL - size[1]) // 2))
66
68
  d.text((x + 2, y + CELL + 1), name[:15], fill=TEXT)
67
69
  return out
70
+
71
+
72
+ PARADES: dict[str, list[str]] = {
73
+ "heroes": ["Knight", "Wizard", "Ranger", "Viking", "King", "Princess", "Ninja", "Hero", "Skeleton", "SkeletonArcher",
74
+ "Zombie", "Orc", "Goblin", "Alien", "Reaper"],
75
+ "monsters": ["Slime", "Frog", "BitingPlant", "Wolf", "Mushroom", "Snail", "Flame", "Ghost", "Snake", "Spider", "Bat",
76
+ "Dragon", "Robot", "Beetle", "Turret"],
77
+ "items": ["Coin", "Star", "Gem", "Heart", "GoldKey", "HealthPotion", "MagicScroll", "Bomb", "TreasureChest", "Arrow",
78
+ "Orb", "BerryBush", "Tree", "PineTree", "MossyRock"],
79
+ }
80
+
81
+
82
+ def parade(which: str = "heroes", columns: int = 5, cell: int = 128, fps: int = 10, seconds: float = 3.0,
83
+ style: str = "fine") -> list[Image.Image]:
84
+ """An animated sheet: every cast member idles for half the time and runs for the other half (where it can),
85
+ each at the largest whole-number scale that fits its cell. Returns the GIF frames."""
86
+ by_name = {c[0]: c for c in CAST}
87
+ cast = [by_name[n] for n in PARADES[which]]
88
+ sprites = [render(name, kind, color, style) for name, kind, color in cast]
89
+ rows = -(-len(cast) // columns)
90
+ steps = round(fps * seconds)
91
+ out = []
92
+ for step in range(steps):
93
+ t = step / fps
94
+ img = Image.new("RGBA", (PAD + columns * (cell + PAD), PAD + rows * (cell + PAD)), BG)
95
+ d = ImageDraw.Draw(img)
96
+ for n, ((name, kind, _color), s) in enumerate(zip(cast, sprites)):
97
+ anim = "run" if "run" in s.animations and t >= seconds / 2 else "idle"
98
+ a = s.animations.get(anim) or next(iter(s.animations.values()))
99
+ w, h = s.frame_size
100
+ zoom = max(1, cell // max(w, h))
101
+ idx = int(t * a.fps) % len(a.frames)
102
+ frame = Image.fromarray(s.frame(*a.frames[idx])).resize((w * zoom, h * zoom), Image.NEAREST)
103
+ x, y = PAD + (n % columns) * (cell + PAD), PAD + (n // columns) * (cell + PAD)
104
+ d.rectangle([x, y, x + cell - 1, y + cell - 1], fill=TILE)
105
+ bottom = KINDS[kind].anchor == "bottom"
106
+ img.alpha_composite(frame, (x + (cell - w * zoom) // 2, y + cell - h * zoom - 6 if bottom else y + (cell - h * zoom) // 2))
107
+ out.append(img.convert("RGB"))
108
+ return out
@@ -16,7 +16,7 @@ from typing import Callable
16
16
 
17
17
  import numpy as np
18
18
 
19
- from . import chibi, creatures, items, motion, palettes
19
+ from . import chibi, creatures, items, monsters, motion, palettes
20
20
  from .spriteset import SpriteSet, build_sheet, finalize
21
21
 
22
22
  RGBA = tuple[int, int, int, int]
@@ -765,6 +765,16 @@ KINDS: dict[str, Kind] = {
765
765
  "ghost": Kind((12, 14), {"idle": (4, 6, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None),
766
766
  "snake": Kind((16, 8), {"idle": (2, 3, True), "run": (4, 8, True), "jump": (1, 1, False), "fall": (1, 1, False),
767
767
  "hurt": (1, 1, False)}, None, "bottom"),
768
+ # more chibi monsters, painted natively at the fine size by monsters.py
769
+ "plant": Kind((14, 16), {"idle": (4, 6, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None, "bottom"),
770
+ "mushroom": Kind((14, 14), {"idle": (2, 3, True), "run": (4, 8, True), "jump": (1, 1, False), "fall": (1, 1, False),
771
+ "hurt": (1, 1, False)}, None, "bottom"),
772
+ "reaper": Kind((14, 16), {"idle": (4, 5, True), "run": (4, 8, True), "hurt": (1, 1, False)}, None),
773
+ "flame": Kind((12, 14), {"idle": (4, 8, True), "run": (4, 10, True), "hurt": (1, 1, False)}, None, "bottom"),
774
+ "snail": Kind((16, 12), {"idle": (2, 3, True), "run": (4, 5, True), "jump": (1, 1, False), "fall": (1, 1, False),
775
+ "hurt": (1, 1, False)}, None, "bottom"),
776
+ "alien": Kind((12, 16), {"idle": (2, 3, True), "run": (4, 10, True), "jump": (1, 1, False), "fall": (1, 1, False),
777
+ "hurt": (1, 1, False)}, None, "bottom"),
768
778
  # chibi items and decorative props, painted natively by items.py
769
779
  "key": Kind((8, 8), {"idle": (4, 6, True)}, None),
770
780
  "potion": Kind((8, 10), {"idle": (4, 6, True)}, None),
@@ -814,7 +824,7 @@ def refine(f: np.ndarray, outline: RGBA) -> np.ndarray:
814
824
 
815
825
  # Animations a painter draws itself (on top of the kind's own), and the kinds that get a derived attack pulse.
816
826
  DRAWN_EXTRA: dict[str, dict[str, tuple[int, float, bool]]] = {"hero": {"attack": (3, 14, False)}}
817
- NATIVE = {**creatures.PAINTERS, **items.PAINTERS} # kinds painted at the fine size (coarse = 2:1 shrink)
827
+ NATIVE = {**creatures.PAINTERS, **items.PAINTERS, **monsters.PAINTERS} # kinds painted at the fine size (coarse = 2:1 shrink)
818
828
  ATTACKERS = {"hero", "slime", "hopper", "flyer", "robot", "bug", "beast", "spider", "dragon", "ghost", "snake"}
819
829
  DERIVED = {"attack": (3, 14.0, False), "die": (5, 10.0, False)}
820
830
 
@@ -0,0 +1,220 @@
1
+ """More chibi monsters in the creatures.py language (big faces, flat colour blocks, one shadow tone, a touch of
2
+ light, a dark outline), painted natively at the fine size: a biting plant, a mushroom, a reaper, a flame,
3
+ a snail and a little alien. The entity colour is the creature's main colour (cap, shell, flame, skin...).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import numpy as np
9
+
10
+ from .chibi import EYE, WHITE, Fig, tone
11
+ from .creatures import FANG, Look, _eye
12
+
13
+ RGBA = tuple[int, int, int, int]
14
+ CLEAR: RGBA = (0, 0, 0, 0)
15
+ LEAF = "#58b848"
16
+ MOUTH = "#8a1a3a"
17
+ TONGUE = "#ff7a96"
18
+ STEM = "#f4e6c6"
19
+ CLOAK = "#3c3058"
20
+ BONE = "#f2ecd8"
21
+ GLOW = (255, 90, 90, 255)
22
+ FLAME_MID = "#ffb02e"
23
+ FLAME_CORE = "#fff0a0"
24
+ SNAIL_BODY = "#d8c4cc"
25
+ WOOD = "#8a5a34"
26
+ STEEL = "#c4ccdc"
27
+
28
+
29
+ def _clip(g: Fig, cx: float, cy: float, rx: float, ry: float, c: RGBA, y0: float = -99, y1: float = 99) -> None:
30
+ """An ellipse restricted to the rows y0..y1 (a cap with a flat underside, a head with a cut-out mouth)."""
31
+ ys, xs = np.mgrid[0:g.h, 0:g.w]
32
+ m = ((xs + 0.5 - cx) / rx) ** 2 + ((ys + 0.5 - cy) / ry) ** 2 <= 1.0
33
+ m &= (ys >= y0) & (ys <= y1)
34
+ g.px[m] = c
35
+
36
+
37
+ def _tri(g: Fig, x: float, y: float, w: float, h: float, c: RGBA) -> None:
38
+ g.poly([(x, y), (x + w / 2, y + h), (x + w, y)] if h > 0 else [(x, y), (x + w / 2, y + h), (x + w, y)], c)
39
+
40
+
41
+ # --- a biting plant ----------------------------------------------------------------------------
42
+
43
+ def paint_plant(g: Fig, L: Look, anim: str, i: int) -> None:
44
+ leaf, cap = tone(LEAF, None), L.body
45
+ k = i % 4
46
+ sway = (0, 1, 0, -1)[k]
47
+ hurt = anim == "hurt"
48
+ gape = 1 if hurt else {"idle": (3, 5, 7, 4)[k], "run": (5, 2, 5, 2)[k]}.get(anim, 4)
49
+ cx, cy = 14 + sway, 11
50
+ g.line(14, 31, 14 + sway, 19, leaf.base, 4) # stem
51
+ g.line(16, 30, 16 + sway, 19, leaf.shadow, 1)
52
+ g.ellipse(7 - sway, 26, 5.5, 2.6, leaf.base) # leaves
53
+ g.ellipse(21 + sway, 26, 5.5, 2.6, leaf.base)
54
+ g.rect(4, 26, 9, 26, leaf.light)
55
+ g.ellipse(cx, cy, 11, 9.5, cap.base) # head
56
+ g.ellipse(cx - 3, cy - 4, 5, 3, cap.light)
57
+ g.ellipse(cx + 5, cy + 3, 5.5, 5, cap.shadow)
58
+ for sx, sy, r in ((cx - 7, cy - 3, 1.6), (cx + 1, cy - 7, 1.9), (cx + 7, cy - 3, 1.5)):
59
+ g.ellipse(sx, sy, r, r, WHITE) # spots
60
+ my = cy + 1 # the mouth
61
+ _clip(g, cx, my, 8.5, gape / 2 + 1.2, tone(MOUTH, None).base, 0, 99)
62
+ g.ellipse(cx, my + gape / 2, 4.6, max(1.0, gape / 3), tone(TONGUE, None).base) if gape > 3 else None
63
+ for x in range(int(cx - 7), int(cx + 7), 3): # fangs
64
+ _tri(g, x, my - gape / 2 - 1, 3, 2.5, FANG)
65
+ g.poly([(x + 1, my + gape / 2 + 1.4), (x + 2.5, my + gape / 2 - 1.6), (x + 4, my + gape / 2 + 1.4)], FANG)
66
+ if hurt:
67
+ g.set(cx - 5, cy - 4, EYE)
68
+ g.set(cx + 5, cy - 4, EYE)
69
+
70
+
71
+ # --- a mushroom --------------------------------------------------------------------------------
72
+
73
+ def paint_mushroom(g: Fig, L: Look, anim: str, i: int) -> None:
74
+ cap, stem = L.body, tone(STEM, None)
75
+ k = i % 4
76
+ bob = {"idle": (0, 1)[i % 2], "run": (0, 1, 0, 1)[k]}.get(anim, 0)
77
+ hurt = anim == "hurt"
78
+ foot = (0, 1, 0, -1)[k] if anim == "run" else 0
79
+ g.ellipse(9, 25.5, 3.2, 2, L.dark.base) # feet
80
+ g.ellipse(19, 25.5, 3.2, 2, L.dark.base)
81
+ g.rect(7 + foot, 25, 9 + foot, 26, L.dark.shadow)
82
+ g.rect(10, 15 + bob, 18, 25, stem.base) # stem
83
+ g.rect(16, 16 + bob, 18, 25, stem.shadow)
84
+ g.rect(9, 20 + bob, 9, 24, stem.base)
85
+ g.rect(19, 20 + bob, 19, 24, stem.shadow)
86
+ _clip(g, 14, 11 + bob, 13, 9.5, cap.base, 0, 16 + bob) # cap, flat underneath
87
+ _clip(g, 10, 8 + bob, 6, 3.4, cap.light, 0, 16 + bob)
88
+ _clip(g, 20, 14 + bob, 6, 3, cap.shadow, 12 + bob, 16 + bob)
89
+ for sx, sy, r in ((7, 9, 2.2), (15, 5.5, 2.4), (21, 10, 2), (12, 13, 1.4)):
90
+ g.ellipse(sx, sy + bob, r, r, WHITE)
91
+ g.rect(4, 15 + bob, 24, 16 + bob, cap.shadow) # the rim
92
+ _eye(g, 11, 18 + bob, closed=(anim == "idle" and i == 1), hurt=hurt)
93
+ _eye(g, 15, 18 + bob, closed=(anim == "idle" and i == 1), hurt=hurt)
94
+ g.rect(13, 22 + bob, 15, 22 + bob, EYE) # tiny mouth
95
+ g.set(10, 21 + bob, tone("#f0a0a0", None).base) # blush
96
+ g.set(17, 21 + bob, tone("#f0a0a0", None).base)
97
+
98
+
99
+ # --- a reaper ----------------------------------------------------------------------------------
100
+
101
+ def paint_reaper(g: Fig, L: Look, anim: str, i: int) -> None:
102
+ robe, bone = tone(CLOAK, None), tone(BONE, None)
103
+ k = i % 4
104
+ bob = (0, 1, 1, 0)[k] if anim in ("idle", "run") else 0
105
+ hurt = anim == "hurt"
106
+ wave = (0, 1, 0, -1)[k]
107
+ pole = tone(WOOD, None)
108
+ g.line(24, 6 + bob, 24, 30 + bob, pole.base, 2) # the scythe
109
+ g.poly([(24, 3 + bob), (14, 2 + bob), (8, 7 + bob), (14, 5 + bob), (24, 8 + bob)], tone(STEEL, None).base)
110
+ g.poly([(24, 3 + bob), (14, 2 + bob), (10, 5 + bob), (24, 5 + bob)], tone(STEEL, None).light)
111
+ g.poly([(8, 16 + bob), (20, 16 + bob), (24, 30 + bob), (3, 30 + bob)], robe.base) # robe
112
+ g.poly([(15, 16 + bob), (20, 16 + bob), (24, 30 + bob), (16, 30 + bob)], robe.shadow)
113
+ for x in range(3, 24, 4): # ragged hem
114
+ g.poly([(x, 29 + bob), (x + 2 + wave, 32), (x + 4, 29 + bob)], robe.base)
115
+ g.ellipse(14, 10 + bob, 9.5, 9, robe.base) # hood
116
+ g.ellipse(17, 12 + bob, 6, 6, robe.shadow)
117
+ g.ellipse(13, 11 + bob, 6.6, 6.4, tone("#14101e", None).base)
118
+ g.ellipse(13, 12 + bob, 4.6, 4.8, bone.base) # skull
119
+ g.rect(9, 12 + bob, 10, 12 + bob, bone.light)
120
+ for ex in (10, 14): # eye sockets with a red glint
121
+ g.rect(ex, 11 + bob, ex + 1, 13 + bob, EYE)
122
+ if not hurt:
123
+ g.set(ex + 1, 12 + bob, GLOW)
124
+ g.rect(11, 15 + bob, 15, 15 + bob, EYE) # teeth line
125
+ for tx in (12, 14):
126
+ g.set(tx, 15 + bob, bone.base)
127
+ g.ellipse(21, 20 + bob, 2.4, 2.4, bone.base) # bony hand on the pole
128
+
129
+
130
+ # --- a flame spirit ----------------------------------------------------------------------------
131
+
132
+ def paint_flame(g: Fig, L: Look, anim: str, i: int) -> None:
133
+ outer, mid, core = L.body, tone(FLAME_MID, None), tone(FLAME_CORE, None)
134
+ k = i % 4
135
+ f = (0, 1, 0, -1)[k]
136
+ bob = (0, 1)[i % 2] if anim == "idle" else 0
137
+ hurt = anim == "hurt"
138
+ g.poly([(12 + f, 0 + bob), (17, 8 + bob), (22, 14), (21, 21), (17, 26), (7, 26), (3, 21), (2, 14), (7, 8 + bob)], outer.base)
139
+ g.poly([(3 - f, 9 + bob), (6, 14), (3, 18)], outer.base) # side tongues
140
+ g.poly([(21 + f, 8 + bob), (19, 14), (22, 18)], outer.base)
141
+ g.poly([(12 - f, 8 + bob), (17, 15), (18, 21), (14, 25), (9, 25), (6, 21), (7, 15)], mid.base)
142
+ g.poly([(12, 14), (15, 19), (14, 24), (10, 24), (9, 19)], core.base)
143
+ g.rect(5, 17, 6, 22, outer.shadow)
144
+ angry = not hurt
145
+ for ex in (7, 14): # slanted angry eyes
146
+ g.rect(ex, 15 + bob, ex + 2, 17 + bob, WHITE)
147
+ g.rect(ex + 1, 16 + bob, ex + 2, 17 + bob, EYE)
148
+ if angry:
149
+ g.line(6, 13 + bob, 10, 15 + bob, EYE, 1)
150
+ g.line(17, 13 + bob, 13, 15 + bob, EYE, 1)
151
+ g.rect(9, 21, 14, 22, EYE) # jagged grin
152
+ for tx in (9, 11, 13):
153
+ g.set(tx, 21, WHITE)
154
+
155
+
156
+ # --- a snail -----------------------------------------------------------------------------------
157
+
158
+ def paint_snail(g: Fig, L: Look, anim: str, i: int) -> None:
159
+ shell, body = L.body, tone(SNAIL_BODY, None)
160
+ k = i % 4
161
+ stretch = (0, 1, 2, 1)[k] if anim == "run" else 0
162
+ bob = (0, 1)[i % 2] if anim == "idle" else 0
163
+ hurt = anim == "hurt"
164
+ g.ellipse(13 + stretch / 2, 20, 12 + stretch / 2, 4.2, body.base) # the foot
165
+ g.rect(3, 22, 23 + stretch, 23, body.shadow)
166
+ g.ellipse(25 + stretch, 17 + bob, 4.5, 4.5, body.base) # head
167
+ g.ellipse(24 + stretch, 15 + bob, 2.5, 1.8, body.light)
168
+ for sx, wig in ((23, 0), (27, 1)): # eye stalks
169
+ x = sx + stretch
170
+ g.line(x, 14 + bob, x - 1 + (wig if k % 2 else 0), 7 + bob, body.base, 2)
171
+ g.ellipse(x - 1 + (wig if k % 2 else 0), 6 + bob, 2.3, 2.3, WHITE)
172
+ g.rect(x - 1 + (wig if k % 2 else 0), 5 + bob, x + (wig if k % 2 else 0), 7 + bob, EYE) if not hurt else g.set(x - 1, 6 + bob, EYE)
173
+ g.rect(26 + stretch, 20 + bob, 28 + stretch, 20 + bob, EYE) # smile
174
+ g.ellipse(11, 12 + bob, 10, 9.5, shell.base) # shell with a spiral
175
+ g.ellipse(8, 8 + bob, 5, 3.4, shell.light)
176
+ g.ellipse(14, 15 + bob, 6.5, 5, shell.shadow)
177
+ for r, c in ((8.2, L.dark.base), (6.4, shell.base), (4.6, L.dark.base), (3.0, shell.base), (1.4, L.dark.base)):
178
+ _clip(g, 11, 12 + bob, r, r, c, 5 + bob, 23)
179
+ g.rect(1, 19, 5, 19, body.base) # tail end
180
+
181
+
182
+ # --- a little alien ----------------------------------------------------------------------------
183
+
184
+ ALIEN = "#6fd24a"
185
+
186
+
187
+ def paint_alien(g: Fig, L: Look, anim: str, i: int) -> None:
188
+ sk = L.body
189
+ k = i % 4
190
+ bob = {"idle": (0, 1)[i % 2], "run": (0, 1, 0, 1)[k]}.get(anim, 0)
191
+ swing = ((-2, 2), (0, 0), (2, -2), (0, 0))[k] if anim == "run" else (0, 0)
192
+ hurt = anim == "hurt"
193
+ g.line(12, 4 + bob, 9, 0 + bob, sk.shadow, 1) # antennae
194
+ g.line(16, 4 + bob, 19, 0 + bob, sk.shadow, 1)
195
+ g.ellipse(9, 0.5 + bob, 1.4, 1.4, sk.light)
196
+ g.ellipse(19, 0.5 + bob, 1.4, 1.4, sk.light)
197
+ g.rect(9 + swing[0], 26, 11 + swing[0], 30, sk.shadow) # legs
198
+ g.rect(15 + swing[1], 26, 17 + swing[1], 30, sk.base)
199
+ g.rect(8 + swing[0], 30, 12 + swing[0], 31, L.dark.base)
200
+ g.rect(14 + swing[1], 30, 18 + swing[1], 31, L.dark.base)
201
+ g.ellipse(13.5, 24 + bob, 6, 5, sk.base) # body
202
+ g.ellipse(11.5, 23 + bob, 3, 2.5, sk.light)
203
+ g.line(7, 22 + bob, 3, 24 + bob + swing[0] // 2, sk.base, 2) # arms
204
+ g.line(20, 22 + bob, 24, 24 + bob + swing[1] // 2, sk.shadow, 2)
205
+ g.ellipse(14, 12 + bob, 11.5, 10, sk.base) # the big head
206
+ g.ellipse(10, 7 + bob, 6, 3.4, sk.light)
207
+ g.ellipse(18.5, 14 + bob, 5, 6, sk.shadow)
208
+ for ex, flip in ((8.5, -1), (19.5, 1)): # big black almond eyes
209
+ if hurt:
210
+ g.line(ex - 2, 10 + bob, ex + 2, 14 + bob, EYE, 1)
211
+ g.line(ex + 2, 10 + bob, ex - 2, 14 + bob, EYE, 1)
212
+ else:
213
+ g.poly([(ex - 3.2, 10 + bob), (ex + 3.2, 11.5 + bob + flip * 0.0), (ex + 1.5 * flip, 17 + bob), (ex - 1.5 * flip, 17 + bob)],
214
+ EYE)
215
+ g.set(ex - 1, 12 + bob, WHITE)
216
+ g.rect(12, 18 + bob, 15, 18 + bob, L.dark.base) # mouth
217
+
218
+
219
+ PAINTERS = {"plant": paint_plant, "mushroom": paint_mushroom, "reaper": paint_reaper, "flame": paint_flame,
220
+ "snail": paint_snail, "alien": paint_alien}