Skip to content

Commit

Permalink
Merge pull request #1061 from wowsims/feature/extend-reforger-toast
Browse files Browse the repository at this point in the history
[UI] Extend suggest reforge toast
  • Loading branch information
1337LutZ committed Sep 27, 2024
2 parents 776c448 + 0ef7109 commit dd04030
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 78 deletions.
8 changes: 4 additions & 4 deletions ui/core/components/gear_picker/gear_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ export class ItemRenderer extends Component {
this.nameContainerElem.appendChild(this.notice.rootElem);
}

if (newItem.reforge) {
const reforgeData = this.player.getReforgeData(newItem, newItem.reforge);
const fromText = shortSecondaryStatNames.get(newItem.reforge?.fromStat);
const toText = shortSecondaryStatNames.get(newItem.reforge?.toStat);
const reforgeData = newItem.getReforgeData();
if (reforgeData) {
const fromText = shortSecondaryStatNames.get(reforgeData.reforge?.fromStat);
const toText = shortSecondaryStatNames.get(reforgeData.reforge?.toStat);
this.reforgeElem.innerText = `Reforged ${Math.abs(reforgeData.fromAmount)} ${fromText}${reforgeData.toAmount} ${toText}`;
this.reforgeElem.classList.remove('hide');
} else {
Expand Down
4 changes: 2 additions & 2 deletions ui/core/components/gear_picker/item_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { SortDirection } from '../../constants/other';
import { EP_TOOLTIP } from '../../constants/tooltips';
import { setItemQualityCssClass } from '../../css_utils';
import { IndividualSimUI } from '../../individual_sim_ui';
import { Player, ReforgeData } from '../../player';
import { Player } from '../../player';
import { Class, GemColor, ItemQuality, ItemRandomSuffix, ItemSlot, ItemSpec } from '../../proto/common';
import { DatabaseFilters, RepFaction, UIEnchant as Enchant, UIGem as Gem, UIItem as Item, UIItem_FactionRestriction } from '../../proto/ui';
import { ActionId } from '../../proto_utils/action_id';
import { getUniqueEnchantString } from '../../proto_utils/enchants';
import { EquippedItem } from '../../proto_utils/equipped_item';
import { EquippedItem, ReforgeData } from '../../proto_utils/equipped_item';
import { difficultyNames, professionNames, REP_FACTION_NAMES, REP_FACTION_QUARTERMASTERS, REP_LEVEL_NAMES } from '../../proto_utils/names';
import { getPVPSeasonFromItem, isPVPItem } from '../../proto_utils/utils';
import { Sim } from '../../sim';
Expand Down
7 changes: 3 additions & 4 deletions ui/core/components/gear_picker/selector_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import clsx from 'clsx';
import tippy from 'tippy.js';
import { ref } from 'tsx-vanilla';

import { Player, ReforgeData } from '../../player';
import { Player } from '../../player';
import { GemColor, ItemQuality, ItemRandomSuffix, ItemSlot } from '../../proto/common';
import { UIEnchant as Enchant, UIGem as Gem, UIItem as Item } from '../../proto/ui';
import { ActionId } from '../../proto_utils/action_id';
import { EquippedItem } from '../../proto_utils/equipped_item';
import { EquippedItem, ReforgeData } from '../../proto_utils/equipped_item';
import { gemMatchesSocket, getEmptyGemSocketIconUrl } from '../../proto_utils/gems';
import { shortSecondaryStatNames, slotNames } from '../../proto_utils/names';
import { Stats } from '../../proto_utils/stats';
Expand Down Expand Up @@ -447,8 +447,7 @@ export default class SelectorModal extends BaseModal {
};
}),
computeEP: (reforge: ReforgeData) => this.player.computeReforgingEP(reforge),
equippedToItemFn: (equippedItem: EquippedItem | null) =>
equippedItem?.reforge ? this.player.getReforgeData(equippedItem, equippedItem.reforge) : null,
equippedToItemFn: (equippedItem: EquippedItem | null) => equippedItem?.getReforgeData() || null,
onRemove: (eventID: number) => {
const equippedItem = gearData.getEquippedItem();
if (equippedItem) {
Expand Down
31 changes: 13 additions & 18 deletions ui/core/components/individual_sim_ui/reforge_summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,21 @@ export class ReforgeSummary extends Component {

private updateTable() {
const body = <></>;
let gear = this.player.getGear();
const reforges = this.player.getGear().getAllReforges();
const totals: ReforgeSummaryTotal = {};
gear.getItemSlots().forEach(itemSlot => {
const item = gear.getEquippedItem(itemSlot);
if (item?.reforge && item.reforge?.id !== 0) {
const reforge = this.player.getReforgeData(item, item.reforge);
if (reforge) {
const { fromStat, toStat, fromAmount, toAmount } = reforge;

if (typeof totals[fromStat] !== 'number') {
totals[fromStat] = 0;
}
if (typeof totals[toStat] !== 'number') {
totals[toStat] = 0;
}
if (fromAmount) totals[fromStat]! += fromAmount;
if (toAmount) totals[toStat]! += toAmount;
}
for (const [_, reforgeData] of reforges) {
const { fromStat, toStat, fromAmount, toAmount } = reforgeData;

if (typeof totals[fromStat] !== 'number') {
totals[fromStat] = 0;
}
});
if (typeof totals[toStat] !== 'number') {
totals[toStat] = 0;
}
if (fromAmount) totals[fromStat]! += fromAmount;
if (toAmount) totals[toStat]! += toAmount;
}

const hasReforgedItems = !!Object.keys(totals).length;
this.rootElem.classList[!hasReforgedItems ? 'add' : 'remove']('hide');
Expand All @@ -77,7 +72,7 @@ export class ReforgeSummary extends Component {
<button
className="btn btn-sm btn-link btn-reset summary-table-reset-button"
onclick={() => {
gear = gear.withoutReforges(this.player.canDualWield2H());
const gear = this.player.getGear().withoutReforges(this.player.canDualWield2H());
this.player.setGear(TypedEvent.nextEventID(), gear);
}}>
<i className="fas fa-times me-1"></i>
Expand Down
88 changes: 75 additions & 13 deletions ui/core/components/suggest_reforges_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import { Constraint, greaterEq, lessEq, Model, Options, Solution, solve } from '
import * as Mechanics from '../constants/mechanics.js';
import { IndividualSimUI } from '../individual_sim_ui';
import { Player } from '../player';
import { Class, ItemSlot, PseudoStat, Spec, Stat } from '../proto/common';
import { StatCapType } from '../proto/ui';
import { Class, ItemSlot, PseudoStat, ReforgeStat, Spec, Stat } from '../proto/common';
import { IndividualSimSettings, StatCapType } from '../proto/ui';
import { ReforgeData } from '../proto_utils/equipped_item';
import { Gear } from '../proto_utils/gear';
import { slotNames, statCapTypeNames } from '../proto_utils/names';
import { shortSecondaryStatNames, slotNames, statCapTypeNames } from '../proto_utils/names';
import { pseudoStatIsCapped, StatCap, Stats, UnitStat, UnitStatPresets } from '../proto_utils/stats';
import { SpecTalents } from '../proto_utils/utils';
import { Sim } from '../sim';
import { ActionGroupItem } from '../sim_ui';
import { EventID, TypedEvent } from '../typed_event';
import { isDevMode, sleep } from '../utils';
import { CopyButton } from './copy_button';
import { BooleanPicker } from './pickers/boolean_picker';
import { EnumPicker } from './pickers/enum_picker';
import { NumberPicker, NumberPickerConfig } from './pickers/number_picker';
Expand Down Expand Up @@ -87,6 +89,8 @@ export class ReforgeOptimizer {
readonly freezeItemSlotsChangeEmitter = new TypedEvent<void>();
protected freezeItemSlots = false;
protected frozenItemSlots = new Map<ItemSlot, boolean>();
protected previousReforges = new Map<ItemSlot, ReforgeData>();
protected currentReforges = new Map<ItemSlot, ReforgeData>();

constructor(simUI: IndividualSimUI<any>, options?: ReforgeOptimizerOptions) {
this.simUI = simUI;
Expand Down Expand Up @@ -117,15 +121,9 @@ export class ReforgeOptimizer {
try {
performance.mark('reforge-optimization-start');
await this.optimizeReforges();
new Toast({
variant: 'success',
body: 'Reforge optimization complete!',
});
} catch {
new Toast({
variant: 'error',
body: 'Reforge optimization failed. Please try again, or report the issue if it persists.',
});
this.onReforgeDone();
} catch (error) {
this.onReforgeError(error);
} finally {
performance.mark('reforge-optimization-end');
if (isDevMode())
Expand Down Expand Up @@ -650,7 +648,9 @@ export class ReforgeOptimizer {
console.log('The following slots will not be cleared:');
console.log(Array.from(this.frozenItemSlots.keys()).filter(key => this.frozenItemSlots.get(key)));
}
const baseGear = this.player.getGear().withoutReforges(this.player.canDualWield2H(), this.frozenItemSlots);
const previousGear = this.player.getGear();
this.previousReforges = previousGear.getAllReforges();
const baseGear = previousGear.withoutReforges(this.player.canDualWield2H(), this.frozenItemSlots);
const baseStats = await this.updateGear(baseGear);

// Compute effective stat caps for just the Reforge contribution
Expand All @@ -673,6 +673,7 @@ export class ReforgeOptimizer {

// Solve in multiple passes to enforce caps
await this.solveModel(baseGear, validatedWeights, reforgeCaps, reforgeSoftCaps, variables, constraints);
this.currentReforges = this.player.getGear().getAllReforges();
}

async updateGear(gear: Gear): Promise<Stats> {
Expand Down Expand Up @@ -1001,4 +1002,65 @@ export class ReforgeOptimizer {
}
return statPoints;
}

onReforgeDone() {
const itemSlots = this.player.getGear().getItemSlots();
const changedSlots = new Map<ItemSlot, ReforgeData | undefined>();
for (const slot of itemSlots) {
const prev = this.previousReforges.get(slot);
const current = this.currentReforges.get(slot);
if (!ReforgeStat.equals(prev?.reforge, current?.reforge)) changedSlots.set(slot, current);
}
const hasReforgeChanges = changedSlots.size;

const copyButtonContainerRef = ref<HTMLDivElement>();
const changedReforgeMessage = (
<>
<p className="mb-0">The following items were reforged:</p>
<ul>
{[...changedSlots].map(([slot, reforge]) => {
if (reforge) {
const slotName = slotNames.get(slot);
const { fromStat, toStat } = reforge;
const fromText = shortSecondaryStatNames.get(fromStat);
const toText = shortSecondaryStatNames.get(toStat);
return (
<li>
{slotName}: {fromText}{toText}
</li>
);
} else {
return <li>{slotNames.get(slot)}: Removed reforge</li>;
}
})}
</ul>
<div ref={copyButtonContainerRef} />
</>
);

if (hasReforgeChanges) {
const settingsExport = IndividualSimSettings.toJson(this.simUI.toProto());
if (settingsExport)
new CopyButton(copyButtonContainerRef.value!, {
extraCssClasses: ['btn-outline-primary'],
getContent: () => JSON.stringify(settingsExport),
text: 'Copy to Reforge Lite',
});
}

new Toast({
variant: 'success',
body: hasReforgeChanges ? changedReforgeMessage : <>No reforge changes were made!</>,
delay: hasReforgeChanges ? 5000 : 3000,
});
}

onReforgeError(error: any) {
if (isDevMode()) console.log(error);

new Toast({
variant: 'error',
body: 'Reforge optimization failed. Please try again, or report the issue if it persists.',
});
}
}
32 changes: 2 additions & 30 deletions ui/core/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
} from './proto/ui';
import { ActionId } from './proto_utils/action_id';
import { Database } from './proto_utils/database';
import { EquippedItem, getWeaponDPS } from './proto_utils/equipped_item';
import { EquippedItem, getWeaponDPS, ReforgeData } from './proto_utils/equipped_item';
import { Gear, ItemSwapGear } from './proto_utils/gear';
import { gemMatchesSocket, isUnrestrictedGem } from './proto_utils/gems';
import { StatCap, Stats } from './proto_utils/stats';
Expand Down Expand Up @@ -220,16 +220,6 @@ export function getSpecConfig<SpecType extends Spec>(spec: SpecType): PlayerConf
return config;
}

export interface ReforgeData {
id: number;
item: Item;
reforge: ReforgeStat;
fromStat: Stat;
toStat: Stat;
fromAmount: number;
toAmount: number;
}

// Manages all the gear / consumes / other settings for a single Player.
export class Player<SpecType extends Spec> {
readonly sim: Sim;
Expand Down Expand Up @@ -455,32 +445,14 @@ export class Player<SpecType extends Spec> {
// Returns all reforgings that are valid with a given item
getAvailableReforgings(equippedItem: EquippedItem): Array<ReforgeData> {
const withRandomSuffixStats = equippedItem.getWithRandomSuffixStats();
return this.sim.db.getAvailableReforges(withRandomSuffixStats.item).map(reforge => {
return this.getReforgeData(equippedItem, reforge);
});
return this.sim.db.getAvailableReforges(withRandomSuffixStats.item).map(reforge => equippedItem.getReforgeData(reforge)!);
}

// Returns reforge given an id
getReforge(id: number): ReforgeStat | undefined {
return this.sim.db.getReforgeById(id);
}

getReforgeData(equippedItem: EquippedItem, reforge: ReforgeStat): ReforgeData {
const withRandomSuffixStats = equippedItem.getWithRandomSuffixStats();
const item = withRandomSuffixStats.item;
const fromAmount = Math.ceil(-item.stats[reforge.fromStat] * reforge.multiplier);
const toAmount = Math.floor(item.stats[reforge.fromStat] * reforge.multiplier);
return {
id: reforge.id,
reforge: reforge,
item: item,
fromStat: reforge.fromStat,
fromAmount: fromAmount,
toStat: reforge.toStat,
toAmount,
};
}

// Returns all enchants that this player can wear in the given slot.
getEnchants(slot: ItemSlot): Array<Enchant> {
return this.sim.db.getEnchants(slot).filter(enchant => canEquipEnchant(enchant, this.playerSpec));
Expand Down
32 changes: 31 additions & 1 deletion ui/core/proto_utils/equipped_item.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GemColor, ItemRandomSuffix, ItemSpec, ItemType, Profession, ReforgeStat } from '../proto/common.js';
import { GemColor, ItemRandomSuffix, ItemSpec, ItemType, Profession, ReforgeStat, Stat } from '../proto/common.js';
import { UIEnchant as Enchant, UIGem as Gem, UIItem as Item } from '../proto/ui.js';
import { distinct } from '../utils.js';
import { ActionId } from './action_id.js';
Expand All @@ -10,6 +10,16 @@ export function getWeaponDPS(item: Item): number {
return (item.weaponDamageMin + item.weaponDamageMax) / 2 / (item.weaponSpeed || 1);
}

export interface ReforgeData {
id: number;
item: Item;
reforge: ReforgeStat;
fromStat: Stat;
toStat: Stat;
fromAmount: number;
toAmount: number;
}

/**
* Represents an equipped item along with enchants/gems attached to it.
*
Expand Down Expand Up @@ -64,6 +74,26 @@ export class EquippedItem {
return this._gems.map(gem => (gem == null ? null : Gem.clone(gem)));
}

getReforgeData(reforge?: ReforgeStat | null): ReforgeData | null {
reforge = reforge || this.reforge;
if (!reforge) return null;
const { id, fromStat, toStat, multiplier } = reforge;
const withRandomSuffixStats = this.getWithRandomSuffixStats();
const item = withRandomSuffixStats.item;
const fromAmount = Math.ceil(-item.stats[fromStat] * multiplier);
const toAmount = Math.floor(item.stats[fromStat] * multiplier);

return {
id,
reforge,
item,
fromStat,
fromAmount,
toStat,
toAmount,
};
}

equals(other: EquippedItem) {
if (!Item.equals(this._item, other.item)) return false;

Expand Down
12 changes: 11 additions & 1 deletion ui/core/proto_utils/gear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { UIEnchant as Enchant, UIGem as Gem, UIItem as Item } from '../proto/ui.
import { isBluntWeaponType, isSharpWeaponType } from '../proto_utils/utils.js';
import { distinct, equalsOrBothNull, getEnumValues } from '../utils.js';
import { Database } from './database';
import { EquippedItem } from './equipped_item.js';
import { EquippedItem, ReforgeData } from './equipped_item.js';
import { gemMatchesSocket, isMetaGemActive } from './gems.js';
import { Stats } from './stats.js';
import { validWeaponCombo } from './utils.js';
Expand Down Expand Up @@ -372,6 +372,16 @@ export class Gear extends BaseGear {

return setItemCount;
}

getAllReforges() {
const reforgedItems = new Map<ItemSlot, ReforgeData>();
this.getEquippedItems().forEach((item, slot) => {
const reforgeData = item?.getReforgeData();
if (!reforgeData) return;
reforgedItems.set(slot, reforgeData);
});
return reforgedItems;
}
}

/**
Expand Down
10 changes: 5 additions & 5 deletions ui/scss/shared/_bootstrap_style_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,14 @@ $gray-800: #323232;
.toast {
--icon-color: var(--bs-warning);

i {
line-height: 1;
color: var(--icon-color);
}

.toast-header {
border-bottom: 0;
padding-bottom: 0;

i {
line-height: 1;
color: var(--icon-color);
}
}

.btn-close {
Expand Down

0 comments on commit dd04030

Please sign in to comment.