diff --git a/package-lock.json b/package-lock.json index 8f6973c509..fc92e5bd81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "ace-builds": "^1.4.7", "core-js": "^3.6.4", "dota2-emoticons": "^1.0.3", - "dotaconstants": "^7.16.0", + "dotaconstants": "^7.17.0", "fuzzy": "^0.1.3", "heatmap.js": "^2.0.5", "history": "^4.10.1", @@ -8920,9 +8920,9 @@ "integrity": "sha512-arwwgkRRsbTnfkHJWCIkSijmd5BcVwNrBCU2I1QXN1bQ557+/Ikmgs9d6HZnA1QvIlkM6M79SdermOvg9Z01Hw==" }, "node_modules/dotaconstants": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/dotaconstants/-/dotaconstants-7.16.0.tgz", - "integrity": "sha512-lO/bkR/Hi77txYD9/iCuZ7Ta9sMKYWd2d5Etd2cGEzEetqxjq6Z/uQZeRZgtxj3h4sgdXXnfHOxHr/sjNYlZug==" + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/dotaconstants/-/dotaconstants-7.17.0.tgz", + "integrity": "sha512-WpdDaWIdJXlylOlzzQN32xIVtzYQN3auiushBJVQ9sw+xHXRf0kqKPqcJD8QGAKL4D757lciztOpPFlUmz4gSg==" }, "node_modules/dotenv": { "version": "8.2.0", @@ -34417,9 +34417,9 @@ "integrity": "sha512-arwwgkRRsbTnfkHJWCIkSijmd5BcVwNrBCU2I1QXN1bQ557+/Ikmgs9d6HZnA1QvIlkM6M79SdermOvg9Z01Hw==" }, "dotaconstants": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/dotaconstants/-/dotaconstants-7.16.0.tgz", - "integrity": "sha512-lO/bkR/Hi77txYD9/iCuZ7Ta9sMKYWd2d5Etd2cGEzEetqxjq6Z/uQZeRZgtxj3h4sgdXXnfHOxHr/sjNYlZug==" + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/dotaconstants/-/dotaconstants-7.17.0.tgz", + "integrity": "sha512-WpdDaWIdJXlylOlzzQN32xIVtzYQN3auiushBJVQ9sw+xHXRf0kqKPqcJD8QGAKL4D757lciztOpPFlUmz4gSg==" }, "dotenv": { "version": "8.2.0", diff --git a/package.json b/package.json index b7d9e1f1a9..2108e6855a 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ace-builds": "^1.4.7", "core-js": "^3.6.4", "dota2-emoticons": "^1.0.3", - "dotaconstants": "^7.16.0", + "dotaconstants": "^7.17.0", "fuzzy": "^0.1.3", "heatmap.js": "^2.0.5", "history": "^4.10.1", diff --git a/src/actions/transformMatch.js b/src/actions/transformMatch.js index d84331c5dd..63b5131427 100644 --- a/src/actions/transformMatch.js +++ b/src/actions/transformMatch.js @@ -17,7 +17,7 @@ function generateExpandedUnitNames(strings) { Object.keys(strings) .filter(str => str.indexOf('npc_dota_') === 0) .forEach((key) => { - // Currently, no unit goes up higher than 4 + // Currently, no unit goes up higher than 4 for (let i = 1; i < 5; i += 1) { expanded[key.replace('#', i)] = strings[key]; } @@ -145,6 +145,16 @@ function generateVisionLog(match) { function transformMatch(m) { const { abilityIds, strings } = store.getState().app; + + // lane winning + const lineResults = m.players.reduce((res, pl) => { + res[pl.isRadiant] = res[pl.isRadiant] || []; + res[pl.isRadiant][pl.lane] = res[pl.isRadiant][pl.lane] || 0; + + res[pl.isRadiant][pl.lane] += pl.gold_t[10] + return res; + }, {}); + const newPlayers = m.players.map((player) => { const newPlayer = { ...player, @@ -153,6 +163,7 @@ function transformMatch(m) { kill_streaks_max: getMaxKeyOfObject(player.kill_streaks), lh_ten: (player.lh_t || [])[10], dn_ten: (player.dn_t || [])[10], + line_win: lineResults[player.isRadiant][player.lane] > lineResults[!player.isRadiant][player.lane], analysis: analyzeMatch(m, player), }; diff --git a/src/components/ItemTooltip/index.jsx b/src/components/ItemTooltip/index.jsx index 4e22dbaf95..c6ee3ee4ae 100644 --- a/src/components/ItemTooltip/index.jsx +++ b/src/components/ItemTooltip/index.jsx @@ -189,7 +189,7 @@ const ItemTooltip = ({ item, inflictor }) => (
- {item.dname} + {item.dname}
{item.dname}
{item.tier ? "Neutral item" : <>Gold{item.cost}}
@@ -197,61 +197,61 @@ const ItemTooltip = ({ item, inflictor }) => (
{(item.attrib && item.attrib.length > 0) && - - {(item.attrib).map((attrib) => ( - - {attrib.header} - {`${attrib.value}`} - {attrib.footer || ''} - - ))} - + + {(item.attrib).map((attrib) => ( + + {attrib.header} + {`${attrib.value}`} + {attrib.footer || ''} + + ))} + } {['active', 'toggle', 'use', 'passive'].map((type) => { if (item[type]) { return item[type].map(ability => - ( - -
- {`${itemAbilities[type].text}: ${ability.name}`} -
- {type === 'active' && item.mc && + ( + +
+ {`${itemAbilities[type].text}: ${ability.name}`} +
+ {type === 'active' && item.mc && {item.mc} } - {type === 'active' && item.cd && + {type === 'active' && item.cd && {item.cd} } -
-
-
styleValues(el)}> - {ability.desc}
-
- )); +
+
styleValues(el)}> + {ability.desc} +
+ + )); } return null; })} {item.hint && item.hint?.map((hint) => {hint})} {item.lore && {item.lore}} {item.components && - - - {item.components.concat((items[`recipe_${inflictor}`] && [`recipe_${inflictor}`]) || []).filter(Boolean).map(component => - items[component] && - ( -
- -
{items[component].cost}
-
- )) - } -
+ + + {item.components.concat((items[`recipe_${inflictor}`] && [`recipe_${inflictor}`]) || []).filter(Boolean).map(component => + items[component] && + ( +
+ +
{items[component].cost}
+
+ )) + } +
} ); diff --git a/src/components/Match/Laning/index.jsx b/src/components/Match/Laning/index.jsx index e8c6971af9..278996e2f5 100644 --- a/src/components/Match/Laning/index.jsx +++ b/src/components/Match/Laning/index.jsx @@ -28,18 +28,28 @@ class Laning extends React.Component { this.setState({ ...this.state, selectedPlayer: playerSlot }); }; + defaultSort = (r1, r2) => { + if (r1.isRadiant !== r2.isRadiant) { + return 1; + } + + return r1.lane - r2.lane; + } + render() { const { match, strings, sponsorURL, sponsorIcon, } = this.props; const { laningColumns } = mcs(strings); + const tableData = [...match.players].sort(this.defaultSort); + return ( diff --git a/src/components/Match/StyledMatch.jsx b/src/components/Match/StyledMatch.jsx index 2c1fb86c96..f51ca98169 100644 --- a/src/components/Match/StyledMatch.jsx +++ b/src/components/Match/StyledMatch.jsx @@ -394,3 +394,11 @@ export const StyledDmgTargetRow = styled.div` } } `; + +export const StyledLineWinnerSpan = styled.span` + & svg { + width: 20px !important; + height: 20px !important; + fill: ${constants.colorGolden}; + } +`; diff --git a/src/components/Match/matchColumns.jsx b/src/components/Match/matchColumns.jsx index 72c8531f40..2fbbdc0b40 100644 --- a/src/components/Match/matchColumns.jsx +++ b/src/components/Match/matchColumns.jsx @@ -23,7 +23,7 @@ import { } from '../../utility'; import { TableHeroImage, inflictorWithValue } from '../Visualizations'; import { CompetitiveRank } from '../Visualizations/Table/HeroImage'; -import { IconBackpack, IconRadiant, IconDire } from '../Icons'; +import { IconBackpack, IconRadiant, IconDire, IconTrophy } from '../Icons'; import constants from '../constants'; import { StyledAbilityUpgrades, @@ -35,6 +35,7 @@ import { StyledUnusedItem, StyledAghanimsBuffs, StyledLevel, + StyledLineWinnerSpan, } from './StyledMatch'; import TargetsBreakdown from './TargetsBreakdown'; import HeroImage from './../Visualizations/HeroImage'; @@ -261,9 +262,8 @@ export default (strings) => { { .concat( match.players.map((player) => player.item_neutral).reduce(sum, 0) > 0 ? { - field: 'item_neutral', - width: 20, - paddingRight: 23, - paddingLeft: 5, - displayFn: (row) => ( -
- {row.item_neutral - ? inflictorWithValue( - itemIds[row.item_neutral], - null, - 'neutral' - ) - : null} -
- ), - } + field: 'item_neutral', + width: 20, + paddingRight: 23, + paddingLeft: 5, + displayFn: (row) => ( +
+ {row.item_neutral + ? inflictorWithValue( + itemIds[row.item_neutral], + null, + 'neutral' + ) + : null} +
+ ), + } : [] ) .concat({ @@ -491,27 +491,25 @@ export default (strings) => { {shardTooltip} b.permanent_buff === AGHANIMS_SCEPTER ) - ? '1' - : '0' - }.png`} + ? '1' + : '0' + }.png`} alt="Aghanim's Scepter" data-tip={scepterTooltip} data-for="scepter" /> b.permanent_buff === AGHANIMS_SHARD ) - ? '1' - : '0' - }.png`} + ? '1' + : '0' + }.png`} alt="Aghanim's Shard" data-tip={shardTooltip} data-for="shard" @@ -526,27 +524,27 @@ export default (strings) => { ) .reduce(sum, 0) > 0 ? { - displayName: strings.th_permanent_buffs, - tooltip: strings.tooltip_permanent_buffs, - field: 'permanent_buffs', - width: 60, - displayFn: (row) => - row.permanent_buffs && row.permanent_buffs.length > 0 - ? row.permanent_buffs - .filter( - (b) => - b.permanent_buff !== AGHANIMS_SCEPTER && - b.permanent_buff !== AGHANIMS_SHARD - ) - .map((buff) => - inflictorWithValue( - buffs[buff.permanent_buff], - buff.stack_count, - 'buff' - ) - ) - : '-', - } + displayName: strings.th_permanent_buffs, + tooltip: strings.tooltip_permanent_buffs, + field: 'permanent_buffs', + width: 60, + displayFn: (row) => + row.permanent_buffs && row.permanent_buffs.length > 0 + ? row.permanent_buffs + .filter( + (b) => + b.permanent_buff !== AGHANIMS_SCEPTER && + b.permanent_buff !== AGHANIMS_SHARD + ) + .map((buff) => + inflictorWithValue( + buffs[buff.permanent_buff], + buff.stack_count, + 'buff' + ) + ) + : '-', + } : [] ); @@ -837,38 +835,38 @@ export default (strings) => {
{field ? field - .filter( - (purchase) => - purchase.time >= curTime - bucket && - purchase.time < curTime - ) - .sort((p1, p2) => { - const item1 = items[p1.key]; - const item2 = items[p2.key]; - if (item1 && item2 && p1.time === p2.time) { - // We're only concerned with sorting by value - // if items are bought at the same time, time is presorted - return item1.cost - item2.cost; - } - return 0; - }) - .map((purchase) => { - if ( - items[purchase.key] && - (showConsumables || - items[purchase.key].qual !== 'consumable') - ) { - return inflictorWithValue( - purchase.key, - formatSeconds(purchase.time), - null, - null, - null, - purchase.charges - ); - } - return null; - }) + .filter( + (purchase) => + purchase.time >= curTime - bucket && + purchase.time < curTime + ) + .sort((p1, p2) => { + const item1 = items[p1.key]; + const item2 = items[p2.key]; + if (item1 && item2 && p1.time === p2.time) { + // We're only concerned with sorting by value + // if items are bought at the same time, time is presorted + return item1.cost - item2.cost; + } + return 0; + }) + .map((purchase) => { + if ( + items[purchase.key] && + (showConsumables || + items[purchase.key].qual !== 'consumable') + ) { + return inflictorWithValue( + purchase.key, + formatSeconds(purchase.time), + null, + null, + null, + purchase.charges + ); + } + return null; + }) : ''}
), @@ -888,8 +886,7 @@ export default (strings) => { field: i, sortFn: (row) => row.lh_t && row.lh_t[minutes], displayFn: (row) => - `${row.lh_t[minutes]} (+${ - row.lh_t[minutes] - row.lh_t[minutes - (bucket / 60)] + `${row.lh_t[minutes]} (+${row.lh_t[minutes] - row.lh_t[minutes - (bucket / 60)] })`, relativeBars: true, sumFn: (acc, row) => @@ -1062,6 +1059,15 @@ export default (strings) => { ), }, + { + displayName: strings.th_win_lane, + tooltip: strings.tooltip_win_lane, + field: 'line_win', + sortFn: true, + displayFn: (row, col, field) => ( + field && + ), + }, { displayName: strings.cs_over_time, tooltip: strings.tooltip_cs_over_time, @@ -1285,11 +1291,10 @@ export default (strings) => { src={`${process.env.REACT_APP_IMAGE_CDN}/apps/570/${cosmetic.image_path}`} alt={cosmetic.name} style={{ - borderBottom: `2px solid ${ - cosmetic.item_rarity - ? cosmeticsRarity[cosmetic.item_rarity] - : constants.colorMuted - }`, + borderBottom: `2px solid ${cosmetic.item_rarity + ? cosmeticsRarity[cosmetic.item_rarity] + : constants.colorMuted + }`, }} /> @@ -1397,13 +1402,13 @@ export default (strings) => {
{field ? Object.keys(field) - .sort((a, b) => field[b] - field[a]) - .map((inflictor) => - inflictorWithValue( - inflictor, - abbreviateNumber(field[inflictor]) - ) + .sort((a, b) => field[b] - field[a]) + .map((inflictor) => + inflictorWithValue( + inflictor, + abbreviateNumber(field[inflictor]) ) + ) : ''}
), @@ -1441,13 +1446,13 @@ export default (strings) => {
{field ? Object.keys(field) - .sort((a, b) => field[b] - field[a]) - .map((inflictor) => - inflictorWithValue( - inflictor, - abbreviateNumber(field[inflictor]) - ) + .sort((a, b) => field[b] - field[a]) + .map((inflictor) => + inflictorWithValue( + inflictor, + abbreviateNumber(field[inflictor]) ) + ) : ''}
), @@ -1460,13 +1465,13 @@ export default (strings) => {
{field ? Object.keys(field) - .sort((a, b) => field[b] - field[a]) - .map((inflictor) => - inflictorWithValue( - inflictor, - abbreviateNumber(field[inflictor]) - ) + .sort((a, b) => field[b] - field[a]) + .map((inflictor) => + inflictorWithValue( + inflictor, + abbreviateNumber(field[inflictor]) ) + ) : ''}
), diff --git a/src/lang/en-US.json b/src/lang/en-US.json index f0b97d65f2..a536b365d3 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -684,7 +684,7 @@ "story_chat_said": "said", "subscriptions_meta_description": "Get automatic match parsing while supporting OpenDota. Just $5/month.", "subscriptions_h1": "Subscribe", - "subscriptions_h2":"Support open source development and the Dota 2 data community", + "subscriptions_h2": "Support open source development and the Dota 2 data community", "subscriptions_body1": "Become a subscriber for just $5 a month and all your matches will be automatically parsed. For users who are unable to pay, you can continue to manually request matches for parsing.", "subscriptions_request": "Request a parse", "subscriptions_h3": "OpenDota Subscription - $5/month", @@ -693,7 +693,7 @@ "subscriptions_li3": "Fund continual development and maintenance of OpenDota", "subscriptions_li4": "Keep OpenDota available for the Dota 2 community", "subscriptions_li5": "Cancel anytime", - "subscriptions_h4":"Thank you for supporting OpenDota!", + "subscriptions_h4": "Thank you for supporting OpenDota!", "subscriptions_button_manage": "Manage Subscription", "subscriptions_button_subscribe": "Subscribe", "subscriptions_button_login": "Login to Subscribe", @@ -775,6 +775,7 @@ "th_biggest_hit": "Biggest Hit", "th_lane": "Lane", "th_map": "Map", + "th_win_lane": "Win", "th_lane_efficiency": "EFF@10", "th_lhten": "LH@10", "th_dnten": "DN@10", @@ -914,7 +915,6 @@ "time_abbr_y": "{0}y", "time_yy": "{0} years", "time_abbr_yy": "{0}y", - "timeline_firstblood": "drew first blood", "timeline_firstblood_key": "drew first blood by killing", "timeline_aegis_picked_up": "picked up", @@ -996,6 +996,7 @@ "tooltip_permanent_buffs": "Permanent buffs such as Flesh Heap stacks or Tomes of Knowledge used", "tooltip_lane": "Lane based on early game position", "tooltip_map": "Heatmap of the player's early game position", + "tooltip_win_lane": "Lane result", "tooltip_lane_efficiency": "Percentage of lane gold (creeps+passive+starting) obtained at 10 minutes", "tooltip_lane_efficiency_pct": "Percentage of lane gold (creeps+passive+starting) obtained at 10 minutes", "tooltip_pings": "Number of times the player pinged the map", @@ -1201,4 +1202,4 @@ "team_courier": "{team}'s courier", "slain_roshan": "Have slain Roshan", "drew_first_blood": "Drew First Blood" -} +} \ No newline at end of file