From 444ce91d3b9a6f30208abc7e6df39de6eb1fbaa7 Mon Sep 17 00:00:00 2001 From: Rosen Rusinov Date: Sat, 11 May 2024 21:48:34 +0200 Subject: [PATCH] magmaw progress --- assets/database/db.bin | Bin 4533180 -> 4533759 bytes assets/database/db.json | 3 +- proto/common.proto | 4 + sim/core/attack.go | 2 +- sim/core/target.go | 20 +- sim/encounters/bwd/bwd.go | 5 + sim/encounters/bwd/magmaw_ai.go | 382 ++++++++++++++++++ sim/encounters/default_ai.go | 3 +- sim/encounters/register_all.go | 2 + ui/core/components/encounter_picker.ts | 40 +- ui/core/components/raid_sim_action.tsx | 1 + ui/scss/core/components/_raid_sim_action.scss | 3 + 12 files changed, 450 insertions(+), 15 deletions(-) create mode 100644 sim/encounters/bwd/bwd.go create mode 100644 sim/encounters/bwd/magmaw_ai.go diff --git a/assets/database/db.bin b/assets/database/db.bin index cb01ae1126afadb966ce976d085f86f91895f133..2486af03d8656b0b7b060894d1d1e3feb424d314 100644 GIT binary patch delta 509 zcmaKmO-~b17=^vlp$zx|j-wVo-~|y%QE01`+NyMD7iED9VByl6nM=K4dM_|zny`Vz z`2#k@2E)p@G_FkCn1~69JGaERbK}mxAT3Q?dR8Yn@B5td`FPLx`*Y9O1%rVYO!Od* zURdZuKhEMD&f@|GFo=t=aS0AC;|i`KfgxPOb=<&B3}Xa@QHvT_aNxem5ENFf!?akmUCEiJtS zON}gsr#C`*`lX7*7D+|jl$NwFnrzczwzF8@;DOkoWx>5jYDKHEE=IdcoKos(RW~Tm zszwbN1pi7Y;_HGUP2Kl4m-`+5itx1Z!x>7tYn{r@NU3Bbc|e(TIJM8-?058IzF=9$ TUyQ*I?_+Iu^yz!K^5^#nPobuf delta 153 zcmV~$TT(#*007W&Z$b*CLQkak!z)Emim@&qX9;&HX8ibQ7k#vVHerTyR)0~j_>F?8 zm_TSl+@^%2lr3r7c4TDb?AnvJ@4%sgqLL%WPMkV(uB@W!Ld~Ts*KXXpbML{UCv^=? gEp5*_UcBn+dGoGs;KQe(FW*M9@y}=;PR7gsHHRoQt^fc4 diff --git a/assets/database/db.json b/assets/database/db.json index 0e529fcd55..d7327c954f 100644 --- a/assets/database/db.json +++ b/assets/database/db.json @@ -10130,7 +10130,8 @@ {"path":"ToGC 25/Gormok","targets":[{"path":"ToGC 25/Gormok","target":{"id":34796,"name":"Gormok","level":83,"mobType":6,"stats":[0,0,0,0,0,0,0,0,0,0,0,805,0,0,0,0,0,0,10643,0,0,0,0,0,0,11853250,0,0,0,0,0,0,0],"minBaseDamage":39600,"damageSpread":0.3333,"swingSpeed":1.5}}]}, {"path":"ToGC 25/Anub'arak","targets":[{"path":"ToGC 25/Anub'arak","target":{"id":34564,"name":"Anub'arak","level":83,"mobType":8,"stats":[0,0,0,0,0,0,0,0,0,0,0,805,0,0,0,0,0,0,10643,0,0,0,0,0,0,27192750,0,0,0,0,0,0,0],"minBaseDamage":58411,"damageSpread":0.45,"swingSpeed":1.5}}]}, {"path":"ICC 25/Sindragosa (Heroic)","targets":[{"path":"ICC 25/Sindragosa (Heroic)","target":{"id":36853,"name":"Sindragosa (Heroic)","level":83,"mobType":8,"stats":[0,0,0,0,0,0,0,0,0,0,0,805,0,0,0,0,0,0,10643,0,0,0,0,0,0,46018500,0,0,0,0,0,0,0],"minBaseDamage":88072,"damageSpread":0.5,"swingSpeed":1.5,"parryHaste":true,"suppressDodge":true,"targetInputs":[{"label":"Include Mystic Buffet","tooltip":"Model the ramping magic damage taken debuff applied during Phase 3 of the encounter, in addition to the normal Phase 1 mechanics."}]}}]}, -{"path":"ICC 25/Lich King (Heroic)","targets":[{"path":"ICC 25/Lich King (Heroic)","target":{"id":36597,"name":"Lich King (Heroic)","level":83,"mobType":8,"stats":[0,0,0,0,0,0,0,0,0,0,0,805,0,0,0,0,0,0,10643,0,0,0,0,0,0,103151165,0,0,0,0,0,0,0],"minBaseDamage":146497,"damageSpread":0.1557,"swingSpeed":1.5,"suppressDodge":true}}]} +{"path":"ICC 25/Lich King (Heroic)","targets":[{"path":"ICC 25/Lich King (Heroic)","target":{"id":36597,"name":"Lich King (Heroic)","level":83,"mobType":8,"stats":[0,0,0,0,0,0,0,0,0,0,0,805,0,0,0,0,0,0,10643,0,0,0,0,0,0,103151165,0,0,0,0,0,0,0],"minBaseDamage":146497,"damageSpread":0.1557,"swingSpeed":1.5,"suppressDodge":true}}]}, +{"path":"Blackwings Descent/Magmaw","targets":[{"path":"Blackwings Descent/Magmaw","target":{"id":41570,"name":"Magmaw","level":88,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,120000000,0,0,0,0,0,0,0],"minBaseDamage":209609,"damageSpread":0.4,"swingSpeed":2.5,"targetInputs":[{"inputType":2,"label":"Size","tooltip":"The size of the Raid","enumValue":1,"enumOptions":["10","25"]},{"label":"Heroic","tooltip":"Is the encounter in Heroic Mode","boolValue":true},{"inputType":1,"label":"Impale Reaction Time","tooltip":"How long will the Raid take to Impale Head in Seconds. (After the initial 10s)","numberValue":5}]}}]} ], "glyphIds":[ {"itemId":40919,"spellId":54830}, diff --git a/proto/common.proto b/proto/common.proto index 74213b0a73..d077c9134b 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -622,6 +622,7 @@ enum MobType { enum InputType { Bool = 0; Number = 1; + Enum = 2; } message TargetInput { @@ -631,6 +632,9 @@ message TargetInput { bool bool_value = 3; double number_value = 4; + + int32 enum_value = 6; + repeated string enum_options = 7; } message Target { diff --git a/sim/core/attack.go b/sim/core/attack.go index f7d1bd5289..b5ec22bc53 100644 --- a/sim/core/attack.go +++ b/sim/core/attack.go @@ -735,7 +735,7 @@ func (aa *AutoAttacks) StopRangedUntil(sim *Simulation, readyAt time.Duration) { sim.rescheduleWeaponAttack(aa.ranged.swingAt) } -// Delays all swing timers for the specified amount. Only used by Slam. +// Delays all swing timers for the specified amount. func (aa *AutoAttacks) DelayMeleeBy(sim *Simulation, delay time.Duration) { if delay <= 0 { return diff --git a/sim/core/target.go b/sim/core/target.go index fc227cfd53..b0b0553463 100644 --- a/sim/core/target.go +++ b/sim/core/target.go @@ -43,16 +43,6 @@ func NewEncounter(options *proto.Encounter) Encounter { ExecuteProportion_90: max(options.ExecuteProportion_90, 0), Targets: []*Target{}, } - // If UseHealth is set, we use the sum of targets health. - if options.UseHealth { - for _, t := range options.Targets { - encounter.EndFightAtHealth += t.Stats[stats.Health] - } - if encounter.EndFightAtHealth == 0 { - encounter.EndFightAtHealth = 1 // default to something so we don't instantly end without anything. - } - } - for targetIndex, targetOptions := range options.Targets { target := NewTarget(targetOptions, int32(targetIndex)) encounter.Targets = append(encounter.Targets, target) @@ -66,6 +56,16 @@ func NewEncounter(options *proto.Encounter) Encounter { encounter.TargetUnits = append(encounter.TargetUnits, &target.Unit) } + // If UseHealth is set, we use the sum of targets health. After creating the targets to make sure stat modifications are done + if options.UseHealth { + for _, t := range options.Targets { + encounter.EndFightAtHealth += t.Stats[stats.Health] + } + if encounter.EndFightAtHealth == 0 { + encounter.EndFightAtHealth = 1 // default to something so we don't instantly end without anything. + } + } + if encounter.EndFightAtHealth > 0 { // Until we pre-sim set duration to 10m encounter.Duration = time.Minute * 10 diff --git a/sim/encounters/bwd/bwd.go b/sim/encounters/bwd/bwd.go new file mode 100644 index 0000000000..29e7cef67b --- /dev/null +++ b/sim/encounters/bwd/bwd.go @@ -0,0 +1,5 @@ +package bwd + +func Register() { + addMagmaw("Blackwings Descent") +} diff --git a/sim/encounters/bwd/magmaw_ai.go b/sim/encounters/bwd/magmaw_ai.go new file mode 100644 index 0000000000..7d41af5624 --- /dev/null +++ b/sim/encounters/bwd/magmaw_ai.go @@ -0,0 +1,382 @@ +package bwd + +import ( + "time" + + "github.com/wowsims/cata/sim/core" + "github.com/wowsims/cata/sim/core/proto" + "github.com/wowsims/cata/sim/core/stats" +) + +func addMagmaw(bossPrefix string) { + core.AddPresetTarget(&core.PresetTarget{ + PathPrefix: bossPrefix, + Config: &proto.Target{ + Id: 41570, + Name: "Magmaw", + Level: 88, + MobType: proto.MobType_MobTypeBeast, + TankIndex: 0, + + Stats: stats.Stats{ + stats.Health: 120_000_000, + stats.Armor: 11977, + stats.AttackPower: 650, + }.ToFloatArray(), + + SpellSchool: proto.SpellSchool_SpellSchoolPhysical, + SwingSpeed: 2.5, + MinBaseDamage: 209609, + DamageSpread: 0.4, + SuppressDodge: false, + ParryHaste: false, + DualWield: false, + DualWieldPenalty: false, + TargetInputs: []*proto.TargetInput{ + { + Label: "Raid Size", + Tooltip: "The size of the Raid", + InputType: proto.InputType_Enum, + EnumValue: 1, + EnumOptions: []string{ + "10", "25", + }, + }, + { + Label: "Heroic", + Tooltip: "Is the encounter in Heroic Mode", + InputType: proto.InputType_Bool, + BoolValue: true, + }, + { + Label: "Impale Reaction Time", + Tooltip: "How long will the Raid take to Impale Head in Seconds. (After the initial 10s)", + InputType: proto.InputType_Number, + NumberValue: 5.0, + }, + }, + }, + AI: func() core.TargetAI { + return &MagmawAI{} + }, + }) + core.AddPresetEncounter("Magmaw", []string{ + bossPrefix + "/Magmaw", + }) +} + +type MagmawAI struct { + Target *core.Target + + canAct bool + + raidSize int + isHeroic bool + impaleDelay float64 + + mangle *core.Spell + magmaSpit *core.Spell + lavaSpew *core.Spell + + pointOfVulnerability *core.Aura + swelteringArmor core.AuraArray +} + +func (ai *MagmawAI) Initialize(target *core.Target, config *proto.Target) { + ai.Target = target + + if target.Env.Raid.Size() <= 1 { + // Individual Sims - use the input configuration + ai.raidSize = []int{10, 25}[config.TargetInputs[0].EnumValue] + } else { + // Raid sim - Set from number of players + ai.raidSize = 10 + if target.Env.Raid.Size() > 10 { + ai.raidSize = 25 + } + } + + ai.isHeroic = config.TargetInputs[1].BoolValue + ai.impaleDelay = config.TargetInputs[2].NumberValue + + ai.registerSpells() +} + +func (ai *MagmawAI) Reset(sim *core.Simulation) { + ai.canAct = true +} + +const BossGCD = time.Millisecond * 1620 + +func (ai *MagmawAI) ExecuteCustomRotation(sim *core.Simulation) { + if !ai.canAct { + ai.Target.WaitUntil(sim, sim.CurrentTime+BossGCD) + return + } + + // Mangle + if ai.mangle.CanCast(sim, ai.Target.CurrentTarget) { + ai.mangle.Cast(sim, ai.Target.CurrentTarget) + return + } + + // Lava Spew + if ai.lavaSpew.CanCast(sim, ai.Target.CurrentTarget) && sim.Proc(0.7, "Lava Spew Cast Roll") { + ai.lavaSpew.Cast(sim, ai.Target.CurrentTarget) + return + } + + // Magma Spit + if ai.magmaSpit.CanCast(sim, ai.Target.CurrentTarget) && sim.Proc(0.6, "Magma Spit Cast Roll") { + ai.magmaSpit.Cast(sim, ai.Target.CurrentTarget) + return + } + + ai.Target.WaitUntil(sim, sim.CurrentTime+BossGCD) +} + +func (ai *MagmawAI) registerSpells() { + // 0 - 10N, 1 - 25N, 2 - 10H, 3 - 25H + scalingIndex := core.TernaryInt(ai.raidSize == 10, core.TernaryInt(ai.isHeroic, 2, 0), core.TernaryInt(ai.isHeroic, 3, 1)) + isIndividualSim := ai.Target.Env.Raid.Size() == 1 + + // Exposed Aura + ai.pointOfVulnerability = ai.Target.GetOrRegisterAura(core.Aura{ + Label: "Point of Vulnerability", + ActionID: core.ActionID{SpellID: 79010}, + Duration: time.Second * 30, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageTakenMultiplier *= 2 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageTakenMultiplier /= 2 + + ai.canAct = true + ai.Target.AutoAttacks.EnableAutoSwing(sim) + }, + }) + + // Mangle Debuff Aura + ai.swelteringArmor = ai.Target.NewAllyAuraArray(func(unit *core.Unit) *core.Aura { + if unit.Type == core.PetUnit { + return nil + } + return unit.GetOrRegisterAura(core.Aura{ + Label: "Sweltering Armor", + ActionID: core.ActionID{SpellID: 78199}, + Duration: time.Second * 90, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.ArmorMultiplier *= 0.5 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.ArmorMultiplier /= 0.5 + }, + }) + }) + + lavaSpewBase := []float64{ + 14799, + 14799, + 20811, + 24049, + }[scalingIndex] + + lavaSpewVariance := []float64{ + 2401, + 2401, + 3376, + 3901, + }[scalingIndex] + + lavaSpewDamageRoll := func(sim *core.Simulation) float64 { + return lavaSpewBase + lavaSpewVariance*sim.RandomFloat("Lava Spew Damage") + } + + ai.lavaSpew = ai.Target.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 77690}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskSpellDamage, + + DamageMultiplier: 1, + + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: ai.Target.NewTimer(), + Duration: time.Second * 30, + }, + IgnoreHaste: true, + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Second * 2, + }, + ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { + spell.Unit.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime+cast.CastTime, false) + }, + }, + + Dot: core.DotConfig{ + IsAOE: true, + Aura: core.Aura{ + Label: "Lava Spew", + ActionID: core.ActionID{SpellID: 77690}, + }, + + TickLength: time.Second * 2, + NumberOfTicks: 3, + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + for _, aoeTarget := range sim.Raid.AllPlayerUnits { + dot.Spell.CalcAndDealDamage(sim, aoeTarget, lavaSpewDamageRoll(sim), dot.Spell.OutcomeAlwaysHit) + } + + // This tick delays melees by up to 300ms after it lands + meleeMinAt := sim.CurrentTime + time.Millisecond*300 + nextMeleeAt := dot.Spell.Unit.AutoAttacks.NextAttackAt() + if nextMeleeAt < meleeMinAt { + dot.Spell.Unit.AutoAttacks.DelayMeleeBy(sim, meleeMinAt-nextMeleeAt) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.AOEDot().Apply(sim) + }, + }) + + magmaSpitBase := []float64{ + 110463, + 132556, + 132556, + 154648, + }[scalingIndex] + + magmaSpitVariance := []float64{ + 17914, + 21496, + 21496, + 25080, + }[scalingIndex] + + magmaSpitDamageRoll := func(sim *core.Simulation) float64 { + return magmaSpitBase + magmaSpitVariance*sim.RandomFloat("Magma Spit Damage") + } + + numTargets := core.TernaryInt32(ai.raidSize == 10, 3, 8) + ai.magmaSpit = ai.Target.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 78359}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskSpellDamage, + + DamageMultiplier: 1, + + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: ai.Target.NewTimer(), + Duration: time.Second * 7, + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + if isIndividualSim { + chanceToBeHit := float64(numTargets) / float64(ai.raidSize) + if sim.Proc(float64(chanceToBeHit), "Magma Spit Hit") { + spell.CalcAndDealDamage(sim, target, magmaSpitDamageRoll(sim), spell.OutcomeAlwaysHit) + } + } else { + if numTargets >= sim.Environment.GetNumTargets() { + for _, aoeTarget := range sim.Raid.AllPlayerUnits { + spell.CalcAndDealDamage(sim, aoeTarget, magmaSpitDamageRoll(sim), spell.OutcomeAlwaysHit) + } + } else { + validTargets := make([]int32, 0) + for idx, _ := range sim.Raid.AllPlayerUnits { + validTargets = append(validTargets, int32(idx)) + } + hitTargets := make([]int32, 0) + for idx := int32(0); idx < numTargets; idx++ { + targetRoll := int(sim.RandomFloat("Magma Spit Target Roll") * float64(len(validTargets))) + rolledTarget := validTargets[targetRoll] + hitTargets = append(hitTargets, int32(rolledTarget)) + + remove := func(s []int32, i int32) []int32 { + s[i] = s[len(s)-1] + return s[:len(s)-1] + } + validTargets = remove(validTargets, rolledTarget) + } + + for idx := int32(0); idx < numTargets; idx++ { + spell.CalcAndDealDamage(sim, sim.Raid.AllPlayerUnits[hitTargets[idx]], magmaSpitDamageRoll(sim), spell.OutcomeAlwaysHit) + } + } + } + }, + }) + + mangleTick := []float64{ + 110463, + 132556, + 132556, + 154648, + }[scalingIndex] + + // Variance listed in DB2 but not observed in logs + // mangleVariance := []float64{ + // 17914, + // 21496, + // 21496, + // 25080, + // }[scalingIndex] + + ai.mangle = ai.Target.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 89773}, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: core.ProcMaskEmpty, + + DamageMultiplier: 1, + + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: ai.Target.NewTimer(), + Duration: time.Second * 90, + }, + }, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Magmaw Mangle", + ActionID: core.ActionID{SpellID: 89773}, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + ai.Target.AutoAttacks.CancelAutoSwing(sim) + ai.canAct = false + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + // Activate Expose + ai.pointOfVulnerability.Activate(sim) + }, + }, + + TickLength: time.Second * 2, + NumberOfTicks: 5 + int32(ai.impaleDelay/2.0), // Simulate Mangle Duration as 10s + Input Delay + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + baseDamage := mangleTick // + mangleVariance*sim.RandomFloat("Magmaw Mangle Tick") + dot.Spell.CalcAndDealPeriodicDamage(sim, target, baseDamage, dot.Spell.OutcomeAlwaysHit) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) * 1.5 + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeAlwaysHit) + spell.Dot(target).Apply(sim) + }, + }) + + ai.Target.RegisterResetEffect(func(sim *core.Simulation) { + ai.mangle.CD.Use(sim) + ai.magmaSpit.CD.Set(time.Second * 5) + ai.lavaSpew.CD.Set(time.Second * 20) + }) +} diff --git a/sim/encounters/default_ai.go b/sim/encounters/default_ai.go index bd619b5eb3..255f718053 100644 --- a/sim/encounters/default_ai.go +++ b/sim/encounters/default_ai.go @@ -62,9 +62,8 @@ func (ai *DefaultAI) ExecuteCustomRotation(sim *core.Simulation) { continue } - if sim.Proc(ability.ChanceToUse, "TargetAbility") { + if ability.Spell.CanCast(sim, ai.Target.CurrentTarget) && sim.Proc(ability.ChanceToUse, "TargetAbility") { ability.Spell.Cast(sim, ai.Target.CurrentTarget) - return } } } diff --git a/sim/encounters/register_all.go b/sim/encounters/register_all.go index 2cf193165b..deb817eb6f 100644 --- a/sim/encounters/register_all.go +++ b/sim/encounters/register_all.go @@ -2,6 +2,7 @@ package encounters import ( "github.com/wowsims/cata/sim/core" + "github.com/wowsims/cata/sim/encounters/bwd" "github.com/wowsims/cata/sim/encounters/icc" "github.com/wowsims/cata/sim/encounters/naxxramas" "github.com/wowsims/cata/sim/encounters/toc" @@ -13,6 +14,7 @@ func init() { ulduar.Register() toc.Register() icc.Register() + bwd.Register() } func AddSingleTargetBossEncounter(presetTarget *core.PresetTarget) { diff --git a/ui/core/components/encounter_picker.ts b/ui/core/components/encounter_picker.ts index c9ba3298ad..8732764c4c 100644 --- a/ui/core/components/encounter_picker.ts +++ b/ui/core/components/encounter_picker.ts @@ -12,6 +12,7 @@ import { SimUI } from '../sim_ui.js'; import { EventID, TypedEvent } from '../typed_event.js'; import { BaseModal } from './base_modal.js'; import { Component } from './component.js'; +import { TextDropdownPicker, TextDropdownValueConfig } from './dropdown_picker'; import { Input } from './input.js'; export interface EncounterPickerConfig { @@ -510,6 +511,7 @@ class TargetInputPicker extends Input { private boolPicker: Input | null; private numberPicker: Input | null; + private enumPicker: TextDropdownPicker | null; private getTargetInput(): TargetInput { return this.encounter.targets[this.targetIndex].targetInputs[this.targetInputIndex] || TargetInput.create(); @@ -529,6 +531,7 @@ class TargetInputPicker extends Input { this.boolPicker = null; this.numberPicker = null; + this.enumPicker = null; this.init(); } @@ -539,6 +542,7 @@ class TargetInputPicker extends Input { return TargetInput.create({ boolValue: this.boolPicker ? this.boolPicker.getInputValue() : undefined, numberValue: this.numberPicker ? this.numberPicker.getInputValue() : undefined, + enumValue: this.enumPicker ? this.enumPicker.getInputValue() : undefined, }); } setInputValue(newValue: TargetInput) { @@ -550,6 +554,10 @@ class TargetInputPicker extends Input { this.boolPicker.rootElem.remove(); this.boolPicker = null; } + if (this.enumPicker) { + this.enumPicker.rootElem.remove(); + this.enumPicker = null; + } this.numberPicker = new NumberPicker(this.rootElem, null, { label: newValue.label, labelTooltip: newValue.tooltip, @@ -565,6 +573,10 @@ class TargetInputPicker extends Input { this.numberPicker.rootElem.remove(); this.numberPicker = null; } + if (this.enumPicker) { + this.enumPicker.rootElem.remove(); + this.enumPicker = null; + } this.boolPicker = new BooleanPicker(this.rootElem, null, { label: newValue.label, labelTooltip: newValue.tooltip, @@ -575,6 +587,31 @@ class TargetInputPicker extends Input { this.encounter.targetsChangeEmitter.emit(eventID); }, }); + } else if (newValue.inputType == InputType.Enum) { + if (this.boolPicker) { + this.boolPicker.rootElem.remove(); + this.boolPicker = null; + } + if (this.numberPicker) { + this.numberPicker.rootElem.remove(); + this.numberPicker = null; + } + if (this.enumPicker) { + this.enumPicker.rootElem.remove(); + this.enumPicker = null; + } + this.enumPicker = new TextDropdownPicker(this.rootElem, null, { + label: newValue.label, + defaultLabel: 'None', + equals: (a, b) => a == b, + values: newValue.enumOptions.map>((option, index) => { return {value: index, label: option}} ), + changedEvent: () => this.encounter.targetsChangeEmitter, + getValue: () => this.getTargetInput().enumValue, + setValue: (eventID: EventID, _: null, newValue: number) => { + this.getTargetInput().enumValue = newValue; + this.encounter.targetsChangeEmitter.emit(eventID); + }, + }); } } } @@ -685,7 +722,8 @@ function makeTargetInputsPicker(parent: HTMLElement, encounter: Encounter, targe index: number, config: ListItemPickerConfig, ) => new TargetInputPicker(parent, encounter, targetIndex, index, config), - hideUi: true, + allowedActions: [], + inline: true, }); } diff --git a/ui/core/components/raid_sim_action.tsx b/ui/core/components/raid_sim_action.tsx index b6a96c4fc9..8418d96af9 100644 --- a/ui/core/components/raid_sim_action.tsx +++ b/ui/core/components/raid_sim_action.tsx @@ -165,6 +165,7 @@ export class RaidSimResultsManager { setResultTooltip('.results-sim-hps', 'Healing+Shielding Per Second, including overhealing.'); setResultTooltip('.results-sim-tps', 'Threat Per Second'); setResultTooltip('.results-sim-dtps', 'Damage Taken Per Second'); + setResultTooltip('.results-sim-dur', 'Average Fight Duration'); setResultTooltip( '.results-sim-tmi', <> diff --git a/ui/scss/core/components/_raid_sim_action.scss b/ui/scss/core/components/_raid_sim_action.scss index ed8a952ca1..3ee29c0f0e 100644 --- a/ui/scss/core/components/_raid_sim_action.scss +++ b/ui/scss/core/components/_raid_sim_action.scss @@ -35,6 +35,9 @@ .results-sim-cod .topline-result-avg:after { content: "% Chance of Death"; } + .results-sim-dur .topline-result-avg:after { + content: " Duration"; + } .results-sim-percent-oom .topline-result-avg:after { content: " spent OOM";