diff --git a/proto/druid.proto b/proto/druid.proto index 599ad187f2..f3e2c5c070 100644 --- a/proto/druid.proto +++ b/proto/druid.proto @@ -151,20 +151,29 @@ message BalanceDruid { Manual = 2; } Type type = 1; - bool use_battle_res = 2; - bool use_is = 3; - bool use_mf = 4; + + enum MfUsage { + NoMf = 0; + BeforeLunar = 1; + MaximizeMf = 2; + } + MfUsage mf_usage = 2; + + enum IsUsage { + NoIs = 0; + BeforeSolar = 1; + MaximizeIs = 2; + } + IsUsage is_usage = 3; + + bool use_battle_res = 4; bool use_wrath = 5; bool use_starfire = 6; bool use_typhoon = 7; bool use_hurricane = 8; - int32 mf_inside_eclipse_threshold = 9; - int32 is_inside_eclipse_threshold = 10; - bool use_smart_cooldowns = 11; - bool maximize_mf_uptime = 12; - bool maximize_is_uptime = 13; - bool maintain_faerie_fire = 14; - int32 player_latency = 15; + bool use_smart_cooldowns = 9; + bool maintain_faerie_fire = 10; + int32 player_latency = 11; } Rotation rotation = 1; diff --git a/sim/druid/balance/balance.go b/sim/druid/balance/balance.go index b02eca71ec..258942e4db 100644 --- a/sim/druid/balance/balance.go +++ b/sim/druid/balance/balance.go @@ -80,30 +80,18 @@ func (moonkin *BalanceDruid) Reset(sim *core.Simulation) { moonkin.RebirthTiming = moonkin.Env.BaseDuration.Seconds() * sim.RandomFloat("Rebirth Timing") if moonkin.Rotation.Type == proto.BalanceDruid_Rotation_Adaptive { + moonkin.Rotation.MfUsage = proto.BalanceDruid_Rotation_NoMf + moonkin.Rotation.IsUsage = proto.BalanceDruid_Rotation_MaximizeIs moonkin.Rotation.UseBattleRes = false - moonkin.Rotation.UseMf = false - moonkin.Rotation.UseIs = true moonkin.Rotation.UseStarfire = true moonkin.Rotation.UseWrath = true moonkin.Rotation.UseTyphoon = false moonkin.Rotation.UseHurricane = false - moonkin.Rotation.MfInsideEclipseThreshold = 15 - moonkin.Rotation.IsInsideEclipseThreshold = 15 moonkin.Rotation.UseSmartCooldowns = true - moonkin.Rotation.MaximizeMfUptime = false - moonkin.Rotation.MaximizeIsUptime = true moonkin.Rotation.MaintainFaerieFire = true moonkin.Rotation.PlayerLatency = 200 } - if !moonkin.Rotation.UseMf { - moonkin.Rotation.MfInsideEclipseThreshold = 15 - } - - if !moonkin.Rotation.UseIs { - moonkin.Rotation.IsInsideEclipseThreshold = 15 - } - if moonkin.Rotation.UseSmartCooldowns { moonkin.potionUsed = false consumes := moonkin.Consumes diff --git a/sim/druid/balance/presets.go b/sim/druid/balance/presets.go index 54c05009ed..dc35d0ef31 100644 --- a/sim/druid/balance/presets.go +++ b/sim/druid/balance/presets.go @@ -59,15 +59,7 @@ var PlayerOptionsAdaptive = &proto.Player_BalanceDruid{ InnervateTarget: &proto.RaidTarget{TargetIndex: 0}, // self innervate }, Rotation: &proto.BalanceDruid_Rotation{ - Type: proto.BalanceDruid_Rotation_Adaptive, - UseIs: true, - UseStarfire: true, - UseWrath: true, - UseBattleRes: true, - IsInsideEclipseThreshold: 15.0, - UseSmartCooldowns: true, - MaximizeIsUptime: true, - PlayerLatency: 200, + Type: proto.BalanceDruid_Rotation_Adaptive, }, }, } diff --git a/sim/druid/balance/rotation.go b/sim/druid/balance/rotation.go index 749d3c8c40..27df86d48e 100644 --- a/sim/druid/balance/rotation.go +++ b/sim/druid/balance/rotation.go @@ -1,6 +1,7 @@ package balance import ( + "github.com/wowsims/wotlk/sim/core/proto" "time" "github.com/wowsims/wotlk/sim/core" @@ -30,19 +31,8 @@ func (moonkin *BalanceDruid) rotation(sim *core.Simulation) *core.Spell { } } - moonfireUptime := moonkin.MoonfireDot.RemainingDuration(sim) - insectSwarmUptime := moonkin.InsectSwarmDot.RemainingDuration(sim) shouldRebirth := sim.GetRemainingDuration().Seconds() < moonkin.RebirthTiming - // Player "brain" latency - playerLatency := time.Duration(rotation.PlayerLatency) - lunarICD := moonkin.LunarICD.Timer.TimeToReady(sim) - solarICD := moonkin.SolarICD.Timer.TimeToReady(sim) - fishingForLunar := lunarICD <= solarICD - fishingForSolar := solarICD < lunarICD - maximizeIsUptime := rotation.MaximizeIsUptime && rotation.UseIs - maximizeMfUptime := rotation.MaximizeMfUptime && rotation.UseMf - if rotation.UseBattleRes && shouldRebirth && moonkin.Rebirth.IsReady(sim) { return moonkin.Rebirth } else if moonkin.Talents.ForceOfNature && moonkin.ForceOfNature.IsReady(sim) { @@ -59,6 +49,21 @@ func (moonkin *BalanceDruid) rotation(sim *core.Simulation) *core.Spell { return moonkin.Hurricane } + moonfireUptime := moonkin.MoonfireDot.RemainingDuration(sim) + insectSwarmUptime := moonkin.InsectSwarmDot.RemainingDuration(sim) + // Player "brain" latency + playerLatency := time.Duration(rotation.PlayerLatency) + lunarICD := moonkin.LunarICD.Timer.TimeToReady(sim) + solarICD := moonkin.SolarICD.Timer.TimeToReady(sim) + fishingForLunar := lunarICD <= solarICD + //fishingForSolar := solarICD < lunarICD + useMf := moonkin.Rotation.MfUsage != proto.BalanceDruid_Rotation_NoMf + useIs := moonkin.Rotation.IsUsage != proto.BalanceDruid_Rotation_NoIs + maximizeMfUptime := moonkin.Rotation.MfUsage == proto.BalanceDruid_Rotation_MaximizeMf + maximizeIsUptime := moonkin.Rotation.IsUsage == proto.BalanceDruid_Rotation_MaximizeIs + shouldRefreshMf := moonfireUptime <= 0 && useMf + shouldRefreshIs := insectSwarmUptime <= 0 && useIs + if moonkin.Talents.Eclipse > 0 { lunarUptime := moonkin.LunarEclipseProcAura.ExpiresAt() - sim.CurrentTime @@ -67,16 +72,16 @@ func (moonkin *BalanceDruid) rotation(sim *core.Simulation) *core.Spell { solarIsActive := moonkin.SolarEclipseProcAura.IsActive() // "Dispelling" eclipse effects before casting if needed - if float64(lunarUptime-moonkin.Starfire.CurCast.CastTime) <= 0 && rotation.UseMf { + if float64(lunarUptime-moonkin.Starfire.CurCast.CastTime) <= 0 && useMf { lunarIsActive = false } - if float64(solarUptime-moonkin.Wrath.CurCast.CastTime) <= 0 && rotation.UseIs { + if float64(solarUptime-moonkin.Wrath.CurCast.CastTime) <= 0 && useIs { solarIsActive = false } if lunarIsActive { lunarIsActive = lunarUptime < (moonkin.LunarEclipseProcAura.Duration - playerLatency) - fishingForSolar = false + //fishingForSolar = false } if solarIsActive { solarIsActive = solarUptime < (moonkin.SolarEclipseProcAura.Duration - playerLatency) @@ -85,41 +90,43 @@ func (moonkin *BalanceDruid) rotation(sim *core.Simulation) *core.Spell { // Eclipse if solarIsActive || lunarIsActive { - if maximizeIsUptime && insectSwarmUptime <= 0 { + if maximizeIsUptime && shouldRefreshIs { return moonkin.InsectSwarm } - if maximizeMfUptime && moonfireUptime <= 0 { + if maximizeMfUptime && shouldRefreshMf { return moonkin.Moonfire } if lunarIsActive { - if (moonfireUptime > 0 || float64(rotation.MfInsideEclipseThreshold) >= lunarUptime.Seconds()) && rotation.UseStarfire { - if (rotation.UseSmartCooldowns && lunarUptime > 14*time.Second) || sim.GetRemainingDuration() < 15*time.Second { - moonkin.castMajorCooldown(moonkin.hyperSpeedMCD, sim, target) - moonkin.castMajorCooldown(moonkin.potionSpeedMCD, sim, target) - } - return moonkin.Starfire - } else if rotation.UseMf { - return moonkin.Moonfire + if (rotation.UseSmartCooldowns && lunarUptime > 14*time.Second) || sim.GetRemainingDuration() < 15*time.Second { + moonkin.castMajorCooldown(moonkin.hyperSpeedMCD, sim, target) + moonkin.castMajorCooldown(moonkin.potionSpeedMCD, sim, target) } + return moonkin.Starfire } else if solarIsActive { - if insectSwarmUptime > 0 || float64(rotation.IsInsideEclipseThreshold) >= solarUptime.Seconds() && rotation.UseWrath { + if rotation.UseWrath { if (rotation.UseSmartCooldowns && solarUptime > 14*time.Second) || sim.GetRemainingDuration() < 15*time.Second { moonkin.castMajorCooldown(moonkin.potionWildMagicMCD, sim, target) } return moonkin.Wrath - } else if rotation.UseIs { - return moonkin.InsectSwarm } } } + if moonkin.Rotation.MfUsage == proto.BalanceDruid_Rotation_BeforeLunar && lunarICD < 2*time.Second && shouldRefreshMf { + return moonkin.Moonfire + } + if moonkin.Rotation.IsUsage == proto.BalanceDruid_Rotation_BeforeSolar && solarICD < 2*time.Second && shouldRefreshIs { + return moonkin.InsectSwarm + } } else { - fishingForLunar, fishingForSolar = true, true // If Eclipse isn't talented we're not fishing + // If Eclipse isn't talented we're not fishing + fishingForLunar = true + //fishingForSolar = true } // Non-Eclipse - if rotation.UseMf && moonfireUptime <= 0 && (fishingForLunar || maximizeMfUptime) { + if maximizeMfUptime && shouldRefreshMf { return moonkin.Moonfire - } else if rotation.UseIs && insectSwarmUptime <= 0 && (fishingForSolar || maximizeIsUptime) { + } else if maximizeIsUptime && shouldRefreshIs { return moonkin.InsectSwarm } else if fishingForLunar && rotation.UseWrath { return moonkin.Wrath diff --git a/sim/druid/druid.go b/sim/druid/druid.go index 9f6c10a123..7e90ccb9d0 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -220,7 +220,6 @@ func (druid *Druid) Initialize() { druid.HasSetBonus(ItemSetLasherweaveRegalia, 4), druid.HasSetBonus(ItemSetGladiatorsWildhide, 2), druid.HasSetBonus(ItemSetGladiatorsWildhide, 4), - druid.HasSetBonus(ItemSetNightsongBattlegear, 2), druid.HasSetBonus(ItemSetNightsongBattlegear, 4), } diff --git a/ui/balance_druid/inputs.ts b/ui/balance_druid/inputs.ts index c88584479c..d4b45f55e8 100644 --- a/ui/balance_druid/inputs.ts +++ b/ui/balance_druid/inputs.ts @@ -1,4 +1,3 @@ -import { BalanceDruid_Options as DruidOptions, BalanceDruid_Rotation_Type as RotationType } from '../core/proto/druid.js'; import { RaidTarget } from '../core/proto/common.js'; import { Spec } from '../core/proto/common.js'; import { NO_TARGET } from '../core/proto_utils/utils.js'; @@ -8,6 +7,14 @@ import { EventID, TypedEvent } from '../core/typed_event.js'; import * as InputHelpers from '../core/components/input_helpers.js'; +import { + BalanceDruid_Options as DruidOptions, + BalanceDruid_Rotation_Type as RotationType, + BalanceDruid_Rotation_MfUsage as MfUsage, + BalanceDruid_Rotation_IsUsage as IsUsage, +} from '../core/proto/druid.js'; + + // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. @@ -45,21 +52,31 @@ export const BalanceDruidRotationConfig = { ], }), InputHelpers.makeRotationBooleanInput({ - fieldName: 'useBattleRes', - label: 'Use Battle Res', - labelTooltip: 'Cast Battle Res on an ally sometime during the encounter.', + fieldName: 'useSmartCooldowns', + label: 'Smart Cooldowns usage', + labelTooltip: 'The rotation will use cooldowns during eclipses, avoiding Haste CDs in solar and Crit CDs in lunar', showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, }), - InputHelpers.makeRotationBooleanInput({ - fieldName: 'useMf', - label: 'Use Moonfire', - labelTooltip: 'Should the rotation use Moonfire.', + InputHelpers.makeRotationEnumInput({ + fieldName: 'mfUsage', + label: 'Moonfire Usage', + labelTooltip: 'Defines how Moonfire will be used in the rotation.', + values: [ + { name: 'Unused', value: MfUsage.NoMf }, + { name: 'Before lunar', value: MfUsage.BeforeLunar }, + { name: 'Maximize', value: MfUsage.MaximizeMf }, + ], showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, }), - InputHelpers.makeRotationBooleanInput({ - fieldName: 'useIs', - label: 'Use Insect Swarm', - labelTooltip: 'Should the rotation use Insect Swarm.', + InputHelpers.makeRotationEnumInput({ + fieldName: 'isUsage', + label: 'Insect Swarm Usage', + labelTooltip: 'Defines how Insect Swarm will be used in the rotation.', + values: [ + { name: 'Unused', value: IsUsage.NoIs }, + { name: 'Before solar', value: IsUsage.BeforeSolar }, + { name: 'Maximize', value: IsUsage.MaximizeIs }, + ], showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, }), InputHelpers.makeRotationBooleanInput({ @@ -86,34 +103,10 @@ export const BalanceDruidRotationConfig = { labelTooltip: 'Should the rotation use Hurricane.', showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, }), - InputHelpers.makeRotationNumberInput({ - fieldName: 'mfInsideEclipseThreshold', - label: 'Moonfire inside eclipse max timing', - labelTooltip: 'Max eclipse uptime at which Moonfire can be applied/refreshed. 15 = never refresh, 0= always refresh.', - showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, - }), - InputHelpers.makeRotationNumberInput({ - fieldName: 'isInsideEclipseThreshold', - label: 'Insect Swarm inside eclipse max timing', - labelTooltip: 'Max eclipse uptime at which Insect Swarm can be applied/refreshed. 15 = never refresh, 0= always refresh.', - showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, - }), - InputHelpers.makeRotationBooleanInput({ - fieldName: 'useSmartCooldowns', - label: 'Smart Cooldowns usage', - labelTooltip: 'The rotation will use cooldowns during eclipses, avoiding Haste CDs in solar and Crit CDs in lunar', - showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, - }), - InputHelpers.makeRotationBooleanInput({ - fieldName: 'maximizeMfUptime', - label: 'Maximize Moonfire uptime', - labelTooltip: 'Rotation will try to keep Moonfire up without clipping', - showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, - }), InputHelpers.makeRotationBooleanInput({ - fieldName: 'maximizeIsUptime', - label: 'Maximize Insect Swarm uptime', - labelTooltip: 'Rotation will try to keep Insect Swarm up without clipping', + fieldName: 'useBattleRes', + label: 'Use Battle Res', + labelTooltip: 'Cast Battle Res on an ally sometime during the encounter.', showWhen: (player: Player) => player.getRotation().type == RotationType.Manual, }), InputHelpers.makeRotationNumberInput({ diff --git a/ui/balance_druid/presets.ts b/ui/balance_druid/presets.ts index 35bd0d3b15..32b3135d0c 100644 --- a/ui/balance_druid/presets.ts +++ b/ui/balance_druid/presets.ts @@ -1,26 +1,31 @@ import { Consumes, - Debuffs, Glyphs, + Debuffs, + EquipmentSpec, + Flask, + Food, + Glyphs, IndividualBuffs, PartyBuffs, + Potions, RaidBuffs, RaidTarget, TristateEffect } from '../core/proto/common.js'; -import { Flask } from '../core/proto/common.js'; -import { Food } from '../core/proto/common.js'; -import { EquipmentSpec } from '../core/proto/common.js'; -import { Potions } from '../core/proto/common.js'; -import { SavedTalents } from '../core/proto/ui.js'; +import {SavedTalents} from '../core/proto/ui.js'; import { - BalanceDruid_Rotation as BalanceDruidRotation, BalanceDruid_Options as BalanceDruidOptions, - BalanceDruid_Rotation_Type as RotationType, DruidMajorGlyph, DruidMinorGlyph, + BalanceDruid_Rotation as BalanceDruidRotation, + BalanceDruid_Rotation_IsUsage, + BalanceDruid_Rotation_MfUsage, + BalanceDruid_Rotation_Type as RotationType, + DruidMajorGlyph, + DruidMinorGlyph, } from '../core/proto/druid.js'; import * as Tooltips from '../core/constants/tooltips.js'; -import { NO_TARGET } from "../core/proto_utils/utils"; +import {NO_TARGET} from "../core/proto_utils/utils"; // Preset options for this spec. // Eventually we will import these values for the raid sim too, so its good to @@ -45,13 +50,12 @@ export const StandardTalents = { export const DefaultRotation = BalanceDruidRotation.create({ type: RotationType.Adaptive, + useSmartCooldowns : true, + mfUsage : BalanceDruid_Rotation_MfUsage.NoMf, + isUsage : BalanceDruid_Rotation_IsUsage.MaximizeIs, + useStarfire: true, + useWrath: true, useBattleRes: false, - useIs: true, - useMf: false, - isInsideEclipseThreshold: 15, - mfInsideEclipseThreshold: 0, - useSmartCooldowns : true, - maximizeIsUptime : true, playerLatency : 200, });