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.
- spawnpoint/__init__.py +3 -0
- spawnpoint/_data/examples/coin_hopper.json +228 -0
- spawnpoint/_data/examples/neon_arena.json +251 -0
- spawnpoint/_data/examples/pixel_arena.json +242 -0
- spawnpoint/_data/examples/pixel_hopper.json +221 -0
- spawnpoint/_data/godot/addons/spawn_point/components/aim_component.gd +63 -0
- spawnpoint/_data/godot/addons/spawn_point/components/animator_component.gd +66 -0
- spawnpoint/_data/godot/addons/spawn_point/components/chase_ai_component.gd +107 -0
- spawnpoint/_data/godot/addons/spawn_point/components/collectible_component.gd +58 -0
- spawnpoint/_data/godot/addons/spawn_point/components/gate_component.gd +64 -0
- spawnpoint/_data/godot/addons/spawn_point/components/health_component.gd +83 -0
- spawnpoint/_data/godot/addons/spawn_point/components/hitbox_component.gd +50 -0
- spawnpoint/_data/godot/addons/spawn_point/components/hurtbox_component.gd +48 -0
- spawnpoint/_data/godot/addons/spawn_point/components/juice_component.gd +219 -0
- spawnpoint/_data/godot/addons/spawn_point/components/patrol_ai_component.gd +63 -0
- spawnpoint/_data/godot/addons/spawn_point/components/platformer_movement_component.gd +204 -0
- spawnpoint/_data/godot/addons/spawn_point/components/player_input_component.gd +77 -0
- spawnpoint/_data/godot/addons/spawn_point/components/projectile_component.gd +64 -0
- spawnpoint/_data/godot/addons/spawn_point/components/spawner_component.gd +58 -0
- spawnpoint/_data/godot/addons/spawn_point/components/stomp_component.gd +56 -0
- spawnpoint/_data/godot/addons/spawn_point/components/top_down_movement_component.gd +101 -0
- spawnpoint/_data/godot/addons/spawn_point/components/trigger_zone_component.gd +32 -0
- spawnpoint/_data/godot/addons/spawn_point/components/weapon_component.gd +83 -0
- spawnpoint/_data/godot/addons/spawn_point/manifest.json +1509 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/camera_rig.gd +71 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/cockpit.gd +62 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/entity_core.gd +98 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/fx.gd +112 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/game_state.gd +111 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/hud.gd +155 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/level.gd +19 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/sfx.gd +58 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/sp.gd +121 -0
- spawnpoint/_data/godot/addons/spawn_point/runtime/world.gd +130 -0
- spawnpoint/_data/godot/addons/spawn_point/shaders/flash.gdshader +9 -0
- spawnpoint/_data/godot/addons/spawn_point/shaders/floor_grid.gdshader +13 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/build.gd +612 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/build.tscn +6 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/capture.gd +42 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/capture.tscn +6 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/dump_manifest.gd +91 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/dump_manifest.tscn +6 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/playtest.gd +428 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/playtest.tscn +6 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/playtest_nav.gd +152 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/record.gd +89 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/record.tscn +6 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/smoke.gd +72 -0
- spawnpoint/_data/godot/addons/spawn_point/tools/smoke.tscn +6 -0
- spawnpoint/analyze.py +387 -0
- spawnpoint/build.py +146 -0
- spawnpoint/cli.py +290 -0
- spawnpoint/cockpit/__init__.py +0 -0
- spawnpoint/cockpit/server.py +473 -0
- spawnpoint/cockpit/static/app.css +111 -0
- spawnpoint/cockpit/static/app.js +327 -0
- spawnpoint/cockpit/static/index.html +79 -0
- spawnpoint/diagnostics.py +91 -0
- spawnpoint/expr.py +219 -0
- spawnpoint/gif.py +58 -0
- spawnpoint/godot.py +107 -0
- spawnpoint/graph.py +175 -0
- spawnpoint/level.py +157 -0
- spawnpoint/llm_server.py +77 -0
- spawnpoint/manifest.py +222 -0
- spawnpoint/playtest/__init__.py +104 -0
- spawnpoint/playtest/report.py +148 -0
- spawnpoint/playtest/runner.py +51 -0
- spawnpoint/playtest/tuner.py +151 -0
- spawnpoint/project.py +94 -0
- spawnpoint/py.typed +0 -0
- spawnpoint/rules.py +310 -0
- spawnpoint/sfx.py +352 -0
- spawnpoint/sprites/__init__.py +0 -0
- spawnpoint/sprites/ai.py +222 -0
- spawnpoint/sprites/contour.py +147 -0
- spawnpoint/sprites/generate.py +507 -0
- spawnpoint/sprites/importer.py +212 -0
- spawnpoint/sprites/resolve.py +139 -0
- spawnpoint/sprites/spriteset.py +159 -0
- spawnpoint/sprites/tiles.py +102 -0
- spawnpoint/studio/__init__.py +0 -0
- spawnpoint/studio/assemble.py +528 -0
- spawnpoint/studio/brief.py +197 -0
- spawnpoint/studio/creative.py +124 -0
- spawnpoint/studio/dataset.py +136 -0
- spawnpoint/studio/edits.py +206 -0
- spawnpoint/studio/levelgen.py +352 -0
- spawnpoint/studio/llm.py +145 -0
- spawnpoint/studio/orchestrator.py +341 -0
- spawnpoint/studio/prompts.py +122 -0
- spawnpoint/studio/web.py +123 -0
- spawnpoint/studio/webcheck.py +210 -0
- spawnpoint/studio_cli.py +232 -0
- spawnpoint_studio-0.1.0.dist-info/METADATA +360 -0
- spawnpoint_studio-0.1.0.dist-info/RECORD +118 -0
- spawnpoint_studio-0.1.0.dist-info/WHEEL +5 -0
- spawnpoint_studio-0.1.0.dist-info/entry_points.txt +2 -0
- spawnpoint_studio-0.1.0.dist-info/licenses/LICENSE +21 -0
- spawnpoint_studio-0.1.0.dist-info/top_level.txt +2 -0
- tests/conftest.py +49 -0
- tests/test_ai_sprites.py +122 -0
- tests/test_analyze.py +116 -0
- tests/test_cockpit.py +148 -0
- tests/test_e2e.py +33 -0
- tests/test_expr.py +59 -0
- tests/test_gif.py +19 -0
- tests/test_godot_library.py +17 -0
- tests/test_graph.py +44 -0
- tests/test_level.py +64 -0
- tests/test_live_llm.py +38 -0
- tests/test_manifest.py +54 -0
- tests/test_playtest.py +129 -0
- tests/test_rules.py +85 -0
- tests/test_sfx.py +30 -0
- tests/test_sprites.py +261 -0
- tests/test_studio.py +335 -0
- 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
|
+
}
|