Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ImportExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const version = 'SSRANDO-TRACKER-NG-V2';

export interface ExportState {
version: string;
state: TrackerState;
state: Partial<TrackerState>;
logicBranch: RemoteReference | undefined;
}

Expand Down
9 changes: 0 additions & 9 deletions src/data/prettyItemNames.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,6 @@
"1": "Slingshot",
"2": "Scattershot"
},
"Gratitude Crystal": {
"5": "5 Gratitude Crystals",
"10": "10 Gratitude Crystals",
"30": "30 Gratitude Crystals",
"40": "40 Gratitude Crystals",
"50": "50 Gratitude Crystals",
"70": "70 Gratitude Crystals",
"80": "80 Gratitude Crystals"
},
"Progressive Wallet": {
"1": "Medium Wallet",
"2": "Big Wallet",
Expand Down
19 changes: 19 additions & 0 deletions src/locationTracker/CrystalAmountsChooser.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.chooser {
padding: 4px;
}

.inputs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(45px, 1fr));
gap: 4px;

align-items: center;

> div {
display: flex;
> input {
flex: 1;
min-width: 0;
}
}
}
5 changes: 5 additions & 0 deletions src/locationTracker/CrystalAmountsChooser.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare const classNames: {
readonly chooser: 'chooser';
readonly inputs: 'inputs';
};
export = classNames;
56 changes: 56 additions & 0 deletions src/locationTracker/CrystalAmountsChooser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { range } from 'es-toolkit';
import { useState } from 'react';
import { useSelector } from 'react-redux';
import { numBatreauxRewardLevels } from '../logic/ThingsThatWouldBeNiceToHaveInTheDump';
import { requiredCrystalCountsSelector } from '../tracker/Selectors';
import { setRequiredCrystalCounts } from '../tracker/Slice';
import { useDeferredReduxUpdate } from '../utils/React';
import styles from './CrystalAmountsChooser.module.css';

export function CrystalAmountsChooser() {
const reduxCounts = useSelector(requiredCrystalCountsSelector);
const [crystalCounts, setCrystalCounts, flushCrystalCounts] =
useDeferredReduxUpdate(
reduxCounts.map((i) => i.toString(10)),
(counts) => {
const newCounts = [...reduxCounts];
for (let i = 0; i < numBatreauxRewardLevels; i++) {
const num = parseInt(counts[i], 10);
newCounts[i] = num;
}
return setRequiredCrystalCounts(newCounts);
},
);

// Pull new state from Redux if it changes for some other reason
const [prevCounts, setPrevCounts] = useState(reduxCounts);
if (reduxCounts !== prevCounts) {
setCrystalCounts(reduxCounts.map((i) => i.toString(10)));
setPrevCounts(reduxCounts);
}

const updateCount = (val: string, idx: number) => {
const newCounts = [...crystalCounts];
newCounts[idx] = val;
setCrystalCounts(newCounts);
};

return (
<div className={styles.chooser}>
<div>Enter Crystal Counts:</div>
<div className={styles.inputs}>
{range(numBatreauxRewardLevels).map((level) => (
<div key={level}>
<input
className="tracker-input"
type="text"
value={crystalCounts[level]}
onChange={(e) => updateCount(e.target.value, level)}
onBlur={flushCrystalCounts}
/>
</div>
))}
</div>
</div>
);
}
11 changes: 11 additions & 0 deletions src/locationTracker/Locations.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';
import { useSelector } from 'react-redux';
import type { HintRegion } from '../logic/Locations';
import { settingSelector } from '../tracker/Selectors';
import { CrystalAmountsChooser } from './CrystalAmountsChooser';
import LocationGroup from './LocationGroup';

export function Locations({
Expand All @@ -11,8 +14,16 @@ export function Locations({
hintRegion: HintRegion<string>;
onChooseEntrance: (exitId: string) => void;
}) {
const randomCrystals =
useSelector(settingSelector('batreaux-counts')) === 'Random';
return (
<>
{randomCrystals && hintRegion.name === "Batreaux's House" && (
<>
<CrystalAmountsChooser />
<hr />
</>
)}
<LocationGroup
wide={wide}
onChooseEntrance={onChooseEntrance}
Expand Down
15 changes: 14 additions & 1 deletion src/locationTracker/mapTracker/MapMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
import { decodeHint } from '../../hints/Hints';
import { hintsToSubmarkers } from '../../hints/HintsParser';
import type { RootState } from '../../store/Store';
import { areaHintSelector, areasSelector } from '../../tracker/Selectors';
import {
areaHintSelector,
areasSelector,
stillNeedToEnterCrystalCountsSelector,
} from '../../tracker/Selectors';
import HintDescription from '../HintsDescription';
import type { LocationGroupContextMenuProps } from '../LocationGroupContextMenu';
import { useContextMenu } from '../context-menu';
Expand Down Expand Up @@ -58,6 +62,13 @@ function MapMarker({
if (dragPreviewHint && isOver) {
hints = [...hints, dragPreviewHint];
}
const needsEnterBatCounts = useSelector(
stillNeedToEnterCrystalCountsSelector,
);
const showEnterBatCounts =
needsEnterBatCounts &&
title === "Batreaux's House" &&
data.checks.numAccessible === 0;

const tooltip = (
<center>
Expand All @@ -67,6 +78,7 @@ function MapMarker({
{hints.map((hint, idx) => (
<HintDescription key={idx} hint={decodeHint(hint)} />
))}
{showEnterBatCounts && 'Click to enter required Gratitude Crystals'}
</center>
);

Expand Down Expand Up @@ -100,6 +112,7 @@ function MapMarker({
]}
>
{Boolean(data.checks.numAccessible) && data.checks.numAccessible}
{showEnterBatCounts && '?'}
</Marker>
);
}
Expand Down
10 changes: 9 additions & 1 deletion src/logic/Logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
cubeCheckToCubeCollected,
cubeCollectedToCubeCheck,
dungeonCompletionItems,
needEnterBatreauxCountsItem,
} from './TrackerModifications';
import {
type RawArea,
Expand Down Expand Up @@ -51,6 +52,7 @@ export interface Logic {
checksByHintRegion: Record<string, string[]>;
exitsByHintRegion: Record<string, string[]>;
dungeonCompletionRequirements: { [dungeon: string]: string };
needsDynamicBatreauxCrystalCounts: boolean;
}

export interface LogicalCheck {
Expand Down Expand Up @@ -326,6 +328,7 @@ export function parseLogic(raw: RawLogic): Logic {
...newItems,
...Object.keys(cubeCollectedToCubeCheck),
...Object.values(dungeonCompletionItems),
needEnterBatreauxCountsItem,
];

// Pessimistically, all items are opaque
Expand Down Expand Up @@ -960,6 +963,10 @@ export function parseLogic(raw: RawLogic): Logic {
exitsByHintRegion,
dungeonCompletionRequirements: raw.dungeon_completion_requirements,
areaGraph,
// Check if this is a dump that requires additional crystal logic
needsDynamicBatreauxCrystalCounts: Boolean(
itemLookup['\\31 Gratitude Crystals'],
),
};
}

Expand Down Expand Up @@ -988,7 +995,8 @@ function mapAreaToBitLogic(
// Hack: We keep these virtual locations opaque...
if (
!locName.endsWith('Gratitude Crystals') &&
!locName.includes("\\Gondo's Upgrades\\Upgrade to")
!locName.includes("\\Gondo's Upgrades\\Upgrade to") &&
!locName.includes('Can Receive Batreaux Level')
) {
opaqueItems.clearBit(b.bit(locName));
}
Expand Down
19 changes: 19 additions & 0 deletions src/logic/Mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
gotRaisingReq,
hordeDoorReq,
impaSongCheck,
numBatreauxRewardLevels,
runtimeOptions,
swordsToAdd,
} from './ThingsThatWouldBeNiceToHaveInTheDump';
import {
dungeonCompletionItems,
needEnterBatreauxCountsItem,
sothItemReplacement,
sothItems,
triforceItemReplacement,
Expand All @@ -30,6 +32,7 @@ export function mapSettings(
settings: TypedOptions,
exits: ExitMapping[],
requiredDungeons: string[],
requiredCrystalCounts: number[],
) {
const requirements: Requirements = {};
const b = new LogicBuilder(logic.allItems, logic.itemLookup, requirements);
Expand Down Expand Up @@ -70,6 +73,22 @@ export function mapSettings(
}
}

if (logic.needsDynamicBatreauxCrystalCounts) {
for (let i = 0; i < numBatreauxRewardLevels; i++) {
const reqName = `\\Can Receive Batreaux Level ${i + 1} Rewards`;
if (requiredCrystalCounts[i] !== 0) {
b.set(
reqName,
b.singleBit(
`\\${requiredCrystalCounts[i]} Gratitude Crystals`,
),
);
} else {
b.set(reqName, b.singleBit(needEnterBatreauxCountsItem));
}
}
}

const raiseGotExpr =
settings['got-start'] === 'Raised'
? b.true()
Expand Down
7 changes: 7 additions & 0 deletions src/logic/ThingsThatWouldBeNiceToHaveInTheDump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,10 @@ export const doesHintDistroUseGossipStone: Record<
export const gotOpeningReq = 'GoT Opening Requirement';
export const gotRaisingReq = 'GoT Raising Requirement';
export const hordeDoorReq = 'Horde Door Requirement';

export const defaultBatreauxRequiredCrystals = [5, 10, 30, 40, 50, 70, 80];
export const numBatreauxRewardLevels = defaultBatreauxRequiredCrystals.length;

export const halfBatreauxRequiredCrystals = defaultBatreauxRequiredCrystals.map(
(amt) => Math.floor(amt / 2),
);
21 changes: 18 additions & 3 deletions src/logic/TrackerModifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { BitVector } from './bitlogic/BitVector';
import { type InventoryItem, isItem, itemMaxes, itemName } from './Inventory';
import type { DungeonName } from './Locations';
import type { Logic } from './Logic';
import { swordsToAdd } from './ThingsThatWouldBeNiceToHaveInTheDump';
import {
defaultBatreauxRequiredCrystals,
swordsToAdd,
} from './ThingsThatWouldBeNiceToHaveInTheDump';

const collectedCubeSuffix = '_TR_Cube_Collected';

Expand Down Expand Up @@ -62,6 +65,11 @@ export const dungeonCompletionItems: Record<string, string> = {
'Sky Keep': '\\Tracker\\Sky Keep Completed',
} satisfies Record<DungeonName, string>;

// A fake item that's required by dynamic Batreaux rewards until we know
// how many crystals are required.
export const needEnterBatreauxCountsItem =
'\\Tracker\\Enter required crystal count';

export function getInitialItems(
settings: TypedOptions,
): TrackerState['inventory'] {
Expand Down Expand Up @@ -176,9 +184,16 @@ export function getTooltipOpaqueBits(
}

// No point in revealing that the math behind 80 crystals is 13*5+15
for (const amt of [5, 10, 30, 40, 50, 70, 80]) {
set(`\\${amt} Gratitude Crystals`);
if (logic.needsDynamicBatreauxCrystalCounts) {
for (let amt = 1; amt <= 80; amt++) {
set(`\\${amt} Gratitude Crystals`);
}
} else {
for (const amt of defaultBatreauxRequiredCrystals) {
set(`\\${amt} Gratitude Crystals`);
}
}
set(needEnterBatreauxCountsItem);

if (settings['gondo-upgrades'] === false) {
set(
Expand Down
1 change: 1 addition & 0 deletions src/options/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const optionCategorization_ = {
Miscellaneous: [
'logic-mode',
'bit-patches',
'batreaux-counts',
'damage-multiplier',
'enabled-tricks-bitless',
'enabled-tricks-glitched',
Expand Down
4 changes: 4 additions & 0 deletions src/permalink/SettingsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export interface AllTypedOptions
| 'Normal'
| 'Beatable Only'
| 'Beatable Then Banned';

// Random Batreaux Crystal Counts
// https://github.com/ssrando/ssrando/pull/587
'batreaux-counts': 'Vanilla' | 'Half' | 'Random' | undefined;
}

export type TypedOptions = Pick<AllTypedOptions, LogicOption>;
5 changes: 5 additions & 0 deletions src/tooltips/TooltipExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ function getReadableItemName(logic: Logic, item: string) {
return prettyItemNames[item][1];
}

if (item === '\\1 Gratitude Crystals') {
// macro name
return '1 Gratitude Crystal';
}

const match = item.match(itemCountPat);
if (match) {
const [, baseName, count] = match;
Expand Down
Loading
Loading