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
5 changes: 1 addition & 4 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { WalletDetailsModal } from './components/WalletDetailsModal';
import { CombatProvider } from './contexts/CombatContext';
import { MapNavigationProvider } from './contexts/MapNavigationContext';
import { useMUD } from './contexts/MUDContext';
import AppRoutes, { HOME_PATH } from './Routes';
Expand All @@ -14,9 +13,7 @@ export const App = (): JSX.Element => {
return (
<Router>
<MapNavigationProvider>
<CombatProvider>
<AppInner />
</CombatProvider>
<AppInner />
</MapNavigationProvider>
</Router>
);
Expand Down
134 changes: 6 additions & 128 deletions packages/client/src/components/ActionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import { Button, HStack, Stack, Text, VStack } from '@chakra-ui/react';
import { useEntityQuery } from '@latticexyz/react';
import {
getComponentValueStrict,
Has,
HasValue,
runQuery,
} from '@latticexyz/recs';
import { decodeEntity } from '@latticexyz/store-sync/recs';
import { useCallback, useMemo, useState } from 'react';
import { useMemo } from 'react';
import { Link } from 'react-router-dom';
// eslint-disable-next-line import/no-named-as-default
import Typist from 'react-typist';
import { formatUnits } from 'viem';

import { useCharacter } from '../contexts/CharacterContext';
import { useCombat } from '../contexts/CombatContext';
import { useMapNavigation } from '../contexts/MapNavigationContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
import { ActionType, type BattleActionOutcome } from '../utils/types';

// enum ActionEvents {
// Attack = 'attack',
Expand Down Expand Up @@ -63,67 +50,22 @@ import { ActionType, type BattleActionOutcome } from '../utils/types';
// ];

export const ActionsPanel = (): JSX.Element => {
const { renderError } = useToast();
const {
components: { ActionOutcome, Actions },
delegatorAddress,
systemCalls: { endTurn },
} = useMUD();
const {
isRefreshing: isRefreshingCharacter,
character,
equippedItems,
refreshCharacter,
} = useCharacter();
const {
battleActionOutcomes,
currentBattle,
isAttacking,
isRefreshing: isRefreshingMap,
isSpawned,
monsterOponent,
monsters,
onAttack,
position,
} = useMapNavigation();
const { currentBattle, monsterOponent } = useCombat();

const [isAttacking, setIsAttacking] = useState(false);

const battleActionOutcomes = useEntityQuery([
Has(ActionOutcome),
HasValue(ActionOutcome, { attackerId: character?.characterId }),
])
.map(entity => {
const _actionOutcome = getComponentValueStrict(ActionOutcome, entity);

const { encounterId, currentTurn, actionNumber } = decodeEntity(
{
encounterId: 'bytes32',
currentTurn: 'uint256',
actionNumber: 'uint256',
},
entity,
);

return {
attackerDamageDelt: formatUnits(
_actionOutcome.attackerDamageDelt,
5,
).toString(),
attackerDied: _actionOutcome.attackerDied,
attackerId: _actionOutcome.attackerId.toString(),
actionId: _actionOutcome.actionId.toString(),
actionNumber: actionNumber.toString(),
blockNumber: _actionOutcome.blockNumber.toString(),
crit: _actionOutcome.crit,
currentTurn: currentTurn.toString(),
defenderDamageDelt: _actionOutcome.defenderDamageDelt.toString(),
defenderDied: _actionOutcome.defenderDied,
defenderId: _actionOutcome.defenderId.toString(),
encounterId: encounterId.toString(),
hit: _actionOutcome.hit,
miss: _actionOutcome.miss,
timestamp: _actionOutcome.timestamp.toString(),
weaponId: _actionOutcome.weaponId.toString(),
} as BattleActionOutcome;
})
.filter(action => action.encounterId === currentBattle?.encounterId);

const actionText = useMemo(() => {
if (isRefreshingCharacter || isRefreshingMap) return '';
Expand Down Expand Up @@ -203,70 +145,6 @@ export const ActionsPanel = (): JSX.Element => {
return '';
}, [isRefreshingCharacter, isRefreshingMap, isSpawned, monsters, position]);

const onAttack = useCallback(
async (itemId: string) => {
try {
setIsAttacking(true);

if (!delegatorAddress) {
throw new Error('Missing delegation.');
}

if (!character) {
throw new Error('Character not found.');
}

if (!currentBattle) {
throw new Error('Battle not found.');
}

if (!monsterOponent) {
throw new Error('Monster not found.');
}

const basicAttackId = Array.from(
runQuery([
Has(Actions),
HasValue(Actions, { actionType: ActionType.PhysicalAttack }),
]),
)[0];

if (!basicAttackId) {
throw new Error('Basic attack not found.');
}

const { error, success } = await endTurn(
currentBattle.encounterId,
character.characterId,
monsterOponent.monsterId,
basicAttackId,
itemId,
currentBattle.currentTurn,
);

if (error && !success) {
throw new Error(error);
}

await refreshCharacter();
} catch (e) {
renderError('Failed to attack.', e);
} finally {
setIsAttacking(false);
}
},
[
Actions,
character,
currentBattle,
delegatorAddress,
endTurn,
monsterOponent,
refreshCharacter,
renderError,
],
);

return (
<>
{currentBattle && equippedItems && monsterOponent && (
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/DelegationButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const DelegationButton = ({
getBurner();
navigate(CHARACTER_CREATION_PATH);
} catch (e) {
renderError('Failed to delegate.', e);
renderError((e as Error)?.message ?? 'Failed to delegate.', e);
} finally {
setIsDelegating(false);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/EditCharacterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export const EditCharacterModal: React.FC<EditCharacterModalProps> = ({
size="sm"
type="button"
>
Upload Avatar
Upload Avatar Image
</Button>
{showError && !(avatar || image) && (
<FormHelperText color="red">
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/ItemEquipModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const ItemEquipModal: React.FC<ItemEquipModalProps> = ({
renderSuccess(`${weapon.name} equipped successfully!`);
onClose();
} catch (e) {
renderError('Failed to equip item.', e);
renderError((e as Error)?.message ?? 'Failed to equip item.', e);
} finally {
setIsEquipping(false);
}
Expand Down Expand Up @@ -107,7 +107,7 @@ export const ItemEquipModal: React.FC<ItemEquipModalProps> = ({
renderSuccess(`${weapon.name} unequipped successfully!`);
onClose();
} catch (e) {
renderError('Failed to unequip item.', e);
renderError((e as Error)?.message ?? 'Failed to unequip item.', e);
} finally {
setIsEquipping(false);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/client/src/components/MapPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from 'react-icons/io';
import { TbDirectionArrows } from 'react-icons/tb';

import { useCombat } from '../contexts/CombatContext';
import { useMapNavigation } from '../contexts/MapNavigationContext';

const SAFE_ZONE_AREA = {
Expand All @@ -18,6 +17,7 @@ const SAFE_ZONE_AREA = {

export const MapPanel = (): JSX.Element => {
const {
currentBattle,
isRefreshing,
isSpawned,
isSpawning,
Expand All @@ -26,7 +26,6 @@ export const MapPanel = (): JSX.Element => {
otherPlayers,
position,
} = useMapNavigation();
const { currentBattle } = useCombat();

return (
<Stack
Expand Down
35 changes: 28 additions & 7 deletions packages/client/src/components/TileDetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { IoIosArrowForward } from 'react-icons/io';
import { useNavigate } from 'react-router-dom';

import { useCharacter } from '../contexts/CharacterContext';
import { useCombat } from '../contexts/CombatContext';
import { useMapNavigation } from '../contexts/MapNavigationContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
Expand All @@ -35,21 +34,43 @@ export const TileDetailsPanel = (): JSX.Element => {
systemCalls: { createMatch },
} = useMUD();
const { character } = useCharacter();
const { isRefreshing, monsters, otherPlayers } = useMapNavigation();
const { currentBattle, monsterOponent } = useCombat();
const {
battleActionOutcomes,
currentBattle,
isRefreshing,
monsterOponent,
monsters,
otherPlayers,
} = useMapNavigation();

const [isInitiating, setIsInitiating] = useState(false);
const [isMonsterHit, setIsMonsterHit] = useState(false);

useEffect(() => {
if (!monsterOponent) return;
if (monsterOponent.currentHp === monsterOponent.baseHp) return;
if (!(battleActionOutcomes[0] && currentBattle)) return;

const currentBattleTurnKey = 'current-battle-turn';
const currentBattleTurn = localStorage.getItem(currentBattleTurnKey);

if (currentBattleTurn) {
if (currentBattleTurn === currentBattle.currentTurn) {
return;
}
}

if (
battleActionOutcomes[battleActionOutcomes.length - 1]
.attackerDamageDelt === '0'
)
return;

setIsMonsterHit(true);
setTimeout(() => {
setIsMonsterHit(false);
}, 700);
}, [monsterOponent, monsterOponent?.baseHp, monsterOponent?.currentHp]);

localStorage.setItem(currentBattleTurnKey, currentBattle.currentTurn);
}, [battleActionOutcomes, currentBattle]);

const onInitiateCombat = useCallback(
async (monster: Monster) => {
Expand All @@ -76,7 +97,7 @@ export const TileDetailsPanel = (): JSX.Element => {

renderSuccess('Battle has begun!');
} catch (e) {
renderError('Failed to initiate battle.', e);
renderError((e as Error)?.message ?? 'Failed to initiate battle.', e);
} finally {
setIsInitiating(false);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/WalletDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const WalletDetailsModal = ({
setDepositAmount('0');
renderSuccess('Funds deposited successfully!');
} catch (e) {
renderError('Error depositing funds.', e);
renderError((e as Error)?.message ?? 'Error depositing funds.', e);
} finally {
setIsDepositing(false);
}
Expand Down Expand Up @@ -131,7 +131,7 @@ export const WalletDetailsModal = ({
setWithdrawAmount('0');
renderSuccess('Funds withdrawn successfully!');
} catch (e) {
renderError('Error withdrawing funds.', e);
renderError((e as Error)?.message ?? 'Error withdrawing funds.', e);
} finally {
setIsWithdrawing(false);
}
Expand Down
7 changes: 5 additions & 2 deletions packages/client/src/contexts/CharacterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const CharacterProvider = ({
try {
await fetchCharacterData();
} catch (e) {
renderError('Error refreshing character.', e);
renderError((e as Error)?.message ?? 'Error refreshing character.', e);
} finally {
setIsRefreshing(false);
}
Expand Down Expand Up @@ -248,7 +248,10 @@ export const CharacterProvider = ({

setEquippedItems(fullItems);
} catch (e) {
renderError('Failed to fetch character data.', e);
renderError(
(e as Error)?.message ?? 'Failed to fetch character data.',
e,
);
}
},
[ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError, worldContract],
Expand Down
Loading