From 9ecb3449d51c67576483b8e8b9f3e6a3a4a834d3 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 28 Aug 2023 12:55:52 -0400 Subject: [PATCH] Core/AreaTriggers: Update areatrigger scalecurve handling with latest research Port From (https://github.com/TrinityCore/TrinityCore/commit/361fe56bc87456b63e685a53ffc63ca1694d336a) --- Source/Framework/Constants/SharedConst.cs | 11 ++ Source/Game/DataStorage/DB2Manager.cs | 116 +++++++++--------- .../Game/Entities/AreaTrigger/AreaTrigger.cs | 108 ++++++++++++---- .../AreaTrigger/AreaTriggerTemplate.cs | 72 ++--------- 4 files changed, 167 insertions(+), 140 deletions(-) diff --git a/Source/Framework/Constants/SharedConst.cs b/Source/Framework/Constants/SharedConst.cs index 318142c78..64941b475 100644 --- a/Source/Framework/Constants/SharedConst.cs +++ b/Source/Framework/Constants/SharedConst.cs @@ -877,6 +877,17 @@ namespace Framework.Constants RaidRestricted = 4 } + public enum CurveInterpolationMode + { + Linear = 0, + Cosine = 1, + CatmullRom = 2, + Bezier3 = 3, + Bezier4 = 4, + Bezier = 5, + Constant = 6, + } + public enum Difficulty : byte { None = 0, diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 85c2d0e98..ddf2f35c1 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -242,14 +242,18 @@ namespace Game.DataStorage foreach (CurrencyContainerRecord currencyContainer in CurrencyContainerStorage.Values) _currencyContainers.Add(currencyContainer.CurrencyTypesID, currencyContainer); - foreach (CurvePointRecord curvePoint in CurvePointStorage.Values) - { + MultiMap unsortedPoints = new(); + foreach (var curvePoint in CurvePointStorage.Values) if (CurveStorage.ContainsKey(curvePoint.CurveID)) - _curvePoints.Add(curvePoint.CurveID, curvePoint); + unsortedPoints.Add(curvePoint.CurveID, curvePoint); + + foreach (var curveId in unsortedPoints.Keys) + { + var curvePoints = unsortedPoints[curveId]; + curvePoints.Sort((point1, point2) => point1.OrderIndex.CompareTo(point2.OrderIndex)); + _curvePoints.AddRange(curveId, curvePoints.Select(p => p.Pos)); } - foreach (var key in _curvePoints.Keys.ToList()) - _curvePoints[key] = _curvePoints[key].OrderBy(point => point.OrderIndex).ToList(); foreach (EmotesTextSoundRecord emoteTextSound in EmotesTextSoundStorage.Values) _emoteTextSounds[Tuple.Create(emoteTextSound.EmotesTextId, emoteTextSound.RaceId, emoteTextSound.SexId, emoteTextSound.ClassId)] = emoteTextSound; @@ -1101,12 +1105,12 @@ namespace Game.DataStorage { var points = _curvePoints.LookupByKey(curveId); if (!points.Empty()) - return Tuple.Create(points.First().Pos.X, points.Last().Pos.X); + return Tuple.Create(points.First().X, points.Last().X); return Tuple.Create(0.0f, 0.0f); } - static CurveInterpolationMode DetermineCurveType(CurveRecord curve, List points) + static CurveInterpolationMode DetermineCurveType(CurveRecord curve, List points) { switch (curve.Type) { @@ -1140,92 +1144,97 @@ namespace Game.DataStorage public float GetCurveValueAt(uint curveId, float x) { + var curve = CurveStorage.LookupByKey(curveId); var points = _curvePoints.LookupByKey(curveId); if (points.Empty()) return 0.0f; - CurveRecord curve = CurveStorage.LookupByKey(curveId); - switch (DetermineCurveType(curve, points)) + return GetCurveValueAt(DetermineCurveType(curve, points), points, x); + } + + public float GetCurveValueAt(CurveInterpolationMode mode, IList points, float x) + { + switch (mode) { case CurveInterpolationMode.Linear: { int pointIndex = 0; - while (pointIndex < points.Count && points[pointIndex].Pos.X <= x) + while (pointIndex < points.Count && points[pointIndex].X <= x) ++pointIndex; if (pointIndex == 0) - return points[0].Pos.Y; + return points[0].Y; if (pointIndex >= points.Count) - return points.Last().Pos.Y; - float xDiff = points[pointIndex].Pos.X - points[pointIndex - 1].Pos.X; + return points[points.Count - 1].Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; if (xDiff == 0.0) - return points[pointIndex].Pos.Y; - return (((x - points[pointIndex - 1].Pos.X) / xDiff) * (points[pointIndex].Pos.Y - points[pointIndex - 1].Pos.Y)) + points[pointIndex - 1].Pos.Y; + return points[pointIndex].Y; + return (((x - points[pointIndex - 1].X) / xDiff) * (points[pointIndex].Y - points[pointIndex - 1].Y)) + points[pointIndex - 1].Y; } case CurveInterpolationMode.Cosine: { int pointIndex = 0; - while (pointIndex < points.Count && points[pointIndex].Pos.X <= x) + while (pointIndex < points.Count && points[pointIndex].X <= x) ++pointIndex; if (pointIndex == 0) - return points[0].Pos.Y; + return points[0].Y; if (pointIndex >= points.Count) - return points.Last().Pos.Y; - float xDiff = points[pointIndex].Pos.X - points[pointIndex - 1].Pos.X; + return points[points.Count - 1].Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; if (xDiff == 0.0) - return points[pointIndex].Pos.Y; - return (float)((points[pointIndex].Pos.Y - points[pointIndex - 1].Pos.Y) * (1.0f - Math.Cos((x - points[pointIndex - 1].Pos.X) / xDiff * Math.PI)) * 0.5f) + points[pointIndex - 1].Pos.Y; + return points[pointIndex].Y; + return (float)((points[pointIndex].Y - points[pointIndex - 1].Y) * (1.0f - Math.Cos((x - points[pointIndex - 1].X) / xDiff * Math.PI)) * 0.5f) + points[pointIndex - 1].Y; } case CurveInterpolationMode.CatmullRom: { int pointIndex = 1; - while (pointIndex < points.Count && points[pointIndex].Pos.X <= x) + while (pointIndex < points.Count && points[pointIndex].X <= x) ++pointIndex; if (pointIndex == 1) - return points[1].Pos.Y; + return points[1].Y; if (pointIndex >= points.Count - 1) - return points[^2].Pos.Y; - float xDiff = points[pointIndex].Pos.X - points[pointIndex - 1].Pos.X; + return points[^2].Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; if (xDiff == 0.0) - return points[pointIndex].Pos.Y; + return points[pointIndex].Y; - float mu = (x - points[pointIndex - 1].Pos.X) / xDiff; - float a0 = -0.5f * points[pointIndex - 2].Pos.Y + 1.5f * points[pointIndex - 1].Pos.Y - 1.5f * points[pointIndex].Pos.Y + 0.5f * points[pointIndex + 1].Pos.Y; - float a1 = points[pointIndex - 2].Pos.Y - 2.5f * points[pointIndex - 1].Pos.Y + 2.0f * points[pointIndex].Pos.Y - 0.5f * points[pointIndex + 1].Pos.Y; - float a2 = -0.5f * points[pointIndex - 2].Pos.Y + 0.5f * points[pointIndex].Pos.Y; - float a3 = points[pointIndex - 1].Pos.Y; + float mu = (x - points[pointIndex - 1].X) / xDiff; + float a0 = -0.5f * points[pointIndex - 2].Y + 1.5f * points[pointIndex - 1].Y - 1.5f * points[pointIndex].Y + 0.5f * points[pointIndex + 1].Y; + float a1 = points[pointIndex - 2].Y - 2.5f * points[pointIndex - 1].Y + 2.0f * points[pointIndex].Y - 0.5f * points[pointIndex + 1].Y; + float a2 = -0.5f * points[pointIndex - 2].Y + 0.5f * points[pointIndex].Y; + float a3 = points[pointIndex - 1].Y; return a0 * mu * mu * mu + a1 * mu * mu + a2 * mu + a3; } case CurveInterpolationMode.Bezier3: { - float xDiff = points[2].Pos.X - points[0].Pos.X; + float xDiff = points[2].X - points[0].X; if (xDiff == 0.0) - return points[1].Pos.Y; - float mu = (x - points[0].Pos.X) / xDiff; - return ((1.0f - mu) * (1.0f - mu) * points[0].Pos.Y) + (1.0f - mu) * 2.0f * mu * points[1].Pos.Y + mu * mu * points[2].Pos.Y; + return points[1].Y; + float mu = (x - points[0].X) / xDiff; + return ((1.0f - mu) * (1.0f - mu) * points[0].Y) + (1.0f - mu) * 2.0f * mu * points[1].Y + mu * mu * points[2].Y; } case CurveInterpolationMode.Bezier4: { - float xDiff = points[3].Pos.X - points[0].Pos.X; + float xDiff = points[3].X - points[0].X; if (xDiff == 0.0) - return points[1].Pos.Y; - float mu = (x - points[0].Pos.X) / xDiff; - return (1.0f - mu) * (1.0f - mu) * (1.0f - mu) * points[0].Pos.Y - + 3.0f * mu * (1.0f - mu) * (1.0f - mu) * points[1].Pos.Y - + 3.0f * mu * mu * (1.0f - mu) * points[2].Pos.Y - + mu * mu * mu * points[3].Pos.Y; + return points[1].Y; + float mu = (x - points[0].X) / xDiff; + return (1.0f - mu) * (1.0f - mu) * (1.0f - mu) * points[0].Y + + 3.0f * mu * (1.0f - mu) * (1.0f - mu) * points[1].Y + + 3.0f * mu * mu * (1.0f - mu) * points[2].Y + + mu * mu * mu * points[3].Y; } case CurveInterpolationMode.Bezier: { - float xDiff = points.Last().Pos.X - points[0].Pos.X; + float xDiff = points[points.Count - 1].X - points[0].X; if (xDiff == 0.0f) - return points.Last().Pos.Y; + return points[points.Count - 1].Y; float[] tmp = new float[points.Count]; for (int c = 0; c < points.Count; ++c) - tmp[c] = points[c].Pos.Y; + tmp[c] = points[c].Y; - float mu = (x - points[0].Pos.X) / xDiff; + float mu = (x - points[0].X) / xDiff; int i = points.Count - 1; while (i > 0) { @@ -1239,7 +1248,7 @@ namespace Game.DataStorage return tmp[0]; } case CurveInterpolationMode.Constant: - return points[0].Pos.Y; + return points[0].Y; default: break; } @@ -2236,7 +2245,7 @@ namespace Game.DataStorage MultiMap _conditionalContentTuning = new(); List<(uint, int)> _contentTuningLabels = new(); MultiMap _currencyContainers = new(); - MultiMap _curvePoints = new(); + MultiMap _curvePoints = new(); Dictionary, EmotesTextSoundRecord> _emoteTextSounds = new(); Dictionary, ExpectedStatRecord> _expectedStatsByLevel = new(); MultiMap _expectedStatModsByContentTuning = new(); @@ -2550,15 +2559,4 @@ namespace Game.DataStorage public List Choices = new(); public List Displays = new(); } - - enum CurveInterpolationMode - { - Linear = 0, - Cosine = 1, - CatmullRom = 2, - Bezier3 = 3, - Bezier4 = 4, - Bezier = 5, - Constant = 6, - } } diff --git a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs index 9664130a1..916f63705 100644 --- a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs +++ b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs @@ -120,27 +120,7 @@ namespace Game.Entities SetUpdateFieldValue(areaTriggerData.ModifyValue(m_areaTriggerData.BoundsRadius2D), GetMaxSearchRadius()); SetUpdateFieldValue(areaTriggerData.ModifyValue(m_areaTriggerData.DecalPropertiesID), GetCreateProperties().DecalPropertiesId); - ScaleCurve extraScaleCurve = areaTriggerData.ModifyValue(m_areaTriggerData.ExtraScaleCurve); - - if (GetCreateProperties().ExtraScale.Structured.StartTimeOffset != 0) - SetUpdateFieldValue(extraScaleCurve.ModifyValue(extraScaleCurve.StartTimeOffset), GetCreateProperties().ExtraScale.Structured.StartTimeOffset); - if (GetCreateProperties().ExtraScale.Structured.X != 0 || GetCreateProperties().ExtraScale.Structured.Y != 0) - { - Vector2 point = new(GetCreateProperties().ExtraScale.Structured.X, GetCreateProperties().ExtraScale.Structured.Y); - SetUpdateFieldValue(ref extraScaleCurve.ModifyValue(extraScaleCurve.Points, 0), point); - } - if (GetCreateProperties().ExtraScale.Structured.Z != 0 || GetCreateProperties().ExtraScale.Structured.W != 0) - { - Vector2 point = new(GetCreateProperties().ExtraScale.Structured.Z, GetCreateProperties().ExtraScale.Structured.W); - SetUpdateFieldValue(ref extraScaleCurve.ModifyValue(extraScaleCurve.Points, 1), point); - } - unsafe - { - if (GetCreateProperties().ExtraScale.Raw.Data[5] != 0) - SetUpdateFieldValue(extraScaleCurve.ModifyValue(extraScaleCurve.ParameterCurve), GetCreateProperties().ExtraScale.Raw.Data[5]); - if (GetCreateProperties().ExtraScale.Structured.OverrideActive != 0) - SetUpdateFieldValue(extraScaleCurve.ModifyValue(extraScaleCurve.OverrideActive), GetCreateProperties().ExtraScale.Structured.OverrideActive != 0); - } + SetScaleCurve(areaTriggerData.ModifyValue(m_areaTriggerData.ExtraScaleCurve), GetCreateProperties().ExtraScale); VisualAnim visualAnim = areaTriggerData.ModifyValue(m_areaTriggerData.VisualAnim); SetUpdateFieldValue(visualAnim.ModifyValue(visualAnim.AnimationDataID), GetCreateProperties().AnimId); @@ -349,6 +329,92 @@ namespace Game.Entities return GetTimeSinceCreated() < GetTimeToTargetScale() ? (float)GetTimeSinceCreated() / GetTimeToTargetScale() : 1.0f; } + float GetScaleCurveValue(ScaleCurve scaleCurve, float x) + { + Cypher.Assert(scaleCurve.OverrideActive, "ScaleCurve must be active to evaluate it"); + + // unpack ParameterCurve + if ((scaleCurve.ParameterCurve & 1) != 0) + return BitConverter.UInt32BitsToSingle((uint)(scaleCurve.ParameterCurve & ~1)); + + Vector2[] points = new Vector2[2]; + for (var i = 0; i < scaleCurve.Points.GetSize(); ++i) + points[i] = new(scaleCurve.Points[i].X, scaleCurve.Points[i].Y); + + CurveInterpolationMode mode = (CurveInterpolationMode)(scaleCurve.ParameterCurve >> 1 & 0x7); + int pointCount = (int)(scaleCurve.ParameterCurve >> 24 & 0xFF); + + return Global.DB2Mgr.GetCurveValueAt(mode, points.AsSpan(0, pointCount).ToArray(), x); + } + + void SetScaleCurve(ScaleCurve scaleCurve, AreaTriggerScaleCurveTemplate curve) + { + if (curve == null) + { + SetUpdateFieldValue(scaleCurve.ModifyValue(scaleCurve.OverrideActive), false); + return; + } + + SetUpdateFieldValue(scaleCurve.ModifyValue(scaleCurve.OverrideActive), true); + SetUpdateFieldValue(scaleCurve.ModifyValue(scaleCurve.StartTimeOffset), curve.StartTimeOffset); + + Position point = null; + // ParameterCurve packing information + // (not_using_points & 1) | ((interpolation_mode & 0x7) << 1) | ((first_point_offset & 0xFFFFF) << 4) | ((point_count & 0xFF) << 24) + // if not_using_points is set then the entire field is simply read as a float (ignoring that lowest bit) + float simpleFloat = curve.Curve; + if (simpleFloat != 0) + { + uint packedCurve = BitConverter.SingleToUInt32Bits(simpleFloat); + packedCurve |= 1; + + SetUpdateFieldValue(scaleCurve.ModifyValue(scaleCurve.ParameterCurve), packedCurve); + + // clear points + for (var i = 0; i < scaleCurve.Points.GetSize(); ++i) + SetUpdateFieldValue(ref scaleCurve.ModifyValue(scaleCurve.Points, i), point); + } + else + { + var curvePoints = curve.CurveTemplate; + if (curvePoints != null) + { + CurveInterpolationMode mode = curvePoints.Mode; + if (curvePoints.Points[1].X < curvePoints.Points[0].X) + mode = CurveInterpolationMode.Constant; + + switch (mode) + { + case CurveInterpolationMode.CatmullRom: + // catmullrom requires at least 4 points, impossible here + mode = CurveInterpolationMode.Cosine; + break; + case CurveInterpolationMode.Bezier3: + case CurveInterpolationMode.Bezier4: + case CurveInterpolationMode.Bezier: + // bezier requires more than 2 points, impossible here + mode = CurveInterpolationMode.Linear; + break; + default: + break; + } + + uint pointCount = 2; + if (mode == CurveInterpolationMode.Constant) + pointCount = 1; + + uint packedCurve = ((uint)mode << 1) | (pointCount << 24); + SetUpdateFieldValue(scaleCurve.ModifyValue(scaleCurve.ParameterCurve), packedCurve); + + for (var i = 0; i < curvePoints.Points.Length; ++i) + { + point.Relocate(curvePoints.Points[i].X, curvePoints.Points[i].Y); + SetUpdateFieldValue(ref scaleCurve.ModifyValue(scaleCurve.Points, i), point); + } + } + } + } + void UpdateTargetList() { List targetList = new(); diff --git a/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs b/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs index a81de5306..1e89d4c09 100644 --- a/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs +++ b/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs @@ -2,7 +2,6 @@ // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; -using Framework.Dynamic; using Game.Maps; using Game.Networking; using System; @@ -92,61 +91,17 @@ namespace Game.Entities } } - /// - /// Scale array definition - /// 0 - time offset from creation for starting of scaling - /// 1+2,3+4 are values for curve points Vector2[2] - // 5 is packed curve information (has_no_data & 1) | ((interpolation_mode & 0x7) << 1) | ((first_point_offset & 0x7FFFFF) << 4) | ((point_count & 0x1F) << 27) - /// 6 bool is_override, only valid for AREATRIGGER_OVERRIDE_SCALE_CURVE, if true then use data from AREATRIGGER_OVERRIDE_SCALE_CURVE instead of ScaleCurveId from CreateObject - /// - [StructLayout(LayoutKind.Explicit)] - public class AreaTriggerScaleInfo + public class AreaTriggerScaleCurvePointsTemplate { - [FieldOffset(0)] - public StructuredData Structured; + public CurveInterpolationMode Mode = CurveInterpolationMode.Linear; + public Vector2[] Points = new Vector2[2]; + } - [FieldOffset(0)] - public RawData Raw; - - [StructLayout(LayoutKind.Explicit)] - public struct StructuredData - { - [FieldOffset(0)] - public uint StartTimeOffset; - - [FieldOffset(4)] - public float X; - - [FieldOffset(8)] - public float Y; - - [FieldOffset(12)] - public float Z; - - [FieldOffset(16)] - public float W; - - [FieldOffset(20)] - public uint CurveParameters; - - [FieldOffset(24)] - public uint OverrideActive; - - public struct curveparameters - { - public uint Raw; - - public uint NoData { get { return Raw & 1; } } - public uint InterpolationMode { get { return (Raw & 0x7) << 1; } } - public uint FirstPointOffset { get { return (Raw & 0x7FFFFF) << 4; } } - public uint PointCount { get { return (Raw & 0x1F) << 27; } } - } - } - - public unsafe struct RawData - { - public fixed uint Data[SharedConst.MaxAreatriggerScale]; - } + public class AreaTriggerScaleCurveTemplate + { + public uint StartTimeOffset; + public float Curve = 1.0f; + public AreaTriggerScaleCurvePointsTemplate CurveTemplate; } public struct AreaTriggerMovementScriptInfo @@ -279,10 +234,7 @@ namespace Game.Entities { public AreaTriggerCreateProperties() { - // legacy code from before it was known what each curve field does - ExtraScale.Raw.Data[5] = 1065353217; - // also OverrideActive does nothing on ExtraScale - ExtraScale.Structured.OverrideActive = 1; + ExtraScale = new(); } public bool HasSplines() { return SplinePoints.Count >= 2; } @@ -324,8 +276,8 @@ namespace Game.Entities public uint TimeToTarget; public uint TimeToTargetScale; - public AreaTriggerScaleInfo OverrideScale = new(); - public AreaTriggerScaleInfo ExtraScale = new(); + public AreaTriggerScaleCurveTemplate OverrideScale; + public AreaTriggerScaleCurveTemplate ExtraScale; public AreaTriggerShapeInfo Shape = new(); public List PolygonVertices = new();