From b11393c94115e84f7ba9aea6fb4b12be1c7fb862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Hillerstr=C3=B6m?= Date: Sat, 14 Dec 2024 22:09:11 +0100 Subject: [PATCH 1/3] Add useful APL values for Energy and Focus --- proto/apl.proto | 14 +- sim/core/apl_value.go | 12 ++ sim/core/apl_values_resources.go | 153 +++++++++++++++++- sim/core/energy.go | 8 + sim/core/focus.go | 12 ++ sim/core/runic_power.go | 2 +- .../individual_sim_ui/apl_values.ts | 138 +++++++++++++--- 7 files changed, 318 insertions(+), 21 deletions(-) diff --git a/proto/apl.proto b/proto/apl.proto index 7ef0b6c56d..64afde9fa1 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -82,7 +82,7 @@ message APLAction { } } -// NextIndex: 88 +// NextIndex: 94 message APLValue { UUID uuid = 87; @@ -122,7 +122,13 @@ message APLValue { APLValueCurrentSolarEnergy current_solar_energy = 68; APLValueCurrentLunarEnergy current_lunar_energy = 69; APLValueCurrentHolyPower current_holy_power = 75; + APLValueMaxEnergy max_energy = 88; + APLValueMaxFocus max_focus = 89; APLValueMaxRunicPower max_runic_power = 86; + APLValueEnergyRegenPerSecond energy_regen_per_second = 90; + APLValueFocusRegenPerSecond focus_regen_per_second = 91; + APLValueEnergyTimeToMax energy_time_to_max = 92; + APLValueFocusTimeToMax focus_time_to_max = 93; // Unit values APLValueUnitIsMoving unit_is_moving = 72; @@ -439,7 +445,13 @@ message APLValueCurrentRunicPower {} message APLValueCurrentSolarEnergy {} message APLValueCurrentLunarEnergy {} message APLValueCurrentHolyPower {} +message APLValueMaxEnergy {} +message APLValueMaxFocus {} message APLValueMaxRunicPower {} +message APLValueEnergyRegenPerSecond {} +message APLValueFocusRegenPerSecond {} +message APLValueEnergyTimeToMax {} +message APLValueFocusTimeToMax {} enum APLValueRuneType { RuneUnknown = 0; diff --git a/sim/core/apl_value.go b/sim/core/apl_value.go index c9aed12439..6fc9abe0fa 100644 --- a/sim/core/apl_value.go +++ b/sim/core/apl_value.go @@ -122,8 +122,20 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue { value = rot.newValueCurrentComboPoints(config.GetCurrentComboPoints(), config.Uuid) case *proto.APLValue_CurrentRunicPower: value = rot.newValueCurrentRunicPower(config.GetCurrentRunicPower(), config.Uuid) + case *proto.APLValue_MaxEnergy: + value = rot.newValueMaxEnergy(config.GetMaxEnergy(), config.Uuid) + case *proto.APLValue_MaxFocus: + value = rot.newValueMaxFocus(config.GetMaxFocus(), config.Uuid) case *proto.APLValue_MaxRunicPower: value = rot.newValueMaxRunicPower(config.GetMaxRunicPower(), config.Uuid) + case *proto.APLValue_EnergyRegenPerSecond: + value = rot.newValueEnergyRegenPerSecond(config.GetEnergyRegenPerSecond(), config.Uuid) + case *proto.APLValue_FocusRegenPerSecond: + value = rot.newValueFocusRegenPerSecond(config.GetFocusRegenPerSecond(), config.Uuid) + case *proto.APLValue_EnergyTimeToMax: + value = rot.newValueEnergyTimeToMax(config.GetEnergyTimeToMax(), config.Uuid) + case *proto.APLValue_FocusTimeToMax: + value = rot.newValueFocusTimeToMax(config.GetFocusTimeToMax(), config.Uuid) // Resources Runes case *proto.APLValue_CurrentRuneCount: diff --git a/sim/core/apl_values_resources.go b/sim/core/apl_values_resources.go index 2b4df905e9..0014245fba 100644 --- a/sim/core/apl_values_resources.go +++ b/sim/core/apl_values_resources.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "time" "github.com/wowsims/cata/sim/core/proto" ) @@ -171,6 +172,81 @@ func (value *APLValueCurrentFocus) String() string { return "Current Focus" } +type APLValueMaxFocus struct { + DefaultAPLValueImpl + maxFocus float64 +} + +func (rot *APLRotation) newValueMaxFocus(_ *proto.APLValueMaxFocus, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasFocusBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Error, "%s does not use Focus", unit.Label) + return nil + } + return &APLValueMaxFocus{ + maxFocus: unit.MaximumFocus(), + } +} +func (value *APLValueMaxFocus) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeFloat +} +func (value *APLValueMaxFocus) GetFloat(sim *Simulation) float64 { + return value.maxFocus +} +func (value *APLValueMaxFocus) String() string { + return fmt.Sprintf("Max Focus(%f)", value.maxFocus) +} + +type APLValueFocusRegenPerSecond struct { + DefaultAPLValueImpl + unit *Unit +} + +func (rot *APLRotation) newValueFocusRegenPerSecond(_ *proto.APLValueFocusRegenPerSecond, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasFocusBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Focus", unit.Label) + return nil + } + return &APLValueFocusRegenPerSecond{ + unit: unit, + } +} +func (value *APLValueFocusRegenPerSecond) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeFloat +} +func (value *APLValueFocusRegenPerSecond) GetFloat(sim *Simulation) float64 { + return value.unit.FocusRegenPerSecond() +} +func (value *APLValueFocusRegenPerSecond) String() string { + return "Focus Regen Per Second" +} + +type APLValueFocusTimeToMax struct { + DefaultAPLValueImpl + unit *Unit +} + +func (rot *APLRotation) newValueFocusTimeToMax(_ *proto.APLValueFocusTimeToMax, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasFocusBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Focus", unit.Label) + return nil + } + return &APLValueFocusTimeToMax{ + unit: unit, + } +} +func (value *APLValueFocusTimeToMax) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeDuration +} +func (value *APLValueFocusTimeToMax) GetDuration(sim *Simulation) time.Duration { + return value.unit.TimeToMaxFocus() +} +func (value *APLValueFocusTimeToMax) String() string { + return "Time To Max Focus" +} + type APLValueCurrentEnergy struct { DefaultAPLValueImpl unit *Unit @@ -196,6 +272,81 @@ func (value *APLValueCurrentEnergy) String() string { return "Current Energy" } +type APLValueMaxEnergy struct { + DefaultAPLValueImpl + unit *Unit +} + +func (rot *APLRotation) newValueMaxEnergy(_ *proto.APLValueMaxEnergy, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasEnergyBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Error, "%s does not use Energy", unit.Label) + return nil + } + return &APLValueMaxEnergy{ + unit: unit, + } +} +func (value *APLValueMaxEnergy) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeFloat +} +func (value *APLValueMaxEnergy) GetFloat(sim *Simulation) float64 { + return value.unit.MaximumEnergy() +} +func (value *APLValueMaxEnergy) String() string { + return "Max Energy" +} + +type APLValueEnergyRegenPerSecond struct { + DefaultAPLValueImpl + unit *Unit +} + +func (rot *APLRotation) newValueEnergyRegenPerSecond(_ *proto.APLValueEnergyRegenPerSecond, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasEnergyBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Energy", unit.Label) + return nil + } + return &APLValueEnergyRegenPerSecond{ + unit: unit, + } +} +func (value *APLValueEnergyRegenPerSecond) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeFloat +} +func (value *APLValueEnergyRegenPerSecond) GetFloat(sim *Simulation) float64 { + return value.unit.EnergyRegenPerSecond() +} +func (value *APLValueEnergyRegenPerSecond) String() string { + return "Energy Regen Per Second" +} + +type APLValueEnergyTimeToMax struct { + DefaultAPLValueImpl + unit *Unit +} + +func (rot *APLRotation) newValueEnergyTimeToMax(_ *proto.APLValueEnergyTimeToMax, uuid *proto.UUID) APLValue { + unit := rot.unit + if !unit.HasEnergyBar() { + rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Energy", unit.Label) + return nil + } + return &APLValueEnergyTimeToMax{ + unit: unit, + } +} +func (value *APLValueEnergyTimeToMax) Type() proto.APLValueType { + return proto.APLValueType_ValueTypeDuration +} +func (value *APLValueEnergyTimeToMax) GetDuration(sim *Simulation) time.Duration { + return value.unit.TimeToMaxEnergy() +} +func (value *APLValueEnergyTimeToMax) String() string { + return "Time To Max Energy" +} + type APLValueCurrentComboPoints struct { DefaultAPLValueImpl unit *Unit @@ -258,7 +409,7 @@ func (rot *APLRotation) newValueMaxRunicPower(_ *proto.APLValueMaxRunicPower, uu return nil } return &APLValueMaxRunicPower{ - maxRunicPower: int32(unit.MaxRunicPower()), + maxRunicPower: int32(unit.MaximumRunicPower()), } } func (value *APLValueMaxRunicPower) Type() proto.APLValueType { diff --git a/sim/core/energy.go b/sim/core/energy.go index 73645b5331..27c827cfd3 100644 --- a/sim/core/energy.go +++ b/sim/core/energy.go @@ -68,6 +68,14 @@ func (eb *energyBar) EnergyRegenPerSecond() float64 { return 10.0 * eb.hasteRatingMultiplier * eb.energyRegenMultiplier } +func (eb *energyBar) TimeToMaxEnergy() time.Duration { + if eb.currentEnergy == eb.maxEnergy { + return time.Duration(0) + } + + return DurationFromSeconds((eb.maxEnergy - eb.currentEnergy) / eb.EnergyRegenPerSecond()) +} + func (eb *energyBar) AddEnergy(sim *Simulation, amount float64, metrics *ResourceMetrics) { if amount < 0 { panic("Trying to add negative energy!") diff --git a/sim/core/focus.go b/sim/core/focus.go index 6fa5208c12..e73a62dfa1 100644 --- a/sim/core/focus.go +++ b/sim/core/focus.go @@ -55,6 +55,10 @@ func (fb *focusBar) CurrentFocus() float64 { return fb.currentFocus } +func (fb *focusBar) MaximumFocus() float64 { + return fb.maxFocus +} + func (fb *focusBar) NextFocusTickAt() time.Duration { return fb.nextFocusTick } @@ -77,6 +81,14 @@ func (fb *focusBar) FocusRegenPerSecond() float64 { } } +func (fb *focusBar) TimeToMaxFocus() time.Duration { + if fb.currentFocus == fb.maxFocus { + return time.Duration(0) + } + + return DurationFromSeconds((fb.maxFocus - fb.currentFocus) / fb.FocusRegenPerSecond()) +} + func (fb *focusBar) getTotalRegenMultiplier() float64 { return fb.hasteRatingMultiplier * fb.focusRegenMultiplier } diff --git a/sim/core/runic_power.go b/sim/core/runic_power.go index 3dd52a7868..04f1f66280 100644 --- a/sim/core/runic_power.go +++ b/sim/core/runic_power.go @@ -163,7 +163,7 @@ func (rp *runicPowerBar) CurrentRunicPower() float64 { return rp.currentRunicPower } -func (rp *runicPowerBar) MaxRunicPower() float64 { +func (rp *runicPowerBar) MaximumRunicPower() float64 { return rp.maxRunicPower } diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 2413aff73f..455d68abf3 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -46,6 +46,10 @@ import { APLValueDotIsActive, APLValueDotRemainingTime, APLValueDotTickFrequency, + APLValueEnergyRegenPerSecond, + APLValueEnergyTimeToMax, + APLValueFocusRegenPerSecond, + APLValueFocusTimeToMax, APLValueFrontOfTarget, APLValueGCDIsReady, APLValueGCDTimeToReady, @@ -56,6 +60,8 @@ import { APLValueMath, APLValueMath_MathOperator as MathOperator, APLValueMax, + APLValueMaxEnergy, + APLValueMaxFocus, APLValueMaxRunicPower, APLValueMin, APLValueNextRuneCooldown, @@ -648,31 +654,39 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) { + const clss = player.getClass(); + return clss !== Class.ClassDeathKnight && clss !== Class.ClassHunter && clss !== Class.ClassRogue && clss !== Class.ClassWarrior; + }, fields: [], }), currentManaPercent: inputBuilder({ - label: 'Mana (%)', - submenu: ['Resources'], + label: 'Current Mana (%)', + submenu: ['Resources', 'Mana'], shortDescription: 'Amount of currently available Mana, as a percentage.', newValue: APLValueCurrentManaPercent.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + return clss !== Class.ClassDeathKnight && clss !== Class.ClassHunter && clss !== Class.ClassRogue && clss !== Class.ClassWarrior; + }, fields: [], }), currentRage: inputBuilder({ @@ -680,20 +694,91 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassWarrior; + }, fields: [], }), currentFocus: inputBuilder({ - label: 'Focus', - submenu: ['Resources'], + label: 'Current Focus', + submenu: ['Resources', 'Focus'], shortDescription: 'Amount of currently available Focus.', newValue: APLValueCurrentFocus.create, + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, + fields: [], + }), + maxFocus: inputBuilder({ + label: 'Max Focus', + submenu: ['Resources', 'Focus'], + shortDescription: 'Amount of maximum available Focus.', + newValue: APLValueMaxFocus.create, + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, + fields: [], + }), + focusRegenPerSecond: inputBuilder({ + label: 'Focus Regen Per Second', + submenu: ['Resources', 'Focus'], + shortDescription: 'Focus regen per second.', + newValue: APLValueFocusRegenPerSecond.create, + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, + fields: [], + }), + focusTimeToMax: inputBuilder({ + label: 'Time To Max Focus', + submenu: ['Resources', 'Focus'], + shortDescription: 'Time until max Focus is reached.', + newValue: APLValueFocusTimeToMax.create, + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, fields: [], }), currentEnergy: inputBuilder({ - label: 'Energy', - submenu: ['Resources'], + label: 'Current Energy', + submenu: ['Resources', 'Energy'], shortDescription: 'Amount of currently available Energy.', newValue: APLValueCurrentEnergy.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; + }, + fields: [], + }), + maxEnergy: inputBuilder({ + label: 'Max Energy', + submenu: ['Resources', 'Energy'], + shortDescription: 'Amount of maximum available Energy.', + newValue: APLValueMaxEnergy.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; + }, + fields: [], + }), + energyRegenPerSecond: inputBuilder({ + label: 'Energy Regen Per Second', + submenu: ['Resources', 'Energy'], + shortDescription: 'Energy regen per second.', + newValue: APLValueEnergyRegenPerSecond.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; + }, + fields: [], + }), + energyTimeToMax: inputBuilder({ + label: 'Time To Max Energy', + submenu: ['Resources', 'Energy'], + shortDescription: 'Time until max Energy is reached.', + newValue: APLValueEnergyTimeToMax.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; + }, fields: [], }), currentComboPoints: inputBuilder({ @@ -701,11 +786,16 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; + }, fields: [], }), currentRunicPower: inputBuilder({ - label: 'Runic Power', - submenu: ['Resources'], + label: 'Current Runic Power', + submenu: ['Resources', 'Runic Power'], shortDescription: 'Amount of currently available Runic Power.', newValue: APLValueCurrentRunicPower.create, includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, @@ -713,7 +803,7 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, @@ -721,7 +811,7 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getSpec() == Spec.SpecBalanceDruid, @@ -729,7 +819,7 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getSpec() == Spec.SpecBalanceDruid, @@ -737,7 +827,7 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getSpec() == Spec.SpecBalanceDruid, @@ -832,6 +922,18 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig0 if autoattacks are not engaged.', newValue: APLValueAutoTimeToNext.create, + includeIf(player: Player, _isPrepull: boolean) { + const clss = player.getClass(); + const spec = player.getSpec(); + return ( + clss !== Class.ClassHunter && + clss !== Class.ClassMage && + clss !== Class.ClassPriest && + clss !== Class.ClassWarlock && + spec !== Spec.SpecBalanceDruid && + spec !== Spec.SpecElementalShaman + ); + }, fields: [], }), From 10586e19feffae7aabd3ea745c1d646fb0b14c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Hillerstr=C3=B6m?= Date: Sat, 14 Dec 2024 22:57:48 +0100 Subject: [PATCH 2/3] Reword time-to-max values --- sim/core/apl_values_resources.go | 4 ++-- ui/core/components/individual_sim_ui/apl_values.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sim/core/apl_values_resources.go b/sim/core/apl_values_resources.go index 0014245fba..e274134394 100644 --- a/sim/core/apl_values_resources.go +++ b/sim/core/apl_values_resources.go @@ -244,7 +244,7 @@ func (value *APLValueFocusTimeToMax) GetDuration(sim *Simulation) time.Duration return value.unit.TimeToMaxFocus() } func (value *APLValueFocusTimeToMax) String() string { - return "Time To Max Focus" + return "Estimated Time To Max Focus" } type APLValueCurrentEnergy struct { @@ -344,7 +344,7 @@ func (value *APLValueEnergyTimeToMax) GetDuration(sim *Simulation) time.Duration return value.unit.TimeToMaxEnergy() } func (value *APLValueEnergyTimeToMax) String() string { - return "Time To Max Energy" + return "Estimated Time To Max Energy" } type APLValueCurrentComboPoints struct { diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 455d68abf3..39781031dc 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -726,9 +726,9 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, fields: [], @@ -770,9 +770,9 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) { const clss = player.getClass(); From accf3998c53bfc9cfe0be7767575db379f964e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Hillerstr=C3=B6m?= Date: Mon, 16 Dec 2024 10:56:23 +0100 Subject: [PATCH 3/3] Replace Time to Max with Time to Target for more flexibility --- proto/apl.proto | 12 ++-- sim/core/apl_value.go | 8 +-- sim/core/apl_values_resources.go | 56 ++++++++++++------- sim/core/energy.go | 6 +- sim/core/focus.go | 6 +- .../individual_sim_ui/apl_values.ts | 24 ++++---- 6 files changed, 66 insertions(+), 46 deletions(-) diff --git a/proto/apl.proto b/proto/apl.proto index 64afde9fa1..10fb6e4f21 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -127,8 +127,8 @@ message APLValue { APLValueMaxRunicPower max_runic_power = 86; APLValueEnergyRegenPerSecond energy_regen_per_second = 90; APLValueFocusRegenPerSecond focus_regen_per_second = 91; - APLValueEnergyTimeToMax energy_time_to_max = 92; - APLValueFocusTimeToMax focus_time_to_max = 93; + APLValueEnergyTimeToTarget energy_time_to_target = 92; + APLValueFocusTimeToTarget focus_time_to_target = 93; // Unit values APLValueUnitIsMoving unit_is_moving = 72; @@ -450,8 +450,12 @@ message APLValueMaxFocus {} message APLValueMaxRunicPower {} message APLValueEnergyRegenPerSecond {} message APLValueFocusRegenPerSecond {} -message APLValueEnergyTimeToMax {} -message APLValueFocusTimeToMax {} +message APLValueEnergyTimeToTarget { + APLValue target_energy = 1; +} +message APLValueFocusTimeToTarget { + APLValue target_focus = 1; +} enum APLValueRuneType { RuneUnknown = 0; diff --git a/sim/core/apl_value.go b/sim/core/apl_value.go index 6fc9abe0fa..f1f5e25cc8 100644 --- a/sim/core/apl_value.go +++ b/sim/core/apl_value.go @@ -132,10 +132,10 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue { value = rot.newValueEnergyRegenPerSecond(config.GetEnergyRegenPerSecond(), config.Uuid) case *proto.APLValue_FocusRegenPerSecond: value = rot.newValueFocusRegenPerSecond(config.GetFocusRegenPerSecond(), config.Uuid) - case *proto.APLValue_EnergyTimeToMax: - value = rot.newValueEnergyTimeToMax(config.GetEnergyTimeToMax(), config.Uuid) - case *proto.APLValue_FocusTimeToMax: - value = rot.newValueFocusTimeToMax(config.GetFocusTimeToMax(), config.Uuid) + case *proto.APLValue_EnergyTimeToTarget: + value = rot.newValueEnergyTimeToTarget(config.GetEnergyTimeToTarget(), config.Uuid) + case *proto.APLValue_FocusTimeToTarget: + value = rot.newValueFocusTimeToTarget(config.GetFocusTimeToTarget(), config.Uuid) // Resources Runes case *proto.APLValue_CurrentRuneCount: diff --git a/sim/core/apl_values_resources.go b/sim/core/apl_values_resources.go index e274134394..7333f54998 100644 --- a/sim/core/apl_values_resources.go +++ b/sim/core/apl_values_resources.go @@ -222,29 +222,37 @@ func (value *APLValueFocusRegenPerSecond) String() string { return "Focus Regen Per Second" } -type APLValueFocusTimeToMax struct { +type APLValueFocusTimeToTarget struct { DefaultAPLValueImpl - unit *Unit + unit *Unit + targetFocus APLValue } -func (rot *APLRotation) newValueFocusTimeToMax(_ *proto.APLValueFocusTimeToMax, uuid *proto.UUID) APLValue { +func (rot *APLRotation) newValueFocusTimeToTarget(config *proto.APLValueFocusTimeToTarget, uuid *proto.UUID) APLValue { unit := rot.unit if !unit.HasFocusBar() { rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Focus", unit.Label) return nil } - return &APLValueFocusTimeToMax{ - unit: unit, + + targetFocus := rot.coerceTo(rot.newAPLValue(config.TargetFocus), proto.APLValueType_ValueTypeFloat) + if targetFocus == nil { + return nil + } + + return &APLValueFocusTimeToTarget{ + unit: unit, + targetFocus: targetFocus, } } -func (value *APLValueFocusTimeToMax) Type() proto.APLValueType { +func (value *APLValueFocusTimeToTarget) Type() proto.APLValueType { return proto.APLValueType_ValueTypeDuration } -func (value *APLValueFocusTimeToMax) GetDuration(sim *Simulation) time.Duration { - return value.unit.TimeToMaxFocus() +func (value *APLValueFocusTimeToTarget) GetDuration(sim *Simulation) time.Duration { + return value.unit.TimeToTargetFocus(value.targetFocus.GetFloat(sim)) } -func (value *APLValueFocusTimeToMax) String() string { - return "Estimated Time To Max Focus" +func (value *APLValueFocusTimeToTarget) String() string { + return "Estimated Time To Target Focus" } type APLValueCurrentEnergy struct { @@ -322,29 +330,37 @@ func (value *APLValueEnergyRegenPerSecond) String() string { return "Energy Regen Per Second" } -type APLValueEnergyTimeToMax struct { +type APLValueEnergyTimeToTarget struct { DefaultAPLValueImpl - unit *Unit + unit *Unit + targetEnergy APLValue } -func (rot *APLRotation) newValueEnergyTimeToMax(_ *proto.APLValueEnergyTimeToMax, uuid *proto.UUID) APLValue { +func (rot *APLRotation) newValueEnergyTimeToTarget(config *proto.APLValueEnergyTimeToTarget, uuid *proto.UUID) APLValue { unit := rot.unit if !unit.HasEnergyBar() { rot.ValidationMessageByUUID(uuid, proto.LogLevel_Warning, "%s does not use Energy", unit.Label) return nil } - return &APLValueEnergyTimeToMax{ - unit: unit, + + targetEnergy := rot.coerceTo(rot.newAPLValue(config.TargetEnergy), proto.APLValueType_ValueTypeFloat) + if targetEnergy == nil { + return nil + } + + return &APLValueEnergyTimeToTarget{ + unit: unit, + targetEnergy: targetEnergy, } } -func (value *APLValueEnergyTimeToMax) Type() proto.APLValueType { +func (value *APLValueEnergyTimeToTarget) Type() proto.APLValueType { return proto.APLValueType_ValueTypeDuration } -func (value *APLValueEnergyTimeToMax) GetDuration(sim *Simulation) time.Duration { - return value.unit.TimeToMaxEnergy() +func (value *APLValueEnergyTimeToTarget) GetDuration(sim *Simulation) time.Duration { + return value.unit.TimeToTargetEnergy(value.targetEnergy.GetFloat(sim)) } -func (value *APLValueEnergyTimeToMax) String() string { - return "Estimated Time To Max Energy" +func (value *APLValueEnergyTimeToTarget) String() string { + return "Estimated Time To Target Energy" } type APLValueCurrentComboPoints struct { diff --git a/sim/core/energy.go b/sim/core/energy.go index 27c827cfd3..bd2d12da1b 100644 --- a/sim/core/energy.go +++ b/sim/core/energy.go @@ -68,12 +68,12 @@ func (eb *energyBar) EnergyRegenPerSecond() float64 { return 10.0 * eb.hasteRatingMultiplier * eb.energyRegenMultiplier } -func (eb *energyBar) TimeToMaxEnergy() time.Duration { - if eb.currentEnergy == eb.maxEnergy { +func (eb *energyBar) TimeToTargetEnergy(targetEnergy float64) time.Duration { + if eb.currentEnergy >= targetEnergy { return time.Duration(0) } - return DurationFromSeconds((eb.maxEnergy - eb.currentEnergy) / eb.EnergyRegenPerSecond()) + return DurationFromSeconds((targetEnergy - eb.currentEnergy) / eb.EnergyRegenPerSecond()) } func (eb *energyBar) AddEnergy(sim *Simulation, amount float64, metrics *ResourceMetrics) { diff --git a/sim/core/focus.go b/sim/core/focus.go index e73a62dfa1..370aa37f5e 100644 --- a/sim/core/focus.go +++ b/sim/core/focus.go @@ -81,12 +81,12 @@ func (fb *focusBar) FocusRegenPerSecond() float64 { } } -func (fb *focusBar) TimeToMaxFocus() time.Duration { - if fb.currentFocus == fb.maxFocus { +func (fb *focusBar) TimeToTargetFocus(targetFocus float64) time.Duration { + if fb.currentFocus >= targetFocus { return time.Duration(0) } - return DurationFromSeconds((fb.maxFocus - fb.currentFocus) / fb.FocusRegenPerSecond()) + return DurationFromSeconds((targetFocus - fb.currentFocus) / fb.FocusRegenPerSecond()) } func (fb *focusBar) getTotalRegenMultiplier() float64 { diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 39781031dc..8db5d2fe59 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -47,9 +47,9 @@ import { APLValueDotRemainingTime, APLValueDotTickFrequency, APLValueEnergyRegenPerSecond, - APLValueEnergyTimeToMax, + APLValueEnergyTimeToTarget, APLValueFocusRegenPerSecond, - APLValueFocusTimeToMax, + APLValueFocusTimeToTarget, APLValueFrontOfTarget, APLValueGCDIsReady, APLValueGCDTimeToReady, @@ -725,13 +725,13 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, fields: [], }), - focusTimeToMax: inputBuilder({ - label: 'Estimated Time To Max Focus', + focusTimeToTarget: inputBuilder({ + label: 'Estimated Time To Target Focus', submenu: ['Resources', 'Focus'], - shortDescription: 'Estimated time until max Focus is reached.', - newValue: APLValueFocusTimeToMax.create, + shortDescription: 'Estimated time until target Focus is reached, will return 0 if at or above target.', + newValue: APLValueFocusTimeToTarget.create, includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassHunter, - fields: [], + fields: [valueFieldConfig('targetFocus')], }), currentEnergy: inputBuilder({ label: 'Current Energy', @@ -769,17 +769,17 @@ const valueKindFactories: { [f in NonNullable]: ValueKindConfig, _isPrepull: boolean) { const clss = player.getClass(); const spec = player.getSpec(); return spec === Spec.SpecFeralDruid || spec === Spec.SpecGuardianDruid || clss === Class.ClassRogue; }, - fields: [], + fields: [valueFieldConfig('targetEnergy')], }), currentComboPoints: inputBuilder({ label: 'Combo Points',