diff --git a/behaviours/summoning_circle.gd b/behaviours/summoning_circle.gd
new file mode 100644
index 0000000..2723260
--- /dev/null
+++ b/behaviours/summoning_circle.gd
@@ -0,0 +1,72 @@
+@icon("res://behaviours/summoning_circle.svg")
+extends Node2D
+class_name SummoningCircle
+
+
+## Emitted when a recipe is matched, before the components are sacrificed.
+signal recipe_matched(m: SummoningRecipe.Match, recipe: SummoningRecipe)
+
+
+## The [SacrificeStone]s part of this summoning circle.
+var stones: Array[SacrificeStone] = []
+
+## The [SummoningRecipe]s performable by this summoning circle.
+var recipes: Array[SummoningRecipe] = []
+
+var _recipes_matched_signals: Array[Callable] = []
+
+
+## Refresh the value of [field stones], and reconnect signals accordingly.
+func refresh_stones() -> void:
+ # Disconnect signals
+ for stone in stones:
+ if stone == null:
+ return
+ if stone.sacrifice_changed.is_connected(_on_sacrifice_changed):
+ stone.sacrifice_changed.disconnect(_on_sacrifice_changed)
+ # Find the new stones
+ stones.assign(
+ find_children("*", "SacrificeStone", true, false)
+ )
+ # Reconnect signals
+ for stone in stones:
+ stone.sacrifice_changed.connect(_on_sacrifice_changed)
+
+## Refresh the value of [field recipes], and reconnect signals accordingly.
+func refresh_recipes() -> void:
+ # Disconnect signals
+ var idx: int = 0
+ for recipe in recipes:
+ var callable = _recipes_matched_signals[idx]
+ if recipe.matched.is_connected(callable):
+ recipe.matched.disconnect(callable)
+ idx += 1
+ _recipes_matched_signals = []
+ # Find the new recipes
+ recipes.assign(
+ find_children("*", "SummoningRecipe", true, false)
+ )
+ # Reconnect signals
+ for recipe in recipes:
+ var callable = _on_recipe_matched.bind(recipe)
+ recipe.matched.connect(callable)
+ _recipes_matched_signals.push_back(callable)
+
+
+func _ready() -> void:
+ refresh_stones()
+ refresh_recipes()
+
+func _on_sacrifice_changed(_entity: Node2D) -> void:
+ for recipe in recipes:
+ var entities: Array[Node2D] = []
+ entities.assign(
+ stones.map(func(stone): return stone.entity)
+ )
+ if recipe.do_match(entities):
+ break
+
+func _on_recipe_matched(m: SummoningRecipe.Match, _recipe: SummoningRecipe) -> void:
+ recipe_matched.emit()
+ for sacrificable in m.sacrificables:
+ sacrificable.sacrifice()
diff --git a/behaviours/summoning_circle.svg b/behaviours/summoning_circle.svg
new file mode 100644
index 0000000..9076e8d
--- /dev/null
+++ b/behaviours/summoning_circle.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/behaviours/summoning_circle.svg.import b/behaviours/summoning_circle.svg.import
new file mode 100644
index 0000000..6c6cd3b
--- /dev/null
+++ b/behaviours/summoning_circle.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmgfh5q206kpm"
+path="res://.godot/imported/summoning_circle.svg-f06b755c16ca8fe188e1218d3d1d14aa.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://behaviours/summoning_circle.svg"
+dest_files=["res://.godot/imported/summoning_circle.svg-f06b755c16ca8fe188e1218d3d1d14aa.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/behaviours/summoning_circle.tscn b/behaviours/summoning_circle.tscn
new file mode 100644
index 0000000..c5566ac
--- /dev/null
+++ b/behaviours/summoning_circle.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://xj0pa287n1wo"]
+
+[ext_resource type="Script" path="res://behaviours/summoning_circle.gd" id="1_x3bxd"]
+
+[node name="SummoningCircle" type="Node2D"]
+script = ExtResource("1_x3bxd")
diff --git a/behaviours/summoning_recipe.gd b/behaviours/summoning_recipe.gd
index 53db5ab..20658a7 100644
--- a/behaviours/summoning_recipe.gd
+++ b/behaviours/summoning_recipe.gd
@@ -3,59 +3,80 @@ extends Node
class_name SummoningRecipe
-## A [Node] describing a possible recipe that can be performed with the [SummoningCircle].
+## A [Node] describing a possible ingredients that can be performed with the [SummoningCircle].
-## Emitted when [method do_match] is called, and the [field recipe] is successfully matched.
-signal matched
+## Emitted when [method do_match] is called, and the [field ingredients] is successfully matched.
+signal matched(match: Match)
## How [Sacrificable]s should be matched.
@export var mode := Mode.NONE
## Which [Sacrificable]s should be matched.
-@export var recipe: Array[StringName] = []
+@export var ingredients: Array[StringName] = []
-func check_match(inputs: Array[StringName]) -> bool:
- var matching: Array[bool] = recipe.map(func(): return false)
- # This is awful, but I am too lazy to implement something better
- # why is there no enumerate Q_Q
- for input in inputs:
- var idx = 0
- for matchable in recipe:
- if input == matchable and not matching[idx]:
- matching[idx] = true
+func get_match(inputs: Array[Node2D]) -> Match:
+ var matched_ingredients: Array[Sacrificable] = []
+ # Find the ingredients
+ for ingredient in ingredients:
+ var matched_this = false
+ for input in inputs:
+ if input == null:
continue
- idx += 1
+ for _sacrificable in input.find_children("*", "Sacrificable", true, false):
+ var sacrificable: Sacrificable = _sacrificable as Sacrificable
+ if sacrificable in matched_ingredients:
+ continue
+ if sacrificable.kind == ingredient:
+ matched_ingredients.push_back(sacrificable)
+ matched_this = true
+ break
+ if matched_this:
+ break
+ if not matched_this:
+ matched_ingredients.push_back(null)
# Use one of the various modes
- # why do i have to do this
match mode:
Mode.NONE:
- return false
+ return null
Mode.ANY:
- return matching.any(func(value): return value)
+ if matched_ingredients.any(func(value): return value != null):
+ return Match.create(matched_ingredients)
+ else:
+ return null
Mode.ALL:
- return matching.all(func(value): return value)
- Mode.EXACT:
- return matching.all(func(value): return value) and len(inputs) == len(recipe)
+ if matched_ingredients.all(func(value): return value != null):
+ return Match.create(matched_ingredients)
+ else:
+ return null
_:
# Static analysis go home you're drunk
- return false
+ return null
-func do_match(inputs: Array[StringName]) -> void:
- if check_match(inputs):
- matched.emit()
+func do_match(inputs: Array[Node2D]) -> Match:
+ var m = get_match(inputs)
+ if m != null:
+ matched.emit(m)
+ return m
+
+
+class Match:
+ var sacrificables: Array[Sacrificable]
+
+ static func create(s: Array[Sacrificable]) -> Match:
+ var this = Match.new()
+ this.sacrificables = s
+ return this
enum Mode {
- ## Never match the recipe.
+ ## Never match the ingredients.
NONE = 0,
- ## Match the recipe if a single one of the sacrificables is matched.
+ ## Match the ingredients if a single one of the sacrificables is matched.
ANY = 1,
- ## Match the recipe if all the sacrificables are matched, even if more are present.
+ ## Match the ingredients if all the sacrificables are matched, even if more are present.
ALL = 2,
- ## Match the recipe if all the sacrificables are matched AND there are no other sacrificables present.
- EXACT = 3,
}
diff --git a/behaviours/summoning_recipe.svg b/behaviours/summoning_recipe.svg
index e675f0a..614ebc2 100644
--- a/behaviours/summoning_recipe.svg
+++ b/behaviours/summoning_recipe.svg
@@ -3,7 +3,7 @@
viewBox="0 0 512 512"
version="1.1"
id="svg1"
- sodipodi:docname="summoning_recipe.svg"
+ sodipodi:docname="summoning_circle.svg"
width="16"
height="16"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
@@ -22,9 +22,9 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
- inkscape:zoom="13.171875"
- inkscape:cx="5.4661922"
- inkscape:cy="7.0225386"
+ inkscape:zoom="5.5"
+ inkscape:cx="-4.9090909"
+ inkscape:cy="27.909091"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="1280"
@@ -33,11 +33,7 @@
inkscape:current-layer="svg1" />
-
diff --git a/entities/sheep.tscn b/entities/sheep.tscn
index 590e115..d60d1a3 100644
--- a/entities/sheep.tscn
+++ b/entities/sheep.tscn
@@ -325,6 +325,7 @@ stream = ExtResource("16_nswfl")
[node name="FallSound" type="AudioStreamPlayer2D" parent="MovementDrag"]
stream = ExtResource("17_8kst2")
+[connection signal="sacrificed" from="Sacrificable" to="." method="queue_free"]
[connection signal="move" from="MovementIdle" to="." method="_on_move"]
[connection signal="move_disabled" from="MovementIdle" to="MovementIdle/BoredTimer" method="stop"]
[connection signal="move_enabled" from="MovementIdle" to="MovementIdle/BoredTimer" method="start"]
diff --git a/entities/summoning_circle.gd b/entities/summoning_circle.gd
deleted file mode 100644
index c7ade52..0000000
--- a/entities/summoning_circle.gd
+++ /dev/null
@@ -1,29 +0,0 @@
-extends Node2D
-class_name SummoningCircle
-
-
-## The [SacrificeStone]s part of this summoning circle.
-var stones: Array[SacrificeStone] = []
-
-
-## Refresh the value of [field stones], and reconnect signals accordingly.
-func refresh_stones() -> void:
- stones.map(func(stone: SacrificeStone):
- if stone == null:
- return
- if stone.sacrifice_changed.is_connected(self._on_sacrifice_changed):
- stone.sacrifice_changed.disconnect(self._on_sacrifice_changed)
- )
- stones.assign(
- find_children("*", "SacrificeStone", true, false)
- )
- stones.map(func(stone: SacrificeStone):
- stone.sacrifice_changed.connect(self._on_sacrifice_changed)
- )
-
-
-func _ready() -> void:
- refresh_stones()
-
-func _on_sacrifice_changed(_entity: Node2D) -> void:
- Log.w(self, "Sacrifice has changed, but no summoning function is implemented.")
diff --git a/entities/summoning_circle.tscn b/entities/summoning_circle.tscn
deleted file mode 100644
index 61eabbe..0000000
--- a/entities/summoning_circle.tscn
+++ /dev/null
@@ -1,34 +0,0 @@
-[gd_scene load_steps=5 format=3 uid="uid://xj0pa287n1wo"]
-
-[ext_resource type="Texture2D" uid="uid://n0wj20mduwy8" path="res://entities/summoning_circle.png" id="1_c6jmb"]
-[ext_resource type="Script" path="res://entities/summoning_circle.gd" id="1_x3bxd"]
-[ext_resource type="PackedScene" uid="uid://ddpo03rb6068c" path="res://entities/sacrifice_stone.tscn" id="2_dwkfn"]
-[ext_resource type="PackedScene" uid="uid://tx1qi6ahlxjp" path="res://behaviours/spawner.tscn" id="3_p6s0q"]
-
-[node name="SummoningCircle" type="Node2D"]
-script = ExtResource("1_x3bxd")
-
-[node name="Spawner" parent="." instance=ExtResource("3_p6s0q")]
-unique_name_in_owner = true
-
-[node name="Sprite" type="Sprite2D" parent="."]
-position = Vector2(2, 4)
-scale = Vector2(2, 2)
-texture = ExtResource("1_c6jmb")
-
-[node name="SacrificeStones" type="Node2D" parent="."]
-
-[node name="StoneUp" parent="SacrificeStones" instance=ExtResource("2_dwkfn")]
-position = Vector2(0, -108)
-
-[node name="StoneTopLeft" parent="SacrificeStones" instance=ExtResource("2_dwkfn")]
-position = Vector2(-92, -64)
-
-[node name="StoneTopRight" parent="SacrificeStones" instance=ExtResource("2_dwkfn")]
-position = Vector2(92, -64)
-
-[node name="StoneBottomLeft" parent="SacrificeStones" instance=ExtResource("2_dwkfn")]
-position = Vector2(-75, 30)
-
-[node name="StoneBottomRight" parent="SacrificeStones" instance=ExtResource("2_dwkfn")]
-position = Vector2(75, 30)
diff --git a/entities/summoning_circle.png b/entities/summoning_circle_pentagram.png
similarity index 100%
rename from entities/summoning_circle.png
rename to entities/summoning_circle_pentagram.png
diff --git a/entities/summoning_circle.png.import b/entities/summoning_circle_pentagram.png.import
similarity index 67%
rename from entities/summoning_circle.png.import
rename to entities/summoning_circle_pentagram.png.import
index c3f0e0f..1a83148 100644
--- a/entities/summoning_circle.png.import
+++ b/entities/summoning_circle_pentagram.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://n0wj20mduwy8"
-path="res://.godot/imported/summoning_circle.png-a9e8e58bff05ee4e15de5d48253b8a5d.ctex"
+path="res://.godot/imported/summoning_circle_pentagram.png-2b4ad22e35663173dd75a1efb3a64309.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://entities/summoning_circle.png"
-dest_files=["res://.godot/imported/summoning_circle.png-a9e8e58bff05ee4e15de5d48253b8a5d.ctex"]
+source_file="res://entities/summoning_circle_pentagram.png"
+dest_files=["res://.godot/imported/summoning_circle_pentagram.png-2b4ad22e35663173dd75a1efb3a64309.ctex"]
[params]
diff --git a/entities/summoning_circle_pentagram.tscn b/entities/summoning_circle_pentagram.tscn
new file mode 100644
index 0000000..e203f82
--- /dev/null
+++ b/entities/summoning_circle_pentagram.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=7 format=3 uid="uid://cgpwig0rd08vh"]
+
+[ext_resource type="Script" path="res://behaviours/summoning_circle.gd" id="1_l5mec"]
+[ext_resource type="PackedScene" uid="uid://tx1qi6ahlxjp" path="res://behaviours/spawner.tscn" id="2_xq0wr"]
+[ext_resource type="Texture2D" uid="uid://n0wj20mduwy8" path="res://entities/summoning_circle_pentagram.png" id="3_stpdd"]
+[ext_resource type="PackedScene" uid="uid://ddpo03rb6068c" path="res://entities/sacrifice_stone.tscn" id="4_qyef2"]
+[ext_resource type="PackedScene" uid="uid://ufjnfj3itypj" path="res://behaviours/summoning_recipe.tscn" id="5_jbk35"]
+[ext_resource type="PackedScene" uid="uid://4d3ksr3171x4" path="res://entities/imp.tscn" id="6_utwxo"]
+
+[node name="SummoningCirclePentagram" type="Node2D"]
+script = ExtResource("1_l5mec")
+
+[node name="Sprite" type="Sprite2D" parent="."]
+position = Vector2(2, 4)
+scale = Vector2(2, 2)
+texture = ExtResource("3_stpdd")
+
+[node name="Stones" type="Node2D" parent="."]
+
+[node name="StoneUp" parent="Stones" instance=ExtResource("4_qyef2")]
+position = Vector2(0, -108)
+
+[node name="StoneTopLeft" parent="Stones" instance=ExtResource("4_qyef2")]
+position = Vector2(-92, -64)
+
+[node name="StoneTopRight" parent="Stones" instance=ExtResource("4_qyef2")]
+position = Vector2(92, -64)
+
+[node name="StoneBottomLeft" parent="Stones" instance=ExtResource("4_qyef2")]
+position = Vector2(-75, 30)
+
+[node name="StoneBottomRight" parent="Stones" instance=ExtResource("4_qyef2")]
+position = Vector2(75, 30)
+
+[node name="Recipes" type="Node" parent="."]
+
+[node name="FiveSheep" parent="Recipes" instance=ExtResource("5_jbk35")]
+mode = 2
+ingredients = Array[StringName]([&"Sheep", &"Sheep", &"Sheep", &"Sheep", &"Sheep"])
+
+[node name="Spawners" type="Node2D" parent="."]
+
+[node name="Imp" parent="Spawners" instance=ExtResource("2_xq0wr")]
+scene = ExtResource("6_utwxo")
+
+[connection signal="matched" from="Recipes/FiveSheep" to="Spawners/Imp" method="spawn" unbinds=1]
diff --git a/scenes/game/main_game.tscn b/scenes/game/main_game.tscn
index b79deb6..39ce024 100644
--- a/scenes/game/main_game.tscn
+++ b/scenes/game/main_game.tscn
@@ -15,7 +15,7 @@
[ext_resource type="PackedScene" uid="uid://cmemgijh6nfmk" path="res://entities/chupacabra.tscn" id="11_ixo4x"]
[ext_resource type="PackedScene" uid="uid://dnjtduk0hla3f" path="res://entities/watcher.tscn" id="14_8rumi"]
[ext_resource type="PackedScene" uid="uid://gl4umoff474y" path="res://entities/cthulhu.tscn" id="15_k41qf"]
-[ext_resource type="PackedScene" uid="uid://xj0pa287n1wo" path="res://entities/summoning_circle.tscn" id="16_2s71o"]
+[ext_resource type="PackedScene" uid="uid://cgpwig0rd08vh" path="res://entities/summoning_circle_pentagram.tscn" id="16_nhsrb"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_058kb"]
texture = ExtResource("2_o7bg5")
@@ -948,5 +948,5 @@ position = Vector2(189, 171)
[node name="Cthulhu" parent="." instance=ExtResource("15_k41qf")]
position = Vector2(226, -137)
-[node name="SummoningCircle" parent="." instance=ExtResource("16_2s71o")]
-position = Vector2(-382, -89)
+[node name="SummoningCirclePentagram" parent="." instance=ExtResource("16_nhsrb")]
+position = Vector2(-321, -191)