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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

重制灵魂镰弹幕与虚影连斩

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

代码差异

5 个文件 +293 -42
Modified Common/MyGlobalProjectile.cs +1 -0
@@ -260,6 +260,7 @@ public class MyGlobalProjectile : GlobalProjectile
260 260 bool requiresAuthorization = projectile.ModProjectile is ReaperActionControllerProjectile
261 261 or ReaperStrikeProjectile
262 262 or ReaperFrostShardProjectile
263 or SoulSickleProjectile
263 264 or ReaperSoulFamiliarProjectile
264 265 or ReaperSoulShardProjectile
265 266 or ReaperVoidRiftProjectile
Modified Common/ReaperCombatService.cs +7 -3
@@ -308,10 +308,14 @@ public static class ReaperCombatService
308 308 }
309 309 case ReaperFormId.Soul:
310 310 {
311 int delay = snapshot.Skill1 switch { 2 => 10, >= 3 => 8, _ => 12 };
312 311 float factor = snapshot.Skill1 switch { 2 => 0.625f, >= 3 => 0.75f, _ => 0.50f };
313 ReaperStrikeProjectile.Spawn(source, swing.owner, snapshot, ReaperHitKind.PrimaryDerived, phase,
314 origin + aim * 66f, aim, ReaperStrikeShape.Circle, 62f, 18f, factor, delay);
312 SoulSickleProjectile.Spawn(source, swing.owner, snapshot,
313 origin + aim * 42f, aim * (14f + snapshot.StageNumber), phase,
314 swing.identity, ReaperHitKind.PrimaryDerived, factor,
315 variant: phase);
316 ReaperSkillCueProjectile.Spawn(source, swing.owner, snapshot, 0,
317 origin + aim * 54f, aim, variant: phase,
318 actionId: swing.identity);
315 319 if (snapshot.Stage >= ReaperStage.StageII && phase == lastPhase)
316 320 ReaperSoulFamiliarProjectile.Spawn(source, swing.owner, snapshot, origin);
317 321 if (snapshot.Stage >= ReaperStage.StageIII && phase == lastPhase)
Modified Projectiles/ReaperActionControllerProjectile.cs +39 -12
@@ -177,12 +177,21 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
177 177 // scythes own all movement and entrance animation, so even decorative
178 178 // weapon echoes would read as another hand swing here.
179 179 int heldEchoCount = !ultimate && snapshot.Form == ReaperFormId.Death
180 ? 0 : 2;
180 ? 0
181 : snapshot.Form == ReaperFormId.Soul
182 ? 3 + snapshot.StageNumber
183 : 2;
181 184 for (int echo = heldEchoCount; echo >= 1; echo--)
182 185 {
183 186 float offset = (0.018f + visualCharge * 0.014f) * echo;
187 Color echoColor = snapshot.Form == ReaperFormId.Soul
188 ? Color.Lerp(primary, secondary,
189 echo / (float)(heldEchoCount + 1))
190 : primary;
184 191 Main.EntitySpriteDraw(texture, grip - Main.screenPosition, null,
185 primary * (0.12f / echo), correctedAngle - offset, anchor,
192 echoColor * (snapshot.Form == ReaperFormId.Soul
193 ? 0.18f / Math.Max(1f, echo * 0.72f)
194 : 0.12f / echo), correctedAngle - offset, anchor,
186 195 scale * (1f + echo * 0.018f), weaponEffects);
187 196 }
188 197 Main.EntitySpriteDraw(texture, grip - Main.screenPosition, null, lightColor,
@@ -488,18 +497,14 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
488 497 float maximum = snapshot.Stage switch { ReaperStage.StageI => 2.3f, ReaperStage.StageII => 3.15f, _ => 4.2f };
489 498 float total = MathHelper.Lerp(minimum, maximum, MathHelper.Clamp(chargeFrames / 45f, 0f, 1f));
490 499 int fullChargePhaseOffset = chargeFrames >= 45 ? 8 : 0;
500 int bladesPerBeat = chargeFrames >= 45 ? 2 : 1;
491 501 for (int index = 0; index < circles; index++)
492 502 {
493 float radius = snapshot.Stage switch
494 {
495 ReaperStage.StageI => 92f + index * 48f,
496 ReaperStage.StageII => 92f + index * 58f,
497 _ => 96f + index * 70f
498 };
499 ChargedStrikeAt(6 + index * 8, player, origin,
500 aim.RotatedBy(index * MathHelper.TwoPi / circles), ReaperStrikeShape.Circle,
501 radius, 23f + snapshot.StageNumber * 2f, total / circles,
502 fullChargePhaseOffset + index);
503 Vector2 launchDirection = aim.RotatedBy(index
504 * MathHelper.TwoPi / circles);
505 SoulVolleyAt(6 + index * 8, player, origin,
506 launchDirection, total / circles, fullChargePhaseOffset + index,
507 bladesPerBeat);
503 508 }
504 509 FinishChargedAt(14 + circles * 8);
505 510 break;
@@ -903,6 +908,28 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
903 908 actionId: Projectile.identity);
904 909 }
905 910
911 private void SoulVolleyAt(int eventTick, Player player, Vector2 origin,
912 Vector2 direction, float multiplier, int phase, int bladeCount)
913 {
914 if (releaseTimer != eventTick || Main.netMode == NetmodeID.MultiplayerClient)
915 return;
916
917 bladeCount = Math.Clamp(bladeCount, 1, 2);
918 float perBladeMultiplier = multiplier / bladeCount;
919 Vector2 normal = direction.RotatedBy(MathHelper.PiOver2);
920 for (int blade = 0; blade < bladeCount; blade++)
921 {
922 float centered = blade - (bladeCount - 1) * 0.5f;
923 Vector2 launch = direction.RotatedBy(centered * 0.16f);
924 SoulSickleProjectile.Spawn(Projectile.GetSource_FromThis(),
925 player.whoAmI, snapshot,
926 origin + normal * centered * 24f + launch * 32f,
927 launch * (15f + snapshot.StageNumber), phase,
928 Projectile.identity, ReaperHitKind.Special, perBladeMultiplier,
929 variant: phase * 2 + blade);
930 }
931 }
932
906 933 private void ExecuteDeathUltimateShatter(Player player)
907 934 {
908 935 HashSet<int> processedRoots = [];
Modified Projectiles/SickleSwingProjectile.cs +10 -4
@@ -1240,7 +1240,9 @@ public sealed class SickleSwingProjectile : ModProjectile
1240 1240
1241 1241 private void DrawWeaponAfterimages(Texture2D texture, float scale)
1242 1242 {
1243 int copies = HasStageVisual(ReaperStage.StageIII) ? 4
1243 bool soulTrail = snapshot.Form == ReaperFormId.Soul;
1244 int copies = soulTrail ? 2 + VisualStage * 2
1245 : HasStageVisual(ReaperStage.StageIII) ? 4
1244 1246 : HasStageVisual(ReaperStage.StageII) ? 2 : 0;
1245 1247 if (copies <= 0 || validHistorySamples < 3)
1246 1248 return;
@@ -1250,11 +1252,15 @@ public sealed class SickleSwingProjectile : ModProjectile
1250 1252 float terminalFade = MathHelper.Clamp((0.985f - animationProgress) / 0.12f, 0f, 1f);
1251 1253 for (int copy = copies; copy >= 1; copy--)
1252 1254 {
1253 int historyIndex = Math.Min(validHistorySamples - 1, copy * 2);
1255 int historyIndex = Math.Min(validHistorySamples - 1,
1256 soulTrail ? copy : copy * 2);
1254 1257 float strength = (copies - copy + 1f) / (copies + 1f) * terminalFade;
1255 Color color = Color.Lerp(primary, secondary, copy / (float)(copies + 1)) * (strength * 0.34f);
1258 float opacity = soulTrail ? 0.52f : 0.34f;
1259 Color color = Color.Lerp(primary, secondary,
1260 copy / (float)(copies + 1)) * (strength * opacity);
1256 1261 DrawWeapon(texture, grip, angleHistory[historyIndex], color,
1257 scale * (0.96f + strength * 0.04f), GetWeaponEffects(historyIndex));
1262 scale * (soulTrail ? 1f : 0.96f + strength * 0.04f),
1263 GetWeaponEffects(historyIndex));
1258 1264 }
1259 1265 }
1260 1266
Modified Projectiles/SoulSickleProjectile.cs +236 -23
@@ -1,15 +1,61 @@
1 using DeathMod.Common;
1 2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
2 6 using Terraria;
3 7 using Terraria.ID;
4 8 using Terraria.ModLoader;
5 9
6 10 namespace DeathMod.Projectiles;
7 11
8 public class SoulSickleProjectile : ModProjectile
12 public sealed class SoulSickleProjectile : ModProjectile
9 13 {
14 private const int HomingDelayFrames = 8;
15
16 private SickleCombatSnapshot snapshot;
17 private ReaperHitKind hitKind;
18 private int phase;
19 private int actionId;
20 private int variant;
21 private int timer;
22 private int targetIndex = -1;
23 private bool configured;
24 private bool serverAuthorized;
25 private int authoritativeDamage;
26 private Vector2 authoritativeCenter;
27 private Vector2 authoritativeVelocity;
28 private readonly HashSet<int> hitRoots = [];
29
30 internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
31 in SickleCombatSnapshot snapshot, Vector2 position, Vector2 velocity,
32 int phase, int actionId, ReaperHitKind hitKind, float damageMultiplier,
33 int variant = 0)
34 {
35 if (Main.netMode == NetmodeID.MultiplayerClient)
36 return -1;
37
38 int projectileIndex = Projectile.NewProjectile(source, position, velocity,
39 ModContent.ProjectileType<SoulSickleProjectile>(),
40 Math.Max(1, (int)Math.Round(snapshot.Damage * damageMultiplier)),
41 snapshot.Knockback * 0.45f, owner);
42 if (projectileIndex < 0 || projectileIndex >= Main.maxProjectiles)
43 return -1;
44
45 Projectile projectile = Main.projectile[projectileIndex];
46 projectile.originalDamage = snapshot.Damage;
47 projectile.CritChance = snapshot.CritChance;
48 if (projectile.ModProjectile is SoulSickleProjectile soulBlade)
49 soulBlade.Configure(snapshot, hitKind, phase, actionId, variant);
50 projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
51 projectile, snapshot, hitKind, phase, velocity.ToRotation(), actionId);
52 projectile.netUpdate = true;
53 return projectileIndex;
54 }
55
10 56 public override void SetStaticDefaults()
11 57 {
12 ProjectileID.Sets.TrailCacheLength[Type] = 18;
58 ProjectileID.Sets.TrailCacheLength[Type] = 24;
13 59 ProjectileID.Sets.TrailingMode[Type] = 2;
14 60 }
15 61
@@ -18,48 +64,215 @@ public class SoulSickleProjectile : ModProjectile
18 64 Projectile.width = 42;
19 65 Projectile.height = 42;
20 66 Projectile.friendly = true;
67 Projectile.hostile = false;
21 68 Projectile.tileCollide = false;
22 Projectile.penetrate = 6;
69 Projectile.penetrate = 4;
23 70 Projectile.ignoreWater = true;
24 Projectile.timeLeft = 260;
71 Projectile.timeLeft = 150;
25 72 Projectile.DamageType = DamageClass.Melee;
26 73 Projectile.netImportant = true;
74 Projectile.usesLocalNPCImmunity = true;
75 Projectile.localNPCHitCooldown = 14;
27 76 }
28 77
78 public override bool ShouldUpdatePosition() => false;
79 public override bool? CanDamage() => configured ? null : false;
80
29 81 public override void AI()
30 82 {
31 Projectile.localAI[0]++;
32 float timer = Projectile.localAI[0];
33 float curveDirection = System.Math.Sign(Projectile.ai[2] == 0f ? 1f : Projectile.ai[2]);
34 int phase = (int)(timer / 24f) % 3;
35 NPC? target = ReaperProjectileHelper.FindTarget(Projectile, Projectile.ai[0] > 0f ? 900f : 680f);
36 if (phase == 0)
83 if (Main.netMode == NetmodeID.Server)
37 84 {
38 Projectile.velocity = Projectile.velocity.RotatedBy(curveDirection * (0.018f + Projectile.ai[1] * 0.01f));
85 if (!serverAuthorized)
86 {
87 Projectile.Kill();
88 return;
89 }
90 Projectile.damage = authoritativeDamage;
91 Projectile.Center = authoritativeCenter;
92 Projectile.velocity = authoritativeVelocity;
39 93 }
40 else if (target is not null && phase == 1)
94 if (!configured)
95 return;
96
97 timer++;
98 int curveDirection = (variant & 1) == 0 ? 1 : -1;
99 if (timer <= HomingDelayFrames)
41 100 {
42 float speed = System.Math.Max(13f, Projectile.velocity.Length());
43 Vector2 desired = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitX) * speed;
44 Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.09f + Projectile.ai[1] * 0.055f);
101 Projectile.velocity = Projectile.velocity.RotatedBy(curveDirection
102 * (0.024f + snapshot.StageNumber * 0.004f));
45 103 }
46 else if (phase == 2 && Projectile.velocity.Length() < 24f)
104 else
47 105 {
48 Projectile.velocity *= 1.018f;
106 NPC? target = GetOrFindTarget();
107 if (target is not null)
108 {
109 float speed = Math.Max(14f + snapshot.StageNumber,
110 Projectile.velocity.Length());
111 Vector2 desired = (target.Center - Projectile.Center)
112 .SafeNormalize(Projectile.velocity.SafeNormalize(Vector2.UnitX))
113 * speed;
114 Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired,
115 0.085f + snapshot.StageNumber * 0.018f);
116 }
117 else
118 {
119 Projectile.velocity = Projectile.velocity.RotatedBy(
120 curveDirection * 0.006f);
121 }
49 122 }
123
50 124 Projectile.rotation = Projectile.velocity.ToRotation();
125 Projectile.GetGlobalProjectile<MyGlobalProjectile>()
126 .UpdateReaperAttackAngle(Projectile.rotation);
127 Projectile.position += Projectile.velocity;
128 if (Main.netMode == NetmodeID.Server)
129 {
130 authoritativeCenter = Projectile.Center;
131 authoritativeVelocity = Projectile.velocity;
132 }
133
51 134 if (!Main.dedServ)
52 135 {
53 Lighting.AddLight(Projectile.Center, new Vector3(0.25f, 0.38f, 0.9f));
54 int dustCount = Projectile.ai[0] > 0f ? 2 : 1;
55 for (int index = 0; index < dustCount; index++)
136 Lighting.AddLight(Projectile.Center, new Vector3(0.22f, 0.34f, 0.82f));
137 if (Main.rand.NextBool(2))
56 138 {
57 Color color = Main.rand.NextBool() ? new Color(85, 225, 255) : new Color(175, 85, 255);
58 Dust dust = Dust.NewDustPerfect(Projectile.Center + Main.rand.NextVector2Circular(13f, 13f), DustID.AncientLight, -Projectile.velocity * 0.07f, 45, color, 0.95f + Projectile.ai[1] * 0.3f);
139 Color color = Main.rand.NextBool()
140 ? new Color(85, 225, 255)
141 : new Color(175, 85, 255);
142 Dust dust = Dust.NewDustPerfect(Projectile.Center
143 + Main.rand.NextVector2Circular(11f, 11f), DustID.AncientLight,
144 -Projectile.velocity * 0.08f, 55, color, 0.95f);
59 145 dust.noGravity = true;
60 146 }
61 147 }
62 148 }
63 149
64 public override bool PreDraw(ref Color lightColor) => ReaperProjectileHelper.DrawTrail(Projectile, new Color(115, 115, 255), scaleMultiplier: 1f + Projectile.ai[1] * 0.25f, copies: 16);
150 public override bool PreDraw(ref Color lightColor)
151 => ReaperProjectileHelper.DrawTrail(Projectile, new Color(120, 115, 255),
152 scaleMultiplier: 1.04f + snapshot.StageNumber * 0.05f, copies: 22);
153
154 public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
155 {
156 int root = target.realLife >= 0 ? target.realLife : target.whoAmI;
157 hitRoots.Add(root);
158 if (targetIndex == target.whoAmI)
159 targetIndex = -1;
160 if (Main.netMode != NetmodeID.MultiplayerClient)
161 Projectile.netUpdate = true;
162 }
163
164 public override void SendExtraAI(BinaryWriter writer)
165 {
166 writer.Write(configured);
167 if (!configured)
168 return;
169 snapshot.Write(writer);
170 writer.Write((byte)hitKind);
171 writer.Write((byte)Math.Clamp(phase, 0, byte.MaxValue));
172 writer.Write(actionId);
173 writer.Write((byte)Math.Clamp(variant, 0, byte.MaxValue));
174 writer.Write((short)Math.Clamp(timer, 0, short.MaxValue));
175 writer.Write((short)targetIndex);
176 }
177
178 public override void ReceiveExtraAI(BinaryReader reader)
179 {
180 bool incomingConfigured = reader.ReadBoolean();
181 if (!incomingConfigured)
182 {
183 if (Main.netMode != NetmodeID.Server)
184 configured = false;
185 return;
186 }
187
188 SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
189 ReaperHitKind incomingHitKind = (ReaperHitKind)reader.ReadByte();
190 int incomingPhase = reader.ReadByte();
191 int incomingActionId = reader.ReadInt32();
192 int incomingVariant = reader.ReadByte();
193 int incomingTimer = reader.ReadInt16();
194 int incomingTarget = reader.ReadInt16();
195 if (Main.netMode == NetmodeID.Server)
196 return;
197
198 snapshot = incomingSnapshot;
199 hitKind = incomingHitKind;
200 phase = incomingPhase;
201 actionId = incomingActionId;
202 variant = incomingVariant;
203 timer = incomingTimer;
204 targetIndex = incomingTarget;
205 configured = true;
206 }
207
208 private void Configure(in SickleCombatSnapshot value, ReaperHitKind kind,
209 int attackPhase, int parentActionId, int projectileVariant)
210 {
211 snapshot = value;
212 hitKind = kind;
213 phase = Math.Max(0, attackPhase);
214 actionId = parentActionId;
215 variant = Math.Max(0, projectileVariant);
216 timer = 0;
217 targetIndex = -1;
218 hitRoots.Clear();
219 configured = true;
220 serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
221 authoritativeDamage = Math.Max(1, Projectile.damage);
222 authoritativeCenter = Projectile.Center;
223 authoritativeVelocity = Projectile.velocity;
224 }
225
226 private NPC? GetOrFindTarget()
227 {
228 float retentionRadius = 760f + snapshot.StageNumber * 80f;
229 int previousTarget = targetIndex;
230 if (targetIndex >= 0 && targetIndex < Main.maxNPCs)
231 {
232 NPC current = Main.npc[targetIndex];
233 int currentRoot = current.realLife >= 0
234 ? current.realLife
235 : current.whoAmI;
236 if (!hitRoots.Contains(currentRoot)
237 && ReaperTargeting.IsValidWeaponTarget(current)
238 && (ReaperTargeting.IsTrainingDummy(current)
239 || current.CanBeChasedBy(Projectile))
240 && Vector2.DistanceSquared(Projectile.Center, current.Center)
241 <= retentionRadius * retentionRadius)
242 {
243 return current;
244 }
245 targetIndex = -1;
246 }
247
248 NPC? target = FindUnhitTarget(520f + snapshot.StageNumber * 90f);
249 targetIndex = target?.whoAmI ?? -1;
250 if (targetIndex != previousTarget
251 && Main.netMode != NetmodeID.MultiplayerClient)
252 Projectile.netUpdate = true;
253 return target;
254 }
255
256 private NPC? FindUnhitTarget(float maximumDistance)
257 {
258 NPC? target = null;
259 foreach (NPC npc in Main.ActiveNPCs)
260 {
261 int root = npc.realLife >= 0 ? npc.realLife : npc.whoAmI;
262 if (hitRoots.Contains(root)
263 || !ReaperTargeting.IsValidWeaponTarget(npc)
264 || !ReaperTargeting.IsTrainingDummy(npc)
265 && !npc.CanBeChasedBy(Projectile))
266 {
267 continue;
268 }
269
270 float distance = Vector2.Distance(npc.Center, Projectile.Center);
271 if (distance >= maximumDistance)
272 continue;
273 maximumDistance = distance;
274 target = npc;
275 }
276 return target;
277 }
65 278 }