diff --git a/proto/common.proto b/proto/common.proto index 4d7593e57a..31a08a4da4 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -998,6 +998,12 @@ message Cooldowns { } message HealingModel { + // Proto version at the time these healing settings were saved. If you + // make any changes to this proto that will break saved browser data or + // old sim links, then make sure to increment the current_version_number + // option within the ProtoVersion message at the top of this file, and + // also modify the updateHealingModelProtoVersion() method of ui/core/player.ts. + int32 api_version = 6; // Healing per second to apply. double hps = 1; // How often healing is applied. diff --git a/ui/core/components/encounter_picker.ts b/ui/core/components/encounter_picker.ts index b65c8294c3..f82201183c 100644 --- a/ui/core/components/encounter_picker.ts +++ b/ui/core/components/encounter_picker.ts @@ -384,7 +384,7 @@ class TargetPicker extends Input { this.statPickers = ALL_TARGET_STATS.map(statData => { const stat = statData.stat; return new NumberPicker(section2, null, { - id: `target-picker-stats-${statData.stat}`, + id: `target-${this.targetIndex}-picker-stats-${statData.stat}`, inline: true, extraCssClasses: statData.extraCssClasses, label: getStatName(stat), @@ -399,7 +399,7 @@ class TargetPicker extends Input { }); this.swingSpeedPicker = new NumberPicker(section3, null, { - id: 'target-picker-swing-speed', + id: `target-${this.targetIndex}-picker-swing-speed`, label: 'Swing Speed', labelTooltip: 'Time in seconds between auto attacks. Set to 0 to disable auto attacks.', float: true, @@ -411,7 +411,7 @@ class TargetPicker extends Input { }, }); this.minBaseDamagePicker = new NumberPicker(section3, null, { - id: 'target-picker-min-base-damage', + id: `target-${this.targetIndex}-picker-min-base-damage`, label: 'Min Base Damage', labelTooltip: 'Base damage for auto attacks, i.e. lowest roll with 0 AP against a 0-armor Player.', changedEvent: () => encounter.targetsChangeEmitter, @@ -422,7 +422,7 @@ class TargetPicker extends Input { }, }); this.damageSpreadPicker = new NumberPicker(section3, null, { - id: 'target-picker-damage-spread', + id: `target-${this.targetIndex}-picker-damage-spread`, label: 'Damage Spread', labelTooltip: 'Fractional spread between the minimum and maximum auto-attack damage from this enemy at 0 Attack Power.', float: true, @@ -434,7 +434,7 @@ class TargetPicker extends Input { }, }); this.dualWieldPicker = new BooleanPicker(section3, null, { - id: 'target-picker-dual-wield', + id: `target-${this.targetIndex}-picker-dual-wield`, label: 'Dual Wield', labelTooltip: 'Uses 2 separate weapons to attack.', inline: true, @@ -447,7 +447,7 @@ class TargetPicker extends Input { }, }); this.dwMissPenaltyPicker = new BooleanPicker(section3, null, { - id: 'target-picker-dw-miss-penalty', + id: `target-${this.targetIndex}-picker-dw-miss-penalty`, label: 'DW Miss Penalty', labelTooltip: 'Enables the Dual Wield Miss Penalty (+19% chance to miss) if dual wielding. Bosses in Hyjal/BT/SWP usually have this disabled to stop tanks from avoidance stacking.', @@ -462,7 +462,7 @@ class TargetPicker extends Input { enableWhen: () => this.getTarget().dualWield, }); this.parryHastePicker = new BooleanPicker(section3, null, { - id: 'target-picker-parry-haste', + id: `target-${this.targetIndex}-picker-parry-haste`, label: 'Parry Haste', labelTooltip: 'Whether this enemy will gain parry haste when parrying attacks.', inline: true, @@ -475,7 +475,7 @@ class TargetPicker extends Input { }, }); this.spellSchoolPicker = new EnumPicker(section3, null, { - id: 'target-picker-spell-school', + id: `target-${this.targetIndex}-picker-spell-school`, label: 'Spell School', labelTooltip: 'Type of damage caused by auto attacks. This is usually Physical, but some enemies have elemental attacks.', values: [ diff --git a/ui/core/encounter.ts b/ui/core/encounter.ts index 13d90e3a67..37fc8a57fe 100644 --- a/ui/core/encounter.ts +++ b/ui/core/encounter.ts @@ -39,7 +39,7 @@ export class Encounter { } get primaryTarget(): TargetProto { - return TargetProto.clone(this.targets[0]); + return this.targets[0]; } getDurationVariation(): number { @@ -200,11 +200,11 @@ export class Encounter { static updateProtoVersion(proto: EncounterProto) { let showOutOfDateEncounterTargetWarning = false; - proto.targets.forEach(target => { + proto.targets.forEach((target, index) => { // If the old target is detected return the // new default target without needing to migrate the stats if (target.minBaseDamage === 65000) { - target = Encounter.defaultTargetProto(); + proto.targets[index] = Encounter.defaultTargetProto(); showOutOfDateEncounterTargetWarning = true; return; } @@ -222,7 +222,7 @@ export class Encounter { new Toast({ delay: 5000, variant: 'info', - body: 'We detected an out-of-date encounter target with WOTLK settings. It has been updated it to the latest version.', + body: 'We detected an out-of-date encounter target with WOTLK settings. Encounter settings have been updated the latest defaults.', }); } } diff --git a/ui/core/player.ts b/ui/core/player.ts index 7d137f4538..378e80f647 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -1,5 +1,6 @@ import Toast from './components/toast'; import * as Mechanics from './constants/mechanics'; +import { CURRENT_API_VERSION } from './constants/other'; import { MAX_PARTY_SIZE, Party } from './party'; import { PlayerClass } from './player_class'; import { PlayerSpec } from './player_spec'; @@ -1505,6 +1506,11 @@ export class Player { fromProto(eventID: EventID, proto: PlayerProto, includeCategories?: Array) { const loadCategory = (cat: SimSettingCategories) => !includeCategories || includeCategories.length == 0 || includeCategories.includes(cat); + // Fix out-of-date protos before importing + if (proto.healingModel && proto.healingModel.apiVersion < CURRENT_API_VERSION) { + Player.updateHealingModelProtoVersion(proto); + } + TypedEvent.freezeAllAndDo(() => { if (loadCategory(SimSettingCategories.Gear)) { this.setGear(eventID, proto.equipment ? this.sim.db.lookupEquipmentSpec(proto.equipment) : new Gear({})); @@ -1548,6 +1554,15 @@ export class Player { }); } + static updateHealingModelProtoVersion(proto: PlayerProto) { + // API version null -> 3: Added new encounter target defaults + // This means we should reset the healing model to prevent incorrect behavior + // due to automatic defaults being calculated based on encounter target stats. + if (proto.healingModel && proto.healingModel.apiVersion < 3) { + proto.healingModel = HealingModel.create(); + } + } + clone(eventID: EventID): Player { const newPlayer = new Player(this.playerSpec, this.sim); newPlayer.fromProto(eventID, this.toProto());