From e66bdd07aa3d7be13fa34cb1ff0f95fd6601861f Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Mon, 20 Apr 2020 20:55:14 +0300 Subject: [PATCH] feat: unique item q20 props --- src/assets/data/index.ts | 6 + .../filters/create-stat-filters.ts | 7 +- .../filters/pseudo/item-property-unique.ts | 163 ++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/web/price-check/filters/pseudo/item-property-unique.ts diff --git a/src/assets/data/index.ts b/src/assets/data/index.ts index 8f45f496..d96c9bfd 100644 --- a/src/assets/data/index.ts +++ b/src/assets/data/index.ts @@ -64,6 +64,12 @@ export const TRADE_TAG_BY_NAME = new Map(TRADE_TAGS) export interface UniqueItem { name: string basetype: string + props: { + pdps?: { min: number, max: number } + ar?: { min: number, max: number } + ev?: { min: number, max: number } + es?: { min: number, max: number } + } stats: Array<{ text: string implicit?: true diff --git a/src/web/price-check/filters/create-stat-filters.ts b/src/web/price-check/filters/create-stat-filters.ts index 26ad57fe..b220a996 100644 --- a/src/web/price-check/filters/create-stat-filters.ts +++ b/src/web/price-check/filters/create-stat-filters.ts @@ -6,6 +6,7 @@ import { StatFilter } from './interfaces' import { filterPseudo } from './pseudo' import { filterItemProp } from './pseudo/item-property' import { getRollAsSingleNumber } from '@/parser/utils' +import { filterUniqueItemProp } from './pseudo/item-property-unique' export interface FiltersCreationContext { readonly item: ParsedItem @@ -21,7 +22,7 @@ export function initUiModFilters (item: ParsedItem): StatFilter[] { } if (item.rarity === ItemRarity.Unique) { - // TODO + filterUniqueItemProp(ctx) } else { filterItemProp(ctx) filterPseudo(ctx) @@ -131,7 +132,9 @@ function filterAdjustmentForNegate ( if (filter.min == null && filter.max == null && filter.defaultMax != null) { filter.min = -1 * (raw.defaultMax as number) } - filter.roll = -1 * raw.roll! + if (filter.roll != null) { + filter.roll = -1 * raw.roll! + } } else { if (filter.min == null && filter.max == null && filter.defaultMin != null) { filter.min = filter.defaultMin diff --git a/src/web/price-check/filters/pseudo/item-property-unique.ts b/src/web/price-check/filters/pseudo/item-property-unique.ts new file mode 100644 index 00000000..25b4403f --- /dev/null +++ b/src/web/price-check/filters/pseudo/item-property-unique.ts @@ -0,0 +1,163 @@ +import { rollToFilter, percentRollDelta } from '../util' +import { INTERNAL_TRADE_ID, StatFilter } from '../interfaces' +import { FiltersCreationContext, itemModToFilter } from '../create-stat-filters' +import { propAt20Quality, variablePropAt20Quality, QUALITY_STATS } from './calc-q20' +import { stat, Uniques } from '@/assets/data' +import { ARMOUR, WEAPON } from '@/parser/meta' +import { Config } from '@/web/Config' + +export function filterUniqueItemProp (ctx: FiltersCreationContext) { + if (ARMOUR.has(ctx.item.category!)) { + armourProps(ctx) + } + if (WEAPON.has(ctx.item.category!)) { + weaponProps(ctx) + } +} + +const ARMOUR_STATS = new Set([ + QUALITY_STATS.ARMOUR.flat, + QUALITY_STATS.EVASION.flat, + QUALITY_STATS.ENERGY_SHIELD.flat, + ...QUALITY_STATS.ARMOUR.incr, + ...QUALITY_STATS.EVASION.incr, + ...QUALITY_STATS.ENERGY_SHIELD.incr +]) + +function armourProps (ctx: FiltersCreationContext) { + const { item } = ctx + + const uniqueInfo = Uniques.get(`${item.name} ${item.baseType}`) + + if (item.props.armour) { + const totalQ20 = Math.floor(propAt20Quality(item.props.armour, QUALITY_STATS.ARMOUR, item)) + + ctx.filters.push(propToFilter( + totalQ20, + uniqueInfo?.props.ar, + 'armour.armour', + 'armour', + 'Armour: #' + )) + } + + if (item.props.evasion) { + const totalQ20 = Math.floor(propAt20Quality(item.props.evasion, QUALITY_STATS.EVASION, item)) + + ctx.filters.push(propToFilter( + totalQ20, + uniqueInfo?.props.ev, + 'armour.evasion_rating', + 'armour', + 'Evasion Rating: #' + )) + } + + if (item.props.energyShield) { + const totalQ20 = Math.floor(propAt20Quality(item.props.energyShield, QUALITY_STATS.ENERGY_SHIELD, item)) + + ctx.filters.push(propToFilter( + totalQ20, + uniqueInfo?.props.es, + 'armour.energy_shield', + 'armour', + 'Energy Shield: #' + )) + } + + createHiddenFilters(ctx, ARMOUR_STATS) +} + +const WEAPON_STATS = new Set([ + QUALITY_STATS.PHYSICAL_DAMAGE.flat, + ...QUALITY_STATS.PHYSICAL_DAMAGE.incr, + stat('#% increased Attack Speed') +]) + +function weaponProps (ctx: FiltersCreationContext) { + const { item } = ctx + + const uniqueInfo = Uniques.get(`${item.name} ${item.baseType}`) + + if (item.props.physicalDamage) { + const physQ20 = variablePropAt20Quality(item.props.physicalDamage!, QUALITY_STATS.PHYSICAL_DAMAGE, item) + const pdpsQ20 = Math.floor((physQ20[0] + physQ20[1]) / 2 * item.props.attackSpeed!) + + ctx.filters.push(propToFilter( + pdpsQ20, + uniqueInfo?.props.pdps, + 'weapon.physical_dps', + 'weapon', + 'Physical DPS: #' + )) + } + + createHiddenFilters(ctx, WEAPON_STATS) +} + +function createHiddenFilters (ctx: FiltersCreationContext, stats: Set) { + for (const m of ctx.modifiers) { + if (stats.has(m.stat.ref)) { + const filter = itemModToFilter(m, ctx.item) + filter.hidden = 'Contributes to the item property' + ctx.filters.push(filter) + } + } + + ctx.modifiers = ctx.modifiers.filter(m => !stats.has(m.stat.ref)) +} + +const TOLERANCE = 4 + +function isWithinBounds (value: number, bounds: { min: number, max: number }) { + return ((value + TOLERANCE) >= bounds.min) && ((value - TOLERANCE) <= bounds.max) +} + +function propToFilter ( + totalQ20: number, + propInfo: { min: number, max: number } | undefined, + tradeId: INTERNAL_TRADE_ID, + type: 'armour' | 'weapon', + text: string +): StatFilter { + if (propInfo && isWithinBounds(totalQ20, propInfo)) { + if (propInfo.min === propInfo.max) { + return ({ + tradeId: [tradeId], + text, + type, + disabled: true, + hidden: 'Roll is not variable', + defaultMin: totalQ20, + defaultMax: totalQ20, + min: totalQ20, + max: totalQ20, + roll: totalQ20 + }) + } else { + const percent = Config.store.searchStatRange * 2 + return ({ + tradeId: [tradeId], + text, + type, + disabled: true, + boundMin: propInfo.min, + boundMax: propInfo.max, + defaultMin: Math.max(percentRollDelta(totalQ20, (propInfo.max - propInfo.min), -percent, Math.floor), propInfo.min), + defaultMax: Math.min(percentRollDelta(totalQ20, (propInfo.max - propInfo.min), +percent, Math.ceil), propInfo.max), + min: totalQ20, + max: undefined, + roll: totalQ20 + }) + } + } else { + // missing unique (Two-Toned Boots) / stat affecting prop is variant (Atziri's Splendour) / corrupted implicit affecting prop + return ({ + tradeId: [tradeId], + text, + type, + disabled: true, + ...rollToFilter(totalQ20, { neverNegated: true }) + }) + } +}