Skip to content
Merged
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 mprocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ procs:
shell: pnpm run dev
anvil:
cwd: packages/contracts
shell: anvil --base-fee 0 --block-time 1
shell: anvil --base-fee 50 --block-time 2
30 changes: 22 additions & 8 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Grid, useBreakpointValue, useDisclosure } from '@chakra-ui/react';
import { Grid, useBreakpointValue } from '@chakra-ui/react';
import { useEffect } from 'react';
import { BrowserRouter as Router, useLocation } from 'react-router-dom';

Expand Down Expand Up @@ -30,17 +30,28 @@ export default App;
const AppInner = (): JSX.Element => {
const { pathname } = useLocation();
const isDesktop = useBreakpointValue({ base: false, lg: true });
const { burnerBalance, burnerBalanceFetched, isSynced } = useMUD();

const { isOpen, onOpen, onClose } = useDisclosure();
const {
burnerBalance,
burnerBalanceFetched,
isSynced,
isWalletDetailsModalOpen,
onCloseWalletDetailsModal,
onOpenWalletDetailsModal,
} = useMUD();

useEffect(() => {
if (pathname === HOME_PATH) return;

if (burnerBalanceFetched && burnerBalance === '0' && isSynced) {
onOpen();
onOpenWalletDetailsModal();
}
}, [burnerBalance, burnerBalanceFetched, isSynced, pathname, onOpen]);
}, [
burnerBalance,
burnerBalanceFetched,
isSynced,
onOpenWalletDetailsModal,
pathname,
]);

return (
<Grid
Expand All @@ -50,10 +61,13 @@ const AppInner = (): JSX.Element => {
templateColumns="100%"
templateRows="auto 1fr auto"
>
<Header onOpenWalletDetailsModal={onOpen} />
<Header onOpenWalletDetailsModal={onOpenWalletDetailsModal} />
<AppRoutes />
{isDesktop && <Footer />}
<WalletDetailsModal isOpen={isOpen} onClose={onClose} />
<WalletDetailsModal
isOpen={isWalletDetailsModalOpen}
onClose={onCloseWalletDetailsModal}
/>
</Grid>
);
};
198 changes: 106 additions & 92 deletions packages/client/src/components/ActionsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ import { useMovement } from '../contexts/MovementContext';
import { EncounterType } from '../utils/types';

export const ActionsPanel = (): JSX.Element => {
const {
isRefreshing: isRefreshingCharacter,
character,
equippedWeapons,
} = useCharacter();
const { character, equippedWeapons } = useCharacter();
const { isSpawned, monstersOnTile, position } = useMap();
const {
actionOutcomes,
Expand All @@ -35,11 +31,14 @@ export const ActionsPanel = (): JSX.Element => {
onContinueToBattleOutcome,
opponent,
} = useBattle();
const { isRefreshing: isRefreshingMap } = useMovement();
const { isRefreshing } = useMovement();

const [turnTimeLeft, setTurnTimeLeft] = useState<number>(32);
const [actionButtonFocus, setActionButtonFocus] = useState<number>(0);

const parentDivRef = useRef<HTMLDivElement>(null);
const actionButton1Ref = useRef<HTMLButtonElement>(null);
const actionButton2Ref = useRef<HTMLButtonElement>(null);

useEffect(() => {
if (parentDivRef.current) {
Expand All @@ -48,98 +47,43 @@ export const ActionsPanel = (): JSX.Element => {
top: parentDivRef.current.scrollHeight,
});
}

if (actionButton1Ref.current) {
actionButton1Ref.current.focus();
setActionButtonFocus(0);
}
}, [actionOutcomes]);

useEffect(() => {
const listener = (event: KeyboardEvent) => {
switch (event.key) {
case 'ArrowLeft':
if (actionButtonFocus === 1 && actionButton2Ref.current) {
actionButton1Ref.current?.focus();
setActionButtonFocus(0);
}
break;
case 'ArrowRight':
if (actionButtonFocus === 0 && actionButton1Ref.current) {
actionButton2Ref.current?.focus();
setActionButtonFocus(1);
}
break;
default:
break;
}
};

window.addEventListener('keydown', listener);
// eslint-disable-next-line consistent-return
return () => window.removeEventListener('keydown', listener);
}, [actionButtonFocus]);

const battleOver = useMemo(
() => currentBattle?.encounterId === lastestBattleOutcome?.encounterId,
[currentBattle, lastestBattleOutcome],
);

const actionText = useMemo(() => {
if (isRefreshingCharacter || isRefreshingMap) return '';

if (!(isSpawned && position)) {
return (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
In order to begin battling, you must{' '}
<Text as="span" fontWeight={700}>
spawn
</Text>{' '}
your character.
</Text>
</Typist>
);
}

if (position.x === 0 && position.y === 0) {
return (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
You are currently in the starting tile.{' '}
<Text as="span" fontWeight={700}>
Move to a new tile
</Text>{' '}
to find monsters to battle.
</Text>
</Typist>
);
}

if ((position.x !== 0 || position.y !== 0) && monstersOnTile.length === 0) {
return (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
startDelay={500}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
Looks like there are no monsters in this tile.{' '}
<Text as="span" fontWeight={700}>
Move to a new tile
</Text>{' '}
to find monsters to battle.
</Text>
</Typist>
);
}

if ((position.x !== 0 || position.y !== 0) && monstersOnTile.length > 0) {
return (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
To initiate a battle,{' '}
<Text as="span" fontWeight={700}>
click on a monster
</Text>
.
</Text>
</Typist>
);
}

return '';
}, [
isRefreshingCharacter,
isRefreshingMap,
isSpawned,
monstersOnTile,
position,
]);

const userTurn = useMemo(() => {
if (!(character && currentBattle)) return false;

Expand Down Expand Up @@ -294,6 +238,7 @@ export const ActionsPanel = (): JSX.Element => {
).toString(),
)
}
ref={index === 0 ? actionButton1Ref : actionButton2Ref}
variant="outline"
w="100%"
>
Expand All @@ -304,7 +249,76 @@ export const ActionsPanel = (): JSX.Element => {
</VStack>
)}
<Stack p={{ base: 2, lg: 4 }}>
{!currentBattle && actionText}
{!currentBattle && !(isSpawned && position) && (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
In order to begin battling, you must{' '}
<Text as="span" fontWeight={700}>
spawn
</Text>{' '}
your character.
</Text>
</Typist>
)}
{!currentBattle &&
isSpawned &&
position?.x === 0 &&
position?.y === 0 && (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
You are currently in the starting tile.{' '}
<Text as="span" fontWeight={700}>
Move to a new tile
</Text>{' '}
to find monsters to battle.
</Text>
</Typist>
)}
{!currentBattle &&
!isRefreshing &&
isSpawned &&
(position?.x !== 0 || position?.y !== 0) &&
monstersOnTile.length === 0 && (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
Looks like there are no monsters in this tile.{' '}
<Text as="span" fontWeight={700}>
Move to a new tile
</Text>{' '}
to find monsters to battle.
</Text>
</Typist>
)}
{!currentBattle &&
!isRefreshing &&
(position?.x !== 0 || position?.y !== 0) &&
monstersOnTile.length > 0 && (
<Typist
avgTypingDelay={10}
cursor={{ show: false }}
stdTypingDelay={10}
>
<Text size={{ base: 'xs', sm: 'sm', lg: 'md' }}>
To initiate a battle,{' '}
<Text as="span" fontWeight={700}>
click on a monster
</Text>
.
</Text>
</Typist>
)}

{opponent &&
actionOutcomes.map((action, i) => {
Expand Down
28 changes: 1 addition & 27 deletions packages/client/src/components/BattleOutcomeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { Link } from 'react-router-dom';

import { useBattle } from '../contexts/BattleContext';
import { useCharacter } from '../contexts/CharacterContext';
import { useMap } from '../contexts/MapContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
import { BATTLE_OUTCOME_SEEN_KEY } from '../utils/constants';
Expand Down Expand Up @@ -55,37 +54,12 @@ export const BattleOutcomeModal: React.FC<BattleOutcomeModalProps> = ({
components: { Items, ItemsBaseURI, ItemsTokenURI, Levels },
} = useMUD();
const { character, refreshCharacter } = useCharacter();
const { allMonsters, otherCharactersOnTile } = useMap();
const { onContinueToBattleOutcome } = useBattle();
const { onContinueToBattleOutcome, opponent } = useBattle();

const [armor, setArmor] = useState<Armor[]>([]);
const [weapons, setWeapons] = useState<Weapon[]>([]);
const [isLoadingItems, setIsLoadingItems] = useState(true);

const opponent = useMemo(() => {
if (!character) return null;
const opponent =
character.id === battleOutcome.defenders[0]
? battleOutcome.attackers[0]
: battleOutcome.defenders[0];

const monsterOpponent = allMonsters.find(
monster => monster.id === opponent,
);
if (monsterOpponent) {
return monsterOpponent;
}

const characterOpponent = otherCharactersOnTile.find(
c => c.id === opponent,
);
if (characterOpponent) {
return characterOpponent;
}

return null;
}, [allMonsters, battleOutcome, character, otherCharactersOnTile]);

const onAcknowledge = useCallback(() => {
localStorage.setItem(BATTLE_OUTCOME_SEEN_KEY, battleOutcome.encounterId);
onContinueToBattleOutcome(false);
Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/components/EditCharacterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export const EditCharacterModal: React.FC<EditCharacterModalProps> = ({
<VStack w="100%">
<FormControl isInvalid={showError && !newName}>
<Input
isDisabled={isUpdating}
onChange={e => setNewName(e.target.value)}
placeholder={'Name'}
type="text"
Expand All @@ -222,13 +223,14 @@ export const EditCharacterModal: React.FC<EditCharacterModalProps> = ({
<Input
accept=".png, .jpg, .jpeg, .webp, .svg"
id="avatarInput"
isDisabled={isUpdating || isUploaded}
onChange={e => setAvatar(e.target.files?.[0] ?? null)}
style={{ display: 'none' }}
type="file"
/>
<Button
alignSelf="start"
isDisabled={isUploaded}
isDisabled={isUpdating || isUploaded}
isLoading={isUploading}
loadingText="Uploading..."
onClick={onUploadAvatar}
Expand All @@ -248,6 +250,7 @@ export const EditCharacterModal: React.FC<EditCharacterModalProps> = ({
<FormControl isInvalid={showError && !newDescription}>
<Textarea
height="200px"
isDisabled={isUpdating}
onChange={e => setNewDescription(e.target.value)}
placeholder="Bio"
value={newDescription}
Expand Down
Loading