返回提交历史
Added
Common/DeathDomainBackdropTextureSystem.cs
+218
-0
Modified
Common/DeathDomainPrimitiveTextureSystem.cs
+0
-48
Modified
Common/DeathDomainVisualSystem.cs
+144
-4
Modified
Projectiles/DeathDomainHarvestSlashProjectile.cs
+2
-81
XFEstudio/DeathMod
将领域黑核重制为视差背景
b85a5a6
代码差异
4 个文件
+364
-133
@@ -0,0 +1,218 @@
1
using Microsoft.Xna.Framework;
2
using Microsoft.Xna.Framework.Graphics;
3
using System;
4
using Terraria;
5
using Terraria.ModLoader;
6
7
namespace DeathMod.Common;
8
9
/// <summary>
10
/// Procedurally builds the three seamless plates used by the Death Domain's
11
/// clipped parallax window. Textures are created lazily from the draw thread so
12
/// no FNA graphics resource is ever allocated by a content-loader worker.
13
/// </summary>
14
[Autoload(Side = ModSide.Client)]
15
internal sealed class DeathDomainBackdropTextureSystem : ModSystem
16
{
17
internal const int LayerWidth = 1024;
18
internal const int LayerHeight = 512;
19
20
private static readonly Texture2D?[] layers = new Texture2D?[3];
21
22
internal static Texture2D GetLayer(int index)
23
{
24
index = Math.Clamp(index, 0, layers.Length - 1);
25
return layers[index] ??= CreateLayer(index);
26
}
27
28
public override void Unload()
29
{
30
Texture2D?[] oldLayers = (Texture2D?[])layers.Clone();
31
Array.Clear(layers);
32
if (Main.dedServ)
33
return;
34
35
Main.QueueMainThreadAction(() =>
36
{
37
foreach (Texture2D? texture in oldLayers)
38
texture?.Dispose();
39
});
40
}
41
42
private static Texture2D CreateLayer(int layer)
43
{
44
Texture2D texture = new(Main.instance.GraphicsDevice, LayerWidth, LayerHeight, false, SurfaceFormat.Color);
45
Color[] pixels = new Color[LayerWidth * LayerHeight];
46
for (int y = 0; y < LayerHeight; y++)
47
{
48
float v = (y + 0.5f) / LayerHeight;
49
for (int x = 0; x < LayerWidth; x++)
50
{
51
float u = (x + 0.5f) / LayerWidth;
52
pixels[y * LayerWidth + x] = layer switch
53
{
54
0 => BuildFarPixel(x, y, u, v),
55
1 => BuildMiddlePixel(u, v),
56
_ => BuildNearPixel(x, u, v)
57
};
58
}
59
}
60
61
texture.SetData(pixels);
62
return texture;
63
}
64
65
private static Color BuildFarPixel(int x, int y, float u, float v)
66
{
67
Vector3 top = new(0.018f, 0.006f, 0.055f);
68
Vector3 bottom = new(0.105f, 0.004f, 0.072f);
69
Vector3 color = Vector3.Lerp(top, bottom, Smooth01(v));
70
71
float crimsonCloud = Wave(u, v, 2f, 1f, 0.13f)
72
+ Wave(u, v, 5f, -2f, 1.7f) * 0.45f
73
+ Wave(u, v, 9f, 3f, 4.2f) * 0.2f;
74
crimsonCloud = Smooth01((crimsonCloud + 1.1f) / 2.45f);
75
float violetCloud = Smooth01((Wave(u, v, 3f, -2f, 3.4f) + 0.78f) / 1.78f);
76
color += new Vector3(0.16f, 0.006f, 0.055f) * crimsonCloud * (0.32f + v * 0.34f);
77
color += new Vector3(0.035f, 0.018f, 0.13f) * violetCloud * 0.42f;
78
79
float hash = Hash01(x, y, 9137);
80
if (hash > 0.9962f)
81
{
82
float star = MathHelper.Clamp((hash - 0.9962f) / 0.0038f, 0f, 1f);
83
Vector3 starColor = (x + y) % 5 == 0
84
? new Vector3(1f, 0.22f, 0.34f)
85
: new Vector3(0.62f, 0.38f, 0.92f);
86
color = Vector3.Lerp(color, starColor, 0.42f + star * 0.58f);
87
}
88
89
return Opaque(color);
90
}
91
92
private static Color BuildMiddlePixel(float u, float v)
93
{
94
Vector3 color = Vector3.Zero;
95
float alpha = 0f;
96
97
// A distant eclipsed blood moon with a luminous, broken corona.
98
Vector2 moonOffset = new((u - 0.28f) * 2f, (v - 0.28f) * 4f);
99
float moonDistance = moonOffset.Length();
100
float corona = 1f - SmoothRange(0.19f, 0.245f, moonDistance);
101
float hollow = SmoothRange(0.115f, 0.17f, moonDistance);
102
float moonRing = MathHelper.Clamp(corona * hollow, 0f, 1f);
103
if (moonRing > 0f)
104
{
105
float fracture = 0.72f + 0.28f * (float)Math.Sin(Math.Atan2(moonOffset.Y, moonOffset.X) * 11f);
106
alpha = moonRing * fracture * 0.88f;
107
color = new Vector3(0.72f, 0.025f, 0.15f);
108
}
109
110
float cloud = Smooth01((Wave(u, v, 4f, 1f, 0.7f) + Wave(u, v, 7f, -2f, 2.9f) * 0.55f + 0.55f) / 2.1f);
111
float cloudAlpha = cloud * (1f - Math.Abs(v - 0.48f) * 1.55f) * 0.2f;
112
BlendPremultiplied(ref color, ref alpha, new Vector3(0.42f, 0.02f, 0.13f), Math.Max(0f, cloudAlpha));
113
114
// Two ranges of dead mountains create the mid-distance depth plane.
115
float rearHorizon = 0.59f
116
+ (float)Math.Sin(u * MathHelper.TwoPi * 3f + 0.4f) * 0.06f
117
+ (float)Math.Sin(u * MathHelper.TwoPi * 7f) * 0.025f;
118
if (v > rearHorizon)
119
{
120
float depth = Smooth01((v - rearHorizon) / 0.18f);
121
BlendPremultiplied(ref color, ref alpha, new Vector3(0.085f, 0.012f, 0.12f), 0.62f + depth * 0.14f);
122
}
123
124
float frontHorizon = 0.71f
125
+ (float)Math.Sin(u * MathHelper.TwoPi * 5f + 2.1f) * 0.055f
126
+ (float)Math.Sin(u * MathHelper.TwoPi * 11f) * 0.018f;
127
if (v > frontHorizon)
128
BlendPremultiplied(ref color, ref alpha, new Vector3(0.035f, 0.004f, 0.055f), 0.82f);
129
130
return Premultiplied(color, alpha);
131
}
132
133
private static Color BuildNearPixel(int x, float u, float v)
134
{
135
const int cellWidth = 64;
136
int cell = x / cellWidth;
137
float cellPosition = (x % cellWidth + 0.5f) / cellWidth;
138
float seed = Hash01(cell, 0, 4271);
139
float halfWidth = MathHelper.Lerp(0.18f, 0.34f, Hash01(cell, 1, 7727));
140
float spireTip = MathHelper.Lerp(0.43f, 0.64f, seed);
141
float towerShoulder = spireTip + Math.Abs(cellPosition - 0.5f) / halfWidth * 0.18f;
142
bool inTower = Math.Abs(cellPosition - 0.5f) <= halfWidth && v >= towerShoulder;
143
float ground = 0.86f + (float)Math.Sin(u * MathHelper.TwoPi * 8f) * 0.018f;
144
bool inGround = v >= ground;
145
if (!inTower && !inGround)
146
return Color.Transparent;
147
148
float edgeDistance = Math.Abs(v - towerShoulder);
149
Vector3 color = edgeDistance < 0.008f && inTower
150
? new Vector3(0.36f, 0.012f, 0.075f)
151
: new Vector3(0.012f, 0.002f, 0.026f);
152
float alpha = inGround ? 0.96f : 0.88f;
153
154
// Narrow crimson windows keep the skyline readable without brightening it.
155
bool window = inTower
156
&& Math.Abs(cellPosition - 0.5f) < 0.035f
157
&& v > towerShoulder + 0.07f
158
&& PositiveModulo(v * 34f + cell * 0.73f, 1f) < 0.18f;
159
if (window)
160
color = new Vector3(0.68f, 0.012f, 0.09f);
161
162
return Premultiplied(color, alpha);
163
}
164
165
private static void BlendPremultiplied(ref Vector3 color, ref float alpha, Vector3 sourceColor, float sourceAlpha)
166
{
167
sourceAlpha = MathHelper.Clamp(sourceAlpha, 0f, 1f);
168
float combinedAlpha = sourceAlpha + alpha * (1f - sourceAlpha);
169
if (combinedAlpha <= 0.0001f)
170
return;
171
172
color = (sourceColor * sourceAlpha + color * alpha * (1f - sourceAlpha)) / combinedAlpha;
173
alpha = combinedAlpha;
174
}
175
176
private static Color Opaque(Vector3 color) => new(
177
MathHelper.Clamp(color.X, 0f, 1f),
178
MathHelper.Clamp(color.Y, 0f, 1f),
179
MathHelper.Clamp(color.Z, 0f, 1f),
180
1f);
181
182
private static Color Premultiplied(Vector3 color, float alpha)
183
{
184
alpha = MathHelper.Clamp(alpha, 0f, 1f);
185
color *= alpha;
186
return new Color(
187
MathHelper.Clamp(color.X, 0f, 1f),
188
MathHelper.Clamp(color.Y, 0f, 1f),
189
MathHelper.Clamp(color.Z, 0f, 1f),
190
alpha);
191
}
192
193
private static float Wave(float u, float v, float horizontalCycles, float verticalCycles, float phase) =>
194
(float)Math.Sin(MathHelper.TwoPi * (u * horizontalCycles + v * verticalCycles) + phase);
195
196
private static float Hash01(int x, int y, int seed)
197
{
198
uint value = (uint)(x * 374761393 + y * 668265263 + seed * 1442695041);
199
value = (value ^ (value >> 13)) * 1274126177u;
200
value ^= value >> 16;
201
return (value & 0x00FFFFFFu) / 16777215f;
202
}
203
204
private static float SmoothRange(float start, float end, float value) =>
205
Smooth01((value - start) / Math.Max(0.0001f, end - start));
206
207
private static float Smooth01(float value)
208
{
209
value = MathHelper.Clamp(value, 0f, 1f);
210
return value * value * (3f - 2f * value);
211
}
212
213
private static float PositiveModulo(float value, float modulus)
214
{
215
float result = value % modulus;
216
return result < 0f ? result + modulus : result;
217
}
218
}
@@ -134,54 +134,6 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
134
134
DrawBlade(start, rotation, length, width, color, completion);
135
135
}
136
136
137
internal static void DrawBladeRange(
138
Vector2 start,
139
float rotation,
140
float length,
141
float width,
142
Color color,
143
float completion,
144
float rangeStart,
145
float rangeEnd)
146
{
147
Texture2D? texture = BladeMask;
148
if (texture is null && !Main.dedServ)
149
{
150
texture = CreateBladeMask();
151
BladeMask = texture;
152
}
153
154
completion = MathHelper.Clamp(completion, 0f, 1f);
155
rangeStart = MathHelper.Clamp(rangeStart, 0f, 1f);
156
rangeEnd = MathHelper.Clamp(rangeEnd, rangeStart, 1f);
157
if (texture is null
158
|| length <= 0.05f
159
|| width <= 0.05f
160
|| completion <= 0.001f
161
|| rangeEnd - rangeStart <= 0.0001f)
162
{
163
return;
164
}
165
166
float visibleLength = Math.Max(0.5f, length * completion);
167
int sourceStart = Math.Clamp((int)Math.Floor(rangeStart * texture.Width), 0, texture.Width - 1);
168
int sourceEnd = Math.Clamp((int)Math.Ceiling(rangeEnd * texture.Width), sourceStart + 1, texture.Width);
169
Rectangle source = new(sourceStart, 0, sourceEnd - sourceStart, texture.Height);
170
float widthGrowth = MathHelper.Lerp(0.22f, 1f, SmoothStep(0f, 0.55f, completion));
171
Vector2 position = start + rotation.ToRotationVector2() * (visibleLength * sourceStart / texture.Width);
172
173
Main.EntitySpriteDraw(
174
texture,
175
position,
176
source,
177
color,
178
rotation,
179
new Vector2(0f, texture.Height * 0.5f),
180
new Vector2(visibleLength / texture.Width, width * widthGrowth / texture.Height),
181
SpriteEffects.None,
182
0f);
183
}
184
185
137
internal static void DrawRadialGradient(SpriteBatch batch, Vector2 center, float radius, Color color)
186
138
{
187
139
if (Main.dedServ || radius <= 0.5f)
@@ -184,8 +184,25 @@ internal class DeathDomainVisualSystem : ModSystem
184
184
float coreNoiseTime = time * 0.8f;
185
185
const float coreNoiseSeed = 16.2f;
186
186
float coreNoiseAmplitude = Math.Min(coreRadius * 0.18f, MathHelper.Lerp(5f, 10f, mastery) * MathHelper.Lerp(0.35f, 1f, reveal));
187
float blackOpacity = MathHelper.Lerp(0.50f, 0.96f, mastery) * reveal;
188
DrawFilledNoiseDisc(batch, pixel, center, coreRadius, coreNoiseTime, coreNoiseSeed, coreNoiseAmplitude, Color.Black * blackOpacity);
187
DrawClippedDeathDomainBackdrop(
188
batch,
189
center,
190
coreRadius,
191
coreNoiseTime,
192
coreNoiseSeed,
193
coreNoiseAmplitude,
194
time,
195
mastery,
196
reveal);
197
DrawFilledNoiseDisc(
198
batch,
199
pixel,
200
center,
201
coreRadius,
202
coreNoiseTime,
203
coreNoiseSeed,
204
coreNoiseAmplitude,
205
new Color(3, 0, 10) * (MathHelper.Lerp(0.08f, 0.26f, mastery) * reveal));
189
206
DeathDomainPrimitiveTextureSystem.DrawRadialGradient(
190
207
batch,
191
208
center,
@@ -196,6 +213,129 @@ internal class DeathDomainVisualSystem : ModSystem
196
213
DrawInwardSuctionWaves(batch, pixel, center, radius, coreRadius, time, mastery, pulse);
197
214
}
198
215
216
private static void DrawClippedDeathDomainBackdrop(
217
SpriteBatch batch,
218
Vector2 center,
219
float baseRadius,
220
float noiseTime,
221
float noiseSeed,
222
float noiseAmplitude,
223
float time,
224
float mastery,
225
float reveal)
226
{
227
const int boundarySegments = 192;
228
const float preferredBandHeight = 2f;
229
Span<Vector2> boundary = stackalloc Vector2[boundarySegments];
230
float extent = 0f;
231
for (int index = 0; index < boundarySegments; index++)
232
{
233
float angle = MathHelper.TwoPi * index / boundarySegments;
234
float radius = Math.Max(1f, baseRadius + BoundaryNoise(angle, noiseTime, noiseSeed) * noiseAmplitude);
235
boundary[index] = angle.ToRotationVector2() * radius;
236
extent = Math.Max(extent, Math.Abs(boundary[index].Y));
237
}
238
239
int bands = Math.Max(1, (int)Math.Ceiling(extent * 2f / preferredBandHeight));
240
float bandHeight = extent * 2f / bands;
241
Vector2 worldCenter = center + Main.screenPosition;
242
// Draw one complete layer at a time. This retains proper alpha compositing
243
// while allowing SpriteBatch to group hundreds of clipped scan bands into
244
// only three texture batches instead of switching textures for every row.
245
for (int layer = 0; layer < 3; layer++)
246
{
247
float parallax = layer switch { 0 => 0.07f, 1 => 0.18f, _ => 0.34f };
248
Vector2 drift = layer switch
249
{
250
0 => new Vector2(5.5f, -1.5f),
251
1 => new Vector2(-11f, 2.5f),
252
_ => new Vector2(17f, -3.5f)
253
} * time;
254
float opacity = layer switch
255
{
256
0 => MathHelper.Lerp(0.88f, 1f, mastery) * reveal,
257
1 => MathHelper.Lerp(0.72f, 0.9f, mastery) * reveal,
258
_ => MathHelper.Lerp(0.78f, 0.96f, mastery) * reveal
259
};
260
Texture2D texture = DeathDomainBackdropTextureSystem.GetLayer(layer);
261
262
for (int index = 0; index < bands; index++)
263
{
264
float scanY = -extent + (index + 0.5f) * bandHeight;
265
float left = float.MaxValue;
266
float right = float.MinValue;
267
for (int edge = 0; edge < boundarySegments; edge++)
268
{
269
Vector2 start = boundary[edge];
270
Vector2 end = boundary[(edge + 1) % boundarySegments];
271
if ((start.Y <= scanY && end.Y > scanY) || (end.Y <= scanY && start.Y > scanY))
272
{
273
float amount = (scanY - start.Y) / (end.Y - start.Y);
274
float intersectionX = MathHelper.Lerp(start.X, end.X, amount);
275
left = Math.Min(left, intersectionX);
276
right = Math.Max(right, intersectionX);
277
}
278
}
279
280
if (left >= right)
281
continue;
282
283
DrawBackdropBand(
284
batch,
285
texture,
286
new Vector2(center.X + left, center.Y + scanY - bandHeight * 0.5f),
287
right - left,
288
bandHeight,
289
worldCenter * parallax + new Vector2(left, scanY),
290
drift,
291
opacity);
292
}
293
}
294
}
295
296
private static void DrawBackdropBand(
297
SpriteBatch batch,
298
Texture2D texture,
299
Vector2 destination,
300
float destinationWidth,
301
float destinationHeight,
302
Vector2 sampleOrigin,
303
Vector2 drift,
304
float opacity)
305
{
306
if (destinationWidth <= 0.05f || destinationHeight <= 0.05f || opacity <= 0.001f)
307
return;
308
309
int sourceHeight = Math.Clamp((int)Math.Ceiling(destinationHeight), 1, texture.Height);
310
int sourceY = (int)PositiveModulo(sampleOrigin.Y + drift.Y, texture.Height);
311
sourceY = Math.Min(sourceY, texture.Height - sourceHeight);
312
float sourceX = PositiveModulo(sampleOrigin.X + drift.X, texture.Width);
313
float remaining = destinationWidth;
314
float destinationX = destination.X;
315
while (remaining > 0.01f)
316
{
317
int sourceStart = Math.Clamp((int)sourceX, 0, texture.Width - 1);
318
float available = texture.Width - sourceStart;
319
float drawWidth = Math.Min(remaining, available);
320
int sourceWidth = Math.Clamp((int)Math.Ceiling(drawWidth), 1, texture.Width - sourceStart);
321
Rectangle source = new(sourceStart, sourceY, sourceWidth, sourceHeight);
322
batch.Draw(
323
texture,
324
new Vector2(destinationX, destination.Y),
325
source,
326
Color.White * opacity,
327
0f,
328
Vector2.Zero,
329
new Vector2(drawWidth / sourceWidth, destinationHeight / sourceHeight),
330
SpriteEffects.None,
331
0f);
332
333
remaining -= drawWidth;
334
destinationX += drawWidth;
335
sourceX = 0f;
336
}
337
}
338
199
339
private static void DrawSovereignSeal(SpriteBatch batch, Texture2D pixel, Vector2 center, int mastery, float animation)
200
340
{
201
341
float time = Main.GlobalTimeWrappedHourly;
@@ -490,8 +630,8 @@ internal class DeathDomainVisualSystem : ModSystem
490
630
491
631
private static float GetMasteryProgress(int mastery)
492
632
{
493
// A fresh necklace has a mastery score of 5; a fully completed tree reaches 70.
494
return MathHelper.Clamp((mastery - 5f) / 65f, 0f, 1f);
633
// A fresh necklace has a mastery score of 5; a fully completed tree reaches 75.
634
return MathHelper.Clamp((mastery - 5f) / 70f, 0f, 1f);
495
635
}
496
636
497
637
private void ClearAnimationState()
@@ -138,44 +138,12 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
138
138
float width = (21f + Mastery * 13f + (IsRequiem ? 8f : 0f)) * Projectile.scale * 0.78f;
139
139
Vector2 center = Projectile.Center - Main.screenPosition;
140
140
141
Vector2 direction = rotation.ToRotationVector2();
142
float bloodReveal = Smooth01((progress - 0.005f) / 0.12f);
143
float bloodFade = 1f - Smooth01((progress - 0.16f) / 0.80f);
144
float bloodStrength = bloodReveal * bloodFade;
145
float bloodLength = length * (3f + Mastery * 0.47f + (IsRequiem ? 0.3f : 0f));
146
Vector2 bloodStart = center - direction * bloodLength * 0.5f;
147
148
// Draw the execution body first. The crossing blood-line is split around
149
// the currently visible black core below, so no crimson pixels are ever
150
// submitted beneath that area; the darkness no longer depends on overdraw.
151
141
DrawTaperedBladeLayer(center, rotation, length, width * 1.52f * impactPulse, reveal,
152
142
new Color(92, 0, 22, 0) * (strength * 0.54f));
153
143
DrawTaperedBladeLayer(center, rotation, length, width * impactPulse, reveal,
154
144
new Color(255, 8, 48, 255) * strength);
155
float blackCoreLength = length * 0.985f;
156
DrawTaperedBladeLayer(center, rotation, blackCoreLength, width * 0.62f * impactPulse, reveal,
145
DrawTaperedBladeLayer(center, rotation, length * 0.985f, width * 0.62f * impactPulse, reveal,
157
146
Color.Black * Math.Min(1f, strength * 1.32f));
158
159
Vector2 blackCoreStart = center - direction * blackCoreLength * 0.5f;
160
float blackCoreVisibleLength = blackCoreLength * reveal;
161
DrawBladeOutsideSpan(
162
bloodStart,
163
rotation,
164
bloodLength,
165
(10f + Mastery * 3.5f) * Projectile.scale,
166
new Color(95, 0, 24, 0) * (bloodStrength * 0.72f),
167
bloodReveal,
168
blackCoreStart,
169
blackCoreVisibleLength);
170
DrawBladeOutsideSpan(
171
bloodStart,
172
rotation,
173
bloodLength,
174
(2.4f + Mastery * 0.85f) * Projectile.scale,
175
new Color(255, 0, 38, 0) * bloodStrength,
176
bloodReveal,
177
blackCoreStart,
178
blackCoreVisibleLength);
179
147
}
180
148
181
149
private static void DrawTaperedBladeLayer(Vector2 center, float rotation, float length, float maximumWidth, float reveal, Color color)
@@ -187,53 +155,6 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
187
155
DeathDomainPrimitiveTextureSystem.DrawBlade(start, rotation, length, maximumWidth, color, reveal);
188
156
}
189
157
190
private static void DrawBladeOutsideSpan(
191
Vector2 start,
192
float rotation,
193
float length,
194
float width,
195
Color color,
196
float reveal,
197
Vector2 excludedStart,
198
float excludedLength)
199
{
200
float visibleLength = length * MathHelper.Clamp(reveal, 0f, 1f);
201
if (visibleLength <= 0.05f || width <= 0.05f)
202
return;
203
204
Vector2 direction = rotation.ToRotationVector2();
205
float exclusionStart = Vector2.Dot(excludedStart - start, direction);
206
float exclusionEnd = exclusionStart + Math.Max(0f, excludedLength);
207
float firstLength = MathHelper.Clamp(exclusionStart, 0f, visibleLength);
208
if (firstLength > 0.05f)
209
{
210
DeathDomainPrimitiveTextureSystem.DrawBladeRange(
211
start,
212
rotation,
213
length,
214
width,
215
color,
216
reveal,
217
0f,
218
firstLength / visibleLength);
219
}
220
221
float secondStart = MathHelper.Clamp(exclusionEnd, 0f, visibleLength);
222
float secondLength = visibleLength - secondStart;
223
if (secondLength > 0.05f)
224
{
225
DeathDomainPrimitiveTextureSystem.DrawBladeRange(
226
start,
227
rotation,
228
length,
229
width,
230
color,
231
reveal,
232
secondStart / visibleLength,
233
1f);
234
}
235
}
236
237
158
private void SpawnExecutionBurst(int slash)
238
159
{
239
160
float rotation = GetSlashRotation(Projectile.rotation, slash);
@@ -301,7 +222,7 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
301
222
internal static float GetSlashLength(float mastery, float scale, int slash)
302
223
{
303
224
float variation = 0.94f + 0.06f * (float)Math.Sin((slash + 1) * 2.7f);
304
return (154f + mastery * 90f) * scale * variation;
225
return (188f + mastery * 108f) * scale * variation;
305
226
}
306
227
307
228
private static float Smooth01(float value)