From cf88be4af4eaee220e7587d2ad2059ca9e50532f Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Mon, 22 Sep 2025 11:56:16 +0300 Subject: [PATCH 01/26] Import indicator --- .gitignore | 1 + .../loot-indicator/loot-indicator-generic.dds | Bin 0 -> 22000 bytes .../loot-indicator/loot-indicator-generic.mdx | Bin 0 -> 1864 bytes .../loot-indicator/loot-indicator-tome.dds | Bin 0 -> 5616 bytes .../loot-indicator/loot-indicator-tome.mdx | Bin 0 -> 1864 bytes .../main/loot-indicator/ui/loot-table-ui.fdf | 52 + assets/main/loot-indicator/ui/ui.toc | 2 + .../loot-indicator/loot-indicator-generic.dds | Bin 0 -> 22000 bytes .../loot-indicator/loot-indicator-generic.mdx | Bin 0 -> 1864 bytes .../loot-indicator/loot-indicator-tome.dds | Bin 0 -> 5616 bytes .../loot-indicator/loot-indicator-tome.mdx | Bin 0 -> 1864 bytes .../roc/loot-indicator/ui/loot-table-ui.fdf | 52 + assets/roc/loot-indicator/ui/ui.toc | 2 + package.json | 10 +- .../items-db/extract-items-data.ts | 52 + .../items-db/extracted-items-data.json | 1134 +++++ .../map-loot/map-loot-parser.ts | 56 + .../model-heights/model-height-generator.ts | 70 + .../model-heights/unit-model-height-data.json | 3674 +++++++++++++++++ src/_workaround.ts | 16 + src/main.ts | 3 + .../loot-indicator/loot-indicator.ts | 312 ++ .../loot-indicator/modules/item-groups.ts | 46 + .../loot-indicator/modules/items-db.ts | 172 + .../loot-indicator/modules/loot-table-ui.ts | 125 + .../unit-hp-bar-position-calculator.ts | 84 + .../loot-indicator/modules/unit-item-drops.ts | 131 + .../loot-indicator/modules/util.ts | 40 + tsconfig.json | 6 +- 29 files changed, 6035 insertions(+), 5 deletions(-) create mode 100644 assets/main/loot-indicator/loot-indicator-generic.dds create mode 100644 assets/main/loot-indicator/loot-indicator-generic.mdx create mode 100644 assets/main/loot-indicator/loot-indicator-tome.dds create mode 100644 assets/main/loot-indicator/loot-indicator-tome.mdx create mode 100644 assets/main/loot-indicator/ui/loot-table-ui.fdf create mode 100644 assets/main/loot-indicator/ui/ui.toc create mode 100644 assets/roc/loot-indicator/loot-indicator-generic.dds create mode 100644 assets/roc/loot-indicator/loot-indicator-generic.mdx create mode 100644 assets/roc/loot-indicator/loot-indicator-tome.dds create mode 100644 assets/roc/loot-indicator/loot-indicator-tome.mdx create mode 100644 assets/roc/loot-indicator/ui/loot-table-ui.fdf create mode 100644 assets/roc/loot-indicator/ui/ui.toc create mode 100644 scripts/loot-indicator/items-db/extract-items-data.ts create mode 100644 scripts/loot-indicator/items-db/extracted-items-data.json create mode 100644 scripts/loot-indicator/map-loot/map-loot-parser.ts create mode 100644 scripts/loot-indicator/model-heights/model-height-generator.ts create mode 100644 scripts/loot-indicator/model-heights/unit-model-height-data.json create mode 100644 src/_workaround.ts create mode 100644 src/player_features/loot-indicator/loot-indicator.ts create mode 100644 src/player_features/loot-indicator/modules/item-groups.ts create mode 100644 src/player_features/loot-indicator/modules/items-db.ts create mode 100644 src/player_features/loot-indicator/modules/loot-table-ui.ts create mode 100644 src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator.ts create mode 100644 src/player_features/loot-indicator/modules/unit-item-drops.ts create mode 100644 src/player_features/loot-indicator/modules/util.ts 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 0000000000000000000000000000000000000000..ac6c9987324a3f4882785bb9b3e1c94a83b51fb3 GIT binary patch literal 22000 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 D0+p{_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b4476c12df4fa8ad0a63d1125857f6cfe3305382 GIT binary patch literal 1864 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ac6c9987324a3f4882785bb9b3e1c94a83b51fb3 GIT binary patch literal 22000 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 D0+p{_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b4476c12df4fa8ad0a63d1125857f6cfe3305382 GIT binary patch literal 1864 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 literal 0 HcmV?d00001 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..e1f5d3e 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,15 @@ "typescript": "5.8.2", "typescript-to-lua": "^1.31.0", "war3-objectdata-th": "^0.2.10", - "war3-transformer": "3.0.9", + "war3-transformer": "github:Psimage/war3-transformer#fix-typescript-dependency", "war3tstlhelper": "1.0.1", - "winston": "3.17.0" + "winston": "3.17.0", + + "war3-types-strict": "0.1.3", + + "mdx-m3-viewer-th": "5.13.1", + "@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..01e81d3 --- /dev/null +++ b/scripts/loot-indicator/map-loot/map-loot-parser.ts @@ -0,0 +1,56 @@ +import UnitsDoo from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/file.js"; +import Unit from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/unit.js"; +import War3MapW3i from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/w3i/file.js"; +import * as fs from "fs-extra"; +import type {RawItemDropSet, RawUnitItemDrop} from "../../../src/loot-indicator/modules/unit-item-drops"; +import DroppedItemSet from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/droppeditemset"; + +// 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..05ac39c --- /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 type {UnitModelHeight} from "../../../src/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/loot-indicator.ts b/src/player_features/loot-indicator/loot-indicator.ts new file mode 100644 index 0000000..ca5db58 --- /dev/null +++ b/src/player_features/loot-indicator/loot-indicator.ts @@ -0,0 +1,312 @@ +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 {getItemById, initItemsDB} from "./modules/items-db"; +import {METAKEY_CTRL, METAKEY_NONE} from "./modules/util"; +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 IS_CTRL_BTN_HELD_LOCAL = false; + +let ACTIVE_INDICATORS = new Map(); + +export function enableCreepLootIndicator() { + initItemsDB(); + initIsReforgedUnitModelsEnabledLocal(); + IS_INDICATOR_ENABLED_LOCAL = loadIsIndicatorEnabled(); + + const unitsWithDrops = findMapInitialCreepsWithDrops(); + createIndicators(unitsWithDrops); + + enableIndicatorFeatureToggleChatCommand() + enableTrackCtrlBtnHeld(); + + 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.show() : indicator.hide(); + + 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)!, "-cli", 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.show() : indicator.hide() + }); + 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)!, "-clp", 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 enableTrackCtrlBtnHeld() { + const tDown = Trigger.create(); + const tUp = Trigger.create(); + for (let i = 0; i < bj_MAX_PLAYERS; i++) { + tDown.registerPlayerKeyEvent(MapPlayer.fromIndex(i)!, OSKEY_LCONTROL, METAKEY_CTRL, true); + tUp.registerPlayerKeyEvent(MapPlayer.fromIndex(i)!, OSKEY_LCONTROL, METAKEY_NONE, false); + } + + tDown.addAction(() => { + if(MapPlayer.fromEvent()!.isLocal()) { + //By default, the action is called multiple times while the button is held down + if (!IS_CTRL_BTN_HELD_LOCAL) { + IS_CTRL_BTN_HELD_LOCAL = true; + } + } + }) + tUp.addAction(() => { + if(MapPlayer.fromEvent()!.isLocal()) { + IS_CTRL_BTN_HELD_LOCAL = false; + } + }) +} + +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; +} + +/* + //Short form (1 dropset, 1 group item) + + Dark Troll + == [Permanent, LVL 1] == + Slipper of Agility + 15 + Ring of Health + + //Long form + + Dark Troll + == Drop 1 == + Slipper of Agility + 15 + Ring of Health + == Drop 2 == + Sentry Ward + Ring of Health + */ +function buildDropsInfoMsg(unit: Unit, drops: ItemDropSet[]): string { + let msg = `\n|cffffff00${unit.name}|r\n` + const hasManyItems = drops.flatMap(s => s.itemDrops.flatMap(d => d.getDropItemIds())).length > 10; + const itemsSeparator = hasManyItems ? ", " : "\n"; + + const groupDrop = getSingleGroupDrop(drops); + if (groupDrop != undefined) { + msg += `== |cff00ff00[${groupDrop.itemGroup.itemClass}, LVL ${groupDrop.itemGroup.itemLevel}]|r ==\n`; + msg += groupDrop.getDropItemIds().map(id => `${getItemById(id)!.name}`).join(itemsSeparator) + } else { + msg += drops.map((dropSet, i) => { + let m = `== |cff00ff00Drop ${i + 1}|r ==\n`; + m += dropSet.itemDrops.flatMap(d => d.getDropItemIds()) + .map(id => `${getItemById(id)!.name}`) + .join(itemsSeparator) + return m; + }).join("\n"); + } + + // You can break msg box with too big messages that contain newlines + if (msg.length > 600) { + msg = msg.substring(0, 600) + "..."; + } + + return msg; +} + +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 indicatorScale: number; + private isVisible: boolean; + private printLootTrigger?: Trigger; + private lootInfoMsg: string; + private updatePosTimer?: Timer; + + constructor(unit: Unit, itemDropSets: ItemDropSet[], indicatorEffect: Effect) { + this.unit = unit; + this.itemDropSets = itemDropSets; + this.indicatorEffect = indicatorEffect; + this.indicatorScale = indicatorEffect.scale; + this.isVisible = true; + this.lootInfoMsg = buildDropsInfoMsg(unit, itemDropSets); + } + + 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.enablePrintLootOnSelection(); + indicator.enableFollowUnit(); + return indicator; + } + + hide() { + if (!this.isVisible) return; + this.isVisible = false; + + this.indicatorEffect.scale = 0; + } + + show() { + if (this.isVisible) return; + this.isVisible = true; + + this.indicatorEffect.scale = this.indicatorScale; + } + + destroy() { + this.updatePosTimer?.destroy(); + this.indicatorEffect.destroy(); + this.printLootTrigger?.destroy(); + } + + private enablePrintLootOnSelection() { + this.printLootTrigger = Trigger.create(); + this.printLootTrigger.registerAnyUnitEvent(EVENT_PLAYER_UNIT_SELECTED); + this.printLootTrigger.addCondition(() => Unit.fromEvent()?.handle === this.unit.handle); + this.printLootTrigger.addAction(() => { + const player = MapPlayer.fromEvent()!; + if (player.isLocal() && IS_CTRL_BTN_HELD_LOCAL) { + DisplayTimedTextToPlayer(player.handle, 0, 0, 5, this.lootInfoMsg); + } + }); + } + + private enableFollowUnit() { + this.updatePosTimer = Timer.create()!; + this.updatePosTimer.start(0.01, true, () => { + if(!this.isVisible) return; + + 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..d28f618 --- /dev/null +++ b/src/player_features/loot-indicator/modules/items-db.ts @@ -0,0 +1,172 @@ +import {ItemClass, ItemGroup, itemGroup2FourCC, VALID_LEVELS} from "./item-groups"; +import {File, Item, Rectangle, Timer} from "w3ts"; +import {Items} from "@objectdata/items"; + +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() { + const wb = Rectangle.getWorldBounds()!; + const x = wb.minX; + const y = wb.minY; + + for (const itemId of Object.keys(COMPILETIME_ITEM_DATA)) { + const item = Item.create(FourCC(itemId), x, y)!; + 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..ddfdd19 --- /dev/null +++ b/src/player_features/loot-indicator/modules/loot-table-ui.ts @@ -0,0 +1,125 @@ +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_LootTableUI_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, 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) + itemBtn.setTooltip("MY-TITLE\n\nasdf dasfdasf asdfdasfasdf asd fdas fasd fdsa fsd fasd fdas fasdf das fasf das fasfdsa fsdfdsfsdf asdf das fas fasdf das fas fsd fasdf as df", + "HELLO WORLD!\n\nAsdf asdf ads fasdf asd fdasf asd f sdaf asd fasd fas fasasdfasdfasdfadsf asdfasdfas dfasd fasdf fdas fasdf das fasd fas dfasd fasd fdas fasd fdas fas fdas fdas fasd fasd fasf") + this.itemBtnList.push(itemBtn) + } + + this.hide() + } + + show(itemIds: string[]) { + this.mainParent.setVisible(true) + + if (itemIds.length > LootTableUI.MAX_ITEMS) { + //Usually maps use preset drop tables that max out at 10 items, + // but who knows what maps might do in the future. + // Can't fit more than 12 right now. + 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(btnOwner: Frame, tooltipOwner: 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", btnOwner, 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", tooltipOwner, 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..0f89d96 --- /dev/null +++ b/src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator.ts @@ -0,0 +1,84 @@ +import {MapPlayer, Rectangle, 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)); + + 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 + const wb = Rectangle.getWorldBounds()!; + const u = Unit.create(MapPlayer.fromIndex(PLAYER_NEUTRAL_AGGRESSIVE)!, FourCC(Units.BlueDrake), wb.minX, wb.minY)!; + 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..14fe654 --- /dev/null +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -0,0 +1,131 @@ +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, () => true); + if (g.size != 1) { + //size=0 can happen when player spawn is at a camp (e.g., on 4 player map, like LostTemple) + //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. + + // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) + g.destroy() + return; + } + const u = g.getUnitAt(0)!; + g.destroy(); + return u; +} + +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/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": [ From 3537a1c42472f8fd185b1e3aa3f3ed28644f5d1e Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Mon, 22 Sep 2025 12:09:27 +0300 Subject: [PATCH 02/26] Add new features to `-commands` msg --- src/showCommands.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/showCommands.ts b/src/showCommands.ts index 735a173..4bd49b4 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 -cli|r to show/hide creep loot indicator.\n` + + ` |cffffff00•|r Type|cffffff00 -clp|r to show/hide creep loot preview.` TriggerAddAction(showCommandsTrigger, () => { DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, 10, commands); From 3cb4e5b363f99972ac0c2a904681f0061b4ab997 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Mon, 22 Sep 2025 18:52:02 +0300 Subject: [PATCH 03/26] Add README short summary --- src/player_features/loot-indicator/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/player_features/loot-indicator/README.md diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md new file mode 100644 index 0000000..d14b55f --- /dev/null +++ b/src/player_features/loot-indicator/README.md @@ -0,0 +1,11 @@ +# 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 item info extracted from in-game files ([extracted-items-data.json](../../../scripts/loot-indicator/items-db/extracted-items-data.json)) From 3b27de141fa39e958a3e5e93b7ae22855bbef65c Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Mon, 22 Sep 2025 18:54:50 +0300 Subject: [PATCH 04/26] clarification --- src/player_features/loot-indicator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index d14b55f..0d181b4 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -8,4 +8,4 @@ To properly position indicator over the health bar, we use per-model height data 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 item info extracted from in-game files ([extracted-items-data.json](../../../scripts/loot-indicator/items-db/extracted-items-data.json)) +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)) From 6df4d13721216372ca141fe40fe94370f67d8690 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 10:27:05 +0300 Subject: [PATCH 05/26] Remove `./dist` at `compileMap()` Sometimes non clean dist causes issues when modifying a map in WE and rerunning `compileMap()` (e.g. using `test` build) --- scripts/utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/utils.ts b/scripts/utils.ts index 18f00c5..ebe9148 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -96,6 +96,9 @@ export function compileMap(config: IProjectConfig) { return false; } + logger.info("Cleaning dist directory..."); + fs.removeSync("./dist"); + const tsLua = "./dist/tstl_output.lua"; if (fs.existsSync(tsLua)) { From 08e69906ec181cb28bb87cde685d7474fc5846fc Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 10:46:59 +0300 Subject: [PATCH 06/26] Fix indicator showing on invisible units --- .../loot-indicator/loot-indicator.ts | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/player_features/loot-indicator/loot-indicator.ts b/src/player_features/loot-indicator/loot-indicator.ts index ca5db58..158d609 100644 --- a/src/player_features/loot-indicator/loot-indicator.ts +++ b/src/player_features/loot-indicator/loot-indicator.ts @@ -58,7 +58,7 @@ function saveIsPreviewEnabled(isEnabled: boolean) { function createIndicators(unitsWithDrops: UnitItemDrop[]) { for (const unitWithDrop of unitsWithDrops) { const indicator = UnitLootIndicator.create(unitWithDrop); - IS_INDICATOR_ENABLED_LOCAL ? indicator.show() : indicator.hide(); + IS_INDICATOR_ENABLED_LOCAL ? indicator.enable() : indicator.disable(); ACTIVE_INDICATORS.set(indicator.unit.handle, indicator); @@ -91,7 +91,7 @@ function enableIndicatorFeatureToggleChatCommand() { saveIsIndicatorEnabled(IS_INDICATOR_ENABLED_LOCAL); ACTIVE_INDICATORS.forEach(indicator => { - IS_INDICATOR_ENABLED_LOCAL ? indicator.show() : indicator.hide() + 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.`) } @@ -226,17 +226,17 @@ class UnitLootIndicator { private readonly indicatorEffect: Effect; private indicatorScale: number; - private isVisible: boolean; + private isEnabled: boolean; private printLootTrigger?: Trigger; private lootInfoMsg: string; - private updatePosTimer?: Timer; + private updateUnitTimer?: Timer; constructor(unit: Unit, itemDropSets: ItemDropSet[], indicatorEffect: Effect) { this.unit = unit; this.itemDropSets = itemDropSets; this.indicatorEffect = indicatorEffect; this.indicatorScale = indicatorEffect.scale; - this.isVisible = true; + this.isEnabled = true; this.lootInfoMsg = buildDropsInfoMsg(unit, itemDropSets); } @@ -264,26 +264,34 @@ class UnitLootIndicator { const indicator = new UnitLootIndicator(unit, itemDropSets, e); indicator.enablePrintLootOnSelection(); - indicator.enableFollowUnit(); + indicator.enableUpdateUnit(); return indicator; } - hide() { - if (!this.isVisible) return; - this.isVisible = false; + disable() { + if (!this.isEnabled) return; + this.isEnabled = false; - this.indicatorEffect.scale = 0; + this.hide(); } - show() { - if (this.isVisible) return; - this.isVisible = true; + 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.updatePosTimer?.destroy(); + this.updateUnitTimer?.destroy(); this.indicatorEffect.destroy(); this.printLootTrigger?.destroy(); } @@ -300,10 +308,18 @@ class UnitLootIndicator { }); } - private enableFollowUnit() { - this.updatePosTimer = Timer.create()!; - this.updatePosTimer.start(0.01, true, () => { - if(!this.isVisible) return; + 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); From 62bd3314c9a2d7d162dd5fe91fb4d4fda6a2eca5 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 10:48:58 +0300 Subject: [PATCH 07/26] Fix indicator too low on Murloc Flesheater --- .../modules/unit-hp-bar-position-calculator.ts | 5 +++++ 1 file changed, 5 insertions(+) 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 index 0f89d96..e3dfc51 100644 --- 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 @@ -35,6 +35,11 @@ const UNITS_MODEL_HEIGHT: Record = compiletime(() => { 'ners', 'nerw' ].forEach((unitType => heights[unitType].sdHeight += 40)); + // Murlocs + [ + 'nmfs' + ].forEach((unitType => heights[unitType].sdHeight += 20)); + return heights }) as Record; From 2a5f65c62526657180c31215efcb32013c21289a Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 12:15:20 +0300 Subject: [PATCH 08/26] Fix "Elder Jungle Stalker" missing indicator "Elder Jungle Stalker" on 1v1_WarHail_v1.1.w3x. The data for position is not always precise, so we search in a bigger radius and pick the closest unit --- .../loot-indicator/modules/unit-item-drops.ts | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/player_features/loot-indicator/modules/unit-item-drops.ts b/src/player_features/loot-indicator/modules/unit-item-drops.ts index 14fe654..6e1ed3a 100644 --- a/src/player_features/loot-indicator/modules/unit-item-drops.ts +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -79,22 +79,60 @@ const RAW_UNIT_ITEM_DROPS = compiletime(() => { function findUnitAtPoint(p: Point): Unit | undefined { const g = Group.create()!; g.enumUnitsInRangeOfPoint(p, 1, () => true); - if (g.size != 1) { - //size=0 can happen when player spawn is at a camp (e.g., on 4 player map, like LostTemple) + 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, () => true) + } + + //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. - - // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) - g.destroy() + g.destroy(); return; } - const u = g.getUnitAt(0)!; + + 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())); } From adbc88b01986b4bb24d2c2a3cbe9792a2c7a9228 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 12:19:51 +0300 Subject: [PATCH 09/26] comment `print` --- src/player_features/loot-indicator/modules/unit-item-drops.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/player_features/loot-indicator/modules/unit-item-drops.ts b/src/player_features/loot-indicator/modules/unit-item-drops.ts index 6e1ed3a..c33ca30 100644 --- a/src/player_features/loot-indicator/modules/unit-item-drops.ts +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -102,7 +102,7 @@ function findUnitAtPoint(p: Point): Unit | undefined { 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}.`) + // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) u = getClosestUnit(g, p); } From 4eec64d4756f6c9631513cc2200c0424c002e7ab Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 12:29:19 +0300 Subject: [PATCH 10/26] Fix updateMaps.sh --- updateMaps.sh | 2 ++ 1 file changed, 2 insertions(+) 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 From 89dc9fbd45ad738d9ba358c47d3ee5205cdfce8d Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 19:30:26 +0300 Subject: [PATCH 11/26] Fix loot indicator appearing on peasants if starting location is on a creep camp --- src/player_features/loot-indicator/modules/unit-item-drops.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/player_features/loot-indicator/modules/unit-item-drops.ts b/src/player_features/loot-indicator/modules/unit-item-drops.ts index c33ca30..f7fa576 100644 --- a/src/player_features/loot-indicator/modules/unit-item-drops.ts +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -78,13 +78,13 @@ const RAW_UNIT_ITEM_DROPS = compiletime(() => { function findUnitAtPoint(p: Point): Unit | undefined { const g = Group.create()!; - g.enumUnitsInRangeOfPoint(p, 1, () => true); + 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, () => true) + 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) From ac924c6a5ebe8659c71c18bcba7f7bfc99378f00 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 20:05:51 +0300 Subject: [PATCH 12/26] Fix a crash caused by spawning a unit at world bounds On WitheringFields, when creating a unit at WorldBounds (minX, minY) causes a crash. Not sure why only on this map, but hopefully it is related to being part of non-playable part of the map. Changed spawning at (0,0) instead. --- src/player_features/loot-indicator/modules/items-db.ts | 6 +----- .../modules/unit-hp-bar-position-calculator.ts | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/player_features/loot-indicator/modules/items-db.ts b/src/player_features/loot-indicator/modules/items-db.ts index d28f618..b6f72e0 100644 --- a/src/player_features/loot-indicator/modules/items-db.ts +++ b/src/player_features/loot-indicator/modules/items-db.ts @@ -41,12 +41,8 @@ export function initItemsDB() { } function createAllItemsDB() { - const wb = Rectangle.getWorldBounds()!; - const x = wb.minX; - const y = wb.minY; - for (const itemId of Object.keys(COMPILETIME_ITEM_DATA)) { - const item = Item.create(FourCC(itemId), x, y)!; + const item = Item.create(FourCC(itemId), 0, 0)!; if(item == undefined) { print(`Failed to create an item with id: ${itemId}`) continue; 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 index e3dfc51..ecdda62 100644 --- 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 @@ -49,8 +49,7 @@ 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 - const wb = Rectangle.getWorldBounds()!; - const u = Unit.create(MapPlayer.fromIndex(PLAYER_NEUTRAL_AGGRESSIVE)!, FourCC(Units.BlueDrake), wb.minX, wb.minY)!; + const u = Unit.create(MapPlayer.fromIndex(PLAYER_NEUTRAL_AGGRESSIVE)!, FourCC(Units.BlueDrake), 0, 0)!; IS_REFORGED_UNIT_MODELS_ENABLED_LOCAL = getUnitModelScale(u) !== 1.2; u.destroy(); } From bfd00040c867be99c3a56d8602c7d5966b94ae97 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 23 Sep 2025 21:42:36 +0300 Subject: [PATCH 13/26] Fix temp unit creation is causing minimap creep camp icon to appear --- .../loot-indicator/modules/unit-hp-bar-position-calculator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index ecdda62..1a7d877 100644 --- 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 @@ -49,7 +49,8 @@ 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 - const u = Unit.create(MapPlayer.fromIndex(PLAYER_NEUTRAL_AGGRESSIVE)!, FourCC(Units.BlueDrake), 0, 0)!; + //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(); } From 1ebd3545413f0c20abe069f80bccaf3cfc75d8cd Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Wed, 24 Sep 2025 12:05:19 +0300 Subject: [PATCH 14/26] Add `Thanks` section --- src/player_features/loot-indicator/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index 0d181b4..0d3b784 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -3,9 +3,16 @@ 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), +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)) +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)) + +# Thanks to + +Coff, Mayday, Tasyen, ModdieMads, Kenshin, TriggerHappy, Luashine, Tordes, Starbuck! + +And a lot of other people from W3Champions and Hive communities! From 09308cd9be2e566652b4c18f99c1276bfabcd1ca Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Wed, 24 Sep 2025 13:20:44 +0300 Subject: [PATCH 15/26] Remove cleaning dist because it is modified at BUILD step (copy resources before compile map). ... --- scripts/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/utils.ts b/scripts/utils.ts index ebe9148..18f00c5 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -96,9 +96,6 @@ export function compileMap(config: IProjectConfig) { return false; } - logger.info("Cleaning dist directory..."); - fs.removeSync("./dist"); - const tsLua = "./dist/tstl_output.lua"; if (fs.existsSync(tsLua)) { From 32bca2fbf575e07e75c57474500d7eae44c0762b Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Wed, 24 Sep 2025 13:43:06 +0300 Subject: [PATCH 16/26] Add `Known issues` section --- src/player_features/loot-indicator/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index 0d3b784..6057eec 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -11,8 +11,15 @@ 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)) + # Thanks to Coff, Mayday, Tasyen, ModdieMads, Kenshin, TriggerHappy, Luashine, Tordes, Starbuck! And a lot of other people from W3Champions and Hive communities! + + +# 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) From 36686f65123b3bbffbd3d734095818d1c74f4d62 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Wed, 24 Sep 2025 14:00:20 +0300 Subject: [PATCH 17/26] Clarify loot preview command --- src/showCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/showCommands.ts b/src/showCommands.ts index 4bd49b4..3c5f002 100644 --- a/src/showCommands.ts +++ b/src/showCommands.ts @@ -23,7 +23,7 @@ export function enableShowCommandsTrigger() { ` |cffffff00•|r Type|cffffff00 -minimap|r to show/hide custom minimap icons.\n` + ` |cffffff00•|r Type|cffffff00 -clock|r to show/hide clock.\n` + ` |cffffff00•|r Type|cffffff00 -cli|r to show/hide creep loot indicator.\n` + - ` |cffffff00•|r Type|cffffff00 -clp|r to show/hide creep loot preview.` + ` |cffffff00•|r Type|cffffff00 -clp|r to show/hide creep loot preview on selection.` TriggerAddAction(showCommandsTrigger, () => { DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, 10, commands); From ce1cf88dc3405eb4af282b1d9cc9f998f478129a Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 00:07:58 +0300 Subject: [PATCH 18/26] Remove "print loot" on ctrl+click (if pressing ctrl+enter, ctrl will 'stuck' - will be considered pressed) --- .../loot-indicator/loot-indicator.ts | 111 ++---------------- 1 file changed, 11 insertions(+), 100 deletions(-) diff --git a/src/player_features/loot-indicator/loot-indicator.ts b/src/player_features/loot-indicator/loot-indicator.ts index 158d609..e05574b 100644 --- a/src/player_features/loot-indicator/loot-indicator.ts +++ b/src/player_features/loot-indicator/loot-indicator.ts @@ -8,8 +8,7 @@ import { UnitItemDrop } from "./modules/unit-item-drops"; import {ItemClass} from "./modules/item-groups"; -import {getItemById, initItemsDB} from "./modules/items-db"; -import {METAKEY_CTRL, METAKEY_NONE} from "./modules/util"; +import {initItemsDB} from "./modules/items-db"; import { calcUnitHpBarPosition, initIsReforgedUnitModelsEnabledLocal, @@ -19,20 +18,17 @@ 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 IS_CTRL_BTN_HELD_LOCAL = false; let ACTIVE_INDICATORS = new Map(); export function enableCreepLootIndicator() { initItemsDB(); initIsReforgedUnitModelsEnabledLocal(); - IS_INDICATOR_ENABLED_LOCAL = loadIsIndicatorEnabled(); + IS_INDICATOR_ENABLED_LOCAL = loadIsIndicatorEnabled(); const unitsWithDrops = findMapInitialCreepsWithDrops(); createIndicators(unitsWithDrops); - enableIndicatorFeatureToggleChatCommand() - enableTrackCtrlBtnHeld(); IS_PREVIEW_ENABLED_LOCAL = loadIsPreviewEnabled(); enableLootTablePreviewUI(); @@ -86,7 +82,7 @@ function enableIndicatorFeatureToggleChatCommand() { } t.addAction(() => { const player = MapPlayer.fromEvent()!; - if(player.isLocal()) { + if (player.isLocal()) { IS_INDICATOR_ENABLED_LOCAL = !IS_INDICATOR_ENABLED_LOCAL; saveIsIndicatorEnabled(IS_INDICATOR_ENABLED_LOCAL); @@ -105,11 +101,11 @@ function enablePreviewFeatureToggleChatCommand() { } t.addAction(() => { const player = MapPlayer.fromEvent()!; - if(player.isLocal()) { + if (player.isLocal()) { IS_PREVIEW_ENABLED_LOCAL = !IS_PREVIEW_ENABLED_LOCAL; saveIsPreviewEnabled(IS_PREVIEW_ENABLED_LOCAL); - if(!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.`) @@ -117,29 +113,6 @@ function enablePreviewFeatureToggleChatCommand() { }) } -function enableTrackCtrlBtnHeld() { - const tDown = Trigger.create(); - const tUp = Trigger.create(); - for (let i = 0; i < bj_MAX_PLAYERS; i++) { - tDown.registerPlayerKeyEvent(MapPlayer.fromIndex(i)!, OSKEY_LCONTROL, METAKEY_CTRL, true); - tUp.registerPlayerKeyEvent(MapPlayer.fromIndex(i)!, OSKEY_LCONTROL, METAKEY_NONE, false); - } - - tDown.addAction(() => { - if(MapPlayer.fromEvent()!.isLocal()) { - //By default, the action is called multiple times while the button is held down - if (!IS_CTRL_BTN_HELD_LOCAL) { - IS_CTRL_BTN_HELD_LOCAL = true; - } - } - }) - tUp.addAction(() => { - if(MapPlayer.fromEvent()!.isLocal()) { - IS_CTRL_BTN_HELD_LOCAL = false; - } - }) -} - function getSingleGroupDrop(itemDropSets: ItemDropSet[]): RandomItemGroupDrop | undefined { if (itemDropSets.length === 1 && itemDropSets[0].itemDrops.length === 1 && itemDropSets[0].itemDrops[0] instanceof RandomItemGroupDrop) { @@ -157,51 +130,6 @@ function isTomeDrop(itemDrop: ItemDrop): boolean { return false; } -/* - //Short form (1 dropset, 1 group item) - - Dark Troll - == [Permanent, LVL 1] == - Slipper of Agility + 15 - Ring of Health - - //Long form - - Dark Troll - == Drop 1 == - Slipper of Agility + 15 - Ring of Health - == Drop 2 == - Sentry Ward - Ring of Health - */ -function buildDropsInfoMsg(unit: Unit, drops: ItemDropSet[]): string { - let msg = `\n|cffffff00${unit.name}|r\n` - const hasManyItems = drops.flatMap(s => s.itemDrops.flatMap(d => d.getDropItemIds())).length > 10; - const itemsSeparator = hasManyItems ? ", " : "\n"; - - const groupDrop = getSingleGroupDrop(drops); - if (groupDrop != undefined) { - msg += `== |cff00ff00[${groupDrop.itemGroup.itemClass}, LVL ${groupDrop.itemGroup.itemLevel}]|r ==\n`; - msg += groupDrop.getDropItemIds().map(id => `${getItemById(id)!.name}`).join(itemsSeparator) - } else { - msg += drops.map((dropSet, i) => { - let m = `== |cff00ff00Drop ${i + 1}|r ==\n`; - m += dropSet.itemDrops.flatMap(d => d.getDropItemIds()) - .map(id => `${getItemById(id)!.name}`) - .join(itemsSeparator) - return m; - }).join("\n"); - } - - // You can break msg box with too big messages that contain newlines - if (msg.length > 600) { - msg = msg.substring(0, 600) + "..."; - } - - return msg; -} - function enableLootTablePreviewUI() { LootTableUI.init(); @@ -209,9 +137,9 @@ function enableLootTablePreviewUI() { t.registerAnyUnitEvent(EVENT_PLAYER_UNIT_SELECTED); t.addAction(() => { const player = MapPlayer.fromEvent()!; - if(player.isLocal() && IS_PREVIEW_ENABLED_LOCAL) { + if (player.isLocal() && IS_PREVIEW_ENABLED_LOCAL) { const indicator = ACTIVE_INDICATORS.get(Unit.fromEvent()!.handle); - if(indicator !== undefined) { + if (indicator !== undefined) { LootTableUI.INSTANCE.show(getAllItemIds(indicator.itemDropSets)); } else { LootTableUI.INSTANCE.hide(); @@ -225,10 +153,8 @@ class UnitLootIndicator { readonly itemDropSets: ItemDropSet[]; private readonly indicatorEffect: Effect; - private indicatorScale: number; + private readonly indicatorScale: number; private isEnabled: boolean; - private printLootTrigger?: Trigger; - private lootInfoMsg: string; private updateUnitTimer?: Timer; constructor(unit: Unit, itemDropSets: ItemDropSet[], indicatorEffect: Effect) { @@ -237,7 +163,6 @@ class UnitLootIndicator { this.indicatorEffect = indicatorEffect; this.indicatorScale = indicatorEffect.scale; this.isEnabled = true; - this.lootInfoMsg = buildDropsInfoMsg(unit, itemDropSets); } static create(unitItemDrop: UnitItemDrop): UnitLootIndicator { @@ -256,14 +181,13 @@ class UnitLootIndicator { //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) { + if (unit.maxMana > 0) { e.playAnimation(ANIM_TYPE_STAND) } else { e.playAnimation(ANIM_TYPE_WALK) } const indicator = new UnitLootIndicator(unit, itemDropSets, e); - indicator.enablePrintLootOnSelection(); indicator.enableUpdateUnit(); return indicator; } @@ -293,28 +217,15 @@ class UnitLootIndicator { destroy() { this.updateUnitTimer?.destroy(); this.indicatorEffect.destroy(); - this.printLootTrigger?.destroy(); - } - - private enablePrintLootOnSelection() { - this.printLootTrigger = Trigger.create(); - this.printLootTrigger.registerAnyUnitEvent(EVENT_PLAYER_UNIT_SELECTED); - this.printLootTrigger.addCondition(() => Unit.fromEvent()?.handle === this.unit.handle); - this.printLootTrigger.addAction(() => { - const player = MapPlayer.fromEvent()!; - if (player.isLocal() && IS_CTRL_BTN_HELD_LOCAL) { - DisplayTimedTextToPlayer(player.handle, 0, 0, 5, this.lootInfoMsg); - } - }); } private enableUpdateUnit() { this.updateUnitTimer = Timer.create()!; this.updateUnitTimer.start(0.01, true, () => { - if(!this.isEnabled) return; + if (!this.isEnabled) return; //Handle invisible units (Murloc Nightcrawler) - if(!this.unit.isVisible(MapPlayer.fromLocal())) { + if (!this.unit.isVisible(MapPlayer.fromLocal())) { this.hide(); return; } else { From 26152e592f4e1b8c367a4f223400b29461d341b3 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 00:20:12 +0300 Subject: [PATCH 19/26] change commands names to "-loot*" --- src/player_features/loot-indicator/loot-indicator.ts | 4 ++-- src/showCommands.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/player_features/loot-indicator/loot-indicator.ts b/src/player_features/loot-indicator/loot-indicator.ts index e05574b..c560cad 100644 --- a/src/player_features/loot-indicator/loot-indicator.ts +++ b/src/player_features/loot-indicator/loot-indicator.ts @@ -78,7 +78,7 @@ function registerUnitItemDroppedEvent(unit: Unit, action: () => void) { function enableIndicatorFeatureToggleChatCommand() { const t = Trigger.create(); for (let i = 0; i < bj_MAX_PLAYERS; i++) { - t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-cli", true); + t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-looticon", true); } t.addAction(() => { const player = MapPlayer.fromEvent()!; @@ -97,7 +97,7 @@ function enableIndicatorFeatureToggleChatCommand() { function enablePreviewFeatureToggleChatCommand() { const t = Trigger.create(); for (let i = 0; i < bj_MAX_PLAYERS; i++) { - t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-clp", true); + t.registerPlayerChatEvent(MapPlayer.fromIndex(i)!, "-lootpreview", true); } t.addAction(() => { const player = MapPlayer.fromEvent()!; diff --git a/src/showCommands.ts b/src/showCommands.ts index 3c5f002..c5c0c60 100644 --- a/src/showCommands.ts +++ b/src/showCommands.ts @@ -22,8 +22,8 @@ export function enableShowCommandsTrigger() { ` |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.\n` + - ` |cffffff00•|r Type|cffffff00 -cli|r to show/hide creep loot indicator.\n` + - ` |cffffff00•|r Type|cffffff00 -clp|r to show/hide creep loot preview on selection.` + ` |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); From b0ef670d321bfd3a8184c3e506e2eaa485794c8b Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 00:26:03 +0300 Subject: [PATCH 20/26] Reformat code --- .../map-loot/map-loot-parser.ts | 23 +++++++++---------- .../model-heights/model-height-generator.ts | 2 +- src/player_features/loot-indicator/README.md | 4 +--- .../loot-indicator/modules/items-db.ts | 12 +++++----- .../unit-hp-bar-position-calculator.ts | 4 ++-- .../loot-indicator/modules/unit-item-drops.ts | 8 +++---- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/scripts/loot-indicator/map-loot/map-loot-parser.ts b/scripts/loot-indicator/map-loot/map-loot-parser.ts index 01e81d3..0237ff9 100644 --- a/scripts/loot-indicator/map-loot/map-loot-parser.ts +++ b/scripts/loot-indicator/map-loot/map-loot-parser.ts @@ -1,9 +1,8 @@ import UnitsDoo from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/file.js"; -import Unit from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/unitsdoo/unit.js"; import War3MapW3i from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/w3i/file.js"; import * as fs from "fs-extra"; -import type {RawItemDropSet, RawUnitItemDrop} from "../../../src/loot-indicator/modules/unit-item-drops"; 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') @@ -16,9 +15,9 @@ export function getMapItemDrops(mapPath: string): RawUnitItemDrop[] { //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) { + if (unit.droppedItemTable >= 0) { const itemTable = war3MapW3i.randomItemTables[unit.droppedItemTable]; - if(itemTable !== undefined) { + if (itemTable !== undefined) { sets.push(...itemTable.sets) } else { console.warn(`Item Table idx ${unit.droppedItemTable} is not found in map file`) @@ -29,11 +28,11 @@ export function getMapItemDrops(mapPath: string): RawUnitItemDrop[] { // 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) { + 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] } + const unitLocation = {x: unit.location[0], y: unit.location[1]} rawDrops.push({unitLocation, itemSets}); } } @@ -42,13 +41,13 @@ export function getMapItemDrops(mapPath: string): RawUnitItemDrop[] { } function loadUnitsDoo(mapPath: string) { - const war3MapW3i = new War3MapW3i(); - war3MapW3i.load(fs.readFileSync(`${mapPath}/war3map.w3i`)) - // writeAsJson(`${mapPath}/war3map.w3i.json`, war3MapW3i); + 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}; + const unitsDoo = new UnitsDoo(); + unitsDoo.load(fs.readFileSync(`${mapPath}/war3mapUnits.doo`), war3MapW3i.getBuildVersion()) + return {unitsDoo, war3MapW3i}; } function writeAsJson(path: string, data: any) { diff --git a/scripts/loot-indicator/model-heights/model-height-generator.ts b/scripts/loot-indicator/model-heights/model-height-generator.ts index 05ac39c..fb40ac7 100644 --- a/scripts/loot-indicator/model-heights/model-height-generator.ts +++ b/scripts/loot-indicator/model-heights/model-height-generator.ts @@ -3,7 +3,7 @@ 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 type {UnitModelHeight} from "../../../src/loot-indicator/modules/unit-hp-bar-position-calculator"; +import {UnitModelHeight} from "../../../src/player_features/loot-indicator/modules/unit-hp-bar-position-calculator"; const WC3_GAME_BASE_DIRECTORY = "D:\\Games\\Warcraft III"; diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index 6057eec..9cf4622 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -8,17 +8,15 @@ To properly position indicator over the health bar, we use per-model height data 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 +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)) - # Thanks to Coff, Mayday, Tasyen, ModdieMads, Kenshin, TriggerHappy, Luashine, Tordes, Starbuck! And a lot of other people from W3Champions and Hive communities! - # Known issues * Indicator appears slightly higher on creeps in shallow water diff --git a/src/player_features/loot-indicator/modules/items-db.ts b/src/player_features/loot-indicator/modules/items-db.ts index b6f72e0..a66e22f 100644 --- a/src/player_features/loot-indicator/modules/items-db.ts +++ b/src/player_features/loot-indicator/modules/items-db.ts @@ -1,6 +1,5 @@ import {ItemClass, ItemGroup, itemGroup2FourCC, VALID_LEVELS} from "./item-groups"; -import {File, Item, Rectangle, Timer} from "w3ts"; -import {Items} from "@objectdata/items"; +import {Item} from "w3ts"; interface CompiletimeItem { includeAsRandomChoice: boolean; @@ -43,13 +42,13 @@ export function initItemsDB() { function createAllItemsDB() { for (const itemId of Object.keys(COMPILETIME_ITEM_DATA)) { const item = Item.create(FourCC(itemId), 0, 0)!; - if(item == undefined) { + if (item == undefined) { print(`Failed to create an item with id: ${itemId}`) continue; } const itemClass = inferItemClass(item); - if(itemClass == undefined) { + if (itemClass == undefined) { item.destroy(); continue; } @@ -78,7 +77,7 @@ function createAllItemsDB() { 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) { + if (!item.includeAsRandomChoice) { continue; } @@ -150,7 +149,8 @@ function inferItemClass(item: Item) { } //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; + case ITEM_TYPE_UNKNOWN: + return ItemClass.Misc; default: { print(`Item ${item.name} (${item.typeId}) has unexpected type!`); return; 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 index 1a7d877..691a008 100644 --- 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 @@ -1,4 +1,4 @@ -import {MapPlayer, Rectangle, Unit} from "w3ts"; +import {MapPlayer, Unit} from "w3ts"; import {Units} from "@objectdata/units"; import {getUnitModelScale, id2FourCC} from "./util"; @@ -61,7 +61,7 @@ export function isReforgedUnitModelsEnabledLocal(): boolean { function getUnitModelHeight(unitId: number): number { const model = UNITS_MODEL_HEIGHT[id2FourCC(unitId)]; - if(isReforgedUnitModelsEnabledLocal()) { + if (isReforgedUnitModelsEnabledLocal()) { return model.hdHeight; } else { return model.sdHeight; diff --git a/src/player_features/loot-indicator/modules/unit-item-drops.ts b/src/player_features/loot-indicator/modules/unit-item-drops.ts index f7fa576..0c4f373 100644 --- a/src/player_features/loot-indicator/modules/unit-item-drops.ts +++ b/src/player_features/loot-indicator/modules/unit-item-drops.ts @@ -99,9 +99,9 @@ function findUnitAtPoint(p: Point): Unit | undefined { } let u: Unit; - if(g.size == 1) { + if (g.size == 1) { u = g.getUnitAt(0); - } else if(g.size > 1) { + } else if (g.size > 1) { // print(`P(${p.x}, ${p.y}) should point to 1 unit, but found ${g.size}.`) u = getClosestUnit(g, p); } @@ -117,7 +117,7 @@ function getClosestUnit(g: Group, p: Point) { for (let i = 1; i < g.size; i++) { const u = g.getUnitAt(i); const dist = calcDistance(u, p) - if(dist < closestDist) { + if (dist < closestDist) { closestDist = dist; closestUnit = u; } @@ -160,7 +160,7 @@ export function findMapInitialCreepsWithDrops(): UnitItemDrop[] { //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; + if (getAllItemIds(dropSets).length === 0) continue; unitItemDrops.push({unit, dropSets}); } From e6bc8478022998c071b4da82bd3bb2a3d2bc9ea3 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 00:44:02 +0300 Subject: [PATCH 21/26] Add more known issues --- src/player_features/loot-indicator/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index 9cf4622..41ac382 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -21,3 +21,5 @@ And a lot of other people from W3Champions and Hive communities! * 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) From 34a8e68a26cab19f8c11e50a9786bcececeebf47 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 12:24:10 +0300 Subject: [PATCH 22/26] Fix `indicator height` on Infernal --- .../modules/unit-hp-bar-position-calculator.ts | 5 +++++ 1 file changed, 5 insertions(+) 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 index 691a008..ef72496 100644 --- 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 @@ -40,6 +40,11 @@ const UNITS_MODEL_HEIGHT: Record = compiletime(() => { 'nmfs' ].forEach((unitType => heights[unitType].sdHeight += 20)); + // Infernal + [ + 'ninf' + ].forEach((unitType => heights[unitType].sdHeight += 260)); + return heights }) as Record; From 96d42d70a7bc042740e9577f219a538cd2c92c25 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 19:46:34 +0300 Subject: [PATCH 23/26] Add `observer mode` section to readme --- src/player_features/loot-indicator/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/player_features/loot-indicator/README.md b/src/player_features/loot-indicator/README.md index 41ac382..8a0033f 100644 --- a/src/player_features/loot-indicator/README.md +++ b/src/player_features/loot-indicator/README.md @@ -11,11 +11,10 @@ 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)) -# Thanks to +# Observer mode -Coff, Mayday, Tasyen, ModdieMads, Kenshin, TriggerHappy, Luashine, Tordes, Starbuck! - -And a lot of other people from W3Champions and Hive communities! +* Indicator will be shown if the observer had `-looticon` enabled before the game +* Loot preview (drop table) is not shown # Known issues @@ -23,3 +22,9 @@ And a lot of other people from W3Champions and Hive communities! * 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! From 6a49ace764d78b209589c9b503a13871bbd56967 Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Thu, 25 Sep 2025 20:34:44 +0300 Subject: [PATCH 24/26] Change to Kenshin icons --- .../loot-indicator/loot-indicator-generic.dds | Bin 22000 -> 5616 bytes .../loot-indicator/loot-indicator-tome.dds | Bin 5616 -> 5616 bytes .../loot-indicator/loot-indicator-generic.dds | Bin 22000 -> 5616 bytes .../loot-indicator/loot-indicator-tome.dds | Bin 5616 -> 5616 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/main/loot-indicator/loot-indicator-generic.dds b/assets/main/loot-indicator/loot-indicator-generic.dds index ac6c9987324a3f4882785bb9b3e1c94a83b51fb3..74257727a1d69c2da46f97cffb0717618637d1b5 100644 GIT binary patch literal 5616 zcmd^@PiP!<6vyB0x^$AVG#&yy4%kp@2#d;6dMKU5T-Jm5XJ>bmO)}71LD)-3Skq*n zc=2Kpf`dRO~@;+e1mS7im2-4N5klU4K8{+uiq@$)xEvh_R9% z`S;oPe!us=Z+`zKgM)AB69BNMs}l_TP2bQ35W!!2=;9%h>d> zg!D%T@JnJN#EFH04oG7y0LA~q6$zf()I9*_`sQ&f^ zfL~2h*=?a~S5F4=Z-!I;Eqr1&aJ*D1(ZTVNaYmL}yB3xfc|k6XvYqlx&Xnhv0rm7< zJUXMeO+HoOh;eM}PvZ?^W2X3GSu8W_!rfo+{Ces_$Ms`X*}h%9-aT4x)4kkZ7sh)L zh^l%~d^s;4vYmKgeqo;3#(aBv`g)j2PaihscKK{qe>fk^w}Ry`I3BJR$ex35gKU<- zV+Ne>=fRr_GOUc4%v8_4M^hE%aJ;8YKk4xb8I?bAcPbv|{INb-zF^zT?Vd;2PW6aJ z;?vy!zt&Isz%VS%=Mnj8@pwmq?S`;52cl|zQh(FT7uX)|A7%shn{LzJU$7n;#A0gv zou2=czhznZt@6j`1BrNC>7~=*<6Ktx+p_?LfS$14cC6QQtgk=6c3!`(U{X8`suuwD6X%w9tZc+$?M zm)^aFk9+qnNp(6ij%Xt47R zvxDPF*t_L;6mO(ZQ1=ZN8N(}oLwMgChQA+G@jiYrXL&q+BPsmBtmAzIw~N4j?C$S~ z`F0!6i{9kH!|YG-F#MMB$-h2PpJ0ET_8V|)W_mv;X+NG~ZoGYt2kt{Sel!uEV>^v^ z@bIx?%uf`*UjI({3mvR{R@IyCFR|zTJkEBg0Oaz+>U{Uo8}Da^aMRIPB*Omf;yak{ zZE^c9`$NspvKdS1C%yfTdB3FhQG6aL?vLl=kx0yce+tJ3oM(x|7b^cM-6x@>*b6c3 z_YVSgV!45Jtuw^I@hD!QP&6G0_AOk3Qr;}neBXJ#l)y(c3m(qF$jvdJTNpp5Bb zeodabxpC&n}qe2a3u6#F!4RNLZmE zj@Mp(sdV;8q-lYv*w%`8kz#;8`67Yb@e`c_-XnNDfdC<2KwJL h;g_FqGsUmd*Ip~jpJi!oj@B)}*Vn(fwm7yp_BWE<{qX<* literal 22000 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 D0+p{_ diff --git a/assets/main/loot-indicator/loot-indicator-tome.dds b/assets/main/loot-indicator/loot-indicator-tome.dds index c905932ae1c064dc728767723b21c0f3bb785a32..5d56be6dc7550ca783127ab9c6e8fd0ae5bb67e0 100644 GIT binary patch literal 5616 zcmd^DO=uHQ5S}CrZpGk=1Uzm+qbMkeprClz)OtukNHyCAtv1$+BFaLPf?aKdR!T2Q zX+4N26vQ9!SV6%*dJq&95B`AIUIY)K4Wa~;y3TAmZ#Uc8ZM5~!J`&Pz=e?Qt&ClC; zZD`o77yz(DUJ5Gy(ida^AO0+%zpEGu&aJ_3LpHN%x@D5API+uXs=p3DCJquVN=&>h z_L^q#UrgBIO_48;vL68An3S5vepz_f&DV|NM&fw1H`>eokO>dn0JzqhH z`8DZp*@z(i?!XAabl{{^IbT7!b3M%;9JKY#yuY}y0{7o1_|R$^yuOTVT;>}cVtMua z0Sam^$Lrnt{}s!I0^wM2nDxs>V?FAeu<=xX3`KhdA20iJ!Cy~jTX{cT_PpdNWT$Cb znDxn4eWtUFr^=I(FHjlJ`Q?8tfgD7>7xMs_7CT?P_<6~TodDrY!4UH;b}G=CX?E0$ zl@GNnpDthLm%jq4p3bm+FFs!KcB}gBc#oq$eIu%IW=#4pA7VD1dcM2)y76D+f8Xrw#Ukhq!H>>At+^mDy|313Z4Qi+UumaV zf1LV(fPG#P-_}ee4hE~s_kZCZpT7U8!XMSf=YX`|;Xf;X+H8MPJ^1ia#QvI1zB~E5 z`JgFlus%1Ye53?r`9i~jFTnZo{SWElLu^kM8D8A?-xXhOAAN@*A2U;pl=1%?bA8Fj z(Re5%-WRgbE?M~~?n{-pm`_r^PX5;d;1BqU(C^kq_HtN=ercM`JsI@?`2=ySE&EVrL%wRH%72OsJtI2a5%r~t!+Ok z)jInHPy5Ko!T0eN>^~gmp_?zg|100m3Jh#sBlso^+2|T8jq!dc-ZS%;wyJ1^=ac^L ztBV4!->ZOD@E@|}5$I{m?PE;ukJ{RR=qKFqbla!+hC)}xd7q&3A8yVXV}B!^bj|m> zuu~t|)Ac>r0732@mY`|Axkvf_|MBe*Rnb`K0B&#MPQfhW(NbR{ZoiH3{nFE2h;hGV z`Tddt(ARa$_IE7e!@-#PAMDqg8Pok>aJWa*Lor_yQIqv5%VlFn#P{foSWoe8(LP;g zIqAExW$OmUvQZaNan$49$%U;SQT`r)@u_iXqlpf+@#BOO)BGPD0NYI{l8U>p+W~$a z>12HIc@kj!-siPHP?++=&bX)cEgw=j6458&k!JWTP^ XKhV_Op>+6%hw(X%l~=!AB0IkU0w@1S literal 5616 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 literal 22000 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 D0+p{_ diff --git a/assets/roc/loot-indicator/loot-indicator-tome.dds b/assets/roc/loot-indicator/loot-indicator-tome.dds index c905932ae1c064dc728767723b21c0f3bb785a32..5d56be6dc7550ca783127ab9c6e8fd0ae5bb67e0 100644 GIT binary patch literal 5616 zcmd^DO=uHQ5S}CrZpGk=1Uzm+qbMkeprClz)OtukNHyCAtv1$+BFaLPf?aKdR!T2Q zX+4N26vQ9!SV6%*dJq&95B`AIUIY)K4Wa~;y3TAmZ#Uc8ZM5~!J`&Pz=e?Qt&ClC; zZD`o77yz(DUJ5Gy(ida^AO0+%zpEGu&aJ_3LpHN%x@D5API+uXs=p3DCJquVN=&>h z_L^q#UrgBIO_48;vL68An3S5vepz_f&DV|NM&fw1H`>eokO>dn0JzqhH z`8DZp*@z(i?!XAabl{{^IbT7!b3M%;9JKY#yuY}y0{7o1_|R$^yuOTVT;>}cVtMua z0Sam^$Lrnt{}s!I0^wM2nDxs>V?FAeu<=xX3`KhdA20iJ!Cy~jTX{cT_PpdNWT$Cb znDxn4eWtUFr^=I(FHjlJ`Q?8tfgD7>7xMs_7CT?P_<6~TodDrY!4UH;b}G=CX?E0$ zl@GNnpDthLm%jq4p3bm+FFs!KcB}gBc#oq$eIu%IW=#4pA7VD1dcM2)y76D+f8Xrw#Ukhq!H>>At+^mDy|313Z4Qi+UumaV zf1LV(fPG#P-_}ee4hE~s_kZCZpT7U8!XMSf=YX`|;Xf;X+H8MPJ^1ia#QvI1zB~E5 z`JgFlus%1Ye53?r`9i~jFTnZo{SWElLu^kM8D8A?-xXhOAAN@*A2U;pl=1%?bA8Fj z(Re5%-WRgbE?M~~?n{-pm`_r^PX5;d;1BqU(C^kq_HtN=ercM`JsI@?`2=ySE&EVrL%wRH%72OsJtI2a5%r~t!+Ok z)jInHPy5Ko!T0eN>^~gmp_?zg|100m3Jh#sBlso^+2|T8jq!dc-ZS%;wyJ1^=ac^L ztBV4!->ZOD@E@|}5$I{m?PE;ukJ{RR=qKFqbla!+hC)}xd7q&3A8yVXV}B!^bj|m> zuu~t|)Ac>r0732@mY`|Axkvf_|MBe*Rnb`K0B&#MPQfhW(NbR{ZoiH3{nFE2h;hGV z`Tddt(ARa$_IE7e!@-#PAMDqg8Pok>aJWa*Lor_yQIqv5%VlFn#P{foSWoe8(LP;g zIqAExW$OmUvQZaNan$49$%U;SQT`r)@u_iXqlpf+@#BOO)BGPD0NYI{l8U>p+W~$a z>12HIc@kj!-siPHP?++=&bX)cEgw=j6458&k!JWTP^ XKhV_Op>+6%hw(X%l~=!AB0IkU0w@1S literal 5616 zcmdT|eN0nV6u$)>uR63wo7ZF}zB0Sv;^Is!!Tkd`wy4=3Ou3~xm<6Jl7-w~>b2_D> zF3f3giPbE@bRi*buqK;JyJhATBC*jd(@caiF~PZHq7#uIE70QIImP?lqv8iyUEGZX ze%^P_$35RSKYyk4F(D+)Foop8Z~P$!LUiye4ga3NlHl7(@RM&Zw2pU7Fy!9%-_uxN zV`pIFb1f!9D5XXc2y9%??PVJ`0Cp7@WnZ!PGgudDCGsJqdd5acxO|cT-xCeS^Zz-% z%UB=E$K}cIx_+C>J3y&J9||1%t+FqtVg=|^Vr?_U^-b?oE+UXscBi8^Rgh^pPp0;Dvrn*EZY0D;_Q&e_wGv#4O~7kM2OAqaHqHK9=XI~X{hh2 z5|?#eWUz_Fy4dCe5-IYCM&DA`L1I%YbjF*c1Z#OvPgG_+pZ()A;NC_R2Y#bCT0{`3Nb7b(sy;;J;++AMi`0 zz+PO)`GNHDl`M?(smJK&^u*wW!zqLeaBK>>e1ep*PsI`WN$M{K3-%J?&gJ|JfC_Cz zianF9e|9Jj^6&cBIKMpb9z7&rjw5oOV6{$8=NRMlN$QXMnsc26oL{o_joZ7;Xyx=H z5DyNA)6VgG?_>0|899!~f0%Ts5F+>LRr2*e;;+uAF5~o%fIr#`ZOZ*8TYtK#(Y_9Q{ye!`lCIq7BzlH^J$@t>xcC~yFZ@3iS_E=*gqnd3eEy&Gv}Xd zeG}~M-`0rcQ^}_{C31oNSjB^e~I!u4j#|`mwE@!Gv;L! z`yWvhxqL`3cJuB{El#{oEk=2rZZ-ZA^=k+DDZQ)cH>=)E7EpRcr4JQMR^ti#A8mXg zeTya@9!FoTe1oUO;H~H_rGNE+JlOwav61sES`%xnl?0>lXpt(iDt5^(H8PfGCPCWl;?C}X1!jfgzS$`9Y%$Zql7NCBFiT_;9 z$kt8IH^6*UvdVQ@7-H+eTQ-nRf>I@;5%ZL8xcGzvHEtR*Y zFPPo&W;xVLSIH`ve@kKh?Udl|JvhMrI8<}DIi=(0)o}mCE}DhIf-b$ z^v`6C>aWmG3*g{N{hv6N63#(5PEekUwpqPa|rRuX%}jt zKcaNxnPC=fJ)GV#zZ~+1wS-P%-{QQl%)e7(fwleX>aP8O`Nwt4;_E(x1Xw(N8&!Y9 z`H?nE9G~c}jL+k7dN&9WzW9EmxiaQ8g-W4+wb{Sw8SKLO3-{CY`b7Uy;#CZ0F45Ls zF<1&6Z-!D2!tx|DSVX!1Y?$A1fa3NNgC`DyNAEt_$NF+WKQU+)oDPTM*1h=$`2+h0 z@{h=6hFnOv>14himh0eEN1MN~e^vQoe>awYz+X2dQ>lNL5ADS^ zB|klie;D*Rc(!~C=a26h+Wyf5^tAjF^+)V(b}04dQT!7(u zgRQmjrlrXbqx^erf$@iGz#e1uNBN8G4#LlGK41oa(M{(#&X4X=_Gi^YeVBYu6Ymmv zaq`3Z;Zgp+=m=xK)PVm%eCzdN=OxX12j0gvx9b0>pCA00%Y*f;jCLy+<=2hVpZMeP z+doHy`3>&hIW;!C>Cde4x3Ryn9HaD`Zg5Uk6NCN&F0N7WU$mFjpC5$p;Bm(0@csID z^m%9D{o(^cdXx>c9)GzLgGR`b*&$@4x5k#jkQKf_%BKTx{8exl7CLm_k*C)KvX|c7GS-gV??M2+Oa}dm)qb zZUu8UvCmJT{NUg3Y#%xkfB*UDT$Q1*s%*>7Smo^;ICH7J#dKqTy>I()+&^PKwU-L3 zhwB=*4 Date: Sun, 28 Sep 2025 20:47:26 +0300 Subject: [PATCH 25/26] cleanup --- package.json | 2 -- .../loot-indicator/modules/loot-table-ui.ts | 16 ++++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e1f5d3e..9f55f3a 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,7 @@ "war3-transformer": "github:Psimage/war3-transformer#fix-typescript-dependency", "war3tstlhelper": "1.0.1", "winston": "3.17.0", - "war3-types-strict": "0.1.3", - "mdx-m3-viewer-th": "5.13.1", "@jamiephan/casclib": "2.0.0-dev.5", "war3-model": "4.0.0" diff --git a/src/player_features/loot-indicator/modules/loot-table-ui.ts b/src/player_features/loot-indicator/modules/loot-table-ui.ts index ddfdd19..22d04a2 100644 --- a/src/player_features/loot-indicator/modules/loot-table-ui.ts +++ b/src/player_features/loot-indicator/modules/loot-table-ui.ts @@ -21,13 +21,11 @@ export class LootTableUI { return; } - this.mainParent = Frame.createType("LI_LootTableUI_Parent", Frame.fromOrigin(ORIGIN_FRAME_SIMPLE_UI_PARENT, 0)!, 0, "SIMPLEFRAME", "")!; + 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, this.mainParent, 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) - itemBtn.setTooltip("MY-TITLE\n\nasdf dasfdasf asdfdasfasdf asd fdas fasd fdsa fsd fasd fdas fasdf das fasf das fasfdsa fsdfdsfsdf asdf das fas fasdf das fas fsd fasdf as df", - "HELLO WORLD!\n\nAsdf asdf ads fasdf asd fdasf asd f sdaf asd fasd fas fasasdfasdfasdfadsf asdfasdfas dfasd fasdf fdas fasdf das fasd fas dfasd fasd fdas fasd fdas fas fdas fdas fasd fasd fasf") this.itemBtnList.push(itemBtn) } @@ -37,10 +35,8 @@ export class LootTableUI { show(itemIds: string[]) { this.mainParent.setVisible(true) + // Can't fit more than 12 right now. if (itemIds.length > LootTableUI.MAX_ITEMS) { - //Usually maps use preset drop tables that max out at 10 items, - // but who knows what maps might do in the future. - // Can't fit more than 12 right now. itemIds = itemIds.slice(0, LootTableUI.MAX_ITEMS) } @@ -76,15 +72,15 @@ class ItemBtn { tooltipSeparator: Frame; tooltipDescription: Frame; - constructor(btnOwner: Frame, tooltipOwner: Frame, createContext: number) { + 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", btnOwner, createContext)!; + 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", tooltipOwner, 0)!; + 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)!; From 868cfddef22903f81f5b2078a79a9dfd3d97c44c Mon Sep 17 00:00:00 2001 From: Yaroslav Buhaiev Date: Tue, 30 Sep 2025 15:40:25 +0300 Subject: [PATCH 26/26] Use fixed war3-transformer from NPM --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9f55f3a..db5c513 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,11 @@ "typescript": "5.8.2", "typescript-to-lua": "^1.31.0", "war3-objectdata-th": "^0.2.10", - "war3-transformer": "github:Psimage/war3-transformer#fix-typescript-dependency", + "war3-transformer": "3.0.10", "war3tstlhelper": "1.0.1", "winston": "3.17.0", "war3-types-strict": "0.1.3", - "mdx-m3-viewer-th": "5.13.1", + "mdx-m3-viewer-th": "5.13.3", "@jamiephan/casclib": "2.0.0-dev.5", "war3-model": "4.0.0" },