XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/DeathMod

fix: diversify sickle combat animations

b4b9f3f
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

15 个文件 +549 -132
Modified Items/BloodSickle.cs +6 -1
@@ -14,10 +14,15 @@ public class BloodSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.52f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<FrostSickle>();
16 16 public override int AlternateNextSickleType => ModContent.ItemType<DevilSickle>();
17 public override bool SupportsCharge => true;
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.Circular;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.BloodCharge;
18 19 public override int MaximumChargeFrames => 48;
19 20 public override Color SickleColor => new(235, 45, 80);
21 public override Color SickleSecondaryColor => new(255, 145, 175);
20 22 public override int SickleDustType => DustID.Blood;
23 public override float TrailWidth => 13f;
24 public override Vector2 HandleAnchor => new(0.13f, 0.94f);
25 public override float TextureRotationCorrection => 0.82f;
21 26
22 27 public override void SetDefaults()
23 28 {
Modified Items/DeathSickle.cs +1 -0
@@ -14,6 +14,7 @@ public class DeathSickle : NormalSickle
14 14 public override int PrimaryNextSickleType => ItemID.None;
15 15 public override int AlternateNextSickleType => ItemID.None;
16 16 public override bool UsesModernSwing => false;
17 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.None;
17 18 public override Color SickleColor => new(235, 35, 65);
18 19
19 20 public override void SetDefaults()
Modified Items/DevilSickle.cs +6 -2
@@ -14,10 +14,14 @@ public class DevilSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.7f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<SoulSickle>();
16 16 public override int AlternateNextSickleType => ModContent.ItemType<VoidSickle>();
17 public override bool SupportsCharge => true;
18 public override int MaximumChargeFrames => 62;
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.Rising;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.InfernalRush;
19 19 public override Color SickleColor => new(255, 125, 25);
20 public override Color SickleSecondaryColor => new(255, 235, 85);
20 21 public override int SickleDustType => DustID.Torch;
22 public override float TrailWidth => 16f;
23 public override Vector2 HandleAnchor => new(0.13f, 0.93f);
24 public override float TextureRotationCorrection => 0.79f;
21 25
22 26 public override void SetDefaults()
23 27 {
Modified Items/FrostSickle.cs +6 -1
@@ -14,10 +14,15 @@ public class FrostSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.65f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<VoidSickle>();
16 16 public override int AlternateNextSickleType => ModContent.ItemType<SoulSickle>();
17 public override bool SupportsCharge => true;
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.CrossCut;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.FrostCharge;
18 19 public override int MaximumChargeFrames => 58;
19 20 public override Color SickleColor => new(105, 195, 255);
21 public override Color SickleSecondaryColor => new(235, 255, 255);
20 22 public override int SickleDustType => DustID.IceTorch;
23 public override float TrailWidth => 14f;
24 public override Vector2 HandleAnchor => new(0.18f, 0.93f);
25 public override float TextureRotationCorrection => 0.80f;
21 26
22 27 public override void SetDefaults()
23 28 {
Modified Items/NormalSickle.cs +45 -11
@@ -13,6 +13,29 @@ using Terraria.ModLoader.IO;
13 13
14 14 namespace DeathMod.Items;
15 15
16 public enum SickleSwingStyle : byte
17 {
18 Diagonal,
19 Overhead,
20 Circular,
21 Rising,
22 CrossCut,
23 SoulOrbit,
24 VoidOrbit
25 }
26
27 public enum SickleAlternateAttackStyle : byte
28 {
29 None,
30 HarvestDash,
31 BoneRush,
32 BloodCharge,
33 InfernalRush,
34 FrostCharge,
35 SoulCharge,
36 VoidStep
37 }
38
16 39 public class NormalSickle : ModItem, IDeathAltarUpgradeable
17 40 {
18 41 public const int BaseUseTime = 25;
@@ -67,15 +90,24 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
67 90 public virtual int PrimaryNextSickleType => ModContent.ItemType<SkullSickle>();
68 91 public virtual int AlternateNextSickleType => ModContent.ItemType<BloodSickle>();
69 92 public virtual bool UsesModernSwing => true;
70 public virtual bool SupportsCharge => false;
93 public virtual SickleSwingStyle SwingStyle => SickleSwingStyle.Diagonal;
94 public virtual SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.HarvestDash;
71 95 public virtual int MaximumChargeFrames => 55 + SickleTier * 8;
72 96 public virtual int SickleReach => 74 + SickleTier * 10;
73 97 public virtual float SickleCollisionWidth => 18f + SickleTier * 3f;
74 98 public virtual int ChargedProjectileBonus => SickleTier >= 2 ? 2 : 1;
75 99 public virtual Color SickleColor => new(80, 205, 225);
100 public virtual Color SickleSecondaryColor => new(210, 250, 255);
76 101 public virtual int SickleDustType => DustID.AncientLight;
102 public virtual float TrailWidth => 8f;
103 public virtual Vector2 HandleAnchor => new(0.06f, 0.95f);
104 public virtual float TextureRotationCorrection => 0.98f;
77 105
78 106 public bool SupportsProjectileCount => AttackProjectileType > ProjectileID.None;
107 public bool HasAlternateAttack => AlternateAttackStyle != SickleAlternateAttackStyle.None;
108 public bool SupportsCharge => AlternateAttackStyle is SickleAlternateAttackStyle.BloodCharge
109 or SickleAlternateAttackStyle.FrostCharge
110 or SickleAlternateAttackStyle.SoulCharge;
79 111 public double DamageMultiplier => Math.Pow(4.2d, (DamageLevel - 1d) / (MaxDamageLevel - 1d));
80 112 public int ScaledBaseDamage => GetScaledDamage(DamageLevel);
81 113 public int AttackUseTime => BaseUseTime - (AttackSpeedLevel - 1);
@@ -105,7 +137,7 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
105 137 Item.autoReuse = true;
106 138 Item.noUseGraphic = UsesModernSwing;
107 139 Item.noMelee = UsesModernSwing;
108 Item.channel = false;
140 Item.channel = SupportsCharge;
109 141 Item.shoot = UsesModernSwing ? ModContent.ProjectileType<SickleSwingProjectile>() : AttackProjectileType;
110 142 Item.shootSpeed = AttackProjectileSpeed;
111 143 ApplyDynamicStats();
@@ -130,7 +162,7 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
130 162 knockback.Flat += KnockbackBonus;
131 163 }
132 164
133 public override bool AltFunctionUse(Player player) => UsesModernSwing && SupportsCharge;
165 public override bool AltFunctionUse(Player player) => UsesModernSwing && HasAlternateAttack;
134 166
135 167 public override bool CanUseItem(Player player)
136 168 {
@@ -140,10 +172,11 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
140 172 if (player.ownedProjectileCounts[ModContent.ProjectileType<SickleSwingProjectile>()] > 0)
141 173 return false;
142 174
143 bool charging = player.altFunctionUse == 2 && SupportsCharge;
175 bool alternateAttack = player.altFunctionUse == 2 && HasAlternateAttack;
176 bool charging = alternateAttack && SupportsCharge;
144 177 Item.channel = charging;
145 Item.useTime = charging ? 10 : BaseUseTime;
146 Item.useAnimation = charging ? 10 : BaseUseTime;
178 Item.useTime = alternateAttack ? 10 : BaseUseTime;
179 Item.useAnimation = alternateAttack ? 10 : BaseUseTime;
147 180 return true;
148 181 }
149 182
@@ -155,8 +188,8 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
155 188 return false;
156 189 }
157 190
158 bool chargedAttack = player.altFunctionUse == 2 && SupportsCharge;
159 int swingDirection = chargedAttack
191 bool alternateAttack = player.altFunctionUse == 2 && HasAlternateAttack;
192 int swingDirection = alternateAttack
160 193 ? player.direction
161 194 : player.GetModPlayer<MyPlayer>().TakeNextSwingDirection(player.direction);
162 195 Vector2 aim = velocity.SafeNormalize(Vector2.UnitX * player.direction);
@@ -169,7 +202,7 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
169 202 knockback,
170 203 player.whoAmI,
171 204 swingDirection,
172 chargedAttack ? 1f : 0f,
205 alternateAttack ? 1f : 0f,
173 206 Item.type);
174 207 return false;
175 208 }
@@ -198,9 +231,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
198 231 {
199 232 OverrideColor = GetGradientColor()
200 233 });
201 if (SupportsCharge)
234 if (HasAlternateAttack)
202 235 {
203 tooltips.Add(new TooltipLine(Mod, "DeathModCharge", Language.GetTextValue("Mods.DeathMod.UI.ChargeHint"))
236 string alternateHint = Language.GetTextValue($"Mods.DeathMod.UI.AlternateHints.{AlternateAttackStyle}");
237 tooltips.Add(new TooltipLine(Mod, "DeathModAlternateAttack", alternateHint)
204 238 {
205 239 OverrideColor = SickleColor
206 240 });
Modified Items/SkullSickle.cs +6 -0
@@ -14,8 +14,14 @@ public class SkullSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.58f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<DevilSickle>();
16 16 public override int AlternateNextSickleType => ModContent.ItemType<FrostSickle>();
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.Overhead;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.BoneRush;
17 19 public override Color SickleColor => new(80, 235, 255);
20 public override Color SickleSecondaryColor => new(235, 245, 220);
18 21 public override int SickleDustType => DustID.DungeonSpirit;
22 public override float TrailWidth => 10f;
23 public override Vector2 HandleAnchor => new(0.25f, 0.94f);
24 public override float TextureRotationCorrection => 0.97f;
19 25
20 26 public override void SetDefaults()
21 27 {
Modified Items/SoulSickle.cs +6 -1
@@ -14,10 +14,15 @@ public class SoulSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.82f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<DeathSickle>();
16 16 public override int AlternateNextSickleType => ItemID.None;
17 public override bool SupportsCharge => true;
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.SoulOrbit;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.SoulCharge;
18 19 public override int MaximumChargeFrames => 72;
19 20 public override Color SickleColor => new(120, 100, 255);
21 public override Color SickleSecondaryColor => new(75, 245, 255);
20 22 public override int SickleDustType => DustID.AncientLight;
23 public override float TrailWidth => 18f;
24 public override Vector2 HandleAnchor => new(0.20f, 0.93f);
25 public override float TextureRotationCorrection => 0.84f;
21 26
22 27 public override void SetDefaults()
23 28 {
Modified Items/VoidSickle.cs +6 -2
@@ -14,11 +14,15 @@ public class VoidSickle : NormalSickle
14 14 public override float ProjectileDamageFactor => 0.88f;
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<DeathSickle>();
16 16 public override int AlternateNextSickleType => ItemID.None;
17 public override bool SupportsCharge => true;
18 public override int MaximumChargeFrames => 78;
17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.VoidOrbit;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.VoidStep;
19 19 public override int ChargedProjectileBonus => 1;
20 20 public override Color SickleColor => new(190, 55, 255);
21 public override Color SickleSecondaryColor => new(255, 85, 235);
21 22 public override int SickleDustType => DustID.Shadowflame;
23 public override float TrailWidth => 20f;
24 public override Vector2 HandleAnchor => new(0.13f, 0.93f);
25 public override float TextureRotationCorrection => 0.89f;
22 26
23 27 public override void SetDefaults()
24 28 {
Modified Localization/en-US.hjson +13 -2
@@ -18,7 +18,7 @@ Mods: {
18 18
19 19 DevilSickle: {
20 20 DisplayName: Infernal Sickle
21 Tooltip: Releases accelerating molten crescents; supports a charged right-click heavy slash
21 Tooltip: Releases accelerating molten crescents; right-click for an instant rushing infernal spin
22 22 }
23 23
24 24 FrostSickle: {
@@ -33,7 +33,7 @@ Mods: {
33 33
34 34 VoidSickle: {
35 35 DisplayName: Void Sickle
36 Tooltip: Releases void arcs that grow into gravitational rifts; supports a charged right-click heavy slash
36 Tooltip: Releases void arcs that grow into gravitational rifts; right-click for a Void Step orbit slash
37 37 }
38 38
39 39 DeathSickle: {
@@ -114,6 +114,17 @@ Mods: {
114 114 Projectiles {2} | Armor penetration {3} | Life steal {4}% | Hit cooldown {7}f
115 115 '''
116 116 ChargeHint: Right-click to charge with a separate soul-gathering pose, then release a high-multiplier heavy slash and empowered projectile pattern
117
118 AlternateHints: {
119 HarvestDash: Right-click: instantly dash toward the cursor with a forward diagonal slash
120 BoneRush: Right-click: rush forward with a non-charged bone-wraith thrust
121 BloodCharge: Hold right-click to charge, then release a circular Blood Covenant execution
122 InfernalRush: Right-click: rush forward with a non-charged infernal spin
123 FrostCharge: Hold right-click to charge, then release a two-stage frost cross-cut
124 SoulCharge: Hold right-click to charge, then release a multi-orbit soul reaping slash
125 VoidStep: Right-click: perform a non-charged Void Step and surrounding rift slash
126 }
127
117 128 WingStats: Flight {0}f | Speed {1} | Acceleration {2} | Vertical power {3} | Infinite flight: {4}
118 129 RobeStats: Defense {0} | Life +{1} | Life regeneration +{2} | Debuff immunities {3}/{4}
119 130 AltarHint: Right-click a Death Altar to open its modification interface
Modified Localization/zh-Hans.hjson +13 -2
@@ -18,7 +18,7 @@ Mods: {
18 18
19 19 DevilSickle: {
20 20 DisplayName: 炼狱镰
21 Tooltip: 释放不断加速的熔火弧刃;支持右键蓄力重斩
21 Tooltip: 释放不断加速的熔火弧刃;右键施展无需蓄力的炼狱突进旋斩
22 22 }
23 23
24 24 FrostSickle: {
@@ -33,7 +33,7 @@ Mods: {
33 33
34 34 VoidSickle: {
35 35 DisplayName: 虚空镰刀
36 Tooltip: 释放成长为引力裂隙的虚空弧;支持右键蓄力重斩
36 Tooltip: 释放成长为引力裂隙的虚空弧;右键施展虚空踏步环斩
37 37 }
38 38
39 39 DeathSickle: {
@@ -114,6 +114,17 @@ Mods: {
114 114 弹幕 {2} | 穿甲 {3} | 吸血 {4}% | 伤害间隔 {7}帧
115 115 '''
116 116 ChargeHint: 右键蓄力:使用独立聚魂动作,松开后施展高倍率重斩与强化弹幕
117
118 AlternateHints: {
119 HarvestDash: 右键:立即向准星方向突进并施展前冲斜斩
120 BoneRush: 右键:无需蓄力,以亡骨镰向前突刺追魂
121 BloodCharge: 右键按住蓄力,松开后施展环身血契处决斩
122 InfernalRush: 右键:无需蓄力,突进并施展炼狱旋斩
123 FrostCharge: 右键按住蓄力,松开后施展两段冰魂交叉斩
124 SoulCharge: 右键按住蓄力,松开后施展多圈灵魂回旋斩
125 VoidStep: 右键:无需蓄力,施展虚空踏步与环身裂隙斩
126 }
127
117 128 WingStats: 飞行 {0}帧 | 速度 {1} | 加速度 {2} | 垂直能力 {3} | 无限飞行:{4}
118 129 RobeStats: 防御 {0} | 生命 +{1} | 生命回复 +{2} | 减益免疫 {3}/{4}
119 130 AltarHint: 在死神祭坛右键打开改造界面
Modified Projectiles/LifeStealWispProjectile.cs +28 -8
@@ -55,6 +55,7 @@ public class LifeStealWispProjectile : ModProjectile
55 55 Projectile.timeLeft = 90;
56 56 Projectile.penetrate = -1;
57 57 Projectile.netImportant = false;
58 Projectile.extraUpdates = 1;
58 59 }
59 60
60 61 public override void AI()
@@ -69,10 +70,14 @@ public class LifeStealWispProjectile : ModProjectile
69 70 Projectile.localAI[0]++;
70 71 float age = Projectile.localAI[0];
71 72 Vector2 toPlayer = player.Center - Projectile.Center;
72 float speed = MathHelper.Lerp(4f, 15f + LifeStealLevel, MathHelper.Clamp(age / 42f, 0f, 1f));
73 Vector2 desired = toPlayer.SafeNormalize(Vector2.UnitY) * speed;
74 float swirl = (float)System.Math.Sin(age * 0.24f + Projectile.ai[1]) * (0.16f - MathHelper.Clamp(age / 420f, 0f, 0.12f));
75 Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired.RotatedBy(swirl), 0.11f + LifeStealLevel * 0.012f);
73 float approach = MathHelper.Clamp(age / 58f, 0f, 1f);
74 float speed = MathHelper.Lerp(3.5f, 13f + LifeStealLevel, approach);
75 Vector2 radialDirection = toPlayer.SafeNormalize(Vector2.UnitY);
76 Vector2 tangent = radialDirection.RotatedBy(MathHelper.PiOver2)
77 * (float)System.Math.Sin(age * 0.2f + Projectile.ai[1])
78 * MathHelper.Lerp(5.5f + LifeStealLevel, 0.7f, approach);
79 Vector2 desired = radialDirection * speed + tangent;
80 Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.085f + LifeStealLevel * 0.01f);
76 81 Projectile.rotation = Projectile.velocity.ToRotation();
77 82
78 83 Color color = Color.Lerp(new Color(215, 20, 45), new Color(255, 105, 195), LifeStealLevel / 5f);
@@ -83,7 +88,7 @@ public class LifeStealWispProjectile : ModProjectile
83 88 dust.noGravity = true;
84 89 }
85 90
86 if (toPlayer.LengthSquared() > 18f * 18f)
91 if (toPlayer.LengthSquared() > 16f * 16f)
87 92 return;
88 93
89 94 for (int index = 0; index < 3 + LifeStealLevel * 2; index++)
@@ -102,10 +107,25 @@ public class LifeStealWispProjectile : ModProjectile
102 107 for (int index = trailLength; index >= 1; index--)
103 108 {
104 109 Vector2 position = Projectile.oldPos[index] + Projectile.Size / 2f - Main.screenPosition;
105 float scale = (trailLength - index + 1f) / trailLength;
106 Main.EntitySpriteDraw(pixel, position, null, color * scale * 0.55f, Projectile.rotation, new Vector2(0.5f), new Vector2(5f + LifeStealLevel, 2f) * scale, SpriteEffects.None);
110 float strength = (trailLength - index + 1f) / trailLength;
111 float size = (1.6f + LifeStealLevel * 0.34f) * strength;
112 Main.EntitySpriteDraw(pixel, position, null, color * strength * 0.48f,
113 MathHelper.PiOver4 + index * 0.18f, new Vector2(0.5f), new Vector2(size), SpriteEffects.None);
114 if (LifeStealLevel >= 3 && index % 2 == 0)
115 {
116 Vector2 orbitOffset = (Projectile.ai[1] + index * 1.7f).ToRotationVector2() * (2f + LifeStealLevel * 0.45f);
117 Main.EntitySpriteDraw(pixel, position + orbitOffset, null, Color.White * strength * 0.32f,
118 MathHelper.PiOver4, new Vector2(0.5f), new Vector2(size * 0.45f), SpriteEffects.None);
119 }
107 120 }
108 Main.EntitySpriteDraw(pixel, Projectile.Center - Main.screenPosition, null, color * 0.95f, Projectile.rotation, new Vector2(0.5f), new Vector2(7f + LifeStealLevel * 1.4f, 3f + LifeStealLevel * 0.5f), SpriteEffects.None);
121
122 Vector2 center = Projectile.Center - Main.screenPosition;
123 float pulse = 1f + (float)System.Math.Sin(Projectile.localAI[0] * 0.24f) * 0.16f;
124 float coreSize = (3.5f + LifeStealLevel * 0.65f) * pulse;
125 Main.EntitySpriteDraw(pixel, center, null, color * 0.9f, MathHelper.PiOver4,
126 new Vector2(0.5f), new Vector2(coreSize), SpriteEffects.None);
127 Main.EntitySpriteDraw(pixel, center, null, Color.White * 0.72f, -MathHelper.PiOver4,
128 new Vector2(0.5f), new Vector2(coreSize * 0.38f), SpriteEffects.None);
109 129 return false;
110 130 }
111 131 }
Modified Projectiles/SickleSwingProjectile.cs +391 -88
@@ -2,6 +2,7 @@ using DeathMod.Items;
2 2 using Microsoft.Xna.Framework;
3 3 using Microsoft.Xna.Framework.Graphics;
4 4 using System;
5 using System.IO;
5 6 using Terraria;
6 7 using Terraria.Audio;
7 8 using Terraria.GameContent;
@@ -12,24 +13,33 @@ namespace DeathMod.Projectiles;
12 13
13 14 public class SickleSwingProjectile : ModProjectile
14 15 {
15 private const int HistoryLength = 12;
16 private const int HistoryLength = 16;
16 17
17 18 private readonly float[] angleHistory = new float[HistoryLength];
19 private readonly Vector2[] gripHistory = new Vector2[HistoryLength];
20 private readonly bool[] flipHistory = new bool[HistoryLength];
18 21 private float aimRotation;
19 22 private float weaponAngle;
20 23 private float chargeRatio;
24 private Vector2 gripPosition;
21 25 private int chargeTimer;
22 26 private int swingTimer;
27 private int actionSegment;
28 private int historySegment = -1;
29 private bool weaponFlipped;
23 30 private bool initialized;
24 31 private bool spawnedAttack;
25 32 private bool fullChargeFeedback;
33 private bool dashApplied;
34 private bool alternateDamageApplied;
35 private bool canDamage;
26 36
27 37 public override string Texture => "Terraria/Images/Projectile_0";
28 38
29 39 private int ItemType => (int)Projectile.ai[2];
30 40 private int SwingDirection => Math.Sign(Projectile.ai[0]) == 0 ? 1 : Math.Sign(Projectile.ai[0]);
31 private bool IsCharging => Projectile.ai[1] == 1f;
32 private bool IsHeavySlash => Projectile.ai[1] == 2f;
41 private bool IsAlternateAttack => Projectile.ai[1] >= 1f;
42 private bool IsReleasedCharge => Projectile.ai[1] == 2f;
33 43
34 44 public override void SetDefaults()
35 45 {
@@ -50,6 +60,20 @@ public class SickleSwingProjectile : ModProjectile
50 60
51 61 public override bool ShouldUpdatePosition() => false;
52 62
63 public override bool? CanDamage() => canDamage ? null : false;
64
65 public override void SendExtraAI(BinaryWriter writer)
66 {
67 writer.Write(chargeRatio);
68 writer.Write((byte)actionSegment);
69 }
70
71 public override void ReceiveExtraAI(BinaryReader reader)
72 {
73 chargeRatio = MathHelper.Clamp(reader.ReadSingle(), 0f, 1f);
74 actionSegment = reader.ReadByte();
75 }
76
53 77 public override void AI()
54 78 {
55 79 if (!TryGetSickle(out Player player, out NormalSickle sickle))
@@ -59,13 +83,7 @@ public class SickleSwingProjectile : ModProjectile
59 83 }
60 84
61 85 if (!initialized)
62 {
63 initialized = true;
64 aimRotation = Projectile.velocity.SafeNormalize(Vector2.UnitX * player.direction).ToRotation();
65 weaponAngle = aimRotation;
66 Array.Fill(angleHistory, weaponAngle);
67 SoundEngine.PlaySound(IsCharging ? SoundID.Item15 : SoundID.Item1, player.Center);
68 }
86 InitializeSwing(player, sickle);
69 87
70 88 Projectile.timeLeft = 2;
71 89 Projectile.Center = player.MountedCenter;
@@ -73,32 +91,35 @@ public class SickleSwingProjectile : ModProjectile
73 91 player.itemTime = 2;
74 92 player.itemAnimation = 2;
75 93 player.ChangeDir(Projectile.velocity.X >= 0f ? 1 : -1);
94 gripPosition = player.MountedCenter;
95 canDamage = false;
76 96
77 if (IsCharging)
97 if (!IsAlternateAttack)
98 UpdatePrimarySlash(player, sickle);
99 else if (sickle.SupportsCharge && !IsReleasedCharge)
78 100 UpdateCharge(player, sickle);
101 else if (sickle.SupportsCharge)
102 UpdateChargedSlash(player, sickle);
79 103 else
80 UpdateSlash(player, sickle, IsHeavySlash);
104 UpdateInstantAlternate(player, sickle);
81 105
82 PushAngleHistory();
106 UpdateHistory();
83 107 UpdatePlayerArm(player);
84 108 SpawnMotionEffects(player, sickle);
85 109 }
86 110
87 111 public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
88 112 {
89 if (IsCharging || !TryGetSickle(out Player player, out NormalSickle sickle))
113 if (!canDamage || !TryGetSickle(out _, out NormalSickle sickle))
90 114 return false;
91 115
92 Vector2 start = player.MountedCenter + weaponAngle.ToRotationVector2() * 10f;
93 Vector2 end = player.MountedCenter + weaponAngle.ToRotationVector2() * sickle.SickleReach;
116 Vector2 start = gripPosition + weaponAngle.ToRotationVector2() * 8f;
117 Vector2 end = gripPosition + weaponAngle.ToRotationVector2() * sickle.SickleReach;
94 118 float collisionPoint = 0f;
119 float widthMultiplier = IsAlternateAttack ? 1.35f : 1f;
95 120 return Collision.CheckAABBvLineCollision(
96 targetHitbox.TopLeft(),
97 targetHitbox.Size(),
98 start,
99 end,
100 sickle.SickleCollisionWidth * (IsHeavySlash ? 1.4f : 1f),
101 ref collisionPoint);
121 targetHitbox.TopLeft(), targetHitbox.Size(), start, end,
122 sickle.SickleCollisionWidth * widthMultiplier, ref collisionPoint);
102 123 }
103 124
104 125 public override bool PreDraw(ref Color lightColor)
@@ -106,65 +127,127 @@ public class SickleSwingProjectile : ModProjectile
106 127 if (!TryGetSickle(out Player player, out NormalSickle sickle))
107 128 return false;
108 129
130 bool activelyCharging = IsAlternateAttack && sickle.SupportsCharge && !IsReleasedCharge;
131 if (activelyCharging)
132 DrawChargeRing(player, sickle);
133 else
134 DrawSlashTrail(sickle);
135
109 136 Texture2D texture = TextureAssets.Item[ItemType].Value;
110 Vector2 origin = new(texture.Width * 0.10f, texture.Height * 0.90f);
111 float textureAxisCorrection = 0.92f;
112 float scale = 0.82f + sickle.SickleTier * 0.075f + (IsHeavySlash ? chargeRatio * 0.12f : 0f);
113 int trailCount = Math.Min(HistoryLength - 1, 2 + sickle.SickleTier * 2 + (IsHeavySlash ? 2 : 0));
137 float scale = 0.82f + sickle.SickleTier * 0.075f + (IsReleasedCharge ? chargeRatio * 0.12f : 0f);
138 int afterimageCount = Math.Min(HistoryLength - 1, 2 + sickle.SickleTier * 2 + (IsAlternateAttack ? 2 : 0));
114 139
115 for (int index = trailCount; index >= 1; index--)
140 for (int index = afterimageCount; index >= 1; index--)
116 141 {
117 float opacity = (trailCount - index + 1f) / (trailCount + 1f) * 0.28f;
118 Color trailColor = sickle.SickleColor with { A = 0 };
119 Main.EntitySpriteDraw(
120 texture,
121 player.MountedCenter - Main.screenPosition,
122 null,
123 trailColor * opacity,
124 angleHistory[index] + textureAxisCorrection,
125 origin,
126 scale * (1f + index * 0.012f),
127 SpriteEffects.None);
142 float opacity = (afterimageCount - index + 1f) / (afterimageCount + 1f) * 0.22f;
143 DrawWeapon(texture, sickle, gripHistory[index], angleHistory[index], flipHistory[index],
144 sickle.SickleColor with { A = 0 }, opacity, scale * (1f + index * 0.01f));
128 145 }
129 146
130 if (IsCharging)
131 DrawChargeRing(player, sickle);
132
133 Color auraColor = sickle.SickleColor with { A = 0 };
134 147 int auraCopies = 1 + sickle.SickleTier / 2;
135 148 for (int index = 0; index < auraCopies; index++)
136 149 {
137 150 float angle = Main.GlobalTimeWrappedHourly * 4f + index * MathHelper.TwoPi / auraCopies;
138 151 Vector2 offset = angle.ToRotationVector2() * (1.5f + chargeRatio * 2.5f);
139 Main.EntitySpriteDraw(
140 texture,
141 player.MountedCenter + offset - Main.screenPosition,
142 null,
143 auraColor * (0.16f + chargeRatio * 0.12f),
144 weaponAngle + textureAxisCorrection,
145 origin,
146 scale,
147 SpriteEffects.None);
152 DrawWeapon(texture, sickle, gripPosition + offset, weaponAngle, weaponFlipped,
153 sickle.SickleColor with { A = 0 }, 0.13f + chargeRatio * 0.1f, scale);
148 154 }
149 155
150 Main.EntitySpriteDraw(
151 texture,
152 player.MountedCenter - Main.screenPosition,
153 null,
154 lightColor,
155 weaponAngle + textureAxisCorrection,
156 origin,
157 scale,
158 SpriteEffects.None);
156 DrawWeapon(texture, sickle, gripPosition, weaponAngle, weaponFlipped, lightColor, 1f, scale);
159 157 return false;
160 158 }
161 159
160 private void InitializeSwing(Player player, NormalSickle sickle)
161 {
162 initialized = true;
163 aimRotation = Projectile.velocity.SafeNormalize(Vector2.UnitX * player.direction).ToRotation();
164 weaponAngle = aimRotation;
165 gripPosition = player.MountedCenter;
166 weaponFlipped = SwingDirection < 0;
167 ResetHistory();
168
169 SoundStyle sound = !IsAlternateAttack
170 ? SoundID.Item1
171 : sickle.SupportsCharge
172 ? SoundID.Item15
173 : sickle.AlternateAttackStyle switch
174 {
175 SickleAlternateAttackStyle.BoneRush => SoundID.Item71,
176 SickleAlternateAttackStyle.InfernalRush => SoundID.Item74,
177 SickleAlternateAttackStyle.VoidStep => SoundID.Item8,
178 _ => SoundID.Item71
179 };
180 SoundEngine.PlaySound(sound, player.Center);
181 }
182
183 private void UpdatePrimarySlash(Player player, NormalSickle sickle)
184 {
185 swingTimer++;
186 float durationMultiplier = sickle.SwingStyle switch
187 {
188 SickleSwingStyle.Overhead => 1.08f,
189 SickleSwingStyle.Circular => 1.24f,
190 SickleSwingStyle.Rising => 1.12f,
191 SickleSwingStyle.CrossCut => 1.26f,
192 SickleSwingStyle.SoulOrbit => 1.36f,
193 SickleSwingStyle.VoidOrbit => 1.45f,
194 _ => 1f
195 };
196 int duration = Math.Max(12, (int)Math.Round(sickle.AttackUseTime * durationMultiplier));
197 float progress = MathHelper.Clamp(swingTimer / (float)duration, 0f, 1f);
198
199 switch (sickle.SwingStyle)
200 {
201 case SickleSwingStyle.Overhead:
202 SetArc(aimRotation, -1.72f, 1.02f, SmoothStep(progress), SwingDirection, 0);
203 gripPosition += new Vector2(0f, -3f);
204 break;
205 case SickleSwingStyle.Circular:
206 SetArc(aimRotation, -2.85f, 2.35f, SmoothStep(progress), SwingDirection, 0);
207 break;
208 case SickleSwingStyle.Rising:
209 SetArc(aimRotation, 2.18f, -1.42f, EaseOutSine(progress), SwingDirection, 0);
210 gripPosition += aimRotation.ToRotationVector2().RotatedBy(MathHelper.PiOver2 * SwingDirection) * 3f;
211 break;
212 case SickleSwingStyle.CrossCut:
213 UpdateCrossCut(progress, aimRotation, SwingDirection, false);
214 break;
215 case SickleSwingStyle.SoulOrbit:
216 SetArc(aimRotation, -3.05f, 2.72f, SmoothStep(progress), SwingDirection, 0);
217 gripPosition += weaponAngle.ToRotationVector2().RotatedBy(MathHelper.PiOver2) * (float)Math.Sin(progress * MathHelper.Pi) * 5f;
218 break;
219 case SickleSwingStyle.VoidOrbit:
220 SetArc(aimRotation, -MathHelper.Pi, MathHelper.Pi, SmoothStep(progress), SwingDirection, 0);
221 gripPosition += aimRotation.ToRotationVector2() * (float)Math.Sin(progress * MathHelper.Pi) * 6f;
222 break;
223 default:
224 SetArc(aimRotation, -2.08f, 1.2f, EaseOutCubic(progress), SwingDirection, 0);
225 break;
226 }
227
228 canDamage = progress is > 0.1f and < 0.96f;
229 float releasePoint = sickle.SwingStyle is SickleSwingStyle.Circular or SickleSwingStyle.SoulOrbit ? 0.48f : 0.38f;
230 TrySpawnAttack(player, sickle, progress, releasePoint, false);
231 if (progress >= 1f)
232 Projectile.Kill();
233 }
234
162 235 private void UpdateCharge(Player player, NormalSickle sickle)
163 236 {
164 237 chargeTimer++;
165 238 chargeRatio = MathHelper.Clamp(chargeTimer / (float)sickle.MaximumChargeFrames, 0f, 1f);
166 239 float breathe = (float)Math.Sin(chargeTimer * 0.12f) * (0.045f + chargeRatio * 0.035f);
167 weaponAngle = aimRotation - player.direction * MathHelper.Lerp(1.05f, 1.35f, chargeRatio) + breathe;
240 float poseOffset = sickle.AlternateAttackStyle switch
241 {
242 SickleAlternateAttackStyle.BloodCharge => 1.22f,
243 SickleAlternateAttackStyle.FrostCharge => 0.9f,
244 SickleAlternateAttackStyle.SoulCharge => 1.52f,
245 _ => 1.1f
246 };
247 weaponAngle = aimRotation - player.direction * MathHelper.Lerp(poseOffset, poseOffset + 0.28f, chargeRatio) + breathe;
248 weaponFlipped = player.direction < 0;
249 gripPosition -= aimRotation.ToRotationVector2() * (5f + chargeRatio * 4f);
250 actionSegment = 0;
168 251
169 252 if (chargeRatio >= 1f && !fullChargeFeedback)
170 253 {
@@ -178,7 +261,11 @@ public class SickleSwingProjectile : ModProjectile
178 261 }
179 262 }
180 263
181 if (player.channel && !player.noItems && !player.CCed)
264 if (Projectile.owner != Main.myPlayer)
265 return;
266
267 bool holdingAlternateUse = player.channel || player.controlUseTile || Main.mouseRight;
268 if (chargeTimer <= 8 || holdingAlternateUse && !player.noItems && !player.CCed)
182 269 return;
183 270
184 271 Projectile.ai[1] = 2f;
@@ -186,35 +273,149 @@ public class SickleSwingProjectile : ModProjectile
186 273 spawnedAttack = false;
187 274 Projectile.damage = Math.Max(1, (int)(Projectile.originalDamage * MathHelper.Lerp(1.35f, 2.45f, chargeRatio)));
188 275 Projectile.netUpdate = true;
189 SoundEngine.PlaySound(SoundID.Item71, player.Center);
276 SoundEngine.PlaySound(sickle.AlternateAttackStyle == SickleAlternateAttackStyle.FrostCharge ? SoundID.Item28 : SoundID.Item71, player.Center);
277 }
278
279 private void UpdateChargedSlash(Player player, NormalSickle sickle)
280 {
281 swingTimer++;
282 int duration = sickle.AlternateAttackStyle switch
283 {
284 SickleAlternateAttackStyle.BloodCharge => Math.Max(30, (int)Math.Round(sickle.AttackUseTime * 1.8f)),
285 SickleAlternateAttackStyle.FrostCharge => Math.Max(32, (int)Math.Round(sickle.AttackUseTime * 1.9f)),
286 SickleAlternateAttackStyle.SoulCharge => Math.Max(38, (int)Math.Round(sickle.AttackUseTime * 2.15f)),
287 _ => 30
288 };
289 float progress = MathHelper.Clamp(swingTimer / (float)duration, 0f, 1f);
290
291 switch (sickle.AlternateAttackStyle)
292 {
293 case SickleAlternateAttackStyle.FrostCharge:
294 UpdateCrossCut(progress, aimRotation, player.direction, true);
295 gripPosition += new Vector2(0f, -2f);
296 break;
297 case SickleAlternateAttackStyle.SoulCharge:
298 SetArc(aimRotation, -3.35f, 5.05f, SmoothStep(progress), player.direction, 0);
299 gripPosition += weaponAngle.ToRotationVector2().RotatedBy(MathHelper.PiOver2) * (6f + chargeRatio * 4f) * (float)Math.Sin(progress * MathHelper.Pi);
300 break;
301 default:
302 SetArc(aimRotation, -3.0f, 2.62f, SmoothStep(SmoothStep(progress)), player.direction, 0);
303 gripPosition += aimRotation.ToRotationVector2() * (float)Math.Sin(progress * MathHelper.Pi) * 5f;
304 break;
305 }
306
307 canDamage = progress is > 0.08f and < 0.97f;
308 float releasePoint = sickle.AlternateAttackStyle == SickleAlternateAttackStyle.SoulCharge ? 0.58f : 0.5f;
309 TrySpawnAttack(player, sickle, progress, releasePoint, true);
310 if (progress >= 1f)
311 Projectile.Kill();
190 312 }
191 313
192 private void UpdateSlash(Player player, NormalSickle sickle, bool heavySlash)
314 private void UpdateInstantAlternate(Player player, NormalSickle sickle)
193 315 {
194 316 swingTimer++;
195 int duration = heavySlash
196 ? Math.Max(26, (int)Math.Round(sickle.AttackUseTime * 1.65f))
197 : sickle.AttackUseTime;
317 int duration = sickle.AlternateAttackStyle switch
318 {
319 SickleAlternateAttackStyle.HarvestDash => 20,
320 SickleAlternateAttackStyle.BoneRush => 24,
321 SickleAlternateAttackStyle.InfernalRush => 30,
322 SickleAlternateAttackStyle.VoidStep => 34,
323 _ => 22
324 };
198 325 float progress = MathHelper.Clamp(swingTimer / (float)duration, 0f, 1f);
199 float easedProgress = heavySlash
200 ? SmoothStep(SmoothStep(progress))
201 : 1f - (float)Math.Pow(1f - progress, 3f);
202 float startAngle = aimRotation - SwingDirection * (heavySlash ? 2.85f : 2.05f);
203 float endAngle = aimRotation + SwingDirection * (heavySlash ? 1.55f : 1.2f);
204 weaponAngle = MathHelper.Lerp(startAngle, endAngle, easedProgress);
205
206 float releasePoint = heavySlash ? 0.52f : 0.38f;
207 if (!spawnedAttack && progress >= releasePoint)
326
327 if (!alternateDamageApplied)
208 328 {
209 spawnedAttack = true;
210 if (Projectile.owner == Main.myPlayer)
211 sickle.SpawnAttackProjectiles(player, Projectile, chargeRatio, heavySlash);
329 alternateDamageApplied = true;
330 float multiplier = sickle.AlternateAttackStyle switch
331 {
332 SickleAlternateAttackStyle.BoneRush => 1.2f,
333 SickleAlternateAttackStyle.InfernalRush => 1.48f,
334 SickleAlternateAttackStyle.VoidStep => 1.65f,
335 _ => 1.25f
336 };
337 Projectile.damage = Math.Max(1, (int)(Projectile.originalDamage * multiplier));
212 338 }
213 339
340 switch (sickle.AlternateAttackStyle)
341 {
342 case SickleAlternateAttackStyle.BoneRush:
343 {
344 float lunge = (float)Math.Sin(progress * MathHelper.Pi);
345 weaponAngle = aimRotation + (float)Math.Sin(progress * MathHelper.TwoPi) * 0.2f;
346 weaponFlipped = player.direction < 0;
347 gripPosition += aimRotation.ToRotationVector2() * lunge * 18f;
348 actionSegment = progress < 0.5f ? 0 : 1;
349 ApplyDash(player, 5.5f, 0.14f);
350 break;
351 }
352 case SickleAlternateAttackStyle.InfernalRush:
353 SetArc(aimRotation, -2.8f, 4.1f, SmoothStep(progress), player.direction, 0);
354 gripPosition += aimRotation.ToRotationVector2() * (float)Math.Sin(progress * MathHelper.Pi) * 12f;
355 ApplyDash(player, 8f, 0.1f);
356 break;
357 case SickleAlternateAttackStyle.VoidStep:
358 SetArc(aimRotation, -3.2f, 6.15f, SmoothStep(progress), player.direction, progress < 0.48f ? 0 : 1);
359 gripPosition += aimRotation.ToRotationVector2() * (float)Math.Sin(progress * MathHelper.Pi) * 16f;
360 ApplyDash(player, 10f, 0.08f);
361 break;
362 default:
363 SetArc(aimRotation, -2.5f, 1.5f, EaseOutCubic(progress), player.direction, 0);
364 gripPosition += aimRotation.ToRotationVector2() * (float)Math.Sin(progress * MathHelper.Pi) * 10f;
365 ApplyDash(player, 6.5f, 0.12f);
366 break;
367 }
368
369 canDamage = progress is > 0.08f and < 0.94f;
370 TrySpawnAttack(player, sickle, progress, 0.42f, true);
214 371 if (progress >= 1f)
215 372 Projectile.Kill();
216 373 }
217 374
375 private void UpdateCrossCut(float progress, float centerAngle, int direction, bool charged)
376 {
377 float split = charged ? 0.48f : 0.54f;
378 if (progress < split)
379 {
380 float segmentProgress = SmoothStep(progress / split);
381 SetArc(centerAngle, charged ? -2.72f : -2.25f, charged ? 1.28f : 0.82f, segmentProgress, direction, 0);
382 }
383 else
384 {
385 float segmentProgress = SmoothStep((progress - split) / (1f - split));
386 SetArc(centerAngle, charged ? 2.58f : 2.18f, charged ? -1.5f : -0.92f, segmentProgress, direction, 1);
387 }
388 }
389
390 private void SetArc(float centerAngle, float startOffset, float endOffset, float progress, int direction, int segment)
391 {
392 float start = centerAngle + startOffset * direction;
393 float end = centerAngle + endOffset * direction;
394 weaponAngle = MathHelper.Lerp(start, end, progress);
395 weaponFlipped = end < start;
396 actionSegment = segment;
397 }
398
399 private void ApplyDash(Player player, float speed, float verticalDamping)
400 {
401 if (dashApplied || Projectile.owner != Main.myPlayer)
402 return;
403
404 dashApplied = true;
405 Vector2 direction = aimRotation.ToRotationVector2();
406 player.velocity = new Vector2(direction.X * speed, player.velocity.Y * verticalDamping + direction.Y * speed * 0.45f);
407 }
408
409 private void TrySpawnAttack(Player player, NormalSickle sickle, float progress, float releasePoint, bool empowered)
410 {
411 if (spawnedAttack || progress < releasePoint)
412 return;
413
414 spawnedAttack = true;
415 if (Projectile.owner == Main.myPlayer)
416 sickle.SpawnAttackProjectiles(player, Projectile, chargeRatio, empowered);
417 }
418
218 419 private void UpdatePlayerArm(Player player)
219 420 {
220 421 float armRotation = weaponAngle - MathHelper.PiOver2;
@@ -224,21 +425,98 @@ public class SickleSwingProjectile : ModProjectile
224 425
225 426 private void SpawnMotionEffects(Player player, NormalSickle sickle)
226 427 {
227 Vector2 bladeTip = player.MountedCenter + weaponAngle.ToRotationVector2() * sickle.SickleReach;
228 float intensity = IsCharging ? 0.3f + chargeRatio : 0.65f + sickle.SickleTier * 0.2f;
428 Vector2 bladeTip = gripPosition + weaponAngle.ToRotationVector2() * sickle.SickleReach;
429 bool charging = IsAlternateAttack && sickle.SupportsCharge && !IsReleasedCharge;
430 float intensity = charging ? 0.3f + chargeRatio : 0.65f + sickle.SickleTier * 0.2f;
229 431 Lighting.AddLight(bladeTip, sickle.SickleColor.ToVector3() * intensity * 0.45f);
230 432
231 433 int chance = Math.Max(1, 5 - sickle.SickleTier);
232 434 if (!Main.rand.NextBool(chance))
233 435 return;
234 436
235 Vector2 velocity = IsCharging
437 Vector2 velocity = charging
236 438 ? (bladeTip - player.MountedCenter).SafeNormalize(Vector2.UnitY).RotatedBy(MathHelper.PiOver2) * Main.rand.NextFloat(-1.2f, 1.2f)
237 : weaponAngle.ToRotationVector2().RotatedBy(MathHelper.PiOver2 * SwingDirection) * Main.rand.NextFloat(0.6f, 2.4f);
439 : weaponAngle.ToRotationVector2().RotatedBy(weaponFlipped ? -MathHelper.PiOver2 : MathHelper.PiOver2) * Main.rand.NextFloat(0.6f, 2.4f);
238 440 Dust dust = Dust.NewDustPerfect(bladeTip, sickle.SickleDustType, velocity, 90, sickle.SickleColor, 0.75f + sickle.SickleTier * 0.12f);
239 441 dust.noGravity = true;
240 442 }
241 443
444 private void DrawSlashTrail(NormalSickle sickle)
445 {
446 Texture2D pixel = TextureAssets.MagicPixel.Value;
447 int segmentCount = Math.Min(HistoryLength - 1, 7 + sickle.SickleTier * 2);
448 float empoweredScale = IsAlternateAttack ? 1.25f : 1f;
449
450 for (int index = segmentCount - 1; index >= 0; index--)
451 {
452 if (gripHistory[index] == Vector2.Zero || gripHistory[index + 1] == Vector2.Zero)
453 continue;
454 if (sickle.SwingStyle is SickleSwingStyle.Overhead or SickleSwingStyle.CrossCut && index % 3 == 2)
455 continue;
456
457 Vector2 currentTip = gripHistory[index] + angleHistory[index].ToRotationVector2() * sickle.SickleReach;
458 Vector2 oldTip = gripHistory[index + 1] + angleHistory[index + 1].ToRotationVector2() * sickle.SickleReach;
459 Vector2 difference = oldTip - currentTip;
460 float length = difference.Length();
461 if (length < 0.5f || length > sickle.SickleReach * 1.2f)
462 continue;
463
464 float strength = 1f - index / (float)segmentCount;
465 float width = sickle.TrailWidth * strength * empoweredScale;
466 if (sickle.SwingStyle == SickleSwingStyle.Rising)
467 width *= 0.8f + strength * 0.65f;
468 if (sickle.SwingStyle == SickleSwingStyle.VoidOrbit)
469 width *= 1f + (float)Math.Sin(index * 1.7f + Main.GlobalTimeWrappedHourly * 8f) * 0.18f;
470
471 DrawTrailSegment(pixel, currentTip, difference, sickle.SickleColor with { A = 0 }, width + 4f, strength * 0.34f);
472 DrawTrailSegment(pixel, currentTip, difference, sickle.SickleSecondaryColor with { A = 0 }, width * 0.42f, strength * 0.62f);
473
474 if (sickle.SwingStyle == SickleSwingStyle.Circular)
475 {
476 Vector2 innerCurrent = gripHistory[index] + angleHistory[index].ToRotationVector2() * (sickle.SickleReach * 0.82f);
477 Vector2 innerOld = gripHistory[index + 1] + angleHistory[index + 1].ToRotationVector2() * (sickle.SickleReach * 0.82f);
478 DrawTrailSegment(pixel, innerCurrent, innerOld - innerCurrent, sickle.SickleSecondaryColor with { A = 0 }, width * 0.32f, strength * 0.5f);
479 }
480 else if (sickle.SwingStyle == SickleSwingStyle.SoulOrbit)
481 {
482 float wave = (float)Math.Sin(index * 1.4f + Main.GlobalTimeWrappedHourly * 7f) * 7f;
483 Vector2 normal = difference.SafeNormalize(Vector2.UnitX).RotatedBy(MathHelper.PiOver2) * wave;
484 DrawTrailSegment(pixel, currentTip + normal, difference, sickle.SickleSecondaryColor with { A = 0 }, width * 0.28f, strength * 0.52f);
485 }
486 else if (sickle.SwingStyle == SickleSwingStyle.VoidOrbit)
487 {
488 Vector2 midpoint = currentTip + difference * 0.5f;
489 float moteSize = 2.5f + strength * 4f;
490 Main.EntitySpriteDraw(pixel, midpoint - Main.screenPosition, null, sickle.SickleSecondaryColor with { A = 0 },
491 MathHelper.PiOver4, new Vector2(0.5f), new Vector2(moteSize), SpriteEffects.None);
492 }
493 }
494 }
495
496 private static void DrawTrailSegment(Texture2D pixel, Vector2 start, Vector2 difference, Color color, float width, float opacity)
497 {
498 Main.EntitySpriteDraw(pixel, start - Main.screenPosition, null, color * opacity,
499 difference.ToRotation(), new Vector2(0f, 0.5f),
500 new Vector2(difference.Length(), Math.Max(1f, width)), SpriteEffects.None);
501 }
502
503 private static void DrawWeapon(Texture2D texture, NormalSickle sickle, Vector2 position, float angle, bool flipped, Color color, float opacity, float scale)
504 {
505 Vector2 anchor = sickle.HandleAnchor;
506 SpriteEffects effects = SpriteEffects.None;
507 float rotationCorrection = sickle.TextureRotationCorrection;
508 if (flipped)
509 {
510 anchor.Y = 1f - anchor.Y;
511 rotationCorrection = -rotationCorrection;
512 effects = SpriteEffects.FlipVertically;
513 }
514
515 Vector2 origin = new(texture.Width * anchor.X, texture.Height * anchor.Y);
516 Main.EntitySpriteDraw(texture, position - Main.screenPosition, null, color * opacity,
517 angle + rotationCorrection, origin, scale, effects);
518 }
519
242 520 private void DrawChargeRing(Player player, NormalSickle sickle)
243 521 {
244 522 Texture2D pixel = TextureAssets.MagicPixel.Value;
@@ -248,17 +526,38 @@ public class SickleSwingProjectile : ModProjectile
248 526 {
249 527 float angle = Main.GlobalTimeWrappedHourly * (2f + sickle.SickleTier * 0.25f) + index * MathHelper.TwoPi / motes;
250 528 Vector2 position = player.MountedCenter + angle.ToRotationVector2() * radius - Main.screenPosition;
251 float moteScale = 2f + chargeRatio * 3f + (index % 2);
252 Color color = sickle.SickleColor with { A = 0 };
253 Main.EntitySpriteDraw(pixel, position, null, color * (0.55f + chargeRatio * 0.4f), angle, new Vector2(0.5f), new Vector2(moteScale, 1.5f), SpriteEffects.None);
529 float moteScale = 2f + chargeRatio * 3f + index % 2;
530 Color color = Color.Lerp(sickle.SickleColor, sickle.SickleSecondaryColor, index % 2) with { A = 0 };
531 Main.EntitySpriteDraw(pixel, position, null, color * (0.55f + chargeRatio * 0.4f), angle + MathHelper.PiOver4,
532 new Vector2(0.5f), new Vector2(moteScale), SpriteEffects.None);
254 533 }
255 534 }
256 535
257 private void PushAngleHistory()
536 private void UpdateHistory()
258 537 {
259 for (int index = angleHistory.Length - 1; index > 0; index--)
538 if (historySegment != actionSegment)
539 {
540 historySegment = actionSegment;
541 ResetHistory();
542 return;
543 }
544
545 for (int index = HistoryLength - 1; index > 0; index--)
546 {
260 547 angleHistory[index] = angleHistory[index - 1];
548 gripHistory[index] = gripHistory[index - 1];
549 flipHistory[index] = flipHistory[index - 1];
550 }
261 551 angleHistory[0] = weaponAngle;
552 gripHistory[0] = gripPosition;
553 flipHistory[0] = weaponFlipped;
554 }
555
556 private void ResetHistory()
557 {
558 Array.Fill(angleHistory, weaponAngle);
559 Array.Fill(gripHistory, gripPosition);
560 Array.Fill(flipHistory, weaponFlipped);
262 561 }
263 562
264 563 private bool TryGetSickle(out Player player, out NormalSickle sickle)
@@ -275,4 +574,8 @@ public class SickleSwingProjectile : ModProjectile
275 574 }
276 575
277 576 private static float SmoothStep(float value) => value * value * (3f - 2f * value);
577
578 private static float EaseOutCubic(float value) => 1f - (float)Math.Pow(1f - value, 3f);
579
580 private static float EaseOutSine(float value) => (float)Math.Sin(value * MathHelper.PiOver2);
278 581 }
Modified README.md +3 -2
Modified build.txt +1 -1
Modified docs/PLAY_GUIDE.md +18 -11