Skip to content
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
5 changes: 5 additions & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,11 @@
},
"pet": {
"title": "Pet"
},
"imbue": {
"title": "Imbues",
"mhImbue": "Main Hand",
"ohImbue": "Off Hand"
}
},
"encounter": {
Expand Down
8 changes: 6 additions & 2 deletions proto/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,12 @@ message ConsumesSpec {
int32 battle_elixir_id = 4;
int32 guardian_elixir_id = 5;
int32 food_id = 6;
int32 explosive_id = 7;
int32 conjured_id = 9;
int32 conjured_id = 7;
int32 explosive_id = 8;
bool superSapper = 9;
bool goblinSapper = 10;
int32 mhImbue_id = 11;
int32 ohImbue_id = 12;
}

enum MobType {
Expand Down
23 changes: 22 additions & 1 deletion schemas/translation.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2521,6 +2521,26 @@
"required": [
"title"
]
},
"imbue": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"mhImbue": {
"type": "string"
},
"ohImbue": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"title",
"mhImbue",
"ohImbue"
]
}
},
"additionalProperties": false,
Expand All @@ -2530,7 +2550,8 @@
"elixirs",
"food",
"engineering",
"pet"
"pet",
"imbue"
]
},
"encounter": {
Expand Down
12 changes: 10 additions & 2 deletions sim/core/armor.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package core

import (
"math"

"github.com/wowsims/tbc/sim/core/stats"
)

func (result *SpellResult) applyArmor(spell *Spell, isPeriodic bool, attackTable *AttackTable) {
armorMitigationMultiplier := spell.armorMultiplier(isPeriodic, attackTable)

Expand Down Expand Up @@ -38,7 +44,9 @@ func (at *AttackTable) getArmorDamageModifier() float64 {
ignoreArmorFactor := Clamp(at.ArmorIgnoreFactor, 0.0, 1.0)

// Assume target > 80
armorConstant := float64(at.Attacker.Level)*4037.5 - 317117.5
defenderArmor := at.Defender.Armor() * (1.0 - ignoreArmorFactor)
armorConstant := float64(at.Attacker.Level)*467.5 - 22167.5
defenderArmor := at.Defender.Armor() - (at.Defender.Armor() * ignoreArmorFactor)
// TBC ANNI: Apply flat ArP
defenderArmor = max(defenderArmor-math.Abs(at.Attacker.stats[stats.ArmorPenetration]), 0)
return 1 - defenderArmor/(defenderArmor+armorConstant)
}
2 changes: 1 addition & 1 deletion sim/core/dot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func NewFakeElementalShaman(char *Character, _ *proto.Player) Agent {
AffectedByCastSpeed: true,
BonusCoefficient: 1,

OnSnapshot: func(sim *Simulation, target *Unit, dot *Dot, isRollover bool) {
OnSnapshot: func(sim *Simulation, target *Unit, dot *Dot) {
dot.Snapshot(target, 100)
},
OnTick: func(sim *Simulation, target *Unit, dot *Dot) {
Expand Down
12 changes: 6 additions & 6 deletions tools/database/dbc/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,17 @@ func ConvertTargetResistanceFlagToPenetrationStat(flag int) proto.Stat {
func ConvertSpellDamageFlagToSchoolDamageStat(flag int) proto.Stat {
switch flag {
case 2:
return proto.Stat_StatHolyPower
return proto.Stat_StatHolyDamage
case 4:
return proto.Stat_StatFirePower
return proto.Stat_StatFireDamage
case 8:
return proto.Stat_StatNaturePower
return proto.Stat_StatNatureDamage
case 16:
return proto.Stat_StatFrostPower
return proto.Stat_StatFrostDamage
case 32:
return proto.Stat_StatShadowPower
return proto.Stat_StatShadowDamage
case 64:
return proto.Stat_StatArcanePower
return proto.Stat_StatArcaneDamage
default:
return proto.Stat_StatSpellDamage
}
Expand Down
2 changes: 1 addition & 1 deletion tools/tooltip/tooltip_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var db = dbc.GetDBC()

func Test_WhenInvalidTernaryGiven_ThenProperlyApplyFixes(t *testing.T) {
tp, error := ParseTooltip("$<dam> damage every ${$16914d3/10}.2 seconds$?$w1!=0[ and movement slowed by $w1%][].",
NewTestDataProvider(CharacterConfig{SpellPower: 1000}),
NewTestDataProvider(CharacterConfig{SpellDamage: 1000}),
16914,
)

Expand Down
31 changes: 29 additions & 2 deletions ui/core/components/individual_sim_ui/consumes_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class ConsumesPicker extends Component {
this.buildFoodPicker();
this.buildEngPicker();
this.buildPetPicker();
this.buildImbuePicker();
}

private buildPotionsPicker(): void {
Expand Down Expand Up @@ -135,12 +136,14 @@ export class ConsumesPicker extends Component {

const explosivesoptions = ConsumablesInputs.makeExplosivesInput(relevantStatOptions(ConsumablesInputs.EXPLOSIVE_CONFIG, this.simUI), i18n.t('settings_tab.consumables.engineering.explosives'));
const explosivePicker = buildIconInput(engiConsumesElem, this.simUI.player, explosivesoptions);
const goblinSapperPicker = buildIconInput(engiConsumesElem, this.simUI.player, ConsumablesInputs.GoblinSapper);
const superSapperPicker = buildIconInput(engiConsumesElem, this.simUI.player, ConsumablesInputs.SuperSapper);

const events = this.simUI.player.professionChangeEmitter.on(() => this.updateRow(row, [explosivePicker]));
const events = this.simUI.player.professionChangeEmitter.on(() => this.updateRow(row, [explosivePicker, goblinSapperPicker, superSapperPicker]));
this.addOnDisposeCallback(() => events.dispose());

// Initial update of row based on current state.
this.updateRow(row, [explosivePicker]);
this.updateRow(row, [explosivePicker, goblinSapperPicker, superSapperPicker]);
}

private buildPetPicker(): void {
Expand All @@ -158,6 +161,26 @@ export class ConsumesPicker extends Component {
}
}

private buildImbuePicker(): void {
const imbuePickerRef = ref<HTMLDivElement>();
const row = this.rootElem.appendChild(
<ConsumeRow label="Imbue">
<div ref={imbuePickerRef} className="picker-group icon-group consumes-row-inputs consumes-imbue"></div>
</ConsumeRow>
);
const imbuePickerElem = imbuePickerRef.value!;

const mhImbueOptions = ConsumablesInputs.makeMHImbueInput(relevantStatOptions(ConsumablesInputs.IMBUE_CONFIG_MH, this.simUI), i18n.t('settings_tab.consumables.imbue.mhImbue'));
const ohImbueOptions = ConsumablesInputs.makeOHImbueinput(relevantStatOptions(ConsumablesInputs.IMBUE_CONFIG_OH, this.simUI), i18n.t('settings_tab.consumables.imbue.ohImbue'));
mhImbueOptions.enableWhen = (player: Player<any>) => !player.getParty() || player.getParty()!.getBuffs().windfuryTotemRank == 0
mhImbueOptions.changedEvent = (player: Player<any>) => TypedEvent.onAny([player.getRaid()?.changeEmitter || player.consumesChangeEmitter]);

buildIconInput(imbuePickerElem, this.simUI.player, mhImbueOptions);
if (isDualWieldSpec(this.simUI.player.getSpec())) {
buildIconInput(imbuePickerElem, this.simUI.player, ohImbueOptions);
}
}

private updateRow(rowElem: Element, pickers: (IconPicker<Player<any>, any> | IconEnumPicker<Player<any>, any>)[]) {
rowElem.classList[!!pickers.find(p => p?.showWhen()) ? 'remove' : 'add']('hide');
}
Expand All @@ -170,3 +193,7 @@ const ConsumeRow = ({ label, children }: { label: string; children: JSX.Element
{children}
</div>
);
function isDualWieldSpec(spec: any): boolean {
return [Spec.SpecEnhancementShaman, Spec.SpecHunter, Spec.SpecRogue, Spec.SpecDPSWarrior, Spec.SpecProtectionWarrior].includes(spec)
}

159 changes: 151 additions & 8 deletions ui/core/components/inputs/consumables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import * as InputHelpers from '../input_helpers';
import { IconEnumValueConfig } from '../pickers/icon_enum_picker';
import { ActionInputConfig, ItemStatOption } from './stat_options';
import i18n from '../../../i18n/config.js';
import { makeBooleanConsumeInput } from '../icon_inputs';
import { playerPresets } from '../../../raid/presets';

export interface ConsumableInputConfig<T> extends ActionInputConfig<T> {
value: T;
Expand Down Expand Up @@ -90,24 +92,165 @@ export const CONJURED_CONFIG = [

export const makeConjuredInput = makeConsumeInputFactory({ consumesFieldName: 'conjuredId' });

export const ExplosiveBigDaddy = {
actionId: ActionId.fromItemId(63396),
value: 89637,
///////////////////////////////////////////////////////////////////////////
// ENGINEERING
///////////////////////////////////////////////////////////////////////////

export const AdamantiteGrenade = {
actionId: ActionId.fromItemId(23737),
value: 30217,
showWhen: (player: Player<any>) => player.hasProfession(Profession.Engineering),
};

export const FelIronBomb = {
actionId: ActionId.fromItemId(23736),
value: 30216,
showWhen: (player: Player<any>) => player.hasProfession(Profession.Engineering),
};

export const HighpoweredBoltGun = {
actionId: ActionId.fromItemId(60223),
value: 82207,
export const GnomishFlameTurrent = {
actionId: ActionId.fromItemId(23841),
value: 30526,
showWhen: (player: Player<any>) => player.hasProfession(Profession.Engineering),
};

export const EXPLOSIVE_CONFIG = [
{ config: ExplosiveBigDaddy, stats: [] },
{ config: HighpoweredBoltGun, stats: [] },
{ config: AdamantiteGrenade, stats: [] },
{ config: FelIronBomb, stats: [] },
{ config: GnomishFlameTurrent, stats: [] },
] as ConsumableStatOption<number>[];
export const makeExplosivesInput = makeConsumeInputFactory({ consumesFieldName: 'explosiveId' });

export const GoblinSapper = makeBooleanConsumeInput({
actionId: ActionId.fromItemId(10646),
fieldName: 'goblinSapper',
showWhen: (player: Player<any>) => player.hasProfession(Profession.Engineering),
})

export const SuperSapper = makeBooleanConsumeInput({
actionId: ActionId.fromItemId(23827),
fieldName: 'superSapper',
showWhen: (player: Player<any>) => player.hasProfession(Profession.Engineering),
})

///////////////////////////////////////////////////////////////////////////
// WEAPON IMBUES
///////////////////////////////////////////////////////////////////////////

// Oils
export const ManaOil = {
actionId: ActionId.fromItemId(20748),
value: 25123,
};
export const BrilWizardOil = {
actionId: ActionId.fromItemId(20749),
value: 25122,
};
export const SupWizardOil = {
actionId: ActionId.fromItemId(22522),
value: 28017,
};
// Stones
export const AdamantiteSharpeningMH = {
actionId: ActionId.fromItemId(23529),
value: 29453,
showWhen: (player: Player<any>) => !player.getGear().hasBluntMHWeapon()
};
export const AdamantiteWeightMH = {
actionId: ActionId.fromItemId(28421),
value: 34340,
showWhen: (player: Player<any>) => player.getGear().hasBluntMHWeapon()
};
export const AdamantiteSharpeningOH = {
actionId: ActionId.fromItemId(23529),
value: 29453,
showWhen: (player: Player<any>) => !player.getGear().hasBluntOHWeapon()
};
export const AdamantiteWeightOH = {
actionId: ActionId.fromItemId(28421),
value: 34340,
showWhen: (player: Player<any>) => player.getGear().hasBluntOHWeapon()
};
// Rogue Poisons
export const RogueInstantPoison = {
actionId: ActionId.fromItemId(21927),
value: 26891,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassRogue
}
export const RogueDeadlyPoison = {
actionId: ActionId.fromItemId(22054),
value: 27186,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassRogue
}
// Shaman Imbues
export const ShamanImbueWindfury = {
actionId: ActionId.fromSpellId(25505),
value: 25505,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassShaman
}
export const ShamanImbueFlametongue = {
actionId: ActionId.fromSpellId(25489),
value: 25489,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassShaman
}

export const ShamanImbueFrostbrand = {
actionId: ActionId.fromSpellId(25500),
value: 25500,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassShaman
}

export const ShamanImbueRockbiter = {
actionId: ActionId.fromSpellId(25485),
value: 25485,
showWhen: (player: Player<any>) => player.getClass() == Class.ClassShaman
}

export const IMBUE_CONFIG_MH = [
{ config: ManaOil, stats: [Stat.StatHealingPower] },
{ config: BrilWizardOil, stats: [Stat.StatSpellDamage] },
{ config: SupWizardOil, stats: [Stat.StatSpellDamage] },
{ config: AdamantiteSharpeningMH, stats: [Stat.StatAttackPower] },
{ config: AdamantiteWeightMH, stats: [Stat.StatAttackPower] },
{ config: RogueInstantPoison, stats: [] },
{ config: RogueDeadlyPoison, stats: [] },
{ config: ShamanImbueRockbiter, stats: [] },
{ config: ShamanImbueFrostbrand, stats: [] },
{ config: ShamanImbueFlametongue, stats: [] },
{ config: ShamanImbueWindfury, stats: [] },
] as ConsumableStatOption<number>[];

export const IMBUE_CONFIG_OH = [
{ config: ManaOil, stats: [Stat.StatHealingPower] },
{ config: BrilWizardOil, stats: [Stat.StatSpellDamage] },
{ config: SupWizardOil, stats: [Stat.StatSpellDamage] },
{ config: AdamantiteSharpeningOH, stats: [Stat.StatAttackPower] },
{ config: AdamantiteWeightOH, stats: [Stat.StatAttackPower] },
{ config: RogueInstantPoison, stats: [] },
{ config: RogueDeadlyPoison, stats: [] },
{ config: ShamanImbueRockbiter, stats: [] },
{ config: ShamanImbueFrostbrand, stats: [] },
{ config: ShamanImbueFlametongue, stats: [] },
{ config: ShamanImbueWindfury, stats: [] },
] as ConsumableStatOption<number>[];

export const makeMHImbueInput = makeConsumeInputFactory({ consumesFieldName: 'mhImbueId' });
export const makeOHImbueinput = makeConsumeInputFactory({ consumesFieldName: 'ohImbueId' });

///////////////////////////////////////////////////////////////////////////
// DRUMS
///////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////////
// SCROLLS
///////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////////

export interface ConsumableInputOptions {
consumesFieldName: keyof ConsumesSpec;
setValue?: (eventID: EventID, player: Player<any>, newValue: number) => void;
Expand Down
Loading