From 8bdd4e17f3e921aaae50ed92f70f20fe79c70219 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Mon, 22 Jul 2024 11:06:19 -0700 Subject: [PATCH 01/10] Start powering the spreadsheet from organizer columns --- src/app/inventory/d2-stores.test.ts | 3 +- src/app/inventory/spreadsheets.ts | 386 ++++++++++++++++------------ src/app/organizer/Columns.tsx | 172 ++++++++++--- src/app/organizer/ItemTable.tsx | 41 +-- src/app/organizer/table-types.ts | 22 ++ 5 files changed, 381 insertions(+), 243 deletions(-) diff --git a/src/app/inventory/d2-stores.test.ts b/src/app/inventory/d2-stores.test.ts index f661495ebc..bd0b5cb87b 100644 --- a/src/app/inventory/d2-stores.test.ts +++ b/src/app/inventory/d2-stores.test.ts @@ -1,6 +1,6 @@ import { getWeaponArchetypeSocket } from 'app/utils/socket-utils'; import { BucketHashes } from 'data/d2/generated-enums'; -import { getTestStores } from 'testing/test-utils'; +import { getTestStores, setupi18n } from 'testing/test-utils'; import { generateCSVExportData } from './spreadsheets'; import { DimStore } from './store-types'; @@ -99,6 +99,7 @@ describe('process stores', () => { test.each(['Weapons', 'Armor', 'Ghost'] as const)( 'generates a correct %s CSV export', (type) => { + setupi18n(); const getTag = () => undefined; const getNotes = () => undefined; const loadoutsByItem = {}; diff --git a/src/app/inventory/spreadsheets.ts b/src/app/inventory/spreadsheets.ts index a213b26fc8..863a222043 100644 --- a/src/app/inventory/spreadsheets.ts +++ b/src/app/inventory/spreadsheets.ts @@ -1,6 +1,8 @@ import { currentAccountSelector } from 'app/accounts/selectors'; import { gaEvent } from 'app/google'; import { LoadoutsByItem, loadoutsByItemSelector } from 'app/loadout/selectors'; +import { buildStatInfo, getColumns } from 'app/organizer/Columns'; +import { SpreadsheetContext } from 'app/organizer/table-types'; import { D1_StatHashes } from 'app/search/d1-known-values'; import D2Sources from 'app/search/items/search-filters/d2-sources'; import { dimArmorStatHashByName } from 'app/search/search-filter-values'; @@ -10,9 +12,7 @@ import { compareBy } from 'app/utils/comparators'; import { DimError } from 'app/utils/dim-error'; import { download } from 'app/utils/download'; import { - getItemKillTrackerInfo, getItemYear, - getMasterworkStatNames, getSpecialtySocketMetadatas, isD1Item, isKillTrackerSocket, @@ -460,183 +460,231 @@ function downloadWeapons( ) { // We need to always emit enough columns for all perks const maxPerks = getMaxPerks(items); + const statHashes = buildStatInfo(items); + const columns = getColumns( + 'spreadsheet', + 'weapon', + statHashes, + getTag, + getNotes, + () => undefined /* wishList */, + false /* hasWishList */, + [] /* customStats */, + loadouts, + new Set() /* newItems */, + 2 /* destinyVersion */, + ).filter((c) => c.csv !== undefined /* || typeof c.header === 'string' */); + + // TODO: error if csv isn't defined for these columns! + + const context: SpreadsheetContext = { nameMap, maxPerks }; // In PapaParse, the keys of the first objects are used as columns. So if a // key is omitted from the first object, it won't show up. // TODO: Replace PapaParse with a simpler/smaller CSV generator const data = items.map((item) => { - const row: Record = { - Name: item.name, - Hash: item.hash, - Id: `"${item.id}"`, - Tag: getTag(item), - Tier: item.tier, - Type: item.typeName, - Source: source(item), - Category: item.type, - Element: item.element?.displayProperties.name, - [item.destinyVersion === 1 ? 'Light' : 'Power']: item.power, - }; - if (item.destinyVersion === 2) { - row['Masterwork Type'] = getMasterworkStatNames(item.masterworkInfo) || undefined; - row['Masterwork Tier'] = item.masterworkInfo?.tier || undefined; - } - row.Owner = nameMap[item.owner]; - if (item.destinyVersion === 1) { - row['% Leveled'] = (item.percentComplete * 100).toFixed(0); - } - row.Locked = item.locked; - row.Equipped = item.equipped; - row.Year = getItemYear(item); - if (item.destinyVersion === 2) { - row.Season = getSeason(item); - const event = getEvent(item); - row.Event = event ? D2EventInfo[event].name : ''; - } - - const stats = { - aa: 0, - impact: 0, - range: 0, - zoom: 0, - stability: 0, - rof: 0, - reload: 0, - magazine: 0, - equipSpeed: 0, - drawtime: 0, - chargetime: 0, - accuracy: 0, - recoil: 0, - blastRadius: 0, - velocity: 0, - airborne: 0, - shieldduration: 0, - chargerate: 0, - guardresist: 0, - guardefficiency: 0, - guardendurance: 0, - swingspeed: 0, - }; - - if (item.stats) { - for (const stat of item.stats) { - if (stat.value) { - switch (stat.statHash) { - case StatHashes.RecoilDirection: - stats.recoil = stat.value; - break; - case StatHashes.AimAssistance: - stats.aa = stat.value; - break; - case StatHashes.Impact: - stats.impact = stat.value; - break; - case StatHashes.Range: - stats.range = stat.value; - break; - case StatHashes.Zoom: - stats.zoom = stat.value; - break; - case StatHashes.Stability: - stats.stability = stat.value; - break; - case StatHashes.RoundsPerMinute: - stats.rof = stat.value; - break; - case StatHashes.ReloadSpeed: - stats.reload = stat.value; - break; - case StatHashes.Magazine: - case StatHashes.AmmoCapacity: - stats.magazine = stat.value; - break; - case StatHashes.Handling: - stats.equipSpeed = stat.value; - break; - case StatHashes.DrawTime: - stats.drawtime = stat.value; - break; - case StatHashes.ChargeTime: - stats.chargetime = stat.value; - break; - case StatHashes.Accuracy: - stats.accuracy = stat.value; - break; - case StatHashes.BlastRadius: - stats.blastRadius = stat.value; - break; - case StatHashes.Velocity: - stats.velocity = stat.value; - break; - case StatHashes.AirborneEffectiveness: - stats.airborne = stat.value; - break; - case StatHashes.ShieldDuration: - stats.shieldduration = stat.value; - break; - case StatHashes.ChargeRate: - stats.chargerate = stat.value; - break; - case StatHashes.GuardResistance: - stats.guardresist = stat.value; - break; - case StatHashes.GuardEfficiency: - stats.guardefficiency = stat.value; - break; - case StatHashes.GuardEndurance: - stats.guardendurance = stat.value; - break; - case StatHashes.SwingSpeed: - stats.swingspeed = stat.value; - break; + let row: Record = {}; + + for (const column of columns) { + // if (!column.csv && typeof column.header === 'string') { + // const value = column.value(item); + // if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + // row[column.header] = value; + // } + // } else + if (typeof column.csv === 'string') { + const value = column.value(item); + // if (value !== undefined) { + row[column.csv] = value; + // } + } else { + const values = column.csv!(item, context); + if (!values || values.length === 0) { + continue; + } + if (Array.isArray(values[0])) { + for (const [key, value] of values) { + row[key] = value; } + } else { + const [key, value] = values; + row[key] = value; } } } - row.Recoil = stats.recoil; - row.AA = stats.aa; - row.Impact = stats.impact; - row.Range = stats.range; - row.Zoom = stats.zoom; - row['Blast Radius'] = stats.blastRadius; - row.Velocity = stats.velocity; - row.Stability = stats.stability; - row.ROF = stats.rof; - row.Reload = stats.reload; - row.Mag = stats.magazine; - if (item.destinyVersion === 2) { - row.Handling = stats.equipSpeed; - } else { - row.Equip = stats.equipSpeed; - } - row['Charge Time'] = stats.chargetime; - if (item.destinyVersion === 2) { - row['Draw Time'] = stats.drawtime; - row.Accuracy = stats.accuracy; - - // Sword Stats - row['Charge Rate'] = stats.chargerate; - row['Guard Resistance'] = stats.guardresist; - row['Guard Efficiency'] = stats.guardefficiency; - row['Guard Endurance'] = stats.guardendurance; - row['Swing Speed'] = stats.swingspeed; - - row['Shield Duration'] = stats.shieldduration; // Glaive - row['Airborne Effectiveness'] = stats.airborne; - - row.Crafted = item.crafted; - row['Crafted Level'] = item.craftedInfo?.level ?? 0; - - row['Kill Tracker'] = getItemKillTrackerInfo(item)?.count ?? 0; - row.Foundry = item.foundry; - } - - row.Loadouts = formatLoadouts(item, loadouts); - row.Notes = getNotes(item); - - addPerks(row, item, maxPerks); + // console.log(item.name, row); + + row = { + ...row, + // Hash: item.hash, + // Id: `"${item.id}"`, + // Tier: item.tier, + // Type: item.typeName, + // Source: source(item), + // Category: item.type, + // Element: item.element?.displayProperties.name, + // [item.destinyVersion === 1 ? 'Light' : 'Power']: item.power, + }; + // if (item.destinyVersion === 2) { + // row['Masterwork Type'] = getMasterworkStatNames(item.masterworkInfo) || undefined; + // row['Masterwork Tier'] = item.masterworkInfo?.tier || undefined; + // } + // row.Owner = nameMap[item.owner]; + // if (item.destinyVersion === 1) { + // row['% Leveled'] = (item.percentComplete * 100).toFixed(0); + // } + // row.Equipped = item.equipped; + // row.Year = getItemYear(item); + // if (item.destinyVersion === 2) { + // row.Season = getSeason(item); + // const event = getEvent(item); + // row.Event = event ? D2EventInfo[event].name : ''; + // } + + // const stats = { + // aa: 0, + // impact: 0, + // range: 0, + // zoom: 0, + // stability: 0, + // rof: 0, + // reload: 0, + // magazine: 0, + // equipSpeed: 0, + // drawtime: 0, + // chargetime: 0, + // accuracy: 0, + // recoil: 0, + // blastRadius: 0, + // velocity: 0, + // airborne: 0, + // shieldduration: 0, + // chargerate: 0, + // guardresist: 0, + // guardefficiency: 0, + // guardendurance: 0, + // swingspeed: 0, + // }; + + // if (item.stats) { + // for (const stat of item.stats) { + // if (stat.value) { + // switch (stat.statHash) { + // case StatHashes.RecoilDirection: + // stats.recoil = stat.value; + // break; + // case StatHashes.AimAssistance: + // stats.aa = stat.value; + // break; + // case StatHashes.Impact: + // stats.impact = stat.value; + // break; + // case StatHashes.Range: + // stats.range = stat.value; + // break; + // case StatHashes.Zoom: + // stats.zoom = stat.value; + // break; + // case StatHashes.Stability: + // stats.stability = stat.value; + // break; + // case StatHashes.RoundsPerMinute: + // stats.rof = stat.value; + // break; + // case StatHashes.ReloadSpeed: + // stats.reload = stat.value; + // break; + // case StatHashes.Magazine: + // case StatHashes.AmmoCapacity: + // stats.magazine = stat.value; + // break; + // case StatHashes.Handling: + // stats.equipSpeed = stat.value; + // break; + // case StatHashes.DrawTime: + // stats.drawtime = stat.value; + // break; + // case StatHashes.ChargeTime: + // stats.chargetime = stat.value; + // break; + // case StatHashes.Accuracy: + // stats.accuracy = stat.value; + // break; + // case StatHashes.BlastRadius: + // stats.blastRadius = stat.value; + // break; + // case StatHashes.Velocity: + // stats.velocity = stat.value; + // break; + // case StatHashes.AirborneEffectiveness: + // stats.airborne = stat.value; + // break; + // case StatHashes.ShieldDuration: + // stats.shieldduration = stat.value; + // break; + // case StatHashes.ChargeRate: + // stats.chargerate = stat.value; + // break; + // case StatHashes.GuardResistance: + // stats.guardresist = stat.value; + // break; + // case StatHashes.GuardEfficiency: + // stats.guardefficiency = stat.value; + // break; + // case StatHashes.GuardEndurance: + // stats.guardendurance = stat.value; + // break; + // case StatHashes.SwingSpeed: + // stats.swingspeed = stat.value; + // break; + // } + // } + // } + // } + + // row.Recoil = stats.recoil; + // row.AA = stats.aa; + // row.Impact = stats.impact; + // row.Range = stats.range; + // row.Zoom = stats.zoom; + // row['Blast Radius'] = stats.blastRadius; + // row.Velocity = stats.velocity; + // row.Stability = stats.stability; + // row.ROF = stats.rof; + // row.Reload = stats.reload; + // row.Mag = stats.magazine; + // if (item.destinyVersion === 2) { + // row.Handling = stats.equipSpeed; + // } else { + // row.Equip = stats.equipSpeed; + // } + // row['Charge Time'] = stats.chargetime; + // if (item.destinyVersion === 2) { + // row['Draw Time'] = stats.drawtime; + // row.Accuracy = stats.accuracy; + + // // Sword Stats + // row['Charge Rate'] = stats.chargerate; + // row['Guard Resistance'] = stats.guardresist; + // row['Guard Efficiency'] = stats.guardefficiency; + // row['Guard Endurance'] = stats.guardendurance; + // row['Swing Speed'] = stats.swingspeed; + + // row['Shield Duration'] = stats.shieldduration; // Glaive + // row['Airborne Effectiveness'] = stats.airborne; + + // row.Crafted = item.crafted; + // row['Crafted Level'] = item.craftedInfo?.level ?? 0; + + // row['Kill Tracker'] = getItemKillTrackerInfo(item)?.count ?? 0; + // row.Foundry = item.foundry; + // } + + // row.Loadouts = formatLoadouts(item, loadouts); + // row.Notes = getNotes(item); + + // addPerks(row, item, maxPerks); return row; }); diff --git a/src/app/organizer/Columns.tsx b/src/app/organizer/Columns.tsx index cf8edf849c..5ed78273ff 100644 --- a/src/app/organizer/Columns.tsx +++ b/src/app/organizer/Columns.tsx @@ -121,6 +121,7 @@ const perkStringSort: Comparator = (a, b) => { * This function generates the columns. */ export function getColumns( + useCase: 'organizer' | 'spreadsheet', itemsType: 'weapon' | 'armor' | 'ghost', statHashes: { [statHash: number]: StatInfo; @@ -133,7 +134,7 @@ export function getColumns( loadoutsByItem: LoadoutsByItem, newItems: Set, destinyVersion: DestinyVersion, - onPlugClicked: (value: { item: DimItem; socket: DimSocket; plugHash: number }) => void, + onPlugClicked?: (value: { item: DimItem; socket: DimSocket; plugHash: number }) => void, ): ColumnDefinition[] { const customStatHashes = customStatDefs.map((c) => c.statHash); const statsGroup: ColumnGroup = { @@ -177,7 +178,7 @@ export function getColumns( if (stat?.statHash === StatHashes.RecoilDirection) { return recoilValue(stat.value); } - return stat?.value || 0; + return stat?.value; }, cell: (_val, item: DimItem) => { const stat = item.stats?.find((s) => s.statHash === statHash); @@ -199,6 +200,7 @@ export function getColumns( const isGhost = itemsType === 'ghost'; const isArmor = itemsType === 'armor'; const isWeapon = itemsType === 'weapon'; + const isSpreadsheet = useCase === 'spreadsheet'; const baseStatColumns: ColumnWithStat[] = destinyVersion === 2 @@ -206,12 +208,12 @@ export function getColumns( ...column, id: `base${column.statHash}`, columnGroup: baseStatsGroup, - value: (item: DimItem): number => { + value: (item: DimItem): number | undefined => { const stat = item.stats?.find((s) => s.statHash === column.statHash); if (stat?.statHash === StatHashes.RecoilDirection) { return recoilValue(stat.base); } - return stat?.base || 0; + return stat?.base; }, cell: (_val, item: DimItem) => { const stat = item.stats?.find((s) => s.statHash === column.statHash); @@ -270,32 +272,49 @@ export function getColumns( const customStats = createCustomStatColumns(customStatDefs); const columns: ColumnDefinition[] = _.compact([ - c({ - id: 'icon', - header: t('Organizer.Columns.Icon'), - value: (i) => i.icon, - cell: (_val, item) => ( - - {(ref, onClick) => ( -
- - {item.crafted && } -
- )} -
- ), - noSort: true, - noHide: true, - }), + !isSpreadsheet && + c({ + id: 'icon', + header: t('Organizer.Columns.Icon'), + value: (i) => i.icon, + cell: (_val, item) => ( + + {(ref, onClick) => ( +
+ + {item.crafted && } +
+ )} +
+ ), + noSort: true, + noHide: true, + }), c({ id: 'name', header: t('Organizer.Columns.Name'), + csv: 'Name', value: (i) => i.name, filter: (name) => `name:${quoteFilterString(name)}`, }), + isSpreadsheet && + c({ + id: 'hash', + header: 'Hash', + csv: 'Hash', + value: (i) => i.hash, + }), + isSpreadsheet && + c({ + id: 'id', + header: 'Id', + csv: 'Id', + value: (i) => `"${i.id}"`, + }), !isGhost && c({ id: 'power', + csv: destinyVersion === 2 ? 'Power' : 'Light', header: , dropdownLabel: t('Organizer.Columns.Power'), value: (item) => item.power, @@ -306,6 +325,7 @@ export function getColumns( c({ id: 'dmg', header: t('Organizer.Columns.Damage'), + csv: 'Element', value: (item) => item.element?.displayProperties.name, cell: (_val, item) => , filter: (_val, item) => `is:${getItemDamageShortName(item)}`, @@ -315,6 +335,7 @@ export function getColumns( c({ id: 'energy', header: t('Organizer.Columns.Energy'), + csv: 'Energy', value: (item) => item.energy?.energyCapacity, defaultSort: SortDirection.DESC, filter: (value) => `energycapacity:>=${value}`, @@ -322,6 +343,7 @@ export function getColumns( c({ id: 'locked', header: , + csv: 'Locked', dropdownLabel: t('Organizer.Columns.Locked'), value: (i) => i.locked, cell: (value) => (value ? : undefined), @@ -331,19 +353,21 @@ export function getColumns( c({ id: 'tag', header: t('Organizer.Columns.Tag'), - value: (item) => getTag(item) ?? '', + csv: 'Tag', + value: (item) => getTag(item), cell: (value) => value && , sort: compareBy((tag) => (tag && tag in tagConfig ? tagConfig[tag].sortOrder : 1000)), filter: (value) => `tag:${value || 'none'}`, }), - c({ - id: 'new', - header: t('Organizer.Columns.New'), - value: (item) => newItems.has(item.id), - cell: (value) => (value ? : undefined), - defaultSort: SortDirection.DESC, - filter: (value) => `${value ? '' : '-'}is:new`, - }), + !isSpreadsheet && + c({ + id: 'new', + header: t('Organizer.Columns.New'), + value: (item) => newItems.has(item.id), + cell: (value) => (value ? : undefined), + defaultSort: SortDirection.DESC, + filter: (value) => `${value ? '' : '-'}is:new`, + }), destinyVersion === 2 && isWeapon && c({ @@ -354,6 +378,8 @@ export function getColumns( craftedDate ? <>{new Date(craftedDate * 1000).toLocaleString()} : undefined, defaultSort: SortDirection.DESC, filter: (value) => `${value ? '' : '-'}is:crafted`, + // TODO: nicer to put the date in the CSV + csv: (item) => ['Crafted', Boolean(item.craftedInfo)], }), c({ id: 'recency', @@ -384,9 +410,31 @@ export function getColumns( c({ id: 'tier', header: t('Organizer.Columns.Tier'), + csv: 'Tier', value: (i) => i.tier, filter: (value) => `is:${value}`, }), + isSpreadsheet && + c({ + id: 'Type', + header: 'Type', + csv: 'Type', + value: (i) => i.typeName, + }), + isSpreadsheet && + c({ + id: 'Category', + header: 'Category', + csv: 'Category', + value: (i) => i.type, + }), + isSpreadsheet && + c({ + id: 'Equipped', + header: 'Equipped', + csv: 'Equipped', + value: (i) => i.equipped, + }), destinyVersion === 2 && isArmor && c({ @@ -573,6 +621,7 @@ export function getColumns( value: (item) => item.masterworkInfo?.tier, defaultSort: SortDirection.DESC, filter: (value) => `masterwork:>=${value}`, + csv: 'Masterwork Tier', }), destinyVersion === 2 && isWeapon && @@ -580,6 +629,7 @@ export function getColumns( id: 'masterworkStat', header: t('Organizer.Columns.MasterworkStat'), value: (item) => getMasterworkStatNames(item.masterworkInfo), + csv: 'Masterwork Type', }), destinyVersion === 2 && isWeapon && @@ -588,6 +638,7 @@ export function getColumns( header: t('Organizer.Columns.Level'), value: (item) => item.craftedInfo?.level, defaultSort: SortDirection.DESC, + csv: (item) => ['Crafted Level', item.craftedInfo?.level ?? 0], }), destinyVersion === 2 && isWeapon && @@ -602,6 +653,7 @@ export function getColumns( c({ id: 'killTracker', header: t('Organizer.Columns.KillTracker'), + csv: 'Kill Tracker', value: (item) => { const killTrackerInfo = getItemKillTrackerInfo(item); return killTrackerInfo?.count; @@ -616,18 +668,28 @@ export function getColumns( }, defaultSort: SortDirection.DESC, }), + destinyVersion === 2 && + c({ + id: 'foundry', + header: t('Organizer.Columns.Foundry'), + csv: 'Foundry', + value: (item) => item.foundry, + filter: (value) => `foundry:${value}`, + }), destinyVersion === 2 && c({ id: 'source', header: t('Organizer.Columns.Source'), value: source, filter: (value) => `source:${value}`, + csv: 'Source', }), c({ id: 'year', header: t('Organizer.Columns.Year'), value: (item) => getItemYear(item), filter: (value) => `year:${value}`, + csv: 'Year', }), destinyVersion === 2 && c({ @@ -635,6 +697,7 @@ export function getColumns( header: t('Organizer.Columns.Season'), value: (i) => getSeason(i), filter: (value) => `season:${value}`, + csv: 'Season', }), destinyVersion === 2 && c({ @@ -645,12 +708,14 @@ export function getColumns( return event ? D2EventInfo[event].name : undefined; }, filter: (value) => `event:${value}`, + csv: 'Event', }), c({ id: 'location', header: t('Organizer.Columns.Location'), value: (item) => item.owner, cell: (_val, item) => , + csv: (item, context) => ['Owner', context.nameMap[item.owner]], }), c({ id: 'loadouts', @@ -694,19 +759,20 @@ export function getColumns( c({ id: 'notes', header: t('Organizer.Columns.Notes'), - value: (item) => getNotes(item) ?? '', + csv: 'Notes', + value: (item) => getNotes(item), cell: (_val, item) => , gridWidth: 'minmax(200px, 1fr)', - filter: (value) => `notes:${quoteFilterString(value)}`, + filter: (value) => `notes:${quoteFilterString(value ?? '')}`, }), isWeapon && hasWishList && c({ id: 'wishListNote', header: t('Organizer.Columns.WishListNotes'), - value: (item) => wishList(item)?.notes?.trim() ?? '', + value: (item) => wishList(item)?.notes?.trim(), gridWidth: 'minmax(200px, 1fr)', - filter: (value) => `wishlistnotes:${quoteFilterString(value)}`, + filter: (value) => `wishlistnotes:${quoteFilterString(value ?? '')}`, }), ]); @@ -960,3 +1026,41 @@ function getIntrinsicSockets(item: DimItem) { ? [intrinsicSocket, ...extraIntrinsicSockets] : extraIntrinsicSockets; } + +/** + * This builds stat infos for all the stats that are relevant to a particular category of items. + * It will return the same result for the same category, since all items in a category share stats. + */ +export function buildStatInfo(items: DimItem[]): { + [statHash: number]: StatInfo; +} { + const statHashes: { + [statHash: number]: StatInfo; + } = {}; + for (const item of items) { + if (item.stats) { + for (const stat of item.stats) { + if (statHashes[stat.statHash]) { + // TODO: we don't yet use the min and max values + statHashes[stat.statHash].max = Math.max(statHashes[stat.statHash].max, stat.value); + statHashes[stat.statHash].min = Math.min(statHashes[stat.statHash].min, stat.value); + } else { + statHashes[stat.statHash] = { + id: stat.statHash, + displayProperties: stat.displayProperties, + min: stat.value, + max: stat.value, + enabled: true, + lowerBetter: stat.smallerIsBetter, + statMaximumValue: stat.maximumValue, + bar: stat.bar, + getStat(item) { + return item.stats ? item.stats.find((s) => s.statHash === stat.statHash) : undefined; + }, + }; + } + } + } + } + return statHashes; +} diff --git a/src/app/organizer/ItemTable.tsx b/src/app/organizer/ItemTable.tsx index 9a016a478d..d87baed872 100644 --- a/src/app/organizer/ItemTable.tsx +++ b/src/app/organizer/ItemTable.tsx @@ -44,7 +44,7 @@ import _ from 'lodash'; import React, { ReactNode, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Dropzone, { DropzoneOptions } from 'react-dropzone'; import { useSelector } from 'react-redux'; -import { getColumnSelectionId, getColumns } from './Columns'; +import { buildStatInfo, getColumnSelectionId, getColumns } from './Columns'; import EnabledColumnsSelector from './EnabledColumnsSelector'; import ItemActions, { TagCommandInfo } from './ItemActions'; import { itemIncludesCategories } from './filtering-utils'; @@ -189,6 +189,7 @@ export default function ItemTable({ categories }: { categories: ItemCategoryTree const columns: ColumnDefinition[] = useMemo( () => getColumns( + 'organizer', itemType, statHashes, getTag, @@ -606,44 +607,6 @@ function sortRows( return Array.from(unsortedRows).sort(comparator); } -/** - * This builds stat infos for all the stats that are relevant to a particular category of items. - * It will return the same result for the same category, since all items in a category share stats. - */ -function buildStatInfo(items: DimItem[]): { - [statHash: number]: StatInfo; -} { - const statHashes: { - [statHash: number]: StatInfo; - } = {}; - for (const item of items) { - if (item.stats) { - for (const stat of item.stats) { - if (statHashes[stat.statHash]) { - // TODO: we don't yet use the min and max values - statHashes[stat.statHash].max = Math.max(statHashes[stat.statHash].max, stat.value); - statHashes[stat.statHash].min = Math.min(statHashes[stat.statHash].min, stat.value); - } else { - statHashes[stat.statHash] = { - id: stat.statHash, - displayProperties: stat.displayProperties, - min: stat.value, - max: stat.value, - enabled: true, - lowerBetter: stat.smallerIsBetter, - statMaximumValue: stat.maximumValue, - bar: stat.bar, - getStat(item) { - return item.stats ? item.stats.find((s) => s.statHash === stat.statHash) : undefined; - }, - }; - } - } - } - } - return statHashes; -} - function TableRow({ row, filteredColumns, diff --git a/src/app/organizer/table-types.ts b/src/app/organizer/table-types.ts index d9df7e3abe..afc7638d0c 100644 --- a/src/app/organizer/table-types.ts +++ b/src/app/organizer/table-types.ts @@ -52,6 +52,28 @@ export interface ColumnDefinition { * but sometimes a custom stat should be limited to only displaying for a certain class */ limitToClass?: DestinyClass; + + /** + * Output this column as CSV. It can be a single key/value pair, or several, + * or nothing which is effectively "N/A". It can also be a single string, + * which means to use the value function as-is and use the `csv` value as the + * column name. We could reuse the header, but that's localized, while + * historically our CSV column names haven't been. + */ + csv?: + | string + | (( + item: DimItem, + spreadsheetContext: SpreadsheetContext, + ) => + | [name: string, value: string | number | boolean] + | [name: string, value: string | number | boolean][] + | undefined); +} + +export interface SpreadsheetContext { + nameMap: { [key: string]: string }; + maxPerks: number; } export interface Row { From b6efb10571edb440e8eeccb21a21ad82f230d44a Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Tue, 6 Aug 2024 23:40:40 -0700 Subject: [PATCH 02/10] Parity with weapons --- config/i18n.json | 1 + .../__snapshots__/d2-stores.test.ts.snap | 374 ------------------ src/app/inventory/spreadsheets.ts | 194 +-------- src/app/organizer/Columns.tsx | 130 +++++- src/app/organizer/table-types.ts | 33 +- src/app/utils/item-utils.ts | 14 +- src/locale/en.json | 1 + 7 files changed, 163 insertions(+), 584 deletions(-) diff --git a/config/i18n.json b/config/i18n.json index e7fa0b2707..31ada799ef 100644 --- a/config/i18n.json +++ b/config/i18n.json @@ -1050,6 +1050,7 @@ "Name": "Name", "Power": "Power", "Damage": "Damage", + "Foundry": "Foundry", "Intrinsics": "Intrinsic", "OriginTraits": "Origin Trait", "Shaders": "Cosmetics", diff --git a/src/app/inventory/__snapshots__/d2-stores.test.ts.snap b/src/app/inventory/__snapshots__/d2-stores.test.ts.snap index d174eda73a..58022b6998 100644 --- a/src/app/inventory/__snapshots__/d2-stores.test.ts.snap +++ b/src/app/inventory/__snapshots__/d2-stores.test.ts.snap @@ -9025,7 +9025,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -9087,7 +9086,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 77, @@ -9149,7 +9147,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -9211,7 +9208,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 79, @@ -9273,7 +9269,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 20, @@ -9335,7 +9330,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 41, @@ -9397,7 +9391,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -9459,7 +9452,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -9521,7 +9513,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -9583,7 +9574,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -9645,7 +9635,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -9707,7 +9696,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 80, @@ -9769,7 +9757,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 100, @@ -9831,7 +9818,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -9893,7 +9879,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -9955,7 +9940,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 74, @@ -10017,7 +10001,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 60, "Guard Resistance": 70, "Handling": 0, @@ -10079,7 +10062,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 80, "Handling": 0, @@ -10141,7 +10123,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 23, @@ -10203,7 +10184,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 56, @@ -10265,7 +10245,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "fotc", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -10327,7 +10306,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -10389,7 +10367,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -10451,7 +10428,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 27, @@ -10513,7 +10489,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 72, @@ -10575,7 +10550,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -10637,7 +10611,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 23, @@ -10699,7 +10672,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 13, @@ -10761,7 +10733,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 48, @@ -10823,7 +10794,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -10885,7 +10855,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 90, "Guard Resistance": 50, "Handling": 0, @@ -10947,7 +10916,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 68, @@ -11009,7 +10977,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 29, @@ -11071,7 +11038,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 75, @@ -11133,7 +11099,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -11195,7 +11160,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 87, @@ -11257,7 +11221,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 79, @@ -11319,7 +11282,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 65, @@ -11381,7 +11343,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -11443,7 +11404,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -11505,7 +11465,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 73, @@ -11567,7 +11526,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 54, @@ -11629,7 +11587,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 67, @@ -11691,7 +11648,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -11753,7 +11709,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -11815,7 +11770,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -11877,7 +11831,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 95, @@ -11939,7 +11892,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -12001,7 +11953,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -12063,7 +12014,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -12125,7 +12075,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 35, @@ -12187,7 +12136,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 71, @@ -12249,7 +12197,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -12311,7 +12258,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -12373,7 +12319,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -12435,7 +12380,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 62, @@ -12497,7 +12441,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -12559,7 +12502,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 58, @@ -12621,7 +12563,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 67, @@ -12683,7 +12624,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -12745,7 +12685,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -12807,7 +12746,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -12869,7 +12807,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -12931,7 +12868,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -12993,7 +12929,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -13055,7 +12990,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 82, @@ -13117,7 +13051,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 35, @@ -13179,7 +13112,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -13241,7 +13173,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -13303,7 +13234,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -13365,7 +13295,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 50, @@ -13427,7 +13356,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -13489,7 +13417,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -13551,7 +13478,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 41, @@ -13613,7 +13539,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -13675,7 +13600,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 57, @@ -13737,7 +13661,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -13799,7 +13722,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 40, "Guard Resistance": 10, "Handling": 0, @@ -13861,7 +13783,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -13923,7 +13844,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 77, @@ -13985,7 +13905,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 65, @@ -14047,7 +13966,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 21, @@ -14109,7 +14027,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -14171,7 +14088,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 76, @@ -14233,7 +14149,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 50, @@ -14295,7 +14210,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -14357,7 +14271,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -14419,7 +14332,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -14481,7 +14393,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 31, @@ -14543,7 +14454,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 24, @@ -14605,7 +14515,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 71, @@ -14667,7 +14576,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 48, @@ -14729,7 +14637,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -14791,7 +14698,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -14853,7 +14759,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 48, @@ -14915,7 +14820,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -14977,7 +14881,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 75, @@ -15039,7 +14942,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -15101,7 +15003,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -15163,7 +15064,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 73, @@ -15225,7 +15125,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -15287,7 +15186,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -15349,7 +15247,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 88, @@ -15411,7 +15308,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 28, @@ -15473,7 +15369,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 31, @@ -15535,7 +15430,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -15597,7 +15491,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -15659,7 +15552,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -15721,7 +15613,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -15783,7 +15674,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 57, @@ -15845,7 +15735,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -15907,7 +15796,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 80, @@ -15969,7 +15857,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -16031,7 +15918,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -16093,7 +15979,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -16155,7 +16040,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -16217,7 +16101,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -16279,7 +16162,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -16341,7 +16223,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -16403,7 +16284,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 56, @@ -16465,7 +16345,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 72, @@ -16527,7 +16406,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Festival of the Lost", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -16589,7 +16467,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -16651,7 +16528,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 86, @@ -16713,7 +16589,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "fotc", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 41, @@ -16775,7 +16650,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -16837,7 +16711,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -16899,7 +16772,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 21, @@ -16961,7 +16833,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -17023,7 +16894,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 72, @@ -17085,7 +16955,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Festival of the Lost", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -17147,7 +17016,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 42, @@ -17209,7 +17077,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -17271,7 +17138,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 20, @@ -17333,7 +17199,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -17395,7 +17260,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -17457,7 +17321,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "fotc", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -17519,7 +17382,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 42, @@ -17581,7 +17443,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -17643,7 +17504,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 78, @@ -17705,7 +17565,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 70, @@ -17767,7 +17626,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 24, @@ -17829,7 +17687,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 50, @@ -17891,7 +17748,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -17953,7 +17809,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -18015,7 +17870,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -18077,7 +17931,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 72, @@ -18139,7 +17992,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 73, @@ -18201,7 +18053,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -18263,7 +18114,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -18325,7 +18175,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 77, @@ -18387,7 +18236,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 68, @@ -18449,7 +18297,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -18511,7 +18358,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 68, @@ -18573,7 +18419,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -18635,7 +18480,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 32, @@ -18697,7 +18541,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -18759,7 +18602,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "tex-mechanica", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -18821,7 +18663,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 86, @@ -18883,7 +18724,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 23, @@ -18945,7 +18785,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 80, "Handling": 0, @@ -19007,7 +18846,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -19069,7 +18907,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -19131,7 +18968,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 13, @@ -19193,7 +19029,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 24, @@ -19255,7 +19090,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 76, @@ -19317,7 +19151,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 28, @@ -19379,7 +19212,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -19441,7 +19273,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -19503,7 +19334,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -19565,7 +19395,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -19627,7 +19456,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 51, @@ -19689,7 +19517,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 14, @@ -19751,7 +19578,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 76, @@ -19813,7 +19639,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 23, @@ -19875,7 +19700,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 20, @@ -19937,7 +19761,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -19999,7 +19822,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 57, @@ -20061,7 +19883,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 81, @@ -20123,7 +19944,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -20185,7 +20005,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -20247,7 +20066,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 68, @@ -20309,7 +20127,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 72, @@ -20371,7 +20188,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -20433,7 +20249,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 82, @@ -20495,7 +20310,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 39, @@ -20557,7 +20371,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 81, @@ -20619,7 +20432,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 29, @@ -20681,7 +20493,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -20743,7 +20554,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 42, @@ -20805,7 +20615,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 54, @@ -20867,7 +20676,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -20929,7 +20737,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 31, @@ -20991,7 +20798,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 16, @@ -21053,7 +20859,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -21115,7 +20920,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -21177,7 +20981,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -21239,7 +21042,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -21301,7 +21103,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -21363,7 +21164,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 24, @@ -21425,7 +21225,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -21487,7 +21286,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -21549,7 +21347,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -21611,7 +21408,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 90, "Guard Resistance": 0, "Handling": 0, @@ -21673,7 +21469,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -21735,7 +21530,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -21797,7 +21591,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -21859,7 +21652,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -21921,7 +21713,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 82, @@ -21983,7 +21774,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 54, @@ -22045,7 +21835,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 65, @@ -22107,7 +21896,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -22169,7 +21957,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Solstice", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 35, @@ -22231,7 +22018,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "Solstice", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -22293,7 +22079,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 19, @@ -22355,7 +22140,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -22417,7 +22201,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 15, @@ -22479,7 +22262,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 67, @@ -22541,7 +22323,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -22603,7 +22384,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -22665,7 +22445,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -22727,7 +22506,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -22789,7 +22567,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -22851,7 +22628,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 48, @@ -22913,7 +22689,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 77, @@ -22975,7 +22750,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 58, @@ -23037,7 +22811,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 56, @@ -23099,7 +22872,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Solstice", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 35, @@ -23161,7 +22933,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -23223,7 +22994,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 44, @@ -23285,7 +23055,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -23347,7 +23116,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -23409,7 +23177,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -23471,7 +23238,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -23533,7 +23299,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -23595,7 +23360,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 71, @@ -23657,7 +23421,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 18, @@ -23719,7 +23482,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -23781,7 +23543,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -23843,7 +23604,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -23905,7 +23665,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -23967,7 +23726,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 58, @@ -24029,7 +23787,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 56, @@ -24091,7 +23848,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -24153,7 +23909,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -24215,7 +23970,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -24277,7 +24031,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Festival of the Lost", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -24339,7 +24092,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 41, @@ -24401,7 +24153,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -24463,7 +24214,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -24525,7 +24275,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -24587,7 +24336,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 65, @@ -24649,7 +24397,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Festival of the Lost", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -24711,7 +24458,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -24773,7 +24519,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 51, @@ -24835,7 +24580,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 0, @@ -24897,7 +24641,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -24959,7 +24702,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Festival of the Lost", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -25021,7 +24763,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -25083,7 +24824,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -25145,7 +24885,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 26, @@ -25207,7 +24946,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -25269,7 +25007,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -25331,7 +25068,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -25393,7 +25129,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -25455,7 +25190,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 84, @@ -25517,7 +25251,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 64, @@ -25579,7 +25312,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 38, @@ -25641,7 +25373,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 49, @@ -25703,7 +25434,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 29, @@ -25765,7 +25495,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 92, @@ -25827,7 +25556,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 92, @@ -25889,7 +25617,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 35, @@ -25951,7 +25678,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -26013,7 +25739,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 67, @@ -26075,7 +25800,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 68, @@ -26137,7 +25861,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 71, @@ -26199,7 +25922,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -26261,7 +25983,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 83, @@ -26323,7 +26044,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 29, @@ -26385,7 +26105,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 50, @@ -26447,7 +26166,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 32, @@ -26509,7 +26227,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 28, @@ -26571,7 +26288,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 36, @@ -26633,7 +26349,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -26695,7 +26410,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": true, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -26757,7 +26471,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 61, @@ -26819,7 +26532,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 67, @@ -26881,7 +26593,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 8, @@ -26943,7 +26654,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 51, @@ -27005,7 +26715,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 75, @@ -27067,7 +26776,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -27129,7 +26837,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 80, "Handling": 0, @@ -27191,7 +26898,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -27253,7 +26959,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 13, @@ -27315,7 +27020,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -27377,7 +27081,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 80, @@ -27439,7 +27142,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "The Dawning", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 48, @@ -27501,7 +27203,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -27563,7 +27264,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 56, @@ -27625,7 +27325,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 32, @@ -27687,7 +27386,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -27749,7 +27447,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 84, @@ -27811,7 +27508,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 51, @@ -27873,7 +27569,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "Guardian Games", "Foundry": "hakke", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -27935,7 +27630,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 0, @@ -27997,7 +27691,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 33, @@ -28059,7 +27752,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "nadir", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 80, "Handling": 0, @@ -28121,7 +27813,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -28183,7 +27874,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -28245,7 +27935,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -28307,7 +27996,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 61, @@ -28369,7 +28057,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 62, @@ -28431,7 +28118,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 16, @@ -28493,7 +28179,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 30, @@ -28555,7 +28240,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 61, @@ -28617,7 +28301,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 78, @@ -28679,7 +28362,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -28741,7 +28423,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 61, @@ -28803,7 +28484,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 90, "Guard Resistance": 0, "Handling": 0, @@ -28865,7 +28545,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 16, @@ -28927,7 +28606,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 21, @@ -28989,7 +28667,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 42, @@ -29051,7 +28728,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 25, @@ -29113,7 +28789,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 52, @@ -29175,7 +28850,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -29237,7 +28911,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -29299,7 +28972,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -29361,7 +29033,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 44, @@ -29423,7 +29094,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 59, @@ -29485,7 +29155,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 64, @@ -29547,7 +29216,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 50, @@ -29609,7 +29277,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 63, @@ -29671,7 +29338,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 42, @@ -29733,7 +29399,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 37, @@ -29795,7 +29460,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 73, @@ -29857,7 +29521,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 40, "Guard Resistance": 40, "Handling": 0, @@ -29919,7 +29582,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 60, @@ -29981,7 +29643,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 40, @@ -30043,7 +29704,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 58, @@ -30105,7 +29765,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 27, @@ -30167,7 +29826,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 61, @@ -30229,7 +29887,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 51, @@ -30291,7 +29948,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 62, @@ -30353,7 +30009,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 76, @@ -30415,7 +30070,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 44, @@ -30477,7 +30131,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 21, @@ -30539,7 +30192,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 27, @@ -30601,7 +30253,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 17, @@ -30663,7 +30314,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -30725,7 +30375,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 45, @@ -30787,7 +30436,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 32, @@ -30849,7 +30497,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 80, "Handling": 0, @@ -30911,7 +30558,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 57, @@ -30973,7 +30619,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -31035,7 +30680,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 34, @@ -31097,7 +30741,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "veist", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -31159,7 +30802,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 57, @@ -31221,7 +30863,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 47, @@ -31283,7 +30924,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 53, @@ -31345,7 +30985,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 43, @@ -31407,7 +31046,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 55, @@ -31469,7 +31107,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 27, @@ -31531,7 +31168,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 77, @@ -31593,7 +31229,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -31655,7 +31290,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "fotc", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 16, @@ -31717,7 +31351,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 29, @@ -31779,7 +31412,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 19, @@ -31841,7 +31473,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "cassoid", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 23, @@ -31903,7 +31534,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": undefined, - "Guard Efficiency": 0, "Guard Endurance": 90, "Guard Resistance": 0, "Handling": 0, @@ -31965,7 +31595,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "field-forged", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 69, @@ -32027,7 +31656,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "suros", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 66, @@ -32089,7 +31717,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "omolon", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 46, @@ -32151,7 +31778,6 @@ exports[`process stores generates a correct Weapons CSV export 1`] = ` "Equipped": false, "Event": "", "Foundry": "fotc", - "Guard Efficiency": 0, "Guard Endurance": 0, "Guard Resistance": 0, "Handling": 16, diff --git a/src/app/inventory/spreadsheets.ts b/src/app/inventory/spreadsheets.ts index 863a222043..f493dbe05c 100644 --- a/src/app/inventory/spreadsheets.ts +++ b/src/app/inventory/spreadsheets.ts @@ -458,6 +458,10 @@ function downloadWeapons( getNotes: (item: DimItem) => string | undefined, loadouts: LoadoutsByItem, ) { + // TODO: Use the weird stat name mapping? + // TODO: Perks + // TODO: map event to empty string if not found + // We need to always emit enough columns for all perks const maxPerks = getMaxPerks(items); const statHashes = buildStatInfo(items); @@ -473,7 +477,7 @@ function downloadWeapons( loadouts, new Set() /* newItems */, 2 /* destinyVersion */, - ).filter((c) => c.csv !== undefined /* || typeof c.header === 'string' */); + ).filter((c) => c.csv || c.csvVal); // TODO: error if csv isn't defined for these columns! @@ -486,199 +490,27 @@ function downloadWeapons( let row: Record = {}; for (const column of columns) { - // if (!column.csv && typeof column.header === 'string') { - // const value = column.value(item); - // if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - // row[column.header] = value; - // } - // } else - if (typeof column.csv === 'string') { - const value = column.value(item); - // if (value !== undefined) { - row[column.csv] = value; - // } - } else { - const values = column.csv!(item, context); + const value = column.value(item); + if (column.csv) { + row[column.csv] ||= value; + } else if (column.csvVal) { + const values = column.csvVal!(value, item, context); if (!values || values.length === 0) { continue; } if (Array.isArray(values[0])) { - for (const [key, value] of values) { - row[key] = value; + for (const [key, value] of values as [string, string | number | boolean | undefined][]) { + row[key] ||= value; } } else { const [key, value] = values; - row[key] = value; + row[key] ||= value; } } } // console.log(item.name, row); - row = { - ...row, - // Hash: item.hash, - // Id: `"${item.id}"`, - // Tier: item.tier, - // Type: item.typeName, - // Source: source(item), - // Category: item.type, - // Element: item.element?.displayProperties.name, - // [item.destinyVersion === 1 ? 'Light' : 'Power']: item.power, - }; - // if (item.destinyVersion === 2) { - // row['Masterwork Type'] = getMasterworkStatNames(item.masterworkInfo) || undefined; - // row['Masterwork Tier'] = item.masterworkInfo?.tier || undefined; - // } - // row.Owner = nameMap[item.owner]; - // if (item.destinyVersion === 1) { - // row['% Leveled'] = (item.percentComplete * 100).toFixed(0); - // } - // row.Equipped = item.equipped; - // row.Year = getItemYear(item); - // if (item.destinyVersion === 2) { - // row.Season = getSeason(item); - // const event = getEvent(item); - // row.Event = event ? D2EventInfo[event].name : ''; - // } - - // const stats = { - // aa: 0, - // impact: 0, - // range: 0, - // zoom: 0, - // stability: 0, - // rof: 0, - // reload: 0, - // magazine: 0, - // equipSpeed: 0, - // drawtime: 0, - // chargetime: 0, - // accuracy: 0, - // recoil: 0, - // blastRadius: 0, - // velocity: 0, - // airborne: 0, - // shieldduration: 0, - // chargerate: 0, - // guardresist: 0, - // guardefficiency: 0, - // guardendurance: 0, - // swingspeed: 0, - // }; - - // if (item.stats) { - // for (const stat of item.stats) { - // if (stat.value) { - // switch (stat.statHash) { - // case StatHashes.RecoilDirection: - // stats.recoil = stat.value; - // break; - // case StatHashes.AimAssistance: - // stats.aa = stat.value; - // break; - // case StatHashes.Impact: - // stats.impact = stat.value; - // break; - // case StatHashes.Range: - // stats.range = stat.value; - // break; - // case StatHashes.Zoom: - // stats.zoom = stat.value; - // break; - // case StatHashes.Stability: - // stats.stability = stat.value; - // break; - // case StatHashes.RoundsPerMinute: - // stats.rof = stat.value; - // break; - // case StatHashes.ReloadSpeed: - // stats.reload = stat.value; - // break; - // case StatHashes.Magazine: - // case StatHashes.AmmoCapacity: - // stats.magazine = stat.value; - // break; - // case StatHashes.Handling: - // stats.equipSpeed = stat.value; - // break; - // case StatHashes.DrawTime: - // stats.drawtime = stat.value; - // break; - // case StatHashes.ChargeTime: - // stats.chargetime = stat.value; - // break; - // case StatHashes.Accuracy: - // stats.accuracy = stat.value; - // break; - // case StatHashes.BlastRadius: - // stats.blastRadius = stat.value; - // break; - // case StatHashes.Velocity: - // stats.velocity = stat.value; - // break; - // case StatHashes.AirborneEffectiveness: - // stats.airborne = stat.value; - // break; - // case StatHashes.ShieldDuration: - // stats.shieldduration = stat.value; - // break; - // case StatHashes.ChargeRate: - // stats.chargerate = stat.value; - // break; - // case StatHashes.GuardResistance: - // stats.guardresist = stat.value; - // break; - // case StatHashes.GuardEfficiency: - // stats.guardefficiency = stat.value; - // break; - // case StatHashes.GuardEndurance: - // stats.guardendurance = stat.value; - // break; - // case StatHashes.SwingSpeed: - // stats.swingspeed = stat.value; - // break; - // } - // } - // } - // } - - // row.Recoil = stats.recoil; - // row.AA = stats.aa; - // row.Impact = stats.impact; - // row.Range = stats.range; - // row.Zoom = stats.zoom; - // row['Blast Radius'] = stats.blastRadius; - // row.Velocity = stats.velocity; - // row.Stability = stats.stability; - // row.ROF = stats.rof; - // row.Reload = stats.reload; - // row.Mag = stats.magazine; - // if (item.destinyVersion === 2) { - // row.Handling = stats.equipSpeed; - // } else { - // row.Equip = stats.equipSpeed; - // } - // row['Charge Time'] = stats.chargetime; - // if (item.destinyVersion === 2) { - // row['Draw Time'] = stats.drawtime; - // row.Accuracy = stats.accuracy; - - // // Sword Stats - // row['Charge Rate'] = stats.chargerate; - // row['Guard Resistance'] = stats.guardresist; - // row['Guard Efficiency'] = stats.guardefficiency; - // row['Guard Endurance'] = stats.guardendurance; - // row['Swing Speed'] = stats.swingspeed; - - // row['Shield Duration'] = stats.shieldduration; // Glaive - // row['Airborne Effectiveness'] = stats.airborne; - - // row.Crafted = item.crafted; - // row['Crafted Level'] = item.craftedInfo?.level ?? 0; - - // row['Kill Tracker'] = getItemKillTrackerInfo(item)?.count ?? 0; - // row.Foundry = item.foundry; // } // row.Loadouts = formatLoadouts(item, loadouts); diff --git a/src/app/organizer/Columns.tsx b/src/app/organizer/Columns.tsx index 5ed78273ff..ac6fffcd83 100644 --- a/src/app/organizer/Columns.tsx +++ b/src/app/organizer/Columns.tsx @@ -11,7 +11,7 @@ import ItemPopupTrigger from 'app/inventory/ItemPopupTrigger'; import NewItemIndicator from 'app/inventory/NewItemIndicator'; import TagIcon from 'app/inventory/TagIcon'; import { TagValue, tagConfig } from 'app/inventory/dim-item-info'; -import { D1Item, DimItem, DimSocket } from 'app/inventory/item-types'; +import { D1GridNode, D1Item, DimItem, DimSocket } from 'app/inventory/item-types'; import { storesSelector } from 'app/inventory/selectors'; import { source } from 'app/inventory/spreadsheets'; import { isHarmonizable } from 'app/inventory/store/deepsight'; @@ -117,6 +117,39 @@ const perkStringSort: Comparator = (a, b) => { return 0; }; +const csvStatNamesForDestinyVersion = ( + destinyVersion: DestinyVersion, +): Partial> => ({ + [StatHashes.RecoilDirection]: 'Recoil', + [StatHashes.AimAssistance]: 'AA', + [StatHashes.Impact]: 'Impact', + [StatHashes.Range]: 'Range', + [StatHashes.Zoom]: 'Zoom', + [StatHashes.BlastRadius]: 'Blast Radius', + [StatHashes.Velocity]: 'Velocity', + [StatHashes.Stability]: 'Stability', + [StatHashes.RoundsPerMinute]: 'ROF', + [StatHashes.ReloadSpeed]: 'Reload', + [StatHashes.AmmoCapacity]: 'Mag', + [StatHashes.Magazine]: 'Mag', + [StatHashes.Handling]: destinyVersion === 2 ? 'Handling' : 'Equip', + [StatHashes.ChargeTime]: 'Charge Time', + [StatHashes.DrawTime]: 'Draw Time', + [StatHashes.Accuracy]: 'Accuracy', + [StatHashes.ChargeRate]: 'Charge Rate', + [StatHashes.GuardResistance]: 'Guard Resistance', + [StatHashes.GuardEndurance]: 'Guard Endurance', + [StatHashes.SwingSpeed]: 'Swing Speed', + [StatHashes.ShieldDuration]: 'Shield Duration', + [StatHashes.AirborneEffectiveness]: 'Airborne Effectiveness', + [StatHashes.Intellect]: 'Intellect', + [StatHashes.Discipline]: 'Discipline', + [StatHashes.Strength]: 'Strength', + [StatHashes.Resilience]: 'Resilience', + [StatHashes.Recovery]: 'Recovery', + [StatHashes.Mobility]: 'Mobility', +}); + /** * This function generates the columns. */ @@ -150,6 +183,8 @@ export function getColumns( header: t('Organizer.Columns.StatQuality'), }; + const csvStatNames = csvStatNamesForDestinyVersion(destinyVersion); + type ColumnWithStat = ColumnDefinition & { statHash: number }; const statColumns: ColumnWithStat[] = _.sortBy( filterMap(Object.entries(statHashes), ([statHashStr, statInfo]): ColumnWithStat | undefined => { @@ -192,6 +227,10 @@ export function getColumns( const statName = _.invert(statHashByName)[statHash]; return `stat:${statName}:${statName === 'rof' ? '=' : '>='}${value}`; }, + csvVal: (_value, item) => { + const stat = item.stats?.find((s) => s.statHash === statHash); + return [csvStatNames[statHash] ?? `UnknownStat ${statHash}`, stat?.value ?? 0]; + }, }; }), (s) => getStatSortOrder(s.statHash), @@ -379,7 +418,7 @@ export function getColumns( defaultSort: SortDirection.DESC, filter: (value) => `${value ? '' : '-'}is:crafted`, // TODO: nicer to put the date in the CSV - csv: (item) => ['Crafted', Boolean(item.craftedInfo)], + csvVal: (value) => ['Crafted', Boolean(value) ? 'crafted' : false], }), c({ id: 'recency', @@ -468,6 +507,7 @@ export function getColumns( value: (item) => item.percentComplete, cell: (value) => percent(value), filter: (value) => `percentage:>=${value}`, + csvVal: (value) => ['% Leveled', (value * 100).toFixed(0)], }), destinyVersion === 2 && isWeapon && @@ -548,6 +588,20 @@ export function getColumns( sort: perkStringSort, filter: (value) => typeof value === 'string' ? `exactperk:${quoteFilterString(value)}` : undefined, + csvVal: (_value, item, { maxPerks }) => { + // This could go on any of the perks columns, since it computes a very + // different view of perks, but I just picked one. + const perks = + isD1Item(item) && item.talentGrid + ? buildNodeNames(item.talentGrid.nodes) + : item.sockets + ? buildSocketNames(item) + : []; + + return _.times(maxPerks, (index) => { + return [`Perks ${index}`, perks[index]] as [name: string, value: string | undefined]; + }); + }, }), destinyVersion === 2 && isWeapon && @@ -601,7 +655,7 @@ export function getColumns( typeof value === 'string' ? `exactperk:${quoteFilterString(value)}` : undefined, }), ...statColumns, - ...baseStatColumns, + ...(isSpreadsheet ? [] : baseStatColumns), ...d1ArmorQualityByStat, destinyVersion === 1 && isArmor && @@ -638,7 +692,7 @@ export function getColumns( header: t('Organizer.Columns.Level'), value: (item) => item.craftedInfo?.level, defaultSort: SortDirection.DESC, - csv: (item) => ['Crafted Level', item.craftedInfo?.level ?? 0], + csvVal: (value) => ['Crafted Level', value ?? 0], }), destinyVersion === 2 && isWeapon && @@ -653,7 +707,6 @@ export function getColumns( c({ id: 'killTracker', header: t('Organizer.Columns.KillTracker'), - csv: 'Kill Tracker', value: (item) => { const killTrackerInfo = getItemKillTrackerInfo(item); return killTrackerInfo?.count; @@ -667,6 +720,7 @@ export function getColumns( ); }, defaultSort: SortDirection.DESC, + csvVal: (value) => ['Kill Tracker', value ?? 0], }), destinyVersion === 2 && c({ @@ -708,14 +762,14 @@ export function getColumns( return event ? D2EventInfo[event].name : undefined; }, filter: (value) => `event:${value}`, - csv: 'Event', + csvVal: (value) => ['Event', value ?? ''], }), c({ id: 'location', header: t('Organizer.Columns.Location'), value: (item) => item.owner, cell: (_val, item) => , - csv: (item, context) => ['Owner', context.nameMap[item.owner]], + csvVal: (value, _item, { nameMap }) => ['Owner', nameMap[value]], }), c({ id: 'loadouts', @@ -755,6 +809,7 @@ export function getColumns( return loadout && `inloadout:${quoteFilterString(loadout.loadout.name)}`; } }, + csvVal: (value) => ['Loadouts', value ?? ''], }), c({ id: 'notes', @@ -1064,3 +1119,64 @@ export function buildStatInfo(items: DimItem[]): { } return statHashes; } + +function buildSocketNames(item: DimItem): string[] { + if (!item.sockets) { + return []; + } + + const sockets = []; + const { intrinsicSocket, modSocketsByCategory, perks } = getDisplayedItemSockets( + item, + /* excludeEmptySockets */ true, + )!; + + if (intrinsicSocket) { + sockets.push(intrinsicSocket); + } + + if (perks) { + sockets.push(...getSocketsByIndexes(item.sockets, perks.socketIndexes)); + } + // Improve this when we use iterator-helpers + sockets.push(...[...modSocketsByCategory.values()].flat()); + + const socketItems = sockets.map( + (s) => + (isKillTrackerSocket(s) && s.plugged?.plugDef.displayProperties.name) || + s.plugOptions.map((p) => + s.plugged?.plugDef.hash === p.plugDef.hash + ? `${p.plugDef.displayProperties.name}*` + : p.plugDef.displayProperties.name, + ), + ); + + return socketItems.flat(); +} + +const D1_FILTERED_NODE_HASHES = [ + 1920788875, // Ascend + 1270552711, // Infuse + 2133116599, // Deactivate Chroma + 643689081, // Kinetic Damage + 472357138, // Void Damage + 1975859941, // Solar Damage + 2688431654, // Arc Damage + 1034209669, // Increase Intellect + 1263323987, // Increase Discipline + 913963685, // Reforge Shell + 193091484, // Increase Strength + 217480046, // Twist Fate + 191086989, // Reforge Artifact + 2086308543, // Upgrade Defense + 4044819214, // The Life Exotic +]; + +function buildNodeNames(nodes: D1GridNode[]): string[] { + return filterMap(nodes, (node) => { + if (D1_FILTERED_NODE_HASHES.includes(node.hash)) { + return; + } + return node.activated ? `${node.name}*` : node.name; + }); +} diff --git a/src/app/organizer/table-types.ts b/src/app/organizer/table-types.ts index afc7638d0c..052e23a58f 100644 --- a/src/app/organizer/table-types.ts +++ b/src/app/organizer/table-types.ts @@ -54,21 +54,26 @@ export interface ColumnDefinition { limitToClass?: DestinyClass; /** - * Output this column as CSV. It can be a single key/value pair, or several, - * or nothing which is effectively "N/A". It can also be a single string, - * which means to use the value function as-is and use the `csv` value as the - * column name. We could reuse the header, but that's localized, while - * historically our CSV column names haven't been. + * A name for this column when it is output as CSV. This will reuse the value + * function as-is. We could reuse the header, but that's localized, while + * historically our CSV column names haven't been. I tried to combine this and + * `csvVal` but ran into type covariance issues. */ - csv?: - | string - | (( - item: DimItem, - spreadsheetContext: SpreadsheetContext, - ) => - | [name: string, value: string | number | boolean] - | [name: string, value: string | number | boolean][] - | undefined); + csv?: string; + + /** + * Optionally override the value of this column for CSV output. This is mostly + * to achieve compatibility with the existing CSV format, but sometimes it's + * used to output complex data for CSV. For example, perks are output as + * multiple columns. + */ + csvVal?( + value: V, + item: DimItem, + spreadsheetContext: SpreadsheetContext, + ): + | [name: string, value: string | number | boolean | undefined] + | [name: string, value: string | number | boolean | undefined][]; } export interface SpreadsheetContext { diff --git a/src/app/utils/item-utils.ts b/src/app/utils/item-utils.ts index 8100460bff..2ab2701245 100644 --- a/src/app/utils/item-utils.ts +++ b/src/app/utils/item-utils.ts @@ -112,15 +112,13 @@ export const isArmor2Mod = (item: DestinyInventoryItemDefinition): boolean => (armor2PlugCategoryHashes.includes(item.plug.plugCategoryHash) || specialtyModPlugCategoryHashes.includes(item.plug.plugCategoryHash)); -/** accepts a DimMasterwork or lack thereof, & always returns a string */ +/** accepts a DimMasterwork or lack thereof */ export function getMasterworkStatNames(mw: DimMasterwork | null) { - return ( - mw?.stats - ?.filter((stat) => stat.isPrimary) - .map((stat) => stat.name) - .filter(Boolean) - .join(', ') ?? '' - ); + return mw?.stats + ?.filter((stat) => stat.isPrimary) + .map((stat) => stat.name) + .filter(Boolean) + .join(', '); } /** Can this item be equipped by the given store? */ diff --git a/src/locale/en.json b/src/locale/en.json index 549256ad1e..9a4b3a3ee8 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -1026,6 +1026,7 @@ "Damage": "Damage", "Energy": "Energy", "Event": "Event", + "Foundry": "", "Harmonizable": "Harmonizable", "Icon": "Icon", "Intrinsics": "Intrinsic", From d71e47a1876aeab848fb465628cc398e085c56fe Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Wed, 7 Aug 2024 00:29:00 -0700 Subject: [PATCH 03/10] Ghosts and armor --- .../__snapshots__/d2-stores.test.ts.snap | 86 ++++- src/app/inventory/d2-stores.test.ts | 19 +- src/app/inventory/spreadsheets.ts | 350 ++++-------------- src/app/organizer/Columns.tsx | 162 ++++---- src/app/organizer/ItemTable.tsx | 6 +- src/app/organizer/table-types.ts | 10 +- src/app/settings/Spreadsheets.tsx | 8 +- 7 files changed, 251 insertions(+), 390 deletions(-) diff --git a/src/app/inventory/__snapshots__/d2-stores.test.ts.snap b/src/app/inventory/__snapshots__/d2-stores.test.ts.snap index 58022b6998..26b24928d1 100644 --- a/src/app/inventory/__snapshots__/d2-stores.test.ts.snap +++ b/src/app/inventory/__snapshots__/d2-stores.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`process stores generates a correct Armor CSV export 1`] = ` +exports[`process stores generates a correct armor CSV export 1`] = ` [ { "Discipline": 32, @@ -8663,10 +8663,12 @@ exports[`process stores generates a correct Armor CSV export 1`] = ` ] `; -exports[`process stores generates a correct Ghost CSV export 1`] = ` +exports[`process stores generates a correct ghost CSV export 1`] = ` [ { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 573576346, "Id": ""6917529085740071304"", "Loadouts": "", @@ -8678,12 +8680,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 2, "Source": "mercury", "Tag": undefined, "Tier": "Exotic", + "Year": 1, }, { + "Energy Capacity": 6, "Equipped": true, + "Event": "", "Hash": 1283654212, "Id": ""6917529127946162805"", "Loadouts": "", @@ -8695,12 +8701,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 8, "Source": "deluxe", "Tag": undefined, "Tier": "Exotic", + "Year": 3, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 227918504, "Id": ""6917529156697381898"", "Loadouts": "", @@ -8712,12 +8722,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 5, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 2, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 4238874520, "Id": ""6917529193444242909"", "Loadouts": "", @@ -8729,12 +8743,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 10, "Source": "seasonpass", "Tag": undefined, "Tier": "Exotic", + "Year": 3, }, { + "Energy Capacity": 10, "Equipped": true, + "Event": "", "Hash": 3398078482, "Id": ""6917529198421309490"", "Loadouts": "", @@ -8746,12 +8764,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": "Blinding Light*", "Perks 2": "Prize Pursuant*", "Perks 3": "Standard Glimmer Booster*", + "Season": 11, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 3, }, { + "Energy Capacity": 6, "Equipped": false, + "Event": "", "Hash": 1558857471, "Id": ""6917529261791074579"", "Loadouts": "", @@ -8763,12 +8785,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 2, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 1, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 1090082587, "Id": ""6917529441305449079"", "Loadouts": "", @@ -8780,12 +8806,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 10, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 3, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 527607310, "Id": ""6917529613981176946"", "Loadouts": "", @@ -8797,12 +8827,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 6, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 2, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 634549439, "Id": ""6917529805257494505"", "Loadouts": "", @@ -8814,12 +8848,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 17, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 5, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 2313814566, "Id": ""6917529812262344063"", "Loadouts": "", @@ -8831,12 +8869,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 18, "Source": "deluxe", "Tag": undefined, "Tier": "Exotic", + "Year": 5, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 227918505, "Id": ""6917529812274003175"", "Loadouts": "", @@ -8848,12 +8890,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 5, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 2, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 1302968378, "Id": ""6917529842269845415"", "Loadouts": "", @@ -8865,12 +8911,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 18, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 5, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 3809059822, "Id": ""6917529859037522184"", "Loadouts": "", @@ -8882,12 +8932,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 19, "Source": "rasputin", "Tag": undefined, "Tier": "Exotic", + "Year": 5, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 1490914249, "Id": ""6917529891097015415"", "Loadouts": "", @@ -8899,12 +8953,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 20, "Source": "seasonpass", "Tag": undefined, "Tier": "Exotic", + "Year": 6, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 3705925878, "Id": ""6917529932856531597"", "Loadouts": "", @@ -8916,12 +8974,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 14, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 4, }, { + "Energy Capacity": 1, "Equipped": true, + "Event": "", "Hash": 210874516, "Id": ""6917529932874117020"", "Loadouts": "", @@ -8933,12 +8995,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 22, "Source": "deluxe", "Tag": undefined, "Tier": "Exotic", + "Year": 6, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 1013853356, "Id": ""6917529950744002122"", "Loadouts": "", @@ -8950,12 +9016,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 12, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 4, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 3775649487, "Id": ""6917529951182029807"", "Loadouts": "", @@ -8967,12 +9037,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 22, "Source": "seasonpass", "Tag": undefined, "Tier": "Exotic", + "Year": 6, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "The Dawning", "Hash": 2549404869, "Id": ""6917529966167567978"", "Loadouts": "", @@ -8984,12 +9058,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 2, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 1, }, { + "Energy Capacity": 1, "Equipped": false, + "Event": "", "Hash": 3733702440, "Id": ""6917529993263920330"", "Loadouts": "", @@ -9001,14 +9079,16 @@ exports[`process stores generates a correct Ghost CSV export 1`] = ` "Perks 1": undefined, "Perks 2": undefined, "Perks 3": undefined, + "Season": 23, "Source": "eververse", "Tag": undefined, "Tier": "Exotic", + "Year": 6, }, ] `; -exports[`process stores generates a correct Weapons CSV export 1`] = ` +exports[`process stores generates a correct weapon CSV export 1`] = ` [ { "AA": 40, diff --git a/src/app/inventory/d2-stores.test.ts b/src/app/inventory/d2-stores.test.ts index bd0b5cb87b..d14ed11c6c 100644 --- a/src/app/inventory/d2-stores.test.ts +++ b/src/app/inventory/d2-stores.test.ts @@ -96,15 +96,12 @@ describe('process stores', () => { throw new Error('Expected at least one item with a perk that cannot roll'); }); - test.each(['Weapons', 'Armor', 'Ghost'] as const)( - 'generates a correct %s CSV export', - (type) => { - setupi18n(); - const getTag = () => undefined; - const getNotes = () => undefined; - const loadoutsByItem = {}; - const csvExport = generateCSVExportData(type, stores, getTag, getNotes, loadoutsByItem); - expect(csvExport).toMatchSnapshot(); - }, - ); + test.each(['weapon', 'armor', 'ghost'] as const)('generates a correct %s CSV export', (type) => { + setupi18n(); + const getTag = () => undefined; + const getNotes = () => undefined; + const loadoutsByItem = {}; + const csvExport = generateCSVExportData(type, stores, getTag, getNotes, loadoutsByItem); + expect(csvExport).toMatchSnapshot(); + }); }); diff --git a/src/app/inventory/spreadsheets.ts b/src/app/inventory/spreadsheets.ts index f493dbe05c..5af7fba97e 100644 --- a/src/app/inventory/spreadsheets.ts +++ b/src/app/inventory/spreadsheets.ts @@ -4,22 +4,14 @@ import { LoadoutsByItem, loadoutsByItemSelector } from 'app/loadout/selectors'; import { buildStatInfo, getColumns } from 'app/organizer/Columns'; import { SpreadsheetContext } from 'app/organizer/table-types'; import { D1_StatHashes } from 'app/search/d1-known-values'; -import D2Sources from 'app/search/items/search-filters/d2-sources'; -import { dimArmorStatHashByName } from 'app/search/search-filter-values'; import { ThunkResult } from 'app/store/types'; import { filterMap } from 'app/utils/collections'; import { compareBy } from 'app/utils/comparators'; import { DimError } from 'app/utils/dim-error'; import { download } from 'app/utils/download'; -import { - getItemYear, - getSpecialtySocketMetadatas, - isD1Item, - isKillTrackerSocket, -} from 'app/utils/item-utils'; +import { isD1Item, isKillTrackerSocket } from 'app/utils/item-utils'; import { getDisplayedItemSockets, getSocketsByIndexes } from 'app/utils/socket-utils'; import { DestinyClass } from 'bungie-api-ts/destiny2'; -import { D2EventInfo } from 'data/d2/d2-event-info-v2'; import { BucketHashes, StatHashes } from 'data/d2/generated-enums'; import _ from 'lodash'; import Papa from 'papaparse'; @@ -28,7 +20,6 @@ import { TagValue, tagConfig } from './dim-item-info'; import { D1GridNode, DimItem } from './item-types'; import { getNotesSelector, getTagSelector, storesSelector } from './selectors'; import { DimStore } from './store-types'; -import { getEvent, getSeason } from './store/season'; function getClass(type: DestinyClass) { switch (type) { @@ -62,11 +53,9 @@ const D1_FILTERED_NODE_HASHES = [ 2086308543, // Upgrade Defense 4044819214, // The Life Exotic ]; -// ignore raid & calus sources in favor of more detailed sources -const sourceKeys = Object.keys(D2Sources).filter((k) => !['raid', 'calus'].includes(k)); export function generateCSVExportData( - type: 'Weapons' | 'Armor' | 'Ghost', + type: 'weapon' | 'armor' | 'ghost', stores: DimStore[], getTag: (item: DimItem) => TagValue | undefined, getNotes: (item: DimItem) => string | undefined, @@ -82,39 +71,75 @@ export function generateCSVExportData( : `${capitalizeFirstLetter(getClass(store.classType))}(${store.powerLevel})`; } allItems.sort(compareBy((item) => item.index)); - const items: DimItem[] = []; - for (const item of allItems) { - if (!item.primaryStat && type !== 'Ghost') { - continue; - } + let items: DimItem[] = []; + if (type === 'weapon') { + items = allItems.filter( + (item) => + // Checking the primary stat filters out some quest items + item.primaryStat && + (item.primaryStat?.statHash === D1_StatHashes.Attack || + item.primaryStat?.statHash === StatHashes.Attack), + ); + } else if (type === 'armor') { + items = allItems.filter( + // Checking the primary stat filters out festival masks + (item) => item.primaryStat && item.primaryStat?.statHash === StatHashes.Defense, + ); + } else if (type === 'ghost') { + items = allItems.filter((item) => item.bucket.hash === BucketHashes.Ghost); + } - if (type === 'Weapons') { - if ( - item.primaryStat?.statHash === D1_StatHashes.Attack || - item.primaryStat?.statHash === StatHashes.Attack - ) { - items.push(item); - } - } else if (type === 'Armor') { - if (item.primaryStat?.statHash === StatHashes.Defense) { - items.push(item); + // We need to always emit enough columns for all perks + const maxPerks = getMaxPerks(items); + const statHashes = buildStatInfo(items); + + // Use the column definitions from Organizer to drive the CSV output + const columns = getColumns( + 'spreadsheet', + type, + statHashes, + getTag, + getNotes, + () => undefined /* wishList */, + false /* hasWishList */, + [] /* customStats */, + loadoutsByItem, + new Set() /* newItems */, + items[0]?.destinyVersion ?? 2 /* destinyVersion */, + ); + + const context: SpreadsheetContext = { nameMap, maxPerks }; + const data = items.map((item) => { + let row: Record = {}; + for (const column of columns) { + const value = column.value(item); + if (column.csv) { + row[column.csv] ||= value; + } else if (column.csvVal) { + const values = column.csvVal!(value, item, context); + if (!values || values.length === 0) { + continue; + } + if (Array.isArray(values[0])) { + for (const [key, value] of values as [string, string | number | boolean | undefined][]) { + row[key] ||= value; + } + } else { + const [key, value] = values; + row[key] ||= value; + } + } else { + // Column has no CSV representation - either remove the column in spreadsheet mode or add a 'csv' or 'csvVal' property + throw new Error(`missing-csv: ${column.id}`); } - } else if (type === 'Ghost' && item.bucket.hash === BucketHashes.Ghost) { - items.push(item); } - } + return row; + }); - switch (type) { - case 'Weapons': - return downloadWeapons(items, nameMap, getTag, getNotes, loadoutsByItem); - case 'Armor': - return downloadArmor(items, nameMap, getTag, getNotes, loadoutsByItem); - case 'Ghost': - return downloadGhost(items, nameMap, getTag, getNotes, loadoutsByItem); - } + return data; } -export function downloadCsvFiles(type: 'Weapons' | 'Armor' | 'Ghost'): ThunkResult { +export function downloadCsvFiles(type: 'weapon' | 'armor' | 'ghost'): ThunkResult { return async (_dispatch, getState) => { const stores = storesSelector(getState()); const getTag = getTagSelector(getState()); @@ -223,7 +248,7 @@ function downloadCsv(filename: string, csv: string) { download(csv, filenameWithExt, 'text/csv'); } -function buildSocketNames(item: DimItem): string[] { +export function buildSocketNames(item: DimItem): string[] { if (!item.sockets) { return []; } @@ -257,7 +282,7 @@ function buildSocketNames(item: DimItem): string[] { return socketItems.flat(); } -function buildNodeNames(nodes: D1GridNode[]): string[] { +export function buildNodeNames(nodes: D1GridNode[]): string[] { return filterMap(nodes, (node) => { if (D1_FILTERED_NODE_HASHES.includes(node.hash)) { return; @@ -282,244 +307,3 @@ function getMaxPerks(items: DimItem[]) { ) || 0 ); } - -function addPerks(row: Record, item: DimItem, maxPerks: number) { - const perks = - isD1Item(item) && item.talentGrid - ? buildNodeNames(item.talentGrid.nodes) - : item.sockets - ? buildSocketNames(item) - : []; - - _.times(maxPerks, (index) => { - row[`Perks ${index}`] = perks[index]; - }); -} - -function formatLoadouts(item: DimItem, loadouts: LoadoutsByItem) { - return loadouts[item.id]?.map(({ loadout }) => loadout.name).join(', ') ?? ''; -} - -function downloadGhost( - items: DimItem[], - nameMap: { [key: string]: string }, - getTag: (item: DimItem) => TagValue | undefined, - getNotes: (item: DimItem) => string | undefined, - loadouts: LoadoutsByItem, -) { - // We need to always emit enough columns for all perks - const maxPerks = getMaxPerks(items); - - const data = items.map((item) => { - const row = { - Name: item.name, - Hash: item.hash, - Id: `"${item.id}"`, - Tag: getTag(item), - Tier: item.tier, - Source: source(item), - Owner: nameMap[item.owner], - Locked: item.locked, - Equipped: item.equipped, - Loadouts: formatLoadouts(item, loadouts), - Notes: getNotes(item), - }; - - addPerks(row, item, maxPerks); - - return row; - }); - - return data; -} - -function equippable(item: DimItem) { - return item.classType === DestinyClass.Unknown ? 'Any' : item.classTypeNameLocalized; -} - -export function source(item: DimItem) { - if (item.destinyVersion === 2) { - return ( - sourceKeys.find( - (src) => - (item.source && D2Sources[src].sourceHashes?.includes(item.source)) || - D2Sources[src].itemHashes?.includes(item.hash), - ) || '' - ); - } -} - -function downloadArmor( - items: DimItem[], - nameMap: { [key: string]: string }, - getTag: (item: DimItem) => TagValue | undefined, - getNotes: (item: DimItem) => string | undefined, - loadouts: LoadoutsByItem, -) { - // We need to always emit enough columns for all perks - const maxPerks = getMaxPerks(items); - - // In PapaParse, the keys of the first objects are used as columns. So if a - // key is omitted from the first object, it won't show up. - // TODO: Replace PapaParse with a simpler/smaller CSV generator - const data = items.map((item) => { - const row: Record = { - Name: item.name, - Hash: item.hash, - Id: `"${item.id}"`, - Tag: getTag(item), - Tier: item.tier, - Type: item.typeName, - Source: source(item), - Equippable: equippable(item), - [item.destinyVersion === 1 ? 'Light' : 'Power']: item.power, - }; - if (item.destinyVersion === 2) { - row['Energy Capacity'] = item.energy?.energyCapacity || undefined; - } - row.Owner = nameMap[item.owner]; - if (item.destinyVersion === 1) { - row['% Leveled'] = (item.percentComplete * 100).toFixed(0); - } - row.Locked = item.locked; - row.Equipped = item.equipped; - row.Year = getItemYear(item); - if (item.destinyVersion === 2) { - row.Season = getSeason(item); - const event = getEvent(item); - row.Event = event ? D2EventInfo[event].name : ''; - } - - if (isD1Item(item)) { - row['% Quality'] = item.quality?.min ?? 0; - } - const stats: { [name: string]: { value: number; pct: number; base: number } } = {}; - if (item.stats) { - if (isD1Item(item)) { - for (const stat of item.stats) { - let pct = 0; - if (stat.scaled?.min) { - pct = Math.round((100 * stat.scaled.min) / (stat.split || 1)); - } - stats[stat.statHash] = { - value: stat.value, - pct, - base: 0, - }; - } - } else { - for (const stat of item.stats) { - stats[stat.statHash] = { - value: stat.value, - base: stat.base, - pct: 0, - }; - } - } - } - if (item.destinyVersion === 1) { - row['% IntQ'] = stats.Intellect?.pct ?? 0; - row['% DiscQ'] = stats.Discipline?.pct ?? 0; - row['% StrQ'] = stats.Strength?.pct ?? 0; - row.Int = stats.Intellect?.value ?? 0; - row.Disc = stats.Discipline?.value ?? 0; - row.Str = stats.Strength?.value ?? 0; - } else { - const armorStats = Object.keys(dimArmorStatHashByName).map((statName) => ({ - name: statName, - stat: stats[dimArmorStatHashByName[statName]!], - })); - for (const stat of armorStats) { - row[capitalizeFirstLetter(stat.name)] = stat.stat?.value ?? 0; - } - for (const stat of armorStats) { - row[`${capitalizeFirstLetter(stat.name)} (Base)`] = stat.stat?.base ?? 0; - } - - if (item.sockets) { - row['Seasonal Mod'] = getSpecialtySocketMetadatas(item)?.map((m) => m.slotTag) ?? ''; - } - } - - row.Loadouts = formatLoadouts(item, loadouts); - row.Notes = getNotes(item); - - addPerks(row, item, maxPerks); - - return row; - }); - return data; -} - -function downloadWeapons( - items: DimItem[], - nameMap: { [key: string]: string }, - getTag: (item: DimItem) => TagValue | undefined, - getNotes: (item: DimItem) => string | undefined, - loadouts: LoadoutsByItem, -) { - // TODO: Use the weird stat name mapping? - // TODO: Perks - // TODO: map event to empty string if not found - - // We need to always emit enough columns for all perks - const maxPerks = getMaxPerks(items); - const statHashes = buildStatInfo(items); - const columns = getColumns( - 'spreadsheet', - 'weapon', - statHashes, - getTag, - getNotes, - () => undefined /* wishList */, - false /* hasWishList */, - [] /* customStats */, - loadouts, - new Set() /* newItems */, - 2 /* destinyVersion */, - ).filter((c) => c.csv || c.csvVal); - - // TODO: error if csv isn't defined for these columns! - - const context: SpreadsheetContext = { nameMap, maxPerks }; - - // In PapaParse, the keys of the first objects are used as columns. So if a - // key is omitted from the first object, it won't show up. - // TODO: Replace PapaParse with a simpler/smaller CSV generator - const data = items.map((item) => { - let row: Record = {}; - - for (const column of columns) { - const value = column.value(item); - if (column.csv) { - row[column.csv] ||= value; - } else if (column.csvVal) { - const values = column.csvVal!(value, item, context); - if (!values || values.length === 0) { - continue; - } - if (Array.isArray(values[0])) { - for (const [key, value] of values as [string, string | number | boolean | undefined][]) { - row[key] ||= value; - } - } else { - const [key, value] = values; - row[key] ||= value; - } - } - } - - // console.log(item.name, row); - - // } - - // row.Loadouts = formatLoadouts(item, loadouts); - // row.Notes = getNotes(item); - - // addPerks(row, item, maxPerks); - - return row; - }); - - return data; -} diff --git a/src/app/organizer/Columns.tsx b/src/app/organizer/Columns.tsx index ac6fffcd83..0565692380 100644 --- a/src/app/organizer/Columns.tsx +++ b/src/app/organizer/Columns.tsx @@ -11,9 +11,8 @@ import ItemPopupTrigger from 'app/inventory/ItemPopupTrigger'; import NewItemIndicator from 'app/inventory/NewItemIndicator'; import TagIcon from 'app/inventory/TagIcon'; import { TagValue, tagConfig } from 'app/inventory/dim-item-info'; -import { D1GridNode, D1Item, DimItem, DimSocket } from 'app/inventory/item-types'; +import { D1Item, DimItem, DimSocket } from 'app/inventory/item-types'; import { storesSelector } from 'app/inventory/selectors'; -import { source } from 'app/inventory/spreadsheets'; import { isHarmonizable } from 'app/inventory/store/deepsight'; import { getEvent, getSeason } from 'app/inventory/store/season'; import { getStatSortOrder } from 'app/inventory/store/stats'; @@ -27,7 +26,12 @@ import { editLoadout } from 'app/loadout-drawer/loadout-events'; import InGameLoadoutIcon from 'app/loadout/ingame/InGameLoadoutIcon'; import { InGameLoadout, Loadout, isInGameLoadout } from 'app/loadout/loadout-types'; import { LoadoutsByItem } from 'app/loadout/selectors'; -import { breakerTypeNames, weaponMasterworkY2SocketTypeHash } from 'app/search/d2-known-values'; +import { + TOTAL_STAT_HASH, + breakerTypeNames, + weaponMasterworkY2SocketTypeHash, +} from 'app/search/d2-known-values'; +import D2Sources from 'app/search/items/search-filters/d2-sources'; import { quoteFilterString } from 'app/search/query-parser'; import { statHashByName } from 'app/search/search-filter-values'; import { getColor, percent } from 'app/shell/formatters'; @@ -47,6 +51,7 @@ import { getItemKillTrackerInfo, getItemYear, getMasterworkStatNames, + getSpecialtySocketMetadatas, isArtificeSocket, isD1Item, isKillTrackerSocket, @@ -77,7 +82,9 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { createCustomStatColumns } from './CustomStatColumns'; +import { buildNodeNames, buildSocketNames } from 'app/inventory/spreadsheets'; import { DeepsightHarmonizerIcon } from 'app/item-popup/DeepsightHarmonizerIcon'; +import { DestinyClass } from 'bungie-api-ts/destiny2'; import styles from './ItemTable.m.scss'; // eslint-disable-line css-modules/no-unused-class import { ColumnDefinition, ColumnGroup, SortDirection, Value } from './table-types'; @@ -117,9 +124,10 @@ const perkStringSort: Comparator = (a, b) => { return 0; }; +/** Stat names are not localized in CSV */ const csvStatNamesForDestinyVersion = ( destinyVersion: DestinyVersion, -): Partial> => ({ +): Partial> => ({ [StatHashes.RecoilDirection]: 'Recoil', [StatHashes.AimAssistance]: 'AA', [StatHashes.Impact]: 'Impact', @@ -142,12 +150,13 @@ const csvStatNamesForDestinyVersion = ( [StatHashes.SwingSpeed]: 'Swing Speed', [StatHashes.ShieldDuration]: 'Shield Duration', [StatHashes.AirborneEffectiveness]: 'Airborne Effectiveness', - [StatHashes.Intellect]: 'Intellect', - [StatHashes.Discipline]: 'Discipline', - [StatHashes.Strength]: 'Strength', + [StatHashes.Intellect]: destinyVersion === 2 ? 'Intellect' : 'Int', + [StatHashes.Discipline]: destinyVersion === 2 ? 'Discipline' : 'Disc', + [StatHashes.Strength]: destinyVersion === 2 ? 'Strength' : 'Str', [StatHashes.Resilience]: 'Resilience', [StatHashes.Recovery]: 'Recovery', [StatHashes.Mobility]: 'Mobility', + [TOTAL_STAT_HASH]: 'Total', }); /** @@ -185,7 +194,7 @@ export function getColumns( const csvStatNames = csvStatNamesForDestinyVersion(destinyVersion); - type ColumnWithStat = ColumnDefinition & { statHash: number }; + type ColumnWithStat = ColumnDefinition & { statHash: StatHashes }; const statColumns: ColumnWithStat[] = _.sortBy( filterMap(Object.entries(statHashes), ([statHashStr, statInfo]): ColumnWithStat | undefined => { const statHash = parseInt(statHashStr, 10) as StatHashes; @@ -242,7 +251,7 @@ export function getColumns( const isSpreadsheet = useCase === 'spreadsheet'; const baseStatColumns: ColumnWithStat[] = - destinyVersion === 2 + destinyVersion === 2 && (isArmor || !isSpreadsheet) ? statColumns.map((column) => ({ ...column, id: `base${column.statHash}`, @@ -262,6 +271,13 @@ export function getColumns( return ; }, filter: (value) => `basestat:${_.invert(statHashByName)[column.statHash]}:>=${value}`, + csvVal: (_value, item) => { + const stat = item.stats?.find((s) => s.statHash === column.statHash); + return [ + `${csvStatNames[column.statHash] ?? `UnknownStatBase ${column.statHash}`} (Base)`, + stat?.base ?? 0, + ]; + }, })) : []; @@ -269,7 +285,7 @@ export function getColumns( destinyVersion === 1 && isArmor ? _.sortBy( Object.entries(statHashes).map(([statHashStr, statInfo]): ColumnWithStat => { - const statHash = parseInt(statHashStr, 10); + const statHash = parseInt(statHashStr, 10) as StatHashes; return { statHash, id: `quality_${statHash}`, @@ -291,6 +307,16 @@ export function getColumns( {value}% ); }, + csvVal: (_value, item) => { + if (!isD1Item(item)) { + throw new Error('Expected D1 item'); + } + const stat = item.stats?.find((s) => s.statHash === statHash); + return [ + `% ${csvStatNames[statHash] ?? `UnknownStat ${statHash}`}Q`, + stat?.scaled?.min ? Math.round((100 * stat.scaled.min) / (stat.split || 1)) : 0, + ]; + }, }; }), (s) => getStatSortOrder(s.statHash), @@ -369,12 +395,21 @@ export function getColumns( cell: (_val, item) => , filter: (_val, item) => `is:${getItemDamageShortName(item)}`, }), + isArmor && + isSpreadsheet && + c({ + id: 'equippable', + header: 'Equippable', + csv: 'Equippable', + value: (item) => + item.classType === DestinyClass.Unknown ? 'Any' : item.classTypeNameLocalized, + }), (isArmor || isGhost) && destinyVersion === 2 && c({ id: 'energy', header: t('Organizer.Columns.Energy'), - csv: 'Energy', + csv: 'Energy Capacity', value: (item) => item.energy?.energyCapacity, defaultSort: SortDirection.DESC, filter: (value) => `energycapacity:>=${value}`, @@ -420,14 +455,16 @@ export function getColumns( // TODO: nicer to put the date in the CSV csvVal: (value) => ['Crafted', Boolean(value) ? 'crafted' : false], }), - c({ - id: 'recency', - header: t('Organizer.Columns.Recency'), - value: (item) => item.id, - cell: () => '', - }), + !isSpreadsheet && + c({ + id: 'recency', + header: t('Organizer.Columns.Recency'), + value: (item) => item.id, + cell: () => '', + }), destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'wishList', header: t('Organizer.Columns.WishList'), @@ -454,6 +491,7 @@ export function getColumns( filter: (value) => `is:${value}`, }), isSpreadsheet && + !isGhost && c({ id: 'Type', header: 'Type', @@ -461,6 +499,7 @@ export function getColumns( value: (i) => i.typeName, }), isSpreadsheet && + isWeapon && c({ id: 'Category', header: 'Category', @@ -499,6 +538,9 @@ export function getColumns( .map((m) => `modslot:${m}`) .join(' ') : ``, + csvVal: (_val, item) => { + return ['Seasonal Mod', getSpecialtySocketMetadatas(item)?.map((m) => m.slotTag) ?? '']; + }, }), destinyVersion === 1 && c({ @@ -511,6 +553,7 @@ export function getColumns( }), destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'archetype', header: t('Organizer.Columns.Archetype'), @@ -537,6 +580,7 @@ export function getColumns( }), destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'breaker', header: t('Organizer.Columns.Breaker'), @@ -555,6 +599,7 @@ export function getColumns( }), destinyVersion === 2 && isArmor && + !isSpreadsheet && c({ id: 'intrinsics', header: t('Organizer.Columns.Intrinsics'), @@ -605,6 +650,7 @@ export function getColumns( }), destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'traits', header: t('Organizer.Columns.Traits'), @@ -623,6 +669,7 @@ export function getColumns( destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'originTrait', header: t('Organizer.Columns.OriginTraits'), @@ -639,6 +686,7 @@ export function getColumns( typeof value === 'string' ? `exactperk:${quoteFilterString(value)}` : undefined, }), destinyVersion === 2 && + !isSpreadsheet && c({ id: 'shaders', header: t('Organizer.Columns.Shaders'), @@ -655,13 +703,14 @@ export function getColumns( typeof value === 'string' ? `exactperk:${quoteFilterString(value)}` : undefined, }), ...statColumns, - ...(isSpreadsheet ? [] : baseStatColumns), + ...baseStatColumns, ...d1ArmorQualityByStat, destinyVersion === 1 && isArmor && c({ id: 'quality', header: t('Organizer.Columns.Quality'), + csv: '% Quality', value: (item) => (isD1Item(item) && item.quality ? item.quality.min : 0), cell: (value) => {value}%, filter: (value) => `quality:>=${value}`, @@ -696,6 +745,7 @@ export function getColumns( }), destinyVersion === 2 && isWeapon && + !isSpreadsheet && c({ id: 'harmonizable', header: t('Organizer.Columns.Harmonizable'), @@ -723,6 +773,7 @@ export function getColumns( csvVal: (value) => ['Kill Tracker', value ?? 0], }), destinyVersion === 2 && + isWeapon && c({ id: 'foundry', header: t('Organizer.Columns.Foundry'), @@ -733,25 +784,25 @@ export function getColumns( destinyVersion === 2 && c({ id: 'source', + csv: 'Source', header: t('Organizer.Columns.Source'), value: source, filter: (value) => `source:${value}`, - csv: 'Source', }), c({ id: 'year', + csv: 'Year', header: t('Organizer.Columns.Year'), value: (item) => getItemYear(item), filter: (value) => `year:${value}`, - csv: 'Year', }), destinyVersion === 2 && c({ id: 'season', + csv: 'Season', header: t('Organizer.Columns.Season'), value: (i) => getSeason(i), filter: (value) => `season:${value}`, - csv: 'Season', }), destinyVersion === 2 && c({ @@ -1120,63 +1171,16 @@ export function buildStatInfo(items: DimItem[]): { return statHashes; } -function buildSocketNames(item: DimItem): string[] { - if (!item.sockets) { - return []; +// ignore raid & calus sources in favor of more detailed sources +const sourceKeys = Object.keys(D2Sources).filter((k) => !['raid', 'calus'].includes(k)); +function source(item: DimItem) { + if (item.destinyVersion === 2) { + return ( + sourceKeys.find( + (src) => + (item.source && D2Sources[src].sourceHashes?.includes(item.source)) || + D2Sources[src].itemHashes?.includes(item.hash), + ) || '' + ); } - - const sockets = []; - const { intrinsicSocket, modSocketsByCategory, perks } = getDisplayedItemSockets( - item, - /* excludeEmptySockets */ true, - )!; - - if (intrinsicSocket) { - sockets.push(intrinsicSocket); - } - - if (perks) { - sockets.push(...getSocketsByIndexes(item.sockets, perks.socketIndexes)); - } - // Improve this when we use iterator-helpers - sockets.push(...[...modSocketsByCategory.values()].flat()); - - const socketItems = sockets.map( - (s) => - (isKillTrackerSocket(s) && s.plugged?.plugDef.displayProperties.name) || - s.plugOptions.map((p) => - s.plugged?.plugDef.hash === p.plugDef.hash - ? `${p.plugDef.displayProperties.name}*` - : p.plugDef.displayProperties.name, - ), - ); - - return socketItems.flat(); -} - -const D1_FILTERED_NODE_HASHES = [ - 1920788875, // Ascend - 1270552711, // Infuse - 2133116599, // Deactivate Chroma - 643689081, // Kinetic Damage - 472357138, // Void Damage - 1975859941, // Solar Damage - 2688431654, // Arc Damage - 1034209669, // Increase Intellect - 1263323987, // Increase Discipline - 913963685, // Reforge Shell - 193091484, // Increase Strength - 217480046, // Twist Fate - 191086989, // Reforge Artifact - 2086308543, // Upgrade Defense - 4044819214, // The Life Exotic -]; - -function buildNodeNames(nodes: D1GridNode[]): string[] { - return filterMap(nodes, (node) => { - if (D1_FILTERED_NODE_HASHES.includes(node.hash)) { - return; - } - return node.activated ? `${node.name}*` : node.name; - }); } diff --git a/src/app/organizer/ItemTable.tsx b/src/app/organizer/ItemTable.tsx index d87baed872..d9ed23f063 100644 --- a/src/app/organizer/ItemTable.tsx +++ b/src/app/organizer/ItemTable.tsx @@ -72,13 +72,13 @@ const categoryToClass: LookupTable = { }; const downloadButtonSettings = [ - { categoryId: ['weapons'], csvType: 'Weapons' as const, label: tl('Bucket.Weapons') }, + { categoryId: ['weapons'], csvType: 'weapon' as const, label: tl('Bucket.Weapons') }, { categoryId: ['hunter', 'titan', 'warlock'], - csvType: 'Armor' as const, + csvType: 'armor' as const, label: tl('Bucket.Armor'), }, - { categoryId: ['ghosts'], csvType: 'Ghost' as const, label: tl('Bucket.Ghost') }, + { categoryId: ['ghosts'], csvType: 'ghost' as const, label: tl('Bucket.Ghost') }, ]; const MemoRow = memo(TableRow); diff --git a/src/app/organizer/table-types.ts b/src/app/organizer/table-types.ts index 052e23a58f..8702888df9 100644 --- a/src/app/organizer/table-types.ts +++ b/src/app/organizer/table-types.ts @@ -16,6 +16,8 @@ export interface ColumnGroup { dropdownLabel?: React.ReactNode; } +export type CSVColumn = [name: string, value: string | string[] | number | boolean | undefined]; + // TODO: column groupings? // TODO: custom configs like the total column? // prop methods make this invariant over V, so disable the rule here @@ -67,13 +69,7 @@ export interface ColumnDefinition { * used to output complex data for CSV. For example, perks are output as * multiple columns. */ - csvVal?( - value: V, - item: DimItem, - spreadsheetContext: SpreadsheetContext, - ): - | [name: string, value: string | number | boolean | undefined] - | [name: string, value: string | number | boolean | undefined][]; + csvVal?(value: V, item: DimItem, spreadsheetContext: SpreadsheetContext): CSVColumn | CSVColumn[]; } export interface SpreadsheetContext { diff --git a/src/app/settings/Spreadsheets.tsx b/src/app/settings/Spreadsheets.tsx index 152419bfd2..305b6b8d6e 100644 --- a/src/app/settings/Spreadsheets.tsx +++ b/src/app/settings/Spreadsheets.tsx @@ -34,7 +34,7 @@ export default function Spreadsheets() { } }; - const downloadCsv = (type: 'Armor' | 'Weapons' | 'Ghost') => dispatch(downloadCsvFiles(type)); + const downloadCsv = (type: 'armor' | 'weapon' | 'ghost') => dispatch(downloadCsvFiles(type)); return (
@@ -48,7 +48,7 @@ export default function Spreadsheets() {