diff --git a/.gitignore b/.gitignore index 22e0fd7..67e37ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ target package-lock.json dist diff --git a/assets/main/loot-indicator/loot-indicator-generic.dds b/assets/main/loot-indicator/loot-indicator-generic.dds new file mode 100644 index 0000000..7425772 Binary files /dev/null and b/assets/main/loot-indicator/loot-indicator-generic.dds differ diff --git a/assets/main/loot-indicator/loot-indicator-generic.mdx b/assets/main/loot-indicator/loot-indicator-generic.mdx new file mode 100644 index 0000000..b4476c1 Binary files /dev/null and b/assets/main/loot-indicator/loot-indicator-generic.mdx differ diff --git a/assets/main/loot-indicator/loot-indicator-tome.dds b/assets/main/loot-indicator/loot-indicator-tome.dds new file mode 100644 index 0000000..5d56be6 Binary files /dev/null and b/assets/main/loot-indicator/loot-indicator-tome.dds differ diff --git a/assets/main/loot-indicator/loot-indicator-tome.mdx b/assets/main/loot-indicator/loot-indicator-tome.mdx new file mode 100644 index 0000000..e5b9c45 Binary files /dev/null and b/assets/main/loot-indicator/loot-indicator-tome.mdx differ diff --git a/assets/main/loot-indicator/ui/loot-table-ui.fdf b/assets/main/loot-indicator/ui/loot-table-ui.fdf new file mode 100644 index 0000000..71df826 --- /dev/null +++ b/assets/main/loot-indicator/ui/loot-table-ui.fdf @@ -0,0 +1,52 @@ + +Frame "SIMPLEBUTTON" "LI_ItemButton" { + UseActiveContext, + Width 0.039, + Height 0.039, + + Texture "LI_ItemButton_Backdrop" { + } +} + +Frame "SIMPLEFRAME" "LI_Tooltip" { + UseActiveContext, + + Frame "BACKDROP" "LI_Tooltip_Box" { + UseActiveContext, + DecorateFileNames, + BackdropTileBackground, + BackdropBackground "ToolTipBackground", + BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R", + BackdropCornerSize 0.01, + BackdropBackgroundSize 0.01, + BackdropBackgroundInsets 0.002 0.002 0.002 0.002, + BackdropEdgeFile "ToolTipBorder", + BackdropBlendAll, + + Frame "TEXT" "LI_Tooltip_Title" { + UseActiveContext, + DecorateFileNames, + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + LayerStyle "IGNORETRACKEVENTS", + FrameFont "InfoPanelTextFont", 0.011, "", + FontJustificationH JUSTIFYLEFT, + } + + Frame "BACKDROP" "LI_Tooltip_Separator" { + UseActiveContext, + BackdropBackground "replaceabletextures\teamcolor\teamcolor08", + } + + Frame "TEXT" "LI_Tooltip_Description" { + UseActiveContext, + FrameFont "InfoPanelTextFont", 0.011, "", + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + LayerStyle "IGNORETRACKEVENTS", + DecorateFileNames, + } + } +} \ No newline at end of file diff --git a/assets/main/loot-indicator/ui/ui.toc b/assets/main/loot-indicator/ui/ui.toc new file mode 100644 index 0000000..afda006 --- /dev/null +++ b/assets/main/loot-indicator/ui/ui.toc @@ -0,0 +1,2 @@ +loot-indicator\ui\loot-table-ui.fdf + diff --git a/assets/roc/loot-indicator/loot-indicator-generic.dds b/assets/roc/loot-indicator/loot-indicator-generic.dds new file mode 100644 index 0000000..7425772 Binary files /dev/null and b/assets/roc/loot-indicator/loot-indicator-generic.dds differ diff --git a/assets/roc/loot-indicator/loot-indicator-generic.mdx b/assets/roc/loot-indicator/loot-indicator-generic.mdx new file mode 100644 index 0000000..b4476c1 Binary files /dev/null and b/assets/roc/loot-indicator/loot-indicator-generic.mdx differ diff --git a/assets/roc/loot-indicator/loot-indicator-tome.dds b/assets/roc/loot-indicator/loot-indicator-tome.dds new file mode 100644 index 0000000..5d56be6 Binary files /dev/null and b/assets/roc/loot-indicator/loot-indicator-tome.dds differ diff --git a/assets/roc/loot-indicator/loot-indicator-tome.mdx b/assets/roc/loot-indicator/loot-indicator-tome.mdx new file mode 100644 index 0000000..e5b9c45 Binary files /dev/null and b/assets/roc/loot-indicator/loot-indicator-tome.mdx differ diff --git a/assets/roc/loot-indicator/ui/loot-table-ui.fdf b/assets/roc/loot-indicator/ui/loot-table-ui.fdf new file mode 100644 index 0000000..71df826 --- /dev/null +++ b/assets/roc/loot-indicator/ui/loot-table-ui.fdf @@ -0,0 +1,52 @@ + +Frame "SIMPLEBUTTON" "LI_ItemButton" { + UseActiveContext, + Width 0.039, + Height 0.039, + + Texture "LI_ItemButton_Backdrop" { + } +} + +Frame "SIMPLEFRAME" "LI_Tooltip" { + UseActiveContext, + + Frame "BACKDROP" "LI_Tooltip_Box" { + UseActiveContext, + DecorateFileNames, + BackdropTileBackground, + BackdropBackground "ToolTipBackground", + BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R", + BackdropCornerSize 0.01, + BackdropBackgroundSize 0.01, + BackdropBackgroundInsets 0.002 0.002 0.002 0.002, + BackdropEdgeFile "ToolTipBorder", + BackdropBlendAll, + + Frame "TEXT" "LI_Tooltip_Title" { + UseActiveContext, + DecorateFileNames, + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + LayerStyle "IGNORETRACKEVENTS", + FrameFont "InfoPanelTextFont", 0.011, "", + FontJustificationH JUSTIFYLEFT, + } + + Frame "BACKDROP" "LI_Tooltip_Separator" { + UseActiveContext, + BackdropBackground "replaceabletextures\teamcolor\teamcolor08", + } + + Frame "TEXT" "LI_Tooltip_Description" { + UseActiveContext, + FrameFont "InfoPanelTextFont", 0.011, "", + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + LayerStyle "IGNORETRACKEVENTS", + DecorateFileNames, + } + } +} \ No newline at end of file diff --git a/assets/roc/loot-indicator/ui/ui.toc b/assets/roc/loot-indicator/ui/ui.toc new file mode 100644 index 0000000..afda006 --- /dev/null +++ b/assets/roc/loot-indicator/ui/ui.toc @@ -0,0 +1,2 @@ +loot-indicator\ui\loot-table-ui.fdf + diff --git a/package.json b/package.json index 7fc3c0d..db5c513 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,13 @@ "typescript": "5.8.2", "typescript-to-lua": "^1.31.0", "war3-objectdata-th": "^0.2.10", - "war3-transformer": "3.0.9", + "war3-transformer": "3.0.10", "war3tstlhelper": "1.0.1", - "winston": "3.17.0" + "winston": "3.17.0", + "war3-types-strict": "0.1.3", + "mdx-m3-viewer-th": "5.13.3", + "@jamiephan/casclib": "2.0.0-dev.5", + "war3-model": "4.0.0" }, "watch": { "build:defs": { diff --git a/scripts/loot-indicator/items-db/extract-items-data.ts b/scripts/loot-indicator/items-db/extract-items-data.ts new file mode 100644 index 0000000..907916b --- /dev/null +++ b/scripts/loot-indicator/items-db/extract-items-data.ts @@ -0,0 +1,52 @@ +import * as casclib from '@jamiephan/casclib' +import SlkFile from "mdx-m3-viewer-th/dist/cjs/parsers/slk/file" +import * as fs from 'fs-extra'; +import {toArrayBuffer} from "../../utils"; + +const WC3_GAME_BASE_DIRECTORY = "D:\\Games\\Warcraft III"; + +interface ItemData { + //Comment is roughly a name, used it here just to give a rough view of what the item actually is. + //The actual in-game name is probably in _locales/enus.w3mod/itemstrings.txt + comment: string; + includeAsRandomChoice: boolean; +} + +generateItemsData("extracted-items-data.json") + +function generateItemsData(resultFilePath: string) { + const storageHandle = casclib.openStorageSync(WC3_GAME_BASE_DIRECTORY) + try { + const unitDataBuffer = casclib.readFileSync(storageHandle, "war3.w3mod:units\\itemdata.slk") + const itemDataSlk = new SlkFile(); + itemDataSlk.load(unitDataBuffer.toString("utf8")); + + let itemDataById = new Map() + + //Header row + const headerRow = itemDataSlk.rows[0]; + const columnName2Idx = headerRow.reduce((map, name, idx) => + map.set(name, idx), new Map()) + const valueRows = itemDataSlk.rows.slice(1); + + for (const row of valueRows) { + const id = row[columnName2Idx.get("itemID")!]; + const comment = row[columnName2Idx.get("comment")!]; + const includeAsRandomChoice = row[columnName2Idx.get("pickRandom")!] === "1"; + + itemDataById.set(id, {comment, includeAsRandomChoice}); + } + + fs.writeFileSync(resultFilePath, JSON.stringify(Object.fromEntries(itemDataById), null, 2)) + console.log("Total items extracted: " + itemDataById.size) + } finally { + casclib.closeStorage(storageHandle); + } +} + +function readFileFromCasc(storageHandle: any, filePath: string) { + // Sometimes `readFileSync()` returns buffer bigger then the file size, so we slice it + const buffer = casclib.readFileSync(storageHandle, filePath); + const size = casclib.findFilesSync(storageHandle, filePath)[0].fileSize; + return toArrayBuffer(buffer.subarray(0, size)); +} \ No newline at end of file diff --git a/scripts/loot-indicator/items-db/extracted-items-data.json b/scripts/loot-indicator/items-db/extracted-items-data.json new file mode 100644 index 0000000..460ff56 --- /dev/null +++ b/scripts/loot-indicator/items-db/extracted-items-data.json @@ -0,0 +1,1134 @@ +{ + "ckng": { + "comment": "Crown of Kings +5", + "includeAsRandomChoice": true + }, + "modt": { + "comment": "mask of death", + "includeAsRandomChoice": true + }, + "tkno": { + "comment": "Tome of Power", + "includeAsRandomChoice": true + }, + "ratf": { + "comment": "Claws of Attack +15", + "includeAsRandomChoice": true + }, + "ofro": { + "comment": "Orb of Frost", + "includeAsRandomChoice": true + }, + "infs": { + "comment": "Inferno Stone", + "includeAsRandomChoice": true + }, + "desc": { + "comment": "Dagger of Escape", + "includeAsRandomChoice": true + }, + "fgdg": { + "comment": "Demonic Figurine", + "includeAsRandomChoice": true + }, + "engr": { + "comment": "Engraved Scale", + "includeAsRandomChoice": true + }, + "shar": { + "comment": "Ice Shard", + "includeAsRandomChoice": true + }, + "ccmd": { + "comment": "Scepter of Mastery", + "includeAsRandomChoice": true + }, + "wild": { + "comment": "Amulet of the Wild", + "includeAsRandomChoice": true + }, + "scav": { + "comment": "Scepter of Avarice", + "includeAsRandomChoice": true + }, + "odef": { + "comment": "Orb of Darkness", + "includeAsRandomChoice": true + }, + "rde4": { + "comment": "Ring of Protection +5", + "includeAsRandomChoice": true + }, + "pmna": { + "comment": "Pendant of Mana", + "includeAsRandomChoice": true + }, + "rhth": { + "comment": "Khadgar's Gem of Health", + "includeAsRandomChoice": true + }, + "ssil": { + "comment": "Staff of Silence", + "includeAsRandomChoice": true + }, + "spsh": { + "comment": "Amulet of Spell Shield", + "includeAsRandomChoice": true + }, + "sres": { + "comment": "Scroll of Restoration", + "includeAsRandomChoice": true + }, + "pdi2": { + "comment": "Potion of Divinity (Invulnerability)", + "includeAsRandomChoice": true + }, + "pres": { + "comment": "Potion of Restoration", + "includeAsRandomChoice": true + }, + "iotw": { + "comment": "Idol of the wild", + "includeAsRandomChoice": true + }, + "fgfh": { + "comment": "Spiked Collar", + "includeAsRandomChoice": true + }, + "fgbd": { + "comment": "Blue Drake Egg", + "includeAsRandomChoice": true + }, + "fgrg": { + "comment": "Stone Token", + "includeAsRandomChoice": true + }, + "hcun": { + "comment": "Hood of Cunning", + "includeAsRandomChoice": true + }, + "hval": { + "comment": "Helm of Valor", + "includeAsRandomChoice": true + }, + "mcou": { + "comment": "Medallion of Courage", + "includeAsRandomChoice": true + }, + "ajen": { + "comment": "Ancient Janggo of Endurance", + "includeAsRandomChoice": true + }, + "clfm": { + "comment": "Cloak of Flames", + "includeAsRandomChoice": true + }, + "ratc": { + "comment": "Claws of Attack +12", + "includeAsRandomChoice": true + }, + "war2": { + "comment": "Warsong Battle Drums (Kodo)", + "includeAsRandomChoice": true + }, + "kpin": { + "comment": "Khadgar's Pipe of Insight", + "includeAsRandomChoice": true + }, + "lgdh": { + "comment": "Legion Doom-Horn", + "includeAsRandomChoice": true + }, + "ankh": { + "comment": "Ankh of Reincarnation", + "includeAsRandomChoice": true + }, + "whwd": { + "comment": "Healing Wards", + "includeAsRandomChoice": true + }, + "fgsk": { + "comment": "Book of the Dead", + "includeAsRandomChoice": true + }, + "wcyc": { + "comment": "Wand of the Wind", + "includeAsRandomChoice": true + }, + "hlst": { + "comment": "Health Stone", + "includeAsRandomChoice": true + }, + "mnst": { + "comment": "Mana Stone", + "includeAsRandomChoice": true + }, + "belv": { + "comment": "Boots of Quel'Thalas +6", + "includeAsRandomChoice": true + }, + "bgst": { + "comment": "Belt of Giant Strength +6", + "includeAsRandomChoice": true + }, + "ciri": { + "comment": "Robe of the Magi +6", + "includeAsRandomChoice": true + }, + "lhst": { + "comment": "Lion Horn of Stormwind", + "includeAsRandomChoice": true + }, + "afac": { + "comment": "Alleria's Flute of Accuracy", + "includeAsRandomChoice": true + }, + "sbch": { + "comment": "Scourge Bone Chimes", + "includeAsRandomChoice": true + }, + "brac": { + "comment": "Runed Bracers", + "includeAsRandomChoice": true + }, + "rwiz": { + "comment": "Sobi Mask", + "includeAsRandomChoice": true + }, + "pghe": { + "comment": "potion of greater healing", + "includeAsRandomChoice": true + }, + "pgma": { + "comment": "Potion of Greater Mana", + "includeAsRandomChoice": true + }, + "pnvu": { + "comment": "potion of invulnerability", + "includeAsRandomChoice": true + }, + "sror": { + "comment": "Scroll of the Beast", + "includeAsRandomChoice": true + }, + "woms": { + "comment": "Wand of Mana Stealing", + "includeAsRandomChoice": true + }, + "crys": { + "comment": "Crystal Ball", + "includeAsRandomChoice": true + }, + "evtl": { + "comment": "Talisman of Evasion", + "includeAsRandomChoice": true + }, + "penr": { + "comment": "Pendant of Energy", + "includeAsRandomChoice": true + }, + "prvt": { + "comment": "Periapt of Vitality", + "includeAsRandomChoice": true + }, + "rat9": { + "comment": "Claws of Attack +9", + "includeAsRandomChoice": true + }, + "rde3": { + "comment": "Ring of Protection +4", + "includeAsRandomChoice": false + }, + "rlif": { + "comment": "Ring of regeneration", + "includeAsRandomChoice": true + }, + "bspd": { + "comment": "Boots of Speed", + "includeAsRandomChoice": false + }, + "rej3": { + "comment": "Replenishment Potion", + "includeAsRandomChoice": true + }, + "will": { + "comment": "Wand of Illusion", + "includeAsRandomChoice": true + }, + "wlsd": { + "comment": "Wand of Lightning Shield", + "includeAsRandomChoice": false + }, + "wswd": { + "comment": "Sentry Wards", + "includeAsRandomChoice": true + }, + "cnob": { + "comment": "Circlet of Nobility", + "includeAsRandomChoice": true + }, + "gcel": { + "comment": "Gloves of Haste", + "includeAsRandomChoice": true + }, + "rat6": { + "comment": "Claws of Attack +6", + "includeAsRandomChoice": true + }, + "rde2": { + "comment": "Ring of Protection +3", + "includeAsRandomChoice": true + }, + "tdx2": { + "comment": "Tome of Agility +2", + "includeAsRandomChoice": true + }, + "tin2": { + "comment": "Tome of Intelligence +2", + "includeAsRandomChoice": true + }, + "tpow": { + "comment": "Tome of Knowledge", + "includeAsRandomChoice": true + }, + "tst2": { + "comment": "Tome of Strength +2", + "includeAsRandomChoice": true + }, + "pnvl": { + "comment": "Potion of Lesser Invulnerability", + "includeAsRandomChoice": true + }, + "clsd": { + "comment": "Cloak of Shadows", + "includeAsRandomChoice": true + }, + "rag1": { + "comment": "Slippers of Agility +3", + "includeAsRandomChoice": true + }, + "rin1": { + "comment": "Mantle of Intelligence +3", + "includeAsRandomChoice": true + }, + "rst1": { + "comment": "Gauntlets of Ogre Strength +3", + "includeAsRandomChoice": true + }, + "manh": { + "comment": "Manual of Health", + "includeAsRandomChoice": true + }, + "tdex": { + "comment": "Tome of Agility +1", + "includeAsRandomChoice": true + }, + "tint": { + "comment": "Tome of Intelligence", + "includeAsRandomChoice": true + }, + "tstr": { + "comment": "Tome of Strength +1", + "includeAsRandomChoice": true + }, + "pomn": { + "comment": "Potion of Omniscience", + "includeAsRandomChoice": true + }, + "wshs": { + "comment": "Wand of Shadowsight", + "includeAsRandomChoice": true + }, + "rej6": { + "comment": "Greater Scroll of Replenishment", + "includeAsRandomChoice": false + }, + "rej5": { + "comment": "Lesser Scroll of Replenishment", + "includeAsRandomChoice": false + }, + "rej4": { + "comment": "Greater Replenishment Potion", + "includeAsRandomChoice": false + }, + "ram4": { + "comment": "Fourth Ring of the Archmagi", + "includeAsRandomChoice": false + }, + "dsum": { + "comment": "Diamond of Summoning", + "includeAsRandomChoice": false + }, + "ofir": { + "comment": "Orb of Fire", + "includeAsRandomChoice": false + }, + "ocor": { + "comment": "Orb of Corruption", + "includeAsRandomChoice": false + }, + "oli2": { + "comment": "Orb of Lightning", + "includeAsRandomChoice": false + }, + "oven": { + "comment": "Orb of Venom", + "includeAsRandomChoice": false + }, + "ram3": { + "comment": "Third Ring of the Archmagi", + "includeAsRandomChoice": true + }, + "tret": { + "comment": "Tome of Retraining", + "includeAsRandomChoice": false + }, + "tgrh": { + "comment": "Tiny Great Hall", + "includeAsRandomChoice": false + }, + "rej2": { + "comment": "Lesser Replenishment Potion", + "includeAsRandomChoice": false + }, + "gemt": { + "comment": "Gem of True Seeing", + "includeAsRandomChoice": false + }, + "ram2": { + "comment": "Second Ring of the Archmagi", + "includeAsRandomChoice": false + }, + "stel": { + "comment": "Staff of Teleportation", + "includeAsRandomChoice": false + }, + "stwp": { + "comment": "Scroll of Town Portal", + "includeAsRandomChoice": false + }, + "wneg": { + "comment": "Wand of Negation", + "includeAsRandomChoice": false + }, + "sneg": { + "comment": "Staff of Negation", + "includeAsRandomChoice": false + }, + "wneu": { + "comment": "Wand of Neutralization", + "includeAsRandomChoice": false + }, + "shea": { + "comment": "Scroll of Healing", + "includeAsRandomChoice": false + }, + "sman": { + "comment": "Scroll of Mana", + "includeAsRandomChoice": false + }, + "rej1": { + "comment": "Minor Replenishment Potion", + "includeAsRandomChoice": false + }, + "pspd": { + "comment": "Potion of Speed", + "includeAsRandomChoice": false + }, + "dust": { + "comment": "Dust of Appearance", + "includeAsRandomChoice": false + }, + "ram1": { + "comment": "First Ring of the Archmagi", + "includeAsRandomChoice": false + }, + "pinv": { + "comment": "potion of invisibility", + "includeAsRandomChoice": false + }, + "phea": { + "comment": "potion of healing", + "includeAsRandomChoice": false + }, + "pman": { + "comment": "Potion of Mana", + "includeAsRandomChoice": false + }, + "spro": { + "comment": "Scroll of Protection", + "includeAsRandomChoice": false + }, + "hslv": { + "comment": "Healing Salve", + "includeAsRandomChoice": false + }, + "moon": { + "comment": "Moonstone", + "includeAsRandomChoice": false + }, + "shas": { + "comment": "Scroll of Speed", + "includeAsRandomChoice": false + }, + "skul": { + "comment": "Sacrificial Skull", + "includeAsRandomChoice": false + }, + "mcri": { + "comment": "Mechanical Critter", + "includeAsRandomChoice": false + }, + "rnec": { + "comment": "Rod of Necromancy", + "includeAsRandomChoice": false + }, + "ritd": { + "comment": "Ritual Dagger", + "includeAsRandomChoice": false + }, + "tsct": { + "comment": "Ivory Tower", + "includeAsRandomChoice": false + }, + "azhr": { + "comment": "Heart of Aszune", + "includeAsRandomChoice": false + }, + "bzbe": { + "comment": "Empty Vial", + "includeAsRandomChoice": false + }, + "bzbf": { + "comment": "Full Vial", + "includeAsRandomChoice": false + }, + "ches": { + "comment": "Cheese", + "includeAsRandomChoice": false + }, + "cnhn": { + "comment": "Horn of Cenarius", + "includeAsRandomChoice": false + }, + "glsk": { + "comment": "Guldan's Skull", + "includeAsRandomChoice": false + }, + "gopr": { + "comment": "Glyph of Purification", + "includeAsRandomChoice": false + }, + "k3m1": { + "comment": "Key of 3 Moons - 1", + "includeAsRandomChoice": false + }, + "k3m2": { + "comment": "Key of 3 Moons - 2", + "includeAsRandomChoice": false + }, + "k3m3": { + "comment": "Key of 3 Moons - 3", + "includeAsRandomChoice": false + }, + "ktrm": { + "comment": "Urn of Kel'Thuzad", + "includeAsRandomChoice": false + }, + "kybl": { + "comment": "bloody key", + "includeAsRandomChoice": false + }, + "kygh": { + "comment": "ghost key", + "includeAsRandomChoice": false + }, + "kymn": { + "comment": "moon key", + "includeAsRandomChoice": false + }, + "kysn": { + "comment": "sun key", + "includeAsRandomChoice": false + }, + "ledg": { + "comment": "Gerard's Lost Ledger", + "includeAsRandomChoice": false + }, + "phlt": { + "comment": "Phat Lewt", + "includeAsRandomChoice": false + }, + "sehr": { + "comment": "Searinox's Heart", + "includeAsRandomChoice": false + }, + "engs": { + "comment": "Enchanted Gemstone", + "includeAsRandomChoice": false + }, + "sorf": { + "comment": "Shadow Orb Fragment", + "includeAsRandomChoice": false + }, + "gmfr": { + "comment": "Gem Fragment", + "includeAsRandomChoice": false + }, + "jpnt": { + "comment": "note to jaina proudmoore", + "includeAsRandomChoice": false + }, + "shwd": { + "comment": "shimmerweed", + "includeAsRandomChoice": false + }, + "skrt": { + "comment": "Skeletal Artifact", + "includeAsRandomChoice": false + }, + "thle": { + "comment": "thunder lizard egg", + "includeAsRandomChoice": false + }, + "sclp": { + "comment": "secret level powerup", + "includeAsRandomChoice": false + }, + "wtlg": { + "comment": "Wirt's Leg", + "includeAsRandomChoice": false + }, + "wolg": { + "comment": "Wirt's Other Leg", + "includeAsRandomChoice": false + }, + "mgtk": { + "comment": "Magtheridon's Keys", + "includeAsRandomChoice": false + }, + "mort": { + "comment": "Mogrin's Report", + "includeAsRandomChoice": false + }, + "dphe": { + "comment": "Thunder Hawk Egg", + "includeAsRandomChoice": false + }, + "dkfw": { + "comment": "Keg of Thunderwater", + "includeAsRandomChoice": false + }, + "dthb": { + "comment": "Thunderbloom Bulb", + "includeAsRandomChoice": false + }, + "fgun": { + "comment": "Flare Gun", + "includeAsRandomChoice": false + }, + "lure": { + "comment": "Monster Lure", + "includeAsRandomChoice": false + }, + "olig": { + "comment": "Orb of Lightning(old)", + "includeAsRandomChoice": false + }, + "amrc": { + "comment": "Amulet of Recall", + "includeAsRandomChoice": false + }, + "flag": { + "comment": "human flag", + "includeAsRandomChoice": false + }, + "gobm": { + "comment": "Goblin Land Mine", + "includeAsRandomChoice": false + }, + "gsou": { + "comment": "Soul Gem", + "includeAsRandomChoice": false + }, + "nflg": { + "comment": "NightElf flag", + "includeAsRandomChoice": false + }, + "nspi": { + "comment": "Necklace of Spell Immunity", + "includeAsRandomChoice": false + }, + "oflg": { + "comment": "Orc flag", + "includeAsRandomChoice": false + }, + "pams": { + "comment": "Anti-Magic Potion", + "includeAsRandomChoice": false + }, + "pgin": { + "comment": "potion of greater invisibility", + "includeAsRandomChoice": false + }, + "rat3": { + "comment": "Claws of Attack +3", + "includeAsRandomChoice": false + }, + "rde0": { + "comment": "Ring of Protection +1", + "includeAsRandomChoice": false + }, + "rde1": { + "comment": "Ring of Protection +2", + "includeAsRandomChoice": false + }, + "rnsp": { + "comment": "Ring of Superiority", + "includeAsRandomChoice": true + }, + "soul": { + "comment": "Soul", + "includeAsRandomChoice": false + }, + "tels": { + "comment": "Goblin Night Scope", + "includeAsRandomChoice": false + }, + "tgxp": { + "comment": "Tome of Greater Experience", + "includeAsRandomChoice": false + }, + "uflg": { + "comment": "Undead flag", + "includeAsRandomChoice": false + }, + "anfg": { + "comment": "Ancient Figurine", + "includeAsRandomChoice": false + }, + "brag": { + "comment": "Bracer of Agility", + "includeAsRandomChoice": false + }, + "drph": { + "comment": "Druid Pouch", + "includeAsRandomChoice": false + }, + "iwbr": { + "comment": "Ironwood Branch", + "includeAsRandomChoice": false + }, + "jdrn": { + "comment": "Jade Ring", + "includeAsRandomChoice": false + }, + "lnrn": { + "comment": "Lion's Ring", + "includeAsRandomChoice": false + }, + "mlst": { + "comment": "Maul of Strength", + "includeAsRandomChoice": false + }, + "oslo": { + "comment": "Orb of Slow", + "includeAsRandomChoice": false + }, + "sbok": { + "comment": "Spell Book", + "includeAsRandomChoice": false + }, + "sksh": { + "comment": "Skull Shield", + "includeAsRandomChoice": false + }, + "sprn": { + "comment": "Spider Ring", + "includeAsRandomChoice": false + }, + "tmmt": { + "comment": "Totem of Might", + "includeAsRandomChoice": false + }, + "vddl": { + "comment": "Voodoo Doll", + "includeAsRandomChoice": false + }, + "spre": { + "comment": "Staff of Preservation", + "includeAsRandomChoice": false + }, + "sfog": { + "comment": "Horn of the Clouds", + "includeAsRandomChoice": false + }, + "sor1": { + "comment": "Shadow Orb +1", + "includeAsRandomChoice": false + }, + "sor2": { + "comment": "Shadow Orb +2", + "includeAsRandomChoice": false + }, + "sor3": { + "comment": "Shadow Orb +3", + "includeAsRandomChoice": false + }, + "sor4": { + "comment": "Shadow Orb +4", + "includeAsRandomChoice": false + }, + "sor5": { + "comment": "Shadow Orb +5", + "includeAsRandomChoice": false + }, + "sor6": { + "comment": "Shadow Orb +6", + "includeAsRandomChoice": false + }, + "sor7": { + "comment": "Shadow Orb +7", + "includeAsRandomChoice": false + }, + "sor8": { + "comment": "Shadow Orb +8", + "includeAsRandomChoice": false + }, + "sor9": { + "comment": "Shadow Orb +9", + "includeAsRandomChoice": false + }, + "sora": { + "comment": "Shadow Orb +10", + "includeAsRandomChoice": false + }, + "fwss": { + "comment": "Frostwyrm Skull Shield", + "includeAsRandomChoice": false + }, + "shtm": { + "comment": "Shamanic Totem", + "includeAsRandomChoice": false + }, + "esaz": { + "comment": "Essence of Aszune", + "includeAsRandomChoice": false + }, + "btst": { + "comment": "orcish battle standard", + "includeAsRandomChoice": false + }, + "tbsm": { + "comment": "Tiny Blacksmith", + "includeAsRandomChoice": false + }, + "tfar": { + "comment": "Tiny Farm", + "includeAsRandomChoice": false + }, + "tlum": { + "comment": "Tiny Lumber Mill", + "includeAsRandomChoice": false + }, + "tbar": { + "comment": "Tiny Barracks", + "includeAsRandomChoice": false + }, + "tbak": { + "comment": "Tiny Altar of Kings", + "includeAsRandomChoice": false + }, + "gldo": { + "comment": "Orb of Kil'jaeden", + "includeAsRandomChoice": false + }, + "stre": { + "comment": "Staff of Reanimation", + "includeAsRandomChoice": false + }, + "horl": { + "comment": "Holy Relic", + "includeAsRandomChoice": false + }, + "hbth": { + "comment": "Helm of Battlethirst", + "includeAsRandomChoice": false + }, + "blba": { + "comment": "Bladebane Armor", + "includeAsRandomChoice": false + }, + "rugt": { + "comment": "Runed Gauntlets", + "includeAsRandomChoice": false + }, + "frhg": { + "comment": "Firehand Gauntlets", + "includeAsRandomChoice": false + }, + "gvsm": { + "comment": "Gloves of Spell Mastery", + "includeAsRandomChoice": false + }, + "crdt": { + "comment": "Crown of the Deathlord", + "includeAsRandomChoice": false + }, + "arsc": { + "comment": "Arcane Scroll", + "includeAsRandomChoice": false + }, + "scul": { + "comment": "Scroll of the Unholy Legion", + "includeAsRandomChoice": false + }, + "tmsc": { + "comment": "Tome of Sacrifices", + "includeAsRandomChoice": false + }, + "dtsb": { + "comment": "Drek'thar's Spellbook", + "includeAsRandomChoice": false + }, + "grsl": { + "comment": "Grimoire of Souls", + "includeAsRandomChoice": false + }, + "arsh": { + "comment": "Arcanite Shield", + "includeAsRandomChoice": false + }, + "shdt": { + "comment": "Shield of the Deathlord", + "includeAsRandomChoice": false + }, + "shhn": { + "comment": "Shield of Honor", + "includeAsRandomChoice": false + }, + "shen": { + "comment": "Enchanted Shield", + "includeAsRandomChoice": false + }, + "thdm": { + "comment": "Thunderlizard Diamond", + "includeAsRandomChoice": false + }, + "stpg": { + "comment": "Stuffed Penguin", + "includeAsRandomChoice": false + }, + "shrs": { + "comment": "Shimmerglaze Roast", + "includeAsRandomChoice": false + }, + "bfhr": { + "comment": "Bloodfeather's Heart", + "includeAsRandomChoice": false + }, + "cosl": { + "comment": "Celestial Orb of Souls", + "includeAsRandomChoice": false + }, + "shcw": { + "comment": "Shaman Claws", + "includeAsRandomChoice": false + }, + "srbd": { + "comment": "Searing Blade", + "includeAsRandomChoice": false + }, + "frgd": { + "comment": "Frostguard", + "includeAsRandomChoice": false + }, + "envl": { + "comment": "Enchanted Vial", + "includeAsRandomChoice": false + }, + "rump": { + "comment": "Rusty Mining Pick", + "includeAsRandomChoice": false + }, + "srtl": { + "comment": "Serathil", + "includeAsRandomChoice": false + }, + "stwa": { + "comment": "Sturdy War Axe", + "includeAsRandomChoice": false + }, + "klmm": { + "comment": "Killmaim", + "includeAsRandomChoice": false + }, + "rots": { + "comment": "Rod of the Sea", + "includeAsRandomChoice": false + }, + "axas": { + "comment": "Ancestral Staff", + "includeAsRandomChoice": false + }, + "mnsf": { + "comment": "Mindstaff", + "includeAsRandomChoice": false + }, + "schl": { + "comment": "Scepter of Healing", + "includeAsRandomChoice": false + }, + "asbl": { + "comment": "Assassin's Blade", + "includeAsRandomChoice": false + }, + "kgal": { + "comment": "Keg of Ale", + "includeAsRandomChoice": false + }, + "ward": { + "comment": "Warsong Battle Drums", + "includeAsRandomChoice": false + }, + "gold": { + "comment": "Chest of Gold", + "includeAsRandomChoice": false + }, + "lmbr": { + "comment": "Bundle of Lumber", + "includeAsRandomChoice": false + }, + "gfor": { + "comment": "Glyph of Fortification", + "includeAsRandomChoice": false + }, + "guvi": { + "comment": "Glyph of UltraVision", + "includeAsRandomChoice": false + }, + "rspl": { + "comment": "Rune of Spirit Link", + "includeAsRandomChoice": false + }, + "rre1": { + "comment": "Rune of Lesser Resurrection", + "includeAsRandomChoice": false + }, + "rre2": { + "comment": "Rune of Greater Resurrection", + "includeAsRandomChoice": false + }, + "gomn": { + "comment": "Glyph of Omniscience", + "includeAsRandomChoice": false + }, + "rsps": { + "comment": "Rune of Shielding", + "includeAsRandomChoice": false + }, + "rspd": { + "comment": "Rune of Speed", + "includeAsRandomChoice": false + }, + "rman": { + "comment": "Rune of Mana(Lesser)", + "includeAsRandomChoice": false + }, + "rma2": { + "comment": "Rune of Mana(Greater)", + "includeAsRandomChoice": false + }, + "rres": { + "comment": "Rune of Restoration", + "includeAsRandomChoice": false + }, + "rreb": { + "comment": "Rune of Rebirth", + "includeAsRandomChoice": false + }, + "rhe1": { + "comment": "Rune of Lesser Healing", + "includeAsRandomChoice": true + }, + "rhe2": { + "comment": "Rune of Healing", + "includeAsRandomChoice": false + }, + "rhe3": { + "comment": "Rune of Greater Healing", + "includeAsRandomChoice": false + }, + "rdis": { + "comment": "Rune of Dispel Magic", + "includeAsRandomChoice": false + }, + "texp": { + "comment": "Tome of Experience", + "includeAsRandomChoice": false + }, + "rwat": { + "comment": "Rune of the Watcher", + "includeAsRandomChoice": false + }, + "pclr": { + "comment": "Clarity Potion", + "includeAsRandomChoice": false + }, + "plcl": { + "comment": "Lesser Clarity Potion", + "includeAsRandomChoice": false + }, + "silk": { + "comment": "Spider Silk", + "includeAsRandomChoice": false + }, + "vamp": { + "comment": "Potion of Vampirism", + "includeAsRandomChoice": true + }, + "sreg": { + "comment": "Scroll of Regeneration", + "includeAsRandomChoice": false + }, + "tcas": { + "comment": "Tiny Castle", + "includeAsRandomChoice": false + }, + "ssan": { + "comment": "Staff of Sanctuary", + "includeAsRandomChoice": false + }, + "ofr2": { + "comment": "Orb of Fire v2", + "includeAsRandomChoice": false + }, + "sxpl": { + "comment": "Seed of Expulsion", + "includeAsRandomChoice": false + }, + "vpur": { + "comment": "Vine of Purification", + "includeAsRandomChoice": false + }, + "pdiv": { + "comment": "Potion of Divinity (Divine Shield)", + "includeAsRandomChoice": false + }, + "fgrd": { + "comment": "Red Drake Egg", + "includeAsRandomChoice": false + }, + "totw": { + "comment": "talisman of the wild", + "includeAsRandomChoice": false + }, + "sand": { + "comment": "Scroll of Animate Dead", + "includeAsRandomChoice": false + }, + "srrc": { + "comment": "Scroll of Resurrection", + "includeAsRandomChoice": false + } +} \ No newline at end of file diff --git a/scripts/loot-indicator/map-loot/map-loot-parser.ts b/scripts/loot-indicator/map-loot/map-loot-parser.ts new file mode 100644 index 0000000..0237ff9 --- /dev/null +++ b/scripts/loot-indicator/map-loot/map-loot-parser.ts @@ -0,0 +1,55 @@ +import UnitsDoo from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/file.js"; +import War3MapW3i from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/w3i/file.js"; +import * as fs from "fs-extra"; +import DroppedItemSet from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/droppeditemset"; +import {RawUnitItemDrop} from "../../../src/player_features/loot-indicator/modules/unit-item-drops"; + +// getMapItemDrops('../../../maps/itemdroptable-testbench.w3x') + +export function getMapItemDrops(mapPath: string): RawUnitItemDrop[] { + const {unitsDoo, war3MapW3i} = loadUnitsDoo(mapPath); + // writeAsJson(`${mapPath}/raw-unit.json`, unitsDoo.units); + let rawDrops = [] as RawUnitItemDrop[]; + for (const unit of unitsDoo.units) { + let sets: DroppedItemSet[] = [] + //Use Custom Item Table sets + sets.push(...unit.droppedItemSets) + //Use Item Table from Map sets (used in "(8)WellspringTemple..." map for a set of 2 runes) + if (unit.droppedItemTable >= 0) { + const itemTable = war3MapW3i.randomItemTables[unit.droppedItemTable]; + if (itemTable !== undefined) { + sets.push(...itemTable.sets) + } else { + console.warn(`Item Table idx ${unit.droppedItemTable} is not found in map file`) + } + } + + //Filter out sets that use "ANY LEVEL" and "ANY CLASS" Random Groups (they cause issues) + // In real Melee map, nobody practically should use it + sets = sets.filter(set => !set.items.some(item => (item.id[1] === "Y") || (item.id[3] === "/"))); + + if (sets.length > 0) { + const itemSets = sets.map(set => { + return ({itemTypes: set.items.map(item => item.id)}); + }) + const unitLocation = {x: unit.location[0], y: unit.location[1]} + rawDrops.push({unitLocation, itemSets}); + } + } + + return rawDrops; +} + +function loadUnitsDoo(mapPath: string) { + const war3MapW3i = new War3MapW3i(); + war3MapW3i.load(fs.readFileSync(`${mapPath}/war3map.w3i`)) + // writeAsJson(`${mapPath}/war3map.w3i.json`, war3MapW3i); + + const unitsDoo = new UnitsDoo(); + unitsDoo.load(fs.readFileSync(`${mapPath}/war3mapUnits.doo`), war3MapW3i.getBuildVersion()) + return {unitsDoo, war3MapW3i}; +} + +function writeAsJson(path: string, data: any) { + fs.writeFileSync(path, JSON.stringify(data, null, 2)); +} \ No newline at end of file diff --git a/scripts/loot-indicator/model-heights/model-height-generator.ts b/scripts/loot-indicator/model-heights/model-height-generator.ts new file mode 100644 index 0000000..fb40ac7 --- /dev/null +++ b/scripts/loot-indicator/model-heights/model-height-generator.ts @@ -0,0 +1,70 @@ +import * as casclib from '@jamiephan/casclib' +import {IniFile} from "mdx-m3-viewer-th/dist/cjs/parsers/ini/file" +import {parseMDX} from 'war3-model'; +import * as fs from 'fs-extra'; +import {toArrayBuffer} from "../../utils"; +import {UnitModelHeight} from "../../../src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator"; + +const WC3_GAME_BASE_DIRECTORY = "D:\\Games\\Warcraft III"; + +generateModelHeights("unit-model-height-data.json") + +function generateModelHeights(resultFilePath: string) { + const storageHandle = casclib.openStorageSync(WC3_GAME_BASE_DIRECTORY) + try { + const unitSkinBuffer = casclib.readFileSync(storageHandle, "war3.w3mod:units\\unitskin.txt") + + const unitSkinIni: IniFile = new IniFile(); + unitSkinIni.load(unitSkinBuffer.toString("utf8")); + + let modelHeights = new Map() + + unitSkinIni.sections.forEach((section, unitType) => { + console.log(section) + let sdHeight: number, hdHeight: number; + + const file = section.get("file")!; + if (file != undefined) { + const modelHeight = getModelHeightFromFile(storageHandle, file, false); + sdHeight = modelHeight; + hdHeight = modelHeight; + } else { + sdHeight = getModelHeightFromFile(storageHandle, section.get("file:sd")!, false); + hdHeight = getModelHeightFromFile(storageHandle, section.get("file:hd")!, true); + } + + modelHeights.set(unitType, {sdHeight, hdHeight}) + }) + + fs.writeFileSync(resultFilePath, JSON.stringify(Object.fromEntries(modelHeights), null, 2)) + } finally { + casclib.closeStorage(storageHandle); + } +} + +function getModelHeightFromFile(storageHandle: any, filePath: string, isHd: boolean = false) { + try { + const fullPath = isHd ? + `war3.w3mod:_hd.w3mod:${filePath}.mdx` : + `war3.w3mod:${filePath}.mdx`; + + console.log(fullPath) + const arrayBuffer = readFileFromCasc(storageHandle, fullPath); + const model = parseMDX(arrayBuffer); + + // This is a guess, but it works for most models + const standSeq = model.Sequences.filter(seq => /^stand[ \-\d]*$/.test(seq.Name.toLowerCase()))[0] ?? model.Sequences[0]; + return standSeq.MaximumExtent[2]; + } catch (e: any) { + console.log(e) + return -1; + } + +} + +function readFileFromCasc(storageHandle: any, filePath: string) { + // Sometimes `readFileSync()` returns buffer bigger then the file size, so we slice it + const buffer = casclib.readFileSync(storageHandle, filePath); + const size = casclib.findFilesSync(storageHandle, filePath)[0].fileSize; + return toArrayBuffer(buffer.subarray(0, size)); +} \ No newline at end of file diff --git a/scripts/loot-indicator/model-heights/unit-model-height-data.json b/scripts/loot-indicator/model-heights/unit-model-height-data.json new file mode 100644 index 0000000..4bcf438 --- /dev/null +++ b/scripts/loot-indicator/model-heights/unit-model-height-data.json @@ -0,0 +1,3674 @@ +{ + "Hblm": { + "sdHeight": 194.1719970703125, + "hdHeight": 194.1719970703125 + }, + "Hmkg": { + "sdHeight": 98.85130310058594, + "hdHeight": 98.85130310058594 + }, + "Hpal": { + "sdHeight": 114.3030014038086, + "hdHeight": 114.3030014038086 + }, + "hbot": { + "sdHeight": 286.25201416015625, + "hdHeight": 286.25201416015625 + }, + "hbsh": { + "sdHeight": 278.4429931640625, + "hdHeight": 278.4429931640625 + }, + "hdes": { + "sdHeight": 251.15899658203125, + "hdHeight": 251.15899658203125 + }, + "hdhw": { + "sdHeight": 103.62300109863281, + "hdHeight": 94.55970001220703 + }, + "hfoo": { + "sdHeight": 99.07219696044922, + "hdHeight": 99.07219696044922 + }, + "hgry": { + "sdHeight": 73.12590026855469, + "hdHeight": 73.12590026855469 + }, + "hgyr": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hkni": { + "sdHeight": 151.84500122070312, + "hdHeight": 151.84500122070312 + }, + "hmil": { + "sdHeight": 94.94760131835938, + "hdHeight": 94.94760131835938 + }, + "hmpr": { + "sdHeight": 123.03199768066406, + "hdHeight": 123.03199768066406 + }, + "hmtm": { + "sdHeight": 78.14430236816406, + "hdHeight": 78.14430236816406 + }, + "hmtt": { + "sdHeight": 111.35600280761719, + "hdHeight": 111.35600280761719 + }, + "hpea": { + "sdHeight": 92.6604995727539, + "hdHeight": 92.6604995727539 + }, + "hphx": { + "sdHeight": 189.30599975585938, + "hdHeight": 189.30599975585938 + }, + "hpxe": { + "sdHeight": 189.30599975585938, + "hdHeight": 189.30599975585938 + }, + "hrif": { + "sdHeight": 82.31400299072266, + "hdHeight": 82.31400299072266 + }, + "hrtt": { + "sdHeight": 111.35600280761719, + "hdHeight": 111.35600280761719 + }, + "hsor": { + "sdHeight": 133.6300048828125, + "hdHeight": 133.6300048828125 + }, + "hspt": { + "sdHeight": 165.5279998779297, + "hdHeight": 165.5279998779297 + }, + "hwat": { + "sdHeight": 167.2239990234375, + "hdHeight": 167.2239990234375 + }, + "hwt2": { + "sdHeight": 167.2239990234375, + "hdHeight": 252.78900146484375 + }, + "hwt3": { + "sdHeight": 167.2239990234375, + "hdHeight": 286.8869934082031 + }, + "halt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "harm": { + "sdHeight": 186.05999755859375, + "hdHeight": 186.05999755859375 + }, + "hars": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hatw": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hbar": { + "sdHeight": 202.54400634765625, + "hdHeight": 202.54400634765625 + }, + "hbla": { + "sdHeight": 174.05999755859375, + "hdHeight": 174.05999755859375 + }, + "hcas": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hctw": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hgra": { + "sdHeight": 242.47500610351562, + "hdHeight": 242.47500610351562 + }, + "hgtw": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hhou": { + "sdHeight": 151.58700561523438, + "hdHeight": 151.58700561523438 + }, + "hkee": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hlum": { + "sdHeight": 235.875, + "hdHeight": 235.875 + }, + "hshy": { + "sdHeight": 283.06298828125, + "hdHeight": 283.06298828125 + }, + "htow": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hvlt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "hwtw": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Obla": { + "sdHeight": 197.85499572753906, + "hdHeight": 197.85499572753906 + }, + "Ofar": { + "sdHeight": 230.72799682617188, + "hdHeight": 230.72799682617188 + }, + "Oshd": { + "sdHeight": 214.89100646972656, + "hdHeight": 214.89100646972656 + }, + "Otch": { + "sdHeight": 213.93899536132812, + "hdHeight": 213.93899536132812 + }, + "nwad": { + "sdHeight": 154.4459991455078, + "hdHeight": 154.4459991455078 + }, + "obot": { + "sdHeight": 98.0353012084961, + "hdHeight": 98.0353012084961 + }, + "ocat": { + "sdHeight": 160.31100463867188, + "hdHeight": 160.31100463867188 + }, + "odes": { + "sdHeight": 138.17100524902344, + "hdHeight": 138.17100524902344 + }, + "odoc": { + "sdHeight": 170.6699981689453, + "hdHeight": 170.6699981689453 + }, + "oeye": { + "sdHeight": 115.84600067138672, + "hdHeight": 115.84600067138672 + }, + "ogru": { + "sdHeight": 100.63200378417969, + "hdHeight": 100.63200378417969 + }, + "ohun": { + "sdHeight": 114.23400115966797, + "hdHeight": 114.23400115966797 + }, + "ohwd": { + "sdHeight": 164.24200439453125, + "hdHeight": 164.24200439453125 + }, + "okod": { + "sdHeight": 169.3769989013672, + "hdHeight": 169.3769989013672 + }, + "opeo": { + "sdHeight": 87.60700225830078, + "hdHeight": 87.60700225830078 + }, + "orai": { + "sdHeight": 172.1739959716797, + "hdHeight": 172.1739959716797 + }, + "oshm": { + "sdHeight": 108.1719970703125, + "hdHeight": 108.1719970703125 + }, + "osp1": { + "sdHeight": 326.9519958496094, + "hdHeight": 326.9519958496094 + }, + "osp2": { + "sdHeight": 326.9519958496094, + "hdHeight": 223.90699768066406 + }, + "osp3": { + "sdHeight": 326.9519958496094, + "hdHeight": 231.33999633789062 + }, + "osp4": { + "sdHeight": 326.9519958496094, + "hdHeight": 229.91799926757812 + }, + "ospm": { + "sdHeight": 145.9770050048828, + "hdHeight": 145.9770050048828 + }, + "ospw": { + "sdHeight": 145.9770050048828, + "hdHeight": 145.9770050048828 + }, + "osw1": { + "sdHeight": 164.6300048828125, + "hdHeight": 164.6300048828125 + }, + "osw2": { + "sdHeight": 164.6300048828125, + "hdHeight": 127.76499938964844 + }, + "osw3": { + "sdHeight": 164.6300048828125, + "hdHeight": 138.41200256347656 + }, + "otau": { + "sdHeight": 165.37399291992188, + "hdHeight": 165.37399291992188 + }, + "otbk": { + "sdHeight": 114.23400115966797, + "hdHeight": 114.23400115966797 + }, + "otbr": { + "sdHeight": 107.46700286865234, + "hdHeight": 107.46700286865234 + }, + "otot": { + "sdHeight": 254.96299743652344, + "hdHeight": 254.96299743652344 + }, + "owyv": { + "sdHeight": 118.80899810791016, + "hdHeight": 118.80899810791016 + }, + "oalt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "obar": { + "sdHeight": -1, + "hdHeight": -1 + }, + "obea": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ofor": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ofrt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ogre": { + "sdHeight": -1, + "hdHeight": -1 + }, + "oshy": { + "sdHeight": 203.03599548339844, + "hdHeight": 221.91200256347656 + }, + "osld": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ostr": { + "sdHeight": -1, + "hdHeight": -1 + }, + "otrb": { + "sdHeight": -1, + "hdHeight": -1 + }, + "otto": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ovln": { + "sdHeight": 234.61000061035156, + "hdHeight": 234.61000061035156 + }, + "owtw": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Edem": { + "sdHeight": -1, + "hdHeight": 143.56500244140625 + }, + "Edmm": { + "sdHeight": -1, + "hdHeight": 143.56500244140625 + }, + "Edmf": { + "sdHeight": -1, + "hdHeight": 130.156005859375 + }, + "Ekee": { + "sdHeight": 233.01800537109375, + "hdHeight": 233.01800537109375 + }, + "Emoo": { + "sdHeight": 150.01600646972656, + "hdHeight": 150.01600646972656 + }, + "Ewar": { + "sdHeight": 160.3719940185547, + "hdHeight": 160.3719940185547 + }, + "earc": { + "sdHeight": 93.87740325927734, + "hdHeight": 93.87740325927734 + }, + "ebal": { + "sdHeight": 88.64830017089844, + "hdHeight": 88.64830017089844 + }, + "ebsh": { + "sdHeight": 278.4429931640625, + "hdHeight": 278.4429931640625 + }, + "echm": { + "sdHeight": 173.32899475097656, + "hdHeight": 173.32899475097656 + }, + "edcm": { + "sdHeight": 150.73199462890625, + "hdHeight": 150.73199462890625 + }, + "edes": { + "sdHeight": 225.9320068359375, + "hdHeight": 225.9320068359375 + }, + "edoc": { + "sdHeight": 150.73199462890625, + "hdHeight": 150.73199462890625 + }, + "edot": { + "sdHeight": 133.61599731445312, + "hdHeight": 133.61599731445312 + }, + "edry": { + "sdHeight": 146.45599365234375, + "hdHeight": 146.45599365234375 + }, + "edtm": { + "sdHeight": 133.61599731445312, + "hdHeight": 133.61599731445312 + }, + "efdr": { + "sdHeight": 72.30269622802734, + "hdHeight": 72.30269622802734 + }, + "efon": { + "sdHeight": 161.2830047607422, + "hdHeight": 161.2830047607422 + }, + "ehip": { + "sdHeight": 127.60800170898438, + "hdHeight": 127.60800170898438 + }, + "ehpr": { + "sdHeight": 127.60800170898438, + "hdHeight": 127.60800170898438 + }, + "emtg": { + "sdHeight": 228.71299743652344, + "hdHeight": 228.71299743652344 + }, + "esen": { + "sdHeight": 125.62999725341797, + "hdHeight": 125.62999725341797 + }, + "espv": { + "sdHeight": 236.1529998779297, + "hdHeight": 236.1529998779297 + }, + "espm": { + "sdHeight": -1, + "hdHeight": 188.02200317382812 + }, + "even": { + "sdHeight": 106.58200073242188, + "hdHeight": 106.58200073242188 + }, + "ewsp": { + "sdHeight": 176.2100067138672, + "hdHeight": 176.2100067138672 + }, + "eaoe": { + "sdHeight": 210.4219970703125, + "hdHeight": 210.4219970703125 + }, + "eaom": { + "sdHeight": 245.177001953125, + "hdHeight": 245.177001953125 + }, + "eaow": { + "sdHeight": 292.0010070800781, + "hdHeight": 292.0010070800781 + }, + "eate": { + "sdHeight": 164.8679962158203, + "hdHeight": 164.8679962158203 + }, + "eden": { + "sdHeight": 217.28399658203125, + "hdHeight": 217.28399658203125 + }, + "edob": { + "sdHeight": 269.1260070800781, + "hdHeight": 269.1260070800781 + }, + "edos": { + "sdHeight": 327.2229919433594, + "hdHeight": 327.2229919433594 + }, + "egol": { + "sdHeight": 197.7239990234375, + "hdHeight": 197.7239990234375 + }, + "emow": { + "sdHeight": 180.03199768066406, + "hdHeight": 180.03199768066406 + }, + "eshy": { + "sdHeight": 300.20001220703125, + "hdHeight": 300.20001220703125 + }, + "etoa": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "etoe": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "etol": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "etrp": { + "sdHeight": 278.5769958496094, + "hdHeight": 278.5769958496094 + }, + "Ucrl": { + "sdHeight": 156.9709930419922, + "hdHeight": 156.9709930419922 + }, + "Udea": { + "sdHeight": 154.08700561523438, + "hdHeight": 164.18299865722656 + }, + "Udre": { + "sdHeight": 161.95799255371094, + "hdHeight": 161.95799255371094 + }, + "Ulic": { + "sdHeight": 177.53500366210938, + "hdHeight": 177.53500366210938 + }, + "uabo": { + "sdHeight": 197.4720001220703, + "hdHeight": 197.4720001220703 + }, + "uaco": { + "sdHeight": 107.28900146484375, + "hdHeight": 107.28900146484375 + }, + "uban": { + "sdHeight": 115.72599792480469, + "hdHeight": 115.72599792480469 + }, + "ubsp": { + "sdHeight": 353.12200927734375, + "hdHeight": 353.12200927734375 + }, + "ucrm": { + "sdHeight": 143.73500061035156, + "hdHeight": 143.73500061035156 + }, + "ucry": { + "sdHeight": 143.73500061035156, + "hdHeight": 143.73500061035156 + }, + "ucs1": { + "sdHeight": 56.724700927734375, + "hdHeight": 56.724700927734375 + }, + "ucs2": { + "sdHeight": 56.724700927734375, + "hdHeight": 44.4739990234375 + }, + "ucs3": { + "sdHeight": 56.724700927734375, + "hdHeight": 54.4635009765625 + }, + "ucsB": { + "sdHeight": 56.724700927734375, + "hdHeight": 44.4739990234375 + }, + "ucsC": { + "sdHeight": 56.724700927734375, + "hdHeight": 54.4635009765625 + }, + "ufro": { + "sdHeight": 122.80400085449219, + "hdHeight": 122.80400085449219 + }, + "ugar": { + "sdHeight": 123.5479965209961, + "hdHeight": 123.5479965209961 + }, + "ugho": { + "sdHeight": 72.86900329589844, + "hdHeight": 72.86900329589844 + }, + "ugrm": { + "sdHeight": 123.5479965209961, + "hdHeight": 123.5479965209961 + }, + "uloc": { + "sdHeight": 80.62010192871094, + "hdHeight": 80.62010192871094 + }, + "umtw": { + "sdHeight": 156.27999877929688, + "hdHeight": 156.27999877929688 + }, + "unec": { + "sdHeight": 130.57899475097656, + "hdHeight": 130.57899475097656 + }, + "uobs": { + "sdHeight": 353.12200927734375, + "hdHeight": 353.12200927734375 + }, + "uplg": { + "sdHeight": 0, + "hdHeight": 0 + }, + "ushd": { + "sdHeight": 142.53599548339844, + "hdHeight": 142.53599548339844 + }, + "uske": { + "sdHeight": 104.60099792480469, + "hdHeight": 104.60099792480469 + }, + "uskm": { + "sdHeight": 129.4340057373047, + "hdHeight": 129.4340057373047 + }, + "uubs": { + "sdHeight": 261.50299072265625, + "hdHeight": 261.50299072265625 + }, + "uaod": { + "sdHeight": 165.66700744628906, + "hdHeight": 165.66700744628906 + }, + "ubon": { + "sdHeight": 258.2380065917969, + "hdHeight": 258.2380065917969 + }, + "ugol": { + "sdHeight": 363.87701416015625, + "hdHeight": 363.87701416015625 + }, + "ugrv": { + "sdHeight": 232.85499572753906, + "hdHeight": 232.85499572753906 + }, + "unp1": { + "sdHeight": 417.4219970703125, + "hdHeight": 417.4219970703125 + }, + "unp2": { + "sdHeight": 417.4219970703125, + "hdHeight": 417.4219970703125 + }, + "unpl": { + "sdHeight": 417.4219970703125, + "hdHeight": 417.4219970703125 + }, + "usap": { + "sdHeight": 101.89299774169922, + "hdHeight": 101.89299774169922 + }, + "usep": { + "sdHeight": 323.07000732421875, + "hdHeight": 323.07000732421875 + }, + "ushp": { + "sdHeight": 163.06900024414062, + "hdHeight": 163.06900024414062 + }, + "uslh": { + "sdHeight": 287.9389953613281, + "hdHeight": 287.9389953613281 + }, + "utod": { + "sdHeight": 206.4080047607422, + "hdHeight": 206.4080047607422 + }, + "utom": { + "sdHeight": 212.77200317382812, + "hdHeight": 212.77200317382812 + }, + "uzg1": { + "sdHeight": 193.1269989013672, + "hdHeight": 193.1269989013672 + }, + "uzg2": { + "sdHeight": 193.1269989013672, + "hdHeight": 193.1269989013672 + }, + "uzig": { + "sdHeight": 193.1269989013672, + "hdHeight": 193.1269989013672 + }, + "Nbrn": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "Nbst": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "Nngs": { + "sdHeight": 166.56300354003906, + "hdHeight": 166.56300354003906 + }, + "Npbm": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "Nalc": { + "sdHeight": 213.07699584960938, + "hdHeight": 213.07699584960938 + }, + "Nalm": { + "sdHeight": 213.07699584960938, + "hdHeight": 213.07699584960938 + }, + "Nal2": { + "sdHeight": 213.07699584960938, + "hdHeight": 213.07699584960938 + }, + "Nal3": { + "sdHeight": 213.07699584960938, + "hdHeight": 213.07699584960938 + }, + "Ntin": { + "sdHeight": 153.3459930419922, + "hdHeight": 153.3459930419922 + }, + "Nrob": { + "sdHeight": 153.3459930419922, + "hdHeight": 153.3459930419922 + }, + "ncgb": { + "sdHeight": 79.53389739990234, + "hdHeight": 79.53389739990234 + }, + "ncg1": { + "sdHeight": 79.53389739990234, + "hdHeight": 79.53389739990234 + }, + "ncg2": { + "sdHeight": 79.53389739990234, + "hdHeight": 79.53389739990234 + }, + "ncg3": { + "sdHeight": 79.53389739990234, + "hdHeight": 79.53389739990234 + }, + "nfac": { + "sdHeight": 169.81100463867188, + "hdHeight": 169.81100463867188 + }, + "nfa1": { + "sdHeight": 169.81100463867188, + "hdHeight": 231.95700073242188 + }, + "nfa2": { + "sdHeight": 169.81100463867188, + "hdHeight": 255.9739990234375 + }, + "Nplh": { + "sdHeight": 223.1909942626953, + "hdHeight": 223.1909942626953 + }, + "Nfir": { + "sdHeight": 221.3159942626953, + "hdHeight": 221.3159942626953 + }, + "Nvol": { + "sdHeight": -1, + "hdHeight": 222.927001953125 + }, + "nlv1": { + "sdHeight": 158.8159942626953, + "hdHeight": 158.8159942626953 + }, + "nlv2": { + "sdHeight": 158.8159942626953, + "hdHeight": 118.0270004272461 + }, + "nlv3": { + "sdHeight": 158.8159942626953, + "hdHeight": 131.10899353027344 + }, + "ndr1": { + "sdHeight": 104.60099792480469, + "hdHeight": 103.79900360107422 + }, + "ndr2": { + "sdHeight": 104.60099792480469, + "hdHeight": 128.26800537109375 + }, + "ndr3": { + "sdHeight": 104.60099792480469, + "hdHeight": 150.9810028076172 + }, + "ngz1": { + "sdHeight": 138.1790008544922, + "hdHeight": 138.1790008544922 + }, + "ngz2": { + "sdHeight": 138.1790008544922, + "hdHeight": 148.52999877929688 + }, + "ngz3": { + "sdHeight": 138.1790008544922, + "hdHeight": 165.27499389648438 + }, + "ngzc": { + "sdHeight": 138.1790008544922, + "hdHeight": 109.93599700927734 + }, + "ngzd": { + "sdHeight": 138.1790008544922, + "hdHeight": 122.24500274658203 + }, + "ngza": { + "sdHeight": 138.1790008544922, + "hdHeight": 142.41400146484375 + }, + "ngz4": { + "sdHeight": 138.1790008544922, + "hdHeight": 172.31199645996094 + }, + "npn1": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "npn2": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "npn3": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "npn4": { + "sdHeight": 258.4989929199219, + "hdHeight": 171.39300537109375 + }, + "npn5": { + "sdHeight": 258.4989929199219, + "hdHeight": 153.6230010986328 + }, + "npn6": { + "sdHeight": 258.4989929199219, + "hdHeight": 174.76300048828125 + }, + "nqb1": { + "sdHeight": 78.39559936523438, + "hdHeight": 78.39559936523438 + }, + "nqb2": { + "sdHeight": 78.39559936523438, + "hdHeight": 120.93199920654297 + }, + "nqb3": { + "sdHeight": 78.39559936523438, + "hdHeight": 130.9980010986328 + }, + "nqb4": { + "sdHeight": 78.39559936523438, + "hdHeight": 151.16700744628906 + }, + "nwe1": { + "sdHeight": 104.4749984741211, + "hdHeight": 104.4749984741211 + }, + "nwe2": { + "sdHeight": 104.4749984741211, + "hdHeight": 168.57699584960938 + }, + "nwe3": { + "sdHeight": 104.4749984741211, + "hdHeight": 256.2460021972656 + }, + "nadk": { + "sdHeight": 103.1719970703125, + "hdHeight": 150.55099487304688 + }, + "nadr": { + "sdHeight": 103.1719970703125, + "hdHeight": 202.74000549316406 + }, + "nadw": { + "sdHeight": 71.18779754638672, + "hdHeight": 71.18779754638672 + }, + "nahy": { + "sdHeight": 206.81100463867188, + "hdHeight": 316.3680114746094 + }, + "nanb": { + "sdHeight": 139.2949981689453, + "hdHeight": 139.2949981689453 + }, + "nanm": { + "sdHeight": 139.2949981689453, + "hdHeight": 139.2949981689453 + }, + "nanc": { + "sdHeight": 139.2949981689453, + "hdHeight": 139.2949981689453 + }, + "nane": { + "sdHeight": 139.2949981689453, + "hdHeight": 145.57899475097656 + }, + "nano": { + "sdHeight": 139.2949981689453, + "hdHeight": 139.2949981689453 + }, + "nanw": { + "sdHeight": 139.2949981689453, + "hdHeight": 143.17100524902344 + }, + "narg": { + "sdHeight": 218.82400512695312, + "hdHeight": 218.82400512695312 + }, + "nass": { + "sdHeight": 115.48500061035156, + "hdHeight": 120.1969985961914 + }, + "nbal": { + "sdHeight": 212.49600219726562, + "hdHeight": 212.49600219726562 + }, + "nba2": { + "sdHeight": 212.49600219726562, + "hdHeight": 374.5740051269531 + }, + "nban": { + "sdHeight": 96.46890258789062, + "hdHeight": 96.46890258789062 + }, + "nbda": { + "sdHeight": 141.0989990234375, + "hdHeight": 141.0989990234375 + }, + "nbdk": { + "sdHeight": 103.1719970703125, + "hdHeight": 148.09100341796875 + }, + "nbdm": { + "sdHeight": 141.0989990234375, + "hdHeight": 141.0989990234375 + }, + "nbdo": { + "sdHeight": 141.0989990234375, + "hdHeight": 206.0959930419922 + }, + "nbdr": { + "sdHeight": 71.18779754638672, + "hdHeight": 71.18779754638672 + }, + "nbds": { + "sdHeight": 141.0989990234375, + "hdHeight": 183.7740020751953 + }, + "nbdw": { + "sdHeight": 141.0989990234375, + "hdHeight": 141.0989990234375 + }, + "nbld": { + "sdHeight": 151.84500122070312, + "hdHeight": 151.84500122070312 + }, + "nbnb": { + "sdHeight": 139.2949981689453, + "hdHeight": 139.2949981689453 + }, + "nbot": { + "sdHeight": 286.25201416015625, + "hdHeight": 172.73399353027344 + }, + "nbrg": { + "sdHeight": 115.48500061035156, + "hdHeight": 100.62699890136719 + }, + "nbwm": { + "sdHeight": 103.1719970703125, + "hdHeight": 103.1719970703125 + }, + "nbzd": { + "sdHeight": 103.1719970703125, + "hdHeight": 103.1719970703125 + }, + "nbzk": { + "sdHeight": 103.1719970703125, + "hdHeight": 147.4739990234375 + }, + "nbzw": { + "sdHeight": 71.18779754638672, + "hdHeight": 71.18779754638672 + }, + "ncea": { + "sdHeight": 122.19599914550781, + "hdHeight": 122.19599914550781 + }, + "ncen": { + "sdHeight": 127.25, + "hdHeight": 188.19400024414062 + }, + "ncer": { + "sdHeight": 127.25, + "hdHeight": 127.25 + }, + "ncfs": { + "sdHeight": 94.36869812011719, + "hdHeight": 95.32969665527344 + }, + "ncim": { + "sdHeight": 122.19599914550781, + "hdHeight": 163.82000732421875 + }, + "ncks": { + "sdHeight": 127.25, + "hdHeight": 175.40699768066406 + }, + "ncnk": { + "sdHeight": 127.25, + "hdHeight": 127.25 + }, + "ndqn": { + "sdHeight": 106.38800048828125, + "hdHeight": 106.38800048828125 + }, + "ndqp": { + "sdHeight": 106.38800048828125, + "hdHeight": 106.38800048828125 + }, + "ndqs": { + "sdHeight": 106.38800048828125, + "hdHeight": 149.30499267578125 + }, + "ndqt": { + "sdHeight": 106.38800048828125, + "hdHeight": 124.7040023803711 + }, + "ndqv": { + "sdHeight": 106.38800048828125, + "hdHeight": 122.89700317382812 + }, + "ndrv": { + "sdHeight": 213.18800354003906, + "hdHeight": 412.83599853515625 + }, + "ndtb": { + "sdHeight": 109.68699645996094, + "hdHeight": 146.27699279785156 + }, + "ndth": { + "sdHeight": 118.10399627685547, + "hdHeight": 148.71200561523438 + }, + "ndtp": { + "sdHeight": 118.10399627685547, + "hdHeight": 118.10399627685547 + }, + "ndtr": { + "sdHeight": 109.68699645996094, + "hdHeight": 109.68699645996094 + }, + "ndtt": { + "sdHeight": 109.68699645996094, + "hdHeight": 109.68699645996094 + }, + "ndtw": { + "sdHeight": 109.68699645996094, + "hdHeight": 175.75900268554688 + }, + "nehy": { + "sdHeight": 206.81100463867188, + "hdHeight": 284.02801513671875 + }, + "nelb": { + "sdHeight": 167.2239990234375, + "hdHeight": 202.2169952392578 + }, + "nele": { + "sdHeight": 167.2239990234375, + "hdHeight": 182.4759979248047 + }, + "nenc": { + "sdHeight": 144.697998046875, + "hdHeight": 178.76499938964844 + }, + "nenf": { + "sdHeight": 96.46890258789062, + "hdHeight": 106.24800109863281 + }, + "nenp": { + "sdHeight": 144.697998046875, + "hdHeight": 200.86599731445312 + }, + "nepl": { + "sdHeight": 144.697998046875, + "hdHeight": 247.67799377441406 + }, + "nerd": { + "sdHeight": 133.98599243164062, + "hdHeight": 133.98599243164062 + }, + "ners": { + "sdHeight": 133.98599243164062, + "hdHeight": 205.0959930419922 + }, + "nerw": { + "sdHeight": 133.98599243164062, + "hdHeight": 133.98599243164062 + }, + "nfel": { + "sdHeight": 95.0698013305664, + "hdHeight": 95.0698013305664 + }, + "nfgb": { + "sdHeight": 191.93899536132812, + "hdHeight": 199.50399780273438 + }, + "nfgo": { + "sdHeight": 171.7550048828125, + "hdHeight": 171.7550048828125 + }, + "nfgt": { + "sdHeight": 315.5989990234375, + "hdHeight": 315.5989990234375 + }, + "nfgu": { + "sdHeight": 191.93899536132812, + "hdHeight": 191.93899536132812 + }, + "nfod": { + "sdHeight": 111.0270004272461, + "hdHeight": 264.8059997558594 + }, + "nfor": { + "sdHeight": 111.0270004272461, + "hdHeight": 111.0270004272461 + }, + "nfot": { + "sdHeight": 111.0270004272461, + "hdHeight": 215.1320037841797 + }, + "nfov": { + "sdHeight": 191.93899536132812, + "hdHeight": 191.93899536132812 + }, + "nfpc": { + "sdHeight": 142.99000549316406, + "hdHeight": 196.66900634765625 + }, + "nfpe": { + "sdHeight": 137.24600219726562, + "hdHeight": 204.45899963378906 + }, + "nfpl": { + "sdHeight": 142.99000549316406, + "hdHeight": 142.99000549316406 + }, + "nfps": { + "sdHeight": 137.24600219726562, + "hdHeight": 164.39999389648438 + }, + "nfpt": { + "sdHeight": 137.24600219726562, + "hdHeight": 137.24600219726562 + }, + "nfpu": { + "sdHeight": 142.99000549316406, + "hdHeight": 227.19400024414062 + }, + "nfra": { + "sdHeight": 126.46900177001953, + "hdHeight": 198.27499389648438 + }, + "nfrb": { + "sdHeight": 126.46900177001953, + "hdHeight": 126.46900177001953 + }, + "nfre": { + "sdHeight": 126.46900177001953, + "hdHeight": 182.14199829101562 + }, + "nfrg": { + "sdHeight": 135.81199645996094, + "hdHeight": 181.322998046875 + }, + "nfrl": { + "sdHeight": 135.81199645996094, + "hdHeight": 135.81199645996094 + }, + "nfrp": { + "sdHeight": 126.46900177001953, + "hdHeight": 126.46900177001953 + }, + "nfrs": { + "sdHeight": 126.46900177001953, + "hdHeight": 126.46900177001953 + }, + "nfsh": { + "sdHeight": 118.10399627685547, + "hdHeight": 185.81500244140625 + }, + "nfsp": { + "sdHeight": 118.10399627685547, + "hdHeight": 118.10399627685547 + }, + "nftb": { + "sdHeight": 109.68699645996094, + "hdHeight": 150.83900451660156 + }, + "nftk": { + "sdHeight": 109.68699645996094, + "hdHeight": 224.08200073242188 + }, + "nftr": { + "sdHeight": 109.68699645996094, + "hdHeight": 109.68699645996094 + }, + "nftt": { + "sdHeight": 109.68699645996094, + "hdHeight": 109.68699645996094 + }, + "ngdk": { + "sdHeight": 103.1719970703125, + "hdHeight": 150.55099487304688 + }, + "nggr": { + "sdHeight": 210.2790069580078, + "hdHeight": 283.59600830078125 + }, + "ngh1": { + "sdHeight": 115.72599792480469, + "hdHeight": 115.72599792480469 + }, + "ngh2": { + "sdHeight": 115.72599792480469, + "hdHeight": 127.87200164794922 + }, + "ngir": { + "sdHeight": 151.92799377441406, + "hdHeight": 213.9040069580078 + }, + "nglm": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngna": { + "sdHeight": 114.60399627685547, + "hdHeight": 114.60399627685547 + }, + "ngnb": { + "sdHeight": 113.99299621582031, + "hdHeight": 149.72900390625 + }, + "ngno": { + "sdHeight": 113.99299621582031, + "hdHeight": 113.99299621582031 + }, + "ngns": { + "sdHeight": 114.60399627685547, + "hdHeight": 154.16099548339844 + }, + "ngnv": { + "sdHeight": 113.99299621582031, + "hdHeight": 113.99299621582031 + }, + "ngnw": { + "sdHeight": 113.99299621582031, + "hdHeight": 113.99299621582031 + }, + "ngrd": { + "sdHeight": 103.1719970703125, + "hdHeight": 103.1719970703125 + }, + "ngrk": { + "sdHeight": 210.2790069580078, + "hdHeight": 121.81600189208984 + }, + "ngrw": { + "sdHeight": 71.18779754638672, + "hdHeight": 71.18779754638672 + }, + "ngsp": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngst": { + "sdHeight": 210.2790069580078, + "hdHeight": 210.2790069580078 + }, + "nhar": { + "sdHeight": 77.82669830322266, + "hdHeight": 77.82669830322266 + }, + "nhdc": { + "sdHeight": 107.28900146484375, + "hdHeight": 115.75 + }, + "nhfp": { + "sdHeight": 107.28900146484375, + "hdHeight": 108.51200103759766 + }, + "nhhr": { + "sdHeight": 107.28900146484375, + "hdHeight": 183.99200439453125 + }, + "nhrh": { + "sdHeight": 77.82669830322266, + "hdHeight": 182.947998046875 + }, + "nhrq": { + "sdHeight": 77.82669830322266, + "hdHeight": 77.82669830322266 + }, + "nhrr": { + "sdHeight": 77.82669830322266, + "hdHeight": 182.35499572753906 + }, + "nhrw": { + "sdHeight": 77.82669830322266, + "hdHeight": 77.82669830322266 + }, + "nhyc": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "nhyd": { + "sdHeight": 206.81100463867188, + "hdHeight": 206.81100463867188 + }, + "nhyh": { + "sdHeight": 206.81100463867188, + "hdHeight": 192.4759979248047 + }, + "nina": { + "sdHeight": 131.0959930419922, + "hdHeight": 131.0959930419922 + }, + "ninc": { + "sdHeight": 137.6529998779297, + "hdHeight": 137.6529998779297 + }, + "ninf": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ninm": { + "sdHeight": 137.6529998779297, + "hdHeight": 185.74200439453125 + }, + "nith": { + "sdHeight": 118.10399627685547, + "hdHeight": 128.19700622558594 + }, + "nitp": { + "sdHeight": 118.10399627685547, + "hdHeight": 114.14299774169922 + }, + "nitr": { + "sdHeight": 109.68699645996094, + "hdHeight": 109.68699645996094 + }, + "nits": { + "sdHeight": 109.68699645996094, + "hdHeight": 138.3560028076172 + }, + "nitt": { + "sdHeight": 109.68699645996094, + "hdHeight": 132.26600646972656 + }, + "nitw": { + "sdHeight": 109.68699645996094, + "hdHeight": 175.7169952392578 + }, + "njgb": { + "sdHeight": 159.95399475097656, + "hdHeight": 242.6510009765625 + }, + "njga": { + "sdHeight": 159.95399475097656, + "hdHeight": 203.98300170898438 + }, + "njg1": { + "sdHeight": 159.95399475097656, + "hdHeight": 159.95399475097656 + }, + "nkob": { + "sdHeight": 72.85359954833984, + "hdHeight": 72.85359954833984 + }, + "nkog": { + "sdHeight": 72.85359954833984, + "hdHeight": 72.85359954833984 + }, + "nkol": { + "sdHeight": 72.85359954833984, + "hdHeight": 115.22000122070312 + }, + "nkot": { + "sdHeight": 72.85359954833984, + "hdHeight": 91.88009643554688 + }, + "nlds": { + "sdHeight": 208.01199340820312, + "hdHeight": 197.5229949951172 + }, + "nlkl": { + "sdHeight": 208.01199340820312, + "hdHeight": 208.01199340820312 + }, + "nlpd": { + "sdHeight": 208.01199340820312, + "hdHeight": 139.72500610351562 + }, + "nlpr": { + "sdHeight": 208.01199340820312, + "hdHeight": 208.01199340820312 + }, + "nlps": { + "sdHeight": 208.01199340820312, + "hdHeight": 208.01199340820312 + }, + "nlrv": { + "sdHeight": 213.18800354003906, + "hdHeight": 410.4739990234375 + }, + "nlsn": { + "sdHeight": 208.01199340820312, + "hdHeight": 161.51199340820312 + }, + "nltc": { + "sdHeight": 208.01199340820312, + "hdHeight": 208.01199340820312 + }, + "nltl": { + "sdHeight": 184.88499450683594, + "hdHeight": 173.45899963378906 + }, + "nlur": { + "sdHeight": 161.7469940185547, + "hdHeight": 161.7469940185547 + }, + "nmam": { + "sdHeight": 165.9080047607422, + "hdHeight": 165.9080047607422 + }, + "nmbg": { + "sdHeight": 94.36869812011719, + "hdHeight": 94.36869812011719 + }, + "nmcf": { + "sdHeight": 94.36869812011719, + "hdHeight": 82.43900299072266 + }, + "nmdr": { + "sdHeight": 165.9080047607422, + "hdHeight": 165.9080047607422 + }, + "nmfs": { + "sdHeight": 77.28579711914062, + "hdHeight": 77.28579711914062 + }, + "nmgd": { + "sdHeight": 167.82200622558594, + "hdHeight": 167.82200622558594 + }, + "nmgr": { + "sdHeight": 167.82200622558594, + "hdHeight": 167.82200622558594 + }, + "nmgw": { + "sdHeight": 167.82200622558594, + "hdHeight": 167.82200622558594 + }, + "nmit": { + "sdHeight": 165.9080047607422, + "hdHeight": 239.34300231933594 + }, + "nmmu": { + "sdHeight": 79.55819702148438, + "hdHeight": 79.55819702148438 + }, + "nmpg": { + "sdHeight": 79.55819702148438, + "hdHeight": 87.9574966430664 + }, + "nmrl": { + "sdHeight": 79.55819702148438, + "hdHeight": 79.55819702148438 + }, + "nmrm": { + "sdHeight": 79.55819702148438, + "hdHeight": 79.55819702148438 + }, + "nmrr": { + "sdHeight": 79.55819702148438, + "hdHeight": 79.55819702148438 + }, + "nmrv": { + "sdHeight": 94.36869812011719, + "hdHeight": 157.43899536132812 + }, + "nmsc": { + "sdHeight": 95.91210174560547, + "hdHeight": 95.91210174560547 + }, + "nmsn": { + "sdHeight": 95.91210174560547, + "hdHeight": 111.27999877929688 + }, + "nmtw": { + "sdHeight": 94.36869812011719, + "hdHeight": 94.36869812011719 + }, + "nmyr": { + "sdHeight": 182.6790008544922, + "hdHeight": 182.6790008544922 + }, + "nmys": { + "sdHeight": 182.6790008544922, + "hdHeight": 182.6790008544922 + }, + "nndk": { + "sdHeight": 137.28799438476562, + "hdHeight": 163.80799865722656 + }, + "nndr": { + "sdHeight": 137.28799438476562, + "hdHeight": 137.28799438476562 + }, + "nnht": { + "sdHeight": 137.28799438476562, + "hdHeight": 75.6416015625 + }, + "nnmg": { + "sdHeight": 94.36869812011719, + "hdHeight": 94.36869812011719 + }, + "nnrg": { + "sdHeight": 182.6790008544922, + "hdHeight": 182.6790008544922 + }, + "nnrs": { + "sdHeight": 182.6790008544922, + "hdHeight": 182.6790008544922 + }, + "nnsu": { + "sdHeight": 209.01199340820312, + "hdHeight": 258.0069885253906 + }, + "nnsw": { + "sdHeight": 209.01199340820312, + "hdHeight": 209.01199340820312 + }, + "nnwa": { + "sdHeight": 134.50900268554688, + "hdHeight": 152.10800170898438 + }, + "nnwl": { + "sdHeight": 134.50900268554688, + "hdHeight": 144.625 + }, + "nnwq": { + "sdHeight": 134.50900268554688, + "hdHeight": 134.50900268554688 + }, + "nnwr": { + "sdHeight": 134.50900268554688, + "hdHeight": 208.55999755859375 + }, + "nnws": { + "sdHeight": 134.50900268554688, + "hdHeight": 134.50900268554688 + }, + "noga": { + "sdHeight": 135.02099609375, + "hdHeight": 135.02099609375 + }, + "nogl": { + "sdHeight": 135.03399658203125, + "hdHeight": 220.8159942626953 + }, + "nogm": { + "sdHeight": 135.03399658203125, + "hdHeight": 234.04600524902344 + }, + "nogn": { + "sdHeight": 135.03399658203125, + "hdHeight": 214.49000549316406 + }, + "nogo": { + "sdHeight": 135.02099609375, + "hdHeight": 135.02099609375 + }, + "nogr": { + "sdHeight": 135.03399658203125, + "hdHeight": 135.03399658203125 + }, + "nomg": { + "sdHeight": 135.03399658203125, + "hdHeight": 135.03399658203125 + }, + "nowb": { + "sdHeight": 177.2729949951172, + "hdHeight": 177.2729949951172 + }, + "nowe": { + "sdHeight": 177.2729949951172, + "hdHeight": 252.12100219726562 + }, + "nowk": { + "sdHeight": 177.2729949951172, + "hdHeight": 271.18798828125 + }, + "npfl": { + "sdHeight": 95.0698013305664, + "hdHeight": 90.62110137939453 + }, + "npfm": { + "sdHeight": 95.0698013305664, + "hdHeight": 95.0698013305664 + }, + "nplb": { + "sdHeight": 138.1790008544922, + "hdHeight": 138.1790008544922 + }, + "nplg": { + "sdHeight": 138.1790008544922, + "hdHeight": 213.68099975585938 + }, + "nqbh": { + "sdHeight": 118.50599670410156, + "hdHeight": 182.9199981689453 + }, + "nrdk": { + "sdHeight": 71.18779754638672, + "hdHeight": 71.18779754638672 + }, + "nrdr": { + "sdHeight": 103.26499938964844, + "hdHeight": 190.06900024414062 + }, + "nrel": { + "sdHeight": 200.61500549316406, + "hdHeight": 161.1009979248047 + }, + "nrog": { + "sdHeight": 96.46890258789062, + "hdHeight": 98.3561019897461 + }, + "nrvd": { + "sdHeight": 213.18800354003906, + "hdHeight": 271.7619934082031 + }, + "nrvf": { + "sdHeight": 213.18800354003906, + "hdHeight": 213.18800354003906 + }, + "nrvi": { + "sdHeight": 213.18800354003906, + "hdHeight": 268.32501220703125 + }, + "nrvl": { + "sdHeight": 213.18800354003906, + "hdHeight": 235.0959930419922 + }, + "nrvs": { + "sdHeight": 213.18800354003906, + "hdHeight": 166.23300170898438 + }, + "nrwm": { + "sdHeight": 103.26499938964844, + "hdHeight": 103.26499938964844 + }, + "nrzb": { + "sdHeight": 115.25700378417969, + "hdHeight": 172.73300170898438 + }, + "nrzg": { + "sdHeight": 115.25700378417969, + "hdHeight": 115.25700378417969 + }, + "nrzm": { + "sdHeight": 115.25700378417969, + "hdHeight": 146.1580047607422 + }, + "nrzs": { + "sdHeight": 115.25700378417969, + "hdHeight": 115.25700378417969 + }, + "nrzt": { + "sdHeight": 118.50599670410156, + "hdHeight": 118.50599670410156 + }, + "nsat": { + "sdHeight": 122.6989974975586, + "hdHeight": 122.6989974975586 + }, + "nsbm": { + "sdHeight": 73.54540252685547, + "hdHeight": 55.74760055541992 + }, + "nsbs": { + "sdHeight": 83.29499816894531, + "hdHeight": 83.29499816894531 + }, + "nsc2": { + "sdHeight": 81.51339721679688, + "hdHeight": 74.78980255126953 + }, + "nsc3": { + "sdHeight": 81.51339721679688, + "hdHeight": 107.90599822998047 + }, + "nscb": { + "sdHeight": 81.51339721679688, + "hdHeight": 81.51339721679688 + }, + "nsel": { + "sdHeight": 200.61500549316406, + "hdHeight": 200.61500549316406 + }, + "nsgb": { + "sdHeight": 237.4459991455078, + "hdHeight": 318.55499267578125 + }, + "nsgg": { + "sdHeight": 218.82400512695312, + "hdHeight": 298.177001953125 + }, + "nsgh": { + "sdHeight": 237.4459991455078, + "hdHeight": 237.4459991455078 + }, + "nsgn": { + "sdHeight": 237.4459991455078, + "hdHeight": 237.4459991455078 + }, + "nsgt": { + "sdHeight": 73.54540252685547, + "hdHeight": 56.092201232910156 + }, + "nska": { + "sdHeight": 112.71499633789062, + "hdHeight": 112.71499633789062 + }, + "nske": { + "sdHeight": 104.60099792480469, + "hdHeight": 104.60099792480469 + }, + "nsca": { + "sdHeight": 112.71499633789062, + "hdHeight": 112.71499633789062 + }, + "nsce": { + "sdHeight": 104.60099792480469, + "hdHeight": 104.60099792480469 + }, + "nskf": { + "sdHeight": 112.71499633789062, + "hdHeight": 149.6840057373047 + }, + "nskg": { + "sdHeight": 104.60099792480469, + "hdHeight": 130.05299377441406 + }, + "nskm": { + "sdHeight": 112.71499633789062, + "hdHeight": 160.97999572753906 + }, + "nsko": { + "sdHeight": 109.94400024414062, + "hdHeight": 109.94400024414062 + }, + "nslf": { + "sdHeight": 88.67449951171875, + "hdHeight": 167.37600708007812 + }, + "nslh": { + "sdHeight": 184.88499450683594, + "hdHeight": 131.6909942626953 + }, + "nsll": { + "sdHeight": 184.88499450683594, + "hdHeight": 184.88499450683594 + }, + "nslm": { + "sdHeight": 88.67449951171875, + "hdHeight": 88.67449951171875 + }, + "nsln": { + "sdHeight": 88.67449951171875, + "hdHeight": 201.177001953125 + }, + "nslr": { + "sdHeight": 184.88499450683594, + "hdHeight": 173.06100463867188 + }, + "nslv": { + "sdHeight": 184.88499450683594, + "hdHeight": 184.88499450683594 + }, + "nsnp": { + "sdHeight": 83.29499816894531, + "hdHeight": 83.29499816894531 + }, + "nsns": { + "sdHeight": 95.91210174560547, + "hdHeight": 181.65899658203125 + }, + "nsoc": { + "sdHeight": 109.94400024414062, + "hdHeight": 173.1280059814453 + }, + "nsog": { + "sdHeight": 109.94400024414062, + "hdHeight": 144.35699462890625 + }, + "nspb": { + "sdHeight": 73.54540252685547, + "hdHeight": 73.54540252685547 + }, + "nspd": { + "sdHeight": 134.50900268554688, + "hdHeight": 134.50900268554688 + }, + "nspg": { + "sdHeight": 73.54540252685547, + "hdHeight": 73.54540252685547 + }, + "nspp": { + "sdHeight": 90.6404037475586, + "hdHeight": 90.6404037475586 + }, + "nspr": { + "sdHeight": 73.54540252685547, + "hdHeight": 73.54540252685547 + }, + "nsqa": { + "sdHeight": 159.95399475097656, + "hdHeight": 277.27398681640625 + }, + "nsqe": { + "sdHeight": 159.95399475097656, + "hdHeight": 159.95399475097656 + }, + "nsqo": { + "sdHeight": 159.95399475097656, + "hdHeight": 219.86500549316406 + }, + "nsqt": { + "sdHeight": 159.95399475097656, + "hdHeight": 159.95399475097656 + }, + "nsra": { + "sdHeight": 119.90599822998047, + "hdHeight": 106.47699737548828 + }, + "nsrh": { + "sdHeight": 119.90599822998047, + "hdHeight": 120.11599731445312 + }, + "nsrn": { + "sdHeight": 119.90599822998047, + "hdHeight": 146.98199462890625 + }, + "nsrv": { + "sdHeight": 213.18800354003906, + "hdHeight": 281.8349914550781 + }, + "nsrw": { + "sdHeight": 119.90599822998047, + "hdHeight": 119.90599822998047 + }, + "nssp": { + "sdHeight": 73.54540252685547, + "hdHeight": 73.54540252685547 + }, + "nsth": { + "sdHeight": 217.11900329589844, + "hdHeight": 217.11900329589844 + }, + "nstl": { + "sdHeight": 107.80500030517578, + "hdHeight": 178.11700439453125 + }, + "nsts": { + "sdHeight": 122.6989974975586, + "hdHeight": 144.8719940185547 + }, + "nstw": { + "sdHeight": 184.88499450683594, + "hdHeight": 205.57200622558594 + }, + "nsty": { + "sdHeight": 107.80500030517578, + "hdHeight": 107.80500030517578 + }, + "nthl": { + "sdHeight": 184.88499450683594, + "hdHeight": 184.88499450683594 + }, + "ntka": { + "sdHeight": 139.3300018310547, + "hdHeight": 139.3300018310547 + }, + "ntkc": { + "sdHeight": 139.3300018310547, + "hdHeight": 145.21499633789062 + }, + "ntkf": { + "sdHeight": 139.3300018310547, + "hdHeight": 139.3300018310547 + }, + "ntkh": { + "sdHeight": 139.3300018310547, + "hdHeight": 102.02200317382812 + }, + "ntks": { + "sdHeight": 139.3300018310547, + "hdHeight": 149.36500549316406 + }, + "ntkt": { + "sdHeight": 139.3300018310547, + "hdHeight": 149.30599975585938 + }, + "ntkw": { + "sdHeight": 139.3300018310547, + "hdHeight": 139.3300018310547 + }, + "ntor": { + "sdHeight": 442.4330139160156, + "hdHeight": 442.4330139160156 + }, + "ntrd": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "ntrg": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "ntrh": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "ntrs": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "ntrt": { + "sdHeight": 180.96600341796875, + "hdHeight": 180.96600341796875 + }, + "ntrv": { + "sdHeight": 213.18800354003906, + "hdHeight": 303.3070068359375 + }, + "ntws": { + "sdHeight": 94.36869812011719, + "hdHeight": 127.60299682617188 + }, + "nubk": { + "sdHeight": 225.2169952392578, + "hdHeight": 135.2030029296875 + }, + "nubr": { + "sdHeight": 225.2169952392578, + "hdHeight": 144.97000122070312 + }, + "nubw": { + "sdHeight": 225.2169952392578, + "hdHeight": 166.5019989013672 + }, + "nvde": { + "sdHeight": 192.05999755859375, + "hdHeight": 177.91299438476562 + }, + "nvdg": { + "sdHeight": 192.05999755859375, + "hdHeight": 144.30099487304688 + }, + "nvdl": { + "sdHeight": 192.05999755859375, + "hdHeight": 70.13770294189453 + }, + "nvdw": { + "sdHeight": 192.05999755859375, + "hdHeight": 192.05999755859375 + }, + "nwen": { + "sdHeight": 159.95399475097656, + "hdHeight": 159.95399475097656 + }, + "nwgs": { + "sdHeight": 182.41099548339844, + "hdHeight": 182.41099548339844 + }, + "nwiz": { + "sdHeight": 123.03199768066406, + "hdHeight": 123.03199768066406 + }, + "nwld": { + "sdHeight": 111.73300170898438, + "hdHeight": 111.73300170898438 + }, + "nwlg": { + "sdHeight": 111.73300170898438, + "hdHeight": 102.49700164794922 + }, + "nwlt": { + "sdHeight": 111.73300170898438, + "hdHeight": 111.73300170898438 + }, + "nwna": { + "sdHeight": 159.95399475097656, + "hdHeight": 279.885009765625 + }, + "nwnr": { + "sdHeight": 159.95399475097656, + "hdHeight": 230.07699584960938 + }, + "nwns": { + "sdHeight": 159.95399475097656, + "hdHeight": 159.95399475097656 + }, + "nwrg": { + "sdHeight": 218.82400512695312, + "hdHeight": 267.2149963378906 + }, + "nws1": { + "sdHeight": 85.96379852294922, + "hdHeight": 85.96379852294922 + }, + "nwwd": { + "sdHeight": 111.73300170898438, + "hdHeight": 103.80400085449219 + }, + "nwwf": { + "sdHeight": 111.73300170898438, + "hdHeight": 111.73300170898438 + }, + "nwwg": { + "sdHeight": 111.73300170898438, + "hdHeight": 103.80400085449219 + }, + "nwzd": { + "sdHeight": 176.65499877929688, + "hdHeight": 176.65499877929688 + }, + "nwzg": { + "sdHeight": 176.65499877929688, + "hdHeight": 163.79299926757812 + }, + "nwzr": { + "sdHeight": 176.65499877929688, + "hdHeight": 153.71299743652344 + }, + "nzep": { + "sdHeight": 84.63569641113281, + "hdHeight": 84.63569641113281 + }, + "nzom": { + "sdHeight": 98.47029876708984, + "hdHeight": 98.47029876708984 + }, + "nzof": { + "sdHeight": 98.47029876708984, + "hdHeight": 79.2322006225586 + }, + "nchp": { + "sdHeight": 123.03199768066406, + "hdHeight": 138.66200256347656 + }, + "nhym": { + "sdHeight": 176.65499877929688, + "hdHeight": 180.07699584960938 + }, + "nalb": { + "sdHeight": 44.68170166015625, + "hdHeight": 44.68170166015625 + }, + "nfro": { + "sdHeight": 54.56589889526367, + "hdHeight": 54.56589889526367 + }, + "nech": { + "sdHeight": 45.29249954223633, + "hdHeight": 45.29249954223633 + }, + "necr": { + "sdHeight": 33.45029830932617, + "hdHeight": 33.45029830932617 + }, + "nrac": { + "sdHeight": 50.06269836425781, + "hdHeight": 50.06269836425781 + }, + "ncrb": { + "sdHeight": 39.400299072265625, + "hdHeight": 39.400299072265625 + }, + "nder": { + "sdHeight": 87.34700012207031, + "hdHeight": 87.34700012207031 + }, + "ndog": { + "sdHeight": 48.16910171508789, + "hdHeight": 48.16910171508789 + }, + "ndwm": { + "sdHeight": 51.153099060058594, + "hdHeight": 51.153099060058594 + }, + "nfbr": { + "sdHeight": 45.495399475097656, + "hdHeight": 45.495399475097656 + }, + "nhmc": { + "sdHeight": 34.04899978637695, + "hdHeight": 34.04899978637695 + }, + "now2": { + "sdHeight": 112.16899871826172, + "hdHeight": 121.97699737548828 + }, + "now3": { + "sdHeight": 112.16899871826172, + "hdHeight": 134.6649932861328 + }, + "nowl": { + "sdHeight": 112.16899871826172, + "hdHeight": 112.16899871826172 + }, + "npig": { + "sdHeight": 45.495399475097656, + "hdHeight": 45.495399475097656 + }, + "npng": { + "sdHeight": 58.916900634765625, + "hdHeight": 58.916900634765625 + }, + "npnw": { + "sdHeight": 58.916900634765625, + "hdHeight": 58.916900634765625 + }, + "nrat": { + "sdHeight": 25.599000930786133, + "hdHeight": 25.599000930786133 + }, + "nsea": { + "sdHeight": 32.531700134277344, + "hdHeight": 32.531700134277344 + }, + "nsha": { + "sdHeight": 37.00149917602539, + "hdHeight": 37.00149917602539 + }, + "nshe": { + "sdHeight": 37.00149917602539, + "hdHeight": 37.00149917602539 + }, + "nshf": { + "sdHeight": 40.563201904296875, + "hdHeight": 40.563201904296875 + }, + "nshw": { + "sdHeight": 37.00149917602539, + "hdHeight": 37.00149917602539 + }, + "nskk": { + "sdHeight": 21.521099090576172, + "hdHeight": 21.521099090576172 + }, + "nsno": { + "sdHeight": 45.4281005859375, + "hdHeight": 45.4281005859375 + }, + "nvil": { + "sdHeight": 93.36119842529297, + "hdHeight": 93.36119842529297 + }, + "nvk2": { + "sdHeight": 60.81800079345703, + "hdHeight": 60.81800079345703 + }, + "nvl2": { + "sdHeight": 93.36119842529297, + "hdHeight": 93.36119842529297 + }, + "nvlk": { + "sdHeight": 60.81800079345703, + "hdHeight": 60.81800079345703 + }, + "nvlw": { + "sdHeight": 101.90499877929688, + "hdHeight": 101.90499877929688 + }, + "nvul": { + "sdHeight": 50.250301361083984, + "hdHeight": 50.250301361083984 + }, + "ncb0": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb1": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb2": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb3": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb4": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb5": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb6": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb7": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb8": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncb9": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncba": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncbb": { + "sdHeight": 365.48199462890625, + "hdHeight": 365.48199462890625 + }, + "ncbc": { + "sdHeight": 386.39300537109375, + "hdHeight": 386.39300537109375 + }, + "ncbd": { + "sdHeight": 386.39300537109375, + "hdHeight": 386.39300537109375 + }, + "ncbe": { + "sdHeight": 386.39300537109375, + "hdHeight": 386.39300537109375 + }, + "ncbf": { + "sdHeight": 386.39300537109375, + "hdHeight": 386.39300537109375 + }, + "ncnt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ncop": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ncp2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ncp3": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nct1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nct2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ndch": { + "sdHeight": 336.9540100097656, + "hdHeight": 336.9540100097656 + }, + "ndh0": { + "sdHeight": 170.427001953125, + "hdHeight": 170.427001953125 + }, + "ndh1": { + "sdHeight": 163.96800231933594, + "hdHeight": 163.96800231933594 + }, + "ndh2": { + "sdHeight": 336.9540100097656, + "hdHeight": 286.84698486328125 + }, + "ndh3": { + "sdHeight": 170.427001953125, + "hdHeight": 255.01199340820312 + }, + "ndh4": { + "sdHeight": 163.96800231933594, + "hdHeight": 229.1699981689453 + }, + "ndrg": { + "sdHeight": 249.55799865722656, + "hdHeight": 249.55799865722656 + }, + "ndrk": { + "sdHeight": 249.55799865722656, + "hdHeight": 249.55799865722656 + }, + "ndro": { + "sdHeight": 249.55799865722656, + "hdHeight": 215.6959991455078 + }, + "ndrr": { + "sdHeight": 249.55799865722656, + "hdHeight": 232.1479949951172 + }, + "ndru": { + "sdHeight": 249.55799865722656, + "hdHeight": 249.55799865722656 + }, + "ndrz": { + "sdHeight": 249.55799865722656, + "hdHeight": 249.55799865722656 + }, + "nfh0": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfh1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfoh": { + "sdHeight": 247.03399658203125, + "hdHeight": 247.03399658203125 + }, + "nfr1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfr2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngad": { + "sdHeight": 255.3520050048828, + "hdHeight": 255.3520050048828 + }, + "ngme": { + "sdHeight": 308.50299072265625, + "hdHeight": 308.50299072265625 + }, + "ngnh": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngni": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngol": { + "sdHeight": 152.9040069580078, + "hdHeight": 152.9040069580078 + }, + "ngt2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ngwr": { + "sdHeight": 247.4739990234375, + "hdHeight": 247.4739990234375 + }, + "nhns": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nhn2": { + "sdHeight": -1, + "hdHeight": 276.29400634765625 + }, + "nmer": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmg0": { + "sdHeight": -1, + "hdHeight": 251.1479949951172 + }, + "nmg1": { + "sdHeight": -1, + "hdHeight": 286.5 + }, + "nmh0": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nmh1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nmh2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nmoo": { + "sdHeight": 247.03399658203125, + "hdHeight": 247.03399658203125 + }, + "nmr0": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr2": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr3": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr4": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr5": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr6": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr7": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr8": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmr9": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmra": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmrb": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmrc": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmrd": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmre": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmrf": { + "sdHeight": 397.0069885253906, + "hdHeight": 397.0069885253906 + }, + "nmrk": { + "sdHeight": 233.531005859375, + "hdHeight": 233.531005859375 + }, + "nnzg": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nshp": { + "sdHeight": 203.03599548339844, + "hdHeight": 203.03599548339844 + }, + "ntav": { + "sdHeight": 196.9770050048828, + "hdHeight": 196.9770050048828 + }, + "nten": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nth0": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nth1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ntn2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ntn3": { + "sdHeight": -1, + "hdHeight": 157.1219940185547 + }, + "ntnt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ntt2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nwgt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Ecen": { + "sdHeight": 233.01800537109375, + "hdHeight": 311.87799072265625 + }, + "Eevi": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Eevm": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Efur": { + "sdHeight": 258.5559997558594, + "hdHeight": 258.5559997558594 + }, + "Eidm": { + "sdHeight": -1, + "hdHeight": 143.56500244140625 + }, + "Eill": { + "sdHeight": -1, + "hdHeight": 143.56500244140625 + }, + "Eilm": { + "sdHeight": -1, + "hdHeight": 143.56500244140625 + }, + "Ekgg": { + "sdHeight": 233.01800537109375, + "hdHeight": 233.01800537109375 + }, + "Emfr": { + "sdHeight": 258.5559997558594, + "hdHeight": 258.5559997558594 + }, + "Emns": { + "sdHeight": 258.5559997558594, + "hdHeight": 258.5559997558594 + }, + "Etyr": { + "sdHeight": 150.01600646972656, + "hdHeight": 144.2899932861328 + }, + "Ewrd": { + "sdHeight": 160.3719940185547, + "hdHeight": 160.3719940185547 + }, + "Hant": { + "sdHeight": 232.6280059814453, + "hdHeight": 170.7239990234375 + }, + "Hapm": { + "sdHeight": 131.6649932861328, + "hdHeight": 131.6649932861328 + }, + "Harf": { + "sdHeight": 127.76200103759766, + "hdHeight": 127.76200103759766 + }, + "Hart": { + "sdHeight": 118.822998046875, + "hdHeight": 118.822998046875 + }, + "Hdgo": { + "sdHeight": 109.90699768066406, + "hdHeight": 110.44300079345703 + }, + "Hhkl": { + "sdHeight": 109.90699768066406, + "hdHeight": 110.58699798583984 + }, + "Hjai": { + "sdHeight": 119.58300018310547, + "hdHeight": 119.58300018310547 + }, + "Hkal": { + "sdHeight": 194.1719970703125, + "hdHeight": 194.1719970703125 + }, + "Hlgr": { + "sdHeight": 164.33099365234375, + "hdHeight": 164.33099365234375 + }, + "Hmbr": { + "sdHeight": 98.85130310058594, + "hdHeight": 98.85130310058594 + }, + "Hmgd": { + "sdHeight": 114.3030014038086, + "hdHeight": 110.74099731445312 + }, + "Hpb1": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Hpb2": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Huth": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Hvsh": { + "sdHeight": 166.56300354003906, + "hdHeight": 166.56300354003906 + }, + "Hvwd": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "Nbbc": { + "sdHeight": 197.85499572753906, + "hdHeight": 197.85499572753906 + }, + "Nklj": { + "sdHeight": 195.2169952392578, + "hdHeight": 195.2169952392578 + }, + "Nkjx": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Nmag": { + "sdHeight": 223.1909942626953, + "hdHeight": 349.1109924316406 + }, + "Nman": { + "sdHeight": 173.5590057373047, + "hdHeight": 173.5590057373047 + }, + "Npld": { + "sdHeight": 173.5590057373047, + "hdHeight": 173.5590057373047 + }, + "Nsjs": { + "sdHeight": 258.4989929199219, + "hdHeight": 168.61300659179688 + }, + "Ocbh": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Ocb2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Odrt": { + "sdHeight": 230.72799682617188, + "hdHeight": 154.89500427246094 + }, + "Ogld": { + "sdHeight": 137.03900146484375, + "hdHeight": 137.03900146484375 + }, + "Ogrh": { + "sdHeight": 202.73899841308594, + "hdHeight": 202.73899841308594 + }, + "Opgh": { + "sdHeight": 202.73899841308594, + "hdHeight": 202.73899841308594 + }, + "Orex": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "Orkn": { + "sdHeight": 214.89100646972656, + "hdHeight": 154.5189971923828 + }, + "Osam": { + "sdHeight": 197.85499572753906, + "hdHeight": 270.4320068359375 + }, + "Otcc": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Othr": { + "sdHeight": 173.11199951171875, + "hdHeight": 173.11199951171875 + }, + "Oths": { + "sdHeight": 173.11199951171875, + "hdHeight": 173.11199951171875 + }, + "Uanb": { + "sdHeight": 156.9709930419922, + "hdHeight": 156.9709930419922 + }, + "Ubal": { + "sdHeight": 161.95799255371094, + "hdHeight": 248.61300659179688 + }, + "Uclc": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Udth": { + "sdHeight": 161.95799255371094, + "hdHeight": 193.94500732421875 + }, + "Uear": { + "sdHeight": 154.08700561523438, + "hdHeight": 154.08700561523438 + }, + "Uktl": { + "sdHeight": 177.53500366210938, + "hdHeight": 185.23599243164062 + }, + "Umal": { + "sdHeight": 161.95799255371094, + "hdHeight": 155.6649932861328 + }, + "Usyl": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "Utic": { + "sdHeight": 161.95799255371094, + "hdHeight": 161.95799255371094 + }, + "Uvar": { + "sdHeight": 161.95799255371094, + "hdHeight": 192.927001953125 + }, + "Uvng": { + "sdHeight": 161.95799255371094, + "hdHeight": 184.697998046875 + }, + "Uwar": { + "sdHeight": 133.98599243164062, + "hdHeight": 133.98599243164062 + }, + "Hgam": { + "sdHeight": 232.6280059814453, + "hdHeight": 232.6280059814453 + }, + "eilw": { + "sdHeight": 135.29100036621094, + "hdHeight": 135.29100036621094 + }, + "enec": { + "sdHeight": 106.58200073242188, + "hdHeight": 106.58200073242188 + }, + "ensh": { + "sdHeight": 125.62999725341797, + "hdHeight": 138.9770050048828 + }, + "eshd": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "etrs": { + "sdHeight": 277.5979919433594, + "hdHeight": 277.5979919433594 + }, + "hbew": { + "sdHeight": 164.06500244140625, + "hdHeight": 164.06500244140625 + }, + "hcth": { + "sdHeight": 109.6969985961914, + "hdHeight": 109.6969985961914 + }, + "hhdl": { + "sdHeight": 120.50900268554688, + "hdHeight": 120.50900268554688 + }, + "hhes": { + "sdHeight": 109.6969985961914, + "hdHeight": 105.00900268554688 + }, + "hrdh": { + "sdHeight": 104.1510009765625, + "hdHeight": 104.1510009765625 + }, + "Naka": { + "sdHeight": 134.04800415039062, + "hdHeight": 134.04800415039062 + }, + "nsw1": { + "sdHeight": 95.0698013305664, + "hdHeight": 115.38099670410156 + }, + "nsw2": { + "sdHeight": 95.0698013305664, + "hdHeight": 172.09100341796875 + }, + "nsw3": { + "sdHeight": 95.0698013305664, + "hdHeight": 247.17999267578125 + }, + "ncat": { + "sdHeight": 160.31100463867188, + "hdHeight": 169.9969940185547 + }, + "nbee": { + "sdHeight": 96.47899627685547, + "hdHeight": 101.6449966430664 + }, + "nbel": { + "sdHeight": 109.6969985961914, + "hdHeight": 109.6969985961914 + }, + "nbsp": { + "sdHeight": 409.67999267578125, + "hdHeight": 409.67999267578125 + }, + "nchg": { + "sdHeight": 100.63200378417969, + "hdHeight": 100.63200378417969 + }, + "nchr": { + "sdHeight": 172.1739959716797, + "hdHeight": 172.1739959716797 + }, + "nchw": { + "sdHeight": 115.23799896240234, + "hdHeight": 115.23799896240234 + }, + "nckb": { + "sdHeight": 169.3769989013672, + "hdHeight": 169.3769989013672 + }, + "ncpn": { + "sdHeight": 87.60700225830078, + "hdHeight": 87.60700225830078 + }, + "ndmu": { + "sdHeight": 98.47029876708984, + "hdHeight": 98.47029876708984 + }, + "ndrd": { + "sdHeight": 134.04800415039062, + "hdHeight": 153.03799438476562 + }, + "ndrn": { + "sdHeight": 134.04800415039062, + "hdHeight": 122.91699981689453 + }, + "ndrt": { + "sdHeight": 134.04800415039062, + "hdHeight": 122.63099670410156 + }, + "ndrf": { + "sdHeight": 134.04800415039062, + "hdHeight": 110.35700225830078 + }, + "ndrh": { + "sdHeight": 166.7689971923828, + "hdHeight": 168.6649932861328 + }, + "ndrj": { + "sdHeight": 88.67449951171875, + "hdHeight": 88.67449951171875 + }, + "ndrm": { + "sdHeight": 166.7689971923828, + "hdHeight": 166.7689971923828 + }, + "ndrp": { + "sdHeight": 134.04800415039062, + "hdHeight": 134.04800415039062 + }, + "ndrs": { + "sdHeight": 166.7689971923828, + "hdHeight": 180.81199645996094 + }, + "ndrw": { + "sdHeight": 134.04800415039062, + "hdHeight": 128.92999267578125 + }, + "ndrl": { + "sdHeight": 134.04800415039062, + "hdHeight": 134.04800415039062 + }, + "ndsa": { + "sdHeight": 184.88499450683594, + "hdHeight": 184.88499450683594 + }, + "negz": { + "sdHeight": -1, + "hdHeight": 153.79299926757812 + }, + "nemi": { + "sdHeight": 123.03199768066406, + "hdHeight": 141.41400146484375 + }, + "nfgl": { + "sdHeight": 223.82899475097656, + "hdHeight": 223.82899475097656 + }, + "ngbl": { + "sdHeight": 151.92799377441406, + "hdHeight": -1 + }, + "nhea": { + "sdHeight": 93.87740325927734, + "hdHeight": 93.87740325927734 + }, + "nhef": { + "sdHeight": 101.90499877929688, + "hdHeight": 101.90499877929688 + }, + "nhem": { + "sdHeight": 96.47899627685547, + "hdHeight": 102.7239990234375 + }, + "nhew": { + "sdHeight": 96.47899627685547, + "hdHeight": 96.47899627685547 + }, + "njks": { + "sdHeight": 96.46890258789062, + "hdHeight": 123.68699645996094 + }, + "nmdm": { + "sdHeight": 144.36099243164062, + "hdHeight": 144.36099243164062 + }, + "nmed": { + "sdHeight": 144.36099243164062, + "hdHeight": 144.36099243164062 + }, + "nmpe": { + "sdHeight": 82.98560333251953, + "hdHeight": 82.98560333251953 + }, + "nmsh": { + "sdHeight": 138.1790008544922, + "hdHeight": 109.93599700927734 + }, + "nser": { + "sdHeight": 103.1719970703125, + "hdHeight": 178.89500427246094 + }, + "nspc": { + "sdHeight": 336.6919860839844, + "hdHeight": 336.6919860839844 + }, + "nssn": { + "sdHeight": 113.55599975585938, + "hdHeight": 113.55599975585938 + }, + "nthr": { + "sdHeight": 103.1719970703125, + "hdHeight": 176.4340057373047 + }, + "nw2w": { + "sdHeight": 115.23799896240234, + "hdHeight": 115.23799896240234 + }, + "nwat": { + "sdHeight": 106.58200073242188, + "hdHeight": 106.58200073242188 + }, + "odkt": { + "sdHeight": 119.90599822998047, + "hdHeight": 119.90599822998047 + }, + "ogrk": { + "sdHeight": 100.63200378417969, + "hdHeight": 134.3179931640625 + }, + "ojgn": { + "sdHeight": 153.2169952392578, + "hdHeight": 153.2169952392578 + }, + "omtg": { + "sdHeight": 146.2209930419922, + "hdHeight": 174.5540008544922 + }, + "onzg": { + "sdHeight": 172.1739959716797, + "hdHeight": 167.36500549316406 + }, + "oosc": { + "sdHeight": 159.00100708007812, + "hdHeight": 159.00100708007812 + }, + "oswy": { + "sdHeight": 138.55599975585938, + "hdHeight": 138.55599975585938 + }, + "ovlj": { + "sdHeight": 170.6699981689453, + "hdHeight": 145.18099975585938 + }, + "owar": { + "sdHeight": 146.2209930419922, + "hdHeight": 146.2209930419922 + }, + "ownr": { + "sdHeight": 88.07219696044922, + "hdHeight": 88.07219696044922 + }, + "uabc": { + "sdHeight": -1, + "hdHeight": -1 + }, + "uarb": { + "sdHeight": 142.85699462890625, + "hdHeight": 142.85699462890625 + }, + "ubdd": { + "sdHeight": 122.80400085449219, + "hdHeight": 374.4909973144531 + }, + "ubdr": { + "sdHeight": 103.1719970703125, + "hdHeight": 377.177001953125 + }, + "ubot": { + "sdHeight": 98.6801986694336, + "hdHeight": 98.6801986694336 + }, + "udes": { + "sdHeight": 250.781005859375, + "hdHeight": 250.781005859375 + }, + "uktg": { + "sdHeight": -1, + "hdHeight": -1 + }, + "uktn": { + "sdHeight": 130.57899475097656, + "hdHeight": 130.57899475097656 + }, + "uswb": { + "sdHeight": 115.72599792480469, + "hdHeight": 145.8769989013672 + }, + "hprt": { + "sdHeight": 526.68798828125, + "hdHeight": 526.68798828125 + }, + "haro": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nbfl": { + "sdHeight": 233.88299560546875, + "hdHeight": 233.88299560546875 + }, + "nbsm": { + "sdHeight": 202.40199279785156, + "hdHeight": 202.40199279785156 + }, + "nbt1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nbt2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nbwd": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ncap": { + "sdHeight": 278.68701171875, + "hdHeight": 278.68701171875 + }, + "ncaw": { + "sdHeight": 246.90199279785156, + "hdHeight": 246.90199279785156 + }, + "ncmw": { + "sdHeight": 180.03199768066406, + "hdHeight": 180.03199768066406 + }, + "ncta": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "ncte": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "nctl": { + "sdHeight": 264.47698974609375, + "hdHeight": 264.47698974609375 + }, + "ndfl": { + "sdHeight": 233.46400451660156, + "hdHeight": 233.46400451660156 + }, + "ndgt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ndke": { + "sdHeight": 467.7489929199219, + "hdHeight": 467.7489929199219 + }, + "ndkw": { + "sdHeight": 467.7489929199219, + "hdHeight": 467.7489929199219 + }, + "ndmg": { + "sdHeight": 379.52099609375, + "hdHeight": 379.52099609375 + }, + "ndrb": { + "sdHeight": 249.55799865722656, + "hdHeight": 249.55799865722656 + }, + "ndt1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ndt2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nef0": { + "sdHeight": 422.4519958496094, + "hdHeight": 422.4519958496094 + }, + "nef1": { + "sdHeight": 520.4630126953125, + "hdHeight": 520.4630126953125 + }, + "nef2": { + "sdHeight": 422.4519958496094, + "hdHeight": 422.4519958496094 + }, + "nef3": { + "sdHeight": 520.4630126953125, + "hdHeight": 520.4630126953125 + }, + "nef4": { + "sdHeight": 422.4519958496094, + "hdHeight": 422.4519958496094 + }, + "nef5": { + "sdHeight": 520.4630126953125, + "hdHeight": 520.4630126953125 + }, + "nef6": { + "sdHeight": 422.4519958496094, + "hdHeight": 422.4519958496094 + }, + "nef7": { + "sdHeight": 520.4630126953125, + "hdHeight": 520.4630126953125 + }, + "nefm": { + "sdHeight": 199.5469970703125, + "hdHeight": 199.5469970703125 + }, + "negf": { + "sdHeight": -1, + "hdHeight": -1 + }, + "negm": { + "sdHeight": -1, + "hdHeight": -1 + }, + "negt": { + "sdHeight": -1, + "hdHeight": -1 + }, + "net1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "net2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfnp": { + "sdHeight": 233.88299560546875, + "hdHeight": 233.88299560546875 + }, + "nfrm": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfrt": { + "sdHeight": 212.36399841308594, + "hdHeight": 212.36399841308594 + }, + "nft1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nft2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nfv0": { + "sdHeight": 394.8599853515625, + "hdHeight": 394.8599853515625 + }, + "nfv1": { + "sdHeight": 523.8300170898438, + "hdHeight": 523.8300170898438 + }, + "nfv2": { + "sdHeight": 192.91000366210938, + "hdHeight": 192.91000366210938 + }, + "nfv3": { + "sdHeight": 394.8599853515625, + "hdHeight": 394.8599853515625 + }, + "nfv4": { + "sdHeight": 523.8300170898438, + "hdHeight": 523.8300170898438 + }, + "ngob": { + "sdHeight": 285.45001220703125, + "hdHeight": 285.45001220703125 + }, + "nhcn": { + "sdHeight": 226.0290069580078, + "hdHeight": 226.0290069580078 + }, + "nheb": { + "sdHeight": 239.2169952392578, + "hdHeight": 239.2169952392578 + }, + "nico": { + "sdHeight": 481.3380126953125, + "hdHeight": 481.3380126953125 + }, + "nitb": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nmgv": { + "sdHeight": 175.39599609375, + "hdHeight": 175.39599609375 + }, + "nnad": { + "sdHeight": 201.31500244140625, + "hdHeight": 201.31500244140625 + }, + "nnfm": { + "sdHeight": 220.0189971923828, + "hdHeight": 220.0189971923828 + }, + "nnsa": { + "sdHeight": 353.9809875488281, + "hdHeight": 353.9809875488281 + }, + "nnsg": { + "sdHeight": 162.2469940185547, + "hdHeight": 162.2469940185547 + }, + "nntg": { + "sdHeight": 127.99700164794922, + "hdHeight": 127.99700164794922 + }, + "nntt": { + "sdHeight": 373.8240051269531, + "hdHeight": 373.8240051269531 + }, + "npgf": { + "sdHeight": -1, + "hdHeight": -1 + }, + "npgr": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nshr": { + "sdHeight": 236.51600646972656, + "hdHeight": 236.51600646972656 + }, + "ntt1": { + "sdHeight": -1, + "hdHeight": -1 + }, + "ntx2": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nvr0": { + "sdHeight": 394.8599853515625, + "hdHeight": 394.8599853515625 + }, + "nvr1": { + "sdHeight": 523.8300170898438, + "hdHeight": 523.8300170898438 + }, + "nvr2": { + "sdHeight": 189.0850067138672, + "hdHeight": 189.0850067138672 + }, + "nwc1": { + "sdHeight": 161.1199951171875, + "hdHeight": 161.1199951171875 + }, + "nwc2": { + "sdHeight": 161.1199951171875, + "hdHeight": 161.1199951171875 + }, + "nwc3": { + "sdHeight": 161.1199951171875, + "hdHeight": 223.13400268554688 + }, + "nwc4": { + "sdHeight": 161.1199951171875, + "hdHeight": 237.63499450683594 + }, + "ocbw": { + "sdHeight": -1, + "hdHeight": 113.30000305175781 + }, + "nzin": { + "sdHeight": -1, + "hdHeight": -1 + }, + "nbse": { + "sdHeight": 288.510009765625, + "hdHeight": 288.510009765625 + }, + "nbsw": { + "sdHeight": 288.510009765625, + "hdHeight": 288.510009765625 + }, + "zcso": { + "sdHeight": -1, + "hdHeight": -1 + }, + "zhyd": { + "sdHeight": 189.7830047607422, + "hdHeight": 189.7830047607422 + }, + "zjug": { + "sdHeight": 153.2169952392578, + "hdHeight": 153.2169952392578 + }, + "zmar": { + "sdHeight": -1, + "hdHeight": -1 + }, + "zshv": { + "sdHeight": 72.85359954833984, + "hdHeight": 72.85359954833984 + }, + "zsmc": { + "sdHeight": 88.2083969116211, + "hdHeight": 88.2083969116211 + }, + "zzrg": { + "sdHeight": 64.0374984741211, + "hdHeight": 64.0374984741211 + }, + "nzlc": { + "sdHeight": 489.5790100097656, + "hdHeight": 489.5790100097656 + }, + "Nmsr": { + "sdHeight": 79.55819702148438, + "hdHeight": 181.65899658203125 + }, + "Nswt": { + "sdHeight": 166.56300354003906, + "hdHeight": 166.56300354003906 + }, + "nggm": { + "sdHeight": 210.2790069580078, + "hdHeight": 270.6549987792969 + }, + "nggg": { + "sdHeight": 210.2790069580078, + "hdHeight": 283.3080139160156 + }, + "nggd": { + "sdHeight": 210.2790069580078, + "hdHeight": 228.95199584960938 + }, + "ngow": { + "sdHeight": 113.99299621582031, + "hdHeight": 113.99299621582031 + }, + "nwzw": { + "sdHeight": 176.65499877929688, + "hdHeight": 183.6999969482422 + }, + "ngos": { + "sdHeight": 113.99299621582031, + "hdHeight": 173.552001953125 + }, + "ngog": { + "sdHeight": -1, + "hdHeight": 59.104400634765625 + }, + "nwar": { + "sdHeight": 111.35600280761719, + "hdHeight": 139.70799255371094 + }, + "nccd": { + "sdHeight": 204.46400451660156, + "hdHeight": 204.46400451660156 + }, + "ncco": { + "sdHeight": 151.64700317382812, + "hdHeight": 151.64700317382812 + }, + "nccu": { + "sdHeight": 141.96099853515625, + "hdHeight": 141.96099853515625 + }, + "nccr": { + "sdHeight": 149.9600067138672, + "hdHeight": 149.9600067138672 + }, + "Hjnd": { + "sdHeight": 110.50800323486328, + "hdHeight": 116.0009994506836 + }, + "nmg2": { + "sdHeight": -1, + "hdHeight": 326.73699951171875 + }, + "obai": { + "sdHeight": 145.9770050048828, + "hdHeight": 145.9770050048828 + }, + "hrrh": { + "sdHeight": 96.47899627685547, + "hdHeight": 106.697998046875 + }, + "Haah": { + "sdHeight": 232.6280059814453, + "hdHeight": 175.7030029296875 + }, + "Hssa": { + "sdHeight": 165.5279998779297, + "hdHeight": 112.37000274658203 + }, + "Hddt": { + "sdHeight": 109.6969985961914, + "hdHeight": 119.7760009765625 + }, + "owad": { + "sdHeight": 146.2209930419922, + "hdHeight": 161.53199768066406 + }, + "Hjas": { + "sdHeight": -1, + "hdHeight": 152.25999450683594 + }, + "Ofth": { + "sdHeight": -1, + "hdHeight": 161.02099609375 + }, + "Uart": { + "sdHeight": -1, + "hdHeight": 161.4080047607422 + }, + "Ekce": { + "sdHeight": -1, + "hdHeight": 183.58900451660156 + }, + "Udef": { + "sdHeight": 161.89300537109375, + "hdHeight": 174.33599853515625 + }, + "Edef": { + "sdHeight": -1, + "hdHeight": 130.156005859375 + }, + "Ewam": { + "sdHeight": 160.3719940185547, + "hdHeight": 160.3719940185547 + }, + "Edei": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Emot": { + "sdHeight": -1, + "hdHeight": 144.2899932861328 + }, + "Ekec": { + "sdHeight": -1, + "hdHeight": 311.87799072265625 + }, + "Ekem": { + "sdHeight": 258.5559997558594, + "hdHeight": 258.5559997558594 + }, + "Ulik": { + "sdHeight": -1, + "hdHeight": -1 + }, + "Udrb": { + "sdHeight": 161.95799255371094, + "hdHeight": 248.61300659179688 + }, + "Udda": { + "sdHeight": -1, + "hdHeight": 184.697998046875 + }, + "Udde": { + "sdHeight": -1, + "hdHeight": 193.94500732421875 + }, + "Udrm": { + "sdHeight": -1, + "hdHeight": 155.6649932861328 + }, + "Udrt": { + "sdHeight": 161.95799255371094, + "hdHeight": 161.95799255371094 + }, + "Udrv": { + "sdHeight": -1, + "hdHeight": 192.927001953125 + }, + "Ucra": { + "sdHeight": 156.9709930419922, + "hdHeight": 156.9709930419922 + }, + "Hpap": { + "sdHeight": 131.6649932861328, + "hdHeight": 131.6649932861328 + }, + "Hpaa": { + "sdHeight": 118.822998046875, + "hdHeight": 118.822998046875 + }, + "Hpaf": { + "sdHeight": 127.76200103759766, + "hdHeight": 127.76200103759766 + }, + "Hpdo": { + "sdHeight": 109.90699768066406, + "hdHeight": 110.44300079345703 + }, + "Hphl": { + "sdHeight": 109.90699768066406, + "hdHeight": 110.58699798583984 + }, + "Hpnb": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Hpmd": { + "sdHeight": -1, + "hdHeight": 110.74099731445312 + }, + "Hpge": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Hpau": { + "sdHeight": 109.90699768066406, + "hdHeight": 109.90699768066406 + }, + "Haks": { + "sdHeight": -1, + "hdHeight": 175.7030029296875 + }, + "Haan": { + "sdHeight": -1, + "hdHeight": 170.7239990234375 + }, + "Hmmb": { + "sdHeight": 98.85130310058594, + "hdHeight": 98.85130310058594 + }, + "Hblk": { + "sdHeight": 194.1719970703125, + "hdHeight": 194.1719970703125 + }, + "Ofad": { + "sdHeight": -1, + "hdHeight": 154.89500427246094 + }, + "Ofat": { + "sdHeight": 173.11199951171875, + "hdHeight": 173.11199951171875 + }, + "Obbc": { + "sdHeight": 197.85499572753906, + "hdHeight": 197.85499572753906 + }, + "Oblh": { + "sdHeight": 202.73899841308594, + "hdHeight": 202.73899841308594 + }, + "Obhp": { + "sdHeight": 202.73899841308594, + "hdHeight": 202.73899841308594 + }, + "Obls": { + "sdHeight": -1, + "hdHeight": 270.4320068359375 + }, + "Otcb": { + "sdHeight": -1, + "hdHeight": 206.60800170898438 + }, + "Oshr": { + "sdHeight": -1, + "hdHeight": 154.5189971923828 + }, + "Npcs": { + "sdHeight": -1, + "hdHeight": 168.61300659179688 + }, + "Nbrx": { + "sdHeight": 258.4989929199219, + "hdHeight": 258.4989929199219 + }, + "Nbrs": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "Nbru": { + "sdHeight": 110.50800323486328, + "hdHeight": 110.50800323486328 + }, + "Nngv": { + "sdHeight": 166.56300354003906, + "hdHeight": 166.56300354003906 + }, + "Ntig": { + "sdHeight": -1, + "hdHeight": 153.79299926757812 + }, + "Npma": { + "sdHeight": -1, + "hdHeight": 349.1109924316406 + }, + "Npla": { + "sdHeight": 173.5590057373047, + "hdHeight": 173.5590057373047 + }, + "Ekgh": { + "sdHeight": 233.01800537109375, + "hdHeight": 233.01800537109375 + }, + "Haga": { + "sdHeight": 232.6280059814453, + "hdHeight": 232.6280059814453 + }, + "Haja": { + "sdHeight": 119.58300018310547, + "hdHeight": 119.58300018310547 + } +} \ No newline at end of file diff --git a/src/_workaround.ts b/src/_workaround.ts new file mode 100644 index 0000000..7154d1a --- /dev/null +++ b/src/_workaround.ts @@ -0,0 +1,16 @@ +export const DUMMY = "to make it look like a module" + +compiletime(() => { + //TODO: How to get rid of this? + + // Fixes issues when importing map-loot-parser.ts and errors from tstl about usage of Enums + require("ts-node").register({ + transpileOnly: true, + // compilerOptions: { + // module: "commonjs", + // esModuleInterop: true, + // allowSyntheticDefaultImports: true, + // } + }); + } +); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 9a53cf2..d4c4524 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ +import * as ignored from "./_workaround"; import { enableDraw } from "./player_features/draw"; import { enableBuildingCancelTrigger } from "./observer_features/buildingCancel"; import { enableItemSoldBoughtTrigger } from "./observer_features/itemSoldBought"; @@ -14,6 +15,7 @@ import { enableForfeit } from "./player_features/forfeit"; import { enableCustomMinimapIcons } from "./player_features/customMinimapIcons"; import { hideGameButtons } from "./player_features/hideGameButtons"; import { enableClock } from "./player_features/clock"; +import { enableCreepLootIndicator } from "./player_features/loot-indicator/loot-indicator"; function init() { enableShowCommandsTrigger(); @@ -22,6 +24,7 @@ function init() { enableUnitDenyTrigger(); enableCustomMinimapIcons(); enableClock(); + enableCreepLootIndicator(); // Observer-Only Features enableItemSoldBoughtTrigger(); diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md new file mode 100644 index 0000000..8a0033f --- /dev/null +++ b/src/player_features/loot-indicator/README.md @@ -0,0 +1,30 @@ +# Short summary + +Creep loot information is taken from inside a map file (`war3mapUnits.doo`). + +The indicator is a WC3 Special Effect, which we manually move every 0.01 seconds. +To properly position indicator over the health bar, we use per-model height data extracted from in-game files +([unit-model-height-data.json](../../../scripts/loot-indicator/model-heights/unit-model-height-data.json), +for more details see https://github.com/Psimage/wc3-drop-indicator-poc) + +Preview UI is a WC3 Frame drawn on top of Command Bar. +To display possible loot drops, we use runtime item info, combined with item data extracted from in-game files +(unavailable at runtime, [extracted-items-data.json](../../../scripts/loot-indicator/items-db/extracted-items-data.json)) + +# Observer mode + +* Indicator will be shown if the observer had `-looticon` enabled before the game +* Loot preview (drop table) is not shown + +# Known issues + +* Indicator appears slightly higher on creeps in shallow water +* Indicator height is not accurate on flying creeps when flying over uneven terrain (slopes/cliffs) +* Indicator does not adapt to scaling changes (e.g. bloodlust) +* Indicator does not adapt to model change (e.g. hex/sheep) + +# Thanks to + +Coff, Mayday, Tasyen, ModdieMads, Kenshin, TriggerHappy, Luashine, Tordes, Starbuck! + +And a lot of other people from W3Champions and Hive communities! diff --git a/src/player_features/loot-indicator/loot-indicator.ts b/src/player_features/loot-indicator/loot-indicator.ts new file mode 100644 index 0000000..c560cad --- /dev/null +++ b/src/player_features/loot-indicator/loot-indicator.ts @@ -0,0 +1,239 @@ +import {Effect, MapPlayer, Trigger, Unit, File, Timer} from "w3ts"; +import { + findMapInitialCreepsWithDrops, + getAllItemIds, + ItemDrop, + ItemDropSet, + RandomItemGroupDrop, + UnitItemDrop +} from "./modules/unit-item-drops"; +import {ItemClass} from "./modules/item-groups"; +import {initItemsDB} from "./modules/items-db"; +import { + calcUnitHpBarPosition, + initIsReforgedUnitModelsEnabledLocal, +} from "./modules/unit-hp-bar-position-calculator"; +import {LootTableUI} from "./modules/loot-table-ui"; + +//For local player. Veriest per player. +let IS_INDICATOR_ENABLED_LOCAL = false; +let IS_PREVIEW_ENABLED_LOCAL = false; + +let ACTIVE_INDICATORS = new Map(); + +export function enableCreepLootIndicator() { + initItemsDB(); + initIsReforgedUnitModelsEnabledLocal(); + + IS_INDICATOR_ENABLED_LOCAL = loadIsIndicatorEnabled(); + const unitsWithDrops = findMapInitialCreepsWithDrops(); + createIndicators(unitsWithDrops); + enableIndicatorFeatureToggleChatCommand() + + IS_PREVIEW_ENABLED_LOCAL = loadIsPreviewEnabled(); + enableLootTablePreviewUI(); + enablePreviewFeatureToggleChatCommand() +} + +function loadIsIndicatorEnabled(): boolean { + return (File.read("w3cCreepLootIndicator.txt") ?? "on") === "on"; +} + +function saveIsIndicatorEnabled(isEnabled: boolean) { + File.write("w3cCreepLootIndicator.txt", isEnabled ? "on" : "off"); +} + +function loadIsPreviewEnabled(): boolean { + return (File.read("w3cCreepLootPreview.txt") ?? "on") === "on"; +} + +function saveIsPreviewEnabled(isEnabled: boolean) { + File.write("w3cCreepLootPreview.txt", isEnabled ? "on" : "off"); +} + +function createIndicators(unitsWithDrops: UnitItemDrop[]) { + for (const unitWithDrop of unitsWithDrops) { + const indicator = UnitLootIndicator.create(unitWithDrop); + IS_INDICATOR_ENABLED_LOCAL ? indicator.enable() : indicator.disable(); + + ACTIVE_INDICATORS.set(indicator.unit.handle, indicator); + + registerUnitItemDroppedEvent(indicator.unit, () => { + indicator.destroy(); + ACTIVE_INDICATORS.delete(indicator.unit.handle); + }); + } +} + +function registerUnitItemDroppedEvent(unit: Unit, action: () => void) { + const t = Trigger.create(); + t.registerUnitEvent(unit, EVENT_UNIT_DEATH) + t.registerUnitEvent(unit, EVENT_UNIT_CHANGE_OWNER) + t.addAction(() => { + action(); + t.destroy(); + }); +} + +function enableIndicatorFeatureToggleChatCommand() { + const t = Trigger.create(); + for (let i = 0; i < bj_MAX_PLAYERS; i++) { + t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-looticon", true); + } + t.addAction(() => { + const player = MapPlayer.fromEvent()!; + if (player.isLocal()) { + IS_INDICATOR_ENABLED_LOCAL = !IS_INDICATOR_ENABLED_LOCAL; + saveIsIndicatorEnabled(IS_INDICATOR_ENABLED_LOCAL); + + ACTIVE_INDICATORS.forEach(indicator => { + IS_INDICATOR_ENABLED_LOCAL ? indicator.enable() : indicator.disable() + }); + DisplayTextToPlayer(player.handle, 0, 0, `|cff00ff00[W3C]:|r Creep loot indicator is now |cffffff00 ` + (IS_INDICATOR_ENABLED_LOCAL ? `ENABLED` : `DISABLED`) + `|r.`) + } + }) +} + +function enablePreviewFeatureToggleChatCommand() { + const t = Trigger.create(); + for (let i = 0; i < bj_MAX_PLAYERS; i++) { + t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-lootpreview", true); + } + t.addAction(() => { + const player = MapPlayer.fromEvent()!; + if (player.isLocal()) { + IS_PREVIEW_ENABLED_LOCAL = !IS_PREVIEW_ENABLED_LOCAL; + saveIsPreviewEnabled(IS_PREVIEW_ENABLED_LOCAL); + + if (!IS_PREVIEW_ENABLED_LOCAL) { + LootTableUI.INSTANCE.hide(); + } + DisplayTextToPlayer(player.handle, 0, 0, `|cff00ff00[W3C]:|r Creep loot preview is now |cffffff00 ` + (IS_PREVIEW_ENABLED_LOCAL ? `ENABLED` : `DISABLED`) + `|r.`) + } + }) +} + +function getSingleGroupDrop(itemDropSets: ItemDropSet[]): RandomItemGroupDrop | undefined { + if (itemDropSets.length === 1 && itemDropSets[0].itemDrops.length === 1 + && itemDropSets[0].itemDrops[0] instanceof RandomItemGroupDrop) { + return itemDropSets[0].itemDrops[0]; + } +} + +function isTomeDrop(itemDrop: ItemDrop): boolean { + if (itemDrop instanceof RandomItemGroupDrop) { + const group = itemDrop.itemGroup; + return group.itemClass === ItemClass.Power_Up && + (group.itemLevel === 1 || group.itemLevel === 2) + } + + return false; +} + +function enableLootTablePreviewUI() { + LootTableUI.init(); + + const t = Trigger.create(); + t.registerAnyUnitEvent(EVENT_PLAYER_UNIT_SELECTED); + t.addAction(() => { + const player = MapPlayer.fromEvent()!; + if (player.isLocal() && IS_PREVIEW_ENABLED_LOCAL) { + const indicator = ACTIVE_INDICATORS.get(Unit.fromEvent()!.handle); + if (indicator !== undefined) { + LootTableUI.INSTANCE.show(getAllItemIds(indicator.itemDropSets)); + } else { + LootTableUI.INSTANCE.hide(); + } + } + }) +} + +class UnitLootIndicator { + readonly unit: Unit; + readonly itemDropSets: ItemDropSet[]; + + private readonly indicatorEffect: Effect; + private readonly indicatorScale: number; + private isEnabled: boolean; + private updateUnitTimer?: Timer; + + constructor(unit: Unit, itemDropSets: ItemDropSet[], indicatorEffect: Effect) { + this.unit = unit; + this.itemDropSets = itemDropSets; + this.indicatorEffect = indicatorEffect; + this.indicatorScale = indicatorEffect.scale; + this.isEnabled = true; + } + + static create(unitItemDrop: UnitItemDrop): UnitLootIndicator { + const unit = unitItemDrop.unit; + const itemDropSets = unitItemDrop.dropSets; + let e: Effect; + + //In 99% of cases a unit has a single set (drops 1 item) with a single group item drop (can drop any item from that group) + const groupDrop = getSingleGroupDrop(itemDropSets); + if (groupDrop && isTomeDrop(groupDrop)) { + e = Effect.create("loot-indicator\\loot-indicator-tome.mdx", 0, 0)!; + } else { + e = Effect.create("loot-indicator\\loot-indicator-generic.mdx", 0, 0)!; + } + + //For units with mana bar, we adjust the position of the effect model with animation + //We don't use Z offset for effect in the world, because that will affect "billboarding", + //and will lead to the effect slightly shifting relative to HP bar depending on the camera angle + if (unit.maxMana > 0) { + e.playAnimation(ANIM_TYPE_STAND) + } else { + e.playAnimation(ANIM_TYPE_WALK) + } + + const indicator = new UnitLootIndicator(unit, itemDropSets, e); + indicator.enableUpdateUnit(); + return indicator; + } + + disable() { + if (!this.isEnabled) return; + this.isEnabled = false; + + this.hide(); + } + + enable() { + if (this.isEnabled) return; + this.isEnabled = true; + + this.show(); + } + + private hide() { + this.indicatorEffect.scale = 0; + } + + private show() { + this.indicatorEffect.scale = this.indicatorScale; + } + + destroy() { + this.updateUnitTimer?.destroy(); + this.indicatorEffect.destroy(); + } + + private enableUpdateUnit() { + this.updateUnitTimer = Timer.create()!; + this.updateUnitTimer.start(0.01, true, () => { + if (!this.isEnabled) return; + + //Handle invisible units (Murloc Nightcrawler) + if (!this.unit.isVisible(MapPlayer.fromLocal())) { + this.hide(); + return; + } else { + this.show(); + } + + const hpBarPos = calcUnitHpBarPosition(this.unit); + this.indicatorEffect.setPosition(hpBarPos.x, hpBarPos.y, hpBarPos.z); + }) + } +} \ No newline at end of file diff --git a/src/player_features/loot-indicator/modules/item-groups.ts b/src/player_features/loot-indicator/modules/item-groups.ts new file mode 100644 index 0000000..f0c4572 --- /dev/null +++ b/src/player_features/loot-indicator/modules/item-groups.ts @@ -0,0 +1,46 @@ +export class ItemGroup { + id: string; + itemClass: ItemClass; + itemLevel: number; + items: string[]; + + constructor(id: string, itemClass: ItemClass, itemLevel: number, items: string[]) { + this.id = id; + this.itemClass = itemClass; + this.itemLevel = itemLevel; + this.items = items; + } +} + +export enum ItemClass { + Permanent = "Permanent", + Charged = "Charged", + Power_Up = "PowerUp", + Artifact = "Artifact", + Purchasable = "Purchasable", + Campaign = "Campaign", + Misc = "Miscellaneous" +} + +const ITEM_CLASS_TO_ENCODED_CLASS = new Map([ + [ItemClass.Permanent, "i"], + [ItemClass.Charged, "j"], + [ItemClass.Power_Up, "k"], + [ItemClass.Artifact, "l"], + [ItemClass.Purchasable, "m"], + [ItemClass.Campaign, "n"], + [ItemClass.Misc, "o"], +]) + +export const VALID_LEVELS = new Set([0, 1, 2, 3, 4, 5, 6, 7, 8]); + +export function itemGroup2FourCC(itemClass: ItemClass, itemLevel: number): string | undefined { + const encodedClass = ITEM_CLASS_TO_ENCODED_CLASS.get(itemClass); + if (encodedClass === undefined) return; + + if (!VALID_LEVELS.has(itemLevel)) return; + + return 'Y' + encodedClass + 'I' + itemLevel.toString(10); +} + + diff --git a/src/player_features/loot-indicator/modules/items-db.ts b/src/player_features/loot-indicator/modules/items-db.ts new file mode 100644 index 0000000..a66e22f --- /dev/null +++ b/src/player_features/loot-indicator/modules/items-db.ts @@ -0,0 +1,168 @@ +import {ItemClass, ItemGroup, itemGroup2FourCC, VALID_LEVELS} from "./item-groups"; +import {Item} from "w3ts"; + +interface CompiletimeItem { + includeAsRandomChoice: boolean; +} + +const COMPILETIME_ITEM_DATA = compiletime(() => { + const fs = require("fs-extra"); + return JSON.parse(fs.readFileSync("./scripts/loot-indicator/items-db/extracted-items-data.json", "utf8")); +}) as Record; + +class ItemInfo { + id: string; + name: string; + classification: ItemClass; + level: number; + extendedTooltip: string; + interfaceIcon: string; + includeAsRandomChoice: boolean; + + constructor(id: string, name: string, level: number, extendedTooltip: string, interfaceIcon: string, classification: ItemClass, includeAsRandomChoice: boolean) { + this.id = id; + this.name = name; + this.classification = classification; + this.level = level; + this.extendedTooltip = extendedTooltip; + this.interfaceIcon = interfaceIcon; + this.includeAsRandomChoice = includeAsRandomChoice; + } +} + +const ITEMS_BY_ID = new Map(); + +const ITEM_GROUPS_BY_ID = new Map(); + +export function initItemsDB() { + createAllItemsDB(); + createItemGroupsDB(); +} + +function createAllItemsDB() { + for (const itemId of Object.keys(COMPILETIME_ITEM_DATA)) { + const item = Item.create(FourCC(itemId), 0, 0)!; + if (item == undefined) { + print(`Failed to create an item with id: ${itemId}`) + continue; + } + + const itemClass = inferItemClass(item); + if (itemClass == undefined) { + item.destroy(); + continue; + } + + const itemInfo = new ItemInfo( + itemId, + item.name, + item.level, + item.extendedTooltip, + item.icon, + itemClass, + COMPILETIME_ITEM_DATA[itemId].includeAsRandomChoice); + + ITEMS_BY_ID.set(itemId, itemInfo); + + item.destroy(); + } + + const expectedItemsCount = Object.keys(COMPILETIME_ITEM_DATA).length; + const actualItemCount = ITEMS_BY_ID.size; + if (actualItemCount != expectedItemsCount) { + print(`Failed to initialize items database. Expected ${expectedItemsCount} items, but got ${actualItemCount}`) + } +} + +function createItemGroupsDB() { + for (const item of ITEMS_BY_ID.values()) { + //ChooseRandomItemEx filters out items that have `Stats - Include As Random Choice` field set to false + if (!item.includeAsRandomChoice) { + continue; + } + + const itemGroupId = itemGroup2FourCC(item.classification, item.level); + if (itemGroupId === undefined) { + print(`Failed to create item group id for ${item.id}: ${item.classification} ${item.level}`) + continue; + } + + let itemGroup = ITEM_GROUPS_BY_ID.get(itemGroupId); + if (itemGroup === undefined) { + itemGroup = new ItemGroup(itemGroupId, item.classification, item.level, []) + ITEM_GROUPS_BY_ID.set(itemGroupId, itemGroup); + } + + itemGroup.items.push(item.id); + } + + //Some groups have no items - but we still need to create EMPTY groups for them (ItemGroup that has 0 items), + //so that `getItemGroupById()` could be used to check if the ID is an ItemGroup id instead of a specific item id + for (const iClass of Object.values(ItemClass)) { + for (const iLevel of Array.from(VALID_LEVELS.values())) { + const groupId = itemGroup2FourCC(iClass, iLevel)!; + const group = ITEM_GROUPS_BY_ID.get(groupId); + if (group === undefined) { + ITEM_GROUPS_BY_ID.set(groupId, new ItemGroup(groupId, iClass, iLevel, [])) + } else { + //Sort items in a group alphabetically by name + //Lua's `<` operator does lexicographical comparison + group.items.sort((a, b) => (ITEMS_BY_ID.get(a)!.name < ITEMS_BY_ID.get(b)!.name) ? -1 : 1); + } + } + } + + const actualGroupsCount = ITEM_GROUPS_BY_ID.size; + const expectedGroupsCount = Object.values(ItemClass).length * VALID_LEVELS.size; + if (actualGroupsCount !== expectedGroupsCount) { + print(`Failed to create correct amount of item groups. Expected ${expectedGroupsCount} groups, but got ${actualGroupsCount}`) + } +} + +function inferItemClass(item: Item) { + const itemType = item.type!; + switch (itemType) { + case ITEM_TYPE_ANY: { + //Including for completeness, should never happen + print(`Item ${item.name} (${item.typeId}) is of type ANY`) + return; + } + case ITEM_TYPE_ARTIFACT: + return ItemClass.Artifact; + case ITEM_TYPE_CAMPAIGN: + return ItemClass.Campaign; + case ITEM_TYPE_CHARGED: + return ItemClass.Charged; + case ITEM_TYPE_MISCELLANEOUS: + return ItemClass.Misc; + case ITEM_TYPE_PERMANENT: + return ItemClass.Permanent; + case ITEM_TYPE_POWERUP: + return ItemClass.Power_Up; + case ITEM_TYPE_PURCHASABLE: + return ItemClass.Purchasable; + //What is this? You can't select this group in WE, there is no such item class in the game + // https://github.com/lep/jassdoc/blob/master/common.j says: "Deprecated, should use ITEM_TYPE_POWERUP" + case ITEM_TYPE_TOME: { + print(`Item ${item.name} (${item.typeId}) is of type TOME`) + return ItemClass.Power_Up; + } + //Don't know why some items are of type UNKNOWN (but their actual class in WE Object Data is "Misc") + // None of those items are selectable by ChooseRandomItemEx (IncludeAsRandomChoice is false) + case ITEM_TYPE_UNKNOWN: + return ItemClass.Misc; + default: { + print(`Item ${item.name} (${item.typeId}) has unexpected type!`); + return; + } + } +} + +export function getItemById(fourCCid: string): ItemInfo | undefined { + return ITEMS_BY_ID.get(fourCCid); +} + +//Group such as "YiI1" - which includes items of class "Permanent", level 1. +export function getItemGroupById(fourCCid: string): ItemGroup | undefined { + return ITEM_GROUPS_BY_ID.get(fourCCid); +} diff --git a/src/player_features/loot-indicator/modules/loot-table-ui.ts b/src/player_features/loot-indicator/modules/loot-table-ui.ts new file mode 100644 index 0000000..22d04a2 --- /dev/null +++ b/src/player_features/loot-indicator/modules/loot-table-ui.ts @@ -0,0 +1,121 @@ +import {Frame} from "w3ts"; +import {getItemById} from "./items-db"; + +export class LootTableUI { + static INSTANCE: LootTableUI; + static readonly MAX_ITEMS = 4 * 3; + + private readonly mainParent!: Frame; + private readonly itemBtnList: ItemBtn[] = []; + + static init() { + LootTableUI.INSTANCE = new LootTableUI(); + } + + private constructor() { + const tocFile = "loot-indicator\\ui\\ui.toc"; + if (!BlzLoadTOCFile(tocFile)) { + print("Failed to load TOC for LootTableUI: " + tocFile) + //Can you fail fast and log it? (e.g., to a war3 log file) + //Because of return, it might crash a millisecond later and nobody will see the print + return; + } + + this.mainParent = Frame.createType("LI_Main_Parent", Frame.fromOrigin(ORIGIN_FRAME_SIMPLE_UI_PARENT, 0)!, 0, "SIMPLEFRAME", "")!; + for (let i = 0; i < LootTableUI.MAX_ITEMS; i++) { + const itemBtn = new ItemBtn(this.mainParent, i); + //ORIGIN_FRAME_COMMAND_BUTTON is available even in Replays (where commandbar is hidden by replay controls) + itemBtn.btn.setPoint(FRAMEPOINT_CENTER, Frame.fromOrigin(ORIGIN_FRAME_COMMAND_BUTTON, i)!, FRAMEPOINT_CENTER, 0, 0) + this.itemBtnList.push(itemBtn) + } + + this.hide() + } + + show(itemIds: string[]) { + this.mainParent.setVisible(true) + + // Can't fit more than 12 right now. + if (itemIds.length > LootTableUI.MAX_ITEMS) { + itemIds = itemIds.slice(0, LootTableUI.MAX_ITEMS) + } + + for (let i = 0; i < LootTableUI.MAX_ITEMS; i++) { + const itemBtn = this.itemBtnList[i]; + const itemId = itemIds[i]; + + if (itemId !== undefined) { + itemBtn.btn.setVisible(true) + + const item = getItemById(itemId)!; + itemBtn.setIcon(item.interfaceIcon) + itemBtn.setTooltip(`|cffffff00${item.name}\n|cff00ff00[${item.classification}, Level ${item.level}]`, + item.extendedTooltip) + } else { + itemBtn.btn.setVisible(false) + } + } + } + + hide() { + this.mainParent.setVisible(false) + } +} + +class ItemBtn { + btn: Frame; + btnBackdrop: Frame; + + tooltip: Frame; + tooltipBox: Frame; + tooltipTitle: Frame; + tooltipSeparator: Frame; + tooltipDescription: Frame; + + constructor(owner: Frame, createContext: number) { + //Buttons created on top of CommandBar have to be "SIMPLEBUTTON" to receive input (hover to show tooltip), + //because non-SIMPLE frames have lower priority than SIMPLE, and the original CommandBar consists of SIMPLE frames. + this.btn = Frame.createSimple("LI_ItemButton", owner, createContext)!; + //NOTE: The draw order is not consistent. In rare cases our btn is drawn below the original one (but it does not matter for our case) + this.btn.setLevel(6) //To draw above black background + this.btnBackdrop = Frame.fromName("LI_ItemButton_Backdrop", createContext)!; + + this.tooltip = Frame.createSimple("LI_Tooltip", owner, 0)!; + this.tooltipBox = Frame.fromName("LI_Tooltip_Box", 0)!; + this.tooltipTitle = Frame.fromName("LI_Tooltip_Title", 0)!; + this.tooltipSeparator = Frame.fromName("LI_Tooltip_Separator", 0)!; + this.tooltipDescription = Frame.fromName("LI_Tooltip_Description", 0)!; + + //Wanted to make container box to be FIXED width and dynamic height (based on text). + //It works, but I had to set FIXED width to "Description" and "Title" instead (and box size is relative to Description and Title) + this.tooltipTitle.setWidth(0.285) + this.tooltipTitle.setPoint(FRAMEPOINT_BOTTOMRIGHT, this.tooltipSeparator, FRAMEPOINT_TOPRIGHT, 0, 0.005) + + this.tooltipSeparator.setHeight(0.0005) + this.tooltipSeparator.setPoint(FRAMEPOINT_BOTTOMLEFT, this.tooltipDescription, FRAMEPOINT_TOPLEFT, 0, 0.005) + this.tooltipSeparator.setPoint(FRAMEPOINT_BOTTOMRIGHT, this.tooltipDescription, FRAMEPOINT_TOPRIGHT, 0, 0.005) + + this.tooltipDescription.setWidth(0.285) + this.tooltipDescription.setAbsPoint(FRAMEPOINT_BOTTOMRIGHT, 0.79, 0.168) + + this.tooltipBox.setPoint(FRAMEPOINT_TOPLEFT, this.tooltipTitle, FRAMEPOINT_TOPLEFT, -0.005, 0.005) + this.tooltipBox.setPoint(FRAMEPOINT_BOTTOMRIGHT, this.tooltipDescription, FRAMEPOINT_BOTTOMRIGHT, 0.005, -0.005) + + this.btn.setTooltip(this.tooltip) + //Need to initially manually hide. + //Need to hide both tooltip and tooltipBox frames likely because "tooltip" is a SIMPLEFRAME, + // while "tooltipBox" is not ("BACKDROP" is a normal frame). + // "SIMPLEBUTTON" only supports "SIMPLEFRAME" as a tooltip + this.tooltip.setVisible(false) + this.tooltipBox.setVisible(false) + } + + setIcon(iconFilePath: string) { + this.btnBackdrop.setTexture(iconFilePath, 0, false) + } + + setTooltip(title: string, description: string) { + this.tooltipTitle.setText(title); + this.tooltipDescription.setText(description); + } +} \ No newline at end of file diff --git a/src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator.ts b/src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator.ts new file mode 100644 index 0000000..ef72496 --- /dev/null +++ b/src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator.ts @@ -0,0 +1,94 @@ +import {MapPlayer, Unit} from "w3ts"; +import {Units} from "@objectdata/units"; +import {getUnitModelScale, id2FourCC} from "./util"; + +//TODO: missing classic HD (aka SD+, that reside in _addons\hd2.w3addon\units.w3mod:_hd.w3mod), +// but they are most likely have the same height as SD (probably differ in texture quality only) +export interface UnitModelHeight { + sdHeight: number; + hdHeight: number; +} + +//TODO: convert key to number FourCC as it is primarily used with number FourCC? +const UNITS_MODEL_HEIGHT: Record = compiletime(() => { + const fs = require("fs-extra"); + const heights = JSON.parse(fs.readFileSync("./scripts/loot-indicator/model-heights/unit-model-height-data.json", "utf8")) as Record; + + // Override some values + + // Flying dragons + [ + 'nbwm', 'nbdk', 'nbdr', //black dragons + 'nbzd', 'nbzk', 'nbzw', //bronze dragons + 'nadk', 'nadr', 'nadw', //blue dragons + 'ngrd', 'ngdk', 'ngrw', //green dragons + 'nrwm', 'nrdk', 'nrdr', //red dragons + ].forEach((unitType => heights[unitType].sdHeight += 40)); + + // Murlocks + [ + 'nmrm', 'nmrr', 'nmpg', 'nmrl', 'nmmu', + ].forEach((unitType => heights[unitType].sdHeight += 16.5)); + + // Eredar + [ + 'ners', 'nerw' + ].forEach((unitType => heights[unitType].sdHeight += 40)); + + // Murlocs + [ + 'nmfs' + ].forEach((unitType => heights[unitType].sdHeight += 20)); + + // Infernal + [ + 'ninf' + ].forEach((unitType => heights[unitType].sdHeight += 260)); + + return heights +}) as Record; + + +//For local player only +let IS_REFORGED_UNIT_MODELS_ENABLED_LOCAL: boolean; + +export function initIsReforgedUnitModelsEnabledLocal() { + //We spawn a unit known to have different Scale for SD and HD mode + //We use NEUTRAL_PASSIVE instead of NEUTRAL_AGGRESSIVE because it causes "creep camp" minimap indicator to appear + const u = Unit.create(MapPlayer.fromIndex(PLAYER_NEUTRAL_PASSIVE)!, FourCC(Units.BlueDrake), 0, 0)!; + IS_REFORGED_UNIT_MODELS_ENABLED_LOCAL = getUnitModelScale(u) !== 1.2; + u.destroy(); +} + +export function isReforgedUnitModelsEnabledLocal(): boolean { + return IS_REFORGED_UNIT_MODELS_ENABLED_LOCAL; +} + +function getUnitModelHeight(unitId: number): number { + const model = UNITS_MODEL_HEIGHT[id2FourCC(unitId)]; + if (isReforgedUnitModelsEnabledLocal()) { + return model.hdHeight; + } else { + return model.sdHeight; + } +} + +export function calcUnitHpBarPosition(u: Unit) { + let x, y, z; + let uScale = getUnitModelScale(u) + //TODO: does not reflect dynamic model skin (e.g. will not return different value if unit was Hexed) + let uHeight = getUnitModelHeight(u.skin); + + // For some collision sizes Unit's position is off with HP bar + if (u.collisionSize != 32 && u.collisionSize != 47) { + x = u.x - 16; + y = u.y - 16; + } else { + x = u.x; + y = u.y; + } + + z = u.localZ + u.getflyHeight() + uHeight * uScale + 16.5; + + return {x, y, z}; +} \ No newline at end of file diff --git a/src/player_features/loot-indicator/modules/unit-item-drops.ts b/src/player_features/loot-indicator/modules/unit-item-drops.ts new file mode 100644 index 0000000..0c4f373 --- /dev/null +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -0,0 +1,169 @@ +import {Group, Point, Unit} from "w3ts"; +import {ItemGroup} from "./item-groups"; +import {getItemById, getItemGroupById} from "./items-db"; + +// Raw data from map file +export interface RawUnitItemDrop { + unitLocation: Point2D; + itemSets: RawItemDropSet[]; +} + +export interface Point2D { + x: number; + y: number; +} + +export interface RawItemDropSet { + itemTypes: string[] +} + +// Enhanced raw data with data from runtime + +export interface UnitItemDrop { + unit: Unit; + dropSets: ItemDropSet[]; +} + +export interface ItemDropSet { + itemDrops: ItemDrop[] +} + +export interface ItemDrop { + getRawId(): string; + + getDropItemIds(): string[]; +} + +export class SpecificItemDrop implements ItemDrop { + itemId: string; + + constructor(itemId: string) { + this.itemId = itemId; + } + + getRawId() { + return this.itemId; + } + + getDropItemIds() { + return [this.itemId]; + } +} + +export class RandomItemGroupDrop implements ItemDrop { + itemGroup: ItemGroup + + constructor(itemGroup: ItemGroup) { + this.itemGroup = itemGroup; + } + + getRawId(): string { + return this.itemGroup.id; + } + + getDropItemIds(): string[] { + return this.itemGroup.items; + } +} + +const RAW_UNIT_ITEM_DROPS = compiletime(() => { + //TODO: How to properly import? Relative to project root. Bonus: no proper code completions for "require"d modules + const {getMapItemDrops} = require("../../../scripts/loot-indicator/map-loot/map-loot-parser.ts"); + + const fs = require("fs-extra"); + const mapFolder = JSON.parse(fs.readFileSync("./config.json", "utf8")).mapFolder; + + return getMapItemDrops(`./maps/${mapFolder}`); +}) as RawUnitItemDrop[] + +function findUnitAtPoint(p: Point): Unit | undefined { + const g = Group.create()!; + g.enumUnitsInRangeOfPoint(p, 1, () => Unit.fromFilter().getOwner().id === PLAYER_NEUTRAL_AGGRESSIVE); + if (g.size == 0) { + //Try a bigger search radius. The data for position is not always precise. + //Example is "Elder Jungle Stalker" on 1v1_WarHail_v1.1.w3x + //Parsed map position: 2338, 3121 + //Actual position: 2384, 3088 + g.enumUnitsInRangeOfPoint(p, 200, () => Unit.fromFilter().getOwner().id === PLAYER_NEUTRAL_AGGRESSIVE) + } + + //size=0 can happen when player spawn is at a camp (e.g., on 4 player map, like LostTemple) + if (g.size == 0) { + // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) + //TODO: remove/disable print statements in production + // Idea: add a "-debug" command (locally persisted flag - same approach as feature flags). + // When enabled, print statements are enabled. + // When something goes wrong, we can ask for a replay file. Then replay it with "debug" flag enabled - and we can see what happened. + g.destroy(); + return; + } + + let u: Unit; + if (g.size == 1) { + u = g.getUnitAt(0); + } else if (g.size > 1) { + // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) + u = getClosestUnit(g, p); + } + + g.destroy(); + return u; +} + +function getClosestUnit(g: Group, p: Point) { + let closestUnit = g.getUnitAt(0); + let closestDist = calcDistance(closestUnit, p); + + for (let i = 1; i < g.size; i++) { + const u = g.getUnitAt(i); + const dist = calcDistance(u, p) + if (dist < closestDist) { + closestDist = dist; + closestUnit = u; + } + } + + return closestUnit; +} + +function calcDistance(u: Unit, p: Point) { + const dx = u.x - p.x; + const dy = u.y - p.y; + + return dx * dx + dy * dy; +} + +export function getAllItemIds(dropSets: ItemDropSet[]) { + return dropSets.flatMap(s => s.itemDrops.flatMap(d => d.getDropItemIds())); +} + +export function findMapInitialCreepsWithDrops(): UnitItemDrop[] { + const unitItemDrops: UnitItemDrop[] = []; + for (const rawDrop of RAW_UNIT_ITEM_DROPS) { + const unit = findUnitAtPoint(Point.create(rawDrop.unitLocation.x, rawDrop.unitLocation.y)); + if (!unit) continue; + + const dropSets: ItemDropSet[] = rawDrop.itemSets.flatMap(itemSet => { + const itemDrops: ItemDrop[] = itemSet.itemTypes.flatMap((itemOrGroupId) => { + const itemGroup = getItemGroupById(itemOrGroupId); + if (itemGroup !== undefined) { + return [new RandomItemGroupDrop(itemGroup)] + } else if (getItemById(itemOrGroupId) !== undefined) { + return [new SpecificItemDrop(itemOrGroupId)] + } else { + print(`Unknown item drop id "${itemOrGroupId}" for unit "${unit.name}" at (${unit.x}, ${unit.y}).`); + return [] as ItemDrop[]; + } + }); + return itemDrops.length > 0 ? [{itemDrops}] : []; + }) + + //Skip unit, if effectively it has no drops. + //This could be mapmaker's mistake, blizzard changing drop tables, or we filtered it out (unknown item drop id) + if (getAllItemIds(dropSets).length === 0) continue; + + unitItemDrops.push({unit, dropSets}); + } + + return unitItemDrops; +} diff --git a/src/player_features/loot-indicator/modules/util.ts b/src/player_features/loot-indicator/modules/util.ts new file mode 100644 index 0000000..21d764e --- /dev/null +++ b/src/player_features/loot-indicator/modules/util.ts @@ -0,0 +1,40 @@ +import {Unit} from "w3ts"; + +export function groupBy( + values: Iterable, + keyFn: (value: T) => K +): Map { + const map = new Map(); + for (const value of values) { + const key = keyFn(value); + let group = map.get(key); + if (!group) { + group = []; + map.set(key, group); + } + group.push(value); + } + return map; +} + +export function id2FourCC(id: number): string { + const a = (id >>> 24) & 0xff; + const b = (id >>> 16) & 0xff; + const c = (id >>> 8) & 0xff; + const d = id & 0xff; + return string.char(a, b, c, d); +} + +//This is the initial "Art - Scaling Value" parameter value +//Does not represent actual runtime scale (e.g., does not change when Bloodlusted/Hexed) +export function getUnitModelScale(unit: Unit): number { + return unit.getField(UNIT_RF_SCALING_VALUE) as number +} + +//https://lep.nrw/jassbot/doc/BlzTriggerRegisterPlayerKeyEvent +//Can combine them by just adding them (+) or by using bitwise OR (|) +export const METAKEY_NONE: number = 0; +export const METAKEY_SHIFT: number = 1; +export const METAKEY_CTRL: number = 2; +export const METAKEY_ALT: number = 4; +export const METAKEY_META: number = 8; //aka windows key \ No newline at end of file diff --git a/src/showCommands.ts b/src/showCommands.ts index 735a173..c5c0c60 100644 --- a/src/showCommands.ts +++ b/src/showCommands.ts @@ -21,7 +21,9 @@ export function enableShowCommandsTrigger() { ` |cffffff00•|r Type|cffffff00 -deny|r to show/hide|cffffff00 !|r when a player's unit is denied.\n` + ` |cffffff00•|r Type|cffffff00 -workercount|r to show/hide goldmine worker count.\n` + ` |cffffff00•|r Type|cffffff00 -minimap|r to show/hide custom minimap icons.\n` + - ` |cffffff00•|r Type|cffffff00 -clock|r to show/hide clock.`; + ` |cffffff00•|r Type|cffffff00 -clock|r to show/hide clock.\n` + + ` |cffffff00•|r Type|cffffff00 -looticon|r to show/hide creep loot indicator.\n` + + ` |cffffff00•|r Type|cffffff00 -lootpreview|r to show/hide creep loot preview on selection.` TriggerAddAction(showCommandsTrigger, () => { DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, 10, commands); diff --git a/tsconfig.json b/tsconfig.json index 2294b59..e397f28 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { "baseUrl": "./src", "allowJs": true, - "target": "es6", + "target": "ES2019", "lib": [ - "es6" + "ES2019" ], "moduleResolution": "node", "paths": { @@ -16,7 +16,7 @@ "../node_modules/*" ], "@objectdata/*": [ - "./node_modules/war3-objectdata-th/dist/cjs/generated/constants/*" + "../node_modules/war3-objectdata-th/dist/cjs/generated/constants/*" ] }, "plugins": [ diff --git a/updateMaps.sh b/updateMaps.sh index c99570f..0393915 100644 --- a/updateMaps.sh +++ b/updateMaps.sh @@ -89,6 +89,8 @@ while IFS= read -r -d '' fullPath; do mv "$buildMapPath" "$targetDir/$newFileName" fi + rm -rf "$buildMapPath" + done < <(find "$cleanMapPath" -type f \( -iname '*.w3m' -o -iname '*.w3x' \) -print0) rm -rf "$buildMapPath" 2>/dev/null