spawnpoint-studio 0.1.0__py3-none-any.whl

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 (118) hide show
  1. spawnpoint/__init__.py +3 -0
  2. spawnpoint/_data/examples/coin_hopper.json +228 -0
  3. spawnpoint/_data/examples/neon_arena.json +251 -0
  4. spawnpoint/_data/examples/pixel_arena.json +242 -0
  5. spawnpoint/_data/examples/pixel_hopper.json +221 -0
  6. spawnpoint/_data/godot/addons/spawn_point/components/aim_component.gd +63 -0
  7. spawnpoint/_data/godot/addons/spawn_point/components/animator_component.gd +66 -0
  8. spawnpoint/_data/godot/addons/spawn_point/components/chase_ai_component.gd +107 -0
  9. spawnpoint/_data/godot/addons/spawn_point/components/collectible_component.gd +58 -0
  10. spawnpoint/_data/godot/addons/spawn_point/components/gate_component.gd +64 -0
  11. spawnpoint/_data/godot/addons/spawn_point/components/health_component.gd +83 -0
  12. spawnpoint/_data/godot/addons/spawn_point/components/hitbox_component.gd +50 -0
  13. spawnpoint/_data/godot/addons/spawn_point/components/hurtbox_component.gd +48 -0
  14. spawnpoint/_data/godot/addons/spawn_point/components/juice_component.gd +219 -0
  15. spawnpoint/_data/godot/addons/spawn_point/components/patrol_ai_component.gd +63 -0
  16. spawnpoint/_data/godot/addons/spawn_point/components/platformer_movement_component.gd +204 -0
  17. spawnpoint/_data/godot/addons/spawn_point/components/player_input_component.gd +77 -0
  18. spawnpoint/_data/godot/addons/spawn_point/components/projectile_component.gd +64 -0
  19. spawnpoint/_data/godot/addons/spawn_point/components/spawner_component.gd +58 -0
  20. spawnpoint/_data/godot/addons/spawn_point/components/stomp_component.gd +56 -0
  21. spawnpoint/_data/godot/addons/spawn_point/components/top_down_movement_component.gd +101 -0
  22. spawnpoint/_data/godot/addons/spawn_point/components/trigger_zone_component.gd +32 -0
  23. spawnpoint/_data/godot/addons/spawn_point/components/weapon_component.gd +83 -0
  24. spawnpoint/_data/godot/addons/spawn_point/manifest.json +1509 -0
  25. spawnpoint/_data/godot/addons/spawn_point/runtime/camera_rig.gd +71 -0
  26. spawnpoint/_data/godot/addons/spawn_point/runtime/cockpit.gd +62 -0
  27. spawnpoint/_data/godot/addons/spawn_point/runtime/entity_core.gd +98 -0
  28. spawnpoint/_data/godot/addons/spawn_point/runtime/fx.gd +112 -0
  29. spawnpoint/_data/godot/addons/spawn_point/runtime/game_state.gd +111 -0
  30. spawnpoint/_data/godot/addons/spawn_point/runtime/hud.gd +155 -0
  31. spawnpoint/_data/godot/addons/spawn_point/runtime/level.gd +19 -0
  32. spawnpoint/_data/godot/addons/spawn_point/runtime/sfx.gd +58 -0
  33. spawnpoint/_data/godot/addons/spawn_point/runtime/sp.gd +121 -0
  34. spawnpoint/_data/godot/addons/spawn_point/runtime/world.gd +130 -0
  35. spawnpoint/_data/godot/addons/spawn_point/shaders/flash.gdshader +9 -0
  36. spawnpoint/_data/godot/addons/spawn_point/shaders/floor_grid.gdshader +13 -0
  37. spawnpoint/_data/godot/addons/spawn_point/tools/build.gd +612 -0
  38. spawnpoint/_data/godot/addons/spawn_point/tools/build.tscn +6 -0
  39. spawnpoint/_data/godot/addons/spawn_point/tools/capture.gd +42 -0
  40. spawnpoint/_data/godot/addons/spawn_point/tools/capture.tscn +6 -0
  41. spawnpoint/_data/godot/addons/spawn_point/tools/dump_manifest.gd +91 -0
  42. spawnpoint/_data/godot/addons/spawn_point/tools/dump_manifest.tscn +6 -0
  43. spawnpoint/_data/godot/addons/spawn_point/tools/playtest.gd +428 -0
  44. spawnpoint/_data/godot/addons/spawn_point/tools/playtest.tscn +6 -0
  45. spawnpoint/_data/godot/addons/spawn_point/tools/playtest_nav.gd +152 -0
  46. spawnpoint/_data/godot/addons/spawn_point/tools/record.gd +89 -0
  47. spawnpoint/_data/godot/addons/spawn_point/tools/record.tscn +6 -0
  48. spawnpoint/_data/godot/addons/spawn_point/tools/smoke.gd +72 -0
  49. spawnpoint/_data/godot/addons/spawn_point/tools/smoke.tscn +6 -0
  50. spawnpoint/analyze.py +387 -0
  51. spawnpoint/build.py +146 -0
  52. spawnpoint/cli.py +290 -0
  53. spawnpoint/cockpit/__init__.py +0 -0
  54. spawnpoint/cockpit/server.py +473 -0
  55. spawnpoint/cockpit/static/app.css +111 -0
  56. spawnpoint/cockpit/static/app.js +327 -0
  57. spawnpoint/cockpit/static/index.html +79 -0
  58. spawnpoint/diagnostics.py +91 -0
  59. spawnpoint/expr.py +219 -0
  60. spawnpoint/gif.py +58 -0
  61. spawnpoint/godot.py +107 -0
  62. spawnpoint/graph.py +175 -0
  63. spawnpoint/level.py +157 -0
  64. spawnpoint/llm_server.py +77 -0
  65. spawnpoint/manifest.py +222 -0
  66. spawnpoint/playtest/__init__.py +104 -0
  67. spawnpoint/playtest/report.py +148 -0
  68. spawnpoint/playtest/runner.py +51 -0
  69. spawnpoint/playtest/tuner.py +151 -0
  70. spawnpoint/project.py +94 -0
  71. spawnpoint/py.typed +0 -0
  72. spawnpoint/rules.py +310 -0
  73. spawnpoint/sfx.py +352 -0
  74. spawnpoint/sprites/__init__.py +0 -0
  75. spawnpoint/sprites/ai.py +222 -0
  76. spawnpoint/sprites/contour.py +147 -0
  77. spawnpoint/sprites/generate.py +507 -0
  78. spawnpoint/sprites/importer.py +212 -0
  79. spawnpoint/sprites/resolve.py +139 -0
  80. spawnpoint/sprites/spriteset.py +159 -0
  81. spawnpoint/sprites/tiles.py +102 -0
  82. spawnpoint/studio/__init__.py +0 -0
  83. spawnpoint/studio/assemble.py +528 -0
  84. spawnpoint/studio/brief.py +197 -0
  85. spawnpoint/studio/creative.py +124 -0
  86. spawnpoint/studio/dataset.py +136 -0
  87. spawnpoint/studio/edits.py +206 -0
  88. spawnpoint/studio/levelgen.py +352 -0
  89. spawnpoint/studio/llm.py +145 -0
  90. spawnpoint/studio/orchestrator.py +341 -0
  91. spawnpoint/studio/prompts.py +122 -0
  92. spawnpoint/studio/web.py +123 -0
  93. spawnpoint/studio/webcheck.py +210 -0
  94. spawnpoint/studio_cli.py +232 -0
  95. spawnpoint_studio-0.1.0.dist-info/METADATA +360 -0
  96. spawnpoint_studio-0.1.0.dist-info/RECORD +118 -0
  97. spawnpoint_studio-0.1.0.dist-info/WHEEL +5 -0
  98. spawnpoint_studio-0.1.0.dist-info/entry_points.txt +2 -0
  99. spawnpoint_studio-0.1.0.dist-info/licenses/LICENSE +21 -0
  100. spawnpoint_studio-0.1.0.dist-info/top_level.txt +2 -0
  101. tests/conftest.py +49 -0
  102. tests/test_ai_sprites.py +122 -0
  103. tests/test_analyze.py +116 -0
  104. tests/test_cockpit.py +148 -0
  105. tests/test_e2e.py +33 -0
  106. tests/test_expr.py +59 -0
  107. tests/test_gif.py +19 -0
  108. tests/test_godot_library.py +17 -0
  109. tests/test_graph.py +44 -0
  110. tests/test_level.py +64 -0
  111. tests/test_live_llm.py +38 -0
  112. tests/test_manifest.py +54 -0
  113. tests/test_playtest.py +129 -0
  114. tests/test_rules.py +85 -0
  115. tests/test_sfx.py +30 -0
  116. tests/test_sprites.py +261 -0
  117. tests/test_studio.py +335 -0
  118. tests/test_web.py +41 -0
@@ -0,0 +1,121 @@
1
+ class_name SP
2
+ ## Static helpers shared by every Spawn Point component.
3
+ ##
4
+ ## Conventions:
5
+ ## - An entity is a physics body (CharacterBody2D / StaticBody2D / Area2D) whose direct child
6
+ ## named "Entity" is an [SPEntityCore].
7
+ ## - Components are direct children of the entity, named by their short name ("Health", ...).
8
+
9
+ # Physics layer bits (1-based layer N == bit 1 << (N - 1)).
10
+ const LAYER_WORLD := 1 << 0
11
+ const LAYER_PLAYER := 1 << 1
12
+ const LAYER_ENEMY := 1 << 2
13
+ const LAYER_PLAYER_HURTBOX := 1 << 3
14
+ const LAYER_ENEMY_HURTBOX := 1 << 4
15
+ const LAYER_PICKUP := 1 << 5
16
+
17
+ const TEAM_PLAYER := "player"
18
+ const TEAM_ENEMY := "enemy"
19
+ const TEAM_NEUTRAL := "neutral"
20
+
21
+ const ENTITY_GROUP := &"sp_entity"
22
+ const PLAYER_GROUP := &"player"
23
+
24
+
25
+ ## Returns the entity root owning [param node] (or [param node] itself), or null.
26
+ static func entity_of(node: Node) -> Node:
27
+ var n := node
28
+ while n != null:
29
+ if n.is_in_group(ENTITY_GROUP) or n.has_node(^"Entity"):
30
+ return n
31
+ n = n.get_parent()
32
+ return null
33
+
34
+
35
+ ## Returns the entity's core node, or null.
36
+ static func core(entity: Node) -> SPEntityCore:
37
+ if entity == null:
38
+ return null
39
+ return entity.get_node_or_null(^"Entity") as SPEntityCore
40
+
41
+
42
+ ## Returns the component named [param short_name] on [param entity], or null.
43
+ static func component(entity: Node, short_name: StringName) -> Node:
44
+ if entity == null:
45
+ return null
46
+ return entity.get_node_or_null(NodePath(String(short_name)))
47
+
48
+
49
+ ## Returns the movement component (platformer or top-down) of [param entity], or null.
50
+ static func movement(entity: Node) -> Node:
51
+ var m := component(entity, &"PlatformerMovement")
52
+ if m == null:
53
+ m = component(entity, &"TopDownMovement")
54
+ return m
55
+
56
+
57
+ ## Team of [param entity]: "player", "enemy" or "neutral".
58
+ static func team_of(entity: Node) -> String:
59
+ if entity == null:
60
+ return TEAM_NEUTRAL
61
+ if entity.has_meta(&"sp_team"):
62
+ return String(entity.get_meta(&"sp_team"))
63
+ var c := core(entity)
64
+ return c.team if c else TEAM_NEUTRAL
65
+
66
+
67
+ ## Hurtbox layer bit for a team.
68
+ static func hurtbox_layer(team: String) -> int:
69
+ match team:
70
+ TEAM_PLAYER:
71
+ return LAYER_PLAYER_HURTBOX
72
+ TEAM_ENEMY:
73
+ return LAYER_ENEMY_HURTBOX
74
+ return 0
75
+
76
+
77
+ ## Layer bits of hurtboxes that a hitbox of [param team] should damage.
78
+ static func opposing_hurtbox_mask(team: String) -> int:
79
+ match team:
80
+ TEAM_PLAYER:
81
+ return LAYER_ENEMY_HURTBOX
82
+ TEAM_ENEMY:
83
+ return LAYER_PLAYER_HURTBOX
84
+ return LAYER_PLAYER_HURTBOX | LAYER_ENEMY_HURTBOX
85
+
86
+
87
+ ## Half extents of the entity's body (from builder metadata or its collision shape).
88
+ static func half_size(entity: Node) -> Vector2:
89
+ if entity.has_meta(&"sp_half_size"):
90
+ return entity.get_meta(&"sp_half_size")
91
+ var cs := entity.get_node_or_null(^"CollisionShape2D") as CollisionShape2D
92
+ if cs and cs.shape:
93
+ return cs.shape.get_rect().size * 0.5
94
+ return Vector2(8, 8)
95
+
96
+
97
+ ## Main color of the entity's visual (sprite's dominant color or placeholder color).
98
+ static func color_of(entity: Node) -> Color:
99
+ var visual := entity.get_node_or_null(^"Visual")
100
+ if visual and visual.has_meta(&"sp_color"):
101
+ return visual.get_meta(&"sp_color")
102
+ var shape := entity.get_node_or_null(^"Visual/Shape") as Polygon2D
103
+ return shape.color if shape else Color.WHITE
104
+
105
+
106
+ ## True when the entity is drawn with a sprite (flip to face) instead of a placeholder shape.
107
+ static func has_sprite(entity: Node) -> bool:
108
+ return entity.has_node(^"Visual/Shape/Sprite")
109
+
110
+
111
+ ## Turns the entity's visual toward [param dir]: sprites flip horizontally, placeholder shapes
112
+ ## rotate (top-down) — side-view pixel art must never be rotated.
113
+ static func face(entity: Node, dir: Vector2, allow_rotation: bool, weight := 0.5) -> void:
114
+ var shape := entity.get_node_or_null(^"Visual/Shape") as Node2D
115
+ if shape == null or dir.length() < 0.01:
116
+ return
117
+ if has_sprite(entity) or not allow_rotation:
118
+ if absf(dir.x) > 0.05:
119
+ shape.scale.x = 1.0 if dir.x > 0.0 else -1.0
120
+ else:
121
+ shape.rotation = lerp_angle(shape.rotation, dir.angle(), weight)
@@ -0,0 +1,130 @@
1
+ extends Node
2
+ ## Autoload "World": entity registry, spawning and rule signal bindings.
3
+
4
+ const ENTITY_SCENE_PATH := "res://entities/%s.tscn"
5
+
6
+ var level: Node2D
7
+ ## Entities below this y are killed (platformers). INF disables.
8
+ var kill_y := INF
9
+
10
+ var _bindings: Array[Dictionary] = []
11
+ var _scene_cache := {}
12
+
13
+
14
+ ## Called by the level root when it enters the tree, before any entity registers.
15
+ func begin_level(level_root: Node2D, level_kill_y: float) -> void:
16
+ level = level_root
17
+ kill_y = level_kill_y
18
+ _bindings.clear()
19
+
20
+
21
+ ## Called by every [SPEntityCore] when its entity is ready.
22
+ func register_entity(entity: Node) -> void:
23
+ for b in _bindings:
24
+ _apply_binding(b, entity)
25
+
26
+
27
+ ## Connects [param callable] to [param signal_name] of component [param component_name] on every
28
+ ## current and future instance of [param entity_type]. The emitting entity is appended as the
29
+ ## last argument.
30
+ func bind_signal(entity_type: StringName, component_name: StringName, signal_name: StringName, callable: Callable) -> void:
31
+ var b := {"type": entity_type, "component": component_name, "signal": signal_name, "callable": callable}
32
+ _bindings.append(b)
33
+ for e in instances(entity_type):
34
+ _apply_binding(b, e)
35
+
36
+
37
+ func _apply_binding(b: Dictionary, entity: Node) -> void:
38
+ if not entity.is_in_group(b.type):
39
+ return
40
+ var src: Object = SP.component(entity, b.component) if b.component != &"" else entity
41
+ if src == null or not src.has_signal(b.signal):
42
+ push_error("Spawn Point: %s has no signal %s.%s" % [b.type, b.component, b.signal])
43
+ return
44
+ var bound: Callable = b.callable.bind(entity)
45
+ if not src.is_connected(b.signal, bound):
46
+ src.connect(b.signal, bound)
47
+
48
+
49
+ ## All live instances of an entity type.
50
+ func instances(entity_type: StringName) -> Array[Node]:
51
+ var out: Array[Node] = []
52
+ if not is_inside_tree():
53
+ return out
54
+ for n in get_tree().get_nodes_in_group(entity_type):
55
+ if n.is_queued_for_deletion() or not n.is_in_group(SP.ENTITY_GROUP):
56
+ continue
57
+ var c := SP.core(n)
58
+ if c and c.dead:
59
+ continue # Dying this frame: rules reacting to "died" must not count it.
60
+ out.append(n)
61
+ return out
62
+
63
+
64
+ func count(entity_type: StringName) -> int:
65
+ return instances(entity_type).size()
66
+
67
+
68
+ func player() -> Node2D:
69
+ var players := get_tree().get_nodes_in_group(SP.PLAYER_GROUP)
70
+ return players[0] as Node2D if players.size() > 0 else null
71
+
72
+
73
+ ## Instantiates entity [param entity_type] at [param pos]. [param meta] is applied as node
74
+ ## metadata before the node enters the tree (e.g. {"sp_team": "enemy"}).
75
+ func spawn(entity_type: String, pos: Vector2, meta := {}) -> Node2D:
76
+ var scene: PackedScene = _scene_cache.get(entity_type)
77
+ if scene == null:
78
+ var path := ENTITY_SCENE_PATH % entity_type
79
+ if not ResourceLoader.exists(path):
80
+ push_error("Spawn Point: unknown entity '%s'" % entity_type)
81
+ return null
82
+ scene = load(path)
83
+ _scene_cache[entity_type] = scene
84
+ var node := scene.instantiate() as Node2D
85
+ for k in meta:
86
+ node.set_meta(k, meta[k])
87
+ node.position = pos
88
+ var parent: Node = level.get_node_or_null(^"Entities") if level else null
89
+ if parent == null:
90
+ parent = level if level else get_tree().current_scene
91
+ parent.add_child(node)
92
+ return node
93
+
94
+
95
+ ## Spawns at tile coordinates (cell center).
96
+ func spawn_at_tile(entity_type: String, tx: int, ty: int) -> Node2D:
97
+ var ts: float = level.get(&"tile_size") if level else 32.0
98
+ return spawn(entity_type, Vector2((tx + 0.5) * ts, (ty + 0.5) * ts))
99
+
100
+
101
+ ## Removes an entity from the game (no death effects).
102
+ func destroy(entity: Node) -> void:
103
+ if entity and is_instance_valid(entity) and not entity.is_queued_for_deletion():
104
+ if entity.is_in_group(SP.PLAYER_GROUP):
105
+ var h := SP.component(entity, &"Health") as HealthComponent
106
+ if h:
107
+ h.kill()
108
+ return
109
+ entity.queue_free()
110
+
111
+
112
+ func destroy_all(entity_type: StringName) -> void:
113
+ for e in instances(entity_type):
114
+ destroy(e)
115
+
116
+
117
+ ## Calls [param method] on component [param component_name] (or the entity root if empty) of
118
+ ## every instance of [param entity_type].
119
+ func call_all(entity_type: StringName, component_name: StringName, method: StringName, args: Array = []) -> void:
120
+ for e in instances(entity_type):
121
+ call_on(e, component_name, method, args)
122
+
123
+
124
+ func call_on(entity: Node, component_name: StringName, method: StringName, args: Array = []) -> Variant:
125
+ if entity == null or not is_instance_valid(entity):
126
+ return null
127
+ var target: Object = SP.component(entity, component_name) if component_name != &"" else entity
128
+ if target == null:
129
+ return null
130
+ return target.callv(method, args)
@@ -0,0 +1,9 @@
1
+ shader_type canvas_item;
2
+
3
+ // 0 = normal color, 1 = solid flash color (hit feedback).
4
+ uniform float flash : hint_range(0.0, 1.0) = 0.0;
5
+ uniform vec4 flash_color : source_color = vec4(1.0);
6
+
7
+ void fragment() {
8
+ COLOR.rgb = mix(COLOR.rgb, flash_color.rgb, flash);
9
+ }
@@ -0,0 +1,13 @@
1
+ shader_type canvas_item;
2
+
3
+ // Subtle checkerboard floor for top-down levels, in world-space tiles.
4
+ uniform float tile_size = 32.0;
5
+ uniform vec4 color_a : source_color = vec4(0.12, 0.13, 0.2, 1.0);
6
+ uniform vec4 color_b : source_color = vec4(0.14, 0.15, 0.23, 1.0);
7
+ uniform vec2 size = vec2(1024.0, 1024.0);
8
+
9
+ void fragment() {
10
+ vec2 cell = floor(UV * size / tile_size);
11
+ float checker = mod(cell.x + cell.y, 2.0);
12
+ COLOR = mix(color_a, color_b, checker);
13
+ }