Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boss spell related apl values for tank sims #4042

Merged
merged 1 commit into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ message SpellStats {
bool has_shield = 6; // Whether this spell applies a shield effect.
bool prepull_only = 5; // Whether this spell may only be cast during prepull.
bool encounter_only = 8; // Whether this spell may only be cast during the encounter (not prepull).
bool has_cast_time = 9; // Whether this spell has a cast time or not.
}
message APLActionStats {
repeated string warnings = 1;
Expand Down
16 changes: 15 additions & 1 deletion proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ message APLAction {
}
}

// NextIndex: 64
// NextIndex: 66
message APLValue {
oneof value {
// Operators
Expand All @@ -97,6 +97,10 @@ message APLValue {
APLValueIsExecutePhase is_execute_phase = 41;
APLValueNumberTargets number_targets = 28;

// Boss values
APLValueBossSpellTimeToReady boss_spell_time_to_ready = 64;
APLValueBossSpellIsCasting boss_spell_is_casting = 65;

// Resource values
APLValueCurrentHealth current_health = 26;
APLValueCurrentHealthPercent current_health_percent = 27;
Expand Down Expand Up @@ -321,6 +325,16 @@ message APLValueIsExecutePhase {
ExecutePhaseThreshold threshold = 1;
}

message APLValueBossSpellTimeToReady {
UnitReference target_unit = 1;
ActionID spell_id = 2;
}

message APLValueBossSpellIsCasting {
UnitReference target_unit = 1;
ActionID spell_id = 2;
}

message APLValueCurrentHealth {
UnitReference source_unit = 1;
}
Expand Down
11 changes: 11 additions & 0 deletions sim/core/apl_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ func (rot *APLRotation) GetAPLSpell(spellId *proto.ActionID) *Spell {
return spell
}

func (rot *APLRotation) GetTargetAPLSpell(spellId *proto.ActionID, targetUnit UnitReference) *Spell {
actionID := ProtoToActionID(spellId)
target := targetUnit.Get()
spell := target.GetSpell(actionID)

if spell == nil {
rot.ValidationWarning("%s does not know spell %s", target.Label, actionID)
}
return spell
}

func (rot *APLRotation) GetAPLDot(targetUnit UnitReference, spellId *proto.ActionID) *Dot {
spell := rot.GetAPLSpell(spellId)

Expand Down
6 changes: 6 additions & 0 deletions sim/core/apl_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue {
case *proto.APLValue_NumberTargets:
return rot.newValueNumberTargets(config.GetNumberTargets())

// Boss
case *proto.APLValue_BossSpellIsCasting:
return rot.newValueBossSpellIsCasting(config.GetBossSpellIsCasting())
case *proto.APLValue_BossSpellTimeToReady:
return rot.newValueBossSpellTimeToReady(config.GetBossSpellTimeToReady())

// Resources
case *proto.APLValue_CurrentHealth:
return rot.newValueCurrentHealth(config.GetCurrentHealth())
Expand Down
56 changes: 56 additions & 0 deletions sim/core/apl_values_boss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package core

import (
"fmt"
"time"

"github.com/wowsims/wotlk/sim/core/proto"
)

type APLValueBossSpellIsCasting struct {
DefaultAPLValueImpl
spell *Spell
}

func (rot *APLRotation) newValueBossSpellIsCasting(config *proto.APLValueBossSpellIsCasting) APLValue {
spell := rot.GetTargetAPLSpell(config.SpellId, rot.GetTargetUnit(config.TargetUnit))
if spell == nil {
return nil
}
return &APLValueBossSpellIsCasting{
spell: spell,
}
}
func (value *APLValueBossSpellIsCasting) Type() proto.APLValueType {
return proto.APLValueType_ValueTypeBool
}
func (value *APLValueBossSpellIsCasting) GetBool(sim *Simulation) bool {
return value.spell.Unit.Hardcast.ActionID == value.spell.ActionID && value.spell.Unit.Hardcast.Expires > sim.CurrentTime
}
func (value *APLValueBossSpellIsCasting) String() string {
return fmt.Sprintf("Boss is Casting(%s)", value.spell.ActionID)
}

type APLValueBossSpellTimeToReady struct {
DefaultAPLValueImpl
spell *Spell
}

func (rot *APLRotation) newValueBossSpellTimeToReady(config *proto.APLValueBossSpellTimeToReady) APLValue {
spell := rot.GetTargetAPLSpell(config.SpellId, rot.GetTargetUnit(config.TargetUnit))
if spell == nil {
return nil
}
return &APLValueBossSpellTimeToReady{
spell: spell,
}
}
func (value *APLValueBossSpellTimeToReady) Type() proto.APLValueType {
return proto.APLValueType_ValueTypeDuration
}
func (value *APLValueBossSpellTimeToReady) GetDuration(sim *Simulation) time.Duration {
return value.spell.TimeToReady(sim)
}
func (value *APLValueBossSpellTimeToReady) String() string {
return fmt.Sprintf("Boss Spell Time to Ready(%s)", value.spell.ActionID)
}
1 change: 1 addition & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ func (unit *Unit) GetMetadata() *proto.UnitMetadata {
HasShield: spell.shields != nil || spell.selfShield != nil,
PrepullOnly: spell.Flags.Matches(SpellFlagPrepullOnly),
EncounterOnly: spell.Flags.Matches(SpellFlagEncounterOnly),
HasCastTime: spell.DefaultCast.CastTime > 0,
}
})

Expand Down
5 changes: 3 additions & 2 deletions sim/encounters/icc/lichking25h_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (ai *LichKing25HAI) Initialize(target *core.Target, _ *proto.Target) {
}

func (ai *LichKing25HAI) Reset(*core.Simulation) {
ai.SoulReaper.CD.Set(ai.SoulReaper.CD.Duration)
}

func (ai *LichKing25HAI) registerSoulReaperSpell(target *core.Target) {
Expand All @@ -80,7 +81,7 @@ func (ai *LichKing25HAI) registerSoulReaperSpell(target *core.Target) {
ActionID: core.ActionID{SpellID: 69409},
SpellSchool: core.SpellSchoolShadow,
ProcMask: core.ProcMaskMeleeMHSpecial,
Flags: core.SpellFlagNone,
Flags: core.SpellFlagAPL,

Cast: core.CastConfig{
CD: core.Cooldown{
Expand Down Expand Up @@ -135,7 +136,7 @@ func (ai *LichKing25HAI) registerSoulReaperSpell(target *core.Target) {
func (ai *LichKing25HAI) DoAction(sim *core.Simulation) {
if ai.Target.GCD.IsReady(sim) {
if ai.Target.CurrentTarget != nil {
if ai.SoulReaper.IsReady(sim) && sim.CurrentTime >= ai.SoulReaper.CD.Duration {
if ai.SoulReaper.IsReady(sim) {
// Based on log analysis, Soul Reaper appears to have a ~75% chance to "proc" on every 1.62 second server tick once it is off cooldown.
// Note that analysis based only on the cast intervals supported a ~40% proc chance fit. However, many of the apparent delays in Soul Reaper casts are
// due to Defile and Infest casts that take priority when the cooldowns overlap. Once these CD conflicts are corrected for, the variance in Soul Reaper
Expand Down
9 changes: 5 additions & 4 deletions sim/encounters/icc/sindragosa25h_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ type Sindragosa25HAI struct {
FrostAura *core.Spell
FrostBreath *core.Spell
FrostBreathDebuff *core.Aura
FirstBreath time.Duration

IncludeMysticBuffet bool
MysticBuffetAuras []*core.Aura
Expand Down Expand Up @@ -88,7 +87,9 @@ func (ai *Sindragosa25HAI) Reset(sim *core.Simulation) {
breathPeriod := time.Millisecond * 22680
maxBreathsPossible := (sim.Duration - time.Millisecond*1500) / breathPeriod
latestAllowedBreath := sim.Duration - time.Millisecond*1500 - breathPeriod*maxBreathsPossible - time.Millisecond*1620
ai.FirstBreath = core.DurationFromSeconds(sim.RandomFloat("Frost Breath Timing") * latestAllowedBreath.Seconds())
firstBreath := core.DurationFromSeconds(sim.RandomFloat("Frost Breath Timing") * latestAllowedBreath.Seconds())

ai.FrostBreath.CD.Set(firstBreath)
}

func (ai *Sindragosa25HAI) registerPermeatingChillAura(target *core.Target) {
Expand Down Expand Up @@ -290,7 +291,7 @@ func (ai *Sindragosa25HAI) registerFrostBreathSpell(target *core.Target) {
ActionID: actionID,
SpellSchool: core.SpellSchoolFrost,
ProcMask: core.ProcMaskSpellDamage,
Flags: core.SpellFlagNone,
Flags: core.SpellFlagAPL,

Cast: core.CastConfig{
CD: core.Cooldown{
Expand Down Expand Up @@ -324,7 +325,7 @@ func (ai *Sindragosa25HAI) DoAction(sim *core.Simulation) {
}

if ai.Target.CurrentTarget != nil {
if ai.FrostBreath.IsReady(sim) && sim.CurrentTime >= ai.FirstBreath {
if ai.FrostBreath.IsReady(sim) {
ai.Target.Unit.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime+time.Millisecond*1500, false)
ai.FrostBreath.Cast(sim, ai.Target.CurrentTarget)
return
Expand Down
29 changes: 28 additions & 1 deletion ui/core/components/individual_sim_ui/apl_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ActionID } from '../../proto/common.js';
import { BooleanPicker } from '../boolean_picker.js';
import { APLValueRuneSlot, APLValueRuneType } from '../../proto/apl.js';

export type ACTION_ID_SET = 'auras' | 'stackable_auras' | 'icd_auras' | 'exclusive_effect_auras' | 'castable_spells' | 'channel_spells' | 'dot_spells' | 'shield_spells';
export type ACTION_ID_SET = 'auras' | 'stackable_auras' | 'icd_auras' | 'exclusive_effect_auras' | 'spells' | 'castable_spells' | 'channel_spells' | 'dot_spells' | 'shield_spells' | 'non_instant_spells';

const actionIdSets: Record<ACTION_ID_SET, {
defaultLabel: string,
Expand Down Expand Up @@ -58,6 +58,17 @@ const actionIdSets: Record<ACTION_ID_SET, {
});
},
},
// Used for non categorized lists
'spells': {
defaultLabel: 'Spell',
getActionIDs: async (metadata) => {
return metadata.getSpells().filter(spell => spell.data.isCastable).map(actionId => {
return {
value: actionId.id,
};
});
},
},
'castable_spells': {
defaultLabel: 'Spell',
getActionIDs: async (metadata) => {
Expand All @@ -74,10 +85,12 @@ const actionIdSets: Record<ACTION_ID_SET, {
[{
value: ActionId.fromEmpty(),
headerText: 'Spells',
submenu: ['Spells'],
}],
(spells || []).map(actionId => {
return {
value: actionId.id,
submenu: ['Spells'],
extraCssClasses: (actionId.data.prepullOnly
? ['apl-prepull-actions-only']
: (actionId.data.encounterOnly
Expand All @@ -88,10 +101,12 @@ const actionIdSets: Record<ACTION_ID_SET, {
[{
value: ActionId.fromEmpty(),
headerText: 'Cooldowns',
submenu: ['Cooldowns'],
}],
(cooldowns || []).map(actionId => {
return {
value: actionId.id,
submenu: ['Cooldowns'],
extraCssClasses: (actionId.data.prepullOnly
? ['apl-prepull-actions-only']
: (actionId.data.encounterOnly
Expand All @@ -102,16 +117,28 @@ const actionIdSets: Record<ACTION_ID_SET, {
[{
value: ActionId.fromEmpty(),
headerText: 'Placeholders',
submenu: ['Placeholders'],
}],
placeholders.map(actionId => {
return {
value: actionId,
submenu: ['Placeholders'],
tooltip: 'The Prepull Potion if CurrentTime < 0, or the Combat Potion if combat has started.',
};
}),
].flat();
},
},
'non_instant_spells': {
defaultLabel: 'Non-instant Spell',
getActionIDs: async (metadata) => {
return metadata.getSpells().filter(spell => spell.data.isCastable && spell.data.hasCastTime).map(actionId => {
return {
value: actionId.id,
};
});
},
},
'channel_spells': {
defaultLabel: 'Channeled Spell',
getActionIDs: async (metadata) => {
Expand Down
24 changes: 24 additions & 0 deletions ui/core/components/individual_sim_ui/apl_values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import {
APLValueWarlockShouldRecastDrainSoul,
APLValueWarlockShouldRefreshCorruption,
APLValueCatNewSavageRoarDuration,
APLValueBossSpellTimeToReady,
APLValueBossSpellIsCasting,
} from '../../proto/apl.js';

import { EventID } from '../../typed_event.js';
Expand Down Expand Up @@ -554,6 +556,28 @@ const valueKindFactories: {[f in NonNullable<APLValueKind>]: ValueKindConfig<APL
fields: [],
}),

// Boss
'bossSpellIsCasting': inputBuilder({
label: 'Spell is Casting',
submenu: ['Boss'],
shortDescription: '',
newValue: APLValueBossSpellIsCasting.create,
fields: [
AplHelpers.unitFieldConfig('targetUnit', 'targets'),
AplHelpers.actionIdFieldConfig('spellId', 'non_instant_spells', 'targetUnit', 'currentTarget'),
]
}),
'bossSpellTimeToReady': inputBuilder({
label: 'Spell Time to Ready',
submenu: ['Boss'],
shortDescription: '',
newValue: APLValueBossSpellTimeToReady.create,
fields: [
AplHelpers.unitFieldConfig('targetUnit', 'targets'),
AplHelpers.actionIdFieldConfig('spellId', 'spells', 'targetUnit', 'currentTarget'),
]
}),

// Resources
'currentHealth': inputBuilder({
label: 'Health',
Expand Down
Loading