Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Rorschach7552 committed Sep 19, 2024
2 parents 7ae4513 + 9bdb967 commit c0f2e58
Show file tree
Hide file tree
Showing 17 changed files with 199 additions and 74 deletions.
6 changes: 5 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"printWidth": 100,
"singleQuote": true
"singleQuote": true,

"importOrder": ["^components/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}
2 changes: 1 addition & 1 deletion src/app/routes/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useMemo } from 'react';
import { Box, styled } from '@mui/system';
import { styled } from '@mui/system';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '../../store';
import { generatePermutations } from '../../features/armor-optimization/generate-permutations';
Expand Down
21 changes: 7 additions & 14 deletions src/features/armor-mods/components/ArmorConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ArmorIcon from '../../../components/ArmorIcon';
import { updateLoadoutArmorMods, updateRequiredStatMods } from '../../../store/LoadoutReducer';
import { armorMods, DestinyArmor } from '../../../types/d2l-types';
import ArmorModSelector from './ArmorModSelector';
import { getModsBySlot } from '../mod-utils';
import { calculateAvailableEnergy, getModsBySlot } from '../mod-utils';
import { ManifestArmorMod, ManifestArmorStatMod } from '../../../types/manifest-types';
import { Alert, Grid, Fade, Snackbar, SnackbarCloseReason, CircularProgress } from '@mui/material';
import { RootState, store } from '../../../store';
Expand Down Expand Up @@ -43,13 +43,6 @@ const ArmorConfig: React.FC<ArmorConfigProps> = ({ armor, statMods, artificeMods
);
};

const calculateAvailableEnergy = (currentSlot: number) => {
const totalEnergyCost = selectedMods.reduce((total, mod, index) => {
return index !== currentSlot ? total + mod.energyCost : total;
}, 0);
return 10 - totalEnergyCost; // Assuming max energy is 10
};

const onSelectMod = async (mod: ManifestArmorMod | ManifestArmorStatMod, slot: number) => {
if (selectedMods[slot].itemHash === mod.itemHash) return;

Expand All @@ -69,7 +62,7 @@ const ArmorConfig: React.FC<ArmorConfigProps> = ({ armor, statMods, artificeMods
return;
}

const availableEnergy = calculateAvailableEnergy(slot);
const availableEnergy = calculateAvailableEnergy(slot, selectedMods);
if (mod.energyCost > availableEnergy) {
setSnackPack((prev) => [
...prev,
Expand Down Expand Up @@ -155,31 +148,31 @@ const ArmorConfig: React.FC<ArmorConfigProps> = ({ armor, statMods, artificeMods
selected={selectedMods[0]}
mods={statMods}
onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => onSelectMod(mod, 0)}
availableEnergy={calculateAvailableEnergy(0)}
availableEnergy={calculateAvailableEnergy(0, selectedMods)}
/>
</Grid>
<Grid item md={1}>
<ArmorModSelector
selected={selectedMods[1]}
mods={armorMods}
onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => onSelectMod(mod, 1)}
availableEnergy={calculateAvailableEnergy(1)}
availableEnergy={calculateAvailableEnergy(1, selectedMods)}
/>
</Grid>
<Grid item md={1}>
<ArmorModSelector
selected={selectedMods[2]}
mods={armorMods}
onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => onSelectMod(mod, 2)}
availableEnergy={calculateAvailableEnergy(2)}
availableEnergy={calculateAvailableEnergy(2, selectedMods)}
/>
</Grid>
<Grid item md={1}>
<ArmorModSelector
selected={selectedMods[3]}
mods={armorMods}
onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => onSelectMod(mod, 3)}
availableEnergy={calculateAvailableEnergy(3)}
availableEnergy={calculateAvailableEnergy(3, selectedMods)}
/>
</Grid>
{armor.artifice === true ? (
Expand All @@ -190,7 +183,7 @@ const ArmorConfig: React.FC<ArmorConfigProps> = ({ armor, statMods, artificeMods
onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) =>
onSelectMod(mod, 4)
}
availableEnergy={calculateAvailableEnergy(4)}
availableEnergy={calculateAvailableEnergy(4, selectedMods)}
/>
</Grid>
) : (
Expand Down
2 changes: 1 addition & 1 deletion src/features/armor-mods/components/ArmorModSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from 'react';
import { Box } from '@mui/system';
import { ManifestArmorMod, ManifestArmorStatMod } from '../../../types/manifest-types';
import { Tooltip, IconButton, styled } from '@mui/material';
import { IconButton, styled } from '@mui/material';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import HoverCard from '../../../components/HoverCard';
Expand Down
20 changes: 20 additions & 0 deletions src/features/armor-mods/mod-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { armorMods } from '../../types/d2l-types';
import { Dispatch, UnknownAction } from 'redux';
import { updateLoadoutArmorMods, updateRequiredStatMods } from '../../store/LoadoutReducer';

/**
* Get armor mods by armor slot
* @returns armor mods array
*/
export async function getModsBySlot(slot: string): Promise<ManifestArmorMod[]> {
const slotMods = await db.manifestArmorModDef
.where('category')
Expand All @@ -32,6 +36,9 @@ export async function getModsBySlot(slot: string): Promise<ManifestArmorMod[]> {
return slotMods;
}

/**
* Auto equips a mod into the loadout config
*/
export function autoEquipStatMod(
mod: ManifestArmorStatMod,
dispatch: Dispatch<UnknownAction>
Expand Down Expand Up @@ -65,3 +72,16 @@ export function autoEquipStatMod(

return false;
}

/**
* Calculates the available energy out of 10 with equipped mods.
*/
export function calculateAvailableEnergy(
currentSlot: number,
selectedMods: (ManifestArmorMod | ManifestArmorStatMod)[]
): number {
const totalEnergyCost = selectedMods.reduce((total, mod, index) => {
return index !== currentSlot ? total + mod.energyCost : total;
}, 0);
return 10 - totalEnergyCost; // Assuming max energy is 10
}
12 changes: 0 additions & 12 deletions src/features/armor-optimization/generate-permutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,18 +153,6 @@ export function generatePermutations(

const totalSum = Object.values(totalStats).reduce((a, b) => a + b, 0);

const baseStats = modifiedPermutation.reduce(
(sum, item) =>
sum +
item.mobility +
item.resilience +
item.recovery +
item.discipline +
item.intellect +
item.strength,
0
);

if (heap.size() < 30000) {
heap.push(modifiedPermutation);
} else {
Expand Down
19 changes: 6 additions & 13 deletions src/features/loadouts/components/ShareLoadout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../../../store/index';
import { useSelector } from 'react-redux';
import { RootState } from '../../../store/index';
import { createSelector } from '@reduxjs/toolkit';
import { encodeLoadout, decodeLoadout } from '../util/loadout-encoder';
import { encodeLoadout } from '../util/loadout-encoder';
import {
Button,
TextField,
Box,
List,
ListItem,
ListItemIcon,
ListItemText,
Expand All @@ -26,6 +24,7 @@ import { CharacterClass, StatName } from '../../../types/d2l-types';
import { SharedLoadoutDto } from '../types';
import { D2LButton } from '../../../components/D2LButton';
import { statIcons } from '../../../util/constants';
import { copyToClipBoard } from '../../../util/app-utils';

const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
Expand Down Expand Up @@ -183,6 +182,7 @@ const StatPriorityList: React.FC<{
</StatListContainer>
);
});

const ShareLoadout: React.FC = () => {
const loadoutState = useSelector(selectLoadoutState);
const selectedCharacterClass = useSelector(selectSelectedCharacterClass);
Expand Down Expand Up @@ -231,13 +231,6 @@ const ShareLoadout: React.FC = () => {
const encodedData = encodeLoadout(dataToShare);
const shareableLink = `https://d2loadouts.com/?d=${encodedData}`;
setShareLink(shareableLink);
console.log('Generated link:', shareableLink);
};

const copyToClipboard = () => {
navigator.clipboard.writeText(shareLink).then(() => {
alert('Link copied to clipboard!');
});
};

return (
Expand Down Expand Up @@ -282,7 +275,7 @@ const ShareLoadout: React.FC = () => {
}}
/>
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<D2LButton onClick={copyToClipboard} sx={{ mr: 1 }}>
<D2LButton onClick={() => copyToClipBoard(shareLink)} sx={{ mr: 1 }}>
Copy Link
</D2LButton>
</Box>
Expand Down
11 changes: 11 additions & 0 deletions src/features/loadouts/util/armor-equipper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { ManifestArmorStatMod, ManifestArmorMod } from '../../../types/manifest-
import { STATUS } from '../constants';
import { Equipper } from './equipper';

/**
* Builder for equipping armor and mods and creating equip result array
*/
export class ArmorEquipper extends Equipper {
private inventorySlots: { [key: string]: any[] };

Expand Down Expand Up @@ -64,6 +67,10 @@ export class ArmorEquipper extends Equipper {
}
}

/**
* Equips an armor piece on the set character, transferring items between vault and character if necessary
* @param armor the armor to equip
*/
public async equipArmor(armor: DestinyArmor): Promise<void> {
const result = {
status: STATUS.SUCCESS,
Expand Down Expand Up @@ -121,6 +128,10 @@ export class ArmorEquipper extends Equipper {
this.result.push(result);
}

/**
* Equips armor mods onto an armor piece
* @param mods mods to equip
*/
public async equipArmorMods(
mods: [string, ManifestArmorMod | ManifestArmorStatMod][]
): Promise<void> {
Expand Down
14 changes: 14 additions & 0 deletions src/features/loadouts/util/equipper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { EquipResult } from '../types';

/**
* Base builder for equipping objects and building equip results
*/
export abstract class Equipper {
protected characterId: number;

Expand All @@ -10,16 +13,27 @@ export abstract class Equipper {
this.result = [];
}

/**
* Set the equipping character
* @param characterId character to equip on
*/
public setCharacter(characterId: number): void {
this.characterId = characterId;
}

/**
* Gets the equip results
* @returns the equip result array
*/
public getResult(): EquipResult[] {
const temp = this.result;
this.reset();
return temp;
}

/**
* Resets the results
*/
public reset(): void {
this.result = [];
}
Expand Down
31 changes: 0 additions & 31 deletions src/features/loadouts/util/loadout-encoder.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,6 @@
import { CharacterClass } from '../../../types/d2l-types';
import { SharedLoadoutDto } from '../types';

/**
* This file provides functions to encode and decode `LoadoutData` objects
* into compact, URL-safe strings and back. It uses a custom base64-like
* character set for encoding numbers, ensuring the result is URL-safe.
*
* Functions:
* - `encodeNumber(num)`: Encodes a number into a custom base64 string.
* - `decodeNumber(str)`: Decodes a custom base64 string back into a number.
* - `encodeLoadout(loadout)`: Converts a `LoadoutData` object into a compact string.
* - `decodeLoadout(encoded)`: Reconstructs a `LoadoutData` object from an encoded string.
*
* Example:
* Given the number 1234, `encodeNumber(1234)` returns "JI".
* Decoding "JI" with `decodeNumber("JI")` returns 1234.
*
* Encoding a simple `LoadoutData` object:
* ```
* const loadout = {
* mods: { helmet: [1], gauntlets: [2], chest: [3], legs: [4] },
* subclass: { super: 5, aspects: [6], fragments: [7], classAbility: 8,
* meleeAbility: 9, movementAbility: 10, grenade: 11 },
* selectedExoticItemHash: '12',
* selectedValues: { mobility: 13, resilience: 14 },
* statPriority: ['mobility', 'resilience'],
* characterClass: 'Titan'
* };
* ```
* `encodeLoadout(loadout)` might return: "1|2|3|4~5|6|7|8|9|A|B~C~mob:D,res:E~mobres~Titan".
* Decoding this string with `decodeLoadout` returns the original `LoadoutData` object.
*/

// We'll use a custom base64 encoding to make the strings URL-safe
const base64Chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';

Expand Down
41 changes: 41 additions & 0 deletions src/features/loadouts/util/loadout-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import { EquipResult, setState } from '../types';
import { ArmorEquipper } from './armor-equipper';
import { SubclassEquipper } from './subclass-equipper';

/**
* Saves a loadout from equipped items to Destiny 2
* @param characterId character for the loadout
* @param colorHash loadout color identifier hash
* @param iconHash loadout icon identifier hash
* @param loadoutIndex loadout index to replace
* @param nameHash loadout name identifierhash
*/
export async function createInGameLoadout(
characterId: string,
colorHash: number,
Expand All @@ -28,6 +36,13 @@ export async function createInGameLoadout(
await snapShotLoadoutRequest(characterId, colorHash, iconHash, loadoutIndex, nameHash);
}

/**
* Equips a loadout, handling armor first then subclass, setting state as it goes.
* @param loadout the loadout to equip
* @param setProcessing the setState for processing object
* @param setEquipStep the setState for equipping item
* @param setResults the setState for results
*/
export async function equipLoadout(
loadout: Loadout,
setProcessing: setState,
Expand Down Expand Up @@ -71,6 +86,17 @@ export async function equipLoadout(
);
}

/**
* Equips armor piece and it's mods, processing exotics last
* @param i armor index
* @param loadout loadout to equip
* @param equipper armor equipper to use
* @param tempEquipped temp equipped array
* @param tempResults temp results array
* @param setProcessing the setState for processing object
* @param setEquipStep the setState for equipping item
* @param setResults the setState for results
*/
async function processArmor(
i: number,
loadout: Loadout,
Expand Down Expand Up @@ -119,12 +145,27 @@ async function processArmor(
}
}

/**
* Sorts a mod dicationary
* @param mods mods to sort
* @returns sorted mods array
*/
function sortMods(mods: {
[key: number]: ManifestArmorMod | ManifestArmorStatMod;
}): [string, ManifestArmorMod | ManifestArmorStatMod][] {
return Object.entries(mods).sort(([, a], [, b]) => b.energyCost - a.energyCost);
}

/**
* Equips subclass config
* @param subclassConfig subclass config to equip
* @param equipper subclass equipper to use
* @param tempEquipped temp equipped array
* @param tempResults temp results array
* @param setProcessing the setState for processing object
* @param setEquipStep the setState for equipping item
* @param setResults the setState for results
*/
async function processSubclass(
subclassConfig: SubclassConfig,
equipper: SubclassEquipper,
Expand Down
Loading

0 comments on commit c0f2e58

Please sign in to comment.