Skip to content

Commit

Permalink
Add importer cata warning
Browse files Browse the repository at this point in the history
  • Loading branch information
1337LutZ committed May 7, 2024
1 parent 459db05 commit 002de14
Showing 1 changed file with 76 additions and 50 deletions.
126 changes: 76 additions & 50 deletions ui/core/components/importers.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import { JsonObject } from '@protobuf-ts/runtime';

import { IndividualSimUI } from '../individual_sim_ui';
import { SimUI } from '../sim_ui';
import { TypedEvent } from '../typed_event';
import {
Class,
EquipmentSpec,
ItemSlot,
Glyphs,
ItemSpec,
Profession,
Race,
Spec,
} from '../proto/common';
import { Class, EquipmentSpec, Glyphs, ItemSlot, ItemSpec, Profession, Race, Spec } from '../proto/common';
import { IndividualSimSettings } from '../proto/ui';
import { Database } from '../proto_utils/database';
import { classNames, nameToClass, nameToRace, nameToProfession } from '../proto_utils/names';
import { classNames, nameToClass, nameToProfession, nameToRace } from '../proto_utils/names';
import { SimSettingCategories } from '../sim';
import { SimUI } from '../sim_ui';
import { classGlyphsConfig, talentSpellIdsToTalentString } from '../talents/factory';
import { GlyphConfig } from '../talents/glyphs_picker';
import { BaseModal } from './base_modal';
import { TypedEvent } from '../typed_event';
import { buf2hex, getEnumValues } from '../utils';
import { JsonObject } from '@protobuf-ts/runtime';
import { SimSettingCategories } from '../sim';
import { BaseModal } from './base_modal';

declare var pako: any;
declare let pako: any;

export abstract class Importer extends BaseModal {
protected readonly textElem: HTMLTextAreaElement;
Expand All @@ -39,13 +31,16 @@ export abstract class Importer extends BaseModal {
<textarea spellCheck="false" class="importer-textarea form-control"></textarea>
`;
this.footer!.innerHTML = `
${this.includeFile ? `
${
this.includeFile
? `
<label for="${uploadInputId}" class="importer-button btn btn-primary upload-button me-2">
<i class="fas fa-file-arrow-up"></i>
Upload File
</label>
<input type="file" id="${uploadInputId}" class="importer-upload-input d-none" hidden>
` : ''
`
: ''
}
<button class="importer-button btn btn-primary import-button">
<i class="fa fa-download"></i>
Expand All @@ -65,18 +60,27 @@ export abstract class Importer extends BaseModal {
}

this.importButton = this.rootElem.getElementsByClassName('import-button')[0] as HTMLButtonElement;
this.importButton.addEventListener('click', event => {
this.importButton.addEventListener('click', async () => {
try {
this.onImport(this.textElem.value || '');
} catch (error) {
alert('Import error: ' + error);
await this.onImport(this.textElem.value || '');
} catch (error: any) {
alert(`Import error:
${error?.message}`);
}
});
}

abstract onImport(data: string): void

protected async finishIndividualImport<SpecType extends Spec>(simUI: IndividualSimUI<SpecType>, charClass: Class, race: Race, equipmentSpec: EquipmentSpec, talentsStr: string, glyphs: Glyphs | null, professions: Array<Profession>): Promise<void> {
abstract onImport(data: string): Promise<void>;

protected async finishIndividualImport<SpecType extends Spec>(
simUI: IndividualSimUI<SpecType>,
charClass: Class,
race: Race,
equipmentSpec: EquipmentSpec,
talentsStr: string,
glyphs: Glyphs | null,
professions: Array<Profession>,
): Promise<void> {
const playerClass = simUI.player.getClass();
if (charClass != playerClass) {
throw new Error(`Wrong Class! Expected ${classNames.get(playerClass)} but found ${classNames.get(charClass)}!`);
Expand All @@ -103,10 +107,10 @@ export abstract class Importer extends BaseModal {
simUI.player.setTalentsString(eventID, talentsStr);
}
if (glyphs) {
simUI.player.setGlyphs(eventID, glyphs)
simUI.player.setGlyphs(eventID, glyphs);
}
if (professions.length > 0) {
simUI.player.setProfessions(eventID, professions)
simUI.player.setProfessions(eventID, professions);
}
});

Expand All @@ -115,21 +119,22 @@ export abstract class Importer extends BaseModal {
if (missingItems.length == 0 && missingEnchants.length == 0) {
alert('Import successful!');
} else {
alert('Import successful, but the following IDs were not found in the sim database:' +
(missingItems.length == 0 ? '' : '\n\nItems: ' + missingItems.join(', ')) +
(missingEnchants.length == 0 ? '' : '\n\nEnchants: ' + missingEnchants.join(', ')));
alert(
'Import successful, but the following IDs were not found in the sim database:' +
(missingItems.length == 0 ? '' : '\n\nItems: ' + missingItems.join(', ')) +
(missingEnchants.length == 0 ? '' : '\n\nEnchants: ' + missingEnchants.join(', ')),
);
}
}
}

interface UrlParseData {
settings: IndividualSimSettings,
categories: Array<SimSettingCategories>,
settings: IndividualSimSettings;
categories: Array<SimSettingCategories>;
}

// For now this just holds static helpers to match the exporter, so it doesn't extend Importer.
export class IndividualLinkImporter {

// Exclude UISettings by default, since most users don't intend to export those.
static readonly DEFAULT_CATEGORIES = getEnumValues(SimSettingCategories).filter(c => c != SimSettingCategories.UISettings) as Array<SimSettingCategories>;

Expand All @@ -148,7 +153,7 @@ export class IndividualLinkImporter {
return map;
})();

static tryParseUrlLocation(location: Location): UrlParseData|null {
static tryParseUrlLocation(location: Location): UrlParseData | null {
let hash = location.hash;
if (hash.length <= 1) {
return null;
Expand All @@ -170,8 +175,7 @@ export class IndividualLinkImporter {
if (urlParams.has(IndividualLinkImporter.CATEGORY_PARAM)) {
const categoryChars = urlParams.get(IndividualLinkImporter.CATEGORY_PARAM)!.split('');
exportCategories = categoryChars
.map(char => [...IndividualLinkImporter.CATEGORY_KEYS.entries()]
.find(e => e[1] == char))
.map(char => [...IndividualLinkImporter.CATEGORY_KEYS.entries()].find(e => e[1] == char))
.filter(e => e)
.map(e => e![0]);
}
Expand Down Expand Up @@ -230,10 +234,18 @@ export class Individual80UImporter<SpecType extends Spec> extends Importer {
`;
}

onImport(data: string) {
async onImport(data: string) {
const importJson = JSON.parse(data);

// Parse all the settings.
const charLevel = importJson?.character?.level;
const hasReforge = importJson?.items?.some((item: any) => !!item?.reforge);
const hasMastery = importJson?.stats?.masteryRating > 0;

if ((charLevel && charLevel > 80) || hasReforge || hasMastery) {
throwCataError();
}

const charClass = nameToClass((importJson?.character?.gameClass as string) || '');
if (charClass == Class.ClassUnknown) {
throw new Error('Could not parse Class!');
Expand All @@ -250,9 +262,9 @@ export class Individual80UImporter<SpecType extends Spec> extends Importer {
talentsStr = talentSpellIdsToTalentString(charClass, talentIds);
}

let equipmentSpec = EquipmentSpec.create();
const equipmentSpec = EquipmentSpec.create();
(importJson.items as Array<any>).forEach(itemJson => {
let itemSpec = ItemSpec.create();
const itemSpec = ItemSpec.create();
itemSpec.id = itemJson.id;
if (itemJson.enchant?.id) {
itemSpec.enchant = itemJson.enchant.id;
Expand Down Expand Up @@ -288,10 +300,15 @@ export class IndividualWowheadGearPlannerImporter<SpecType extends Spec> extends
`;
}

onImport(url: string) {
async onImport(url: string) {
const isCataUrl = url.includes('wowhead.com/cata');
if (isCataUrl) {
throwCataError();
}

const match = url.match(/www\.wowhead\.com\/wotlk\/gear-planner\/([a-z\-]+)\/([a-z\-]+)\/([a-zA-Z0-9_\-]+)/);
if (!match) {
throw new Error(`Invalid WCL URL ${url}, must look like "https://www.wowhead.com/wotlk/gear-planner/CLASS/RACE/XXXX"`);
throw new Error(`Invalid WowHead URL ${url}, must look like "https://www.wowhead.com/wotlk/gear-planner/CLASS/RACE/XXXX"`);
}

// Parse all the settings.
Expand All @@ -307,7 +324,7 @@ export class IndividualWowheadGearPlannerImporter<SpecType extends Spec> extends

const base64Data = match[3].replaceAll('_', '/').replaceAll('-', '+');
//console.log('Base64: ' + base64Data);
const data = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))
const data = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
//console.log('Hex: ' + buf2hex(data));

// Binary schema
Expand Down Expand Up @@ -337,19 +354,19 @@ export class IndividualWowheadGearPlannerImporter<SpecType extends Spec> extends
// First byte in glyphs section seems to always be 0x30
cur = 1;
let hasGlyphs = false;
const d = "0123456789abcdefghjkmnpqrstvwxyz";
const d = '0123456789abcdefghjkmnpqrstvwxyz';
const glyphStr = String.fromCharCode(...glyphBytes);
const glyphIds = [0, 0, 0, 0, 0, 0];
while (cur < glyphBytes.length) {

// First byte for each glyph is 0x3z, where z is the glyph position.
// 0, 1, 2 are major glyphs, 3, 4, 5 are minor glyphs.
const glyphPosition = d.indexOf(glyphStr[cur]);
cur++;

// For some reason, wowhead uses the spell IDs for the glyphs and
// applies a ridiculous hashing scheme.
const spellId = 0 +
const spellId =
0 +
(d.indexOf(glyphStr[cur + 0]) << 15) +
(d.indexOf(glyphStr[cur + 1]) << 10) +
(d.indexOf(glyphStr[cur + 2]) << 5) +
Expand Down Expand Up @@ -390,7 +407,7 @@ export class IndividualWowheadGearPlannerImporter<SpecType extends Spec> extends
cur++;

const numGems = (gearBytes[cur] & 0b11100000) >> 5;
const highid = (gearBytes[cur] & 0b00011111);
const highid = gearBytes[cur] & 0b00011111;
cur++;

itemSpec.id = (highid << 16) + (gearBytes[cur] << 8) + gearBytes[cur + 1];
Expand All @@ -407,7 +424,7 @@ export class IndividualWowheadGearPlannerImporter<SpecType extends Spec> extends

for (let gemIdx = 0; gemIdx < numGems; gemIdx++) {
const gemPosition = (gearBytes[cur] & 0b11100000) >> 5;
const highgemid = (gearBytes[cur] & 0b00011111);
const highgemid = gearBytes[cur] & 0b00011111;
cur++;

const gemId = (highgemid << 16) + (gearBytes[cur] << 8) + gearBytes[cur + 1];
Expand Down Expand Up @@ -488,13 +505,17 @@ export class IndividualAddonImporter<SpecType extends Spec> extends Importer {
throw new Error('Could not parse Race!');
}

const professions = (importJson['professions'] as Array<{ name: string, level: number }>).map(profData => nameToProfession(profData.name));
const professions = (importJson['professions'] as Array<{ name: string; level: number }>).map(profData => nameToProfession(profData.name));
professions.forEach((prof, i) => {
if (prof == Profession.ProfessionUnknown) {
throw new Error(`Could not parse profession '${importJson['professions'][i]}'`);
}
});

if (importJson['glyphs']['prime']) {
throwCataError();
}

const talentsStr = (importJson['talents'] as string) || '';
const glyphsConfig = classGlyphsConfig[charClass];

Expand Down Expand Up @@ -524,12 +545,17 @@ export class IndividualAddonImporter<SpecType extends Spec> extends Importer {
}
}

const throwCataError = () => {
throw new Error(`WowSims does not support the Cata Pre-patch.
Please use: https://wowsims.github.io/cata/ instead`);
};

function glyphNameToID(glyphName: string, glyphsConfig: Record<number, GlyphConfig>): number {
if (!glyphName) {
return 0;
}

for (let glyphIDStr in glyphsConfig) {
for (const glyphIDStr in glyphsConfig) {
if (glyphsConfig[glyphIDStr].name == glyphName) {
return parseInt(glyphIDStr);
}
Expand Down

0 comments on commit 002de14

Please sign in to comment.