diff --git a/data/itemRegs/magics.yaml b/data/itemRegs/magics.yaml
index 7b53fa5..c2c25eb 100644
--- a/data/itemRegs/magics.yaml
+++ b/data/itemRegs/magics.yaml
@@ -7,4 +7,8 @@
- id: necromancy
scene_path: res://prefab/magics/curseOfTheUndead.tscn
icon_path: res://sprites/projectile/curseOfTheUndead.png
+ max_stack_value: 1
+- id: x3
+ scene_path: res://prefab/magics/x3.tscn
+ icon_path: res://sprites/projectile/x3.png
max_stack_value: 1
\ No newline at end of file
diff --git a/locals/Item.csv b/locals/Item.csv
index ecdd158..2edbad0 100644
--- a/locals/Item.csv
+++ b/locals/Item.csv
@@ -4,4 +4,6 @@ item_staff_necromancy_desc,发射诅咒,可将敌人转化为邪恶的怪物
item_portable_backpacks,便携式背包,PortableBackpacks,ポータブルバックパック
item_portable_backpacks_desc,为玩家提供9个物品槽。,Provides 9 item slots for the player.,プレイヤーに9つのアイテムスロットを提供します。
item_necromancy,死灵法术,necromancy,ネクロマンシー
-item_necromancy_desc,法术的实体化弹丸。,The materialized projectile of a spell.,術の実体化した弾丸です。
\ No newline at end of file
+item_necromancy_desc,法术的实体化弹丸。,The materialized projectile of a spell.,術の実体化した弾丸です。
+item_x3,三重射击法术,Triple shot spell,三重射撃術です
+item_x3_desc,使发射器一次射出三颗弹丸。,Make the launcher shoot three pellets at a time.,発射機から一度に三つの弾丸を発射させます。
\ No newline at end of file
diff --git a/prefab/magics/x3.tscn b/prefab/magics/x3.tscn
new file mode 100644
index 0000000..2a136dd
--- /dev/null
+++ b/prefab/magics/x3.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=6 format=3 uid="uid://cg75t3fw5c6er"]
+
+[ext_resource type="Script" path="res://scripts/spell/MultipleFireSpell.cs" id="1_cnhod"]
+[ext_resource type="Texture2D" uid="uid://mb5yijtw7sw5" path="res://sprites/projectile/x3.png" id="3_b3s8h"]
+[ext_resource type="AudioStream" uid="uid://cak6chjjsu7wo" path="res://sounds/fire.wav" id="4_ffr2k"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_3eq4k"]
+size = Vector2(30, 30)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_i3lbq"]
+size = Vector2(30, 31)
+
+[node name="x3" type="RigidBody2D"]
+collision_layer = 8
+collision_mask = 34
+angular_damp = -1.0
+script = ExtResource("1_cnhod")
+
+[node name="DamageArea2D" type="Area2D" parent="."]
+collision_layer = 8
+collision_mask = 102
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea2D"]
+shape = SubResource("RectangleShape2D_3eq4k")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2(0, -0.5)
+shape = SubResource("RectangleShape2D_i3lbq")
+
+[node name="Marker2D" type="Marker2D" parent="."]
+position = Vector2(65, 0)
+
+[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="Marker2D"]
+stream = ExtResource("4_ffr2k")
+bus = &"SoundEffect"
+
+[node name="TipLabel" type="Label" parent="."]
+offset_left = -19.0
+offset_top = 23.0
+offset_right = 21.0
+offset_bottom = 48.0
+
+[node name="CurseOfTheUndead" type="Sprite2D" parent="."]
+
+[node name="X3" type="Sprite2D" parent="."]
+texture = ExtResource("3_b3s8h")
diff --git a/prefab/roomTemplates/dungeon/initialRoom.tscn b/prefab/roomTemplates/dungeon/initialRoom.tscn
index e44b0cf..c809ed2 100644
--- a/prefab/roomTemplates/dungeon/initialRoom.tscn
+++ b/prefab/roomTemplates/dungeon/initialRoom.tscn
@@ -45,6 +45,16 @@ position = Vector2(142, 84)
script = ExtResource("3_v1tlc")
ItemId = "staff_necromancy"
+[node name="ItemMarker2D3" type="Marker2D" parent="."]
+position = Vector2(214, 83)
+script = ExtResource("3_v1tlc")
+ItemId = "x3"
+
+[node name="ItemMarker2D4" type="Marker2D" parent="."]
+position = Vector2(366, 90)
+script = ExtResource("3_v1tlc")
+ItemId = "necromancy"
+
[node name="ItemMarker2D2" type="Marker2D" parent="."]
position = Vector2(321, 118)
script = ExtResource("3_v1tlc")
diff --git a/prefab/weapons/StaffNecromancy.tscn b/prefab/weapons/StaffNecromancy.tscn
index 044f4d2..1397579 100644
--- a/prefab/weapons/StaffNecromancy.tscn
+++ b/prefab/weapons/StaffNecromancy.tscn
@@ -17,6 +17,7 @@ collision_mask = 34
angular_damp = -1.0
script = ExtResource("1_w8hhv")
_numberSlots = 5
+_fireSequentially = true
FiringIntervalAsMillisecond = 300
_recoilStrength = 5
UniqueIcon = ExtResource("3_31iau")
diff --git a/scripts/projectile/ISpell.cs b/scripts/projectile/ISpell.cs
index 87b4186..dc00581 100644
--- a/scripts/projectile/ISpell.cs
+++ b/scripts/projectile/ISpell.cs
@@ -33,12 +33,23 @@ public interface ISpell
///
///
void RestoreWeapon(ProjectileWeapon projectileWeapon);
-
+
///
/// Modify the projectile
/// 修改抛射体
///
- ///
- void ModifyProjectile(Projectile projectile);
+ ///
+ ///What is the current projectile? For example, a weapon can fire three projectiles at once, with indexes 0,1,2
+ ///当前抛射体是第几个?例如:武器可一下发射3个抛射体,索引为0,1,2
+ ///
+ ///
+ ///Projectile object
+ ///抛射体对象
+ ///
+ ///
+ ///The velocity of the projectile
+ ///抛射体的飞行速度
+ ///
+ void ModifyProjectile(int index,Projectile projectile, ref Vector2 velocity);
}
\ No newline at end of file
diff --git a/scripts/spell/MultipleFireSpell.cs b/scripts/spell/MultipleFireSpell.cs
new file mode 100644
index 0000000..3d3dd4c
--- /dev/null
+++ b/scripts/spell/MultipleFireSpell.cs
@@ -0,0 +1,82 @@
+using ColdMint.scripts.projectile;
+using ColdMint.scripts.utils;
+using ColdMint.scripts.weapon;
+using Godot;
+
+namespace ColdMint.scripts.spell;
+
+///
+/// MultipleFireSpell
+/// 多重射击法术
+///
+///
+///Use this spell to create shotgun effects
+///通过此法术打造霰弹枪的效果
+///
+public partial class MultipleFireSpell : SpellPickAble
+{
+ ///
+ /// How many projectiles are generated per fire
+ /// 每次开火生成多少个抛射体
+ ///
+ [Export]
+ public int NumberOfProjectiles { get; set; } = 3;
+
+ ///
+ /// RandomAngle
+ /// 随机角度
+ ///
+ [Export]
+ public bool RandomAngle { get; set; }
+
+ ///
+ /// Unit radian
+ /// 单位弧度
+ ///
+ ///
+ ///Unit radian of correction for the projectile Angle.Suppose there are three bullets fired at once, and this is the arc between the two bullets.
+ ///对抛射体角度修正的单位弧度。假定有三颗子弹一次发射,这是两颗子弹之间的弧度。
+ ///
+ [Export]
+ public float UnitRadian { get; set; } = 0.069813f;
+
+ ///
+ /// initial Radian
+ /// 起始弧度
+ ///
+ ///
+ ///The Angle of the first bullet, and subsequent bullets will be offset in unit radians.
+ ///第一颗子弹的角度,随后的子弹会以单位弧度偏移。
+ ///
+ private float _initialRadian;
+ private float _maxRadian;
+ private int _oldNumberOfProjectiles;
+ public override void ModifyWeapon(ProjectileWeapon projectileWeapon)
+ {
+ base.ModifyWeapon(projectileWeapon);
+ _oldNumberOfProjectiles = projectileWeapon.NumberOfProjectiles;
+ projectileWeapon.NumberOfProjectiles = NumberOfProjectiles;
+ _initialRadian = -(NumberOfProjectiles / 2f * UnitRadian);
+ _maxRadian = NumberOfProjectiles * UnitRadian;
+ }
+
+ public override void RestoreWeapon(ProjectileWeapon projectileWeapon)
+ {
+ base.RestoreWeapon(projectileWeapon);
+ projectileWeapon.NumberOfProjectiles = _oldNumberOfProjectiles;
+ }
+
+ public override void ModifyProjectile(int index, Projectile projectile, ref Vector2 velocity)
+ {
+ base.ModifyProjectile(index, projectile, ref velocity);
+ if (RandomAngle)
+ {
+ velocity = velocity.Rotated(_initialRadian + _maxRadian * RandomUtils.Instance.NextSingle());
+ }
+ else
+ {
+ velocity = velocity.Rotated(_initialRadian + UnitRadian * index);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/scripts/spell/SpellPickAble.cs b/scripts/spell/SpellPickAble.cs
index 683407d..441d199 100644
--- a/scripts/spell/SpellPickAble.cs
+++ b/scripts/spell/SpellPickAble.cs
@@ -38,17 +38,17 @@ public partial class SpellPickAble : PickAbleTemplate, ISpell
return _projectileScene;
}
- public void ModifyWeapon(ProjectileWeapon projectileWeapon)
+ public virtual void ModifyWeapon(ProjectileWeapon projectileWeapon)
{
}
- public void RestoreWeapon(ProjectileWeapon projectileWeapon)
+ public virtual void RestoreWeapon(ProjectileWeapon projectileWeapon)
{
}
- public void ModifyProjectile(Projectile projectile)
+ public virtual void ModifyProjectile(int index, Projectile projectile, ref Vector2 velocity)
{
}
diff --git a/scripts/weapon/ProjectileWeapon.cs b/scripts/weapon/ProjectileWeapon.cs
index 35d13ab..5e0775c 100644
--- a/scripts/weapon/ProjectileWeapon.cs
+++ b/scripts/weapon/ProjectileWeapon.cs
@@ -216,36 +216,57 @@ public partial class ProjectileWeapon : WeaponTemplate
LogCat.LogError("projectile_scene_is_null");
return false;
}
- for (var i = spellScope[0]; i <= spellScope[1]; i++)
- {
- var spell = _spells[i];
- spell.ModifyWeapon(this);
- }
+ ModifyWeapon(spellScope);
for (var i = 0; i < NumberOfProjectiles; i++)
{
var projectile = NodeUtils.InstantiatePackedScene(packedScene);
if (projectile == null)
{
LogCat.LogError("projectile_is_null");
+ RestoreWeapon(spellScope);
return false;
}
+ var velocity = _marker2D.GlobalPosition.DirectionTo(enemyGlobalPosition) * projectile.Speed;
for (var s = spellScope[0]; s <= spellScope[1]; s++)
{
var spell = _spells[s];
- spell.ModifyProjectile(projectile);
+ spell.ModifyProjectile(i, projectile, ref velocity);
}
NodeUtils.CallDeferredAddChild(GameSceneDepend.ProjectileContainer, projectile);
projectile.Owner = owner;
projectile.TargetNode = GameSceneDepend.TemporaryTargetNode;
- projectile.Velocity =
- _marker2D.GlobalPosition.DirectionTo(enemyGlobalPosition) * projectile.Speed;
+ projectile.Velocity = velocity;
projectile.Position = _marker2D.GlobalPosition;
}
+ RestoreWeapon(spellScope);
+ return true;
+ }
+
+ ///
+ /// Modify weapon attributes
+ /// 修改武器属性
+ ///
+ ///
+ private void ModifyWeapon(int[] spellScope)
+ {
+ for (var i = spellScope[0]; i <= spellScope[1]; i++)
+ {
+ var spell = _spells[i];
+ spell.ModifyWeapon(this);
+ }
+ }
+
+ ///
+ /// Restores modifications to weapons
+ /// 恢复对武器的修改
+ ///
+ ///
+ private void RestoreWeapon(int[] spellScope)
+ {
for (var i = spellScope[0]; i <= spellScope[1]; i++)
{
var spell = _spells[i];
spell.RestoreWeapon(this);
}
- return true;
}
}
\ No newline at end of file
diff --git a/sprites/projectile/x3.png b/sprites/projectile/x3.png
new file mode 100644
index 0000000..cc78f71
Binary files /dev/null and b/sprites/projectile/x3.png differ
diff --git a/sprites/projectile/x3.png.import b/sprites/projectile/x3.png.import
new file mode 100644
index 0000000..9af0540
--- /dev/null
+++ b/sprites/projectile/x3.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mb5yijtw7sw5"
+path="res://.godot/imported/x3.png-096b2fc27f9da2412a5f72aad28677de.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://sprites/projectile/x3.png"
+dest_files=["res://.godot/imported/x3.png-096b2fc27f9da2412a5f72aad28677de.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