返回提交历史
Added
Common/DeathDomainLootAttractionSystem.cs
+112
-0
Modified
Common/MyPlayer.cs
+29
-2
Modified
Common/ReaperCombatService.cs
+3
-2
Modified
Projectiles/LifeStealWispProjectile.cs
+4
-3
XFEstudio/DeathMod
新增死神领域全图拾取与逐斩吸血
11881d2
代码差异
4 个文件
+148
-7
@@ -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
}
@@ -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);
@@ -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)
@@ -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
{