From fd8d3c5c80e2eabb5898d209c785cce65710255d Mon Sep 17 00:00:00 2001 From: Kayla Glick <12898988+kayla-glick@users.noreply.github.com> Date: Sun, 14 Apr 2024 15:37:00 -0400 Subject: [PATCH] Port item source info from SoD (#4246) * port two SoD updates * fix icon enum tooltips * gear picker style tweaks from sod * fix gem summary tooltips * fix apl tooltips * fix improved icon tooltips * port over improved item source information * update comment --- assets/img/alliance.png | Bin 0 -> 1315 bytes assets/img/horde.png | Bin 0 -> 3045 bytes proto/ui.proto | 26 +++++- ui/core/components/gear_picker.tsx | 129 +++++++++++++++++++---------- ui/core/proto_utils/names.ts | 41 ++++----- 5 files changed, 132 insertions(+), 64 deletions(-) create mode 100644 assets/img/alliance.png create mode 100644 assets/img/horde.png diff --git a/assets/img/alliance.png b/assets/img/alliance.png new file mode 100644 index 0000000000000000000000000000000000000000..34d97a35bc1273a3020b97e76535bf376855c41c GIT binary patch literal 1315 zcmbVLTWs4@7`9H=s>(LDr*0iQSl+5VbcyYRIJHSyg~VCSnyO6$O^FJ1;| zOImIMq`|~CDlSrY(=;|VMXPpE%gUJ001XflLPJ7=@xUH7U`R+jAjC~59CuB}1L1)q z+2^9~`~L6$&wuvz_3m8n+2WxnYQ54OPq{PsHZ(VofAe2&$z*E9at05eJT9p=q@o%c zg|uR*V=x6(ZMygg3{zACt*0|MlkAal$ndEy##c5h!ltNjSJ_f?6A;s*a7;HN%ukoE zFtn~kn89F@OIk5l(7R`BI55+j&dp5ZM2+d{q{C&22pABnblI3R9jP2)mUShvc8^(x zUWVX_2y@3&CfP^FkPYdekMrg@fujZ9#|Q2Yg+e=MKgV+{=Vy7pmk&t+zr=C$%3w$| zTgywScw!|M*+rNF#+JmgrBcaP3iyyc#`2;lx*C4Jmms{(w29TS*L1el6yngy*}8>w zWYVspI*O)ngdtAvq+nR9vZk|ACQ>lAtXeGZLz-02jp@y-Fc5n*b_4}{FPI}sc zYzjJP%FdCC%Wri;OBXn>)-+%iJ%zbgQ7@kAFXlYC^uz56W6%fU9MP{>o$U6 zk;rjq>${01({0fo>@n&C7T5jC&@VmRg+YY$*fz(=IxIL<5``fh+aB>_xG2D6{fxYTI884U^ zWG-QFFyGWogJKjka@jd;jVOl~EU>F5IQa-YZ-6&+aCr(mHQe&zu;<()cy$OIhhWhH ziS0lW!M+jC2@RYvz)}$$*T4(?pfuEaYz(XuvPVY2LJ`b822K_L6u~hF4&@sb5s0;c z*(|75tKjjE{~(4#IMq8qw*O;L8;6d*1%VUHlqUi@xPv1Mbr>my;`+28sfYEtqyuRUMV zu5I!!K7HxOE4P~OJB1eBIsNUQmH8W2JI8N7Tg?R6&tAS>Z4WnYrmnYaSokHoZJrpW N6uCEk_Q8?o{su#<)^z{? literal 0 HcmV?d00001 diff --git a/assets/img/horde.png b/assets/img/horde.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c4fbea00e2fc4026d3422e0c59d361d95c73c1 GIT binary patch literal 3045 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 literal 0 HcmV?d00001 diff --git a/proto/ui.proto b/proto/ui.proto index 343ba90593..3ebab6e9ee 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -104,12 +104,31 @@ enum DungeonDifficulty { DifficultyRaid25H = 6; } +enum RepLevel { + RepLevelUnknown = 0; + RepLevelHated = 1; + RepLevelHostile = 2; + RepLevelUnfriendly = 3; + RepLevelNeutral = 4; + RepLevelFriendly = 5; + RepLevelHonored = 6; + RepLevelRevered = 7; + RepLevelExalted = 8; +} + +// TODO: Wotlk Rep Factions +// Use the faction ID for the field index +enum RepFaction { + RepFactionUnknown = 0; +} + message UIItemSource { oneof source { CraftedSource crafted = 1; DropSource drop = 2; QuestSource quest = 3; SoldBySource sold_by = 4; + RepSource rep = 5; } } message CraftedSource { @@ -132,6 +151,11 @@ message SoldBySource { string npc_name = 2; int32 zone_id = 3; } +message RepSource { + RepFaction rep_faction_id = 1; + RepLevel rep_level = 2; + Faction faction_id = 3; +} message UIEnchant { // All enchants have an effect ID. Some also have an item ID, others have a spell ID, @@ -142,7 +166,7 @@ message UIEnchant { int32 item_id = 2; // ID of the enchant "item". Might be 0 if not available. int32 spell_id = 3; // ID of the enchant "spell". Might be 0 if not available. - string name = 4; + string name = 4; string icon = 5; ItemType type = 6; // Which type of item this enchant can be applied to. diff --git a/ui/core/components/gear_picker.tsx b/ui/core/components/gear_picker.tsx index 7674bae0f4..f3f245c360 100644 --- a/ui/core/components/gear_picker.tsx +++ b/ui/core/components/gear_picker.tsx @@ -6,12 +6,12 @@ import { setItemQualityCssClass } from '../css_utils'; import { IndividualSimUI } from '../individual_sim_ui.js'; import { Player } from '../player'; import { Class, GemColor, ItemQuality, ItemSlot, ItemSpec, ItemType } from '../proto/common'; -import { DatabaseFilters, UIEnchant as Enchant, UIGem as Gem, UIItem as Item } from '../proto/ui.js'; +import { DatabaseFilters, RepFaction, UIEnchant as Enchant, UIGem as Gem, UIItem as Item, UIItem_FactionRestriction } from '../proto/ui.js'; import { ActionId } from '../proto_utils/action_id'; import { getEnchantDescription, getUniqueEnchantString } from '../proto_utils/enchants'; import { EquippedItem } from '../proto_utils/equipped_item'; import { gemMatchesSocket, getEmptyGemSocketIconUrl } from '../proto_utils/gems'; -import { difficultyNames, professionNames, slotNames } from '../proto_utils/names.js'; +import { difficultyNames, professionNames, REP_FACTION_NAMES, REP_LEVEL_NAMES, slotNames } from '../proto_utils/names.js'; import { Stats } from '../proto_utils/stats'; import { Sim } from '../sim.js'; import { SimUI } from '../sim_ui'; @@ -694,7 +694,7 @@ export class SelectorModal extends BaseModal { ilist.dispose(); }); - tabAnchor.value!.addEventListener('shown.bs.tab', event => { + tabAnchor.value!.addEventListener('shown.bs.tab', _event => { ilist.sizeRefresh(); }); @@ -840,14 +840,8 @@ export class ItemList { title: EP_TOOLTIP, }); - const show1hWeaponsSelector = makeShow1hWeaponsSelector( - this.tabContent.getElementsByClassName('selector-modal-show-1h-weapons')[0] as HTMLElement, - player.sim, - ); - const show2hWeaponsSelector = makeShow2hWeaponsSelector( - this.tabContent.getElementsByClassName('selector-modal-show-2h-weapons')[0] as HTMLElement, - player.sim, - ); + makeShow1hWeaponsSelector(this.tabContent.getElementsByClassName('selector-modal-show-1h-weapons')[0] as HTMLElement, player.sim); + makeShow2hWeaponsSelector(this.tabContent.getElementsByClassName('selector-modal-show-2h-weapons')[0] as HTMLElement, player.sim); if (!(label == 'Items' && (slot == ItemSlot.ItemSlotMainHand || (slot == ItemSlot.ItemSlotOffHand && player.getClass() == Class.ClassWarrior)))) { (this.tabContent.getElementsByClassName('selector-modal-show-1h-weapons')[0] as HTMLElement).style.display = 'none'; (this.tabContent.getElementsByClassName('selector-modal-show-2h-weapons')[0] as HTMLElement).style.display = 'none'; @@ -855,15 +849,12 @@ export class ItemList { makeShowEPValuesSelector(this.tabContent.getElementsByClassName('selector-modal-show-ep-values')[0] as HTMLElement, player.sim); - const showMatchingGemsSelector = makeShowMatchingGemsSelector( - this.tabContent.getElementsByClassName('selector-modal-show-matching-gems')[0] as HTMLElement, - player.sim, - ); + makeShowMatchingGemsSelector(this.tabContent.getElementsByClassName('selector-modal-show-matching-gems')[0] as HTMLElement, player.sim); if (!label.startsWith('Gem')) { (this.tabContent.getElementsByClassName('selector-modal-show-matching-gems')[0] as HTMLElement).style.display = 'none'; } - const phaseSelector = makePhaseSelector(this.tabContent.getElementsByClassName('selector-modal-phase-selector')[0] as HTMLElement, player.sim); + makePhaseSelector(this.tabContent.getElementsByClassName('selector-modal-phase-selector')[0] as HTMLElement, player.sim); if (label == 'Items') { const filtersButton = this.tabContent.getElementsByClassName('selector-modal-filters-button')[0] as HTMLElement; @@ -907,7 +898,7 @@ export class ItemList { ); const removeButton = this.tabContent.getElementsByClassName('selector-modal-remove-button')[0] as HTMLButtonElement; - removeButton.addEventListener('click', event => { + removeButton.addEventListener('click', _event => { onRemove(TypedEvent.nextEventID()); }); @@ -928,7 +919,7 @@ export class ItemList { player.sim.showExperimentalChangeEmitter.on(() => { simAllButton.hidden = !player.sim.getShowExperimental(); }); - simAllButton.addEventListener('click', event => { + simAllButton.addEventListener('click', _event => { if (simUI instanceof IndividualSimUI) { const itemSpecs = Array(); const isRangedOrTrinket = @@ -1222,22 +1213,26 @@ export class ItemList { } private getSourceInfo(item: Item, sim: Sim): JSX.Element { - if (!item.sources || item.sources.length == 0) { - return <>; - } - - const makeAnchor = (href: string, inner: string) => { + const makeAnchor = (href: string, inner: string | JSX.Element) => { return ( - + {inner} ); }; - const source = item.sources[0]; + if (!item.sources || item.sources.length == 0) { + return <>; + } + + let source = item.sources[0]; if (source.source.oneofKind == 'crafted') { const src = source.source.crafted; - return makeAnchor(ActionId.makeSpellUrl(src.spellId), professionNames.get(src.profession) ?? 'Unknown'); + + if (src.spellId) { + return makeAnchor(ActionId.makeSpellUrl(src.spellId), professionNames.get(src.profession) ?? 'Unknown'); + } + return makeAnchor(ActionId.makeItemUrl(item.id), professionNames.get(src.profession) ?? 'Unknown'); } else if (source.source.oneofKind == 'drop') { const src = source.source.drop; const zone = sim.db.getZone(src.zoneId); @@ -1246,30 +1241,78 @@ export class ItemList { throw new Error('No zone found for item: ' + item); } - const rtnEl = makeAnchor(ActionId.makeZoneUrl(zone.id), `${zone.name} (${difficultyNames.get(src.difficulty) ?? 'Unknown'})`); - const category = src.category ? ` - ${src.category}` : ''; if (npc) { - rtnEl.appendChild(document.createElement('br')); - rtnEl.appendChild(makeAnchor(ActionId.makeNpcUrl(npc.id), `${npc.name + category}`)); + return makeAnchor( + ActionId.makeNpcUrl(npc.id), + + {zone.name} ({difficultyNames.get(src.difficulty) ?? 'Unknown'}) +
+ {npc.name + category} +
, + ); } else if (src.otherName) { - /*innerHTML += ` -
- ${src.otherName + category} - `;*/ - } else if (category) { - /*innerHTML += ` -
- ${category} - `;*/ + return makeAnchor( + ActionId.makeZoneUrl(zone.id), + + {zone.name} +
+ {src.otherName} +
, + ); } - return rtnEl; - } else if (source.source.oneofKind == 'quest') { + return makeAnchor(ActionId.makeZoneUrl(zone.id), zone.name); + } else if (source.source.oneofKind == 'quest' && source.source.quest.name) { const src = source.source.quest; - return makeAnchor(ActionId.makeQuestUrl(src.id), src.name); + return makeAnchor( + ActionId.makeQuestUrl(src.id), + + Quest + {item.factionRestriction == UIItem_FactionRestriction.ALLIANCE_ONLY && ( + + )} + {item.factionRestriction == UIItem_FactionRestriction.HORDE_ONLY && ( + + )} +
+ {src.name} +
, + ); + } else if ((source = item.sources.find(source => source.source.oneofKind == 'rep') ?? source).source.oneofKind == 'rep') { + const factionNames = item.sources + .filter(source => source.source.oneofKind == 'rep') + .map(source => + source.source.oneofKind == 'rep' ? REP_FACTION_NAMES[source.source.rep.repFactionId] : REP_FACTION_NAMES[RepFaction.RepFactionUnknown], + ); + const src = source.source.rep; + return makeAnchor( + ActionId.makeItemUrl(item.id), + <> + {factionNames.map(name => ( + + {name} + {item.factionRestriction == UIItem_FactionRestriction.ALLIANCE_ONLY && ( + + )} + {item.factionRestriction == UIItem_FactionRestriction.HORDE_ONLY && ( + + )} +
+
+ ))} + {REP_LEVEL_NAMES[src.repLevel]} + , + ); } else if (source.source.oneofKind == 'soldBy') { const src = source.source.soldBy; - return makeAnchor(ActionId.makeNpcUrl(src.npcId), src.npcName); + return makeAnchor( + ActionId.makeNpcUrl(src.npcId), + + Sold by +
+ {src.npcName} +
, + ); } return <>; } diff --git a/ui/core/proto_utils/names.ts b/ui/core/proto_utils/names.ts index 1c1889da5e..f68df8506c 100644 --- a/ui/core/proto_utils/names.ts +++ b/ui/core/proto_utils/names.ts @@ -1,20 +1,6 @@ -import { - ArmorType, - Class, - ItemSlot, - Profession, - PseudoStat, - Race, - RangedWeaponType, - Stat, - WeaponType, -} from '../proto/common.js'; -import { - DungeonDifficulty, - RaidFilterOption, - SourceFilterOption, -} from '../proto/ui.js'; import { ResourceType } from '../proto/api.js'; +import { ArmorType, Class, ItemSlot, Profession, PseudoStat, Race, RangedWeaponType, Stat, WeaponType } from '../proto/common.js'; +import { DungeonDifficulty, RaidFilterOption, RepFaction, RepLevel, SourceFilterOption } from '../proto/ui.js'; export const armorTypeNames: Map = new Map([ [ArmorType.ArmorTypeUnknown, 'Unknown'], @@ -117,7 +103,7 @@ export function nameToProfession(name: string): Profession { const lower = name.toLowerCase(); for (const [key, value] of professionNames) { if (value.toLowerCase() == lower) { - return key + return key; } } return Profession.ProfessionUnknown; @@ -226,8 +212,7 @@ export const pseudoStatNames: Map = new Map([ export function getClassStatName(stat: Stat, playerClass: Class): string { const statName = statNames.get(stat); - if (!statName) - return 'UnknownStat'; + if (!statName) return 'UnknownStat'; if (playerClass == Class.ClassHunter) { return statName.replace('Melee', 'Ranged'); } else { @@ -317,7 +302,7 @@ export const raidNames: Map = new Map([ [RaidFilterOption.RaidVaultOfArchavon, 'Vault of Archavon'], [RaidFilterOption.RaidUlduar, 'Ulduar'], [RaidFilterOption.RaidTrialOfTheCrusader, 'Trial of the Crusader'], - [RaidFilterOption.RaidOnyxiasLair, 'Onyxia\'s Lair'], + [RaidFilterOption.RaidOnyxiasLair, "Onyxia's Lair"], [RaidFilterOption.RaidIcecrownCitadel, 'Icecrown Citadel'], [RaidFilterOption.RaidRubySanctum, 'Ruby Sanctum'], ]); @@ -333,3 +318,19 @@ export const difficultyNames: Map = new Map([ [DungeonDifficulty.DifficultyRaid25, '25N'], [DungeonDifficulty.DifficultyRaid25H, '25H'], ]); + +export const REP_LEVEL_NAMES: Record = { + [RepLevel.RepLevelUnknown]: 'Unknown', + [RepLevel.RepLevelHated]: 'Hated', + [RepLevel.RepLevelHostile]: 'Hostile', + [RepLevel.RepLevelUnfriendly]: 'Unfriendly', + [RepLevel.RepLevelNeutral]: 'Neutral', + [RepLevel.RepLevelFriendly]: 'Friendly', + [RepLevel.RepLevelHonored]: 'Honored', + [RepLevel.RepLevelRevered]: 'Revered', + [RepLevel.RepLevelExalted]: 'Exalted', +}; + +export const REP_FACTION_NAMES: Record = { + [RepFaction.RepFactionUnknown]: 'Unknown', +};