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.
- {spawnpoint_studio-0.3.4/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.5}/PKG-INFO +8 -4
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/README.md +7 -3
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/build.py +3 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cli.py +12 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/demo.py +8 -4
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/demo_touch.js +1 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/graph.py +2 -0
- spawnpoint_studio-0.3.5/compiler/spawnpoint/logo.py +167 -0
- spawnpoint_studio-0.3.5/compiler/spawnpoint/music.py +117 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/farm.py +38 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/gallery.py +1 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/assemble.py +24 -3
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/farmgen.py +8 -2
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5/compiler/spawnpoint_studio.egg-info}/PKG-INFO +8 -4
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +5 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_farm.py +29 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/sunny_farm.json +107 -46
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_days_component.gd +66 -3
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_plot_component.gd +8 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_save_component.gd +18 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farmer_component.gd +24 -4
- spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/game_menu_component.gd +157 -0
- spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/shop_component.gd +109 -0
- spawnpoint_studio-0.3.5/godot/addons/spawn_point/components/sprinkler_component.gd +58 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/manifest.json +168 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/game_state.gd +1 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/sfx.gd +30 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/pyproject.toml +1 -1
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/LICENSE +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/MANIFEST.in +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/__init__.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/analyze.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/__init__.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/server.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/app.css +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/app.js +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/cockpit/static/index.html +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/diagnostics.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/expr.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/gif.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/godot.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/level.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/llm_server.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/manifest.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/__init__.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/report.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/runner.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/playtest/tuner.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/project.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/py.typed +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/rules.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sfx.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/__init__.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/ai.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/cartoon.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/cartoon_monsters.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/chibi.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/contour.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/creatures.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/generate.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/importer.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/items.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/monsters.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/motion.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/palettes.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/resolve.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/scenery.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/spriteset.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/sprites/tiles.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/__init__.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/brief.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/creative.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/dataset.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/edits.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/genres.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/levelgen.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/llm.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/orchestrator.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/prompts.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/web.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio/webcheck.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint/studio_cli.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/conftest.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_ai_sprites.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_analyze.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_cockpit.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_demo.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_e2e.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_expr.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_genres.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_gif.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_godot_library.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_graph.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_level.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_live_llm.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_manifest.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_playtest.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_rules.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_sfx.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_sprites.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_studio.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/compiler/tests/test_web.py +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/castle_defense.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/coin_hopper.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/dino_eggs.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/knight_quest.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/neon_arena.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/night_survivor.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/ninja_rescue.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/pixel_arena.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/pixel_hopper.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/examples/star_run.json +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/aim_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/animator_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/base_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/bubble_board_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/crop_pest_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/experience_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/farm_animal_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/fishing_spot_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/flight_path_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/gate_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/gatherable_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/ground_patch_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/health_bar_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/health_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/juice_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/level_up_menu_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/market_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/path_follower_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/shmup_ship_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/sleep_spot_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/spawner_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tool_bench_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_brain_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_builder_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/tower_slot_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/warp_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/wave_spawner_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/components/xp_orb_component.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/fx.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/hud.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/level.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/sp.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/runtime/world.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/build.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/build.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/capture.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/capture.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/overview.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/overview.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/record.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/record.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/smoke.gd +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/setup.cfg +0 -0
- {spawnpoint_studio-0.3.4 → spawnpoint_studio-0.3.5}/setup.py +0 -0
{spawnpoint_studio-0.3.4/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.3.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawnpoint-studio
|
|
3
|
-
Version: 0.3.
|
|
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).
|
|
191
|
-
|
|
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).
|
|
167
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|