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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

新增死神领域全图拾取与逐斩吸血

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

代码差异

4 个文件 +148 -7
Added Common/DeathDomainLootAttractionSystem.cs +112 -0
@@ -0,0 +1,112 @@
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using Terraria;
5 using Terraria.ID;
6 using Terraria.ModLoader;
7
8 namespace DeathMod.Common;
9
10 internal sealed class DeathDomainLootAttractionSystem : ModSystem
11 {
12 private const float ArrivalDistance = 44f;
13 private const float MinimumPullStep = 18f;
14 private const float MaximumPullStep = 160f;
15 private const int NetworkSyncInterval = 5;
16 private static readonly List<Player> ActiveCollectors = [];
17
18 public override void OnWorldUnload()
19 {
20 ActiveCollectors.Clear();
21 }
22
23 public override void PostUpdateWorld()
24 {
25 // World item positions are authoritative on the server. Clients only replay
26 // the synchronized flight so a domain cannot collect an item client-side.
27 if (Main.netMode == NetmodeID.MultiplayerClient)
28 return;
29
30 RefreshActiveCollectors();
31 if (ActiveCollectors.Count == 0)
32 return;
33
34 for (int itemIndex = 0; itemIndex < Main.maxItems; itemIndex++)
35 {
36 Item item = Main.item[itemIndex];
37 if (!item.active || item.IsAir || item.stack <= 0)
38 continue;
39
40 Player? collector = FindNearestDomainOwner(item.Center);
41 if (collector is null)
42 continue;
43
44 Vector2 toCollector = collector.MountedCenter - item.Center;
45 float distance = toCollector.Length();
46 if (distance <= 0.001f)
47 continue;
48
49 item.noGrabDelay = 0;
50 Vector2 direction = toCollector / distance;
51 bool arrived = distance <= ArrivalDistance;
52 if (arrived)
53 {
54 item.Center = collector.MountedCenter;
55 item.velocity = Vector2.Zero;
56 }
57 else
58 {
59 // Move the world entity itself so the global pull is not stopped by
60 // terrain. Velocity is retained for smooth interpolation on clients.
61 float step = MathHelper.Clamp(distance * 0.075f,
62 MinimumPullStep, MaximumPullStep);
63 item.position += direction * Math.Min(distance - ArrivalDistance, step);
64 item.velocity = Vector2.Lerp(item.velocity,
65 direction * MathHelper.Clamp(step * 0.22f, 6f, 32f), 0.58f);
66 }
67
68 if (Main.netMode == NetmodeID.Server
69 && (Main.GameUpdateCount + (ulong)itemIndex)
70 % NetworkSyncInterval == 0)
71 {
72 NetMessage.SendData(MessageID.SyncItem, -1, -1, null, itemIndex);
73 }
74 }
75 }
76
77 private static Player? FindNearestDomainOwner(Vector2 itemCenter)
78 {
79 Player? nearest = null;
80 float nearestDistanceSquared = float.MaxValue;
81 foreach (Player player in ActiveCollectors)
82 {
83 float distanceSquared = Vector2.DistanceSquared(itemCenter,
84 player.MountedCenter);
85 if (distanceSquared >= nearestDistanceSquared)
86 continue;
87
88 nearestDistanceSquared = distanceSquared;
89 nearest = player;
90 }
91
92 return nearest;
93 }
94
95 private static void RefreshActiveCollectors()
96 {
97 ActiveCollectors.Clear();
98 for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
99 {
100 Player player = Main.player[playerIndex];
101 if (!player.active || player.dead)
102 continue;
103
104 MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
105 if (modPlayer.DeathDomainEnabled
106 && modPlayer.GetDeathNecklaceForReaperRift() is not null)
107 {
108 ActiveCollectors.Add(player);
109 }
110 }
111 }
112 }
Modified Common/MyPlayer.cs +29 -2
@@ -588,6 +588,34 @@ public class MyPlayer : ModPlayer
588 588 return amount;
589 589 }
590 590
591 internal int ApplyDeathReaperLifeSteal(int damageDone, Vector2 visualSource,
592 int requestedLevel = 1)
593 {
594 if (damageDone <= 0 || Player.dead || Main.netMode == NetmodeID.MultiplayerClient)
595 return 0;
596
597 DeathNecklace? necklace = ActiveDeathNecklace ?? FindEquippedDeathNecklace();
598 int lifeStealLevel = Math.Clamp(Math.Max(requestedLevel,
599 necklace?.LifeStealLevel ?? 0), 1, DeathNecklace.MaxLifeStealLevel);
600 int missingLife = Math.Max(0, Player.statLifeMax2 - Player.statLife);
601 int perHitCap = Math.Max(1, (int)Math.Ceiling(Player.statLifeMax2 * 0.025f));
602 int amount = Math.Min(missingLife, Math.Min(perHitCap,
603 Math.Max(1, (int)Math.Ceiling(damageDone * lifeStealLevel * 0.01f))));
604
605 if (amount > 0)
606 {
607 Player.Heal(amount);
608 if (Main.netMode == NetmodeID.Server)
609 NetMessage.SendData(MessageID.PlayerLifeMana, -1, -1, null, Player.whoAmI);
610 }
611
612 // Death Reaper attacks always show the return stream, including at full life.
613 // This makes every successful slash readable without granting phantom healing.
614 LifeStealVisuals.Spawn(visualSource, Player.whoAmI, lifeStealLevel, amount,
615 forceFeedback: true);
616 return amount;
617 }
618
591 619 internal int TakeNextSwingDirection(int facingDirection)
592 620 {
593 621 int direction = swingComboDirection * facingDirection;
@@ -1245,8 +1273,7 @@ public class MyPlayer : ModPlayer
1245 1273 Vector2 lifeStealSource = target.Center;
1246 1274 target.StrikeNPC(harvestHit, fromNet: false, noPlayerInteraction: false);
1247 1275 int actualDamage = Math.Max(0, lifeBeforeHit - Math.Max(0, target.life));
1248 int healedLife = TryLifeSteal(actualDamage, lifeStealLevel);
1249 LifeStealVisuals.Spawn(lifeStealSource, Player.whoAmI, lifeStealLevel, healedLife);
1276 ApplyDeathReaperLifeSteal(actualDamage, lifeStealSource, lifeStealLevel);
1250 1277 if (Main.netMode == NetmodeID.Server)
1251 1278 {
1252 1279 NetMessage.SendStrikeNPC(target, in harvestHit);
Modified Common/ReaperCombatService.cs +3 -2
@@ -504,6 +504,9 @@ public static class ReaperCombatService
504 504 }
505 505 break;
506 506 case ReaperFormId.Death:
507 if (data.ReaperHitKind is ReaperHitKind.Primary or ReaperHitKind.PrimaryDerived)
508 player.GetModPlayer<MyPlayer>().ApplyDeathReaperLifeSteal(damageDone,
509 target.Center);
507 510 if (data.ReaperHitKind == ReaperHitKind.Ultimate)
508 511 {
509 512 player.GetModPlayer<ReaperCombatPlayer>().ApplyDeathUltimateHealing(damageDone);
@@ -517,8 +520,6 @@ public static class ReaperCombatService
517 520 modPlayer.RecordDeathCycleHit(data.ReaperPhase);
518 521 modPlayer.QueueDeathNecklaceHarvestFromReaperHit(target,
519 522 data.AttackAngle);
520 if (data.ReaperPhase == 1)
521 player.GetModPlayer<ReaperCombatPlayer>().ApplyDeathBloodHealing(damageDone);
522 523 if (data.ReaperPhase == 2)
523 524 target.AddBuff(BuffID.OnFire3, 60 * 3);
524 525 if (data.ReaperPhase == 3 && !bossTarget)
Modified Projectiles/LifeStealWispProjectile.cs +4 -3
@@ -11,10 +11,11 @@ namespace DeathMod.Projectiles;
11 11
12 12 internal static class LifeStealVisuals
13 13 {
14 public static void Spawn(Vector2 source, int playerIndex, int lifeStealLevel, int healedLife)
14 public static void Spawn(Vector2 source, int playerIndex, int lifeStealLevel, int healedLife,
15 bool forceFeedback = false)
15 16 {
16 17 if (lifeStealLevel <= 0
17 || healedLife <= 0
18 || healedLife <= 0 && !forceFeedback
18 19 || playerIndex < 0
19 20 || playerIndex >= Main.maxPlayers
20 21 || !Main.player[playerIndex].active
@@ -23,7 +24,7 @@ internal static class LifeStealVisuals
23 24 return;
24 25 }
25 26
26 int count = Math.Max(1, (healedLife + 7) / 8);
27 int count = Math.Max(1, (Math.Max(1, healedLife) + 7) / 8);
27 28 Player player = Main.player[playerIndex];
28 29 for (int index = 0; index < count; index++)
29 30 {