Skip to content

Commit

Permalink
Merge pull request #61 from dragoni7/save-loadout-in-game-menu
Browse files Browse the repository at this point in the history
Save loadout in game menu
  • Loading branch information
dragoni7 authored Sep 12, 2024
2 parents 6161b0a + 25dd32b commit 5a7be01
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/components/LoadoutCustomization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EquipLoadout from '../features/loadouts/components/EquipLoadout';
import AbilitiesModification from '../features/subclass/AbilitiesModification';
import ShareLoadout from '../features/loadouts/components/ShareLoadout';
import { SubclassConfig } from '../types/d2l-types';
import SaveLoadout from '../features/loadouts/components/SaveLoadout';

interface LoadoutCustomizationProps {
onBackClick: () => void;
Expand Down
1 change: 0 additions & 1 deletion src/features/armor-mods/components/RequiredMod.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from 'react';
import { ManifestArmorStatMod } from '../../../types/manifest-types';
import { Tooltip, styled } from '@mui/material';
import { autoEquipStatMod } from '../mod-utils';
Expand Down
3 changes: 2 additions & 1 deletion src/features/loadouts/components/EquipLoadout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import LoadingBorder from './LoadingBorder';
import FadeIn from './FadeIn';
import { equipLoadout } from '../util/loadout-utils';
import { TransitionProps } from '@mui/material/transitions';
import SaveLoadout from './SaveLoadout';

const StyledTitle = styled(Typography)(({ theme }) => ({
paddingBottom: theme.spacing(1),
Expand Down Expand Up @@ -268,7 +269,7 @@ const EquipLoadout: React.FC = () => {
</Grid>
<Grid item md={4}>
<FadeIn delay={600}>
<Button>Save in-game</Button>
<SaveLoadout />
</FadeIn>
</Grid>
</>
Expand Down
179 changes: 179 additions & 0 deletions src/features/loadouts/components/SaveLoadout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
Button,
Drawer,
Grid,
Box,
Autocomplete,
TextField,
Select,
FormControl,
InputLabel,
ImageList,
styled,
} from '@mui/material';
import { useState } from 'react';
import useLoadoutIdentifiers from '../hooks/use-loadout-identifiers';
import {
ManifestLoadoutColor,
ManifestLoadoutIcon,
ManifestLoadoutName,
} from '../../../types/manifest-types';
import useSelectedCharacterLoadouts from '../hooks/use-selected-character-loadouts';
import { snapShotLoadoutRequest } from '../../../lib/bungie_api/requests';
import { store } from '../../../store';

const LoadoutSlot = styled('img')(({ theme }) => ({
backgroundSize: 'cover',
backgroundPosition: 'center',
width: '51%',
height: 'auto',
border: '2px outset transparent',
'&:hover': { border: '2px solid blue' },
}));

export default function SaveLoadout() {
const [loadoutDrawerOpen, setLoadoutDrawerOpen] = useState<boolean>(false);
const [loadoutName, setLoadoutName] = useState<ManifestLoadoutName | null>(null);
const [loadoutColor, setLoadoutColor] = useState<ManifestLoadoutColor | null>(null);
const [loadoutIcon, setLoadoutIcon] = useState<ManifestLoadoutIcon | null>(null);
const [identifiersSet, setIdentifiersSet] = useState<boolean>(false);

const loadoutIdentifiers = useLoadoutIdentifiers();
const loadouts = useSelectedCharacterLoadouts();

function handleBackClick() {
setLoadoutDrawerOpen(false);
setLoadoutName(null);
setLoadoutColor(null);
setLoadoutIcon(null);
}

const SetIdentifiersDrawer = (
<Grid container alignItems="center" textAlign="center" rowGap={3} paddingX={4} paddingY={3}>
<Grid item md={12}>
SET IDENTIFIERS
</Grid>
<Grid item md={12}>
<Autocomplete
disablePortal
id="loadout-names"
value={loadoutName}
onChange={(event, newValue) => setLoadoutName(newValue)}
options={loadoutIdentifiers.loadoutNames}
getOptionLabel={(option) => option.name}
renderInput={(params) => <TextField {...params} label="NAME" />}
/>
</Grid>
<Grid item md={12}>
<FormControl fullWidth>
<InputLabel id="loadout-colors-label">COLOR</InputLabel>
<Select
labelId="loadout-colors-label"
id="loadout-colors"
label="COLOR"
value={loadoutColor}
renderValue={(selected) => <img src={selected?.imagePath} width="20%" height="auto" />}
>
<ImageList cols={4}>
{loadoutIdentifiers.loadoutColors.map((color) => (
<img
src={color.imagePath}
width="60%"
height="auto"
onClick={() => setLoadoutColor(color)}
/>
))}
</ImageList>
</Select>
</FormControl>
</Grid>
<Grid item md={12}>
<FormControl fullWidth>
<InputLabel id="loadout-icons-label">ICON</InputLabel>
<Select
labelId="loadout-icons-label"
id="loadout-icons"
label="ICON"
value={loadoutIcon}
renderValue={(selected) => <img src={selected?.imagePath} width="20%" height="auto" />}
>
<ImageList cols={4}>
{loadoutIdentifiers.loadoutIcons.map((icon) => (
<img
src={icon.imagePath}
width="60%"
height="auto"
onClick={() => setLoadoutIcon(icon)}
/>
))}
</ImageList>
</Select>
</FormControl>
</Grid>
<Grid item md={6}>
<Button onClick={handleBackClick}>BACK</Button>
</Grid>
<Grid item md={6}>
<Button
disabled={loadoutName === null && loadoutColor === null && loadoutIcon === null}
onClick={() => setIdentifiersSet(true)}
>
NEXT
</Button>
</Grid>
</Grid>
);

const SelectLoadoutSlotDrawer = (
<Grid container alignItems="center" textAlign="center" rowGap={2} paddingX={4} paddingY={3}>
<Grid item md={12}>
SELECT SLOT TO OVERWRITE
</Grid>
{loadouts?.map((loadout, index) => (
<Grid item md={6}>
<LoadoutSlot
onClick={async () => {
const characterId = store.getState().profile.selectedCharacter?.id;

if (characterId && loadoutColor && loadoutIcon && loadoutName)
await snapShotLoadoutRequest(
String(characterId),
loadoutColor?.hash,
loadoutIcon?.hash,
index,
loadoutName?.hash
);

setIdentifiersSet(false);
setLoadoutDrawerOpen(false);
}}
src={
loadoutIdentifiers.loadoutIcons.find((icon) => icon.hash === loadout.iconHash)
?.imagePath
}
style={{
backgroundImage: `url(${
loadoutIdentifiers.loadoutColors.find((color) => color.hash === loadout.colorHash)
?.imagePath
})`,
}}
/>
</Grid>
))}
<Grid item md={6}>
<Button onClick={() => setIdentifiersSet(false)}>BACK</Button>
</Grid>
</Grid>
);

return (
<>
<Button onClick={() => setLoadoutDrawerOpen(true)}>SAVE IN-GAME</Button>
<Drawer open={loadoutDrawerOpen} anchor="right">
<Box sx={{ width: '24vw' }}>
{identifiersSet ? SelectLoadoutSlotDrawer : SetIdentifiersDrawer}
</Box>
</Drawer>
</>
);
}
24 changes: 24 additions & 0 deletions src/features/loadouts/hooks/use-loadout-identifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';
import { db } from '../../../store/db';
import {
ManifestLoadoutColor,
ManifestLoadoutIcon,
ManifestLoadoutName,
} from '../../../types/manifest-types';

export default function useLoadoutIdentifiers() {
const [loadoutColors, setLoadoutColors] = useState<ManifestLoadoutColor[]>([]);
const [loadoutNames, setLoadoutNames] = useState<ManifestLoadoutName[]>([]);
const [loadoutIcons, setLoadoutIcons] = useState<ManifestLoadoutIcon[]>([]);

useEffect(() => {
const gatherIdentifiers = async () => {
setLoadoutColors(await db.manifestLoadoutColorDef.toArray());
setLoadoutIcons(await db.manifestLoadoutIconDef.toArray());
setLoadoutNames(await db.manifestLoadoutNameDef.toArray());
};
gatherIdentifiers().catch(console.error);
}, []);

return { loadoutNames, loadoutColors, loadoutIcons };
}
13 changes: 13 additions & 0 deletions src/features/loadouts/hooks/use-selected-character-loadouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState, useEffect } from 'react';
import { DestinyLoadout } from '../../../types/d2l-types';
import { store } from '../../../store';

export default function useSelectedCharacterLoadouts() {
const [loadouts, setLoadouts] = useState<DestinyLoadout[] | undefined>(undefined);

useEffect(() => {
setLoadouts(store.getState().profile.selectedCharacter?.loadouts);
}, []);

return loadouts;
}
8 changes: 0 additions & 8 deletions src/features/loadouts/util/loadout-utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { ARMOR_ARRAY, DAMAGE_TYPE } from '../../../lib/bungie_api/constants';
import { snapShotLoadoutRequest } from '../../../lib/bungie_api/requests';
import { db } from '../../../store/db';
import {
armor,
armorMods,
Character,
DestinyArmor,
FilteredPermutation,
Loadout,
StatName,
Subclass,
SubclassConfig,
} from '../../../types/d2l-types';
Expand All @@ -19,9 +14,6 @@ import {
ManifestPlug,
ManifestStatPlug,
} from '../../../types/manifest-types';
import { filterPermutations } from '../../armor-optimization/filter-permutations';
import { generatePermutations } from '../../armor-optimization/generate-permutations';
import { DecodedLoadoutInfo } from '../components/findMatchingArmorSet';
import { EquipResult, setState } from '../types';
import { ArmorEquipper } from './armor-equipper';
import { SubclassEquipper } from './subclass-equipper';
Expand Down
13 changes: 13 additions & 0 deletions src/features/profile/destiny-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function getProfileData(): Promise<ProfileData> {
const characterEquipment = response.data.Response.characterEquipment.data;
const characterData = response.data.Response.characters.data;
const profileCollectibles = response.data.Response.profileCollectibles.data.collectibles;
const characterLoadouts = response.data.Response.characterLoadouts.data;

for (const key in characterData) {
const characterClass = getCharacterClass(characterData[key].classHash);
Expand All @@ -54,8 +55,20 @@ export async function getProfileData(): Promise<ProfileData> {
},
subclasses: {},
exoticClassCombos: [],
loadouts: [],
};

// gather character's loadouts
for (const loadout of characterLoadouts[character.id].loadouts) {
character.loadouts.push({
colorHash: loadout.colorHash,
iconHash: loadout.iconHash,
nameHash: loadout.nameHash,
armor: loadout.items.slice(3, 8),
subclass: loadout.items[8],
});
}

// iterate character's equipped items
for (const item of characterEquipment[key].items) {
switch (item.bucketHash) {
Expand Down
51 changes: 51 additions & 0 deletions src/lib/bungie_api/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,57 @@ export async function updateManifest() {
}
}
}

const loadoutColorComponent =
response.data.Response.jsonWorldComponentContentPaths.en['DestinyLoadoutColorDefinition'];

const loadoutColorResponse = await getManifestComponentRequest(loadoutColorComponent);

if (loadoutColorResponse) {
for (const hash in loadoutColorResponse.data) {
const current = loadoutColorResponse.data[hash];

await db.manifestLoadoutColorDef.add({
imagePath: urlPrefix + current.colorImagePath,
hash: current.hash,
index: current.index,
});
}
}

const loadoutIconComponent =
response.data.Response.jsonWorldComponentContentPaths.en['DestinyLoadoutIconDefinition'];

const loadoutIconResponse = await getManifestComponentRequest(loadoutIconComponent);

if (loadoutIconResponse) {
for (const hash in loadoutIconResponse.data) {
const current = loadoutIconResponse.data[hash];

await db.manifestLoadoutIconDef.add({
imagePath: urlPrefix + current.iconImagePath,
hash: current.hash,
index: current.index,
});
}
}

const loadoutNameComponent =
response.data.Response.jsonWorldComponentContentPaths.en['DestinyLoadoutNameDefinition'];

const loadoutNameResponse = await getManifestComponentRequest(loadoutNameComponent);

if (loadoutNameResponse) {
for (const hash in loadoutNameResponse.data) {
const current = loadoutNameResponse.data[hash];

await db.manifestLoadoutNameDef.add({
name: current.name,
hash: current.hash,
index: current.index,
});
}
}
}
} else {
throw new Error('Error retrieving manifest');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/bungie_api/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function getProfileDataRequest(): Promise<AxiosResponse<any, any>> {
const destinyMembership = store.getState().destinyMembership.membership;

return _get(
`/Platform/Destiny2/${destinyMembership.membershipType}/Profile/${destinyMembership.membershipId}/?components=102,200,201,300,205,302,304,305,800`,
`/Platform/Destiny2/${destinyMembership.membershipType}/Profile/${destinyMembership.membershipId}/?components=102,200,201,300,205,206,302,304,305,800`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Expand Down
9 changes: 9 additions & 0 deletions src/store/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
ManifestAspect,
ManifestStatPlug,
ManifestEntry,
ManifestLoadoutColor,
ManifestLoadoutIcon,
ManifestLoadoutName,
} from '../types/manifest-types';

const db = new Dexie('manifestDb') as Dexie & {
Expand All @@ -25,6 +28,9 @@ const db = new Dexie('manifestDb') as Dexie & {
manifestSubclassAspectsDef: EntityTable<ManifestAspect, 'itemHash'>;
manifestSubclassFragmentsDef: EntityTable<ManifestStatPlug, 'itemHash'>;
manifestSubclass: EntityTable<ManifestSubclass, 'itemHash'>;
manifestLoadoutColorDef: EntityTable<ManifestLoadoutColor, 'hash'>;
manifestLoadoutIconDef: EntityTable<ManifestLoadoutIcon, 'hash'>;
manifestLoadoutNameDef: EntityTable<ManifestLoadoutName, 'hash'>;
};

db.version(1).stores({
Expand All @@ -43,6 +49,9 @@ db.version(1).stores({
'itemHash, name, icon, secondaryIcon, category, perks, isOwned, mobilityMod, resilienceMod, recoveryMod, disciplineMod, intellectMod, strengthMod',
manifestSubclass:
'itemHash, name, icon, secondaryIcon, screenshot, flavorText, damageType, class, isOwned',
manifestLoadoutColorDef: 'hash, imagePath, index',
manifestLoadoutIconDef: 'hash, imagePath, index',
manifestLoadoutNameDef: 'hash, name, index',
});

export { db };
Loading

0 comments on commit 5a7be01

Please sign in to comment.