Skip to content

Commit c7b2197

Browse files
authored
feat(attributes): 增加属性组件代理 (#80)
1 parent 5bee420 commit c7b2197

7 files changed

Lines changed: 292 additions & 2 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using GameFrameX.Apps.Player.Attribute;
4+
using GameFrameX.Hotfix.Logic.Player.Attribute;
5+
6+
namespace GameFrameX.Apps.SelfCheck;
7+
8+
internal static class AttributeAgentSelfCheck
9+
{
10+
public static void Run()
11+
{
12+
var values = new Dictionary<int, long>
13+
{
14+
[(int)AttributeType.LifeBase] = 1000,
15+
[(int)AttributeType.Life] = 1000
16+
};
17+
18+
var result = PlayerAttributeMutation.ApplyValue(values, AttributeType.LifeAdd, 200, false);
19+
AssertEqual(200, values[(int)AttributeType.LifeAdd], "PlayerAttributeComponentAgent.LifeAdd");
20+
AssertEqual(1200, values[(int)AttributeType.Life], "PlayerAttributeComponentAgent.Life");
21+
AssertEqual(AttributeType.Life, result.FinalAttributeType, "PlayerAttributeComponentAgent.FinalAttribute");
22+
AssertEqual(true, result.ShouldDispatch, "PlayerAttributeComponentAgent.DispatchChanged");
23+
24+
result = PlayerAttributeMutation.ApplyValue(values, AttributeType.LifeAdd, 200, false);
25+
AssertEqual(false, result.StateChanged, "PlayerAttributeComponentAgent.UnchangedState");
26+
AssertEqual(false, result.ShouldDispatch, "PlayerAttributeComponentAgent.UnchangedDispatch");
27+
28+
result = PlayerAttributeMutation.ApplyValue(values, AttributeType.LifeAdd, 300, true);
29+
AssertEqual(1300, values[(int)AttributeType.Life], "PlayerAttributeComponentAgent.SilentLife");
30+
AssertEqual(false, result.ShouldDispatch, "PlayerAttributeComponentAgent.SilentDispatch");
31+
}
32+
33+
private static void AssertEqual<T>(T expected, T actual, string name)
34+
{
35+
if (!EqualityComparer<T>.Default.Equals(expected, actual))
36+
{
37+
throw new InvalidOperationException(name + " 自检失败,expected=" + expected + ", actual=" + actual + "。");
38+
}
39+
}
40+
}

GameFrameX.Apps.SelfCheck/GameFrameX.Apps.SelfCheck.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<ItemGroup>
1212
<Compile Include="..\GameFrameX.Apps\Player\Attribute\*.cs" Link="Player\Attribute\%(Filename)%(Extension)" />
1313
<Compile Include="..\GameFrameX.Apps\Player\Attribute\Entity\PlayerAttributeState.cs" Link="Player\Attribute\Entity\PlayerAttributeState.cs" />
14+
<Compile Include="..\GameFrameX.Hotfix\Logic\Player\Attribute\PlayerAttributeMutation.cs" Link="Player\Attribute\PlayerAttributeMutation.cs" />
1415
</ItemGroup>
1516

1617
</Project>
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using GameFrameX.Apps.Player.Attribute;
3+
using GameFrameX.Apps.SelfCheck;
34

45
AttributeCoreSelfCheck.Run();
5-
Console.WriteLine("AttributeCoreSelfCheck PASS");
6+
AttributeAgentSelfCheck.Run();
7+
Console.WriteLine("AttributeSelfCheck PASS");

GameFrameX.Apps/Common/Event/EventId.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ public enum EventId
7070
/// </summary>
7171
PlayerSendItem,
7272

73+
/// <summary>
74+
/// 玩家最终属性变化
75+
/// </summary>
76+
AttributeChanged,
77+
7378
#endregion
7479

7580
/// <summary>
@@ -86,4 +91,4 @@ public enum EventId
8691
WorldLevelChange,
8792

8893
#endregion
89-
}
94+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using GameFrameX.Apps.Player.Attribute;
2+
using GameFrameX.Core.Abstractions.Events;
3+
4+
namespace GameFrameX.Apps.Common.EventData;
5+
6+
/// <summary>
7+
/// 玩家最终属性变化事件参数。
8+
/// </summary>
9+
public sealed class AttributeChangedEventArgs : GameEventArgs
10+
{
11+
/// <summary>
12+
/// 玩家ID。
13+
/// </summary>
14+
public long PlayerId { get; }
15+
16+
/// <summary>
17+
/// 发生变化的最终属性。
18+
/// </summary>
19+
public AttributeType AttributeType { get; }
20+
21+
/// <summary>
22+
/// 触发重算的属性槽。
23+
/// </summary>
24+
public AttributeType SourceAttributeType { get; }
25+
26+
/// <summary>
27+
/// 旧最终属性值。
28+
/// </summary>
29+
public long OldValue { get; }
30+
31+
/// <summary>
32+
/// 新最终属性值。
33+
/// </summary>
34+
public long NewValue { get; }
35+
36+
/// <summary>
37+
/// 初始化属性变化事件参数。
38+
/// </summary>
39+
/// <param name="playerId">玩家ID。</param>
40+
/// <param name="attributeType">发生变化的最终属性。</param>
41+
/// <param name="sourceAttributeType">触发重算的属性槽。</param>
42+
/// <param name="oldValue">旧最终属性值。</param>
43+
/// <param name="newValue">新最终属性值。</param>
44+
public AttributeChangedEventArgs(long playerId, AttributeType attributeType, AttributeType sourceAttributeType, long oldValue, long newValue)
45+
{
46+
PlayerId = playerId;
47+
AttributeType = attributeType;
48+
SourceAttributeType = sourceAttributeType;
49+
OldValue = oldValue;
50+
NewValue = newValue;
51+
}
52+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using GameFrameX.Apps.Common.Event;
2+
using GameFrameX.Apps.Common.EventData;
3+
using GameFrameX.Apps.Player.Attribute;
4+
using GameFrameX.Apps.Player.Attribute.Component;
5+
using GameFrameX.Apps.Player.Attribute.Entity;
6+
using GameFrameX.Hotfix.Common.Events;
7+
8+
namespace GameFrameX.Hotfix.Logic.Player.Attribute;
9+
10+
public class PlayerAttributeComponentAgent : StateComponentAgent<PlayerAttributeComponent, PlayerAttributeState>
11+
{
12+
/// <summary>
13+
/// 读取玩家属性值,缺失属性默认返回 0。
14+
/// </summary>
15+
/// <param name="attributeType">属性编号。</param>
16+
/// <returns>当前属性值。</returns>
17+
public long Get(AttributeType attributeType)
18+
{
19+
return OwnerComponent.State.GetValue(attributeType);
20+
}
21+
22+
/// <summary>
23+
/// 设置属性值。派生槽会重算最终属性,最终值变化时派发事件。
24+
/// </summary>
25+
/// <param name="attributeType">属性编号。</param>
26+
/// <param name="value">新属性值。</param>
27+
public Task Set(AttributeType attributeType, long value)
28+
{
29+
return Set(attributeType, value, false);
30+
}
31+
32+
/// <summary>
33+
/// 静默设置属性值,适用于初始化或批量装载。
34+
/// </summary>
35+
/// <param name="attributeType">属性编号。</param>
36+
/// <param name="value">新属性值。</param>
37+
public Task SetSilent(AttributeType attributeType, long value)
38+
{
39+
return Set(attributeType, value, true);
40+
}
41+
42+
/// <summary>
43+
/// 累加属性值。派生槽会重算最终属性,最终值变化时派发事件。
44+
/// </summary>
45+
/// <param name="attributeType">属性编号。</param>
46+
/// <param name="value">累加值。</param>
47+
public Task Add(AttributeType attributeType, long value)
48+
{
49+
return Set(attributeType, Get(attributeType) + value, false);
50+
}
51+
52+
/// <summary>
53+
/// 静默初始化多项属性,只写回状态,不派发业务事件。
54+
/// </summary>
55+
/// <param name="attributes">属性初始值。</param>
56+
public async Task InitializeSilent(Dictionary<AttributeType, long> attributes)
57+
{
58+
if (attributes == null)
59+
{
60+
throw new ArgumentNullException(nameof(attributes));
61+
}
62+
63+
var changed = false;
64+
foreach (var attribute in attributes)
65+
{
66+
changed |= PlayerAttributeMutation.ApplyValue(OwnerComponent.State.Values, attribute.Key, attribute.Value, true).StateChanged;
67+
}
68+
69+
if (changed)
70+
{
71+
await OwnerComponent.WriteStateAsync();
72+
}
73+
}
74+
75+
private async Task Set(AttributeType attributeType, long value, bool silent)
76+
{
77+
var result = PlayerAttributeMutation.ApplyValue(OwnerComponent.State.Values, attributeType, value, silent);
78+
if (!result.StateChanged)
79+
{
80+
return;
81+
}
82+
83+
await OwnerComponent.WriteStateAsync();
84+
if (result.ShouldDispatch)
85+
{
86+
this.Dispatch(EventId.AttributeChanged, new AttributeChangedEventArgs(ActorId, result.FinalAttributeType, result.SourceAttributeType, result.OldFinalValue, result.NewFinalValue));
87+
}
88+
}
89+
90+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using GameFrameX.Apps.Player.Attribute;
4+
5+
namespace GameFrameX.Hotfix.Logic.Player.Attribute;
6+
7+
/// <summary>
8+
/// 玩家属性状态写入与最终值重算逻辑。
9+
/// </summary>
10+
public static class PlayerAttributeMutation
11+
{
12+
/// <summary>
13+
/// 将属性值写入状态字典,并返回最终属性变化判定。
14+
/// </summary>
15+
/// <param name="values">状态字典。</param>
16+
/// <param name="attributeType">属性编号。</param>
17+
/// <param name="value">新属性值。</param>
18+
/// <param name="silent">是否静默写入。</param>
19+
/// <returns>属性写入结果。</returns>
20+
public static AttributeChangeResult ApplyValue(Dictionary<int, long> values, AttributeType attributeType, long value, bool silent)
21+
{
22+
if (values == null)
23+
{
24+
throw new ArgumentNullException(nameof(values));
25+
}
26+
27+
var oldValue = GetValue(values, attributeType);
28+
if (oldValue == value)
29+
{
30+
return AttributeChangeResult.NotChanged(attributeType, value);
31+
}
32+
33+
values[(int)attributeType] = value;
34+
35+
if (AttributeCore.TryGetFinalAttribute(attributeType, out var finalAttribute))
36+
{
37+
var oldFinalValue = GetValue(values, finalAttribute);
38+
var newFinalValue = AttributeCore.RecalculateFinal(ToAttributeDictionary(values), finalAttribute);
39+
values[(int)finalAttribute] = newFinalValue;
40+
return new AttributeChangeResult(attributeType, finalAttribute, oldFinalValue, newFinalValue, true, oldFinalValue != newFinalValue && !silent);
41+
}
42+
43+
if (AttributeCore.IsFinalAttribute(attributeType))
44+
{
45+
return new AttributeChangeResult(attributeType, attributeType, oldValue, value, true, !silent);
46+
}
47+
48+
return new AttributeChangeResult(attributeType, AttributeType.None, 0L, 0L, true, false);
49+
}
50+
51+
private static long GetValue(Dictionary<int, long> values, AttributeType attributeType)
52+
{
53+
long value;
54+
return values.TryGetValue((int)attributeType, out value) ? value : 0L;
55+
}
56+
57+
private static Dictionary<AttributeType, long> ToAttributeDictionary(Dictionary<int, long> values)
58+
{
59+
var attributes = new Dictionary<AttributeType, long>();
60+
foreach (var item in values)
61+
{
62+
attributes[(AttributeType)item.Key] = item.Value;
63+
}
64+
65+
return attributes;
66+
}
67+
}
68+
69+
/// <summary>
70+
/// 属性写入后的最终属性变化判定。
71+
/// </summary>
72+
public sealed class AttributeChangeResult
73+
{
74+
public AttributeType SourceAttributeType { get; }
75+
76+
public AttributeType FinalAttributeType { get; }
77+
78+
public long OldFinalValue { get; }
79+
80+
public long NewFinalValue { get; }
81+
82+
public bool StateChanged { get; }
83+
84+
public bool ShouldDispatch { get; }
85+
86+
public AttributeChangeResult(AttributeType sourceAttributeType, AttributeType finalAttributeType, long oldFinalValue, long newFinalValue, bool stateChanged, bool shouldDispatch)
87+
{
88+
SourceAttributeType = sourceAttributeType;
89+
FinalAttributeType = finalAttributeType;
90+
OldFinalValue = oldFinalValue;
91+
NewFinalValue = newFinalValue;
92+
StateChanged = stateChanged;
93+
ShouldDispatch = shouldDispatch;
94+
}
95+
96+
public static AttributeChangeResult NotChanged(AttributeType sourceAttributeType, long value)
97+
{
98+
return new AttributeChangeResult(sourceAttributeType, AttributeType.None, value, value, false, false);
99+
}
100+
}

0 commit comments

Comments
 (0)