From ac239ac36fe572dc0c17473637477ed3fb23b7e9 Mon Sep 17 00:00:00 2001 From: James Tanner Date: Fri, 18 Aug 2023 21:11:45 -0700 Subject: [PATCH] Add apl values for min, max, and sequences, and prot paladin APL preset --- proto/apl.proto | 25 +- sim/core/apl.go | 4 +- sim/core/apl_action.go | 27 +- sim/core/apl_actions_core.go | 9 + sim/core/apl_actions_sequences.go | 2 - sim/core/apl_value.go | 21 + sim/core/apl_values_aura.go | 3 + sim/core/apl_values_operators.go | 163 ++++- sim/core/apl_values_sequences.go | 118 ++++ sim/paladin/protection/TestProtection.results | 640 +++++++++--------- sim/paladin/protection/protection.go | 19 +- .../individual_sim_ui/apl_values.ts | 50 ++ ui/protection_paladin/presets.ts | 35 +- ui/protection_paladin/sim.ts | 4 + 14 files changed, 770 insertions(+), 350 deletions(-) create mode 100644 sim/core/apl_values_sequences.go diff --git a/proto/apl.proto b/proto/apl.proto index 9108945531..e4cca99304 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -51,7 +51,7 @@ message APLAction { } } -// NextIndex: 44 +// NextIndex: 49 message APLValue { oneof value { // Operators @@ -61,6 +61,8 @@ message APLValue { APLValueNot not = 4; APLValueCompare cmp = 5; APLValueMath math = 38; + APLValueMax max = 47; + APLValueMin min = 48; // Encounter values APLValueCurrentTime current_time = 7; @@ -114,6 +116,11 @@ message APLValue { // Dot values APLValueDotIsActive dot_is_active = 6; APLValueDotRemainingTime dot_remaining_time = 13; + + // Sequence values + APLValueSequenceIsComplete sequence_is_complete = 44; + APLValueSequenceIsReady sequence_is_ready = 45; + APLValueSequenceTimeToReady sequence_time_to_ready = 46; } } @@ -229,6 +236,12 @@ message APLValueMath { APLValue lhs = 2; APLValue rhs = 3; } +message APLValueMax { + repeated APLValue vals = 1; +} +message APLValueMin { + repeated APLValue vals = 1; +} message APLValueCurrentTime {} message APLValueCurrentTimePercent {} @@ -353,3 +366,13 @@ message APLValueDotIsActive { message APLValueDotRemainingTime { ActionID spell_id = 1; } + +message APLValueSequenceIsComplete { + string sequence_name = 1; +} +message APLValueSequenceIsReady { + string sequence_name = 1; +} +message APLValueSequenceTimeToReady { + string sequence_name = 1; +} \ No newline at end of file diff --git a/sim/core/apl.go b/sim/core/apl.go index 6055e9a5a5..371961c4ff 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -86,11 +86,11 @@ func (unit *Unit) newAPLRotation(config *proto.APLRotation) *APLRotation { // Finalize for _, action := range rotation.prepullActions { - action.impl.Finalize(rotation) + action.Finalize(rotation) rotation.curWarnings = nil } for i, action := range rotation.priorityList { - action.impl.Finalize(rotation) + action.Finalize(rotation) rotation.priorityListWarnings[configIdxs[i]] = append(rotation.priorityListWarnings[configIdxs[i]], rotation.curWarnings...) rotation.curWarnings = nil diff --git a/sim/core/apl_action.go b/sim/core/apl_action.go index cbcd1e7bbf..96e897d8b1 100644 --- a/sim/core/apl_action.go +++ b/sim/core/apl_action.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "github.com/wowsims/wotlk/sim/core/proto" ) @@ -10,6 +11,13 @@ type APLAction struct { impl APLActionImpl } +func (action *APLAction) Finalize(rot *APLRotation) { + action.impl.Finalize(rot) + for _, value := range action.GetAllAPLValues() { + value.Finalize(rot) + } +} + func (action *APLAction) IsReady(sim *Simulation) bool { return (action.condition == nil || action.condition.GetBool(sim)) && action.impl.IsReady(sim) } @@ -25,6 +33,19 @@ func (action *APLAction) GetAllActions() []*APLAction { return actions } +// Returns all APLValues used by this action and all of its inner Actions. +func (action *APLAction) GetAllAPLValues() []APLValue { + var values []APLValue + for _, a := range action.GetAllActions() { + values = append(values, a.impl.GetAPLValues()...) + if action.condition != nil { + values = append(values, a.condition) + values = append(values, a.condition.GetInnerValues()...) + } + } + return FilterSlice(values, func(val APLValue) bool { return val != nil }) +} + func (action *APLAction) String() string { if action.condition == nil { return fmt.Sprintf("ACTION = %s", action.impl) @@ -34,9 +55,12 @@ func (action *APLAction) String() string { } type APLActionImpl interface { - // Returns all inner Actions. + // Returns all inner APL Actions. GetInnerActions() []*APLAction + // Returns all APLValues used by this Action (but not by inner Actions). + GetAPLValues() []APLValue + // Performs optional post-processing. Finalize(*APLRotation) @@ -58,6 +82,7 @@ type defaultAPLActionImpl struct { } func (impl defaultAPLActionImpl) GetInnerActions() []*APLAction { return nil } +func (impl defaultAPLActionImpl) GetAPLValues() []APLValue { return nil } func (impl defaultAPLActionImpl) Finalize(*APLRotation) {} func (impl defaultAPLActionImpl) Reset(*Simulation) {} diff --git a/sim/core/apl_actions_core.go b/sim/core/apl_actions_core.go index 3e0a8f2b53..696285d4f9 100644 --- a/sim/core/apl_actions_core.go +++ b/sim/core/apl_actions_core.go @@ -74,6 +74,9 @@ func (rot *APLRotation) newActionMultidot(config *proto.APLActionMultidot) APLAc maxOverlap: maxOverlap, } } +func (action *APLActionMultidot) GetAPLValues() []APLValue { + return []APLValue{action.maxOverlap} +} func (action *APLActionMultidot) Reset(*Simulation) { action.nextTarget = nil } @@ -143,6 +146,9 @@ func (rot *APLRotation) newActionMultishield(config *proto.APLActionMultishield) maxOverlap: maxOverlap, } } +func (action *APLActionMultishield) GetAPLValues() []APLValue { + return []APLValue{action.maxOverlap} +} func (action *APLActionMultishield) Reset(*Simulation) { action.nextTarget = nil } @@ -211,6 +217,9 @@ func (rot *APLRotation) newActionWait(config *proto.APLActionWait) APLActionImpl duration: rot.coerceTo(rot.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration), } } +func (action *APLActionWait) GetAPLValues() []APLValue { + return []APLValue{action.duration} +} func (action *APLActionWait) IsReady(sim *Simulation) bool { return action.duration != nil } diff --git a/sim/core/apl_actions_sequences.go b/sim/core/apl_actions_sequences.go index 3aa0992bbe..3c198fb9b6 100644 --- a/sim/core/apl_actions_sequences.go +++ b/sim/core/apl_actions_sequences.go @@ -54,7 +54,6 @@ func (action *APLActionSequence) String() string { type APLActionResetSequence struct { defaultAPLActionImpl - unit *Unit name string sequence *APLActionSequence } @@ -65,7 +64,6 @@ func (rot *APLRotation) newActionResetSequence(config *proto.APLActionResetSeque return nil } return &APLActionResetSequence{ - unit: rot.unit, name: config.SequenceName, } } diff --git a/sim/core/apl_value.go b/sim/core/apl_value.go index b0fbc0e09a..a967694c44 100644 --- a/sim/core/apl_value.go +++ b/sim/core/apl_value.go @@ -7,6 +7,9 @@ import ( ) type APLValue interface { + // Returns all inner APLValues. + GetInnerValues() []APLValue + // The type of value that will be returned. Type() proto.APLValueType @@ -18,6 +21,9 @@ type APLValue interface { GetDuration(*Simulation) time.Duration GetString(*Simulation) string + // Performs optional post-processing. + Finalize(*APLRotation) + // Pretty-print string for debugging. String() string } @@ -26,6 +32,9 @@ type APLValue interface { type defaultAPLValueImpl struct { } +func (impl defaultAPLValueImpl) GetInnerValues() []APLValue { return nil } +func (impl defaultAPLValueImpl) Finalize(*APLRotation) {} + func (impl defaultAPLValueImpl) GetBool(sim *Simulation) bool { panic("Unimplemented GetBool") } @@ -61,6 +70,10 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue { return rot.newValueCompare(config.GetCmp()) case *proto.APLValue_Math: return rot.newValueMath(config.GetMath()) + case *proto.APLValue_Max: + return rot.newValueMax(config.GetMax()) + case *proto.APLValue_Min: + return rot.newValueMin(config.GetMin()) // Encounter case *proto.APLValue_CurrentTime: @@ -152,6 +165,14 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue { case *proto.APLValue_DotRemainingTime: return rot.newValueDotRemainingTime(config.GetDotRemainingTime()) + // Sequences + case *proto.APLValue_SequenceIsComplete: + return rot.newValueSequenceIsComplete(config.GetSequenceIsComplete()) + case *proto.APLValue_SequenceIsReady: + return rot.newValueSequenceIsReady(config.GetSequenceIsReady()) + case *proto.APLValue_SequenceTimeToReady: + return rot.newValueSequenceTimeToReady(config.GetSequenceTimeToReady()) + default: return nil } diff --git a/sim/core/apl_values_aura.go b/sim/core/apl_values_aura.go index a653c547f1..6f8f3d5b2b 100644 --- a/sim/core/apl_values_aura.go +++ b/sim/core/apl_values_aura.go @@ -129,6 +129,9 @@ func (rot *APLRotation) newValueAuraShouldRefresh(config *proto.APLValueAuraShou maxOverlap: maxOverlap, } } +func (value *APLValueAuraShouldRefresh) GetInnerValues() []APLValue { + return []APLValue{value.maxOverlap} +} func (value *APLValueAuraShouldRefresh) Type() proto.APLValueType { return proto.APLValueType_ValueTypeBool } diff --git a/sim/core/apl_values_operators.go b/sim/core/apl_values_operators.go index 577f8c1d2e..9261efd0c9 100644 --- a/sim/core/apl_values_operators.go +++ b/sim/core/apl_values_operators.go @@ -10,6 +10,7 @@ import ( ) type APLValueConst struct { + defaultAPLValueImpl valType proto.APLValueType intVal int32 @@ -80,10 +81,14 @@ func (value *APLValueConst) String() string { } type APLValueCoerced struct { + defaultAPLValueImpl valueType proto.APLValueType inner APLValue } +func (value *APLValueCoerced) GetInnerValues() []APLValue { + return []APLValue{value.inner} +} func (value *APLValueCoerced) Type() proto.APLValueType { return value.valueType } @@ -204,19 +209,34 @@ var aplValueTypeOrder = []proto.APLValueType{ proto.APLValueType_ValueTypeBool, } -// Coerces 2 values into the same type, returning the two new values. -func (rot *APLRotation) coerceToSameType(value1 APLValue, value2 APLValue) (APLValue, APLValue) { - if value1 == nil || value2 == nil { - return value1, value2 - } - - var coercionType proto.APLValueType +func higherOrderType(type1 proto.APLValueType, type2 proto.APLValueType) proto.APLValueType { for _, listType := range aplValueTypeOrder { - if value1.Type() == listType || value2.Type() == listType { - coercionType = listType + if listType == type1 { + return type2 + } else if listType == type2 { + return type1 + } + } + return type1 +} +func highestOrderTypeList(values []APLValue) proto.APLValueType { + coercionType := aplValueTypeOrder[0] + for _, val := range values { + if val != nil { + coercionType = higherOrderType(coercionType, val.Type()) } } - return rot.coerceTo(value1, coercionType), rot.coerceTo(value2, coercionType) + return coercionType +} +func (rot *APLRotation) coerceAllToSameType(values []APLValue) []APLValue { + coercionType := highestOrderTypeList(values) + return MapSlice(values, func(val APLValue) APLValue { return rot.coerceTo(val, coercionType) }) +} + +// Coerces 2 values into the same type, returning the two new values. +func (rot *APLRotation) coerceToSameType(value1 APLValue, value2 APLValue) (APLValue, APLValue) { + coerced := rot.coerceAllToSameType([]APLValue{value1, value2}) + return coerced[0], coerced[1] } type APLValueCompare struct { @@ -242,6 +262,9 @@ func (rot *APLRotation) newValueCompare(config *proto.APLValueCompare) APLValue rhs: rhs, } } +func (value *APLValueCompare) GetInnerValues() []APLValue { + return []APLValue{value.lhs, value.rhs} +} func (value *APLValueCompare) Type() proto.APLValueType { return proto.APLValueType_ValueTypeBool } @@ -350,6 +373,9 @@ func (rot *APLRotation) newValueMath(config *proto.APLValueMath) APLValue { rhs: rhs, } } +func (value *APLValueMath) GetInnerValues() []APLValue { + return []APLValue{value.lhs, value.rhs} +} func (value *APLValueMath) Type() proto.APLValueType { return value.lhs.Type() } @@ -415,7 +441,109 @@ func (value *APLValueMath) GetDuration(sim *Simulation) time.Duration { return 0 } func (value *APLValueMath) String() string { - return fmt.Sprintf("%s %s %s", value.lhs, value.op, value.rhs) + return fmt.Sprintf("Math(%s %s %s)", value.lhs, value.op, value.rhs) +} + +type APLValueMax struct { + defaultAPLValueImpl + vals []APLValue +} + +func (rot *APLRotation) newValueMax(config *proto.APLValueMax) APLValue { + vals := MapSlice(config.Vals, func(val *proto.APLValue) APLValue { + return rot.newAPLValue(val) + }) + vals = rot.coerceAllToSameType(vals) + vals = FilterSlice(vals, func(val APLValue) bool { return val != nil }) + if len(vals) == 0 { + return nil + } else if len(vals) == 1 { + return vals[0] + } + return &APLValueMax{ + vals: vals, + } +} +func (value *APLValueMax) GetInnerValues() []APLValue { + return value.vals +} +func (value *APLValueMax) Type() proto.APLValueType { + return value.vals[0].Type() +} +func (value *APLValueMax) GetInt(sim *Simulation) int32 { + result := value.vals[0].GetInt(sim) + for i := 1; i < len(value.vals); i++ { + result = MaxInt32(result, value.vals[i].GetInt(sim)) + } + return result +} +func (value *APLValueMax) GetFloat(sim *Simulation) float64 { + result := value.vals[0].GetFloat(sim) + for i := 1; i < len(value.vals); i++ { + result = MaxFloat(result, value.vals[i].GetFloat(sim)) + } + return result +} +func (value *APLValueMax) GetDuration(sim *Simulation) time.Duration { + result := value.vals[0].GetDuration(sim) + for i := 1; i < len(value.vals); i++ { + result = MaxDuration(result, value.vals[i].GetDuration(sim)) + } + return result +} +func (value *APLValueMax) String() string { + return fmt.Sprintf("Max(%s)", strings.Join(MapSlice(value.vals, func(subvalue APLValue) string { return fmt.Sprintf("(%s)", subvalue) }), ", ")) +} + +type APLValueMin struct { + defaultAPLValueImpl + vals []APLValue +} + +func (rot *APLRotation) newValueMin(config *proto.APLValueMin) APLValue { + vals := MapSlice(config.Vals, func(val *proto.APLValue) APLValue { + return rot.newAPLValue(val) + }) + vals = rot.coerceAllToSameType(vals) + vals = FilterSlice(vals, func(val APLValue) bool { return val != nil }) + if len(vals) == 0 { + return nil + } else if len(vals) == 1 { + return vals[0] + } + return &APLValueMin{ + vals: vals, + } +} +func (value *APLValueMin) GetInnerValues() []APLValue { + return value.vals +} +func (value *APLValueMin) Type() proto.APLValueType { + return value.vals[0].Type() +} +func (value *APLValueMin) GetInt(sim *Simulation) int32 { + result := value.vals[0].GetInt(sim) + for i := 1; i < len(value.vals); i++ { + result = MinInt32(result, value.vals[i].GetInt(sim)) + } + return result +} +func (value *APLValueMin) GetFloat(sim *Simulation) float64 { + result := value.vals[0].GetFloat(sim) + for i := 1; i < len(value.vals); i++ { + result = MinFloat(result, value.vals[i].GetFloat(sim)) + } + return result +} +func (value *APLValueMin) GetDuration(sim *Simulation) time.Duration { + result := value.vals[0].GetDuration(sim) + for i := 1; i < len(value.vals); i++ { + result = MinDuration(result, value.vals[i].GetDuration(sim)) + } + return result +} +func (value *APLValueMin) String() string { + return fmt.Sprintf("Min(%s)", strings.Join(MapSlice(value.vals, func(subvalue APLValue) string { return fmt.Sprintf("(%s)", subvalue) }), ", ")) } type APLValueAnd struct { @@ -430,11 +558,16 @@ func (rot *APLRotation) newValueAnd(config *proto.APLValueAnd) APLValue { vals = FilterSlice(vals, func(val APLValue) bool { return val != nil }) if len(vals) == 0 { return nil + } else if len(vals) == 1 { + return vals[0] } return &APLValueAnd{ vals: vals, } } +func (value *APLValueAnd) GetInnerValues() []APLValue { + return value.vals +} func (value *APLValueAnd) Type() proto.APLValueType { return proto.APLValueType_ValueTypeBool } @@ -462,11 +595,16 @@ func (rot *APLRotation) newValueOr(config *proto.APLValueOr) APLValue { vals = FilterSlice(vals, func(val APLValue) bool { return val != nil }) if len(vals) == 0 { return nil + } else if len(vals) == 1 { + return vals[0] } return &APLValueOr{ vals: vals, } } +func (value *APLValueOr) GetInnerValues() []APLValue { + return value.vals +} func (value *APLValueOr) Type() proto.APLValueType { return proto.APLValueType_ValueTypeBool } @@ -496,6 +634,9 @@ func (rot *APLRotation) newValueNot(config *proto.APLValueNot) APLValue { val: val, } } +func (value *APLValueNot) GetInnerValues() []APLValue { + return []APLValue{value.val} +} func (value *APLValueNot) Type() proto.APLValueType { return proto.APLValueType_ValueTypeBool } diff --git a/sim/core/apl_values_sequences.go b/sim/core/apl_values_sequences.go new file mode 100644 index 0000000000..0c035ce481 --- /dev/null +++ b/sim/core/apl_values_sequences.go @@ -0,0 +1,118 @@ +package core + +import ( + "fmt" + "time" + + "github.com/wowsims/wotlk/sim/core/proto" +) + +type APLValueSequenceIsComplete struct { + defaultAPLValueImpl + name string + sequence *APLActionSequence +} + +func (rot *APLRotation) newValueSequenceIsComplete(config *proto.APLValueSequenceIsComplete) APLValue { + if config.SequenceName == "" { + rot.validationWarning("Sequence Is Complete() must provide a sequence name") + return nil + } + return &APLValueSequenceIsComplete{ + name: config.SequenceName, + } +} +func (value *APLValueSequenceIsComplete) Finalize(rot *APLRotation) { + for _, otherAction := range rot.allAPLActions() { + if sequence, ok := otherAction.impl.(*APLActionSequence); ok && sequence.name == value.name { + value.sequence = sequence + return + } + } + rot.validationWarning("No sequence with name: '%s'", value.name) +} +func (value *APLValueSequenceIsComplete) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeBool +} +func (value *APLValueSequenceIsComplete) GetBool(sim *Simulation) bool { + return value.sequence.curIdx >= len(value.sequence.actions) +} +func (value *APLValueSequenceIsComplete) String() string { + return fmt.Sprintf("Sequence Is Complete(%s)", value.name) +} + +type APLValueSequenceIsReady struct { + defaultAPLValueImpl + name string + sequence *APLActionSequence +} + +func (rot *APLRotation) newValueSequenceIsReady(config *proto.APLValueSequenceIsReady) APLValue { + if config.SequenceName == "" { + rot.validationWarning("Sequence Is Ready() must provide a sequence name") + return nil + } + return &APLValueSequenceIsReady{ + name: config.SequenceName, + } +} +func (value *APLValueSequenceIsReady) Finalize(rot *APLRotation) { + for _, otherAction := range rot.allAPLActions() { + if sequence, ok := otherAction.impl.(*APLActionSequence); ok && sequence.name == value.name { + value.sequence = sequence + return + } + } + rot.validationWarning("No sequence with name: '%s'", value.name) +} +func (value *APLValueSequenceIsReady) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeBool +} +func (value *APLValueSequenceIsReady) GetBool(sim *Simulation) bool { + return value.sequence.IsReady(sim) +} +func (value *APLValueSequenceIsReady) String() string { + return fmt.Sprintf("Sequence Is Ready(%s)", value.name) +} + +type APLValueSequenceTimeToReady struct { + defaultAPLValueImpl + name string + sequence *APLActionSequence +} + +func (rot *APLRotation) newValueSequenceTimeToReady(config *proto.APLValueSequenceTimeToReady) APLValue { + if config.SequenceName == "" { + rot.validationWarning("Sequence Time To Ready() must provide a sequence name") + return nil + } + return &APLValueSequenceTimeToReady{ + name: config.SequenceName, + } +} +func (value *APLValueSequenceTimeToReady) Finalize(rot *APLRotation) { + for _, otherAction := range rot.allAPLActions() { + if sequence, ok := otherAction.impl.(*APLActionSequence); ok && sequence.name == value.name { + value.sequence = sequence + return + } + } + rot.validationWarning("No sequence with name: '%s'", value.name) +} +func (value *APLValueSequenceTimeToReady) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeDuration +} +func (value *APLValueSequenceTimeToReady) GetDuration(sim *Simulation) time.Duration { + if value.sequence.curIdx >= len(value.sequence.actions) { + return NeverExpires + } else if subaction, ok := value.sequence.actions[value.sequence.curIdx].impl.(*APLActionCastSpell); ok { + return subaction.spell.TimeToReady(sim) + } else if value.sequence.IsReady(sim) { + return 0 + } else { + return 3 * time.Second + } +} +func (value *APLValueSequenceTimeToReady) String() string { + return fmt.Sprintf("Sequence Time To Ready(%s)", value.name) +} diff --git a/sim/paladin/protection/TestProtection.results b/sim/paladin/protection/TestProtection.results index 985a1b0bb5..2cb69bffe4 100644 --- a/sim/paladin/protection/TestProtection.results +++ b/sim/paladin/protection/TestProtection.results @@ -46,1112 +46,1112 @@ character_stats_results: { dps_results: { key: "TestProtection-AllItems-AegisBattlegear" value: { - dps: 3514.3717 - tps: 8402.72322 + dps: 3469.53338 + tps: 8285.9601 } } dps_results: { key: "TestProtection-AllItems-AegisPlate" value: { - dps: 3293.28472 - tps: 7945.82573 + dps: 3133.19945 + tps: 7537.34196 } } dps_results: { key: "TestProtection-AllItems-Althor'sAbacus-50359" value: { - dps: 3283.02178 - tps: 7914.46577 + dps: 3154.43766 + tps: 7574.0778 } } dps_results: { key: "TestProtection-AllItems-Althor'sAbacus-50366" value: { - dps: 3285.62491 - tps: 7921.16622 + dps: 3156.85028 + tps: 7580.2879 } } dps_results: { key: "TestProtection-AllItems-AshtongueTalismanofZeal-32489" value: { - dps: 3289.93155 - tps: 7932.25152 + dps: 3157.65061 + tps: 7582.34794 } } dps_results: { key: "TestProtection-AllItems-AustereEarthsiegeDiamond" value: { - dps: 3248.9732 - tps: 7826.82473 + dps: 3120.13779 + tps: 7485.78995 } } dps_results: { key: "TestProtection-AllItems-Bandit'sInsignia-40371" value: { - dps: 3387.21154 - tps: 8104.62852 + dps: 3257.8296 + tps: 7762.9157 } } dps_results: { key: "TestProtection-AllItems-BaubleofTrueBlood-50354" value: { - dps: 3261.91246 - tps: 7860.13037 - hps: 87.72491 + dps: 3134.97726 + tps: 7523.98675 + hps: 87.7113 } } dps_results: { key: "TestProtection-AllItems-BaubleofTrueBlood-50726" value: { - dps: 3261.91246 - tps: 7860.13037 - hps: 87.72491 + dps: 3134.97726 + tps: 7523.98675 + hps: 87.7113 } } dps_results: { key: "TestProtection-AllItems-BeamingEarthsiegeDiamond" value: { - dps: 3276.12068 - tps: 7898.41787 + dps: 3146.33238 + tps: 7551.41679 } } dps_results: { key: "TestProtection-AllItems-Beast-tamer'sShoulders-30892" value: { - dps: 3182.74119 - tps: 7664.69134 + dps: 3066.49465 + tps: 7355.5999 } } dps_results: { key: "TestProtection-AllItems-BlessedBattlegearofUndeadSlaying" value: { - dps: 2874.00179 - tps: 6898.93368 + dps: 2847.07282 + tps: 6831.36548 } } dps_results: { key: "TestProtection-AllItems-BlessedGarboftheUndeadSlayer" value: { - dps: 2917.18443 - tps: 7024.898 + dps: 2806.95645 + tps: 6745.10515 } } dps_results: { key: "TestProtection-AllItems-BlessedRegaliaofUndeadCleansing" value: { - dps: 2824.68746 - tps: 6828.92963 + dps: 2697.6449 + tps: 6493.34991 } } dps_results: { key: "TestProtection-AllItems-BracingEarthsiegeDiamond" value: { - dps: 3251.9313 - tps: 7679.73134 + dps: 3122.87941 + tps: 7344.94205 } } dps_results: { key: "TestProtection-AllItems-ChaoticSkyflareDiamond" value: { - dps: 3290.3143 - tps: 7923.34888 + dps: 3161.62157 + tps: 7581.81655 } } dps_results: { key: "TestProtection-AllItems-CorpseTongueCoin-50349" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-CorpseTongueCoin-50352" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-CorrodedSkeletonKey-50356" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 hps: 64 } } dps_results: { key: "TestProtection-AllItems-DarkmoonCard:Berserker!-42989" value: { - dps: 3307.16237 - tps: 7965.12016 + dps: 3177.46554 + tps: 7622.35332 } } dps_results: { key: "TestProtection-AllItems-DarkmoonCard:Death-42990" value: { - dps: 3365.52189 - tps: 8045.12917 + dps: 3236.12531 + tps: 7703.34324 } } dps_results: { key: "TestProtection-AllItems-DarkmoonCard:Greatness-44255" value: { - dps: 3425.3691 - tps: 8266.7202 + dps: 3271.27787 + tps: 7857.52912 } } dps_results: { key: "TestProtection-AllItems-DeadlyGladiator'sLibramofFortitude-42852" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-Death'sChoice-47464" value: { - dps: 3582.48152 - tps: 8610.03615 + dps: 3450.79115 + tps: 8260.72371 } } dps_results: { key: "TestProtection-AllItems-DeathKnight'sAnguish-38212" value: { - dps: 3292.49899 - tps: 7931.12385 + dps: 3166.52929 + tps: 7598.36967 } } dps_results: { key: "TestProtection-AllItems-Deathbringer'sWill-50362" value: { - dps: 3406.23636 - tps: 8171.26257 + dps: 3299.506 + tps: 7889.20442 } } dps_results: { key: "TestProtection-AllItems-Deathbringer'sWill-50363" value: { - dps: 3426.4487 - tps: 8208.42969 + dps: 3326.76169 + tps: 7944.72761 } } dps_results: { key: "TestProtection-AllItems-Defender'sCode-40257" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-DestructiveSkyflareDiamond" value: { - dps: 3263.12411 - tps: 7860.71969 + dps: 3134.414 + tps: 7519.48097 } } dps_results: { key: "TestProtection-AllItems-DislodgedForeignObject-50348" value: { - dps: 3374.19131 - tps: 8106.00136 + dps: 3306.89738 + tps: 7936.68617 } } dps_results: { key: "TestProtection-AllItems-DislodgedForeignObject-50353" value: { - dps: 3392.58454 - tps: 8153.96044 + dps: 3308.77168 + tps: 7942.45361 } } dps_results: { key: "TestProtection-AllItems-EffulgentSkyflareDiamond" value: { - dps: 3248.9732 - tps: 7826.82473 + dps: 3120.13779 + tps: 7485.78995 } } dps_results: { key: "TestProtection-AllItems-EmberSkyflareDiamond" value: { - dps: 3267.16552 - tps: 7873.49308 + dps: 3131.52213 + tps: 7514.68947 } } dps_results: { key: "TestProtection-AllItems-EnigmaticSkyflareDiamond" value: { - dps: 3261.13962 - tps: 7855.81471 + dps: 3132.90152 + tps: 7515.97776 } } dps_results: { key: "TestProtection-AllItems-EnigmaticStarflareDiamond" value: { - dps: 3259.79001 - tps: 7852.96387 + dps: 3130.96335 + tps: 7511.74883 } } dps_results: { key: "TestProtection-AllItems-EphemeralSnowflake-50260" value: { - dps: 3304.077 - tps: 7943.32524 + dps: 3184.38201 + tps: 7633.44107 } } dps_results: { key: "TestProtection-AllItems-EternalEarthsiegeDiamond" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-ExtractofNecromanticPower-40373" value: { - dps: 3361.45285 - tps: 8041.12124 + dps: 3229.89051 + tps: 7692.70433 } } dps_results: { key: "TestProtection-AllItems-EyeoftheBroodmother-45308" value: { - dps: 3318.42219 - tps: 7994.68123 + dps: 3187.12542 + tps: 7648.18876 } } dps_results: { key: "TestProtection-AllItems-Figurine-SapphireOwl-42413" value: { - dps: 3298.09486 - tps: 7960.91599 + dps: 3147.53859 + tps: 7562.23591 } } dps_results: { key: "TestProtection-AllItems-ForethoughtTalisman-40258" value: { - dps: 3274.97575 - tps: 7893.75529 + dps: 3146.98046 + tps: 7554.88297 } } dps_results: { key: "TestProtection-AllItems-ForgeEmber-37660" value: { - dps: 3296.35569 - tps: 7940.67065 + dps: 3167.01885 + tps: 7599.33443 } } dps_results: { key: "TestProtection-AllItems-ForlornSkyflareDiamond" value: { - dps: 3251.9313 - tps: 7834.43887 + dps: 3122.87941 + tps: 7492.84687 } } dps_results: { key: "TestProtection-AllItems-ForlornStarflareDiamond" value: { - dps: 3251.33968 - tps: 7832.91604 + dps: 3122.33108 + tps: 7491.43548 } } dps_results: { key: "TestProtection-AllItems-FuriousGladiator'sLibramofFortitude-42853" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-FuryoftheFiveFlights-40431" value: { - dps: 3385.12932 - tps: 8146.34481 + dps: 3254.13226 + tps: 7799.31989 } } dps_results: { key: "TestProtection-AllItems-FuturesightRune-38763" value: { - dps: 3269.17788 - tps: 7878.83156 + dps: 3141.60689 + tps: 7541.05141 } } dps_results: { key: "TestProtection-AllItems-Gladiator'sVindication" value: { - dps: 3624.61041 - tps: 8667.84697 + dps: 3587.93378 + tps: 8564.04084 } } dps_results: { key: "TestProtection-AllItems-GlowingTwilightScale-54573" value: { - dps: 3284.32334 - tps: 7917.816 + dps: 3155.64397 + tps: 7577.18285 } } dps_results: { key: "TestProtection-AllItems-GlowingTwilightScale-54589" value: { - dps: 3287.28144 - tps: 7925.43015 + dps: 3158.38559 + tps: 7584.23977 } } dps_results: { key: "TestProtection-AllItems-GnomishLightningGenerator-41121" value: { - dps: 3337.41295 - tps: 8001.73184 + dps: 3205.72844 + tps: 7655.16374 } } dps_results: { key: "TestProtection-AllItems-HatefulGladiator'sLibramofFortitude-42851" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-IllustrationoftheDragonSoul-40432" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-ImpassiveSkyflareDiamond" value: { - dps: 3261.13962 - tps: 7855.81471 + dps: 3132.90152 + tps: 7515.97776 } } dps_results: { key: "TestProtection-AllItems-ImpassiveStarflareDiamond" value: { - dps: 3259.79001 - tps: 7852.96387 + dps: 3130.96335 + tps: 7511.74883 } } dps_results: { key: "TestProtection-AllItems-IncisorFragment-37723" value: { - dps: 3329.05608 - tps: 8008.49936 + dps: 3200.71012 + tps: 7667.91088 } } dps_results: { key: "TestProtection-AllItems-InsightfulEarthsiegeDiamond" value: { - dps: 3286.23773 - tps: 7937.13872 + dps: 3133.12078 + tps: 7530.31227 } } dps_results: { key: "TestProtection-AllItems-InvigoratingEarthsiegeDiamond" value: { - dps: 3265.69109 - tps: 7865.61135 + dps: 3136.33211 + tps: 7523.17111 hps: 16.14611 } } dps_results: { key: "TestProtection-AllItems-Lavanthor'sTalisman-37872" value: { - dps: 3281.57638 - tps: 7910.74531 + dps: 3156.51835 + tps: 7579.43352 } } dps_results: { key: "TestProtection-AllItems-LibramofFuriousBlows-37574" value: { - dps: 3221.53813 - tps: 7753.89395 + dps: 3112.73487 + tps: 7464.2355 } } dps_results: { key: "TestProtection-AllItems-LibramofReciprocation-40706" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-LibramofThreeTruths-50455" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-LibramofValiance-47661" value: { - dps: 3446.36817 - tps: 8285.58215 + dps: 3332.83055 + tps: 7983.16568 } } dps_results: { key: "TestProtection-AllItems-LibramoftheSacredShield-45145" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-LightbringerBattlegear" value: { - dps: 2866.36563 - tps: 6892.7913 + dps: 2728.13679 + tps: 6526.13214 } } dps_results: { key: "TestProtection-AllItems-LightswornBattlegear" value: { - dps: 3837.26347 - tps: 9150.22382 + dps: 3779.34609 + tps: 9011.01061 } } dps_results: { key: "TestProtection-AllItems-LightswornPlate" value: { - dps: 3365.13005 - tps: 8091.28288 + dps: 3247.92904 + tps: 7777.03646 } } dps_results: { key: "TestProtection-AllItems-MajesticDragonFigurine-40430" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-MeteoriteWhetstone-37390" value: { - dps: 3360.6914 - tps: 8087.35575 + dps: 3233.97098 + tps: 7761.03687 } } dps_results: { key: "TestProtection-AllItems-NevermeltingIceCrystal-50259" value: { - dps: 3335.07097 - tps: 8029.13802 + dps: 3207.51144 + tps: 7692.89027 } } dps_results: { key: "TestProtection-AllItems-OfferingofSacrifice-37638" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-PersistentEarthshatterDiamond" value: { - dps: 3262.50673 - tps: 7858.22342 + dps: 3133.24747 + tps: 7516.05089 } } dps_results: { key: "TestProtection-AllItems-PersistentEarthsiegeDiamond" value: { - dps: 3265.69109 - tps: 7865.61135 + dps: 3136.33211 + tps: 7523.17111 } } dps_results: { key: "TestProtection-AllItems-PetrifiedScarab-21685" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-PetrifiedTwilightScale-54571" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-PetrifiedTwilightScale-54591" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-PowerfulEarthshatterDiamond" value: { - dps: 3248.9732 - tps: 7826.82473 + dps: 3120.13779 + tps: 7485.78995 } } dps_results: { key: "TestProtection-AllItems-PowerfulEarthsiegeDiamond" value: { - dps: 3248.9732 - tps: 7826.82473 + dps: 3120.13779 + tps: 7485.78995 } } dps_results: { key: "TestProtection-AllItems-PurifiedShardoftheGods" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-RedemptionBattlegear" value: { - dps: 3225.71496 - tps: 7686.59722 + dps: 3237.72456 + tps: 7716.6291 } } dps_results: { key: "TestProtection-AllItems-RedemptionPlate" value: { - dps: 3136.13404 - tps: 7562.13522 + dps: 3036.62651 + tps: 7294.31061 } } dps_results: { key: "TestProtection-AllItems-ReignoftheDead-47316" value: { - dps: 3414.78706 - tps: 8101.8708 + dps: 3274.52229 + tps: 7748.43842 } } dps_results: { key: "TestProtection-AllItems-ReignoftheDead-47477" value: { - dps: 3433.48014 - tps: 8131.03797 + dps: 3291.2028 + tps: 7774.5501 } } dps_results: { key: "TestProtection-AllItems-RelentlessEarthsiegeDiamond" value: { - dps: 3289.65231 - tps: 7921.86506 + dps: 3160.47724 + tps: 7579.05957 } } dps_results: { key: "TestProtection-AllItems-RelentlessGladiator'sLibramofFortitude-42854" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-RevitalizingSkyflareDiamond" value: { - dps: 3257.13927 - tps: 7849.42769 + dps: 3129.9194 + tps: 7510.02455 } } dps_results: { key: "TestProtection-AllItems-RuneofRepulsion-40372" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-SavageGladiator'sLibramofFortitude-42611" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-AllItems-SealofthePantheon-36993" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-ShinyShardoftheGods" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-Sindragosa'sFlawlessFang-50361" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-SliverofPureIce-50339" value: { - dps: 3320.55712 - tps: 8018.89316 + dps: 3165.16872 + tps: 7606.73889 } } dps_results: { key: "TestProtection-AllItems-SliverofPureIce-50346" value: { - dps: 3321.06544 - tps: 8020.72793 + dps: 3167.40038 + tps: 7612.90318 } } dps_results: { key: "TestProtection-AllItems-SoulPreserver-37111" value: { - dps: 3270.71609 - tps: 7882.79092 + dps: 3143.03253 + tps: 7544.72101 } } dps_results: { key: "TestProtection-AllItems-SouloftheDead-40382" value: { - dps: 3303.60337 - tps: 7956.53758 + dps: 3174.6824 + tps: 7615.75353 } } dps_results: { key: "TestProtection-AllItems-SparkofLife-37657" value: { - dps: 3343.86911 - tps: 8057.96741 + dps: 3180.59789 + tps: 7627.83443 } } dps_results: { key: "TestProtection-AllItems-SphereofRedDragon'sBlood-37166" value: { - dps: 3362.83282 - tps: 8101.06839 + dps: 3228.06445 + tps: 7746.05491 } } dps_results: { key: "TestProtection-AllItems-StormshroudArmor" value: { - dps: 2676.29879 - tps: 6453.52267 + dps: 2569.08761 + tps: 6171.58279 } } dps_results: { key: "TestProtection-AllItems-SwiftSkyflareDiamond" value: { - dps: 3265.69109 - tps: 7865.61135 + dps: 3136.33211 + tps: 7523.17111 } } dps_results: { key: "TestProtection-AllItems-SwiftStarflareDiamond" value: { - dps: 3262.50673 - tps: 7858.22342 + dps: 3133.24747 + tps: 7516.05089 } } dps_results: { key: "TestProtection-AllItems-SwiftWindfireDiamond" value: { - dps: 3256.9341 - tps: 7845.29455 + dps: 3127.84937 + tps: 7503.5905 } } dps_results: { key: "TestProtection-AllItems-TalismanofTrollDivinity-37734" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-TearsoftheVanquished-47215" value: { - dps: 3298.09486 - tps: 7965.23285 + dps: 3147.53859 + tps: 7565.99721 } } dps_results: { key: "TestProtection-AllItems-TheGeneral'sHeart-45507" value: { - dps: 3261.84179 - tps: 7859.94848 + dps: 3134.80768 + tps: 7523.55024 } } dps_results: { key: "TestProtection-AllItems-TheTwinBladesofAzzinoth" value: { - dps: 3103.63914 - tps: 7496.90847 + dps: 2991.11809 + tps: 7203.62799 } } dps_results: { key: "TestProtection-AllItems-ThunderingSkyflareDiamond" value: { - dps: 3244.68899 - tps: 7811.53989 + dps: 3131.52119 + tps: 7510.94555 } } dps_results: { key: "TestProtection-AllItems-TinyAbominationinaJar-50351" value: { - dps: 3537.65486 - tps: 8467.15665 + dps: 3330.61033 + tps: 7944.85364 } } dps_results: { key: "TestProtection-AllItems-TinyAbominationinaJar-50706" value: { - dps: 3582.75486 - tps: 8578.60341 + dps: 3369.78615 + tps: 8036.54267 } } dps_results: { key: "TestProtection-AllItems-TirelessSkyflareDiamond" value: { - dps: 3251.9313 - tps: 7834.43887 + dps: 3122.87941 + tps: 7492.84687 } } dps_results: { key: "TestProtection-AllItems-TirelessStarflareDiamond" value: { - dps: 3251.33968 - tps: 7832.91604 + dps: 3122.33108 + tps: 7491.43548 } } dps_results: { key: "TestProtection-AllItems-TomeofArcanePhenomena-36972" value: { - dps: 3324.60994 - tps: 8008.18194 + dps: 3169.26413 + tps: 7603.518 } } dps_results: { key: "TestProtection-AllItems-TomeoftheLightbringer-32368" value: { - dps: 3239.23606 - tps: 7801.76132 + dps: 3120.95304 + tps: 7487.88841 } } dps_results: { key: "TestProtection-AllItems-TrenchantEarthshatterDiamond" value: { - dps: 3251.33968 - tps: 7832.91604 + dps: 3122.33108 + tps: 7491.43548 } } dps_results: { key: "TestProtection-AllItems-TrenchantEarthsiegeDiamond" value: { - dps: 3251.9313 - tps: 7834.43887 + dps: 3122.87941 + tps: 7492.84687 } } dps_results: { key: "TestProtection-AllItems-Turalyon'sBattlegear" value: { - dps: 3483.64621 - tps: 8319.9179 + dps: 3497.37756 + tps: 8355.31248 } } dps_results: { key: "TestProtection-AllItems-Turalyon'sPlate" value: { - dps: 3318.21978 - tps: 7983.46046 + dps: 3200.38625 + tps: 7679.35963 } } dps_results: { key: "TestProtection-AllItems-UndeadSlayer'sBlessedArmor" value: { - dps: 2842.12091 - tps: 6814.22581 + dps: 2809.13172 + tps: 6734.90308 } } dps_results: { key: "TestProtection-AllItems-Val'anyr,HammerofAncientKings-46017" value: { - dps: 3197.43922 - tps: 7735.97814 + dps: 3086.72076 + tps: 7445.56756 } } dps_results: { key: "TestProtection-AllItems-WingedTalisman-37844" value: { - dps: 3270.69258 - tps: 7882.7304 + dps: 3142.17965 + tps: 7542.52569 } } dps_results: { key: "TestProtection-AllItems-WrathfulGladiator'sLibramofFortitude-51478" value: { - dps: 3204.44906 - tps: 7712.2196 + dps: 3099.63274 + tps: 7433.00994 } } dps_results: { key: "TestProtection-Average-Default" value: { - dps: 3687.78912 - tps: 8847.21578 - dtps: 5.81815 + dps: 3512.11867 + tps: 8391.60832 + dtps: 8.32573 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-FullBuffs-LongMultiTarget" value: { - dps: 11612.87571 - tps: 31196.52748 + dps: 11635.40894 + tps: 31226.99602 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-FullBuffs-LongSingleTarget" value: { - dps: 3069.51047 - tps: 7333.37427 + dps: 2927.44432 + tps: 6958.9487 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-FullBuffs-ShortSingleTarget" value: { - dps: 3262.47759 - tps: 7720.43259 + dps: 3146.92215 + tps: 7432.74235 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-NoBuffs-LongMultiTarget" value: { - dps: 3077.94782 - tps: 8747.3525 + dps: 2927.7533 + tps: 8371.08633 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-NoBuffs-LongSingleTarget" value: { - dps: 1360.43797 - tps: 3281.55063 + dps: 1294.35524 + tps: 3114.02581 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOC-NoBuffs-ShortSingleTarget" value: { - dps: 1720.17487 - tps: 4209.18809 + dps: 1699.56672 + tps: 4159.28168 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-FullBuffs-LongMultiTarget" value: { - dps: 10553.48836 - tps: 28476.3479 + dps: 10607.79608 + tps: 28596.18353 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-FullBuffs-LongSingleTarget" value: { - dps: 3075.09294 - tps: 7342.9998 + dps: 2906.69027 + tps: 6909.15001 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-FullBuffs-ShortSingleTarget" value: { - dps: 3275.2722 - tps: 7753.23763 + dps: 3093.28407 + tps: 7307.56852 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-NoBuffs-LongMultiTarget" value: { - dps: 2550.58905 - tps: 7391.58747 + dps: 2423.46192 + tps: 7074.56373 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-NoBuffs-LongSingleTarget" value: { - dps: 1280.03188 - tps: 3074.93104 + dps: 1212.31168 + tps: 2903.68027 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOR-NoBuffs-ShortSingleTarget" value: { - dps: 1667.38386 - tps: 4073.66409 + dps: 1645.89611 + tps: 4019.1441 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-FullBuffs-LongMultiTarget" value: { - dps: 11450.46833 - tps: 30783.70412 + dps: 11520.61637 + tps: 30929.42979 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-FullBuffs-LongSingleTarget" value: { - dps: 3401.53568 - tps: 8186.80267 + dps: 3237.22132 + tps: 7765.35241 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-FullBuffs-ShortSingleTarget" value: { - dps: 3547.00978 - tps: 8473.12567 + dps: 3389.37775 + tps: 8065.80072 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-NoBuffs-LongMultiTarget" value: { - dps: 3033.53776 - tps: 8632.19941 + dps: 2879.33762 + tps: 8244.72355 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-NoBuffs-LongSingleTarget" value: { - dps: 1468.86342 - tps: 3561.8637 + dps: 1412.93779 + tps: 3418.92565 } } dps_results: { key: "TestProtection-Settings-BloodElf-P1-Protection Paladin SOV-NoBuffs-ShortSingleTarget" value: { - dps: 1810.61828 - tps: 4442.32743 + dps: 1791.33318 + tps: 4395.32452 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-FullBuffs-LongMultiTarget" value: { - dps: 11356.33125 - tps: 30492.05337 + dps: 11370.10448 + tps: 30500.09059 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-FullBuffs-LongSingleTarget" value: { - dps: 3033.3315 - tps: 7234.9133 + dps: 2903.31302 + tps: 6896.41194 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-FullBuffs-ShortSingleTarget" value: { - dps: 3265.82834 - tps: 7728.0352 + dps: 3150.19657 + tps: 7440.24096 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-NoBuffs-LongMultiTarget" value: { - dps: 3061.61258 - tps: 8673.46402 + dps: 2852.08759 + tps: 8128.55055 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-NoBuffs-LongSingleTarget" value: { - dps: 1355.31942 - tps: 3268.20699 + dps: 1247.56943 + tps: 2992.02726 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOC-NoBuffs-ShortSingleTarget" value: { - dps: 1716.54067 - tps: 4196.047 + dps: 1612.32249 + tps: 3935.23601 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-FullBuffs-LongMultiTarget" value: { - dps: 10271.76543 - tps: 27707.71914 + dps: 10334.79027 + tps: 27850.87132 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-FullBuffs-LongSingleTarget" value: { - dps: 3047.65191 - tps: 7268.77555 + dps: 2874.92327 + tps: 6827.20585 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-FullBuffs-ShortSingleTarget" value: { - dps: 3278.96138 - tps: 7761.7095 + dps: 3096.85022 + tps: 7315.78375 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-NoBuffs-LongMultiTarget" value: { - dps: 2543.86821 - tps: 7342.3064 + dps: 2350.93442 + tps: 6841.8112 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-NoBuffs-LongSingleTarget" value: { - dps: 1270.65737 - tps: 3049.61089 + dps: 1171.62526 + tps: 2798.74695 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOR-NoBuffs-ShortSingleTarget" value: { - dps: 1664.51324 - tps: 4065.38881 + dps: 1573.2113 + tps: 3828.34619 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-FullBuffs-LongMultiTarget" value: { - dps: 11204.51915 - tps: 30110.97033 + dps: 11264.93161 + tps: 30232.64169 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-FullBuffs-LongSingleTarget" value: { - dps: 3377.97139 - tps: 8127.7234 + dps: 3213.04049 + tps: 7698.7182 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-FullBuffs-ShortSingleTarget" value: { - dps: 3550.93624 - tps: 8482.23072 + dps: 3393.19327 + tps: 8074.69013 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-NoBuffs-LongMultiTarget" value: { - dps: 3031.00707 - tps: 8596.37832 + dps: 2803.50859 + tps: 8001.23661 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-NoBuffs-LongSingleTarget" value: { - dps: 1465.39555 - tps: 3551.44734 + dps: 1372.38557 + tps: 3313.31693 } } dps_results: { key: "TestProtection-Settings-Human-P1-Protection Paladin SOV-NoBuffs-ShortSingleTarget" value: { - dps: 1809.72055 - tps: 4441.21827 + dps: 1713.50883 + tps: 4192.98257 } } dps_results: { key: "TestProtection-SwitchInFrontOfTarget-Default" value: { - dps: 3796.1651 - tps: 9106.57661 - dtps: 4.75925 + dps: 3625.98023 + tps: 8657.07422 + dtps: 10.07858 } } diff --git a/sim/paladin/protection/protection.go b/sim/paladin/protection/protection.go index 5b1829995e..0426627144 100644 --- a/sim/paladin/protection/protection.go +++ b/sim/paladin/protection/protection.go @@ -92,16 +92,23 @@ func (prot *ProtectionPaladin) Initialize() { if prot.Options.UseAvengingWrath { prot.RegisterAvengingWrathCD() } + + if !prot.IsUsingAPL { + prot.RegisterPrepullAction(-3*time.Second, func(sim *core.Simulation) { + prot.HolyShield.Cast(sim, nil) + }) + } + + if !prot.IsUsingAPL { + prot.RegisterPrepullAction(-1500*time.Millisecond, func(sim *core.Simulation) { + prot.DivinePlea.Cast(sim, nil) + }) + } } func (prot *ProtectionPaladin) Reset(sim *core.Simulation) { prot.Paladin.Reset(sim) - // Pre-activate Holy Shield before combat starts. - // Assume it gets cast 3s before entering combat. - prot.HolyShieldAura.Activate(sim) - prot.HolyShield.CD.Timer.Set(time.Second * 7) - sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int) { if isExecute == 20 { prot.OnGCDReady(sim) @@ -120,8 +127,6 @@ func (prot *ProtectionPaladin) Reset(sim *core.Simulation) { prot.SealOfRighteousnessAura.Activate(sim) } - prot.DivinePleaAura.Activate(sim) - prot.DivinePlea.CD.Use(sim) prot.RighteousFuryAura.Activate(sim) prot.Paladin.PseudoStats.Stunned = false } diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 62b6678d19..17e3e742e3 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -7,6 +7,8 @@ import { APLValueCompare_ComparisonOperator as ComparisonOperator, APLValueMath, APLValueMath_MathOperator as MathOperator, + APLValueMax, + APLValueMin, APLValueConst, APLValueCurrentTime, APLValueCurrentTimePercent, @@ -43,6 +45,9 @@ import { APLValueAuraShouldRefresh, APLValueDotIsActive, APLValueDotRemainingTime, + APLValueSequenceIsComplete, + APLValueSequenceIsReady, + APLValueSequenceTimeToReady, APLValueRuneCooldown, APLValueNextRuneCooldown, APLValueNumberTargets, @@ -380,6 +385,24 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfig]: ValueKindConfigTrue if there are no more subactions left to execute in the sequence, otherwise False.', + newValue: APLValueSequenceIsComplete.create, + fields: [ + AplHelpers.stringFieldConfig('sequenceName'), + ], + }), + 'sequenceIsReady': inputBuilder({ + label: 'Sequence Is Ready', + submenu: ['Sequence'], + shortDescription: 'True if the next subaction in the sequence is ready to be executed, otherwise False.', + newValue: APLValueSequenceIsReady.create, + fields: [ + AplHelpers.stringFieldConfig('sequenceName'), + ], + }), + 'sequenceTimeToReady': inputBuilder({ + label: 'Sequence Time To Ready', + submenu: ['Sequence'], + shortDescription: 'Retuens the amount of time remaining until the next subaction in the sequence will be ready.', + newValue: APLValueSequenceTimeToReady.create, + fields: [ + AplHelpers.stringFieldConfig('sequenceName'), + ], + }), }; diff --git a/ui/protection_paladin/presets.ts b/ui/protection_paladin/presets.ts index 2bd871ad16..c4a3af2f9e 100644 --- a/ui/protection_paladin/presets.ts +++ b/ui/protection_paladin/presets.ts @@ -1,14 +1,12 @@ -import { Conjured, Consumes } from '../core/proto/common.js'; +import { Consumes } from '../core/proto/common.js'; import { CustomRotation, CustomSpell } from '../core/proto/common.js'; import { EquipmentSpec } from '../core/proto/common.js'; import { Flask } from '../core/proto/common.js'; import { Food } from '../core/proto/common.js'; -import { Glyphs } from '../core/proto/common.js'; -import { ItemSpec } from '../core/proto/common.js'; import { Potions } from '../core/proto/common.js'; import { Spec } from '../core/proto/common.js'; -import { Faction } from '../core/proto/common.js'; -import { SavedTalents } from '../core/proto/ui.js'; +import { SavedRotation, SavedTalents } from '../core/proto/ui.js'; +import { APLRotation } from '../core/proto/apl.js'; import { Player } from '../core/player.js'; import { @@ -21,7 +19,6 @@ import { ProtectionPaladin_Options as ProtectionPaladinOptions, } from '../core/proto/paladin.js'; -import * as Gems from '../core/proto_utils/gems.js'; import * as Tooltips from '../core/constants/tooltips.js'; // Preset options for this spec. @@ -65,6 +62,32 @@ export const DefaultRotation = ProtectionPaladinRotation.create({ }), }); +export const ROTATION_DEFAULT = { + name: 'Default (969)', + rotation: SavedRotation.create({ + specRotationOptionsJson: ProtectionPaladinRotation.toJsonString(ProtectionPaladinRotation.create({ + })), + rotation: APLRotation.fromJsonString(`{ + "enabled": true, + "prepullActions": [ + {"action":{"castSpell":{"spellId":{"spellId":48952}}},"doAtValue":{"const":{"val":"-3s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":54428}}},"doAtValue":{"const":{"val":"-1500ms"}}}, + {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1s"}}} + ], + "priorityList": [ + {"action":{"autocastOtherCooldowns":{}}}, + {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"spellTimeToReady":{"spellId":{"spellId":53595}}},"rhs":{"const":{"val":"3s"}}}},"castSpell":{"spellId":{"spellId":61411}}}}, + {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"spellTimeToReady":{"spellId":{"spellId":61411}}},"rhs":{"const":{"val":"3s"}}}},"castSpell":{"spellId":{"spellId":53595}}}}, + {"action":{"castSpell":{"spellId":{"spellId":48806}}}}, + {"action":{"castSpell":{"spellId":{"spellId":48819}}}}, + {"action":{"castSpell":{"spellId":{"spellId":48952}}}}, + {"action":{"castSpell":{"spellId":{"spellId":53408}}}}, + {"action":{"condition":{"and":{"vals":[{"gcdIsReady":{}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":61411}}}}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":53595}}}}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":48819}}}}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":48952}}}}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":53408}}}}}]}},"wait":{"duration":{"min":{"vals":[{"spellTimeToReady":{"spellId":{"spellId":61411}}},{"spellTimeToReady":{"spellId":{"spellId":53595}}},{"spellTimeToReady":{"spellId":{"spellId":48819}}},{"spellTimeToReady":{"spellId":{"spellId":48952}}},{"spellTimeToReady":{"spellId":{"spellId":53408}}}]}}}}} + ] + }`), + }), +}; + export const DefaultOptions = ProtectionPaladinOptions.create({ aura: PaladinAura.RetributionAura, judgement: PaladinJudgement.JudgementOfWisdom, diff --git a/ui/protection_paladin/sim.ts b/ui/protection_paladin/sim.ts index 37dc429fd7..e2f12527c9 100644 --- a/ui/protection_paladin/sim.ts +++ b/ui/protection_paladin/sim.ts @@ -210,6 +210,10 @@ export class ProtectionPaladinSimUI extends IndividualSimUI