diff --git a/packages/client/src/Routes.tsx b/packages/client/src/Routes.tsx index 0ddd2dc59..ace2927f6 100644 --- a/packages/client/src/Routes.tsx +++ b/packages/client/src/Routes.tsx @@ -45,7 +45,7 @@ const AppRoutes: React.FC = () => { } /> } /> } /> - } /> + } /> } /> ); diff --git a/packages/client/src/components/ActionsPanel.tsx b/packages/client/src/components/ActionsPanel.tsx index f0a2c6153..f817fb8a9 100644 --- a/packages/client/src/components/ActionsPanel.tsx +++ b/packages/client/src/components/ActionsPanel.tsx @@ -3,11 +3,12 @@ import { Button, Divider, HStack, + Progress, Stack, Text, VStack, } from '@chakra-ui/react'; -import { useEffect, useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; // eslint-disable-next-line import/no-named-as-default import Typist from 'react-typist'; @@ -16,6 +17,7 @@ import { useBattle } from '../contexts/BattleContext'; import { useCharacter } from '../contexts/CharacterContext'; import { useMap } from '../contexts/MapContext'; import { useMovement } from '../contexts/MovementContext'; +import { EncounterType } from '../utils/types'; export const ActionsPanel = (): JSX.Element => { const { @@ -23,18 +25,20 @@ export const ActionsPanel = (): JSX.Element => { character, equippedWeapons, } = useCharacter(); - const { aliveMonsters, isSpawned, position } = useMap(); + const { isSpawned, monstersOnTile, position } = useMap(); const { actionOutcomes, attackingItemId, currentBattle, lastestBattleOutcome, - monsterOponent, onAttack, onContinueToBattleOutcome, + opponent, } = useBattle(); const { isRefreshing: isRefreshingMap } = useMovement(); + const [turnTimeLeft, setTurnTimeLeft] = useState(32); + const parentDivRef = useRef(null); useEffect(() => { @@ -90,7 +94,7 @@ export const ActionsPanel = (): JSX.Element => { ); } - if ((position.x !== 0 || position.y !== 0) && aliveMonsters.length === 0) { + if ((position.x !== 0 || position.y !== 0) && monstersOnTile.length === 0) { return ( { ); } - if ((position.x !== 0 || position.y !== 0) && aliveMonsters.length > 0) { + if ((position.x !== 0 || position.y !== 0) && monstersOnTile.length > 0) { return ( { return ''; }, [ - aliveMonsters, isRefreshingCharacter, isRefreshingMap, isSpawned, + monstersOnTile, position, ]); + const userTurn = useMemo(() => { + if (!(character && currentBattle)) return false; + + if (currentBattle.encounterType === EncounterType.PvE) { + return true; + } + + const attackersTurn = Number(currentBattle.currentTurn) % 2 === 1; + + if (attackersTurn && currentBattle.attackers.includes(character?.id)) { + return true; + } + + if (!attackersTurn && currentBattle.defenders.includes(character?.id)) { + return true; + } + + return false; + }, [character, currentBattle]); + + const turnEndTime = useMemo(() => { + if (!currentBattle) return 0; + + const _turnEndTime = + (BigInt(currentBattle.currentTurnTimer) + BigInt(32)) * BigInt(1000); + + return Number(_turnEndTime); + }, [currentBattle]); + + useEffect(() => { + if (turnEndTime - Date.now() < 0) { + setTurnTimeLeft(0); + } else { + setTurnTimeLeft(Math.floor((turnEndTime - Date.now()) / 1000)); + } + + const interval = setInterval(() => { + if (turnEndTime - Date.now() < 0) { + setTurnTimeLeft(0); + return; + } + setTurnTimeLeft(prev => prev - 1); + }, 1000); + + return () => clearInterval(interval); + }, [turnEndTime, turnTimeLeft]); + + const canAttack = useMemo(() => { + if (!currentBattle) return false; + + if (currentBattle.encounterType === EncounterType.PvE) { + return true; + } + + if (userTurn) { + return true; + } + + if (turnTimeLeft === 0) { + return true; + } + + return false; + }, [currentBattle, userTurn, turnTimeLeft]); + return ( - {!battleOver && currentBattle && equippedWeapons && monsterOponent && ( + {!battleOver && currentBattle && equippedWeapons && opponent && ( - - Choose your move: - - {equippedWeapons.length === 0 && ( - - You have no equipped items. In order to attack, you must go to - your{' '} - - character page - {' '} - and equip at least 1 item. + {currentBattle.encounterType === EncounterType.PvE && ( + + + Choose your move! + )} - + + {currentBattle.encounterType === EncounterType.PvP && ( + <> + {userTurn && ( + + + Choose your move! + {' '} + You have {turnTimeLeft} seconds before your opponent can + attack. + + )} + {!userTurn && !canAttack && ( + + It is your opponent's turn. But you can attack in{' '} + {turnTimeLeft} seconds. + + )} + {!userTurn && canAttack && ( + + Your opponent took too long to make a move.{' '} + + You can now attack! + + + )} + {equippedWeapons.length === 0 && ( + + You have no equipped items. In order to attack, you must go to + your{' '} + + character page + {' '} + and equip at least 1 item. + + )} + + )} + + {currentBattle.encounterType === EncounterType.PvP && ( + + )} {equippedWeapons.map((item, index) => (