Skip to content

Commit

Permalink
Merge pull request #10887 from DestinyItemManager/character-stats-dis…
Browse files Browse the repository at this point in the history
…playprops

Use displayProperties for stats, remove D1 stuff
  • Loading branch information
bhollis authored Jan 13, 2025
2 parents ba7be5c + ea2e29d commit 5193e54
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 189 deletions.
1 change: 0 additions & 1 deletion i18next-scanner.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ module.exports = {
// prettier-ignore
const keys = {
buckets: { list: ['General', 'Inventory', 'Postmaster', 'Progress', 'Unknown'] },
cooldowns: { list: ['Grenade', 'Melee', 'Super'] },
difficulty: { list: ['Normal', 'Hard'] },
progress: { list: ['Bounties', 'Items', 'Quests'] },
sockets: { list: ['Mod', 'Ability', 'Shader', 'Ornament', 'Fragment', 'Aspect', 'Projection', 'Transmat', 'Super'] }
Expand Down
7 changes: 5 additions & 2 deletions src/app/destiny1/loadout-builder/GeneratedSet.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { t } from 'app/i18next-t';
import { findItemsByBucket } from 'app/inventory/stores-helpers';
import { applyLoadout } from 'app/loadout-drawer/loadout-apply';
import { editLoadout } from 'app/loadout-drawer/loadout-events';
import { Loadout } from 'app/loadout/loadout-types';
import D1CharacterStats from 'app/store-stats/D1CharacterStats';
import { D1CharacterStats } from 'app/store-stats/D1CharacterStats';
import { useThunkDispatch } from 'app/store/thunk-dispatch';
import { filterMap } from 'app/utils/collections';
import { BucketHashes } from 'data/d2/generated-enums';
import { useState } from 'react';
import { D1Item } from '../../inventory/item-types';
import { DimStore } from '../../inventory/store-types';
Expand All @@ -26,6 +28,7 @@ interface Props {
export default function GeneratedSet({ setType, store, activesets, excludeItem }: Props) {
const [collapsed, setCollapsed] = useState(true);
const dispatch = useThunkDispatch();
const subclass = findItemsByBucket(store, BucketHashes.Subclass).find((i) => i.equipped);

const toggle = () => setCollapsed((collapsed) => !collapsed);

Expand Down Expand Up @@ -64,7 +67,7 @@ export default function GeneratedSet({ setType, store, activesets, excludeItem }
</>
)}{' '}
<div className="dim-stats">
<D1CharacterStats stats={setType.tiers[activesets].stats} />
<D1CharacterStats stats={setType.tiers[activesets].stats} subclassHash={subclass?.hash} />
</div>
</div>
<div className="loadout-builder-section">
Expand Down
31 changes: 7 additions & 24 deletions src/app/destiny1/loadout-builder/calculate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { characterStatFromStatDef } from 'app/inventory/store/character-utils';
import { D1BucketHashes } from 'app/search/d1-known-values';
import { sumBy } from 'app/utils/collections';
import { infoLog } from 'app/utils/log';
Expand Down Expand Up @@ -93,9 +94,9 @@ export async function getSetBucketsStep(
}

let processedCount = 0;
const intellectIcon = defs.Stat.get(StatHashes.Intellect).icon;
const strengthIcon = defs.Stat.get(StatHashes.Strength).icon;
const disciplineIcon = defs.Stat.get(StatHashes.Discipline).icon;
const intellect = characterStatFromStatDef(defs.Stat.get(StatHashes.Intellect), 0);
const strength = characterStatFromStatDef(defs.Stat.get(StatHashes.Strength), 0);
const discipline = characterStatFromStatDef(defs.Stat.get(StatHashes.Discipline), 0);

for (const helm of helms) {
for (const gauntlet of gauntlets) {
Expand Down Expand Up @@ -123,27 +124,9 @@ export async function getSetBucketsStep(
[BucketHashes.Ghost]: ghost,
},
stats: {
[StatHashes.Intellect]: {
hash: StatHashes.Intellect,
value: 0,
name: 'Intellect',
description: '',
icon: intellectIcon,
},
[StatHashes.Discipline]: {
hash: StatHashes.Discipline,
value: 0,
name: 'Discipline',
description: '',
icon: disciplineIcon,
},
[StatHashes.Strength]: {
hash: StatHashes.Strength,
value: 0,
name: 'Strength',
description: '',
icon: strengthIcon,
},
[StatHashes.Intellect]: { ...intellect },
[StatHashes.Discipline]: { ...discipline },
[StatHashes.Strength]: { ...strength },
},
setHash: '',
includesVendorItems: false,
Expand Down
14 changes: 2 additions & 12 deletions src/app/inventory/store-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,11 @@ export interface DimCharacterStatChange {
export interface DimCharacterStat {
/** The DestinyStatDefinition hash for the stat. */
hash: number;
/** The localized name of the stat. */
name: string;
/** An icon associated with the stat. */
icon: string;
displayProperties: DestinyDisplayPropertiesDefinition;

/** The current value of the stat. */
value: number;

/** The localized description of the stat. */
description: string;

/** A localized description of this stat's effect. */
effect?: 'Grenade' | 'Melee' | 'Super';
/** Cooldown time for the associated ability. */
cooldown?: string;

/** How this stat exactly was calculated. */
breakdown?: DimCharacterStatChange[];
}
Expand Down
123 changes: 25 additions & 98 deletions src/app/inventory/store/character-utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { D1ManifestDefinitions } from 'app/destiny1/d1-definitions';
import { D1Character, D1StatLabel } from 'app/destiny1/d1-manifest-types';
import { D1Character, D1StatDefinition } from 'app/destiny1/d1-manifest-types';
import { ArmorTypes } from 'app/destiny1/loadout-builder/types';
import { D1BucketHashes } from 'app/search/d1-known-values';
import { BucketHashes, StatHashes } from 'data/d2/generated-enums';
import { BucketHashes } from 'data/d2/generated-enums';
import { DimCharacterStat } from '../store-types';

// Cooldowns
const cooldownsSuperA = ['5:00', '4:46', '4:31', '4:15', '3:58', '3:40'];
const cooldownsSuperB = ['5:30', '5:14', '4:57', '4:39', '4:20', '4:00'];
const cooldownsGrenade = ['1:00', '0:55', '0:49', '0:42', '0:34', '0:25'];
const cooldownsMelee = ['1:10', '1:04', '0:57', '0:49', '0:40', '0:29'];

/**
* D1 specific armor bonus calculation.
*/
// thanks to /u/iihavetoes for the bonuses at each level
// thanks to /u/tehdaw for the spreadsheet with bonuses
// https://docs.google.com/spreadsheets/d/1YyFDoHtaiOOeFoqc5Wc_WC2_qyQhBlZckQx5Jd4bJXI/edit?pref=2&pli=1#gid=0
Expand Down Expand Up @@ -70,109 +67,39 @@ export function getBonus(light: number, bucketHash: ArmorTypes): number {
}
}

export const statsWithTiers = [StatHashes.Discipline, StatHashes.Intellect, StatHashes.Strength];
export function getD1CharacterStatTiers(stat: DimCharacterStat) {
if (!statsWithTiers.includes(stat.hash)) {
return [];
}
const tiers = new Array<number>(5);
let remaining = stat.value;
for (let t = 0; t < 5; t++) {
remaining -= tiers[t] = remaining > 60 ? 60 : remaining;
}
return tiers;
}

const stats: D1StatLabel[] = [
'STAT_INTELLECT',
'STAT_DISCIPLINE',
'STAT_STRENGTH',
'STAT_ARMOR',
'STAT_RECOVERY',
'STAT_AGILITY',
];

/**
* Compute character-level stats (int, dis, str).
* Compute D1 character-level stats (int, dis, str).
*/
export function getCharacterStatsData(
defs: D1ManifestDefinitions,
data: D1Character['characterBase'],
) {
const ret: { [statHash: string]: DimCharacterStat } = {};
for (const statId of stats) {
for (const statId of ['STAT_DISCIPLINE', 'STAT_INTELLECT', 'STAT_STRENGTH'] as const) {
const rawStat = data.stats[statId];
if (!rawStat) {
continue;
}

const stat: DimCharacterStat = {
hash: rawStat.statHash,
value: rawStat.value,
name: '',
description: '',
icon: '',
};

switch (statId) {
case 'STAT_INTELLECT':
stat.effect = 'Super';
stat.icon = defs.Stat.get(StatHashes.Intellect).icon;
break;
case 'STAT_DISCIPLINE':
stat.effect = 'Grenade';
stat.icon = defs.Stat.get(StatHashes.Discipline).icon;
break;
case 'STAT_STRENGTH':
stat.effect = 'Melee';
stat.icon = defs.Stat.get(StatHashes.Strength).icon;
break;
default:
break;
}

const statDef = defs.Stat.get(stat.hash);
if (statDef) {
stat.name = statDef.statName; // localized name
stat.description = statDef.statDescription;
}

if (statsWithTiers.includes(stat.hash)) {
const tier = Math.floor(Math.min(300, stat.value) / 60);
if (data.peerView) {
stat.cooldown = getAbilityCooldown(data.peerView.equipment[0].itemHash, statId, tier);
}
}

ret[stat.hash] = stat;
const statDef = defs.Stat.get(rawStat.statHash);
ret[statDef.hash] = characterStatFromStatDef(statDef, rawStat.value);
}
return ret;
}

// following code is from https://github.com/DestinyTrialsReport
function getAbilityCooldown(subclass: number, ability: string, tier: number) {
switch (ability) {
case 'STAT_INTELLECT':
switch (subclass) {
case 2007186000: // Defender
case 4143670656: // Nightstalker
case 2455559914: // Striker
case 3658182170: // Sunsinger
return cooldownsSuperA[tier];
default:
return cooldownsSuperB[tier];
}
case 'STAT_DISCIPLINE':
return cooldownsGrenade[tier];
case 'STAT_STRENGTH':
switch (subclass) {
case 4143670656: // Nightstalker
case 1716862031: // Gunslinger
return cooldownsMelee[tier];
default:
return cooldownsGrenade[tier];
}
default:
return '-:--';
}
export function characterStatFromStatDef(
statDef: D1StatDefinition,
value: number,
): DimCharacterStat {
return {
hash: statDef.statHash,
displayProperties: {
name: statDef.statName,
description: statDef.statDescription,
icon: statDef.icon,
hasIcon: Boolean(statDef.icon),
highResIcon: '',
iconSequences: [],
},
value,
};
}
4 changes: 1 addition & 3 deletions src/app/inventory/store/d2-store-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,8 @@ export function getCharacterStatsData(
const value = stats[statHash] || 0;
const stat: DimCharacterStat = {
hash: statHash,
name: def.displayProperties.name,
description: def.displayProperties.description,
displayProperties: def.displayProperties,
value,
icon: def.displayProperties.icon,
};
ret[statHash] = stat;
}
Expand Down
3 changes: 1 addition & 2 deletions src/app/loadout-builder/filter/StatConstraintEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,7 @@ function StatTierBar({
stat={{
hash: statHash,
value: tierNum * 10,
name: statDef.displayProperties.name,
description: statDef.displayProperties.description,
displayProperties: statDef.displayProperties,
}}
equippedHashes={equippedHashes}
/>
Expand Down
3 changes: 1 addition & 2 deletions src/app/loadout-builder/generated-sets/SetStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ export function SetStats({
<StatTooltip
stat={{
hash: statHash,
name: statDef.displayProperties.name,
displayProperties: statDef.displayProperties,
value,
description: statDef.displayProperties.description,
breakdown: getStatsBreakdown()[statHash].breakdown,
}}
equippedHashes={equippedHashes}
Expand Down
7 changes: 2 additions & 5 deletions src/app/loadout-drawer/loadout-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,8 @@ export function getLoadoutStats(

// Construct map of stat hash to DimCharacterStat
const stats: { [hash: number | string]: DimCharacterStat } = {};
for (const {
hash,
displayProperties: { description, icon, name },
} of statDefs) {
stats[hash] = { hash, description, icon: icon, name, value: 0, breakdown: [] };
for (const { hash, displayProperties } of statDefs) {
stats[hash] = { hash, displayProperties, value: 0, breakdown: [] };
}

// Sum the items stats into the stats
Expand Down
4 changes: 2 additions & 2 deletions src/app/store-stats/CharacterStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ function CharacterStats({
className={clsx('stat', {
boostedValue: stat.breakdown?.some((change) => change.source === 'runtimeEffect'),
})}
aria-label={`${stat.name} ${stat.value}`}
aria-label={`${stat.displayProperties.name} ${stat.value}`}
role="group"
>
<BungieImage src={stat.icon} alt={stat.name} />
<BungieImage src={stat.displayProperties.icon} alt={stat.displayProperties.name} />
<div>{stat.value}</div>
</div>
</PressTip>
Expand Down
Loading

0 comments on commit 5193e54

Please sign in to comment.