mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2026-09-25 22:25:58 +00:00
rework pseudo
This commit is contained in:
+11
-9
@@ -6,11 +6,13 @@ import type { ParsedStat } from './stat-translations'
|
||||
export interface StatCalculated {
|
||||
stat: Stat
|
||||
type: ModifierType
|
||||
sources: Array<{
|
||||
modifier: ParsedModifier
|
||||
stat: ParsedStat
|
||||
contributes?: StatRoll
|
||||
}>
|
||||
sources: StatSource[]
|
||||
}
|
||||
|
||||
export interface StatSource {
|
||||
modifier: ParsedModifier
|
||||
stat: ParsedStat
|
||||
contributes?: StatRoll
|
||||
}
|
||||
|
||||
export interface StatRoll {
|
||||
@@ -61,11 +63,11 @@ export function sumStatsByModType (mods: readonly ParsedModifier[]): StatCalcula
|
||||
}
|
||||
|
||||
export function statSourcesTotal (
|
||||
calc: StatCalculated
|
||||
sources: StatSource[]
|
||||
): StatRoll | undefined {
|
||||
return (calc.sources.length === 1)
|
||||
? (calc.sources[0].contributes)
|
||||
: (calc.sources.reduce((sum, { contributes }) => {
|
||||
return (sources.length === 1)
|
||||
? (sources[0].contributes)
|
||||
: (sources.reduce((sum, { contributes }) => {
|
||||
sum.value += contributes!.value
|
||||
sum.min += contributes!.min
|
||||
sum.max += contributes!.max
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface PreparedStat {
|
||||
|
||||
export function prepareMapStats (item: ParsedItem): PreparedStat[] {
|
||||
return item.statsByType.map(calc => {
|
||||
const roll = statSourcesTotal(calc)
|
||||
const roll = statSourcesTotal(calc.sources)
|
||||
const translation = translateStatWithRoll(calc, roll)
|
||||
|
||||
const prepared = {
|
||||
|
||||
@@ -208,12 +208,14 @@ export default defineComponent({
|
||||
showSourceInfo: computed(() =>
|
||||
props.filter.sources.length &&
|
||||
props.filter.option == null && (
|
||||
props.filter.tag === FilterTag.Pseudo ||
|
||||
(
|
||||
props.filter.sources.length >= 2 ||
|
||||
props.filter.sources[0].modifier.info.name != null ||
|
||||
props.filter.sources[0].modifier.info.tier != null ||
|
||||
props.filter.sources[0].modifier.info.rank != null
|
||||
)
|
||||
),
|
||||
)),
|
||||
inputFocus,
|
||||
toggleFilter
|
||||
}
|
||||
@@ -376,7 +378,6 @@ export default defineComponent({
|
||||
"Armour: #": "Броня: #",
|
||||
"Evasion Rating: #": "Уклонение: #",
|
||||
"Energy Shield: #": "Энерг. щит: #",
|
||||
"+#% total to one of Elemental Resistances": "Всего +#% к сопротивлению одной из стихий",
|
||||
"Map is not occupied by Elder Guardian": "Карта не захвачена Хранителем Древнего",
|
||||
"Block: #%": "Блок: #%",
|
||||
"variant": "вариант",
|
||||
@@ -392,8 +393,7 @@ export default defineComponent({
|
||||
"Buyer will likely change anointment": "Покупатель, скорее всего, поменяет зачарование",
|
||||
"Select only if price-checking as base item for crafting": "Отмечайте, если проверяете цену в качестве базового предмета для крафта",
|
||||
"1 Empty or Crafted Modifier": "1 свободное или ремесленное свойство",
|
||||
"Select only if item has 6 modifiers (1 of which is crafted) or if it has 5 modifiers": "Выбирайте, только если у предмета 6 свойств (1 из которых ремесленное) или если у него 5 свойств",
|
||||
"Stat has a relatively small value": "Относительно небольшое значение у мода"
|
||||
"Select only if item has 6 modifiers (1 of which is crafted) or if it has 5 modifiers": "Выбирайте, только если у предмета 6 свойств (1 из которых ремесленное) или если у него 5 свойств"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -110,7 +110,7 @@ export function calculatedStatToFilter (
|
||||
}
|
||||
}
|
||||
|
||||
const roll = statSourcesTotal(calc)
|
||||
const roll = statSourcesTotal(calc.sources)
|
||||
const translation = translateStatWithRoll(calc, roll)
|
||||
|
||||
const filter: StatFilter = {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { pseudoStat, sumPseudoStats } from './util'
|
||||
import { FiltersCreationContext } from '../create-stat-filters'
|
||||
import { rollToFilter } from '../util'
|
||||
import { STAT_BY_REF, stat } from '@/assets/data'
|
||||
import { ModifierType } from '@/parser/modifiers'
|
||||
|
||||
const TO_ALL_ATTRS = stat('+# to all Attributes')
|
||||
|
||||
const STR_ATTR = {
|
||||
pseudo: pseudoStat('+# total to Strength'),
|
||||
stats: [
|
||||
stat('+# to Strength'),
|
||||
stat('+# to Strength and Intelligence'),
|
||||
stat('+# to Strength and Dexterity'),
|
||||
TO_ALL_ATTRS
|
||||
]
|
||||
}
|
||||
|
||||
const DEX_ATTR = {
|
||||
pseudo: pseudoStat('+# total to Dexterity'),
|
||||
stats: [
|
||||
stat('+# to Dexterity'),
|
||||
stat('+# to Dexterity and Intelligence'),
|
||||
stat('+# to Strength and Dexterity'),
|
||||
TO_ALL_ATTRS
|
||||
]
|
||||
}
|
||||
|
||||
const INT_ATTR = {
|
||||
pseudo: pseudoStat('+# total to Intelligence'),
|
||||
stats: [
|
||||
stat('+# to Intelligence'),
|
||||
stat('+# to Strength and Intelligence'),
|
||||
stat('+# to Dexterity and Intelligence'),
|
||||
TO_ALL_ATTRS
|
||||
]
|
||||
}
|
||||
|
||||
const ATTRS = [
|
||||
STR_ATTR,
|
||||
DEX_ATTR,
|
||||
INT_ATTR
|
||||
]
|
||||
|
||||
const TO_MAXIMUM_LIFE = stat('+# to maximum Life')
|
||||
const TO_MAXIMUM_MANA = stat('+# to maximum Mana')
|
||||
|
||||
export function filterAttributes (ctx: FiltersCreationContext) {
|
||||
const attrs: Array<{
|
||||
pseudo: ReturnType<typeof pseudoStat>
|
||||
total: number
|
||||
hasFlat: boolean
|
||||
countToAll: number
|
||||
}> = []
|
||||
|
||||
for (const attr of ATTRS) {
|
||||
const total = sumPseudoStats(ctx.statsByType, attr.stats)
|
||||
if (total !== undefined) {
|
||||
const hasNotToAll = ctx.statsByType.some(m => attr.stats.includes(m.stat.ref) && m.stat.ref !== TO_ALL_ATTRS)
|
||||
const countToAll = ctx.statsByType.filter(m => m.stat.ref === TO_ALL_ATTRS).length
|
||||
|
||||
attrs.push({ pseudo: attr.pseudo, total, hasFlat: hasNotToAll, countToAll })
|
||||
}
|
||||
}
|
||||
|
||||
const isOnlyToTotal = (attrs.length && attrs.every(a => !a.hasFlat))
|
||||
if (isOnlyToTotal) {
|
||||
const totalToAllAttrs = sumPseudoStats(ctx.statsByType, [TO_ALL_ATTRS])!
|
||||
|
||||
ctx.filters.push({
|
||||
...pseudoStat('+# total to all Attributes'),
|
||||
disabled: true,
|
||||
sources: [],
|
||||
roll: rollToFilter(totalToAllAttrs, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
} else {
|
||||
for (const attr of attrs) {
|
||||
ctx.filters.push({
|
||||
...attr.pseudo,
|
||||
disabled: true,
|
||||
sources: [],
|
||||
hidden: (attr.hasFlat || attr.countToAll >= 2) ? undefined : 'Stat has a relatively small value',
|
||||
roll: rollToFilter(attr.total, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const attr of attrs) {
|
||||
if (attr.pseudo.text === STR_ATTR.pseudo.text) {
|
||||
ctx.statsByType.push({
|
||||
stat: STAT_BY_REF.get(TO_MAXIMUM_LIFE)!,
|
||||
type: ModifierType.Explicit,
|
||||
sources: [
|
||||
// TODO
|
||||
// {
|
||||
// modifier: undefined,
|
||||
// stat: undefined,
|
||||
// contributes: attr.total * (5 / 10)
|
||||
// }
|
||||
]
|
||||
})
|
||||
}
|
||||
if (attr.pseudo.text === INT_ATTR.pseudo.text) {
|
||||
ctx.statsByType.push({
|
||||
stat: STAT_BY_REF.get(TO_MAXIMUM_MANA)!,
|
||||
type: ModifierType.Explicit,
|
||||
sources: [
|
||||
// TODO
|
||||
// {
|
||||
// modifier: undefined,
|
||||
// stat: undefined,
|
||||
// contributes: attr.total * (5 / 10)
|
||||
// }
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const statsToRemove = new Set(ATTRS.flatMap(a => a.stats))
|
||||
ctx.statsByType = ctx.statsByType.filter(m => !statsToRemove.has(m.stat.ref))
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export function propAt20Quality (
|
||||
let flat = 0
|
||||
|
||||
for (const calc of item.statsByType) {
|
||||
const roll = statSourcesTotal(calc)!
|
||||
const roll = statSourcesTotal(calc.sources)!
|
||||
if (calc.stat.ref === statRefs.flat) {
|
||||
flat = roll.value
|
||||
} else if (statRefs.incr.includes(calc.stat.ref)) {
|
||||
|
||||
@@ -1,175 +1,410 @@
|
||||
import { FiltersCreationContext } from '../create-stat-filters'
|
||||
import { rollToFilter } from '../util'
|
||||
import { pseudoStat, sumPseudoStats } from './util'
|
||||
import { filterResists } from './resistances'
|
||||
import { filterAttributes } from './attributes'
|
||||
import { stat, STAT_BY_REF } from '@/assets/data'
|
||||
import { ModifierType, statSourcesTotal } from '@/parser/modifiers'
|
||||
import { rollToFilter } from '../util'
|
||||
import { filterPseudoSources, pseudoStat } from './util'
|
||||
import type { FiltersCreationContext } from '../create-stat-filters'
|
||||
import type { StatFilter } from '../interfaces'
|
||||
|
||||
export function filterPseudo (ctx: FiltersCreationContext) {
|
||||
filterResists(ctx)
|
||||
filterAttributes(ctx)
|
||||
filterRemainingPseudo(ctx)
|
||||
const RESISTANCES_INFO = [
|
||||
{ ref: stat('+#% to all Elemental Resistances'), elements: ['fire', 'cold', 'lightning'] },
|
||||
{ ref: stat('+#% to Fire Resistance'), elements: ['fire'] },
|
||||
{ ref: stat('+#% to Cold Resistance'), elements: ['cold'] },
|
||||
{ ref: stat('+#% to Lightning Resistance'), elements: ['lightning'] },
|
||||
{ ref: stat('+#% to Fire and Lightning Resistances'), elements: ['fire', 'lightning'] },
|
||||
{ ref: stat('+#% to Fire and Cold Resistances'), elements: ['fire', 'cold'] },
|
||||
{ ref: stat('+#% to Cold and Lightning Resistances'), elements: ['cold', 'lightning'] },
|
||||
{ ref: stat('+#% to Chaos Resistance'), elements: [], chaos: true },
|
||||
{ ref: stat('+#% to Fire and Chaos Resistances'), elements: ['fire'], chaos: true },
|
||||
{ ref: stat('+#% to Cold and Chaos Resistances'), elements: ['cold'], chaos: true },
|
||||
{ ref: stat('+#% to Lightning and Chaos Resistances'), elements: ['lightning'], chaos: true }
|
||||
]
|
||||
|
||||
const ATTRIBUTES_INFO = [
|
||||
{ ref: stat('+# to all Attributes'), attributes: ['str', 'dex', 'int'] },
|
||||
{ ref: stat('+# to Strength'), attributes: ['str'] },
|
||||
{ ref: stat('+# to Dexterity'), attributes: ['dex'] },
|
||||
{ ref: stat('+# to Intelligence'), attributes: ['int'] },
|
||||
{ ref: stat('+# to Strength and Intelligence'), attributes: ['str', 'int'] },
|
||||
{ ref: stat('+# to Strength and Dexterity'), attributes: ['str', 'dex'] },
|
||||
{ ref: stat('+# to Dexterity and Intelligence'), attributes: ['dex', 'int'] }
|
||||
]
|
||||
|
||||
interface PseudoRule {
|
||||
group?: string
|
||||
pseudo: string
|
||||
disabled?: boolean
|
||||
replaces?: string
|
||||
stats: Array<{
|
||||
ref: string
|
||||
multiplier?: number
|
||||
required?: boolean
|
||||
}>
|
||||
mutate?: (filter: StatFilter) => void
|
||||
}
|
||||
|
||||
function filterRemainingPseudo (ctx: FiltersCreationContext) {
|
||||
for (const pseudoMod of REST_PSEUDO) {
|
||||
const total = sumPseudoStats(ctx.statsByType, pseudoMod.stats)
|
||||
|
||||
if (total !== undefined) {
|
||||
ctx.filters.push({
|
||||
...pseudoMod.pseudo,
|
||||
disabled: true,
|
||||
sources: [],
|
||||
roll: rollToFilter(total, {
|
||||
neverNegated: true,
|
||||
dp: pseudoMod.stats.some((ref) => STAT_BY_REF.get(ref)!.dp),
|
||||
percent: ctx.searchInRange
|
||||
})
|
||||
})
|
||||
const PSEUDO_RULES: PseudoRule[] = [
|
||||
{
|
||||
pseudo: stat('+#% total Elemental Resistance'),
|
||||
disabled: false,
|
||||
stats:
|
||||
RESISTANCES_INFO.filter(info => info.elements.length)
|
||||
.map(info => ({ ref: info.ref, multiplier: info.elements.length }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+#% total to Fire Resistance'),
|
||||
group: 'to_x_ele_res',
|
||||
stats:
|
||||
RESISTANCES_INFO.filter(info => info.elements.includes('fire'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+#% total to Cold Resistance'),
|
||||
group: 'to_x_ele_res',
|
||||
stats:
|
||||
RESISTANCES_INFO.filter(info => info.elements.includes('cold'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+#% total to Lightning Resistance'),
|
||||
group: 'to_x_ele_res',
|
||||
stats:
|
||||
RESISTANCES_INFO.filter(info => info.elements.includes('lightning'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+#% total to Chaos Resistance'),
|
||||
stats:
|
||||
RESISTANCES_INFO.filter(info => info.chaos === true)
|
||||
.map(info => ({ ref: info.ref })),
|
||||
mutate (filter) {
|
||||
if (filter.sources.length === 1 &&
|
||||
filter.sources[0].modifier.info.type === ModifierType.Crafted
|
||||
) {
|
||||
filter.hidden = 'Crafted Chaos Resistance without Explicit mod has no value'
|
||||
} else {
|
||||
filter.disabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statsToRemove = new Set(REST_PSEUDO.flatMap(a => a.stats))
|
||||
ctx.statsByType = ctx.statsByType.filter(m => !statsToRemove.has(m.stat.ref))
|
||||
}
|
||||
|
||||
const REST_PSEUDO = [
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+# total maximum Life'),
|
||||
pseudo: stat('+# total to all Attributes'),
|
||||
group: 'to_all_attrs',
|
||||
stats: [
|
||||
stat('+# to maximum Life')
|
||||
{ ref: stat('+# to all Attributes') }
|
||||
// NOTE: not including other sources from `ATTRIBUTES_INFO`
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+# total maximum Mana'),
|
||||
pseudo: stat('+# total to Strength'),
|
||||
group: 'to_x_attr',
|
||||
stats:
|
||||
ATTRIBUTES_INFO.filter(info => info.attributes.includes('str'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+# total to Dexterity'),
|
||||
group: 'to_x_attr',
|
||||
stats:
|
||||
ATTRIBUTES_INFO.filter(info => info.attributes.includes('dex'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+# total to Intelligence'),
|
||||
group: 'to_x_attr',
|
||||
stats:
|
||||
ATTRIBUTES_INFO.filter(info => info.attributes.includes('int'))
|
||||
.map(info => ({ ref: info.ref }))
|
||||
},
|
||||
{
|
||||
pseudo: stat('+# total maximum Life'),
|
||||
disabled: false,
|
||||
stats: [
|
||||
stat('+# to maximum Mana')
|
||||
{ ref: stat('+# to maximum Life'), required: true },
|
||||
...ATTRIBUTES_INFO.filter(info => info.attributes.includes('str'))
|
||||
.map(info => ({ ref: info.ref, multiplier: 5 / 10 }))
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% total increased maximum Energy Shield'),
|
||||
pseudo: stat('+# total maximum Mana'),
|
||||
stats: [
|
||||
stat('#% increased maximum Energy Shield')
|
||||
{ ref: stat('+# to maximum Mana'), required: true },
|
||||
...ATTRIBUTES_INFO.filter(info => info.attributes.includes('int'))
|
||||
.map(info => ({ ref: info.ref, multiplier: 5 / 10 }))
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+# total maximum Energy Shield'),
|
||||
pseudo: stat('#% total increased maximum Energy Shield'),
|
||||
stats: [
|
||||
stat('+# to maximum Energy Shield') // global
|
||||
{ ref: stat('#% increased maximum Energy Shield') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% total Attack Speed'),
|
||||
pseudo: stat('+# total maximum Energy Shield'),
|
||||
stats: [
|
||||
stat('#% increased Attack Speed') // global
|
||||
// stat('#% increased Attack and Cast Speed')
|
||||
{ ref: stat('+# to maximum Energy Shield') } // global
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% total Cast Speed'),
|
||||
pseudo: stat('+#% total Attack Speed'),
|
||||
stats: [
|
||||
stat('#% increased Cast Speed')
|
||||
// stat('#% increased Attack and Cast Speed')
|
||||
{ ref: stat('#% increased Attack Speed') } // global
|
||||
// { ref: stat('#% increased Attack and Cast Speed') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% total increased Physical Damage'),
|
||||
pseudo: stat('+#% total Cast Speed'),
|
||||
stats: [
|
||||
stat('#% increased Physical Damage')
|
||||
{ ref: stat('#% increased Cast Speed') }
|
||||
// { ref: stat('#% increased Attack and Cast Speed') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% total Critical Strike Chance for Spells'),
|
||||
pseudo: stat('#% increased Movement Speed'),
|
||||
stats: [
|
||||
stat('#% increased Critical Strike Chance for Spells'),
|
||||
stat('#% increased Global Critical Strike Chance')
|
||||
{ ref: stat('#% increased Movement Speed') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Movement Speed'),
|
||||
pseudo: stat('#% total increased Physical Damage'),
|
||||
stats: [
|
||||
stat('#% increased Movement Speed')
|
||||
{ ref: stat('#% increased Global Physical Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% Global Critical Strike Chance'),
|
||||
pseudo: stat('+#% Global Critical Strike Chance'),
|
||||
group: 'global_crit_chance',
|
||||
stats: [
|
||||
stat('#% increased Global Critical Strike Chance')
|
||||
{ ref: stat('#% increased Global Critical Strike Chance') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% Global Critical Strike Multiplier'),
|
||||
pseudo: stat('+#% total Critical Strike Chance for Spells'),
|
||||
replaces: 'global_crit_chance',
|
||||
stats: [
|
||||
stat('+#% to Global Critical Strike Multiplier')
|
||||
{ ref: stat('#% increased Critical Strike Chance for Spells'), required: true },
|
||||
{ ref: stat('#% increased Global Critical Strike Chance') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Lightning Damage'),
|
||||
pseudo: stat('+#% Global Critical Strike Multiplier'),
|
||||
stats: [
|
||||
stat('#% increased Lightning Damage'),
|
||||
stat('#% increased Elemental Damage')
|
||||
{ ref: stat('+#% to Global Critical Strike Multiplier') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Cold Damage'),
|
||||
pseudo: stat('#% increased Elemental Damage'),
|
||||
group: 'incr_ele_dmg',
|
||||
stats: [
|
||||
stat('#% increased Cold Damage'),
|
||||
stat('#% increased Elemental Damage')
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Fire Damage'),
|
||||
pseudo: stat('#% increased Lightning Damage'),
|
||||
replaces: 'incr_ele_dmg',
|
||||
stats: [
|
||||
stat('#% increased Fire Damage'),
|
||||
stat('#% increased Elemental Damage')
|
||||
{ ref: stat('#% increased Lightning Damage'), required: true },
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Elemental Damage with Attack Skills'),
|
||||
pseudo: stat('#% increased Cold Damage'),
|
||||
replaces: 'incr_ele_dmg',
|
||||
stats: [
|
||||
stat('#% increased Elemental Damage with Attack Skills')
|
||||
{ ref: stat('#% increased Cold Damage'), required: true },
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Spell Damage'),
|
||||
pseudo: stat('#% increased Fire Damage'),
|
||||
group: 'incr_fire_dmg',
|
||||
replaces: 'incr_ele_dmg',
|
||||
stats: [
|
||||
stat('#% increased Spell Damage')
|
||||
{ ref: stat('#% increased Fire Damage'), required: true },
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Burning Damage'),
|
||||
pseudo: stat('#% increased Spell Damage'),
|
||||
group: 'incr_spell_dmg',
|
||||
stats: [
|
||||
stat('#% increased Burning Damage')
|
||||
{ ref: stat('#% increased Spell Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('# Life Regenerated per Second'),
|
||||
pseudo: stat('#% increased Lightning Spell Damage'),
|
||||
replaces: 'incr_spell_dmg',
|
||||
stats: [
|
||||
stat('Regenerate # Life per second')
|
||||
{ ref: stat('#% increased Lightning Spell Damage'), required: true },
|
||||
{ ref: stat('#% increased Spell Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% of Life Regenerated per Second'),
|
||||
pseudo: stat('#% increased Cold Spell Damage'),
|
||||
replaces: 'incr_spell_dmg',
|
||||
stats: [
|
||||
stat('Regenerate #% of Life per second')
|
||||
{ ref: stat('#% increased Cold Spell Damage'), required: true },
|
||||
{ ref: stat('#% increased Spell Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% of Physical Attack Damage Leeched as Life'),
|
||||
pseudo: stat('#% increased Fire Spell Damage'),
|
||||
replaces: 'incr_spell_dmg',
|
||||
stats: [
|
||||
stat('#% of Physical Attack Damage Leeched as Life')
|
||||
{ ref: stat('#% increased Fire Spell Damage'), required: true },
|
||||
{ ref: stat('#% increased Spell Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% of Physical Attack Damage Leeched as Mana'),
|
||||
pseudo: stat('#% increased Elemental Damage with Attack Skills'),
|
||||
replaces: 'incr_ele_dmg',
|
||||
stats: [
|
||||
stat('#% of Physical Attack Damage Leeched as Mana')
|
||||
{ ref: stat('#% increased Elemental Damage with Attack Skills'), required: true },
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('#% increased Mana Regeneration Rate'),
|
||||
pseudo: stat('#% increased Burning Damage'),
|
||||
replaces: 'incr_fire_dmg',
|
||||
stats: [
|
||||
stat('#% increased Mana Regeneration Rate')
|
||||
{ ref: stat('#% increased Burning Damage'), required: true },
|
||||
{ ref: stat('#% increased Fire Damage') },
|
||||
{ ref: stat('#% increased Elemental Damage') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: stat('# Life Regenerated per Second'),
|
||||
stats: [
|
||||
{ ref: stat('Regenerate # Life per second') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: stat('#% of Life Regenerated per Second'),
|
||||
stats: [
|
||||
{ ref: stat('Regenerate #% of Life per second') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: stat('#% of Physical Attack Damage Leeched as Life'),
|
||||
stats: [
|
||||
{ ref: stat('#% of Physical Attack Damage Leeched as Life') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: stat('#% of Physical Attack Damage Leeched as Mana'),
|
||||
stats: [
|
||||
{ ref: stat('#% of Physical Attack Damage Leeched as Mana') }
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: stat('#% increased Mana Regeneration Rate'),
|
||||
stats: [
|
||||
{ ref: stat('#% increased Mana Regeneration Rate') }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export function filterPseudo (ctx: FiltersCreationContext) {
|
||||
const filterByGroup = new Map<string, StatFilter[]>()
|
||||
|
||||
rulesLoop:
|
||||
for (const rule of PSEUDO_RULES) {
|
||||
const sources = filterPseudoSources(ctx.statsByType, ({ stat }, source) => {
|
||||
const info = rule.stats.find(info => info.ref === stat.ref)
|
||||
if (!info) return null
|
||||
|
||||
const multi = info.multiplier ?? 1
|
||||
return {
|
||||
...source,
|
||||
contributes: {
|
||||
value: source.contributes!.value * multi,
|
||||
min: source.contributes!.min * multi,
|
||||
max: source.contributes!.max * multi
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!sources.length) continue
|
||||
|
||||
if (rule.stats.some(s => s.required)) {
|
||||
for (const stat of rule.stats) {
|
||||
if (stat.required && !sources.some(source =>
|
||||
source.stat.stat.ref === stat.ref)) {
|
||||
continue rulesLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const total = statSourcesTotal(sources)!
|
||||
|
||||
const filter = {
|
||||
...pseudoStat(rule.pseudo),
|
||||
disabled: rule.disabled ?? true,
|
||||
sources: sources,
|
||||
roll: rollToFilter(total.value, {
|
||||
neverNegated: true,
|
||||
// TODO: is this correct/can be improved
|
||||
dp: rule.stats.some(({ ref }) => STAT_BY_REF.get(ref)!.dp),
|
||||
percent: ctx.searchInRange
|
||||
})
|
||||
}
|
||||
|
||||
if (rule.mutate) {
|
||||
rule.mutate(filter)
|
||||
}
|
||||
|
||||
ctx.filters.push(filter)
|
||||
|
||||
if (rule.replaces && filterByGroup.has(rule.replaces)) {
|
||||
const replacedFilters = filterByGroup.get(rule.replaces)!
|
||||
ctx.filters = ctx.filters.filter(filter => !replacedFilters.includes(filter))
|
||||
}
|
||||
|
||||
if (rule.group) {
|
||||
if (filterByGroup.has(rule.group)) {
|
||||
filterByGroup.get(rule.group)!.push(filter)
|
||||
} else {
|
||||
filterByGroup.set(rule.group, [filter])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.statsByType = ctx.statsByType.filter(m =>
|
||||
!PSEUDO_RULES.some(rule => rule.stats.some(({ ref }) => m.stat.ref === ref)))
|
||||
|
||||
if (filterByGroup.has('to_x_ele_res')) {
|
||||
const resFilters = filterByGroup.get('to_x_ele_res')!
|
||||
|
||||
resFilters.sort((a, b) => b.roll!.value - a.roll!.value)
|
||||
const maxFilter = (resFilters[0]?.roll?.value === resFilters[1]?.roll?.value)
|
||||
? undefined
|
||||
: resFilters[0]
|
||||
|
||||
if (maxFilter) {
|
||||
maxFilter.hidden = 'Filtering by exact Elemental Resistance unreasonably increases the price'
|
||||
}
|
||||
|
||||
ctx.filters = ctx.filters.filter(filter => !resFilters.includes(filter) || filter === maxFilter)
|
||||
}
|
||||
|
||||
if (filterByGroup.has('to_x_attr')) {
|
||||
const attrFilters = filterByGroup.get('to_x_attr')!
|
||||
attrFilters.sort((a, b) => b.roll!.value - a.roll!.value)
|
||||
if (attrFilters.length === 3) {
|
||||
if (attrFilters.every(f => f.roll!.value === attrFilters[0].roll!.value)) {
|
||||
// Here `to_all_attrs` filter may or may not (very rare event) exist.
|
||||
// Anyway we are removing all attribute filters.
|
||||
ctx.filters = ctx.filters.filter(filter => !attrFilters.includes(filter))
|
||||
} else {
|
||||
if (filterByGroup.has('to_all_attrs')) {
|
||||
const toAll = filterByGroup.get('to_all_attrs')!
|
||||
ctx.filters = ctx.filters.filter(filter => !toAll.includes(filter))
|
||||
}
|
||||
|
||||
if (attrFilters[1].roll!.value === attrFilters[2].roll!.value) {
|
||||
ctx.filters = ctx.filters.filter(filter => filter !== attrFilters[1] && filter !== attrFilters[2])
|
||||
} else {
|
||||
ctx.filters = ctx.filters.filter(filter => filter !== attrFilters[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import { pseudoStat, sumPseudoStats } from './util'
|
||||
import { FiltersCreationContext } from '../create-stat-filters'
|
||||
import { rollToFilter } from '../util'
|
||||
import { stat } from '@/assets/data'
|
||||
import { ModifierType } from '@/parser/modifiers'
|
||||
import { FilterTag } from '../interfaces'
|
||||
|
||||
const TO_ALL_RES = stat('+#% to all Elemental Resistances')
|
||||
|
||||
const ELEMENTAL_RES = [
|
||||
{
|
||||
pseudo: pseudoStat('+#% total to Fire Resistance'),
|
||||
stats: [
|
||||
stat('+#% to Fire Resistance'),
|
||||
stat('+#% to Fire and Lightning Resistances'),
|
||||
stat('+#% to Fire and Cold Resistances'),
|
||||
stat('+#% to Fire and Chaos Resistances'),
|
||||
TO_ALL_RES
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% total to Cold Resistance'),
|
||||
stats: [
|
||||
stat('+#% to Cold Resistance'),
|
||||
stat('+#% to Fire and Cold Resistances'),
|
||||
stat('+#% to Cold and Lightning Resistances'),
|
||||
stat('+#% to Cold and Chaos Resistances'),
|
||||
TO_ALL_RES
|
||||
]
|
||||
},
|
||||
{
|
||||
pseudo: pseudoStat('+#% total to Lightning Resistance'),
|
||||
stats: [
|
||||
stat('+#% to Lightning Resistance'),
|
||||
stat('+#% to Fire and Lightning Resistances'),
|
||||
stat('+#% to Cold and Lightning Resistances'),
|
||||
stat('+#% to Lightning and Chaos Resistances'),
|
||||
TO_ALL_RES
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const CHAOS_RES = {
|
||||
pseudo: pseudoStat('+#% total to Chaos Resistance'),
|
||||
stats: [
|
||||
stat('+#% to Chaos Resistance'),
|
||||
stat('+#% to Fire and Chaos Resistances'),
|
||||
stat('+#% to Cold and Chaos Resistances'),
|
||||
stat('+#% to Lightning and Chaos Resistances')
|
||||
]
|
||||
}
|
||||
|
||||
export function filterResists (ctx: FiltersCreationContext) {
|
||||
const resists: Array<{ pseudo: ReturnType<typeof pseudoStat>, total: number, hasFlat: boolean }> = []
|
||||
|
||||
for (const eleRes of ELEMENTAL_RES) {
|
||||
const hasFlat = ctx.statsByType.some(m =>
|
||||
eleRes.stats.includes(m.stat.ref) && m.stat.ref !== TO_ALL_RES)
|
||||
|
||||
const total = sumPseudoStats(ctx.statsByType, eleRes.stats)
|
||||
if (total !== undefined) {
|
||||
resists.push({ pseudo: eleRes.pseudo, total, hasFlat })
|
||||
}
|
||||
}
|
||||
|
||||
const totalRes = resists.reduce((a, b) => a + b.total, 0)
|
||||
|
||||
if (resists.length > 0) {
|
||||
ctx.filters.push({
|
||||
...pseudoStat('+#% total Elemental Resistance'),
|
||||
disabled: (resists.length === 1),
|
||||
sources: [],
|
||||
roll: rollToFilter(totalRes, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
|
||||
const maxRes = Math.max(...resists.map(r => r.total))
|
||||
|
||||
if ((maxRes / totalRes > 0.67) || resists.filter(r => r.hasFlat).length === 1) {
|
||||
ctx.filters.push({
|
||||
text: '+#% total to one of Elemental Resistances',
|
||||
statRef: '+#% total to one of Elemental Resistances',
|
||||
tradeId: ELEMENTAL_RES.flatMap(r => r.pseudo.tradeId),
|
||||
tag: FilterTag.Pseudo,
|
||||
disabled: false,
|
||||
sources: [],
|
||||
roll: rollToFilter(maxRes, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (resists.length === 3) {
|
||||
const totalToAllRes = Math.min(...resists.map(r => r.total))
|
||||
|
||||
if ((totalToAllRes * resists.length) / totalRes > 0.8) {
|
||||
ctx.filters.push({
|
||||
...pseudoStat('+#% total to all Elemental Resistances'),
|
||||
disabled: false,
|
||||
sources: [],
|
||||
roll: rollToFilter(totalToAllRes, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const resist of resists) {
|
||||
ctx.filters.push({
|
||||
...resist.pseudo,
|
||||
disabled: true,
|
||||
hidden: 'Filtering by exact Elemental Resistance unreasonably increases the price',
|
||||
sources: [],
|
||||
roll: rollToFilter(resist.total, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
}
|
||||
|
||||
const chaosTotal = sumPseudoStats(ctx.statsByType, CHAOS_RES.stats)
|
||||
if (chaosTotal != null) {
|
||||
const hasNotCrafted = ctx.statsByType.some(mod =>
|
||||
mod.type !== ModifierType.Crafted &&
|
||||
CHAOS_RES.stats.includes(mod.stat.ref))
|
||||
|
||||
ctx.filters.push({
|
||||
...CHAOS_RES.pseudo,
|
||||
disabled: true, // NOTE: unlike EleRes it is disabled
|
||||
hidden: hasNotCrafted ? undefined : 'Crafted Chaos Resistance without Explicit mod has no value',
|
||||
sources: [],
|
||||
roll: rollToFilter(chaosTotal, { neverNegated: true, percent: ctx.searchInRange })
|
||||
})
|
||||
}
|
||||
|
||||
const statsToRemove = new Set([...ELEMENTAL_RES.flatMap(r => r.stats), ...CHAOS_RES.stats])
|
||||
ctx.statsByType = ctx.statsByType.filter(m => !statsToRemove.has(m.stat.ref))
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { STAT_BY_REF } from '@/assets/data'
|
||||
import { StatCalculated, ModifierType } from '@/parser/modifiers'
|
||||
import { StatCalculated, ModifierType, StatSource } from '@/parser/modifiers'
|
||||
import { FilterTag, InternalTradeId } from '../interfaces'
|
||||
|
||||
export function pseudoStat (ref: string) {
|
||||
@@ -30,3 +30,19 @@ export function sumPseudoStats (stats: StatCalculated[], pseudoRefs: string[]) {
|
||||
: total,
|
||||
undefined)
|
||||
}
|
||||
|
||||
export function filterPseudoSources (
|
||||
stats: StatCalculated[],
|
||||
mapFn: (calc: StatCalculated, source: StatSource) => StatSource | null
|
||||
): StatSource[] {
|
||||
const out: StatSource[] = []
|
||||
for (const calc of stats) {
|
||||
for (const source of calc.sources) {
|
||||
const result = mapFn(calc, source)
|
||||
if (result) {
|
||||
out.push(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user