Skip to content

Commit

Permalink
Merge pull request #3242 from wowsims/apl
Browse files Browse the repository at this point in the history
Simplify spell and aura stat info, and use an APLValue for the Wait a…
  • Loading branch information
jimmyt857 authored Jul 3, 2023
2 parents 8ae19db + 0551596 commit c1fe848
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 77 deletions.
15 changes: 12 additions & 3 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,16 @@ message RaidSimResult {
message ComputeStatsRequest {
Raid raid = 1;
}
message AuraStats {
ActionID id = 1;
}
message SpellStats {
ActionID id = 1;

bool is_castable = 2; // Whether this spell may be cast by the APL logic.
bool is_major_cooldown = 3; // Whether this spell is a major cooldown.
bool has_dot = 4; // Whether this spell applies a DoT effect.
}
message PlayerStats {
// Stats
UnitStats base_stats = 6;
Expand All @@ -304,10 +314,9 @@ message PlayerStats {

repeated string sets = 3;
IndividualBuffs buffs = 4;
repeated ActionID cooldowns = 5;

repeated ActionID spells = 10;
repeated ActionID auras = 11;
repeated SpellStats spells = 10;
repeated AuraStats auras = 11;
}
message PartyStats {
repeated PlayerStats players = 1;
Expand Down
2 changes: 1 addition & 1 deletion proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ message APLActionCastSpell {
}

message APLActionWait {
Duration duration = 1;
APLValue duration = 1;
}

///////////////////////////////////////////////////////////////////////////
Expand Down
10 changes: 4 additions & 6 deletions sim/core/apl_actions_core.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package core

import (
"time"

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

Expand All @@ -29,18 +27,18 @@ func (action *APLActionCastSpell) Execute(sim *Simulation) {

type APLActionWait struct {
unit *Unit
duration time.Duration
duration APLValue
}

func (unit *Unit) newActionWait(config *proto.APLActionWait) APLActionImpl {
return &APLActionWait{
unit: unit,
duration: DurationFromProto(config.Duration),
duration: unit.coerceTo(unit.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration),
}
}
func (action *APLActionWait) IsAvailable(sim *Simulation) bool {
return true
return action.duration != nil
}
func (action *APLActionWait) Execute(sim *Simulation) {
action.unit.WaitUntil(sim, sim.CurrentTime+action.duration)
action.unit.WaitUntil(sim, sim.CurrentTime+action.duration.GetDuration(sim))
}
20 changes: 12 additions & 8 deletions sim/core/character.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,20 +501,24 @@ func (character *Character) FillPlayerStats(playerStats *proto.PlayerStats) {
}
character.clearBuildPhaseAuras(CharacterBuildPhaseAll)
playerStats.Sets = character.GetActiveSetBonusNames()
playerStats.Cooldowns = character.GetMajorCooldownIDs()

aplSpells := FilterSlice(character.Spellbook, func(spell *Spell) bool {
return spell.Flags.Matches(SpellFlagAPL)
})
playerStats.Spells = MapSlice(aplSpells, func(spell *Spell) *proto.ActionID {
return spell.ActionID.ToProto()
playerStats.Spells = MapSlice(character.Spellbook, func(spell *Spell) *proto.SpellStats {
return &proto.SpellStats{
Id: spell.ActionID.ToProto(),

IsCastable: spell.Flags.Matches(SpellFlagAPL),
IsMajorCooldown: spell.Flags.Matches(SpellFlagMCD),
HasDot: spell.dots != nil,
}
})

aplAuras := FilterSlice(character.auras, func(aura *Aura) bool {
return !aura.ActionID.IsEmptyAction()
})
playerStats.Auras = MapSlice(aplAuras, func(aura *Aura) *proto.ActionID {
return aura.ActionID.ToProto()
playerStats.Auras = MapSlice(aplAuras, func(aura *Aura) *proto.AuraStats {
return &proto.AuraStats{
Id: aura.ActionID.ToProto(),
}
})
}

Expand Down
38 changes: 32 additions & 6 deletions ui/core/components/individual_sim_ui/apl_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EventID } from '../../typed_event.js';
import { Input, InputConfig } from '../input.js';
import { Player } from '../../player.js';
import { TextDropdownPicker } from '../dropdown_picker.js';
import { ListItemPickerConfig, ListPicker } from '../list_picker.js';

import * as AplHelpers from './apl_helpers.js';
import * as AplValues from './apl_values.js';
Expand Down Expand Up @@ -40,6 +41,7 @@ export class APLActionPicker extends Input<Player<any>, APLAction> {
player.rotationChangeEmitter.emit(eventID);
},
});
this.conditionPicker.rootElem.classList.add('apl-action-condition');

this.actionDiv = document.createElement('div');
this.actionDiv.classList.add('apl-action-picker-action');
Expand Down Expand Up @@ -145,29 +147,53 @@ export class APLActionPicker extends Input<Player<any>, APLAction> {
player.rotationChangeEmitter.emit(eventID);
},
});
this.actionPicker.rootElem.classList.add('apl-action-' + newActionType);
}
}

type ActionTypeConfig = {
type ActionTypeConfig<T> = {
label: string,
newValue: () => object,
factory: (parent: HTMLElement, player: Player<any>, config: InputConfig<Player<any>, any>) => Input<Player<any>, any>,
newValue: () => T,
factory: (parent: HTMLElement, player: Player<any>, config: InputConfig<Player<any>, T>) => Input<Player<any>, T>,
};

function inputBuilder<T extends object>(label: string, newValue: () => T, fields: Array<AplHelpers.APLPickerBuilderFieldConfig<T, any>>): ActionTypeConfig {
function inputBuilder<T>(label: string, newValue: () => T, fields: Array<AplHelpers.APLPickerBuilderFieldConfig<T, any>>): ActionTypeConfig<T> {
return {
label: label,
newValue: newValue,
factory: AplHelpers.aplInputBuilder(newValue, fields),
};
}

export const actionTypeFactories: Record<NonNullable<APLActionType>, ActionTypeConfig> = {
export const actionTypeFactories: Record<NonNullable<APLActionType>, ActionTypeConfig<any>> = {
['castSpell']: inputBuilder('Cast', APLActionCastSpell.create, [
AplHelpers.actionIdFieldConfig('spellId', 'all_spells'),
AplHelpers.actionIdFieldConfig('spellId', 'castable_spells'),
]),
['sequence']: inputBuilder('Sequence', APLActionSequence.create, [
{
field: 'actions',
newValue: () => [],
factory: (parent, player, config) => new ListPicker<Player<any>, APLAction>(parent, player, {
...config,
// Override setValue to replace undefined elements with default messages.
setValue: (eventID: EventID, player: Player<any>, newValue: Array<APLAction>) => {
config.setValue(eventID, player, newValue.map(val => val || APLAction.create()));
},

itemLabel: 'Action',
newItem: APLAction.create,
copyItem: (oldValue: APLAction) => oldValue ? APLAction.clone(oldValue) : oldValue,
newItemPicker: (parent: HTMLElement, listPicker: ListPicker<Player<any>, APLAction>, index: number, config: ListItemPickerConfig<Player<any>, APLAction>) => new APLActionPicker(parent, player, config),
horizontalLayout: true,
allowedActions: ['create', 'delete'],
}),
},
]),
['wait']: inputBuilder('Wait', APLActionWait.create, [
{
field: 'duration',
newValue: APLValue.create,
factory: (parent, player, config) => new AplValues.APLValuePicker(parent, player, config),
},
]),
};
43 changes: 19 additions & 24 deletions ui/core/components/individual_sim_ui/apl_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,52 @@
import { ActionId } from '../../proto_utils/action_id.js';
import { Player } from '../../player.js';
import { EventID, TypedEvent } from '../../typed_event.js';
import { stringComparator } from '../../utils.js';
import { bucket } from '../../utils.js';
import { DropdownPicker, DropdownPickerConfig, DropdownValueConfig, TextDropdownPicker } from '../dropdown_picker.js';
import { Input, InputConfig } from '../input.js';
import { ActionID } from '../../proto/common.js';

export type ACTION_ID_SET = 'all_spells' | 'dots';

export interface APLActionIDPickerConfig<ModObject> extends Omit<DropdownPickerConfig<ModObject, ActionId>, 'defaultLabel' | 'equals' | 'setOptionContent' | 'values' | 'getValue' | 'setValue'> {
actionIdSet: ACTION_ID_SET,
getValue: (obj: ModObject) => ActionID,
setValue: (eventID: EventID, obj: ModObject, newValue: ActionID) => void,
}
export type ACTION_ID_SET = 'castable_spells' | 'dot_spells';

const actionIdSets: Record<ACTION_ID_SET, {
defaultLabel: string,
getActionIDs: (player: Player<any>) => Promise<Array<DropdownValueConfig<ActionId>>>,
}> = {
['all_spells']: {
['castable_spells']: {
defaultLabel: 'Spell',
getActionIDs: async (player) => {
const playerStats = player.getCurrentStats();
const spellPromises = Promise.all(playerStats.spells.map(spell => ActionId.fromProto(spell).fill()));
const cooldownPromises = Promise.all(playerStats.cooldowns.map(cd => ActionId.fromProto(cd).fill()));
const castableSpells = player.getSpells().filter(spell => spell.data.isCastable);

let [spells, cooldowns] = await Promise.all([spellPromises, cooldownPromises]);
spells = spells.sort((a, b) => stringComparator(a.name, b.name))
cooldowns = cooldowns.sort((a, b) => stringComparator(a.name, b.name))
// Split up non-cooldowns and cooldowns into separate sections for easier browsing.
const {'spells': spells, 'cooldowns': cooldowns } = bucket(castableSpells, spell => spell.data.isMajorCooldown ? 'cooldowns' : 'spells');

return [...spells, ...cooldowns].map(actionId => {
return [...(spells || []), ...(cooldowns || [])].map(actionId => {
return {
value: actionId,
value: actionId.id,
};
});
},
},
['dots']: {
['dot_spells']: {
defaultLabel: 'DoT Spell',
getActionIDs: async (player) => {
const playerStats = player.getCurrentStats();
let spells = await Promise.all(playerStats.spells.map(spell => ActionId.fromProto(spell).fill()));
spells = spells.sort((a, b) => stringComparator(a.name, b.name))
const dotSpells = player.getSpells().filter(spell => spell.data.hasDot);

return spells.map(actionId => {
return dotSpells.map(actionId => {
return {
value: actionId,
value: actionId.id,
};
});
},
},
};

export interface APLActionIDPickerConfig<ModObject> extends Omit<DropdownPickerConfig<ModObject, ActionId>, 'defaultLabel' | 'equals' | 'setOptionContent' | 'values' | 'getValue' | 'setValue'> {
actionIdSet: ACTION_ID_SET,
getValue: (obj: ModObject) => ActionID,
setValue: (eventID: EventID, obj: ModObject, newValue: ActionID) => void,
}

export class APLActionIDPicker extends DropdownPicker<Player<any>, ActionId> {
constructor(parent: HTMLElement, player: Player<any>, config: APLActionIDPickerConfig<Player<any>>) {
const actionIdSet = actionIdSets[config.actionIdSet];
Expand Down Expand Up @@ -81,7 +76,7 @@ export class APLActionIDPicker extends DropdownPicker<Player<any>, ActionId> {
this.setOptions(values);
};
updateValues();
player.currentStatsEmitter.on(updateValues);
player.currentSpellsAndAurasEmitter.on(updateValues);
}
}

Expand Down
4 changes: 2 additions & 2 deletions ui/core/components/individual_sim_ui/apl_values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { EventID, TypedEvent } from '../../typed_event.js';
import { Input, InputConfig } from '../input.js';
import { Player } from '../../player.js';
import { TextDropdownPicker, TextDropdownValueConfig } from '../dropdown_picker.js';
import { ListItemPickerConfig, ListPicker, ListItemAction } from '../list_picker.js';
import { ListItemPickerConfig, ListPicker } from '../list_picker.js';

import * as AplHelpers from './apl_helpers.js';

Expand Down Expand Up @@ -286,6 +286,6 @@ const valueTypeFactories: Record<NonNullable<APLValueType>, ValueTypeConfig<any>
},
]),
['dotIsActive']: inputBuilder('Dot Is Active', APLValueDotIsActive.create, [
AplHelpers.actionIdFieldConfig('spellId', 'dots'),
AplHelpers.actionIdFieldConfig('spellId', 'dot_spells'),
]),
};
12 changes: 3 additions & 9 deletions ui/core/components/individual_sim_ui/cooldowns_picker.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { Component } from '../component.js';
import { IconEnumPicker, IconEnumValueConfig } from '../icon_enum_picker.js';
import { Input, InputConfig } from '../input.js';
import { NumberListPicker } from '../number_list_picker.js';
import { Player } from '../../player.js';
import { EventID, TypedEvent } from '../../typed_event.js';
import { ActionID as ActionIdProto, ItemSlot } from '../../proto/common.js';
import { Cooldowns } from '../../proto/common.js';
import { Cooldown } from '../../proto/common.js';
import { ActionId } from '../../proto_utils/action_id.js';
import { Class } from '../../proto/common.js';
import { Spec } from '../../proto/common.js';
import { getEnumValues } from '../../utils.js';
import { wait } from '../../utils.js';
import { Tooltip } from 'bootstrap';
import { NumberPicker } from '../number_picker.js';
import { Sim } from 'ui/core/sim.js';
Expand All @@ -26,7 +20,7 @@ export class CooldownsPicker extends Component {
this.player = player;
this.cooldownPickers = [];

TypedEvent.onAny([this.player.currentStatsEmitter]).on(eventID => {
TypedEvent.onAny([this.player.currentSpellsAndAurasEmitter]).on(eventID => {
this.update();
});
this.update();
Expand Down Expand Up @@ -133,7 +127,7 @@ export class CooldownsPicker extends Component {
}

private makeActionPicker(parentElem: HTMLElement, cooldownIndex: number): IconEnumPicker<Player<any>, ActionIdProto> {
const availableCooldowns = this.player.getCurrentStats().cooldowns;
const availableCooldowns = this.player.getSpells().filter(spell => spell.data.isMajorCooldown).map(spell => spell.id);

const actionPicker = new IconEnumPicker<Player<any>, ActionIdProto>(parentElem, this.player, {
extraCssClasses: [
Expand All @@ -143,7 +137,7 @@ export class CooldownsPicker extends Component {
values: ([
{ color: '#grey', value: ActionIdProto.create() },
] as Array<IconEnumValueConfig<Player<any>, ActionIdProto>>).concat(availableCooldowns.map(cooldownAction => {
return { actionId: ActionId.fromProto(cooldownAction), value: cooldownAction };
return { actionId: cooldownAction, value: cooldownAction.toProto() };
})),
equals: (a: ActionIdProto, b: ActionIdProto) => ActionIdProto.equals(a, b),
zeroValue: ActionIdProto.create(),
Expand Down
Loading

0 comments on commit c1fe848

Please sign in to comment.