diff --git a/components/Bullets/HealingMissle.tscn b/components/Bullets/HealingMissle.tscn
new file mode 100644
index 0000000..9fc1bd5
--- /dev/null
+++ b/components/Bullets/HealingMissle.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=5 format=3 uid="uid://ds6yxgj6r8f4v"]
+
+[ext_resource type="PackedScene" uid="uid://crtdkysmnkith" path="res://components/Abstracts/BulletBase.tscn" id="1_1wl5l"]
+[ext_resource type="Texture2D" uid="uid://dpvk6pja35rdb" path="res://resources/items/apple-white.png" id="2_0jb7f"]
+[ext_resource type="Script" uid="uid://d2ubbdwexom6t" path="res://scripts/Contents/Bullets/HealingMissle.gd" id="2_nxr84"]
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_nxr84"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": ExtResource("2_0jb7f")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[node name="HealingMissle" instance=ExtResource("1_1wl5l")]
+script = ExtResource("2_nxr84")
+speed = 1.0
+lifeTime = 5000.0
+allowFriendlyDamage = true
+
+[node name="texture" parent="." index="0"]
+modulate = Color(0.0068707466, 1, 0, 1)
+scale = Vector2(0.44360867, 0.44360867)
+sprite_frames = SubResource("SpriteFrames_nxr84")
diff --git a/components/Characters/Rooster.tscn b/components/Characters/Rooster.tscn
index 61dea8f..6cf793e 100644
--- a/components/Characters/Rooster.tscn
+++ b/components/Characters/Rooster.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=14 format=3 uid="uid://dky8574uqc18r"]
+[gd_scene load_steps=15 format=3 uid="uid://dky8574uqc18r"]
[ext_resource type="PackedScene" uid="uid://cvogxi7mktumf" path="res://components/Abstracts/EntityBase.tscn" id="1_e5pl8"]
[ext_resource type="Script" uid="uid://cthtupc6dtbav" path="res://scripts/Contents/Characters/Rooster.gd" id="2_oqdqd"]
[ext_resource type="PackedScene" uid="uid://dldnbpubu2jgm" path="res://components/Weapons/GrassWall.tscn" id="3_0omr3"]
+[ext_resource type="PackedScene" uid="uid://blwoev5sencdh" path="res://components/Weapons/Gobo.tscn" id="3_jluqw"]
[ext_resource type="PackedScene" uid="uid://c0n3igy4hucrg" path="res://components/Weapons/PurpleCrystal.tscn" id="3_joj4g"]
[ext_resource type="AudioStream" uid="uid://cdrevrq7n6yqa" path="res://resources/sounds/effect/Boing.mp3" id="4_66s6c"]
[ext_resource type="AudioStream" uid="uid://benyec5bqni0b" path="res://resources/sounds/effect/Chomp.wav" id="4_k0yme"]
@@ -59,10 +60,13 @@ metadata/_edit_vertical_guides_ = [71.0]
[node name="weaponStore" parent="." index="1"]
process_mode = 4
-[node name="PurpleCrystal" parent="weaponStore" index="0" instance=ExtResource("3_joj4g")]
+[node name="Gobo" parent="weaponStore" index="0" instance=ExtResource("3_jluqw")]
+offset_bottom = 446.0
+
+[node name="PurpleCrystal" parent="weaponStore" index="1" instance=ExtResource("3_joj4g")]
offset_bottom = 350.0
-[node name="GrassWall" parent="weaponStore" index="1" instance=ExtResource("3_0omr3")]
+[node name="GrassWall" parent="weaponStore" index="2" instance=ExtResource("3_0omr3")]
[node name="sprint" parent="sounds" index="0"]
stream = ExtResource("4_66s6c")
diff --git a/components/Summons/Gobo.tscn b/components/Summons/Gobo.tscn
new file mode 100644
index 0000000..c313bae
--- /dev/null
+++ b/components/Summons/Gobo.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=7 format=3 uid="uid://b2ilo4ag801m2"]
+
+[ext_resource type="PackedScene" uid="uid://cvogxi7mktumf" path="res://components/Abstracts/EntityBase.tscn" id="1_hgynr"]
+[ext_resource type="Texture2D" uid="uid://xen2edbhf052" path="res://resources/characters/gobo/gobo-a.svg" id="2_e0tdx"]
+[ext_resource type="Script" uid="uid://cqax27sw0wlme" path="res://scripts/Contents/Summons/Gobo.gd" id="2_ftr65"]
+[ext_resource type="Texture2D" uid="uid://s12imwmfics6" path="res://resources/characters/gobo/gobo-b.svg" id="3_ftr65"]
+[ext_resource type="Texture2D" uid="uid://d38bjxoowbg7t" path="res://resources/characters/gobo/gobo-c.svg" id="4_w2qsx"]
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_0xeeo"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": ExtResource("2_e0tdx")
+}],
+"loop": true,
+"name": &"idle",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": ExtResource("2_e0tdx")
+}, {
+"duration": 1.0,
+"texture": ExtResource("3_ftr65")
+}, {
+"duration": 1.0,
+"texture": ExtResource("4_w2qsx")
+}],
+"loop": true,
+"name": &"walk",
+"speed": 5.0
+}]
+
+[node name="Gobo" instance=ExtResource("1_hgynr")]
+script = ExtResource("2_ftr65")
+attraction = 0.0
+displayName = "Gobo"
+
+[node name="texture" parent="." index="3"]
+sprite_frames = SubResource("SpriteFrames_0xeeo")
+animation = &"walk"
+frame = 2
+frame_progress = 0.92641157
+
+[node name="statebar" parent="." index="4"]
+position = Vector2(0, -122)
diff --git a/components/Weapons/Gobo.tscn b/components/Weapons/Gobo.tscn
new file mode 100644
index 0000000..db5c642
--- /dev/null
+++ b/components/Weapons/Gobo.tscn
@@ -0,0 +1,39 @@
+[gd_scene load_steps=4 format=3 uid="uid://blwoev5sencdh"]
+
+[ext_resource type="PackedScene" uid="uid://ckq2cq6m23hq3" path="res://components/Abstracts/WeaponCardBase.tscn" id="1_0udcw"]
+[ext_resource type="Script" uid="uid://cbg3xkg1giv35" path="res://scripts/Contents/Weapons/Gobo.gd" id="2_7yxbe"]
+[ext_resource type="Texture2D" uid="uid://xen2edbhf052" path="res://resources/characters/gobo/gobo-a.svg" id="2_tw58e"]
+
+[node name="Gobo" instance=ExtResource("1_0udcw")]
+script = ExtResource("2_7yxbe")
+avatarTexture = ExtResource("2_tw58e")
+displayName = "Gobo"
+quality = 4
+typeTopic = 4
+store = {
+"atk": 50,
+"health": 200,
+"percent": 0.1
+}
+storeType = {
+"atk": 1,
+"health": 1,
+"percent": 2
+}
+descriptionTemplate = "召唤[b]Gobo[/b],Gobo会自主行动,每受到$atk点伤害后会发射一枚[b]疗愈导弹[/b]。
+[b]疗愈导弹[/b]命中障碍物或友军时对其产生Gobo剩余生命值×$percent点的治疗。
+Gobo初始拥有$health点生命值。"
+cooldown = 1000.0
+
+[node name="avatar" parent="container/info" index="0"]
+texture = ExtResource("2_tw58e")
+
+[node name="name" parent="container/info" index="2"]
+displayName = "Gobo"
+quality = 4
+typeTopic = 4
+
+[node name="description" parent="container" index="2"]
+text = "[center]召唤[b]Gobo[/b],Gobo会自主行动,每受到[color=cyan]50[/color]点伤害后会发射一枚[b]疗愈导弹[/b]。
+[b]疗愈导弹[/b]命中障碍物或友军时对其产生Gobo剩余生命值×[color=cyan]10.0%[/color]点的治疗。
+Gobo初始拥有[color=cyan]200[/color]点生命值。[/center]"
diff --git a/resources/characters/gobo/gobo-a.svg b/resources/characters/gobo/gobo-a.svg
new file mode 100644
index 0000000..a117c67
--- /dev/null
+++ b/resources/characters/gobo/gobo-a.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/resources/characters/gobo/gobo-a.svg.import b/resources/characters/gobo/gobo-a.svg.import
new file mode 100644
index 0000000..56073bf
--- /dev/null
+++ b/resources/characters/gobo/gobo-a.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://xen2edbhf052"
+path="res://.godot/imported/gobo-a.svg-324026b3e192456adada726ade96da2d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://resources/characters/gobo/gobo-a.svg"
+dest_files=["res://.godot/imported/gobo-a.svg-324026b3e192456adada726ade96da2d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/resources/characters/gobo/gobo-b.svg b/resources/characters/gobo/gobo-b.svg
new file mode 100644
index 0000000..1622b6b
--- /dev/null
+++ b/resources/characters/gobo/gobo-b.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/resources/characters/gobo/gobo-b.svg.import b/resources/characters/gobo/gobo-b.svg.import
new file mode 100644
index 0000000..ef7baf1
--- /dev/null
+++ b/resources/characters/gobo/gobo-b.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://s12imwmfics6"
+path="res://.godot/imported/gobo-b.svg-075dcd98d32f1272ba543e817534f6fc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://resources/characters/gobo/gobo-b.svg"
+dest_files=["res://.godot/imported/gobo-b.svg-075dcd98d32f1272ba543e817534f6fc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/resources/characters/gobo/gobo-c.svg b/resources/characters/gobo/gobo-c.svg
new file mode 100644
index 0000000..fe18434
--- /dev/null
+++ b/resources/characters/gobo/gobo-c.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/resources/characters/gobo/gobo-c.svg.import b/resources/characters/gobo/gobo-c.svg.import
new file mode 100644
index 0000000..510d403
--- /dev/null
+++ b/resources/characters/gobo/gobo-c.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d38bjxoowbg7t"
+path="res://.godot/imported/gobo-c.svg-7d92174bbaa3236d452fc842786da9f3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://resources/characters/gobo/gobo-c.svg"
+dest_files=["res://.godot/imported/gobo-c.svg-7d92174bbaa3236d452fc842786da9f3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/resources/items/apple-white.png b/resources/items/apple-white.png
new file mode 100644
index 0000000..ee4bef7
Binary files /dev/null and b/resources/items/apple-white.png differ
diff --git a/resources/items/apple-white.png.import b/resources/items/apple-white.png.import
new file mode 100644
index 0000000..32e61bd
--- /dev/null
+++ b/resources/items/apple-white.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dpvk6pja35rdb"
+path="res://.godot/imported/apple-white.png-d39950409511c2e490d5c9c504fbd06c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://resources/items/apple-white.png"
+dest_files=["res://.godot/imported/apple-white.png-d39950409511c2e490d5c9c504fbd06c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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
diff --git a/scripts/Contents/Bullets/HealingMissle.gd b/scripts/Contents/Bullets/HealingMissle.gd
new file mode 100644
index 0000000..7bdc218
--- /dev/null
+++ b/scripts/Contents/Bullets/HealingMissle.gd
@@ -0,0 +1,5 @@
+extends BulletBase
+class_name HealingMissleBullet
+
+func ai():
+ PresetBulletAI.forward(self, rotation)
diff --git a/scripts/Contents/Bullets/HealingMissle.gd.uid b/scripts/Contents/Bullets/HealingMissle.gd.uid
new file mode 100644
index 0000000..4561a5b
--- /dev/null
+++ b/scripts/Contents/Bullets/HealingMissle.gd.uid
@@ -0,0 +1 @@
+uid://d2ubbdwexom6t
diff --git a/scripts/Contents/Summons/Gobo.gd b/scripts/Contents/Summons/Gobo.gd
new file mode 100644
index 0000000..2953bb8
--- /dev/null
+++ b/scripts/Contents/Summons/Gobo.gd
@@ -0,0 +1,32 @@
+extends SummonBase
+class_name GoboSummon
+
+var percent: float = 0.0
+var healthSinceLastLaunch: float = 0
+
+func initHealth(maxHealth: float):
+ super.initHealth(maxHealth)
+ healthSinceLastLaunch = maxHealth
+
+func register():
+ fields[FieldStore.Entity.MOVEMENT_SPEED] = 1.5
+ healthChanged.connect(
+ func(newHealth):
+ if healthSinceLastLaunch - newHealth >= 1:
+ launch()
+ healthSinceLastLaunch = newHealth
+ )
+func ai():
+ var target = BulletTool.findClosetBulletCanDamage(position, get_tree(), self)
+ if is_instance_valid(target):
+ move(target.position - position)
+
+func launch():
+ for bullet in BulletBase.generate(
+ ComponentManager.getBullet("HealingMissle"),
+ self,
+ position,
+ position.angle_to_point(EntityTool.findClosetEntity(myMaster.position, get_tree(), isPlayer(), !isPlayer(), [self]).position)
+ ):
+ if bullet is HealingMissleBullet:
+ bullet.baseDamage = health * percent * -1
diff --git a/scripts/Contents/Summons/Gobo.gd.uid b/scripts/Contents/Summons/Gobo.gd.uid
new file mode 100644
index 0000000..ec06fc2
--- /dev/null
+++ b/scripts/Contents/Summons/Gobo.gd.uid
@@ -0,0 +1 @@
+uid://cqax27sw0wlme
diff --git a/scripts/Contents/Weapons/Gobo.gd b/scripts/Contents/Weapons/Gobo.gd
new file mode 100644
index 0000000..8d96fe7
--- /dev/null
+++ b/scripts/Contents/Weapons/Gobo.gd
@@ -0,0 +1,13 @@
+@tool
+extends Weapon
+
+func update(to: int, origin: Dictionary, _entity: EntityBase):
+ origin["health"] += 5 * to * soulLevel
+ origin["percent"] += 0.02 * to * soulLevel
+ return origin
+func attack(entity: EntityBase):
+ var gobo = entity.summon(ComponentManager.getSummon("Gobo"))
+ if gobo is GoboSummon:
+ gobo.percent = readStore("percent")
+ gobo.initHealth(readStore("health"))
+ return true
diff --git a/scripts/Contents/Weapons/Gobo.gd.uid b/scripts/Contents/Weapons/Gobo.gd.uid
new file mode 100644
index 0000000..a7aa8fb
--- /dev/null
+++ b/scripts/Contents/Weapons/Gobo.gd.uid
@@ -0,0 +1 @@
+uid://cbg3xkg1giv35
diff --git a/scripts/Contents/Weapons/Shield.gd b/scripts/Contents/Weapons/Shield.gd
index 35f14ad..53cbdca 100644
--- a/scripts/Contents/Weapons/Shield.gd
+++ b/scripts/Contents/Weapons/Shield.gd
@@ -7,7 +7,6 @@ func update(to: int, origin: Dictionary, _entity: EntityBase):
return origin
func attack(entity: EntityBase):
var summon = entity.summon(ComponentManager.getSummon("Shield"))
- if !summon: return true
- summon.fields[FieldStore.Entity.MAX_HEALTH] = readStore("atk")
- summon.health = summon.fields[FieldStore.Entity.MAX_HEALTH]
+ if summon:
+ summon.initHealth(readStore("atk"))
return true
diff --git a/scripts/Statemachine/BulletBase.gd b/scripts/Statemachine/BulletBase.gd
index 2495bcb..faa21b9 100644
--- a/scripts/Statemachine/BulletBase.gd
+++ b/scripts/Statemachine/BulletBase.gd
@@ -10,7 +10,7 @@ class_name BulletBase
@export var penerateDamageReduction: float = 0.0
@export var lifeDistance: float = -1 # -1表示无限距离
@export var lifeTime: float = -1 # -1表示无限时间
-@export var indisDamage: bool = false # 是否无差别伤害(不区分敌我)
+@export var allowFriendlyDamage: bool = false # 是否无差别伤害(不区分敌我)
@export var canDamageSelf: bool = false # 是否可以伤害发射者
@export var autoSpawnAnimation: bool = false
@export var autoLoopAnimation: bool = false
diff --git a/scripts/Statemachine/EntityBase.gd b/scripts/Statemachine/EntityBase.gd
index 9f9172d..f600f1e 100644
--- a/scripts/Statemachine/EntityBase.gd
+++ b/scripts/Statemachine/EntityBase.gd
@@ -200,6 +200,10 @@ func _physics_process(_delta: float) -> void:
trailParticle.emitting = trailing
# 通用方法
+func initHealth(maxHealth: float):
+ fields[FieldStore.Entity.MAX_HEALTH] = maxHealth
+ health = maxHealth
+ statebar.forceSync()
func rebuildWeaponIcons():
if isPlayer():
for i in UIState.skillIconContainer.get_children():
diff --git a/scripts/Tools/BulletTool.gd b/scripts/Tools/BulletTool.gd
index 1bfaa25..4fa0c13 100644
--- a/scripts/Tools/BulletTool.gd
+++ b/scripts/Tools/BulletTool.gd
@@ -8,9 +8,12 @@ static func fromArea(area: Area2D) -> BulletBase:
static func canDamage(bullet: BulletBase, target: EntityBase) -> bool:
if !bullet or !target or !bullet.launcher: return false
if target.currentInvinsible: return false
- if !bullet.canDamageSelf && target == bullet.launcher: return false
if !GameRule.allowFriendlyFire:
- if target.isPlayer() == bullet.launcher.isPlayer() and bullet.launcher.currentFocusedBoss != target: return false
+ if target.isPlayer() == bullet.launcher.isPlayer() and bullet.launcher.currentFocusedBoss != target and !bullet.allowFriendlyDamage:
+ return false
+ if !bullet.canDamageSelf:
+ if target == bullet.launcher:
+ return false
return true
static func findClosetBullet(to: Vector2, fromTree: SceneTree) -> BulletBase:
var result: BulletBase = null