├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── dota2.d.ts ├── events.d.ts ├── index.ts ├── interfaces.d.ts ├── items.ts ├── items_prices.ts ├── parsed.d.ts └── utils.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "rules": { 13 | "no-console": 1, 14 | "@typescript-eslint/explicit-module-boundary-types": 0, 15 | "@typescript-eslint/ban-types": 0, 16 | "@typescript-eslint/no-explicit-any": 0, 17 | "@typescript-eslint/no-empty-function":0, 18 | "no-empty": 0, 19 | "@typescript-eslint/no-var-requires":0, 20 | "no-mixed-spaces-and-tabs": ["warn", "smart-tabs"], 21 | "@typescript-eslint/explicit-function-return-type":0, 22 | "@typescript-eslint/camelcase":0 23 | } 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | compiled 2 | node_modules 3 | coverage 4 | dist 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .eslintignore 4 | .eslintrc 5 | .prettierignore 6 | .gitignore 7 | .prettierrc 8 | __tests__ 9 | dist.browser 10 | tsc 11 | node_modules 12 | tsconfig.json 13 | tsconfig.esm.json 14 | jest.config.ts 15 | coverage 16 | compiled 17 | src 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi":true, 3 | "trailingComma":"none", 4 | "singleQuote":true, 5 | "printWidth":120, 6 | "useTabs":true, 7 | "tabWidth":4, 8 | "arrowParens":"avoid" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hubert Walczak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dota 2 GSI Digest 2 | 3 | ## How does it work? 4 | The GSI object takes raw request from Dota 2 GSI's system, parses this to more comfortable form and calls listeners on certain events. You need to configure GSI file and receiving end yourself. 5 | 6 | ## Installing 7 | ### For Node and React 8 | ```npm install dotagsi``` 9 | 10 | ## Example #1 11 | ```javascript 12 | import express from 'express'; 13 | import { DOTA2GSI } from 'dotagsi'; 14 | 15 | const app = express(); 16 | const GSI = new DOTA2GSI(); 17 | 18 | app.use(express.urlencoded({extended:true})); 19 | app.use(express.raw({limit:'10Mb', type: 'application/json' })); 20 | 21 | app.post('/', (req, res) => { 22 | const text = req.body.toString().replace(/"(player|owner)":([ ]*)([0-9]+)/gm, '"$1": "$3"').replace(/(player|owner):([ ]*)([0-9]+)/gm, '"$1": "$3"'); 23 | const data = JSON.parse(text); 24 | GSI.digest(data); 25 | res.sendStatus(200); 26 | }); 27 | 28 | GSI.on('data', dota2 => { 29 | dota2.draft.radiant[0].class; 30 | }); 31 | 32 | app.listen(3000); 33 | ``` 34 | 35 | ## Methods 36 | 37 | |Method|Description|Example|Returned objects| 38 | |---|---|---|---| 39 | |`digest(GSIData)`|Gets raw GSI data from Dota 2 and does magic|`GSI.digest(req.body)`|Dota 2 Parsed| 40 | |`on('event', callback)`|Sets listener for given event (check them below)|`GSI.on('data', data => console.log(data));`|| 41 | 42 | Beside that, DOTA2GSI implements standard Event Emitter interfaces. 43 | 44 | ## Events 45 | 46 | |Event|Name|Callback| 47 | |---|---|---| 48 | |Data incoming|`data`|(data: CSGO Parsed) => {}| 49 | 50 | 51 | ## Objects 52 | 53 | ### Faction 54 | `dire` or `radiant` 55 | 56 | ### Item Type 57 | `slot` or `stash` or `teleport` or `neutral` 58 | 59 | ### Wearable Type 60 | `wearable` or `style` 61 | #### Dota 2 Parsed 62 | 63 | |Property|Type| 64 | |---|---| 65 | |provider|`Provider`| 66 | |map|`Map`| 67 | |player|`Player or null`| 68 | |players|`Array of Players`| 69 | |buildings|`Array of Buldings`| 70 | |draft|`Draft`| 71 | |roshan|`Roshan`| 72 | |outposts|`Outposts`| 73 | |events|`Array of Events`| 74 | |neutral_items|`NeutralItems or null`| 75 | 76 | #### Team Extension 77 | 78 | |Property|Type| 79 | |---|---| 80 | |id|`string`| 81 | |name|`string`| 82 | |country|`string or null`| 83 | |logo|`string or null`| 84 | |map_score|`number`| 85 | 86 | #### Player Extension 87 | 88 | |Property|Type| 89 | |---|---| 90 | |id|`string`| 91 | |name|`string`| 92 | |steramid|`string`| 93 | |realName|`string or null`| 94 | |country|`string or null`| 95 | |avatar|`string or null`| 96 | 97 | 98 | ### Building 99 | 100 | |Property|Type| 101 | |---|---| 102 | |health|`number`| 103 | |max_health|`number`| 104 | |faction|`Faction`| 105 | |attack|`melee` or `range` or `null`| 106 | |type|`tower` or `rax` or `fort`| 107 | |side|`good` or `bad`| 108 | |position|`top` or `mid` or `bot` or `null`| 109 | |number|`number` or `null`| 110 | 111 | 112 | #### Provider 113 | 114 | |Property|Type| 115 | |---|---| 116 | |name|`Dota 2`| 117 | |appid|570| 118 | |version|`number`| 119 | |timestamp|`number`| 120 | 121 | #### Map 122 | 123 | |Property|Type| 124 | |---|---| 125 | |matchid|`string`| 126 | |name|`string`| 127 | |game_time|`number`| 128 | |clock_time|`number`| 129 | |daytime|`boolean`| 130 | |nightstalker_night|`boolean`| 131 | |game_state|`string`| 132 | |paused|`boolean`| 133 | |win_team|`string`| 134 | |customgamename|`string`| 135 | |roshan_state|`string`| 136 | |roshan_state_end_seconds|`number`| 137 | |radiant_win_chance|`number`| 138 | |radiant|`Team`| 139 | |dire|`Team`| 140 | 141 | #### Roshan 142 | 143 | |Property|Type| 144 | |---|---| 145 | |alive|`boolean`| 146 | |health|`number`| 147 | |max_health|`number`| 148 | |phase_time_remaining|`number`| 149 | |spawn_phase|`number`| 150 | |xpos|`number`| 151 | |ypos|`number`| 152 | |yaw|`number`| 153 | |items_drop|`Map or Item Names`| 154 | 155 | #### Outposts 156 | 157 | |Property|Type| 158 | |---|---| 159 | |outsideNorth|`Faction` or `null`| 160 | |jungleNorth|`Faction` or `null`| 161 | |jungleSouth|`Faction` or `null`| 162 | |outsideSouth|`Faction` or `null`| 163 | 164 | #### Event 165 | 166 | |Property|Type| 167 | |---|---| 168 | |event_type|`string`| 169 | |game_time|`number`| 170 | |various other values, depending on event_type|`unknown`| 171 | 172 | #### Neutral Items 173 | 174 | |Property|Type| 175 | |---|---| 176 | |team2|`Team Neutral Items Tiers`| 177 | |team3|`Team Neutral Items Tiers`| 178 | |tier0|`Neutral Items Tier Summary`| 179 | |tier1|`Neutral Items Tier Summary`| 180 | |tier2|`Neutral Items Tier Summary`| 181 | |tier3|`Neutral Items Tier Summary`| 182 | |tier4|`Neutral Items Tier Summary`| 183 | 184 | #### Neutral Items Tier Summary 185 | |Property|Type| 186 | |---|---| 187 | |tier|`number`| 188 | |max_count|`number`| 189 | |drop_after_time|`number`| 190 | 191 | #### Team Neutral Items Tiers 192 | 193 | |Property|Type| 194 | |---|---| 195 | |items_found|`number`| 196 | |tier0|`Neutral Items In Tier`| 197 | |tier1|`Neutral Items In Tier`| 198 | |tier2|`Neutral Items In Tier`| 199 | |tier3|`Neutral Items In Tier`| 200 | |tier4|`Neutral Items In Tier`| 201 | 202 | #### Team Neutral Items in Tier 203 | 204 | |Property|Type| 205 | |---|---| 206 | |item0|`Single Neutral Item`| 207 | |item1|`Single Neutral Item`| 208 | |item2|`Single Neutral Item`| 209 | |item3|`Single Neutral Item`| 210 | |item4|`Single Neutral Item`| 211 | |completion_time|`number or null`| 212 | 213 | #### Single Neutral Item 214 | 215 | |Property|Type| 216 | |---|---| 217 | |name|`string`| 218 | |tier|`number`| 219 | |state|`'stash' | 'unknown' | 'equipped'`| 220 | |player_id|`number or null`| 221 | 222 | #### Team 223 | 224 | |Property|Type| 225 | |---|---| 226 | |ward_purchase_cooldown|`number`| 227 | |name|`string`| 228 | |map_score|`number`| 229 | |extra|`Custom object`| 230 | |id|`string` or `null`| 231 | |country|`string` or `null`| 232 | |logo|`string` or `null`| 233 | 234 | #### Player 235 | 236 | |Property|Type| 237 | |---|---| 238 | |steamid|`string`| 239 | |name|`string`| 240 | |realName|`string` or `null`| 241 | |country|`string` or `null`| 242 | |avatar|`string` or `null`| 243 | |extra|`Custom object`| 244 | |hero|`Hero` or `null`| 245 | |courier|`Courier` or `null`| 246 | |abilities|`Array of Abilities`| 247 | |items|`Array of Items`| 248 | |wearables|`Array of Wearables`| 249 | |kill_list|`Array of KillEntries`| 250 | |activity|`string`| 251 | |kills|`number`| 252 | |deaths|`number`| 253 | |assists|`number`| 254 | |last_hits|`number`| 255 | |denies|`number`| 256 | |kill_streak|`number`| 257 | |commands_issued|`number`| 258 | |team_name|`string`| 259 | |gold|`number`| 260 | |gold_reliable|`number`| 261 | |gold_unreliable|`number`| 262 | |gold_from_hero_kills|`number`| 263 | |gold_from_creep_kills|`number`| 264 | |gold_from_income|`number`| 265 | |gold_from_shared|`number`| 266 | |gpm|`number`| 267 | |xpm|`number`| 268 | |net_worth|`number`| 269 | |hero_damage|`number`| 270 | |wards_purchased|`number`| 271 | |wards_placed|`number`| 272 | |wards_destroyed|`number`| 273 | |runes_activated|`number`| 274 | |camps_stacked|`number`| 275 | |support_gold_spent|`number`| 276 | |consumable_gold_spent|`number`| 277 | |item_gold_spent|`number`| 278 | |gold_lost_to_death|`number`| 279 | |gold_spent_on_buybacks|`number`| 280 | 281 | 282 | #### Hero 283 | 284 | |Property|Type| 285 | |---|---| 286 | |id|`number`| 287 | |xpos?|`number`| 288 | |ypos?|`number`| 289 | |level?|`number`| 290 | |name?|`string`| 291 | |xp?|`number`| 292 | |alive?|`boolean`| 293 | |respawn_seconds?|`number`| 294 | |buyback_cost?|`number`| 295 | |buyback_cooldown?|`number`| 296 | |health?|`number`| 297 | |max_health?|`number`| 298 | |health_percent?|`number`| 299 | |mana?|`number`| 300 | |max_mana?|`number`| 301 | |mana_percent?|`number`| 302 | |silenced?|`boolean`| 303 | |stunned?|`boolean`| 304 | |disarmed?|`boolean`| 305 | |magicimmune?|`boolean`| 306 | |hexed?|`boolean`| 307 | |muted?|`boolean`| 308 | |mana?|`boolean`| 309 | |break?|`boolean`| 310 | |aghanims_scepter|`boolean`| 311 | |aghanims_shard?|`boolean`| 312 | |smoked?|`boolean`| 313 | |has_debuff?|`boolean`| 314 | |selected_unit?|`boolean`| 315 | |talent_1?|`boolean`| 316 | |talent_2?|`boolean`| 317 | |talent_3?|`boolean`| 318 | |talent_4?|`boolean`| 319 | |talent_5?|`boolean`| 320 | |talent_6?|`boolean`| 321 | |talent_7?|`boolean`| 322 | |talent_8?|`boolean`| 323 | 324 | #### Courier 325 | 326 | |Property|Type| 327 | |---|---| 328 | |health|`number`| 329 | |max_health|`number`| 330 | |alive|`boolean`| 331 | |boost|`boolean`| 332 | |flying_upgrade|`boolean`| 333 | |shield|`boolean`| 334 | |respawn_time_remaining|`number`| 335 | |xpos|`number`| 336 | |ypos|`number`| 337 | |yaw|`number`| 338 | |items|`Array of Courier Item`| 339 | |lost_items|`Array of Courier Item`| 340 | |owner|`number`| 341 | |team?|`Faction`| 342 | 343 | #### Courier Item 344 | 345 | |Property|Type| 346 | |---|---| 347 | |owner|`number`| 348 | |name|`string`| 349 | 350 | #### Ability 351 | 352 | |Property|Type| 353 | |---|---| 354 | |id|`number`| 355 | |name|`string`| 356 | |level|`number`| 357 | |can_cast|`boolean`| 358 | |passive|`boolean`| 359 | |ability_active|`boolean`| 360 | |ultimate|`boolean`| 361 | |cooldown|`number`| 362 | |charges?|`number`| 363 | |max_charges?|`number`| 364 | |charge_cooldown?|`number`| 365 | 366 | #### Item 367 | 368 | |Property|Type| 369 | |---|---| 370 | |id|`number`| 371 | |name|`string`| 372 | |type|`Item Type`| 373 | |can_cast?|`boolean`| 374 | |passive?|`boolean`| 375 | |item_level?|`number`| 376 | |purchaser?|`number`| 377 | |cooldown?|`number`| 378 | |charges?|`number`| 379 | 380 | 381 | #### Draft 382 | 383 | |Property|Type| 384 | |---|---| 385 | |activeteam|`number`| 386 | |pick|`boolean`| 387 | |activeteam_time_remaining|`number`| 388 | |radiant|`Team Draft`| 389 | |dire|`Team Draft`| 390 | 391 | 392 | #### Team Draft 393 | 394 | |Property|Type| 395 | |---|---| 396 | |home_team|`boolean`| 397 | |bonus_time|`number`| 398 | |picks|`Array of Draft Entries`| 399 | 400 | #### Draft Entry 401 | 402 | |Property|Type| 403 | |---|---| 404 | |type|`pick` or `ban`| 405 | |player_id|`number`| 406 | |class|`string`| 407 | |order|`number`| 408 | 409 | 410 | #### Wearable 411 | 412 | |Property|Type| 413 | |---|---| 414 | |id|`number`| 415 | |type|`Wearable Type`| 416 | |value|`number`| 417 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dotagsi", 3 | "version": "1.5.4", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "devDependencies": { 9 | "@types/node": "^14.14.35", 10 | "@typescript-eslint/eslint-plugin": "^4.18.0", 11 | "@typescript-eslint/parser": "^4.18.0", 12 | "eslint": "^9.3.0", 13 | "prettier": "^2.2.1", 14 | "tsup": "^8.0.2", 15 | "typescript": "^5.4.5" 16 | }, 17 | "scripts": { 18 | "build": "tsup src/index.ts --format cjs,esm --dts", 19 | "lint": "eslint . --ext .ts", 20 | "prettier-format": "prettier --config .prettierrc --write **/*.ts", 21 | "post-compile": "npm run prettier-format && npm run lint" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/osztenkurden/dota2gsi.git" 26 | }, 27 | "author": "", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/osztenkurden/dota2gsi/issues" 31 | }, 32 | "homepage": "https://github.com/osztenkurden/dota2gsi#readme" 33 | } 34 | -------------------------------------------------------------------------------- /src/dota2.d.ts: -------------------------------------------------------------------------------- 1 | import type { Faction } from './interfaces'; 2 | 3 | export interface Dota2Raw { 4 | buildings?: Buildings; 5 | provider: Provider; 6 | map: MapRaw; 7 | player: Players; 8 | hero: Heros; 9 | abilities: Abilities; 10 | items: Items; 11 | draft: Draft; 12 | wearables: Wearables; 13 | minimap?: { [pointName: string]: MinimapPoint }; 14 | couriers?: { [courierName: string]: CourierRaw }; 15 | roshan?: Roshan; 16 | neutralitems?: NeutralItemsRaw; 17 | events?: GSIEvent[]; 18 | } 19 | 20 | interface Buildings { 21 | radiant: TeamBuildings; 22 | dire: TeamBuildings; 23 | } 24 | 25 | export interface BuildingInfo { 26 | health: number; 27 | max_health: number; 28 | } 29 | 30 | export type RadiantPlayerIds = 0 | 1 | 2 | 3 | 4; 31 | export type DirePlayerIds = 5 | 6 | 7 | 8 | 9; 32 | 33 | type PlayerIds = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; 34 | type SlotsIds = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 35 | 36 | type MapSides = 'top' | 'mid' | 'bot'; 37 | 38 | type AttackType = 'melee' | 'range'; 39 | 40 | type GuysType = 'good' | 'bad'; 41 | 42 | export type TeamBuildingsKeys = 43 | | `dota_${GuysType}guys_tower${1 | 2 | 3}_${MapSides}` 44 | | `dota_${GuysType}guys_tower4_${'top' | 'bot'}` 45 | | `${GuysType}_rax_${AttackType}_${MapSides}` 46 | | `dota_${GuysType}guys_fort`; 47 | 48 | type TeamBuildings = { 49 | [x in TeamBuildingsKeys]?: BuildingInfo | null; 50 | }; 51 | 52 | interface Provider { 53 | name: string; 54 | appid: number; 55 | version: number; 56 | timestamp: number; 57 | } 58 | export interface MapRaw { 59 | name: string; 60 | matchid: string; 61 | game_time: number; 62 | clock_time: number; 63 | daytime: boolean; 64 | nightstalker_night: boolean; 65 | game_state: string; 66 | paused: boolean; 67 | win_team: string; 68 | customgamename: string; 69 | radiant_ward_purchase_cooldown: number; 70 | dire_ward_purchase_cooldown: number; 71 | roshan_state: string; 72 | roshan_state_end_seconds: number; 73 | radiant_win_chance: number; 74 | } 75 | 76 | export type PlayerKey = `player${N}`; 77 | 78 | export type PlayerKeys = PlayerKey; 79 | 80 | type PlayerList = { 81 | [x in PlayerKey]?: T; 82 | }; 83 | 84 | type TeamPlayerList = { 85 | team2: PlayerList; 86 | team3: PlayerList; 87 | }; 88 | 89 | type Players = TeamPlayerList; 90 | 91 | type KillList = { 92 | [x in `victimid_${PlayerIds}`]?: number | null; 93 | }; 94 | 95 | export interface PlayerRaw { 96 | steamid: string; 97 | name: string; 98 | activity: string; 99 | kills: number; 100 | deaths: number; 101 | assists: number; 102 | last_hits: number; 103 | denies: number; 104 | kill_streak: number; 105 | commands_issued: number; 106 | kill_list: KillList; 107 | team_name: 'dire' | 'radiant'; 108 | gold: number; 109 | gold_reliable: number; 110 | gold_unreliable: number; 111 | gold_from_hero_kills: number; 112 | gold_from_creep_kills: number; 113 | gold_from_income: number; 114 | gold_from_shared: number; 115 | gpm: number; 116 | xpm: number; 117 | net_worth: number; 118 | hero_damage: number; 119 | wards_purchased: number; 120 | hero_healing: number; 121 | wards_placed: number; 122 | wards_destroyed: number; 123 | tower_damage: number; 124 | team_slot: number; 125 | player_slot: number; 126 | runes_activated: number; 127 | camps_stacked: number; 128 | support_gold_spent: number; 129 | consumable_gold_spent: number; 130 | item_gold_spent: number; 131 | gold_lost_to_death: number; 132 | gold_spent_on_buybacks: number; 133 | } 134 | 135 | export interface HeroRaw { 136 | id: number; 137 | facet?: number | null; 138 | xpos?: number | null; 139 | ypos?: number | null; 140 | name?: string | null; 141 | level?: number | null; 142 | xp?: number | null; 143 | alive?: boolean | null; 144 | respawn_seconds?: number | null; 145 | buyback_cost?: number | null; 146 | buyback_cooldown?: number | null; 147 | health?: number | null; 148 | max_health?: number | null; 149 | health_percent?: number | null; 150 | mana?: number | null; 151 | max_mana?: number | null; 152 | mana_percent?: number | null; 153 | silenced?: boolean | null; 154 | stunned?: boolean | null; 155 | disarmed?: boolean | null; 156 | magicimmune?: boolean | null; 157 | hexed?: boolean | null; 158 | muted?: boolean | null; 159 | break?: boolean | null; 160 | aghanims_scepter?: boolean | null; 161 | aghanims_shard?: boolean | null; 162 | smoked?: boolean | null; 163 | has_debuff?: boolean | null; 164 | selected_unit?: boolean | null; 165 | talent_1?: boolean | null; 166 | talent_2?: boolean | null; 167 | talent_3?: boolean | null; 168 | talent_4?: boolean | null; 169 | talent_5?: boolean | null; 170 | talent_6?: boolean | null; 171 | talent_7?: boolean | null; 172 | talent_8?: boolean | null; 173 | } 174 | 175 | type Heros = TeamPlayerList; 176 | 177 | export interface AbilityRaw { 178 | name: string; 179 | level: number; 180 | can_cast: boolean; 181 | passive: boolean; 182 | ability_active: boolean; 183 | cooldown: number; 184 | ultimate: boolean; 185 | charges?: number; 186 | max_charges?: number; 187 | charge_cooldown?: number; 188 | } 189 | 190 | interface ItemRaw { 191 | name: string; 192 | purchaser?: number | null; 193 | can_cast?: boolean | null; 194 | cooldown?: number | null; 195 | passive?: boolean | null; 196 | charges?: number | null; 197 | contains_rune?: 'empty' | 'water' | 'arcane' | 'double_damage' | 'haste' | 'regen' | 'shield' | 'illusion'; 198 | } 199 | 200 | type Slots = { 201 | [x in `${Type}${SlotsIds}`]?: N; 202 | }; 203 | 204 | type Abilities = TeamPlayerList>; 205 | 206 | type Items = TeamPlayerList>; 207 | 208 | type BanIds = 0 | 1 | 2 | 3 | 4 | 5 | 6; 209 | type PickIds = 0 | 1 | 2 | 3 | 4; 210 | 211 | export type TeamDraftRaw = { [x in `pick${PickIds}_id` | `ban${BanIds}_id`]: number } & 212 | { [x in `pick${PickIds}_class` | `ban${BanIds}_class`]: string } & { home_team: boolean }; 213 | 214 | interface Draft { 215 | activeteam?: number; 216 | pick?: boolean; 217 | activeteam_time_remaining?: number; 218 | radiant_bonus_time?: number; 219 | dire_bonus_time?: number; 220 | team2?: TeamDraftRaw; 221 | team3?: TeamDraftRaw; 222 | } 223 | 224 | type Wearables = TeamPlayerList>; 225 | 226 | export type TierIds = 0 | 1 | 2 | 3 | 4; 227 | export type PlayerInTierIds = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; 228 | export type ItemTierIds = 0 | 1 | 2 | 3 | 4; 229 | type ChoiceIds = 0 | 1 | 2 | 3; 230 | type ItemIds = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; 231 | 232 | export interface MinimapPoint { 233 | xpos: number; 234 | ypos: number; 235 | image?: string; 236 | team?: number; 237 | yaw: number; 238 | unitname?: string; 239 | visionrange?: number; 240 | } 241 | 242 | interface CourierItemRaw { 243 | owner: string; 244 | name: string; 245 | } 246 | 247 | export interface CourierRaw { 248 | health: number; 249 | max_health: number; 250 | alive: boolean; 251 | boost: boolean; 252 | flying_upgrade: boolean; 253 | shield: boolean; 254 | respawn_time_remaining: number; 255 | xpos: number; 256 | ypos: number; 257 | yaw: number; 258 | items: { [itemId: string]: CourierItemRaw }; 259 | owner: string; 260 | } 261 | 262 | export interface Roshan { 263 | alive: boolean; 264 | health: number; 265 | max_health: number; 266 | phase_time_remaining: number; 267 | spawn_phase: number; 268 | xpos: number; 269 | ypos: number; 270 | yaw: number; 271 | items_drop?: { 272 | [x in `item_${ItemIds}`]?: string; 273 | }; 274 | } 275 | 276 | export interface BaseGSIEvent { 277 | game_time: number; 278 | } 279 | 280 | interface TipEvent extends BaseGSIEvent { 281 | event_type: 'tip'; 282 | tip_amount: number; 283 | sender_player_id: number; 284 | receiver_player_id: number; 285 | } 286 | 287 | interface BountyRunePickupEvent extends BaseGSIEvent { 288 | event_type: 'bounty_rune_pickup'; 289 | player_id: number; 290 | team: Faction; 291 | bounty_value: number; 292 | team_gold: number; 293 | } 294 | 295 | interface AegisPickupEvent extends BaseGSIEvent { 296 | event_type: 'aegis_picked_up'; 297 | player_id: number; 298 | snatched: boolean; 299 | } 300 | 301 | interface CourierKilledEvent extends BaseGSIEvent { 302 | event_type: 'courier_killed'; 303 | killer_player_id: number; 304 | courier_team: Faction; 305 | owning_player_id: number; 306 | } 307 | 308 | interface RoshanKilledEvent extends BaseGSIEvent { 309 | event_type: 'roshan_killed'; 310 | killer_player_id: number; 311 | killed_by_team: Faction; 312 | } 313 | 314 | export type GSIEvent = TipEvent | BountyRunePickupEvent | AegisPickupEvent | CourierKilledEvent | RoshanKilledEvent; 315 | 316 | export interface League { 317 | league_id: number; 318 | match_id: string; 319 | } 320 | 321 | type NeutralItemsInPlayerTierChoicesRaw = { 322 | [x in `choice${ChoiceIds}`]: { 323 | item_name: string; 324 | item_level: number; 325 | selected: boolean; 326 | } 327 | }; 328 | 329 | export type NeutralItemsInPlayerTierRaw = { 330 | tier: number; 331 | trinket_choices: {} | NeutralItemsInPlayerTierChoicesRaw; 332 | enchantment_choices: {} | NeutralItemsInPlayerTierChoicesRaw 333 | } 334 | 335 | 336 | export type NeutralItemsInPlayerRaw = { 337 | current_madstone: number; 338 | total_madstone: number; 339 | crafting_tier: number; } 340 | & { 341 | [x in `tier${TierIds}`]: NeutralItemsInPlayerTierRaw; 342 | }; 343 | 344 | export type TeamNeutralItemsRaw = { 345 | items_found: number; 346 | } & { 347 | [x in `player${PlayerInTierIds}`]: NeutralItemsInPlayerRaw; 348 | }; 349 | 350 | export type NeutralItemsRaw = { 351 | team2: TeamNeutralItemsRaw; 352 | team3: TeamNeutralItemsRaw; 353 | max_madstone: number; 354 | } & { 355 | [x in `tier${TierIds}`]: { 356 | tier: number; 357 | madstone_required: number; 358 | drop_after_time: number; 359 | escalating_recraft_cost: number; 360 | }; 361 | }; 362 | 363 | export enum XPReason { 364 | Unspecified = 0, 365 | HeroKill = 1, 366 | CreepKill = 2, 367 | RoshanKill = 3, 368 | Unknown4 = 4, 369 | Unknown5 = 5 370 | } 371 | -------------------------------------------------------------------------------- /src/events.d.ts: -------------------------------------------------------------------------------- 1 | import type * as I from './interfaces'; 2 | 3 | export interface Events { 4 | data: (data: I.Dota2) => void; 5 | matchEnd: (data: I.MatchEnd) => void; 6 | newListener: (eventName: K, listener: Events[K]) => void; 7 | removeListener: (eventName: K, listener: Events[K]) => void; 8 | } 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | BuildingInfo, 3 | DirePlayerIds, 4 | Dota2Raw, 5 | HeroRaw, 6 | PlayerKey, 7 | PlayerKeys, 8 | GSIEvent, 9 | PlayerRaw, 10 | RadiantPlayerIds, 11 | TeamBuildingsKeys, 12 | Roshan 13 | } from './dota2'; 14 | import type { MatchEnd, PlayerExtension, TeamExtension } from './interfaces'; 15 | import { getItem } from './items_prices'; 16 | import type { 17 | Ability, 18 | AttackType, 19 | Building, 20 | BuildingType, 21 | Dota2, 22 | Map as DotaMap, 23 | Draft, 24 | DraftEntry, 25 | Faction, 26 | Hero, 27 | Outposts, 28 | Item, 29 | ItemType, 30 | KillEntry, 31 | KillEvent, 32 | MapSides, 33 | Player, 34 | Provider, 35 | Side, 36 | Team, 37 | TeamDraft, 38 | Wearable, 39 | WearableType, 40 | NeutralItems 41 | } from './parsed'; 42 | import { parseBuilding, parseDraft, parseMap, parseNeutralItems, parseOutposts, parsePlayer } from './utils'; 43 | 44 | interface Events { 45 | data: (data: Dota2) => void; 46 | kill: (kill: KillEvent) => void; 47 | matchEnd: (data: MatchEnd) => void; 48 | newListener: (eventName: K, listener: Events[K]) => void; 49 | removeListener: (eventName: K, listener: Events[K]) => void; 50 | } 51 | 52 | export type AnyEventName = T | (string & {}); 53 | 54 | export type BaseEvents = keyof Events; 55 | 56 | export type EventNames = AnyEventName; 57 | 58 | export type EmptyListener = () => void; 59 | 60 | export type Callback = K extends BaseEvents ? Events[K] | EmptyListener : EmptyListener; 61 | interface EventDescriptor { 62 | listener: Events[BaseEvents]; 63 | once: boolean; 64 | } 65 | class DOTA2GSI { 66 | private descriptors: Map; 67 | private maxListeners: number; 68 | teams: { 69 | radiant: TeamExtension | null; 70 | dire: TeamExtension | null; 71 | }; 72 | players: PlayerExtension[]; 73 | last?: Dota2; 74 | current?: Dota2; 75 | constructor() { 76 | this.descriptors = new Map(); 77 | this.teams = { 78 | radiant: null, 79 | dire: null 80 | }; 81 | this.maxListeners = 10; 82 | this.players = []; 83 | } 84 | eventNames = () => { 85 | const listeners = this.descriptors.entries(); 86 | const nonEmptyEvents: EventNames[] = []; 87 | 88 | for (const entry of listeners) { 89 | if (entry[1] && entry[1].length > 0) { 90 | nonEmptyEvents.push(entry[0]); 91 | } 92 | } 93 | 94 | return nonEmptyEvents; 95 | }; 96 | getMaxListeners = () => this.maxListeners; 97 | 98 | listenerCount = (eventName: EventNames) => { 99 | const listeners = this.listeners(eventName); 100 | return listeners.length; 101 | }; 102 | 103 | listeners = (eventName: EventNames) => { 104 | const descriptors = this.descriptors.get(eventName) || []; 105 | return descriptors.map(descriptor => descriptor.listener); 106 | }; 107 | 108 | removeListener = (eventName: K, listener: Callback) => { 109 | return this.off(eventName, listener); 110 | }; 111 | 112 | off = (eventName: K, listener: Callback) => { 113 | const descriptors = this.descriptors.get(eventName) || []; 114 | 115 | this.descriptors.set( 116 | eventName, 117 | descriptors.filter(descriptor => descriptor.listener !== listener) 118 | ); 119 | this.emit('removeListener', eventName, listener); 120 | return this; 121 | }; 122 | 123 | addListener = (eventName: K, listener: Callback) => { 124 | return this.on(eventName, listener); 125 | }; 126 | 127 | on = (eventName: K, listener: Callback) => { 128 | this.emit('newListener', eventName, listener); 129 | const listOfListeners = [...(this.descriptors.get(eventName) || [])]; 130 | 131 | listOfListeners.push({ listener, once: false }); 132 | this.descriptors.set(eventName, listOfListeners); 133 | 134 | return this; 135 | }; 136 | 137 | once = (eventName: K, listener: Callback) => { 138 | const listOfListeners = [...(this.descriptors.get(eventName) || [])]; 139 | 140 | listOfListeners.push({ listener, once: true }); 141 | this.descriptors.set(eventName, listOfListeners); 142 | 143 | return this; 144 | }; 145 | 146 | prependListener = (eventName: K, listener: Callback) => { 147 | const listOfListeners = [...(this.descriptors.get(eventName) || [])]; 148 | 149 | listOfListeners.unshift({ listener, once: false }); 150 | this.descriptors.set(eventName, listOfListeners); 151 | 152 | return this; 153 | }; 154 | 155 | emit = (eventName: EventNames, arg?: any, arg2?: any) => { 156 | const listeners = this.descriptors.get(eventName); 157 | if (!listeners || listeners.length === 0) return false; 158 | 159 | listeners.forEach(listener => { 160 | if (listener.once) { 161 | this.descriptors.set( 162 | eventName, 163 | listeners.filter(listenerInArray => listenerInArray !== listener) 164 | ); 165 | } 166 | (listener.listener as any)(arg, arg2); 167 | }); 168 | return true; 169 | }; 170 | 171 | prependOnceListener = (eventName: K, listener: Callback) => { 172 | const listOfListeners = [...(this.descriptors.get(eventName) || [])]; 173 | 174 | listOfListeners.unshift({ listener, once: true }); 175 | this.descriptors.set(eventName, listOfListeners); 176 | 177 | return this; 178 | }; 179 | 180 | removeAllListeners = (eventName: EventNames) => { 181 | this.descriptors.set(eventName, []); 182 | return this; 183 | }; 184 | 185 | setMaxListeners = (n: number) => { 186 | this.maxListeners = n; 187 | return this; 188 | }; 189 | 190 | rawListeners = (eventName: EventNames) => { 191 | return this.descriptors.get(eventName) || []; 192 | }; 193 | 194 | digest = (rawGSI: Dota2Raw) => { 195 | if (!rawGSI || !rawGSI.map) return null; 196 | const rawPlayers: { id: number; player: PlayerRaw }[] = []; 197 | 198 | for (const [key, player] of Object.entries({ ...rawGSI.player.team2, ...rawGSI.player.team3 })) { 199 | const id = Number(key.replace(/([^0-9])/g, '')); 200 | if (isNaN(id) || !player) continue; 201 | 202 | rawPlayers.push({ id, player }); 203 | } 204 | 205 | const rawBuildings: { id: TeamBuildingsKeys; building: BuildingInfo }[] = []; 206 | 207 | for (const [id, building] of Object.entries( 208 | rawGSI.buildings ? { ...rawGSI.buildings.dire, ...rawGSI.buildings.radiant } : {} 209 | )) { 210 | if (!building) continue; 211 | rawBuildings.push({ id: id as TeamBuildingsKeys, building }); 212 | } 213 | 214 | const players = rawPlayers.map(data => parsePlayer(data.player, data.id, rawGSI, this.players, this.current)); 215 | 216 | const gsi: Dota2 = { 217 | provider: rawGSI.provider, 218 | map: parseMap(rawGSI.map, this.teams), 219 | players, 220 | player: players.find(player => player.hero && player.hero.selected_unit) || null, 221 | buildings: rawBuildings.map(entry => parseBuilding(entry.id, entry.building)), 222 | roshan: rawGSI.roshan ? rawGSI.roshan : null, 223 | neutral_items: 224 | parseNeutralItems(rawGSI.map.game_time, rawGSI.neutralitems, this.last?.neutral_items || undefined) || 225 | null, 226 | events: rawGSI.events ? rawGSI.events : null, 227 | outposts: parseOutposts(rawGSI.minimap), 228 | draft: { 229 | activeteam: rawGSI.draft.activeteam, 230 | pick: rawGSI.draft.pick, 231 | activeteam_time_remaining: rawGSI.draft.activeteam_time_remaining, 232 | radiant: 233 | rawGSI.draft.team2 && 234 | 'home_team' in rawGSI.draft.team2 && 235 | rawGSI.draft.radiant_bonus_time !== undefined 236 | ? { 237 | home_team: rawGSI.draft.team2.home_team, 238 | bonus_time: rawGSI.draft.radiant_bonus_time, 239 | picks: parseDraft(rawGSI.draft.team2) 240 | } 241 | : undefined, 242 | dire: 243 | rawGSI.draft.team3 && 244 | 'home_team' in rawGSI.draft.team3 && 245 | rawGSI.draft.dire_bonus_time !== undefined 246 | ? { 247 | home_team: rawGSI.draft.team3.home_team, 248 | bonus_time: rawGSI.draft.dire_bonus_time, 249 | picks: parseDraft(rawGSI.draft.team3) 250 | } 251 | : undefined 252 | } 253 | }; 254 | this.current = gsi; 255 | 256 | if (this.last) { 257 | for (const player of gsi.players) { 258 | const previousPlayer = this.last.players.find(lastPlayer => lastPlayer.steamid === player.steamid); 259 | if (!previousPlayer) continue; 260 | const newKills = player.kill_list.filter(kill => { 261 | const previousKill = previousPlayer.kill_list.find(oldKill => oldKill.victimid === kill.victimid); 262 | if (!previousKill) return true; 263 | return previousKill.amount !== kill.amount; 264 | }); 265 | for (const killEntry of newKills) { 266 | const victim = gsi.players.find(player => player.id === killEntry.victimid); 267 | if (!victim) continue; 268 | 269 | const kill: KillEvent = { 270 | victim, 271 | killer: player 272 | }; 273 | this.emit('kill', kill); 274 | } 275 | } 276 | if (gsi.map.win_team !== 'none' && this.last.map.win_team === 'none') { 277 | const winTeam = gsi.map.win_team.toLowerCase(); 278 | if (winTeam.includes('dire')) { 279 | this.emit('matchEnd', { 280 | faction: 'dire', 281 | teamId: gsi.map.dire.id, 282 | name: gsi.map.dire.name 283 | } as MatchEnd); 284 | } else { 285 | this.emit('matchEnd', { 286 | faction: 'radiant', 287 | teamId: gsi.map.radiant.id, 288 | name: gsi.map.radiant.name 289 | } as MatchEnd); 290 | } 291 | } 292 | } 293 | 294 | this.emit('data', gsi); 295 | this.last = gsi; 296 | return gsi; 297 | }; 298 | } 299 | 300 | export { DOTA2GSI, getItem }; 301 | export type { 302 | PlayerRaw, 303 | Dota2Raw, 304 | Player, 305 | PlayerExtension, 306 | TeamExtension, 307 | Hero, 308 | HeroRaw, 309 | NeutralItems, 310 | PlayerKeys, 311 | PlayerKey, 312 | RadiantPlayerIds, 313 | DirePlayerIds, 314 | Roshan, 315 | Dota2, 316 | Faction, 317 | Side, 318 | KillEvent, 319 | Outposts, 320 | AttackType, 321 | Ability, 322 | Building, 323 | BuildingType, 324 | ItemType, 325 | MapSides, 326 | Team, 327 | Provider, 328 | Item, 329 | Draft, 330 | DraftEntry, 331 | TeamDraft, 332 | GSIEvent, 333 | Wearable, 334 | WearableType, 335 | KillEntry, 336 | DotaMap as Map, 337 | }; 338 | -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | import type { Faction } from './parsed'; 2 | 3 | //export * from './dota2'; 4 | export type * from './parsed'; 5 | 6 | export interface TeamExtension { 7 | id: string; 8 | name: string; 9 | country: string | null; 10 | logo: string | null; 11 | map_score: number; 12 | extra: Record; 13 | short_name: string | null; 14 | } 15 | 16 | export interface PlayerExtension { 17 | id: string; 18 | name: string; 19 | steamid: string; 20 | realName: string | null; 21 | country: string | null; 22 | avatar: string | null; 23 | extra: Record; 24 | } 25 | 26 | export interface MatchEnd { 27 | faction: Faction; 28 | teamId: string | null; 29 | name: string; 30 | } 31 | -------------------------------------------------------------------------------- /src/items.ts: -------------------------------------------------------------------------------- 1 | export const items = [ 2 | { name: 'item_aegis', origin: 'Aegis of the Immortal', price: 0 }, 3 | { name: 'item_boots_of_elves', origin: 'Band of Elvenskin', price: 450 }, 4 | { name: 'item_belt_of_strength', origin: 'Belt of Strength', price: 450 }, 5 | { name: 'item_blade_of_alacrity', origin: 'Blade of Alacrity', price: 1000 }, 6 | { name: 'item_blades_of_attack', origin: 'Blades of Attack', price: 450 }, 7 | { name: 'item_blight_stone', origin: 'Blight Stone', price: 300 }, 8 | { name: 'item_blink', origin: 'Blink dagger', price: 2250 }, 9 | { name: 'item_boots', origin: 'Boots of Speed', price: 500 }, 10 | { name: 'item_bottle', origin: 'Bottle', price: 675 }, 11 | { name: 'item_broadsword', origin: 'Broadsword', price: 1000 }, 12 | { name: 'item_chainmail', origin: 'Chainmail', price: 550 }, 13 | { name: 'item_cheese', origin: 'Cheese', price: 1000 }, 14 | { name: 'item_circlet', origin: 'Circlet', price: 155 }, 15 | { name: 'item_clarity', origin: 'Clarity', price: 50 }, 16 | { name: 'item_claymore', origin: 'Claymore', price: 1350 }, 17 | { name: 'item_cloak', origin: 'Cloak', price: 800 }, 18 | { name: 'item_crown', origin: 'Crown', price: 450 }, 19 | { name: 'item_demon_edge', origin: 'Demon Edge', price: 2200 }, 20 | { name: 'item_dust', origin: 'Dust of Appearance', price: 80 }, 21 | { name: 'item_eagle', origin: 'Eaglesong', price: 2800 }, 22 | { name: 'item_enchanted_mango', origin: 'Enchanted Mango', price: 65 }, 23 | { name: 'item_energy_booster', origin: 'Energy Booster', price: 800 }, 24 | { name: 'item_faerie_fire', origin: 'Faerie Fire', price: 65 }, 25 | { name: 'item_gauntlets', origin: 'Gauntlets of Strength', price: 140 }, 26 | { name: 'item_gem', origin: 'Gem of True Sight', price: 900 }, 27 | { name: 'item_ghost', origin: 'Ghost Scepter', price: 1500 }, 28 | { name: 'item_gloves', origin: 'Gloves of Haste', price: 450 }, 29 | { name: 'item_flask', origin: 'Healing Salve', price: 100 }, 30 | { name: 'item_helm_of_iron_will', origin: 'Helm of Iron Will', price: 975 }, 31 | { name: 'item_hyperstone', origin: 'Hyperstone', price: 2000 }, 32 | { name: 'item_infused_raindrop', origin: 'Infused Raindrops', price: 225 }, 33 | { name: 'item_branches', origin: 'Iron Branch', price: 50 }, 34 | { name: 'item_javelin', origin: 'Javelin', price: 1100 }, 35 | { name: 'item_magic_stick', origin: 'Magic Stick', price: 200 }, 36 | { name: 'item_mantle', origin: 'Mantle of Intelligence', price: 140 }, 37 | { name: 'item_mithril_hammer', origin: 'Mithril Hammer', price: 1600 }, 38 | { name: 'item_lifesteal', origin: 'Morbid Mask', price: 900 }, 39 | { name: 'item_mystic_staff', origin: 'Mystic Staff', price: 2800 }, 40 | { name: 'item_ward_observer', origin: 'Observer Wards', price: 0 }, 41 | { name: 'item_ogre_axe', origin: 'Ogre Axe', price: 1000 }, 42 | { name: 'item_orb_of_venom', origin: 'Orb of Venom', price: 275 }, 43 | { name: 'item_platemail', origin: 'Platemail', price: 1400 }, 44 | { name: 'item_point_booster', origin: 'Point Booster', price: 1200 }, 45 | { name: 'item_quarterstaff', origin: 'Quarterstaff', price: 875 }, 46 | { name: 'item_quelling_blade', origin: 'Quelling Blade', price: 100 }, 47 | { name: 'item_reaver', origin: 'Reaver', price: 2800 }, 48 | { name: 'item_refresher_shard', origin: 'Refresher Shard', price: 1000 }, 49 | { name: 'item_ring_of_health', origin: 'Ring of Health', price: 700 }, 50 | { name: 'item_ring_of_protection', origin: 'Ring of Protection', price: 175 }, 51 | { name: 'item_ring_of_regen', origin: 'Ring of Regen', price: 175 }, 52 | { name: 'item_ring_of_tarrasque', origin: 'Ring of Tarrasque', price: 1800 }, 53 | { name: 'item_robe', origin: 'Robe of the Magi', price: 450 }, 54 | { name: 'item_relic', origin: 'Sacred Relic', price: 3400 }, 55 | { name: 'item_sobi_mask', origin: 'Sobi Mask', price: 175 }, 56 | { name: 'item_ward_sentry', origin: 'Sentry Ward', price: 50 }, 57 | { name: 'item_shadow_amulet', origin: 'Shadow Amulet', price: 1000 }, 58 | { name: 'item_slippers', origin: 'Slippers of Agility', price: 140 }, 59 | { name: 'item_smoke_of_deceit', origin: 'Smoke of Deceit', price: 50 }, 60 | { name: 'item_staff_of_wizardry', origin: 'Staff of Wizardry', price: 1000 }, 61 | { name: 'item_stout_shield', origin: 'Stout Shield', price: 100 }, 62 | { name: 'item_talisman_of_evasion', origin: 'Talisman of Evasion', price: 1300 }, 63 | { name: 'item_tango', origin: 'Tango', price: 90 }, 64 | { name: 'item_tome_of_knowledge', origin: 'Tome of Knowledge', price: 75 }, 65 | { name: 'item_tpscroll', origin: 'Teleport Scroll', price: 100 }, 66 | { name: 'item_ultimate_orb', origin: 'Ultimate Orb', price: 2050 }, 67 | { name: 'item_vitality_booster', origin: 'Vitality Booster', price: 1000 }, 68 | { name: 'item_void_stone', origin: 'Void Stone', price: 700 }, 69 | { name: 'item_wind_lace', origin: 'Wind Lace', price: 250 }, 70 | { name: 'item_abyssal_blade', origin: 'Abyssal Blade', price: 6250 }, 71 | { name: 'item_aeon_disk', origin: 'Aeon Disk', price: 3000 }, 72 | { name: 'item_aether_lens', origin: 'Aether Lens', price: 2275 }, 73 | { name: 'item_ultimate_scepter_2', origin: 'Aghanim\'s Blessing', price: 5800 }, 74 | { name: 'item_ultimate_scepter', origin: 'Aghanim\'s Scepter', price: 4200 }, 75 | { name: 'item_aghanims_shard', origin: 'Aghanim\'s Shard', price: 1400 }, 76 | { name: 'item_arcane_boots', origin: 'Arcane Boots', price: 1300 }, 77 | { name: 'item_armlet', origin: 'Armlet of Mordiggian', price: 2500 }, 78 | { name: 'item_assault', origin: 'Assault Cuirass', price: 5125 }, 79 | { name: 'item_bfury', origin: 'Battlefury', price: 4100 }, 80 | { name: 'item_black_king_bar', origin: 'Black King Bar', price: 4050 }, 81 | { name: 'item_blade_mail', origin: 'Blade Mail', price: 2100 }, 82 | { name: 'item_bloodstone', origin: 'Bloodstone', price: 4400 }, 83 | { name: 'item_bloodthorn', origin: 'Bloodthorn', price: 6800 }, 84 | { name: 'item_travel_boots', origin: 'Boots of Travel', price: 2500 }, 85 | { name: 'item_bracer', origin: 'Bracer', price: 505 }, 86 | { name: 'item_buckler', origin: 'Buckler', price: 425 }, 87 | { name: 'item_butterfly', origin: 'Butterfly', price: 4975 }, 88 | { name: 'item_crimson_guard', origin: 'Crimson Guard', price: 3725 }, 89 | { name: 'item_lesser_crit', origin: 'Crystalys', price: 1900 }, 90 | { name: 'item_greater_crit', origin: 'Daedalus', price: 5100 }, 91 | { name: 'item_dagon_#(1-5)', origin: 'Dagon', price: 2700 }, 92 | { name: 'item_desolator', origin: 'Desolator', price: 3500 }, 93 | { name: 'item_diffusal_blade', origin: 'Diffusal Blade', price: 2500 }, 94 | { name: 'item_diffusal_blade_2', origin: 'Diffusal Blade', price: 3850 }, 95 | { name: 'item_dragon_lance', origin: 'Dragon Lance', price: 1900 }, 96 | { name: 'item_ancient_janggo', origin: 'Drum of Endurance', price: 1650 }, 97 | { name: 'item_echo_sabre', origin: 'Echo Sabre', price: 2500 }, 98 | { name: 'item_ethereal_blade', origin: 'Ethereal Blade', price: 4650 }, 99 | { name: 'item_cyclone', origin: 'Eul\'s Scepter of Divinity', price: 2625 }, 100 | { name: 'item_skadi', origin: 'Skadi', price: 5300 }, 101 | { name: 'item_force_staff', origin: 'Force Staff', price: 2200 }, 102 | { name: 'item_glimmer_cape', origin: 'Glimmer Cape', price: 2150 }, 103 | { name: 'item_guardian_greaves', origin: 'Guardian Greaves', price: 4950 }, 104 | { name: 'item_hand_of_midas', origin: 'Hand of Midas', price: 2200 }, 105 | { name: 'item_headdress', origin: 'Headdress', price: 425 }, 106 | { name: 'item_heart', origin: 'Heart of Tarrasque', price: 5100 }, 107 | { name: 'item_heavens_halberd', origin: 'Heaven\'s Halberd', price: 3550 }, 108 | { name: 'item_helm_of_the_dominator', origin: 'Helm of the Dominator', price: 2625 }, 109 | { name: 'item_hood_of_defiance', origin: 'Hood of Defiance', price: 0 }, 110 | { name: 'item_hurricane_pike', origin: 'Hurricane Pike', price: 4450 }, 111 | { name: 'item_iron_talon', origin: 'Iron Talon', price: 0 }, 112 | { name: 'item_kaya', origin: 'Kaya', price: 2050 }, 113 | { name: 'item_kaya_and_sange', origin: 'Kaya and Sange', price: 4100 }, 114 | { name: 'item_sphere', origin: 'Sphere', price: 4600 }, 115 | { name: 'item_lotus_orb', origin: 'Lotus Orb', price: 3850 }, 116 | { name: 'item_maelstrom', origin: 'Maelstrom', price: 2700 }, 117 | { name: 'item_magic_wand', origin: 'Magic Wand', price: 450 }, 118 | { name: 'item_manta', origin: 'Manta', price: 4600 }, 119 | { name: 'item_mask_of_madness', origin: 'Mask of Madness', price: 1775 }, 120 | { name: 'item_medallion_of_courage', origin: 'Medallion of Courage', price: 1025 }, 121 | { name: 'item_mekansm', origin: 'Mekansm', price: 1775 }, 122 | { name: 'item_meteor_hammer', origin: 'Meteor Hammer', price: 2400 }, 123 | { name: 'item_mjollnir', origin: 'Mjollnir', price: 5500 }, 124 | { name: 'item_monkey_king_bar', origin: 'Monkey King Bar', price: 4900 }, 125 | { name: 'item_moon_shard', origin: 'Moon Shard', price: 4000 }, 126 | { name: 'item_necronomicon_#(1-3)', origin: 'Necronomicon', price: 2050 }, 127 | { name: 'item_null_talisman', origin: 'Null Talisman', price: 505 }, 128 | { name: 'item_nullifier', origin: 'Nullifier', price: 4375 }, 129 | { name: 'item_oblivion_staff', origin: 'Oblivion Staff', price: 1500 }, 130 | { name: 'item_octarine_core', origin: 'Octarine Core', price: 4600 }, 131 | { name: 'item_orchid', origin: 'Orchid Malevolence', price: 3475 }, 132 | { name: 'item_pers', origin: 'Perseverence', price: 1400 }, 133 | { name: 'item_phase_boots', origin: 'Phase Boots', price: 1500 }, 134 | { name: 'item_pipe', origin: 'Pipe of Insight', price: 3375 }, 135 | { name: 'item_power_treads', origin: 'Power Treads', price: 1400 }, 136 | { name: 'item_radiance', origin: 'Radiance', price: 4700 }, 137 | { name: 'item_rapier', origin: 'Divine Rapier', price: 5600 }, 138 | { name: 'item_refresher', origin: 'Refresher', price: 5000 }, 139 | { name: 'item_ring_of_aquila', origin: 'Ring of Aquila', price: 0 }, 140 | { name: 'item_ring_of_basilius', origin: 'Ring of Basilius', price: 425 }, 141 | { name: 'item_rod_of_atos', origin: 'Rod of Atos', price: 2250 }, 142 | { name: 'item_sange', origin: 'Sange', price: 2050 }, 143 | { name: 'item_sange_and_yasha', origin: 'Sange and Yasha', price: 4100 }, 144 | { name: 'item_satanic', origin: 'Satanic', price: 5050 }, 145 | { name: 'item_sheepstick', origin: 'Scythe of Vyse', price: 5550 }, 146 | { name: 'item_invis_sword', origin: 'Shadow Blade', price: 3000 }, 147 | { name: 'item_shivas_guard', origin: 'Shiva\'s Guard', price: 4850 }, 148 | { name: 'item_silver_edge', origin: 'Silver Edge', price: 5450 }, 149 | { name: 'item_basher', origin: 'Skull Basher', price: 2875 }, 150 | { name: 'item_solar_crest', origin: 'Solar Crest', price: 2425 }, 151 | { name: 'item_soul_booster', origin: 'Soul Booster', price: 3000 }, 152 | { name: 'item_soul_ring', origin: 'Soul Ring', price: 805 }, 153 | { name: 'item_spirit_vessel', origin: 'Spirit Vessel', price: 2780 }, 154 | { name: 'item_tranquil_boots', origin: 'Tranquil Boots', price: 925 }, 155 | { name: 'item_urn_of_shadows', origin: 'Urn of Shadows', price: 880 }, 156 | { name: 'item_vanguard', origin: 'Vanguard', price: 1700 }, 157 | { name: 'item_veil_of_discord', origin: 'Veil of Discord', price: 1525 }, 158 | { name: 'item_vladmir', origin: 'Vladmir\'s Offering', price: 2450 }, 159 | { name: 'item_wraith_band', origin: 'Wraith Band', price: 505 }, 160 | { name: 'item_yasha', origin: 'Yasha', price: 2050 }, 161 | { name: 'item_yasha_and_kaya', origin: 'Yasha and Kaya', price: 4100 }, 162 | { name: 'item_arcane_ring', origin: 'Arcane Ring', price: 0 }, 163 | { name: 'item_broom_handle', origin: 'Broom Handle', price: 0 }, 164 | { name: 'item_faded_broach', origin: 'Faded Broach', price: 0 }, 165 | { name: 'item_ironwood_tree', origin: 'Ironwood Tree', price: 0 }, 166 | { name: 'item_keen_optic', origin: 'Keen Optic', price: 0 }, 167 | { name: 'item_mango_tree', origin: 'Mango Tree', price: 0 }, 168 | { name: 'item_ocean_heart', origin: 'Ocean Heart', price: 0 }, 169 | { name: 'item_royal_jelly', origin: 'Royal Jelly', price: 0 }, 170 | { name: 'item_trusty_shovel', origin: 'Trusty Shovel', price: 0 }, 171 | { name: 'item_clumsy_net', origin: 'Clumsy Net', price: 0 }, 172 | { name: 'item_dragon_scale', origin: 'Dragon Scale', price: 0 }, 173 | { name: 'item_essence_ring', origin: 'Essence Ring', price: 0 }, 174 | { name: 'item_grove_bow', origin: 'Grove Bow', price: 0 }, 175 | { name: 'item_imp_claw', origin: 'Imp Claw', price: 0 }, 176 | { name: 'item_nether_shawl', origin: 'Nether Shawl', price: 0 }, 177 | { name: 'item_vambrace', origin: 'Vambrace', price: 0 }, 178 | { name: 'item_vampire_fangs', origin: 'Vampire Fangs', price: 0 }, 179 | { name: 'item_craggy_coat', origin: 'Craggy Coat', price: 0 }, 180 | { name: 'item_enchanted_quiver', origin: 'Enchanted Quiver', price: 0 }, 181 | { name: 'item_greater_faerie_fire', origin: 'Greater Faerie Fire', price: 0 }, 182 | { name: 'item_mind_breaker', origin: 'Mind Breaker', price: 0 }, 183 | { name: 'item_orb_of_destruction', origin: 'Orb of Destruction', price: 0 }, 184 | { name: 'item_paladin_sword', origin: 'Paladin Sword', price: 0 }, 185 | { name: 'item_quickening_charm', origin: 'Quickening Charm', price: 0 }, 186 | { name: 'item_repair_kit', origin: 'Repair Kit', price: 0 }, 187 | { name: 'item_spider_legs', origin: 'Spider Legs', price: 0 }, 188 | { name: 'item_spy_gadget', origin: 'Telescope', price: 0 }, 189 | { name: 'item_titan_sliver', origin: 'Titan Sliver', price: 0 }, 190 | { name: 'item_flicker', origin: 'Flicker', price: 0 }, 191 | { name: 'item_havoc_hammer', origin: 'Havoc Hammer', price: 0 }, 192 | { name: 'item_panic_button', origin: 'Magic Lamp', price: 0 }, 193 | { name: 'item_minotaur_horn', origin: 'Minotaur Horn', price: 0 }, 194 | { name: 'item_ninja_gear', origin: 'Ninja Gear', price: 0 }, 195 | { name: 'item_spell_prism', origin: 'Spell Prism', price: 0 }, 196 | { name: 'item_the_leveller', origin: 'The Leveller', price: 0 }, 197 | { name: 'item_timeless_relic', origin: 'Timeless Relic', price: 0 }, 198 | { name: 'item_witless_shako', origin: 'Witless Shako', price: 0 }, 199 | { name: 'item_apex', origin: 'Apex', price: 0 }, 200 | { name: 'item_ballista', origin: 'Ballista', price: 0 }, 201 | { name: 'item_demonicon', origin: 'Book of the Dead', price: 0 }, 202 | { name: 'item_ex_machina', origin: 'Ex Machina', price: 0 }, 203 | { name: 'item_fallen_sky', origin: 'Fallen Sky', price: 0 }, 204 | { name: 'item_force_boots', origin: 'Force Boots', price: 0 }, 205 | { name: 'item_mirror_shield', origin: 'Mirror Shield', price: 0 }, 206 | { name: 'item_pirate_hat', origin: 'Pirate Hat', price: 0 }, 207 | { name: 'item_seer_stone', origin: 'Seer Stone', price: 0 }, 208 | { name: 'item_desolator_2', origin: 'Stygian Desolator', price: 0 }, 209 | { name: 'item_recipe_trident', origin: 'Recipe: Trident', price: 1 }, 210 | { name: 'item_woodland_striders', origin: 'Woodland Striders', price: 0 }, 211 | { name: 'item_elixir', origin: 'Elixir', price: 0 }, 212 | { name: 'item_fusion_rune', origin: 'Fusion Rune', price: 0 }, 213 | { name: 'item_helm_of_the_undying', origin: 'Helm of the Undying', price: 0 }, 214 | { name: 'item_phoenix_ash', origin: 'Phoenix Ash', price: 0 }, 215 | { name: 'item_third_eye', origin: 'Third Eye', price: 0 }, 216 | { name: 'item_tome_of_aghanim', origin: 'Tome of Aghanim', price: 0 }, 217 | { name: 'item_dimensional_doorway', origin: 'Dimensional Doorway', price: 0 }, 218 | { name: 'item_greater_mango', origin: 'Greater Mango', price: 0 }, 219 | { name: 'item_horizon', origin: 'Horizon', price: 0 }, 220 | { name: 'item_halloween_candy_corn', origin: 'Diretide/Greevil Taffy', price: 0 }, 221 | { name: 'item_greevil_whistle_toggle', origin: 'Greeviling/Greevil Whistle', price: 0 }, 222 | { name: 'item_winter_stocking', origin: 'Greeviling/Xmas Stocking', price: 0 }, 223 | { name: 'item_winter_skates', origin: 'Greeviling/Speed Skates', price: 0 }, 224 | { name: 'item_winter_cake', origin: 'Greeviling/Fruit-bit Cake', price: 0 }, 225 | { name: 'item_winter_cookie', origin: 'Greeviling/Wizard Cookie', price: 0 }, 226 | { name: 'item_winter_coco', origin: 'Greeviling/Cocoa with Marshmallows', price: 0 }, 227 | { name: 'item_winter_ham', origin: 'Greeviling/Clove Studded Ham', price: 0 }, 228 | { name: 'item_winter_kringle', origin: 'Greeviling/Kringle', price: 0 }, 229 | { name: 'item_winter_mushroom', origin: 'Greeviling/Snow Mushroom', price: 0 }, 230 | { name: 'item_winter_greevil_treat', origin: 'Greeviling/Greevil Treat', price: 0 }, 231 | { name: 'item_winter_greevil_garbage', origin: 'Greeviling/Greevil Chow', price: 0 }, 232 | { name: 'item_winter_grevil_chewy', origin: 'Greeviling/Greevil Blink Bone', price: 0 }, 233 | { name: 'item_greater_clarity', origin: 'Greater Clarity', price: 90 }, 234 | { name: 'item_greater_flask', origin: 'Wraith-Night/Greater Salve', price: 0 }, 235 | { name: 'item_arcane_boots_2', origin: 'Wraith-Night/Arcane Boots II', price: 0 }, 236 | { name: 'item_tranquil_boots_2', origin: 'Wraith-Night/Slippers of Halcyon', price: 0 }, 237 | { name: 'item_firecrackers', origin: 'Flinching Firecrackers', price: 100 }, 238 | { name: 'item_firework_mine', origin: 'Firework Mine', price: 1000 }, 239 | { name: 'item_scare_the_beast', origin: 'Fighting the Year Beast/Scare the Beast', price: 0 }, 240 | { name: 'item_vermillion_robe', origin: 'Fighting the Year Beast/Vermillion Robe', price: 0 }, 241 | { name: 'item_river_painter', origin: 'River Vial: Chrome', price: 0 }, 242 | { name: 'item_river_painter2', origin: 'River Vial: Dry', price: 0 }, 243 | { name: 'item_river_painter3', origin: 'River Vial: Slime', price: 0 }, 244 | { name: 'item_river_painter4', origin: 'River Vial: Oil', price: 0 }, 245 | { name: 'item_river_painter5', origin: 'River Vial: Electrified', price: 0 }, 246 | { name: 'item_river_painter6', origin: 'River Vial: Potion', price: 0 }, 247 | { name: 'item_river_painter7', origin: 'River Vial: Blood', price: 0 }, 248 | { name: 'item_ability_id', origin: 'override', price: 0 }, 249 | { name: 'item_aghsfort_bloodstone', origin: 'override', price: 1400 }, 250 | { name: 'item_aghsfort_travel_boots', origin: 'override', price: 2500 }, 251 | { name: 'item_aghsfort_travel_boots_2', origin: 'override', price: 0 }, 252 | { name: 'item_alt_charge_count', origin: 'override', price: 0 }, 253 | { name: 'item_amount', origin: 'override', price: 0 }, 254 | { name: 'item_arcanist_potion', origin: 'override', price: 0 }, 255 | { name: 'item_bag_of_gold_caster_only', origin: 'override', price: 0 }, 256 | { name: 'item_bottomless_chalice', origin: 'Bottomless Chalice', price: 0 }, 257 | { name: 'item_build_category', origin: 'override', price: 0 }, 258 | { name: 'item_build_index', origin: 'override', price: 0 }, 259 | { name: 'item_charge_count', origin: 'override', price: 0 }, 260 | { name: 'item_class', origin: 'override', price: 0 }, 261 | { name: 'item_clientacks.txt', origin: 'override', price: 0 }, 262 | { name: 'item_cooldown_reduction', origin: 'override', price: 0 }, 263 | { name: 'item_dagon_2', origin: 'Dagon', price: 3950 }, 264 | { name: 'item_dagon_3', origin: 'Dagon', price: 5200 }, 265 | { name: 'item_dagon_4', origin: 'Dagon', price: 6450 }, 266 | { name: 'item_dagon_5', origin: 'Dagon', price: 7700 }, 267 | { name: 'item_datadriven', origin: 'override', price: 0 }, 268 | { name: 'item_debug', origin: 'override', price: 0 }, 269 | { name: 'item_def0', origin: 'override', price: 0 }, 270 | { name: 'item_def1', origin: 'override', price: 0 }, 271 | { name: 'item_def6', origin: 'override', price: 0 }, 272 | { name: 'item_def7', origin: 'override', price: 0 }, 273 | { name: 'item_def%d', origin: 'override', price: 0 }, 274 | { name: 'item_def_for_price', origin: 'override', price: 0 }, 275 | { name: 'item_definition_index_t', origin: 'override', price: 0 }, 276 | { name: 'item_description_alt', origin: 'override', price: 0 }, 277 | { name: 'item_elixer', origin: 'Elixer', price: 0 }, 278 | { name: 'item_equip_state', origin: 'override', price: 0 }, 279 | { name: 'item_gold_spent', origin: 'override', price: 0 }, 280 | { name: 'item_grandmasters_glaive', origin: 'Grandmaster\'s Glaive', price: 5000 }, 281 | { name: 'item_gungir', origin: 'Gleipnir', price: 5650 }, 282 | { name: 'item_has_escalating_chance', origin: 'override', price: 0 }, 283 | { name: 'item_has_escalating_chance_by_rarity', origin: 'override', price: 0 }, 284 | { name: 'item_helm_of_the_dominator_2', origin: 'override', price: 0 }, 285 | { name: 'item_hero_pick_count', origin: 'override', price: 0 }, 286 | { name: 'item_hero_pick_percent', origin: 'override', price: 0 }, 287 | { name: 'item_hero_win_count', origin: 'override', price: 0 }, 288 | { name: 'item_hero_win_percent', origin: 'override', price: 0 }, 289 | { name: 'item_i', origin: 'override', price: 0 }, 290 | { name: 'item_iconname', origin: 'override', price: 0 }, 291 | { name: 'item_illusionsts_cape', origin: 'Illusionsts Cape', price: 0 }, 292 | { name: 'item_image', origin: 'override', price: 0 }, 293 | { name: 'item_includes_extra_gems', origin: 'override', price: 0 }, 294 | { name: 'item_index', origin: 'override', price: 0 }, 295 | { name: 'item_level', origin: 'override', price: 0 }, 296 | { name: 'item_level_max', origin: 'override', price: 0 }, 297 | { name: 'item_level_min', origin: 'override', price: 0 }, 298 | { name: 'item_levels', origin: 'override', price: 0 }, 299 | { name: 'item_lore', origin: 'override', price: 0 }, 300 | { name: 'item_lua', origin: 'override', price: 0 }, 301 | { name: 'item_lunar_crest', origin: 'Lunar Crest', price: 2300 }, 302 | { name: 'item_mysterious_hat', origin: 'Fairy\'s Trinket', price: 0 }, 303 | { name: 'item_n', origin: 'override', price: 0 }, 304 | { name: 'item_name_new', origin: 'override', price: 0 }, 305 | { name: 'item_name_old', origin: 'override', price: 0 }, 306 | { name: 'item_necronomicon_2', origin: 'Necronomicon', price: 0 }, 307 | { name: 'item_necronomicon_3', origin: 'Necronomicon', price: 0 }, 308 | { name: 'item_nian_flag_trap', origin: 'override', price: 0 }, 309 | { name: 'item_ofrenda', origin: 'Beloved Memory', price: 0 }, 310 | { name: 'item_ofrenda_pledge', origin: 'Forebearer\'s Fortune', price: 0 }, 311 | { name: 'item_ogre_seal_totem', origin: 'Ogre Seal Totem', price: 0 }, 312 | { name: 'item_orb_of_pestilence', origin: 'override', price: 0 }, 313 | { name: 'item_owner_name', origin: 'override', price: 0 }, 314 | { name: 'item_owner_type', origin: 'override', price: 0 }, 315 | { name: 'item_owning_bundle_name', origin: 'override', price: 0 }, 316 | { name: 'item_pack', origin: 'override', price: 0 }, 317 | { name: 'item_panel_id', origin: 'override', price: 0 }, 318 | { name: 'item_passives', origin: 'override', price: 0 }, 319 | { name: 'item_penta_edged_sword', origin: 'Penta-Edged Sword', price: 0 }, 320 | { name: 'item_pick_count', origin: 'override', price: 0 }, 321 | { name: 'item_pick_rate', origin: 'override', price: 0 }, 322 | { name: 'item_player_pick_count', origin: 'override', price: 0 }, 323 | { name: 'item_player_pick_percent', origin: 'override', price: 0 }, 324 | { name: 'item_player_slot', origin: 'override', price: 0 }, 325 | { name: 'item_player_win_count', origin: 'override', price: 0 }, 326 | { name: 'item_player_win_percent', origin: 'override', price: 0 }, 327 | { name: 'item_pocket_roshan', origin: 'Pocket Roshan', price: 1000 }, 328 | { name: 'item_pogo_stick', origin: 'Tumbler\'s Toy', price: 0 }, 329 | { name: 'item_prefab', origin: 'override', price: 0 }, 330 | { name: 'item_rarity_class', origin: 'override', price: 0 }, 331 | { name: 'item_rarity_name', origin: 'override', price: 0 }, 332 | { name: 'item_rarity_token', origin: 'override', price: 0 }, 333 | { name: 'item_redemption', origin: 'override', price: 0 }, 334 | { name: 'item_regular_price', origin: 'override', price: 0 }, 335 | { name: 'item_replace', origin: 'override', price: 0 }, 336 | { name: 'item_restriction_desc', origin: 'override', price: 0 }, 337 | { name: 'item_%s', origin: 'override', price: 0 }, 338 | { name: 'item_sale_amount', origin: 'override', price: 0 }, 339 | { name: 'item_schema_initialized', origin: 'override', price: 0 }, 340 | { name: 'item_seasonal_restriction', origin: 'override', price: 0 }, 341 | { name: 'item_set_name', origin: 'override', price: 0 }, 342 | { name: 'item_sets', origin: 'override', price: 0 }, 343 | { name: 'item_sort_index', origin: 'override', price: 0 }, 344 | { name: 'item_spooky_tpscroll', origin: 'override', price: 100 }, 345 | { name: 'item_spring2021_defusal_bomb', origin: 'override', price: 0 }, 346 | { name: 'item_state', origin: 'override', price: 0 }, 347 | { name: 'item_state_class', origin: 'override', price: 0 }, 348 | { name: 'item_steam_cache_version_t', origin: 'override', price: 0 }, 349 | { name: 'item_style_count', origin: 'override', price: 0 }, 350 | { name: 'item_style_%d', origin: 'override', price: 0 }, 351 | { name: 'item_subtitle', origin: 'override', price: 0 }, 352 | { name: 'item_super_blink', origin: 'override', price: 0 }, 353 | { name: 'item_super_overwhelming_blink', origin: 'override', price: 0 }, 354 | { name: 'item_tango_single', origin: 'Tango Single Instance', price: 30 }, 355 | { name: 'item_title_tag', origin: 'override', price: 0 }, 356 | { name: 'item_tombstone', origin: 'override', price: 0 }, 357 | { name: 'item_tome_of_omniscience', origin: 'override', price: 10 }, 358 | { name: 'item_tool', origin: 'override', price: 0 }, 359 | { name: 'item_tpscroll_2', origin: 'override', price: 100 }, 360 | { name: 'item_travel_boots_2', origin: 'Boots of Travel', price: 4500 }, 361 | { name: 'item_type', origin: 'override', price: 0 }, 362 | { name: 'item_type_name', origin: 'override', price: 0 }, 363 | { name: 'item_ultimate_scepter_roshan', origin: 'Aghanim\'s Blessing - Roshan', price: 5800 }, 364 | { name: 'item_uneditable_description', origin: 'override', price: 0 }, 365 | { name: 'item_upgraded_barricade', origin: 'override', price: 0 }, 366 | { name: 'item_upgraded_mortar', origin: 'override', price: 0 }, 367 | { name: 'item_vengeances_shadow', origin: 'Shadow of Vengeance', price: 0 }, 368 | { name: 'item_ward_dispenser', origin: 'Observer and Sentry Wards', price: 50 }, 369 | { name: 'item_win_count', origin: 'override', price: 0 }, 370 | { name: 'item_win_rate', origin: 'override', price: 0 }, 371 | { name: 'item_blood_grenade', origin: 'Blood Grenade', price: 50 }, 372 | { name: 'item_diadem', origin: 'Diadem', price: 1000 }, 373 | { name: 'item_blitz_knuckles', origin: 'Blitz Knuckles', price: 1000 }, 374 | { name: 'item_cornucopia', origin: 'Cornucopia', price: 1200 }, 375 | { name: 'item_fluffy_hat', origin: 'Fluffy Hat', price: 250 }, 376 | { name: 'item_voodoo_mask', origin: 'Voodoo Mask', price: 700 }, 377 | { name: 'item_orb_of_corrosion', origin: 'Orb of Corrosion', price: 925 }, 378 | { name: 'item_falcon_blade', origin: 'Falcon Blade', price: 1125 }, 379 | { name: 'item_helm_of_the_overlord', origin: 'Helm of the Overlord', price: 6400 }, 380 | { name: 'item_pavise', origin: 'Pavise', price: 1400 }, 381 | { name: 'item_holy_locket', origin: 'Holy Locket', price: 2400 }, 382 | { name: 'item_wraith_pact', origin: 'Wraith Pact', price: 4050 }, 383 | { name: 'item_boots_of_bearing', origin: 'Boots of Bearing', price: 4275 }, 384 | { name: 'item_witch_blade', origin: 'Witch Blade', price: 2600 }, 385 | { name: 'item_wind_waker', origin: 'Wind Waker', price: 6825 }, 386 | { name: 'item_eternal_shroud', origin: 'Eternal Shroud', price: 3100 }, 387 | { name: 'item_mage_slayer', origin: 'Mage Slayer', price: 2500 }, 388 | { name: 'item_phylactery', origin: 'Phylactery', price: 2375 }, 389 | { name: 'item_harpoon', origin: 'Harpoon', price: 4500 }, 390 | { name: 'item_arcane_blink', origin: 'Arcane Blink', price: 6800 }, 391 | { name: 'item_overwhelming_blink', origin: 'Overwhelming Blink', price: 6800 }, 392 | { name: 'item_swift_blink', origin: 'Swift Blink', price: 6800 }, 393 | { name: 'item_orb_of_revelations', origin: 'Orb of Revelations', price: 0 }, 394 | { name: 'item_voidwalker_scythe', origin: 'Voidwalker Scythe', price: 3900 }, 395 | { name: 'item_flying_courier', origin: 'Flying Courier', price: 100 }, 396 | { name: 'item_greater_salve', origin: 'Greater Salve', price: 275 }, 397 | { name: 'item_slippers_of_halcyon', origin: 'Slippers of Halcyon', price: 2075 }, 398 | { name: 'item_creed_of_omniscience', origin: 'Creed of Omniscience', price: 10 }, 399 | { name: 'item_pelt_of_the_old_wolf', origin: 'Pelt of the Old Wolf', price: 30 }, 400 | { name: 'item_paw_of_lucius', origin: 'Paw of Lucius', price: 25 }, 401 | { name: 'item_sign_of_the_arachnid', origin: 'Sign of the Arachnid', price: 65 }, 402 | { name: 'item_unhallowed_icon', origin: 'Unhallowed Icon', price: 75 }, 403 | { name: 'item_preserved_skull', origin: 'Preserved Skull', price: 60 }, 404 | { name: 'item_treads_of_ermacor', origin: 'Treads of Ermacor', price: 90 }, 405 | { name: 'item_guardian_shell', origin: 'Guardian Shell', price: 70 }, 406 | { name: 'item_carapace_of_qaldin', origin: 'Carapace of Qaldin', price: 130 }, 407 | { name: 'item_ice_dragon_maw', origin: 'Ice Dragon Maw', price: 40 }, 408 | { name: 'item_precious_egg', origin: 'Precious Egg', price: 45 }, 409 | { name: 'item_ambient_sorcery', origin: 'Ambient Sorcery', price: 35 }, 410 | { name: 'item_gravel_foot', origin: 'Gravel Foot', price: 55 }, 411 | { name: 'item_stonework_pendant', origin: 'Stonework Pendant', price: 65 }, 412 | { name: 'item_lifestone', origin: 'Lifestone', price: 50 }, 413 | { name: 'item_slippers_of_the_abyss', origin: 'Slippers of the Abyss', price: 130 }, 414 | { name: 'item_wand_of_the_brine', origin: 'Wand of the Brine', price: 110 }, 415 | { name: 'item_glimmerdark_shield', origin: 'Glimmerdark Shield', price: 160 }, 416 | { name: 'item_lance_of_pursuit', origin: 'Lance of Pursuit', price: 0 }, 417 | { name: 'item_occult_bracelet', origin: 'Occult Bracelet', price: 0 }, 418 | { name: 'item_possessed_mask', origin: 'Possessed Mask', price: 0 }, 419 | { name: 'item_seeds_of_serenity', origin: 'Seeds of Serenity', price: 0 }, 420 | { name: 'item_bullwhip', origin: 'Bullwhip', price: 0 }, 421 | { name: 'item_dagger_of_ristul', origin: 'Dagger of Ristul', price: 0 }, 422 | { name: 'item_eye_of_the_vizier', origin: 'Eye of the Vizier', price: 0 }, 423 | { name: 'item_philosophers_stone', origin: 'Philosopher\'s Stone', price: 0 }, 424 | { name: 'item_pupils_gift', origin: 'Pupil\'s Gift', price: 0 }, 425 | { name: 'item_specialists_array', origin: 'Mechanical Release', price: 0 }, 426 | { name: 'item_ceremonial_robe', origin: 'Ceremonial Robe', price: 0 }, 427 | { name: 'item_cloak_of_flames', origin: 'Cloak Of Flames', price: 0 }, 428 | { name: 'item_elven_tunic', origin: 'Elven Tunic', price: 0 }, 429 | { name: 'item_psychic_headband', origin: 'Psychic Headband', price: 0 }, 430 | { name: 'item_stormcrafter', origin: 'Stormcrafter', price: 0 }, 431 | { name: 'item_trickster_cloak', origin: 'Trickster Cloak', price: 0 }, 432 | { name: 'item_book_of_shadows', origin: 'Book Of Shadows', price: 0 }, 433 | { name: 'item_giants_ring', origin: 'Giant\'s Ring', price: 0 }, 434 | { name: 'item_chipped_vest', origin: 'Chipped Vest', price: 0 }, 435 | { name: 'item_poor_mans_shield', origin: 'Poor Man\'s Shield', price: 0 }, 436 | { name: 'item_princes_knife', origin: 'Prince\'s Knife', price: 0 }, 437 | { name: 'item_quicksilver_amulet', origin: 'Quicksilver Amulet', price: 0 }, 438 | { name: 'item_ancient_perseverance', origin: 'Ancient Perseverance', price: 0 }, 439 | { name: 'item_assassins_dagger', origin: 'Assassin\'s Dagger', price: 0 }, 440 | { name: 'item_gladiator_helm', origin: 'Gladiator Helm', price: 0 }, 441 | { name: 'item_gloves_of_travel', origin: 'Gloves Of Travel', price: 0 }, 442 | { name: 'item_icarus_wings', origin: 'Icarus Wings', price: 0 }, 443 | { name: 'item_light_robes', origin: 'Light Robes', price: 0 }, 444 | { name: 'item_mechanical_arm', origin: 'Mechanical Arm', price: 0 }, 445 | { name: 'item_oakheart', origin: 'Oakheart', price: 0 }, 446 | { name: 'item_overflowing_elixir', origin: 'Overflowing Elixir', price: 0 }, 447 | { name: 'item_satchel', origin: 'Satchel', price: 0 }, 448 | { name: 'item_star_mace', origin: 'Star Mace', price: 0 }, 449 | { name: 'item_venom_gland', origin: 'Venom Gland', price: 0 }, 450 | { name: 'item_warhammer', origin: 'Warhammer', price: 0 }, 451 | { name: 'item_wizard_glass', origin: 'Wizard Glass', price: 0 }, 452 | { name: 'item_book_of_strength', origin: 'Book of Strength', price: 0 }, 453 | { name: 'item_book_of_agility', origin: 'Book of Agility', price: 0 }, 454 | { name: 'item_book_of_intelligence', origin: 'Book of Intelligence', price: 0 }, 455 | { name: 'item_aghanims_shard_roshan', origin: 'Aghanim\'s Shard', price: 1400 }, 456 | { name: 'item_ascetic_cap', origin: 'Ascetic\'s Cap', price: 0 }, 457 | { name: 'item_bag_of_gold', origin: 'Gold', price: 0 }, 458 | { name: 'item_band_of_elvenskin', origin: '"Spider Queens Band"', price: 750 }, 459 | { name: 'item_bear_cloak', origin: 'Cloak of the Bear', price: 30 }, 460 | { name: 'item_courier', origin: 'Animal Courier', price: 50 }, 461 | { name: 'item_crimson_robe', origin: 'Vermillion Robe', price: 9750 }, 462 | { name: 'item_es_arcana_blink', origin: 'Greevil Blink Bone', price: 0 }, 463 | { name: 'item_gem_of_true_sight', origin: 'Potent Gem', price: 1850 }, 464 | { name: 'item_hand_of_midas_ogre_arcana', origin: 'Ogre Seal Totem', price: 0 }, 465 | { name: 'item_martyrs_plate', origin: 'Shadow Plate', price: 2600 }, 466 | { name: 'item_pocket_tower', origin: 'Pocket Campfire', price: 300 }, 467 | { name: 'item_revenants_brooch', origin: 'Revenant\'s Brooch', price: 6200 }, 468 | { name: 'item_royale_with_cheese', origin: 'Flask', price: 2 }, 469 | { name: 'item_tome_of_shadow_wave', origin: 'Shadow of Vengeance', price: 0 }, 470 | { name: 'item_recipe_abyssal_blade', origin: 'Recipe: Abyssal Blade', price: 1675 }, 471 | { name: 'item_recipe_aeon_disk', origin: 'Recipe: Aeon Disk', price: 1200 }, 472 | { name: 'item_recipe_aether_lens', origin: 'Recipe: Aether Lens', price: 775 }, 473 | { name: 'item_recipe_armlet', origin: 'Recipe: Armlet of Mordiggian', price: 625 }, 474 | { name: 'item_recipe_assault', origin: 'Recipe: Assault Cuirass', price: 1300 }, 475 | { name: 'item_recipe_black_king_bar', origin: 'Recipe: Black King Bar', price: 1450 }, 476 | { name: 'item_recipe_blade_mail', origin: 'Recipe: Blade Mail', price: 550 }, 477 | { name: 'item_recipe_bloodstone', origin: 'Recipe: Bloodstone', price: 700 }, 478 | { name: 'item_recipe_bloodthorn', origin: 'Recipe: Bloodthorn', price: 825 }, 479 | { name: 'item_recipe_buckler', origin: 'Recipe: Buckler', price: 250 }, 480 | { name: 'item_recipe_lesser_crit', origin: 'Recipe: Crystalys', price: 450 }, 481 | { name: 'item_recipe_greater_crit', origin: 'Recipe: Daedalus', price: 1000 }, 482 | { name: 'item_recipe_diffusal_blade', origin: 'Recipe: Diffusal Blade', price: 1050 }, 483 | { name: 'item_recipe_dragon_lance', origin: 'Recipe: Dragon Lance', price: 450 }, 484 | { name: 'item_recipe_ancient_janggo', origin: 'Recipe: Drum of Endurance', price: 500 }, 485 | { name: 'item_recipe_ethereal_blade', origin: 'Recipe: Ethereal Blade', price: 1100 }, 486 | { name: 'item_recipe_force_staff', origin: 'Recipe: Force Staff', price: 950 }, 487 | { name: 'item_recipe_glimmer_cape', origin: 'Recipe: Glimmer Cape', price: 350 }, 488 | { name: 'item_recipe_guardian_greaves', origin: 'Recipe: Guardian Greaves', price: 1450 }, 489 | { name: 'item_recipe_hand_of_midas', origin: 'Recipe: Hand of Midas', price: 1750 }, 490 | { name: 'item_recipe_headdress', origin: 'Recipe: Headdress', price: 250 }, 491 | { name: 'item_recipe_heart', origin: 'Recipe: Heart of Tarrasque', price: 1300 }, 492 | { name: 'item_recipe_helm_of_the_dominator', origin: 'Recipe: Helm of the Dominator', price: 650 }, 493 | { name: 'item_recipe_hurricane_pike', origin: 'Recipe: Hurricane Pike', price: 350 }, 494 | { name: 'item_recipe_kaya', origin: 'Recipe: Kaya', price: 600 }, 495 | { name: 'item_recipe_magic_wand', origin: 'Recipe: Magic Wand', price: 150 }, 496 | { name: 'item_recipe_manta', origin: 'Recipe: Manta', price: 500 }, 497 | { name: 'item_recipe_mekansm', origin: 'Recipe: Mekasmm', price: 800 }, 498 | { name: 'item_recipe_meteor_hammer', origin: 'Recipe: Meteor Hammer', price: 550 }, 499 | { name: 'item_recipe_mjollnir', origin: 'Recipe: Mjollnir', price: 800 }, 500 | { name: 'item_recipe_monkey_king_bar', origin: 'Recipe: Monkey King Bar', price: 600 }, 501 | { name: 'item_recipe_orchid', origin: 'Recipe: Orchid Malevolence', price: 275 }, 502 | { name: 'item_recipe_pipe', origin: 'Recipe: Pipe of Insight', price: 1450 }, 503 | { name: 'item_recipe_refresher', origin: 'Recipe: Refresher', price: 2600 }, 504 | { name: 'item_recipe_ring_of_basilius', origin: 'Recipe: Ring of Basilius', price: 250 }, 505 | { name: 'item_recipe_rod_of_atos', origin: 'Recipe: Rod of Atos', price: 250 }, 506 | { name: 'item_recipe_sange', origin: 'Recipe: Sange', price: 600 }, 507 | { name: 'item_recipe_silver_edge', origin: 'Recipe: Silver Edge', price: 550 }, 508 | { name: 'item_recipe_basher', origin: 'Recipe: Skull Basher', price: 825 }, 509 | { name: 'item_recipe_solar_crest', origin: 'Recipe: Solar Crest', price: 700 }, 510 | { name: 'item_recipe_soul_ring', origin: 'Recipe: Soul Ring', price: 350 }, 511 | { name: 'item_recipe_spirit_vessel', origin: 'Recipe: Spirit Vessel', price: 1200 }, 512 | { name: 'item_recipe_urn_of_shadows', origin: 'Recipe: Urn of Shadows', price: 375 }, 513 | { name: 'item_recipe_veil_of_discord', origin: 'Recipe: Veil of Discord', price: 650 }, 514 | { name: 'item_recipe_yasha', origin: 'Recipe: Yasha', price: 600 }, 515 | { name: 'item_recipe_aghsfort_bloodstone', origin: 'override', price: 0 }, 516 | { name: 'item_recipe_ancient_perseverance', origin: 'override', price: 0 }, 517 | { name: 'item_recipe_apex', origin: 'override', price: 0 }, 518 | { name: 'item_recipe_arcane_boots', origin: 'Recipe: Arcane Boots', price: 0 }, 519 | { name: 'item_recipe_arcane_ring', origin: 'override', price: 0 }, 520 | { name: 'item_recipe_ballista', origin: 'override', price: 0 }, 521 | { name: 'item_recipe_bfury', origin: 'Recipe: Battlefury', price: 450 }, 522 | { name: 'item_recipe_book_of_shadows', origin: 'override', price: 0 }, 523 | { name: 'item_recipe_bracer', origin: 'Recipe: Bracer', price: 210 }, 524 | { name: 'item_recipe_broom_handle', origin: 'override', price: 0 }, 525 | { name: 'item_recipe_bullwhip', origin: 'override', price: 0 }, 526 | { name: 'item_recipe_butterfly', origin: 'Recipe: Butterfly', price: 0 }, 527 | { name: 'item_recipe_ceremonial_robe', origin: 'override', price: 0 }, 528 | { name: 'item_recipe_chipped_vest', origin: 'override', price: 0 }, 529 | { name: 'item_recipe_cloak_of_flames', origin: 'override', price: 0 }, 530 | { name: 'item_recipe_clumsy_net', origin: 'override', price: 0 }, 531 | { name: 'item_recipe_cyclone', origin: 'Recipe: Eul\'s Scepter of Divinity', price: 675 }, 532 | { name: 'item_recipe_dagon', origin: 'Recipe: Dagon', price: 1150 }, 533 | { name: 'item_recipe_dagon_2', origin: 'override', price: 0 }, 534 | { name: 'item_recipe_dagon_3', origin: 'override', price: 0 }, 535 | { name: 'item_recipe_dagon_4', origin: 'override', price: 0 }, 536 | { name: 'item_recipe_dagon_5', origin: 'override', price: 0 }, 537 | { name: 'item_recipe_demonicon', origin: 'override', price: 0 }, 538 | { name: 'item_recipe_desolator', origin: 'Recipe: Desolator', price: 0 }, 539 | { name: 'item_recipe_desolator_2', origin: 'override', price: 0 }, 540 | { name: 'item_recipe_diffusal_blade_2', origin: 'override', price: 0 }, 541 | { name: 'item_recipe_dimensional_doorway', origin: 'override', price: 0 }, 542 | { name: 'item_recipe_dragon_scale', origin: 'override', price: 0 }, 543 | { name: 'item_recipe_eagle_eye', origin: 'override', price: 0 }, 544 | { name: 'item_recipe_echo_sabre', origin: 'Recipe: Echo Sabre', price: 0 }, 545 | { name: 'item_recipe_elven_tunic', origin: 'override', price: 0 }, 546 | { name: 'item_recipe_enchanted_quiver', origin: 'override', price: 0 }, 547 | { name: 'item_recipe_essence_ring', origin: 'override', price: 0 }, 548 | { name: 'item_recipe_ex_machina', origin: 'override', price: 0 }, 549 | { name: 'item_recipe_faded_broach', origin: 'override', price: 0 }, 550 | { name: 'item_recipe_fallen_sky', origin: 'Recipe: Fallen Sky', price: 0 }, 551 | { name: 'item_recipe_fluffy_hat', origin: 'override', price: 0 }, 552 | { name: 'item_recipe_force_boots', origin: 'override', price: 0 }, 553 | { name: 'item_recipe_fortitude_ring', origin: 'override', price: 0 }, 554 | { name: 'item_recipe_giants_ring', origin: 'override', price: 0 }, 555 | { name: 'item_recipe_gladiator_helm', origin: 'override', price: 0 }, 556 | { name: 'item_recipe_gloves_of_travel', origin: 'override', price: 0 }, 557 | { name: 'item_recipe_grandmasters_glaive', origin: 'Recipe: Grandmaster\'s Glaive', price: 0 }, 558 | { name: 'item_recipe_grove_bow', origin: 'override', price: 0 }, 559 | { name: 'item_recipe_gungir', origin: 'Recipe: Gleipnir', price: 700 }, 560 | { name: 'item_recipe_havoc_hammer', origin: 'override', price: 0 }, 561 | { name: 'item_recipe_helm_of_the_dominator_2', origin: 'override', price: 0 }, 562 | { name: 'item_recipe_hood_of_defiance', origin: 'Recipe: Hood of Defiance', price: 0 }, 563 | { name: 'item_recipe_illusionsts_cape', origin: 'override', price: 0 }, 564 | { name: 'item_recipe_invis_sword', origin: 'Recipe: Invisibility Sword', price: 0 }, 565 | { name: 'item_recipe_iron_talon', origin: 'Recipe: Iron Talon', price: 1 }, 566 | { name: 'item_recipe_ironwood_tree', origin: 'Recipe: Ironwood Tree', price: 1 }, 567 | { name: 'item_recipe_kaya_and_sange', origin: 'Recipe: Kaya and Sange', price: 0 }, 568 | { name: 'item_recipe_keen_optic', origin: 'override', price: 0 }, 569 | { name: 'item_recipe_lunar_crest', origin: 'Recipe: Lunar Crest', price: 250 }, 570 | { name: 'item_recipe_maelstrom', origin: 'Recipe: Maelstrom', price: 0 }, 571 | { name: 'item_recipe_magus_minimus', origin: 'override', price: 0 }, 572 | { name: 'item_recipe_mask_of_dispair', origin: 'override', price: 0 }, 573 | { name: 'item_recipe_mask_of_madness', origin: 'Recipe: Mask of Madness', price: 0 }, 574 | { name: 'item_recipe_medallion_of_courage', origin: 'Recipe: Medallion of Courage', price: 0 }, 575 | { name: 'item_recipe_mind_breaker', origin: 'override', price: 0 }, 576 | { name: 'item_recipe_mind_breaker_2', origin: 'override', price: 0 }, 577 | { name: 'item_recipe_minotaur_horn', origin: 'override', price: 0 }, 578 | { name: 'item_recipe_moon_shard', origin: 'Recipe: Moon Shard', price: 0 }, 579 | { name: 'item_recipe_mysterious_hat', origin: 'override', price: 0 }, 580 | { name: 'item_recipe_naginata', origin: 'override', price: 0 }, 581 | { name: 'item_recipe_necronomicon', origin: 'Recipe: Necronomicon', price: 1250 }, 582 | { name: 'item_recipe_necronomicon_2', origin: 'override', price: 0 }, 583 | { name: 'item_recipe_necronomicon_3', origin: 'override', price: 0 }, 584 | { name: 'item_recipe_nether_shawl', origin: 'override', price: 0 }, 585 | { name: 'item_recipe_ninja_gear', origin: 'override', price: 0 }, 586 | { name: 'item_recipe_nullifier', origin: 'Recipe: Nullifier', price: 0 }, 587 | { name: 'item_recipe_null_talisman', origin: 'Recipe: Null Talisman', price: 210 }, 588 | { name: 'item_recipe_oakheart', origin: 'override', price: 0 }, 589 | { name: 'item_recipe_oblivion_staff', origin: 'Recipe: Oblivion Staff', price: 0 }, 590 | { name: 'item_recipe_ocean_heart', origin: 'override', price: 0 }, 591 | { name: 'item_recipe_octarine_core', origin: 'Recipe: Octarine Core', price: 200 }, 592 | { name: 'item_recipe_orb_of_destruction', origin: 'override', price: 0 }, 593 | { name: 'item_recipe_orb_of_pestilence', origin: 'override', price: 0 }, 594 | { name: 'item_recipe_orb_of_revelations', origin: 'override', price: 0 }, 595 | { name: 'item_recipe_overflowing_elixir', origin: 'override', price: 0 }, 596 | { name: 'item_recipe_paladin_sword', origin: 'override', price: 0 }, 597 | { name: 'item_recipe_panic_button', origin: 'override', price: 0 }, 598 | { name: 'item_recipe_penta_edged_sword', origin: 'override', price: 0 }, 599 | { name: 'item_recipe_pers', origin: 'Recipe: Perseverence', price: 0 }, 600 | { name: 'item_recipe_phase_boots', origin: 'Recipe: Phase Boots', price: 0 }, 601 | { name: 'item_recipe_phoenix_ash', origin: 'override', price: 0 }, 602 | { name: 'item_recipe_phylactery', origin: 'Recipe: Phylactery', price: 0 }, 603 | { name: 'item_recipe_pirate_hat', origin: 'override', price: 0 }, 604 | { name: 'item_recipe_poor_mans_shield', origin: 'Recipe: Poor Man\'s Shield', price: 0 }, 605 | { name: 'item_recipe_possessed_mask', origin: 'override', price: 0 }, 606 | { name: 'item_recipe_power_treads', origin: 'Recipe: Power Treads', price: 0 }, 607 | { name: 'item_recipe_psychic_headband', origin: 'override', price: 0 }, 608 | { name: 'item_recipe_pupils_gift', origin: 'override', price: 0 }, 609 | { name: 'item_recipe_quickening_charm', origin: 'override', price: 0 }, 610 | { name: 'item_recipe_quicksilver_amulet', origin: 'override', price: 0 }, 611 | { name: 'item_recipe_radiance', origin: 'Recipe: Radiance', price: 0 }, 612 | { name: 'item_recipe_rapier', origin: 'Recipe: Divine Rapier', price: 0 }, 613 | { name: 'item_recipe_ring_of_aquila', origin: 'Recipe: Ring of Aquila', price: 0 }, 614 | { name: 'item_recipe_sange_and_yasha', origin: 'Recipe: Sange and Yasha', price: 0 }, 615 | { name: 'item_recipe_satanic', origin: 'Recipe: Satanic', price: 0 }, 616 | { name: 'item_recipe_seer_stone', origin: 'override', price: 0 }, 617 | { name: 'item_recipe_sheepstick', origin: 'Recipe: Scythe of Vyse', price: 0 }, 618 | { name: 'item_recipe_skadi', origin: 'Recipe: Skadi', price: 0 }, 619 | { name: 'item_recipe_sorcerers_staff', origin: 'override', price: 0 }, 620 | { name: 'item_recipe_soul_booster', origin: 'Recipe: Soul Booster', price: 0 }, 621 | { name: 'item_recipe_star_mace', origin: 'override', price: 0 }, 622 | { name: 'item_recipe_stormcrafter', origin: 'override', price: 0 }, 623 | { name: 'item_recipe_tenderizer', origin: 'override', price: 0 }, 624 | { name: 'item_recipe_the_leveller', origin: 'override', price: 0 }, 625 | { name: 'item_recipe_timeless_relic', origin: 'override', price: 0 }, 626 | { name: 'item_recipe_titan_sliver', origin: 'override', price: 0 }, 627 | { name: 'item_recipe_tranquil_boots', origin: 'Recipe: Tranquil Boots', price: 0 }, 628 | { name: 'item_recipe_tranquil_boots2', origin: 'override', price: 0 }, 629 | { name: 'item_recipe_travel_boots', origin: 'Recipe: Boots of Travel', price: 2000 }, 630 | { name: 'item_recipe_travel_boots_2', origin: 'Recipe: Boots of Travel', price: 0 }, 631 | { name: 'item_recipe_trickster_cloak', origin: 'override', price: 0 }, 632 | { name: 'item_recipe_trusty_shovel', origin: 'override', price: 0 }, 633 | { name: 'item_recipe_ultimate_scepter_2', origin: 'Recipe: Aghanim\'s Blessing Recipe', price: 1600 }, 634 | { name: 'item_recipe_vambrace', origin: 'override', price: 0 }, 635 | { name: 'item_recipe_vanguard', origin: 'Recipe: Vanguard', price: 0 }, 636 | { name: 'item_recipe_vengeances_shadow', origin: 'override', price: 0 }, 637 | { name: 'item_recipe_venom_gland', origin: 'override', price: 0 }, 638 | { name: 'item_recipe_vermillion_robe', origin: 'override', price: 0 }, 639 | { name: 'item_recipe_vladmir', origin: 'Recipe: Vladimir', price: 250 }, 640 | { name: 'item_recipe_voidwalker_scythe', origin: 'override', price: 0 }, 641 | { name: 'item_recipe_ward_dispenser', origin: 'Recipe: Ward Dispenser', price: 0 }, 642 | { name: 'item_recipe_warhammer', origin: 'override', price: 0 }, 643 | { name: 'item_recipe_wizard_glass', origin: 'override', price: 0 }, 644 | { name: 'item_recipe_woodland_striders', origin: 'override', price: 0 }, 645 | { name: 'item_recipe_wraith_band', origin: 'Recipe: Wraith Band', price: 210 }, 646 | { name: 'item_recipe_yasha_and_kaya', origin: 'Recipe: Yasha and Kaya', price: 0 }, 647 | { name: 'item_recipe_arcane_blink', origin: 'Recipe: Arcane Blink', price: 1750 }, 648 | { name: 'item_recipe_boots_of_bearing', origin: 'Recipe: Boots of Bearing', price: 1700 }, 649 | { name: 'item_recipe_crimson_guard', origin: 'Recipe: Crimson Guard', price: 1050 }, 650 | { name: 'item_recipe_eternal_shroud', origin: 'Recipe: Eternal Shroud', price: 600 }, 651 | { name: 'item_recipe_falcon_blade', origin: 'Recipe: Falcon Blade', price: 250 }, 652 | { name: 'item_recipe_heavens_halberd', origin: 'Recipe: Heaven\'s Halberd', price: 200 }, 653 | { name: 'item_recipe_helm_of_the_overlord', origin: 'Recipe: Helm of the Dominator', price: 1325 }, 654 | { name: 'item_recipe_holy_locket', origin: 'Recipe: Holy Locket', price: 525 }, 655 | { name: 'item_recipe_mage_slayer', origin: 'Recipe: Mage Slayer', price: 200 }, 656 | { name: 'item_recipe_orb_of_corrosion', origin: 'Recipe: Orb of Corrosion', price: 100 }, 657 | { name: 'item_recipe_overwhelming_blink', origin: 'Recipe: Overwhelming Blink', price: 1750 }, 658 | { name: 'item_recipe_revenants_brooch', origin: 'Recipe: Revenant\'s Brooch', price: 800 }, 659 | { name: 'item_recipe_shivas_guard', origin: 'Recipe: Shiva\'s Guard', price: 650 }, 660 | { name: 'item_recipe_swift_blink', origin: 'Recipe: Swift Blink', price: 1750 }, 661 | { name: 'item_recipe_wind_waker', origin: 'Recipe: Wind Waker', price: 1400 }, 662 | { name: 'item_recipe_witch_blade', origin: 'Recipe: Witch Blade', price: 600 }, 663 | { name: 'item_recipe_wraith_pact', origin: 'Recipe: Wraith Pact', price: 400 }, 664 | { name: 'item_recipe_sphere', origin: 'Recipe: Sphere', price: 1350 }, 665 | { name: 'item_crown', origin: 'Crown', price: 450 }, 666 | { name: 'item_famango', origin: 'Healing Lotus', price: 0 }, 667 | { name: 'item_recipe_samurai_tabi', origin: 'Recipe: Samurai Tabi', price: 1100 }, 668 | { name: 'item_samurai_tabi', origin: 'Samurai Tabi', price: 4500 }, 669 | { name: 'item_recipe_hermes_sandals', origin: 'Recipe: Hermes Sandals', price: 500 }, 670 | { name: 'item_hermes_sandals', origin: 'Hermes Sandals', price: 4800 }, 671 | { name: 'item_recipe_witches_switch', origin: 'Recipe: Witches Switch', price: 625 }, 672 | { name: 'item_witches_switch', origin: 'Witches Switch', price: 1900 }, 673 | { name: 'item_dagon', origin: 'Dagon', price: 2850 }, 674 | { name: 'item_necronomicon', origin: 'Necronomicon', price: 2050 }, 675 | { name: 'item_recipe_ultimate_scepter', origin: 'Recipe: Scepter', price: 0 }, 676 | { name: 'item_recipe_lotus_orb', origin: 'Recipe: Lotus Orb', price: 250 }, 677 | { name: 'item_recipe_disperser', origin: 'Recipe: Disperser', price: 1000 }, 678 | { name: 'item_disperser', origin: 'Disperser', price: 5700 }, 679 | { name: 'item_recipe_pavise', origin: 'Recipe: Pavise', price: 275 }, 680 | { name: 'item_mutation_tombstone', origin: 'Mutation Tombstone', price: 0 }, 681 | { name: 'item_force_field', origin: 'Arcanist\'s Armor', price: 0 }, 682 | { name: 'item_black_powder_bag', origin: 'Blast Rig', price: 0 }, 683 | { name: 'item_spark_of_courage', origin: 'Spark Of Courage', price: 0 }, 684 | { name: 'item_trident', origin: 'Trident', price: 0 }, 685 | { name: 'item_heavy_blade', origin: 'Witchbane', price: 0 }, 686 | { name: 'item_unstable_wand', origin: 'Pig Pole', price: 0 }, 687 | { name: 'item_fortitude_ring', origin: 'Ring of Fortitude', price: 0 }, 688 | { name: 'item_paintball', origin: 'Fae Grenade', price: 0 }, 689 | { name: 'item_recipe_harpoon', origin: 'Recipe: Harpoon', price: 1000 }, 690 | { name: 'item_wand_of_sanctitude', origin: 'Wand of Sanctity', price: 0 }, 691 | { name: 'item_great_famango', origin: 'Great Healing Lotus', price: 0 }, 692 | { name: 'item_greater_famango', origin: 'Greater Healing Lotus', price: 0 }, 693 | { name: 'item_tiara_of_selemene', origin: 'Tiara of Selemene', price: 1800 }, 694 | { name: 'item_recipe_angels_demise', origin: 'Khanda Recipe', price: 600 }, 695 | { name: 'item_angels_demise', origin: 'Khanda', price: 5000 }, 696 | { name: 'item_aetherial_halo', origin: 'Aetherial Hammer', price: 5900 }, 697 | { name: 'item_caster_rapier', origin: 'Caster Rapier', price: 5600 }, 698 | { name: 'item_combo_breaker', origin: 'combo_breaker', price: 0 }, 699 | { name: 'item_devastator', origin: 'Parasma', price: 5575 }, 700 | { name: 'item_sample_picker', origin: "Assassin's Contract", price: 0 }, 701 | { name: 'item_misericorde', origin: "Brigand's Blade", price: 0 }, 702 | { name: 'item_slime_vial', origin: 'slime_vial', price: 0 }, 703 | { name: 'item_defiant_shell', origin: 'Defiant Shell', price: 0 }, 704 | { name: 'item_arcane_scout', origin: 'arcane_scout', price: 0 }, 705 | { name: 'item_barricade', origin: 'barricade', price: 0 }, 706 | { name: 'item_manacles_of_power', origin: 'manacles_of_power', price: 0 }, 707 | { name: 'item_ofrenda_shovel', origin: 'Scrying Shovel', price: 0 }, 708 | { name: 'item_muertas_gun', origin: 'Mercy & Grace', price: 0 }, 709 | { name: 'item_tier1_token', origin: 'Tier 1 Token', price: 0 }, 710 | { name: 'item_tier2_token', origin: 'Tier 2 Token', price: 0 }, 711 | { name: 'item_tier3_token', origin: 'Tier 3 Token', price: 0 }, 712 | { name: 'item_tier4_token', origin: 'Tier 4 Token', price: 0 }, 713 | { name: 'item_tier5_token', origin: 'Tier 5 Token', price: 0 }, 714 | { name: 'item_vindicators_axe', origin: "Vindicator's Axe", price: 0 }, 715 | { name: 'item_duelist_gloves', origin: 'Duelist Gloves', price: 0 }, 716 | { name: 'item_ancient_guardian', origin: 'Ancient Guardian', price: 0 }, 717 | { name: 'item_safety_bubble', origin: 'Safety Bubble', price: 0 }, 718 | { name: 'item_whisper_of_the_dread', origin: 'Whisper of the Dread', price: 0 }, 719 | { name: 'item_nemesis_curse', origin: 'Nemesis Curse', price: 0 }, 720 | { name: 'item_avianas_feather', origin: "Aviana's Feather", price: 0 }, 721 | { name: 'item_unwavering_condition', origin: 'Unwavering Condition', price: 0 }, 722 | { name: 'item_horizons_equilibrium', origin: "Horizon's Equilibrium", price: 0 }, 723 | { name: 'item_blighted_spirit', origin: 'Blighted Spirit', price: 0 }, 724 | { name: 'item_dandelion_amulet', origin: 'Dandelion Amulet', price: 0 }, 725 | { name: 'item_turtle_shell', origin: 'Turtle Shell', price: 0 }, 726 | { name: 'item_gossamer_cape', origin: 'Gossamer Cape', price: 0 }, 727 | { name: 'item_light_collector', origin: 'Light Collector', price: 0 }, 728 | { name: 'item_rattlecage', origin: 'Rattlecage', price: 0 }, 729 | { name: 'item_doubloon', origin: 'Doubloon', price: 0 }, 730 | { name: 'item_roshans_banner', origin: "Roshan's Banner", price: 0 } 731 | ]; 732 | export default items; -------------------------------------------------------------------------------- /src/items_prices.ts: -------------------------------------------------------------------------------- 1 | import items from './items.js'; 2 | 3 | type ItemPriceDefinition = { 4 | name: string; 5 | origin: string; 6 | price: number; 7 | }; 8 | 9 | type ItemDefinitions = { [key: string]: ItemPriceDefinition }; 10 | 11 | const itemDefinitions: ItemDefinitions = items.reduce((p, x) => { 12 | p[x.name] = x; 13 | return p; 14 | }, {} as ItemDefinitions); 15 | 16 | export const getItem = (itemName: string): ItemPriceDefinition => { 17 | if (itemName in itemDefinitions) { 18 | return itemDefinitions[itemName]!; 19 | } 20 | 21 | return { 22 | name: itemName, 23 | price: 0, 24 | origin: 'unknown' 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/parsed.d.ts: -------------------------------------------------------------------------------- 1 | import { GSIEvent as GSIEventRaw, PlayerInTierIds, Roshan, TierIds } from './dota2'; 2 | 3 | export type Faction = 'dire' | 'radiant'; 4 | //Dire = bad = team3, radiant = good = team2; 5 | 6 | export type Side = 'bad' | 'good'; 7 | 8 | export type AttackType = 'melee' | 'range' | null; 9 | 10 | export type BuildingType = 'tower' | 'rax' | 'fort'; 11 | 12 | export type MapSides = 'top' | 'mid' | 'bot'; 13 | 14 | export type CourierItem = { 15 | owner: number; 16 | name: string; 17 | }; 18 | 19 | export type Courier = { 20 | health: number; 21 | max_health: number; 22 | alive: boolean; 23 | boost: boolean; 24 | flying_upgrade: boolean; 25 | shield: boolean; 26 | respawn_time_remaining: number; 27 | xpos: number; 28 | ypos: number; 29 | yaw: number; 30 | items: CourierItem[]; 31 | lost_items: CourierItem[]; 32 | owner: number; 33 | team?: Faction; 34 | }; 35 | 36 | export type KillEvent = { 37 | killer: Player; 38 | victim: Player; 39 | }; 40 | 41 | export type GSIEvent = GSIEventRaw; 42 | 43 | export interface Outposts { 44 | outsideNorth?: Faction; 45 | jungleNorth?: Faction; 46 | jungleSouth?: Faction; 47 | outsideSouth?: Faction; 48 | } 49 | 50 | export interface Dota2 { 51 | buildings: Building[]; 52 | provider: Provider; 53 | map: Map; 54 | players: Player[]; 55 | draft: Draft; 56 | player: Player | null; 57 | roshan: Roshan | null; 58 | outposts: Outposts; 59 | events: GSIEvent[] | null; 60 | neutral_items: NeutralItems | null; 61 | //previously?: Previously | null; 62 | //added?: Added | null; 63 | } 64 | 65 | export interface Building { 66 | health: number; 67 | max_health: number; 68 | faction: Faction; 69 | attack: AttackType; 70 | type: BuildingType; 71 | side: Side; 72 | position: MapSides | null; 73 | number: number | null; 74 | } 75 | 76 | export interface Provider { 77 | name: string; 78 | appid: number; 79 | version: number; 80 | timestamp: number; 81 | } 82 | 83 | export interface Team { 84 | ward_purchase_cooldown: number; 85 | name: string; 86 | map_score: number; 87 | extra: Record; 88 | 89 | id: string | null; 90 | country: string | null; 91 | logo: string | null; 92 | short_name: string | null; 93 | } 94 | 95 | export interface Map { 96 | name: string; 97 | matchid: string; 98 | game_time: number; 99 | clock_time: number; 100 | daytime: boolean; 101 | nightstalker_night: boolean; 102 | game_state: string; 103 | paused: boolean; 104 | win_team: string; 105 | customgamename: string; 106 | radiant: Team; 107 | dire: Team; 108 | roshan_state: string; 109 | roshan_state_end_seconds: number; 110 | radiant_win_chance: number; 111 | } 112 | 113 | export type KillEntry = { 114 | victimid: number; 115 | amount: number; 116 | }; 117 | 118 | export interface Player { 119 | steamid: string; 120 | defaultName: string; 121 | id: number; 122 | 123 | realName: string | null; 124 | country: string | null; 125 | avatar: string | null; 126 | extra: Record; 127 | 128 | hero: Hero | null; 129 | abilities: Ability[]; 130 | items: Item[]; 131 | wearables: Wearable[]; 132 | kill_list: KillEntry[]; 133 | courier: Courier | null; 134 | name: string; 135 | activity: string; 136 | kills: number; 137 | deaths: number; 138 | assists: number; 139 | last_hits: number; 140 | hero_healing: number; 141 | denies: number; 142 | kill_streak: number; 143 | commands_issued: number; 144 | tower_damage: number; 145 | team_name: string; 146 | gold: number; 147 | team_slot: number; 148 | player_slot: number; 149 | gold_reliable: number; 150 | gold_unreliable: number; 151 | gold_from_hero_kills: number; 152 | gold_from_creep_kills: number; 153 | gold_from_income: number; 154 | gold_from_shared: number; 155 | gpm: number; 156 | xpm: number; 157 | net_worth: number; 158 | hero_damage: number; 159 | wards_purchased: number; 160 | wards_placed: number; 161 | wards_destroyed: number; 162 | runes_activated: number; 163 | camps_stacked: number; 164 | support_gold_spent: number; 165 | consumable_gold_spent: number; 166 | item_gold_spent: number; 167 | gold_lost_to_death: number; 168 | gold_spent_on_buybacks: number; 169 | } 170 | 171 | export interface Hero { 172 | id: number; 173 | facet?: number | null; 174 | facetIndex?: number | null; 175 | xpos?: number | null; 176 | ypos?: number | null; 177 | name?: string | null; 178 | level?: number | null; 179 | xp?: number | null; 180 | alive?: boolean | null; 181 | respawn_seconds?: number | null; 182 | buyback_cost?: number | null; 183 | buyback_cooldown?: number | null; 184 | health?: number | null; 185 | max_health?: number | null; 186 | health_percent?: number | null; 187 | mana?: number | null; 188 | max_mana?: number | null; 189 | mana_percent?: number | null; 190 | silenced?: boolean | null; 191 | stunned?: boolean | null; 192 | disarmed?: boolean | null; 193 | magicimmune?: boolean | null; 194 | hexed?: boolean | null; 195 | muted?: boolean | null; 196 | break?: boolean | null; 197 | aghanims_scepter?: boolean | null; 198 | aghanims_shard?: boolean | null; 199 | smoked?: boolean | null; 200 | has_debuff?: boolean | null; 201 | selected_unit?: boolean | null; 202 | talent_1?: boolean | null; 203 | talent_2?: boolean | null; 204 | talent_3?: boolean | null; 205 | talent_4?: boolean | null; 206 | talent_5?: boolean | null; 207 | talent_6?: boolean | null; 208 | talent_7?: boolean | null; 209 | talent_8?: boolean | null; 210 | attributes_level?: number | null; 211 | } 212 | 213 | export interface Ability { 214 | id: number; 215 | name: string; 216 | level: number; 217 | can_cast: boolean; 218 | passive: boolean; 219 | ability_active: boolean; 220 | cooldown: number; 221 | ultimate: boolean; 222 | charges?: number; 223 | max_charges?: number; 224 | charge_cooldown?: number; 225 | } 226 | 227 | export type ItemType = 'slot' | 'stash' | 'teleport' | 'neutral'; 228 | 229 | export interface Item { 230 | id: number; 231 | name: string; 232 | type: ItemType; 233 | purchaser?: number | null; 234 | can_cast?: boolean | null; 235 | cooldown?: number | null; 236 | passive?: boolean | null; 237 | charges?: number | null; 238 | item_level?: number | null; 239 | contains_rune?: 'empty' | 'water' | 'arcane' | 'double_damage' | 'haste' | 'regen' | 'shield' | 'illusion'; 240 | } 241 | 242 | export type DraftEntry = { 243 | type: 'pick' | 'ban'; 244 | player_id: number; 245 | class: string; 246 | order: number; 247 | }; 248 | 249 | export type TeamDraft = { 250 | home_team: boolean; 251 | bonus_time: number; 252 | picks: DraftEntry[]; 253 | }; 254 | 255 | export interface Draft { 256 | activeteam?: number; 257 | pick?: boolean; 258 | activeteam_time_remaining?: number; 259 | radiant?: TeamDraft; 260 | dire?: TeamDraft; 261 | } 262 | 263 | export type WearableType = 'wearable' | 'style'; 264 | 265 | export type Wearable = { 266 | id: number; 267 | type: WearableType; 268 | value: number; 269 | }; 270 | 271 | export type NeutralItemInTier = 272 | { 273 | name: string; 274 | tier: number; 275 | } & ( 276 | | { 277 | state: 'stash'; 278 | } 279 | | { 280 | state: 'unknown'; 281 | } 282 | | { 283 | state: 'equipped'; 284 | player_id: number; 285 | } 286 | ); 287 | 288 | 289 | export type NeutralItemsInTier = { 290 | [x in `item${ItemTierIds}`]: NeutralItemInTier 291 | } & { 292 | completion_time?: number; 293 | }; 294 | 295 | export type TeamNeutralItems = { 296 | items_found: number; 297 | } & { 298 | [x in `tier${TierIds}`]: NeutralItemsInTier; 299 | }; 300 | 301 | export type NeutralItems = { 302 | team2: TeamNeutralItems; 303 | team3: TeamNeutralItems; 304 | } & { 305 | [x in `tier${TierIds}`]: { 306 | tier: number; 307 | max_count: number; 308 | drop_after_time: number; 309 | }; 310 | }; 311 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AbilityRaw, 3 | BuildingInfo, 4 | CourierRaw, 5 | ItemRaw, 6 | MapRaw, 7 | PlayerRaw, 8 | DirePlayerIds, 9 | Dota2Raw, 10 | PlayerKey, 11 | PlayerKeys, 12 | RadiantPlayerIds, 13 | NeutralItemsRaw, 14 | TeamBuildingsKeys, 15 | TeamDraftRaw, 16 | MinimapPoint, 17 | NeutralItemsInPlayerRaw, 18 | TeamNeutralItemsRaw, 19 | TierIds 20 | } from './dota2'; 21 | import type { 22 | Ability, 23 | AttackType, 24 | Building, 25 | BuildingType, 26 | Faction, 27 | Item, 28 | ItemType, 29 | Map, 30 | MapSides, 31 | PlayerExtension, 32 | Side, 33 | Team, 34 | TeamExtension, 35 | Wearable, 36 | WearableType 37 | } from './interfaces'; 38 | import type { Courier, CourierItem, Dota2, DraftEntry, Hero, NeutralItemInTier, NeutralItems, NeutralItemsInTier, Outposts, Player, TeamNeutralItems } from './parsed'; 39 | 40 | type RadiantPlayers = PlayerKey; 41 | type DirePlayers = PlayerKey; 42 | 43 | type Attributes = 'abilities' | 'items' | 'wearables'; 44 | 45 | type AttributeList = T extends 'abilities' 46 | ? Ability 47 | : T extends 'items' 48 | ? Item 49 | : T extends 'wearables' 50 | ? Wearable 51 | : never; 52 | 53 | const getPlayersAttribute = ( 54 | playerId: number, 55 | data: Dota2Raw, 56 | attribute: T 57 | ): AttributeList[] => { 58 | const identifier = `player${playerId}` as PlayerKeys; 59 | 60 | const attributeSlots = 61 | data[attribute]?.team2[identifier as RadiantPlayers] || data[attribute]?.team3[identifier as DirePlayers]; 62 | 63 | if (!attributeSlots) return [] as any[]; 64 | 65 | const response = []; 66 | 67 | for (const [key, entry] of Object.entries(attributeSlots)) { 68 | if (!entry && entry !== 0) continue; 69 | 70 | const id = Number(key.replace(/([^0-9])/g, '')); 71 | 72 | if (isNaN(id)) continue; 73 | 74 | if (attribute === 'abilities') { 75 | const element: Ability = { 76 | ...(entry as AbilityRaw), 77 | id 78 | }; 79 | response.push(element); 80 | } else if (attribute === 'items') { 81 | const type = key.replace(/([0-9])/g, '') as ItemType; 82 | const element: Item = { 83 | ...(entry as ItemRaw), 84 | type, 85 | id 86 | }; 87 | response.push(element); 88 | } else if (attribute === 'wearables') { 89 | const type = key.replace(/([0-9])/g, '') as WearableType; 90 | const element: Wearable = { 91 | id, 92 | type, 93 | value: entry as number 94 | }; 95 | response.push(element); 96 | } 97 | } 98 | 99 | return response as AttributeList[]; 100 | }; 101 | 102 | const getPlayersCourier = ( 103 | id: number, 104 | couriers: { [courierName: string]: CourierRaw }, 105 | lastCouriers: Courier[], 106 | team: string 107 | ) => { 108 | for (const courier in couriers) { 109 | if (!couriers[courier]) continue; 110 | if (Number(couriers[courier]!.owner) === id) return parseCourier(couriers[courier]!, lastCouriers[id], team); 111 | } 112 | return undefined; 113 | }; 114 | 115 | export const parsePlayer = ( 116 | basePlayer: PlayerRaw, 117 | id: number, 118 | data: Dota2Raw, 119 | extensions: PlayerExtension[], 120 | lastData?: Dota2 121 | ) => { 122 | const extension = extensions.find(player => player.steamid === basePlayer.steamid); 123 | 124 | const identifier = `player${id}` as PlayerKeys; 125 | 126 | const targetHero = 127 | (data.hero.team2[identifier as RadiantPlayers] || data.hero.team3[identifier as DirePlayers] || null) as Hero | null; 128 | 129 | if (targetHero && targetHero.facet !== null && targetHero.facet !== undefined) { 130 | targetHero.facetIndex = targetHero.facet - 1; 131 | } 132 | const player: Player = { 133 | ...basePlayer, 134 | id, 135 | hero: targetHero, 136 | abilities: getPlayersAttribute(id, data, 'abilities'), 137 | items: getPlayersAttribute(id, data, 'items'), 138 | wearables: getPlayersAttribute(id, data, 'wearables'), 139 | name: (extension && extension.name) || basePlayer.name, 140 | defaultName: basePlayer.name, 141 | country: (extension && extension.country) || null, 142 | avatar: (extension && extension.avatar) || null, 143 | extra: (extension && extension.extra) || {}, 144 | realName: (extension && extension.realName) || null, 145 | courier: 146 | getPlayersCourier( 147 | id, 148 | data.couriers || {}, 149 | lastData ? lastData.players.flatMap(x => (x.courier ? [x.courier] : [])) : [], 150 | basePlayer.team_name 151 | ) || null, 152 | kill_list: [] 153 | }; 154 | 155 | for (const [key, value] of Object.entries(basePlayer.kill_list || {})) { 156 | if (!value) continue; 157 | const victimid = Number(key.replace(/([^0-9])/g, '')); 158 | const existingEntry = player.kill_list.find(killEntry => killEntry.victimid === victimid); 159 | 160 | if (!existingEntry) { 161 | player.kill_list.push({ 162 | victimid, 163 | amount: value 164 | }); 165 | continue; 166 | } 167 | existingEntry.amount = value; 168 | } 169 | 170 | return player; 171 | }; 172 | 173 | export const parseTeam = (map: MapRaw, type: Faction, extension: TeamExtension | null): Team => ({ 174 | ward_purchase_cooldown: map[type === 'dire' ? `dire_ward_purchase_cooldown` : 'radiant_ward_purchase_cooldown'], 175 | name: (extension && extension.name) || type.toUpperCase(), 176 | map_score: (extension && extension.map_score) || 0, 177 | extra: (extension && extension.extra) || {}, 178 | id: (extension && extension.id) || null, 179 | country: (extension && extension.country) || null, 180 | logo: (extension && extension.logo) || null, 181 | short_name: (extension && extension.short_name) || null 182 | }); 183 | 184 | export const parseMap = ( 185 | rawMap: MapRaw, 186 | extensions: { dire: TeamExtension | null; radiant: TeamExtension | null } 187 | ): Map => { 188 | const map: Map = { 189 | name: rawMap.name, 190 | matchid: rawMap.matchid, 191 | game_state: rawMap.game_state, 192 | game_time: rawMap.game_time, 193 | clock_time: rawMap.clock_time, 194 | daytime: rawMap.daytime, 195 | nightstalker_night: rawMap.nightstalker_night, 196 | paused: rawMap.paused, 197 | win_team: rawMap.win_team, 198 | customgamename: rawMap.customgamename, 199 | roshan_state: rawMap.roshan_state, 200 | roshan_state_end_seconds: rawMap.roshan_state_end_seconds, 201 | radiant_win_chance: rawMap.radiant_win_chance, 202 | dire: parseTeam(rawMap, 'dire', extensions.dire), 203 | radiant: parseTeam(rawMap, 'radiant', extensions.radiant) 204 | }; 205 | return map; 206 | }; 207 | 208 | export const parseBuilding = (buildingKey: TeamBuildingsKeys, building: BuildingInfo): Building => { 209 | const side = buildingKey.includes('good') ? 'good' : ('bad' as Side); 210 | const faction = side === 'good' ? 'radiant' : ('dire' as Faction); 211 | let attack: AttackType = null; 212 | let type: BuildingType = 'tower'; 213 | 214 | let order: number | null = null; 215 | 216 | if (buildingKey.includes('rax')) { 217 | type = 'rax'; 218 | attack = buildingKey.includes('melee') ? 'melee' : 'range'; 219 | } else if (buildingKey.includes('fort')) { 220 | type = 'fort'; 221 | } else { 222 | const towerNumber = Number(buildingKey.substr(buildingKey.indexOf('tower') + 5, 1)); 223 | if (!isNaN(towerNumber)) { 224 | order = towerNumber; 225 | } 226 | } 227 | 228 | const lastSegment = buildingKey.substring(buildingKey.lastIndexOf('_') + 1); 229 | 230 | const position = ['top', 'bot', 'mid'].includes(lastSegment) ? (lastSegment as MapSides) : null; 231 | 232 | return { 233 | side, 234 | faction, 235 | attack, 236 | type, 237 | position, 238 | number: order, 239 | ...building 240 | }; 241 | }; 242 | 243 | export const parseDraft = (draft: TeamDraftRaw) => { 244 | const entries: DraftEntry[] = []; 245 | 246 | const keys = Object.keys(draft).sort(); 247 | 248 | for (const key of keys) { 249 | if (key === 'home_team') continue; 250 | 251 | const order = Number(key.replace(/([^0-9])/g, '')); 252 | 253 | if (isNaN(order)) continue; 254 | 255 | const type = key.startsWith('pick') ? 'pick' : 'ban'; 256 | 257 | const value = draft[key as keyof TeamDraftRaw]; 258 | 259 | let currentEntry = entries.find(entry => entry.order === order && entry.type === type); 260 | if (!currentEntry) { 261 | currentEntry = { 262 | player_id: 0, 263 | type, 264 | class: '', 265 | order 266 | }; 267 | entries.push(currentEntry); 268 | } 269 | if (key.includes('_id')) { 270 | currentEntry.player_id = value as number; 271 | } else { 272 | currentEntry.class = value as string; 273 | } 274 | } 275 | 276 | return entries; 277 | }; 278 | 279 | export const parseCourier = (courier: CourierRaw, lastCourier?: Courier, team?: string): Courier => { 280 | const items = []; 281 | for (const item in courier.items) { 282 | items.push(courier.items[item]!); 283 | } 284 | 285 | let lostItems: CourierItem[] = []; 286 | if (!courier.alive && lastCourier) { 287 | if (!lastCourier.alive) { 288 | lostItems = lastCourier.lost_items; 289 | } else { 290 | lostItems = lastCourier.items; 291 | } 292 | } 293 | 294 | return { 295 | ...courier, 296 | items: items.map(x => ({ 297 | name: x.name, 298 | owner: Number(x.owner) 299 | })), 300 | team: team === undefined ? undefined : team === 'radiant' ? 'radiant' : 'dire', 301 | owner: Number(courier.owner), 302 | lost_items: lostItems 303 | }; 304 | }; 305 | 306 | export const parseOutposts = (minimap?: { [pointName: string]: MinimapPoint }): Outposts => { 307 | if (!minimap) return {}; 308 | const outposts = Object.values(minimap).filter(x => x.unitname === 'npc_dota_watch_tower'); 309 | const outsideNorth = outposts.find(x => x.ypos > 6000); 310 | const jungleNorth = outposts.find(x => x.ypos > 0 && x.ypos < 6000); 311 | const jungleSouth = outposts.find(x => x.ypos > -6000 && x.ypos < 0); 312 | const outsideSouth = outposts.find(x => x.ypos < -6000); 313 | 314 | return { 315 | outsideNorth: !(outsideNorth && outsideNorth.team) ? undefined : outsideNorth.team === 2 ? 'radiant' : 'dire', 316 | jungleNorth: !(jungleNorth && jungleNorth.team) ? undefined : jungleNorth.team === 2 ? 'radiant' : 'dire', 317 | jungleSouth: !(jungleSouth && jungleSouth.team) ? undefined : jungleSouth.team === 2 ? 'radiant' : 'dire', 318 | outsideSouth: !(outsideSouth && outsideSouth.team) ? undefined : outsideSouth.team === 2 ? 'radiant' : 'dire' 319 | }; 320 | }; 321 | 322 | const checkItemTier = (tier: any) => { 323 | for (const value of ['item0', 'item1', 'item2', 'item3', 'item4']) { 324 | if (tier[value] === undefined) { 325 | return false; 326 | } 327 | } 328 | return true; 329 | }; 330 | 331 | const parseTierIntoOldFormat = (players: NeutralItemsInPlayerRaw[], tier: TierIds): NeutralItemsInTier => { 332 | const result = players.map((player, i) => { 333 | const item = { 334 | state: 'equipped' as const, 335 | tier: 0, 336 | name: '', 337 | level: 0, 338 | enchantment_name: '', 339 | enchantment_level: 0, 340 | player_id: i, 341 | }; 342 | 343 | const currentTier = player[`tier${tier}`]; 344 | 345 | if ('choice0' in currentTier.trinket_choices) { 346 | const trinket_choices = currentTier.trinket_choices; 347 | const choices = [trinket_choices.choice0, trinket_choices.choice1, trinket_choices.choice2, trinket_choices.choice3]; 348 | const selectedChoice = choices.find(choice => choice.selected); 349 | if (selectedChoice) { 350 | item.name = selectedChoice.item_name; 351 | item.level = selectedChoice.item_level; 352 | } 353 | } 354 | 355 | if ('choice0' in currentTier.enchantment_choices) { 356 | const enchantment_choices = currentTier.enchantment_choices; 357 | const choices = [enchantment_choices.choice0, enchantment_choices.choice1, enchantment_choices.choice2, enchantment_choices.choice3]; 358 | const selectedChoice = choices.find(choice => choice.selected); 359 | if (selectedChoice) { 360 | item.enchantment_name = selectedChoice.item_name; 361 | item.enchantment_level = selectedChoice.item_level || 1 362 | } 363 | } 364 | 365 | return item; 366 | }); 367 | 368 | return { 369 | item0: result[0]!, 370 | item1: result[1]!, 371 | item2: result[2]!, 372 | item3: result[3]!, 373 | item4: result[4]!, 374 | } 375 | } 376 | 377 | const parseNeutralItemsTeamIntoOldFormat = (team: TeamNeutralItemsRaw): TeamNeutralItems => { 378 | const players = [team.player0, team.player1, team.player2, team.player3, team.player4, 379 | team.player5, team.player6, team.player7, team.player8, team.player9 380 | ].filter(player => player); 381 | const result: TeamNeutralItems = { 382 | items_found: team.items_found, 383 | tier0: parseTierIntoOldFormat(players, 0), 384 | tier1: parseTierIntoOldFormat(players, 1), 385 | tier2: parseTierIntoOldFormat(players, 2), 386 | tier3: parseTierIntoOldFormat(players, 3), 387 | tier4: parseTierIntoOldFormat(players, 4), 388 | } 389 | return result; 390 | } 391 | 392 | const parseNeutralItemTierIntoOldFormat = (tier: { 393 | tier: number; 394 | madstone_required: number; 395 | drop_after_time: number; 396 | escalating_recraft_cost: number; 397 | }): { 398 | tier: number; 399 | max_count: number; 400 | drop_after_time: number; 401 | madstone_required: number; 402 | escalating_recraft_cost: number; 403 | } => { 404 | return { 405 | ...tier, 406 | max_count: 1 407 | } 408 | } 409 | 410 | const parseNeutralItemsIntoOldFormat = (neutralItems: NeutralItemsRaw): NeutralItems => { 411 | const result: NeutralItems = { 412 | tier0: parseNeutralItemTierIntoOldFormat(neutralItems.tier0), 413 | tier1: parseNeutralItemTierIntoOldFormat(neutralItems.tier1), 414 | tier2: parseNeutralItemTierIntoOldFormat(neutralItems.tier2), 415 | tier3: parseNeutralItemTierIntoOldFormat(neutralItems.tier3), 416 | tier4: parseNeutralItemTierIntoOldFormat(neutralItems.tier4), 417 | 418 | team2: parseNeutralItemsTeamIntoOldFormat(neutralItems.team2), 419 | team3: parseNeutralItemsTeamIntoOldFormat(neutralItems.team3) 420 | }; 421 | 422 | 423 | return result; 424 | } 425 | 426 | export const parseNeutralItems = ( 427 | currentTime: number, 428 | neutralItems?: NeutralItemsRaw, 429 | lastNeutralItems?: NeutralItems 430 | ): NeutralItems | undefined => { 431 | if (!neutralItems) return undefined; 432 | 433 | const newNeutralItems = parseNeutralItemsIntoOldFormat(neutralItems); 434 | 435 | if (!lastNeutralItems) return newNeutralItems; 436 | 437 | const result: NeutralItems = { ...newNeutralItems }; 438 | 439 | const teams = [ 440 | [result.team2, lastNeutralItems.team2], 441 | [result.team3, lastNeutralItems.team3] 442 | ]; 443 | for (const [nowTeam, lastTeam] of teams) { 444 | if (!nowTeam || !lastTeam) continue; 445 | for (const [tierNow, tierThen] of [ 446 | [nowTeam.tier0, lastTeam.tier0], 447 | [nowTeam.tier1, lastTeam.tier1], 448 | [nowTeam.tier2, lastTeam.tier2], 449 | [nowTeam.tier3, lastTeam.tier3], 450 | [nowTeam.tier4, lastTeam.tier4] 451 | ]) { 452 | if (!tierNow || !tierThen) continue; 453 | if (tierThen.completion_time) { 454 | tierNow.completion_time = tierThen.completion_time; 455 | } else if (!checkItemTier(tierThen) && checkItemTier(tierNow)) { 456 | tierNow.completion_time = currentTime; 457 | } 458 | } 459 | } 460 | 461 | return newNeutralItems; 462 | }; 463 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Base Options: */ 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "verbatimModuleSyntax": true, 8 | "allowJs": true, 9 | "resolveJsonModule": true, 10 | "moduleDetection": "force", 11 | /* Strictness */ 12 | "strict": true, 13 | "noUncheckedIndexedAccess": true, 14 | /* If NOT transpiling with TypeScript: */ 15 | "moduleResolution": "Bundler", 16 | "module": "ESNext", 17 | "noEmit": true, 18 | /* If your code runs in the DOM: */ 19 | "lib": ["es2022", "dom", "dom.iterable"], 20 | } 21 | } --------------------------------------------------------------------------------