11using System . Diagnostics ;
2-
32using Darkages . Enums ;
43using Darkages . Network . Client ;
54using Darkages . Network . Server ;
@@ -9,79 +8,64 @@ namespace Darkages.Network.Components;
98
109public class PlayerRegenerationComponent ( WorldServer server ) : WorldServerComponent ( server )
1110{
12- private static readonly Stopwatch PlayerRegenControl = new ( ) ;
1311 private const int ComponentSpeed = 1000 ;
1412
1513 protected internal override async Task Update ( )
1614 {
17- var componentStopWatch = new Stopwatch ( ) ;
18- componentStopWatch . Start ( ) ;
19- var variableGameSpeed = ComponentSpeed ;
15+ var sw = Stopwatch . StartNew ( ) ;
16+ var target = ComponentSpeed ;
2017
2118 while ( ServerSetup . Instance . Running )
2219 {
23- if ( componentStopWatch . Elapsed . TotalMilliseconds < variableGameSpeed )
20+ var elapsed = sw . Elapsed . TotalMilliseconds ;
21+ if ( elapsed < target )
2422 {
25- await Task . Delay ( 1 ) ;
23+ var remaining = ( int ) ( target - elapsed ) ;
24+
25+ if ( remaining > 0 )
26+ await Task . Delay ( Math . Min ( remaining , 50 ) ) ;
27+
2628 continue ;
2729 }
2830
29- _ = UpdatePlayerRegeneration ( ) ;
30- var awaiter = ( int ) ( ComponentSpeed - componentStopWatch . Elapsed . TotalMilliseconds ) ;
31+ UpdatePlayerRegeneration ( ) ;
32+
33+ var postElapsed = sw . Elapsed . TotalMilliseconds ;
34+ var overshoot = postElapsed - ComponentSpeed ;
3135
32- if ( awaiter < 0 )
36+ if ( overshoot > 0 && overshoot < ComponentSpeed )
3337 {
34- variableGameSpeed = ComponentSpeed + awaiter ;
35- componentStopWatch . Restart ( ) ;
36- continue ;
38+ // Slightly compensate next tick if we ran late
39+ target = ComponentSpeed - ( int ) overshoot ;
40+ }
41+ else
42+ {
43+ target = ComponentSpeed ;
3744 }
3845
39- await Task . Delay ( TimeSpan . FromMilliseconds ( awaiter ) ) ;
40- variableGameSpeed = ComponentSpeed ;
41- componentStopWatch . Restart ( ) ;
46+ sw . Restart ( ) ;
4247 }
4348 }
4449
45- private static async Task UpdatePlayerRegeneration ( )
50+ private void UpdatePlayerRegeneration ( )
4651 {
47- if ( ! ServerSetup . Instance . Running || ! Server . Aislings . Any ( ) ) return ;
48- const int maxConcurrency = 10 ;
49- var semaphore = new SemaphoreSlim ( maxConcurrency ) ;
50- var playerUpdateTasks = new List < Task > ( ) ;
52+ var players = Server . Aislings
53+ . Where ( p => p ? . Client != null && p . LoggedIn )
54+ . ToList ( ) ;
5155
52- if ( ! PlayerRegenControl . IsRunning )
53- PlayerRegenControl . Start ( ) ;
56+ if ( players . Count == 0 )
57+ return ;
5458
55- if ( PlayerRegenControl . Elapsed . TotalMilliseconds < 1000 ) return ;
56-
57- try
58- {
59- var players = Server . Aislings . Where ( player => player ? . Client != null ) . ToList ( ) ;
60-
61- foreach ( var player in players )
62- {
63- await semaphore . WaitAsync ( ) ;
64- var task = ProcessUpdates ( player ) . ContinueWith ( t =>
65- {
66- semaphore . Release ( ) ;
67- } , TaskScheduler . Default ) ;
68-
69- playerUpdateTasks . Add ( task ) ;
70- }
71-
72- await Task . WhenAll ( playerUpdateTasks ) ;
73- PlayerRegenControl . Restart ( ) ;
74- }
75- catch ( Exception ex )
76- {
77- SentrySdk . CaptureException ( ex ) ;
78- }
59+ foreach ( var player in players )
60+ ProcessUpdates ( player ) ;
7961 }
8062
81- private static Task ProcessUpdates ( Aisling player )
63+ private static void ProcessUpdates ( Aisling player )
8264 {
83- if ( player ? . Client == null ) return Task . CompletedTask ;
84- if ( ! player . LoggedIn ) return Task . CompletedTask ;
65+ if ( player ? . Client == null ) return ;
66+ if ( ! player . LoggedIn ) return ;
67+
68+ // Statuses that disable regen
8569 if ( player . IsPoisoned || player . Skulled || player . IsDead ( ) )
8670 {
8771 player . RegenTimerDisabled = true ;
@@ -91,28 +75,36 @@ private static Task ProcessUpdates(Aisling player)
9175 player . RegenTimerDisabled = false ;
9276 }
9377
94- if ( player . RegenTimerDisabled ) return Task . CompletedTask ;
78+ if ( player . RegenTimerDisabled ) return ;
79+
80+ // Already full
9581 if ( player . CurrentHp == player . MaximumHp &&
96- player . CurrentMp == player . MaximumMp ) return Task . CompletedTask ;
82+ player . CurrentMp == player . MaximumMp )
83+ return ;
9784
85+ // Clamp overflows
9886 if ( player . CurrentHp > player . MaximumHp || player . CurrentMp > player . MaximumMp )
9987 {
10088 player . CurrentHp = player . MaximumHp ;
10189 player . CurrentMp = player . MaximumMp ;
10290 player . Client . SendAttributes ( StatUpdateType . Vitality ) ;
10391 }
10492
105- if ( player . Path == Class . Peasant | player . GameMaster )
93+ // Special-case: peasants / GMs
94+ if ( player . Path == Class . Peasant || player . GameMaster )
10695 {
10796 player . Recover ( ) ;
10897 }
10998
110- if ( player . CurrentMp < 1 ) player . CurrentMp = 1 ;
99+ if ( player . CurrentMp < 1 )
100+ player . CurrentMp = 1 ;
111101
112102 var hpRegenSeed = HpRegenSoftCap ( player . Client ) ;
113103 var mpRegenSeed = MpRegenSoftCap ( player . Client ) ;
104+
114105 var hpHardCap = Math . Abs ( player . BaseHp / 3.00 ) ;
115106 var mpHardCap = Math . Abs ( player . BaseMp / 3.00 ) ;
107+
116108 var performedRegen = false ;
117109
118110 if ( player . CurrentHp < player . MaximumHp )
@@ -129,11 +121,8 @@ private static Task ProcessUpdates(Aisling player)
129121
130122 if ( performedRegen )
131123 player . Client . SendAttributes ( StatUpdateType . Vitality ) ;
132-
133- return Task . CompletedTask ;
134124 }
135125
136-
137126 private static void RegenHpCalculator ( WorldClient client , double seed , double cap )
138127 {
139128 var currentHp = client . Aisling . CurrentHp ;
@@ -172,7 +161,6 @@ private static void RegenMpCalculator(WorldClient client, double seed, double ca
172161 }
173162 }
174163
175-
176164 private static double HpRegenSoftCap ( WorldClient client )
177165 {
178166 var conMod = Math . Abs ( client . Aisling . Con / 3.00 ) ;
0 commit comments