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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

fix: synchronize reaper combat and altar upgrades

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

代码差异

7 个文件 +312 -31
Modified Common/MyGlobalNPC.cs +52 -7
@@ -23,13 +23,31 @@ public class MyGlobalNPC : GlobalNPC
23 23 private readonly int[] fatedStackTimers = new int[AbsoluteMaximumFatedStacks];
24 24 private int lastSicklePlayer = -1;
25 25 private int fatedSecondTimer;
26 private bool deathMarked;
26 27
27 28 public int FatedStackCount { get; private set; }
28 29
30 public override void SetDefaults(NPC npc)
31 {
32 Array.Clear(sickleParticipants);
33 Array.Clear(fatedStackTimers);
34 lastSicklePlayer = -1;
35 fatedSecondTimer = 0;
36 deathMarked = false;
37 FatedStackCount = 0;
38 }
39
29 40 public override void OnHitByItem(NPC npc, Player player, Item item, NPC.HitInfo hit, int damageDone)
30 41 {
31 42 if (item.ModItem is NormalSickle or LegacyDeath)
32 43 RegisterSickleHit(npc, player.whoAmI);
44
45 if (Main.netMode == NetmodeID.MultiplayerClient
46 && player.whoAmI == Main.myPlayer
47 && item.ModItem is LegacyDeath)
48 {
49 DeathMod.SendSickleItemHit(npc);
50 }
33 51 }
34 52
35 53 public override void OnHitByProjectile(NPC npc, Projectile projectile, NPC.HitInfo hit, int damageDone)
@@ -48,7 +66,7 @@ public class MyGlobalNPC : GlobalNPC
48 66
49 67 public override void PostDraw(NPC npc, SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor)
50 68 {
51 if (!npc.HasBuff(ModContent.BuffType<DeathMarkBuff>()))
69 if (!deathMarked)
52 70 return;
53 71
54 72 Texture2D markTexture = TextureAssets.Buff[ModContent.BuffType<DeathMarkBuff>()].Value;
@@ -74,11 +92,13 @@ public class MyGlobalNPC : GlobalNPC
74 92
75 93 public override void SendExtraAI(NPC npc, BitWriter bitWriter, BinaryWriter binaryWriter)
76 94 {
95 bitWriter.WriteBit(deathMarked);
77 96 binaryWriter.Write((byte)FatedStackCount);
78 97 }
79 98
80 99 public override void ReceiveExtraAI(NPC npc, BitReader bitReader, BinaryReader binaryReader)
81 100 {
101 deathMarked = bitReader.ReadBit();
82 102 FatedStackCount = Math.Clamp((int)binaryReader.ReadByte(), 0, AbsoluteMaximumFatedStacks);
83 103 }
84 104
@@ -107,13 +127,16 @@ public class MyGlobalNPC : GlobalNPC
107 127 Main.player[lastSicklePlayer].GetModPlayer<MyPlayer>().AddSouls(1);
108 128 }
109 129
110 private void RegisterSickleHit(NPC npc, int playerIndex)
130 internal void RegisterSickleHit(NPC npc, int playerIndex, bool applyDeathMark = true)
111 131 {
112 if (playerIndex < 0 || playerIndex >= Main.maxPlayers || npc.friendly)
132 if (Main.netMode == NetmodeID.MultiplayerClient
133 || playerIndex < 0
134 || playerIndex >= Main.maxPlayers
135 || npc.friendly)
113 136 return;
114 137
115 if (Main.netMode != NetmodeID.MultiplayerClient)
116 npc.AddBuff(ModContent.BuffType<DeathMarkBuff>(), DeathMarkDuration);
138 if (applyDeathMark)
139 MarkForReaping(npc);
117 140
118 141 sickleParticipants[playerIndex] = true;
119 142 lastSicklePlayer = playerIndex;
@@ -145,6 +168,28 @@ public class MyGlobalNPC : GlobalNPC
145 168 npc.netUpdate = true;
146 169 }
147 170
171 internal void MarkForReaping(NPC npc)
172 {
173 bool newlyMarked = !deathMarked;
174 deathMarked = true;
175 npc.AddBuff(ModContent.BuffType<DeathMarkBuff>(), DeathMarkDuration);
176 SyncBuffState(npc);
177 if (newlyMarked)
178 DeathMod.BroadcastDeathMark(npc, marked: true);
179 }
180
181 internal void ReceiveDeathMarkState(bool marked)
182 {
183 deathMarked = marked;
184 }
185
186 internal static void SyncBuffState(NPC npc)
187 {
188 npc.netUpdate = true;
189 if (Main.netMode == NetmodeID.Server)
190 NetMessage.SendData(MessageID.NPCBuffs, -1, -1, null, npc.whoAmI);
191 }
192
148 193 private void UpdateFatedState(NPC npc)
149 194 {
150 195 if (Main.netMode == NetmodeID.MultiplayerClient || FatedStackCount <= 0)
@@ -196,7 +241,7 @@ public class MyGlobalNPC : GlobalNPC
196 241 if (Main.netMode == NetmodeID.Server || !npc.active)
197 242 return;
198 243
199 if (npc.HasBuff(ModContent.BuffType<DeathMarkBuff>()) && Main.rand.NextBool(4))
244 if (deathMarked && Main.rand.NextBool(4))
200 245 {
201 246 float angle = Main.GlobalTimeWrappedHourly * 3.1f + npc.whoAmI * 0.73f + Main.rand.NextFloat(-0.35f, 0.35f);
202 247 Vector2 orbit = new Vector2(Math.Max(12f, npc.width * 0.58f), Math.Max(9f, npc.height * 0.36f));
@@ -205,7 +250,7 @@ public class MyGlobalNPC : GlobalNPC
205 250 markDust.noGravity = true;
206 251 }
207 252
208 if (FatedStackCount <= 0 || !npc.HasBuff(ModContent.BuffType<FatedBuff>()))
253 if (FatedStackCount <= 0)
209 254 return;
210 255
211 256 int particleCount = Main.rand.NextBool(2) ? Math.Min(2, FatedStackCount) : 1;
Modified Common/MyGlobalProjectile.cs +21 -3
@@ -103,7 +103,23 @@ public class MyGlobalProjectile : GlobalProjectile
103 103
104 104 public override void OnHitNPC(Projectile projectile, NPC target, NPC.HitInfo hit, int damageDone)
105 105 {
106 if (!IsSickleProjectile || projectile.owner < 0 || projectile.owner >= Main.maxPlayers)
106 if ((!IsSickleProjectile && projectile.ModProjectile is not SickleSwingProjectile)
107 || projectile.owner < 0
108 || projectile.owner >= Main.maxPlayers)
109 return;
110
111 if (Main.netMode == NetmodeID.MultiplayerClient)
112 {
113 if (projectile.owner != Main.myPlayer)
114 return;
115
116 Main.player[projectile.owner].GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
117 LifeStealVisuals.Spawn(target.Center, projectile.owner, LifeStealLevel);
118 DeathMod.SendSickleHit(projectile, target);
119 return;
120 }
121
122 if (Main.netMode == NetmodeID.Server)
107 123 return;
108 124
109 125 Main.player[projectile.owner].GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
@@ -116,15 +132,17 @@ public class MyGlobalProjectile : GlobalProjectile
116 132 if (Main.netMode == NetmodeID.MultiplayerClient || target.friendly || target.lifeMax <= 5)
117 133 return;
118 134
119 target.AddBuff(ModContent.BuffType<Buffs.DeathMarkBuff>(), MyGlobalNPC.DeathMarkDuration);
135 target.GetGlobalNPC<MyGlobalNPC>().MarkForReaping(target);
120 136 NPC statusTarget = target.realLife >= 0 && target.realLife < Main.maxNPCs && Main.npc[target.realLife].active
121 137 ? Main.npc[target.realLife]
122 138 : target;
123 139 if (statusTarget.whoAmI != target.whoAmI)
124 statusTarget.AddBuff(ModContent.BuffType<Buffs.DeathMarkBuff>(), MyGlobalNPC.DeathMarkDuration);
140 statusTarget.GetGlobalNPC<MyGlobalNPC>().MarkForReaping(statusTarget);
125 141 if (debuffType > 0 && debuffDuration > 0)
126 142 statusTarget.AddBuff(debuffType, debuffDuration);
127 143 if (fatedStacks > 0 && fatedMaximumStacks > 0 && fatedDurationFrames > 0)
128 144 statusTarget.GetGlobalNPC<MyGlobalNPC>().AddFatedStacks(statusTarget, fatedStacks, fatedMaximumStacks, fatedDurationFrames);
145
146 MyGlobalNPC.SyncBuffState(statusTarget);
129 147 }
130 148 }
Modified Common/SickleUpgradeService.cs +1 -0
@@ -122,5 +122,6 @@ internal static class DeathAltarUpgradeService
122 122 player.whoAmI,
123 123 PlayerItemSlotID.Inventory0 + slot,
124 124 player.inventory[slot].prefix);
125 DeathMod.SendInventorySlot(player, slot);
125 126 }
126 127 }
Modified DeathMod.cs +177 -1
@@ -1,10 +1,14 @@
1 1 using DeathMod.Common;
2 using DeathMod.Items;
3 using DeathMod.Projectiles;
2 4 using Microsoft.Xna.Framework;
5 using System;
3 6 using System.IO;
4 7 using Terraria;
5 8 using Terraria.ID;
6 9 using Terraria.Localization;
7 10 using Terraria.ModLoader;
11 using Terraria.ModLoader.IO;
8 12
9 13 namespace DeathMod;
10 14
@@ -16,7 +20,10 @@ public class DeathMod : Mod
16 20 RequestExtractEssence,
17 21 RequestAbsorbEssence,
18 22 RequestAltarUpgrade,
19 OperationResult
23 OperationResult,
24 SickleHitRequest,
25 SyncInventorySlot,
26 SyncDeathMark
20 27 }
21 28
22 29 internal enum OperationResult : byte
@@ -81,6 +88,41 @@ public class DeathMod : Mod
81 88 ShowOperationResult(result);
82 89 break;
83 90 }
91 case MessageType.SickleHitRequest:
92 {
93 int projectileIdentity = reader.ReadInt32();
94 short npcIndex = reader.ReadInt16();
95 if (Main.netMode == NetmodeID.Server)
96 HandleSickleHitRequest(whoAmI, projectileIdentity, npcIndex);
97 break;
98 }
99 case MessageType.SyncInventorySlot:
100 {
101 if (Main.netMode != NetmodeID.MultiplayerClient)
102 break;
103
104 byte slot = reader.ReadByte();
105 Item item = ItemIO.Receive(reader, readStack: true, readFavorite: true);
106 if (slot < 58)
107 {
108 Main.LocalPlayer.inventory[slot] = item;
109 Recipe.FindRecipes(canDelayCheck: true);
110 }
111 break;
112 }
113 case MessageType.SyncDeathMark:
114 {
115 short npcIndex = reader.ReadInt16();
116 bool marked = reader.ReadBoolean();
117 if (Main.netMode == NetmodeID.MultiplayerClient
118 && npcIndex >= 0
119 && npcIndex < Main.maxNPCs
120 && Main.npc[npcIndex].active)
121 {
122 Main.npc[npcIndex].GetGlobalNPC<MyGlobalNPC>().ReceiveDeathMarkState(marked);
123 }
124 break;
125 }
84 126 default:
85 127 Logger.Warn($"Unknown DeathMod packet: {messageType}");
86 128 break;
@@ -104,6 +146,140 @@ public class DeathMod : Mod
104 146 packet.Send(player.whoAmI);
105 147 }
106 148
149 internal static void SendInventorySlot(Player player, int slot)
150 {
151 if (Main.netMode != NetmodeID.Server || slot < 0 || slot >= 58)
152 return;
153
154 ModPacket packet = ModContent.GetInstance<DeathMod>().GetPacket();
155 packet.Write((byte)MessageType.SyncInventorySlot);
156 packet.Write((byte)slot);
157 ItemIO.Send(player.inventory[slot], packet, writeStack: true, writeFavorite: true);
158 packet.Send(player.whoAmI);
159 }
160
161 internal static void BroadcastDeathMark(NPC npc, bool marked)
162 {
163 if (Main.netMode != NetmodeID.Server || npc.whoAmI < 0 || npc.whoAmI >= Main.maxNPCs)
164 return;
165
166 ModPacket packet = ModContent.GetInstance<DeathMod>().GetPacket();
167 packet.Write((byte)MessageType.SyncDeathMark);
168 packet.Write((short)npc.whoAmI);
169 packet.Write(marked);
170 packet.Send();
171 }
172
173 internal static void SendSickleHit(Projectile projectile, NPC target)
174 {
175 if (Main.netMode != NetmodeID.MultiplayerClient || projectile.owner != Main.myPlayer)
176 return;
177
178 SendSickleHit(projectile.identity, target);
179 }
180
181 internal static void SendSickleItemHit(NPC target)
182 {
183 if (Main.netMode != NetmodeID.MultiplayerClient)
184 return;
185
186 SendSickleHit(-1, target);
187 }
188
189 private static void SendSickleHit(int projectileIdentity, NPC target)
190 {
191 if (!target.active || target.whoAmI < 0 || target.whoAmI >= Main.maxNPCs)
192 return;
193
194 ModPacket packet = ModContent.GetInstance<DeathMod>().GetPacket();
195 packet.Write((byte)MessageType.SickleHitRequest);
196 packet.Write(projectileIdentity);
197 packet.Write((short)target.whoAmI);
198 packet.Send();
199 }
200
201 private static void HandleSickleHitRequest(int playerIndex, int projectileIdentity, int npcIndex)
202 {
203 if (playerIndex < 0 || playerIndex >= Main.maxPlayers || npcIndex < 0 || npcIndex >= Main.maxNPCs)
204 return;
205
206 Player player = Main.player[playerIndex];
207 NPC target = Main.npc[npcIndex];
208 if (!player.active || player.dead || !target.active || target.friendly || target.lifeMax <= 5)
209 return;
210
211 Projectile? projectile = projectileIdentity >= 0
212 ? FindOwnedProjectile(playerIndex, projectileIdentity)
213 : null;
214 MyGlobalProjectile? projectileData = projectile?.GetGlobalProjectile<MyGlobalProjectile>();
215 NormalSickle? heldSickle = player.HeldItem.ModItem as NormalSickle;
216 bool holdingSickle = player.HeldItem.ModItem is NormalSickle or LegacyDeath;
217 bool validProjectile = projectileData is { IsSickleProjectile: true }
218 || projectile?.ModProjectile is SickleSwingProjectile
219 || projectileIdentity >= 0 && holdingSickle;
220 bool validItemHit = projectileIdentity < 0 && holdingSickle;
221 if (!validProjectile && !validItemHit)
222 return;
223
224 int debuffType;
225 int debuffDuration;
226 int fatedStacks;
227 int fatedMaximumStacks;
228 int fatedDurationFrames;
229 if (projectileData is { IsSickleProjectile: true })
230 {
231 debuffType = projectileData.OnHitDebuffType;
232 debuffDuration = projectileData.OnHitDebuffDuration;
233 fatedStacks = projectileData.FatedStacksPerHit;
234 fatedMaximumStacks = projectileData.FatedMaximumStacks;
235 fatedDurationFrames = projectileData.FatedDurationFrames;
236 }
237 else if (heldSickle is not null)
238 {
239 debuffType = heldSickle.OnHitDebuffType;
240 debuffDuration = heldSickle.OnHitDebuffDuration;
241 fatedStacks = heldSickle.FatedStacksPerHit;
242 fatedMaximumStacks = heldSickle.FatedMaximumStacks;
243 fatedDurationFrames = heldSickle.FatedDurationFrames;
244 }
245 else
246 {
247 debuffType = 0;
248 debuffDuration = 0;
249 fatedStacks = 0;
250 fatedMaximumStacks = 0;
251 fatedDurationFrames = 0;
252 }
253
254 target.GetGlobalNPC<MyGlobalNPC>().RegisterSickleHit(target, playerIndex, applyDeathMark: false);
255 MyGlobalProjectile.ApplyStatusEffects(
256 target,
257 debuffType,
258 debuffDuration,
259 fatedStacks,
260 fatedMaximumStacks,
261 fatedDurationFrames);
262
263 if (projectile?.ModProjectile is SickleSwingProjectile
264 && projectile.ai[1] >= 1f
265 && heldSickle?.AlternateAttackStyle == SickleAlternateAttackStyle.HarvestUppercut)
266 {
267 SickleSwingProjectile.ApplyHarvestUppercut(target, player.direction, projectile.knockBack);
268 }
269 }
270
271 private static Projectile? FindOwnedProjectile(int playerIndex, int identity)
272 {
273 for (int index = 0; index < Main.maxProjectiles; index++)
274 {
275 Projectile projectile = Main.projectile[index];
276 if (projectile.active && projectile.owner == playerIndex && projectile.identity == identity)
277 return projectile;
278 }
279
280 return null;
281 }
282
107 283 private static void ShowOperationResult(OperationResult result)
108 284 {
109 285 string key = $"Mods.DeathMod.Messages.{result}";
Modified Items/NormalSickle.cs +14 -0
@@ -237,6 +237,20 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
237 237
238 238 public override void OnHitNPC(Player player, NPC target, NPC.HitInfo hit, int damageDone)
239 239 {
240 if (Main.netMode == NetmodeID.MultiplayerClient)
241 {
242 if (player.whoAmI != Main.myPlayer)
243 return;
244
245 player.GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
246 LifeStealVisuals.Spawn(target.Center, player.whoAmI, LifeStealLevel);
247 DeathMod.SendSickleItemHit(target);
248 return;
249 }
250
251 if (Main.netMode == NetmodeID.Server)
252 return;
253
240 254 player.GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
241 255 LifeStealVisuals.Spawn(target.Center, player.whoAmI, LifeStealLevel);
242 256 MyGlobalProjectile.ApplyStatusEffects(target, OnHitDebuffType, OnHitDebuffDuration, FatedStacksPerHit, FatedMaximumStacks, FatedDurationFrames);
Modified Projectiles/LifeStealWispProjectile.cs +38 -16
@@ -34,11 +34,13 @@ internal static class LifeStealVisuals
34 34
35 35 public class LifeStealWispProjectile : ModProjectile
36 36 {
37 private const int TrailHistoryLength = 32;
38
37 39 private int LifeStealLevel => (int)Projectile.ai[0];
38 40
39 41 public override void SetStaticDefaults()
40 42 {
41 ProjectileID.Sets.TrailCacheLength[Type] = 12;
43 ProjectileID.Sets.TrailCacheLength[Type] = TrailHistoryLength;
42 44 ProjectileID.Sets.TrailingMode[Type] = 2;
43 45 }
44 46
@@ -119,41 +121,61 @@ public class LifeStealWispProjectile : ModProjectile
119 121 Vector2 origin = texture.Size() * 0.5f;
120 122 float rotation = Projectile.velocity.ToRotation() - MathHelper.PiOver2;
121 123 float baseScale = 0.34f + LifeStealLevel * 0.045f;
122 int trailLength = 2 + LifeStealLevel;
124 int trailPointCount = System.Math.Min(Projectile.oldPos.Length, 14 + LifeStealLevel * 3);
125 float levelRatio = MathHelper.Clamp(LifeStealLevel / 5f, 0f, 1f);
126 Color outerColor = Color.Lerp(new Color(125, 0, 28), new Color(245, 38, 155), levelRatio) with { A = 0 };
127 Color coreColor = Color.Lerp(new Color(235, 22, 48), new Color(255, 155, 220), levelRatio) with { A = 0 };
123 128
124 for (int index = trailLength; index >= 1; index--)
129 for (int index = trailPointCount - 1; index >= 0; index--)
125 130 {
126 131 Vector2 older = Projectile.oldPos[index] + Projectile.Size * 0.5f;
127 Vector2 newer = Projectile.oldPos[index - 1] + Projectile.Size * 0.5f;
128 if (Projectile.oldPos[index] == Vector2.Zero || Projectile.oldPos[index - 1] == Vector2.Zero)
132 Vector2 newer = index == 0
133 ? Projectile.Center
134 : Projectile.oldPos[index - 1] + Projectile.Size * 0.5f;
135 if (Projectile.oldPos[index] == Vector2.Zero
136 || index > 0 && Projectile.oldPos[index - 1] == Vector2.Zero)
129 137 continue;
130 138
131 139 Vector2 segment = newer - older;
132 140 float length = segment.Length();
133 if (length is < 0.5f or > 20f)
141 if (length is < 0.35f or > 40f)
134 142 continue;
135 143
136 float strength = 1f - index / (float)(trailLength + 1);
137 Color ribbonColor = Color.Lerp(new Color(170, 5, 35), new Color(255, 95, 185), LifeStealLevel / 5f) with { A = 0 };
138 Main.EntitySpriteDraw(pixel, older - Main.screenPosition, null, ribbonColor * strength * 0.5f,
139 segment.ToRotation(), new Vector2(0f, pixel.Height * 0.5f),
140 new Vector2(length / pixel.Width, (1.5f + LifeStealLevel * 0.42f) * strength / pixel.Height), SpriteEffects.None);
144 float strength = 1f - index / (float)trailPointCount;
145 float taper = (float)System.Math.Pow(strength, 0.72f);
146 float outerWidth = (2.25f + LifeStealLevel * 0.58f) * taper;
147 float coreWidth = (0.72f + LifeStealLevel * 0.2f) * taper;
148 DrawTrailSegment(pixel, older, segment, outerColor * strength * 0.34f, outerWidth);
149 DrawTrailSegment(pixel, older, segment, coreColor * strength * 0.82f, coreWidth);
141 150 }
142 151
143 for (int index = trailLength; index >= 1; index--)
152 int afterimageCount = System.Math.Min(trailPointCount, 5 + LifeStealLevel);
153 for (int index = afterimageCount - 1; index >= 1; index--)
144 154 {
145 155 if (Projectile.oldPos[index] == Vector2.Zero)
146 156 continue;
147 157
148 float strength = (trailLength - index + 1f) / (trailLength + 1f);
158 float strength = 1f - index / (float)afterimageCount;
149 159 Vector2 position = Projectile.oldPos[index] + Projectile.Size * 0.5f - Main.screenPosition;
150 Color trailColor = Color.Lerp(new Color(220, 20, 45), new Color(255, 100, 190), LifeStealLevel / 5f) with { A = 0 };
151 Main.EntitySpriteDraw(texture, position, null, trailColor * strength * 0.42f,
152 rotation, origin, baseScale * (0.65f + strength * 0.25f), SpriteEffects.None);
160 Main.EntitySpriteDraw(texture, position, null, coreColor * strength * 0.26f,
161 rotation, origin, baseScale * (0.58f + strength * 0.24f), SpriteEffects.None);
153 162 }
154 163
155 164 Main.EntitySpriteDraw(texture, Projectile.Center - Main.screenPosition, null, Color.White,
156 165 rotation, origin, baseScale, SpriteEffects.None);
157 166 return false;
158 167 }
168
169 private static void DrawTrailSegment(Texture2D pixel, Vector2 start, Vector2 segment, Color color, float width)
170 {
171 Main.EntitySpriteDraw(
172 pixel,
173 start - Main.screenPosition,
174 null,
175 color,
176 segment.ToRotation(),
177 new Vector2(0f, pixel.Height * 0.5f),
178 new Vector2(segment.Length() / pixel.Width, width / pixel.Height),
179 SpriteEffects.None);
180 }
159 181 }
Modified Projectiles/SickleSwingProjectile.cs +9 -4
@@ -350,7 +350,7 @@ public class SickleSwingProjectile : ModProjectile
350 350 SetArc(aimRotation, 2.35f, -1.15f, EaseOutSine(progress), player.direction, 0);
351 351 gripPosition += aimRotation.ToRotationVector2() * lift * 20f;
352 352 gripPosition.Y -= lift * 12f;
353 ApplyDash(player, sickle, 19.5f, 19, true);
353 ApplyDash(player, sickle, 9.75f, 19, true);
354 354 break;
355 355 }
356 356 case SickleAlternateAttackStyle.BoneBarrage:
@@ -383,15 +383,20 @@ public class SickleSwingProjectile : ModProjectile
383 383
384 384 public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
385 385 {
386 if (!IsAlternateAttack || Main.netMode == NetmodeID.MultiplayerClient
386 if (!IsAlternateAttack || Main.netMode != NetmodeID.SinglePlayer
387 387 || !TryGetSickle(out Player player, out NormalSickle sickle)
388 388 || sickle.AlternateAttackStyle != SickleAlternateAttackStyle.HarvestUppercut)
389 389 return;
390 390
391 ApplyHarvestUppercut(target, player.direction, Projectile.knockBack);
392 }
393
394 internal static void ApplyHarvestUppercut(NPC target, int direction, float knockback)
395 {
391 396 float resistance = MathHelper.Clamp(target.knockBackResist, 0.15f, 1f);
392 float liftSpeed = (6.5f + Projectile.knockBack * 0.35f) * resistance;
397 float liftSpeed = (6.5f + knockback * 0.35f) * resistance;
393 398 target.velocity.Y = Math.Min(target.velocity.Y, -liftSpeed);
394 target.velocity.X += player.direction * (1.8f + Projectile.knockBack * 0.12f) * resistance;
399 target.velocity.X += direction * (1.8f + knockback * 0.12f) * resistance;
395 400 target.netUpdate = true;
396 401 }
397 402