From 72ad9b7e7e7039eeb5d85b1c5ede7d3369079750 Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Tue, 30 Jul 2024 08:00:59 -0600
Subject: [PATCH 1/4] Tweak ActionOutcome type name
---
packages/client/src/components/ActionsPanel.tsx | 4 ++--
packages/client/src/components/TileDetailsPanel.tsx | 11 ++++-------
.../client/src/contexts/MapNavigationContext.tsx | 12 ++++++------
packages/client/src/utils/types.ts | 12 +++++++++++-
.../out/CharacterSystem.sol/CharacterSystem.json | 2 +-
.../contracts/out/CombatSystem.sol/CombatSystem.json | 2 +-
.../out/EquipmentSystem.sol/EquipmentSystem.json | 2 +-
packages/contracts/out/IWorld.sol/IWorld.json | 2 +-
packages/contracts/out/MapSystem.sol/MapSystem.json | 2 +-
9 files changed, 28 insertions(+), 21 deletions(-)
diff --git a/packages/client/src/components/ActionsPanel.tsx b/packages/client/src/components/ActionsPanel.tsx
index eeb70aa4f..c25153386 100644
--- a/packages/client/src/components/ActionsPanel.tsx
+++ b/packages/client/src/components/ActionsPanel.tsx
@@ -56,7 +56,7 @@ export const ActionsPanel = (): JSX.Element => {
equippedItems,
} = useCharacter();
const {
- battleActionOutcomes,
+ actionOutcomes,
currentBattle,
isAttacking,
isRefreshing: isRefreshingMap,
@@ -192,7 +192,7 @@ export const ActionsPanel = (): JSX.Element => {
{!currentBattle && actionText}
{monsterOponent &&
- battleActionOutcomes.map((action, i) => {
+ actionOutcomes.map((action, i) => {
if (action.miss) {
return (
{
} = useMUD();
const { character } = useCharacter();
const {
- battleActionOutcomes,
+ actionOutcomes,
currentBattle,
isRefreshing,
monsterOponent,
@@ -47,7 +47,7 @@ export const TileDetailsPanel = (): JSX.Element => {
const [isMonsterHit, setIsMonsterHit] = useState(false);
useEffect(() => {
- if (!(battleActionOutcomes[0] && currentBattle)) return;
+ if (!(actionOutcomes[0] && currentBattle)) return;
const currentBattleTurnKey = 'current-battle-turn';
const currentBattleTurn = localStorage.getItem(currentBattleTurnKey);
@@ -58,10 +58,7 @@ export const TileDetailsPanel = (): JSX.Element => {
}
}
- if (
- battleActionOutcomes[battleActionOutcomes.length - 1]
- .attackerDamageDelt === '0'
- )
+ if (actionOutcomes[actionOutcomes.length - 1].attackerDamageDelt === '0')
return;
setIsMonsterHit(true);
@@ -70,7 +67,7 @@ export const TileDetailsPanel = (): JSX.Element => {
}, 700);
localStorage.setItem(currentBattleTurnKey, currentBattle.currentTurn);
- }, [battleActionOutcomes, currentBattle]);
+ }, [actionOutcomes, currentBattle]);
const onInitiateCombat = useCallback(
async (monster: Monster) => {
diff --git a/packages/client/src/contexts/MapNavigationContext.tsx b/packages/client/src/contexts/MapNavigationContext.tsx
index a43910961..8b0b500e4 100644
--- a/packages/client/src/contexts/MapNavigationContext.tsx
+++ b/packages/client/src/contexts/MapNavigationContext.tsx
@@ -32,8 +32,8 @@ import { useToast } from '../hooks/useToast';
import { GAME_BOARD_PATH } from '../Routes';
import { fetchMetadataFromUri, uriToHttp } from '../utils/helpers';
import {
+ type ActionOutcomeType,
ActionType,
- type BattleActionOutcome,
type Character,
type CombatDetails,
type Monster,
@@ -42,7 +42,7 @@ import { useCharacter } from './CharacterContext';
import { useMUD } from './MUDContext';
type MapNavigationContextType = {
- battleActionOutcomes: BattleActionOutcome[];
+ actionOutcomes: ActionOutcomeType[];
currentBattle: CombatDetails | null;
isAttacking: boolean;
isRefreshing: boolean;
@@ -58,7 +58,7 @@ type MapNavigationContextType = {
};
const MapNavigationContext = createContext({
- battleActionOutcomes: [],
+ actionOutcomes: [],
currentBattle: null,
isAttacking: false,
isRefreshing: false,
@@ -537,7 +537,7 @@ export const MapNavigationProvider = ({
],
);
- const battleActionOutcomes = useEntityQuery([
+ const actionOutcomes = useEntityQuery([
Has(ActionOutcome),
HasValue(ActionOutcome, { attackerId: character?.characterId }),
])
@@ -573,14 +573,14 @@ export const MapNavigationProvider = ({
miss: _actionOutcome.miss,
timestamp: _actionOutcome.timestamp.toString(),
weaponId: _actionOutcome.weaponId.toString(),
- } as BattleActionOutcome;
+ } as ActionOutcomeType;
})
.filter(action => action.encounterId === currentBattle?.encounterId);
return (
Date: Tue, 30 Jul 2024 08:33:24 -0600
Subject: [PATCH 2/4] Add level up indicators
---
packages/client/src/components/Level.tsx | 8 ++-
packages/client/src/components/StatsPanel.tsx | 49 ++++++++++++++-----
packages/client/src/pages/Character.tsx | 43 +++++++++++-----
packages/client/src/utils/theme.ts | 42 +++++++++++++++-
4 files changed, 114 insertions(+), 28 deletions(-)
diff --git a/packages/client/src/components/Level.tsx b/packages/client/src/components/Level.tsx
index bc5c8cbe6..c74d12c35 100644
--- a/packages/client/src/components/Level.tsx
+++ b/packages/client/src/components/Level.tsx
@@ -10,6 +10,8 @@ export const Level = ({
return (
100%
-
+
Level {currentLevel}
diff --git a/packages/client/src/components/StatsPanel.tsx b/packages/client/src/components/StatsPanel.tsx
index 0511b4cf8..453859a0c 100644
--- a/packages/client/src/components/StatsPanel.tsx
+++ b/packages/client/src/components/StatsPanel.tsx
@@ -32,19 +32,20 @@ export const StatsPanel = (): JSX.Element => {
} = useMUD();
const { character, equippedItems } = useCharacter();
- const nextLevelXpRequirement = useComponentValue(
- Levels,
- encodeEntity(
- { level: 'uint256' },
- { level: BigInt(Number(character?.level ?? 0) + 1) },
- ),
- )?.experience;
+ const nextLevelXpRequirement =
+ useComponentValue(
+ Levels,
+ encodeEntity(
+ { level: 'uint256' },
+ { level: BigInt(Number(character?.level ?? 0) + 1) },
+ ),
+ )?.experience ?? BigInt(0);
const levelPercent = useMemo(() => {
- if (!(character && nextLevelXpRequirement)) return 0;
- return (
- (100 * Number(character.experience)) / Number(nextLevelXpRequirement)
- );
+ if (!character) return 0;
+ const percent =
+ (100 * Number(character.experience)) / Number(nextLevelXpRequirement);
+ return percent > 100 ? 100 : percent;
}, [character, nextLevelXpRequirement]);
if (!(character && equippedItems)) {
@@ -130,10 +131,34 @@ export const StatsPanel = (): JSX.Element => {
- {experience}/{nextLevelXpRequirement?.toString() ?? '0'}
+ = nextLevelXpRequirement ? 'green' : 'black'
+ }
+ fontWeight={
+ BigInt(experience) >= nextLevelXpRequirement ? 'bold' : 'normal'
+ }
+ >
+ {BigInt(experience) >= nextLevelXpRequirement
+ ? nextLevelXpRequirement.toString()
+ : experience}
+
+ /{nextLevelXpRequirement.toString()}
+ {BigInt(experience) >= nextLevelXpRequirement && (
+
+ )}
+
Equipped Items
diff --git a/packages/client/src/pages/Character.tsx b/packages/client/src/pages/Character.tsx
index 9a2f5ed91..7907b0ac8 100644
--- a/packages/client/src/pages/Character.tsx
+++ b/packages/client/src/pages/Character.tsx
@@ -308,19 +308,20 @@ export const CharacterPage = (): JSX.Element => {
const maxItemsEquipped = equippedWeapons.length === MAX_EQUIPPED_WEAPONS;
- const nextLevelXpRequirement = useComponentValue(
- Levels,
- encodeEntity(
- { level: 'uint256' },
- { level: BigInt(Number(character?.level ?? 0) + 1) },
- ),
- )?.experience;
+ const nextLevelXpRequirement =
+ useComponentValue(
+ Levels,
+ encodeEntity(
+ { level: 'uint256' },
+ { level: BigInt(Number(character?.level ?? 0) + 1) },
+ ),
+ )?.experience ?? BigInt(0);
const levelPercent = useMemo(() => {
- if (!(character && nextLevelXpRequirement)) return 0;
- return (
- (100 * Number(character.experience)) / Number(nextLevelXpRequirement)
- );
+ if (!character) return 0;
+ const percent =
+ (100 * Number(character.experience)) / Number(nextLevelXpRequirement);
+ return percent > 100 ? 100 : percent;
}, [character, nextLevelXpRequirement]);
if (isLoadingCharacter) {
@@ -472,8 +473,24 @@ export const CharacterPage = (): JSX.Element => {
$GOLD
- {character.experience}/
- {nextLevelXpRequirement?.toString() ?? '0'}
+ = nextLevelXpRequirement
+ ? 'green'
+ : 'black'
+ }
+ fontWeight={
+ BigInt(character.experience) >= nextLevelXpRequirement
+ ? 'bold'
+ : 'normal'
+ }
+ >
+ {BigInt(character.experience) >= nextLevelXpRequirement
+ ? nextLevelXpRequirement.toString()
+ : character.experience}
+
+ /{nextLevelXpRequirement.toString()}
diff --git a/packages/client/src/utils/theme.ts b/packages/client/src/utils/theme.ts
index e1327de26..633718520 100644
--- a/packages/client/src/utils/theme.ts
+++ b/packages/client/src/utils/theme.ts
@@ -29,6 +29,32 @@ const Button = {
},
},
variants: {
+ gold: {
+ bg: 'gold',
+ border: '2px solid',
+ borderColor: 'black',
+ color: 'black',
+ _active: {
+ bg: 'rgba(0, 0, 0, 1)',
+ color: 'white',
+ _disabled: {
+ bg: 'rgba(0, 0, 0, 0.7)',
+ },
+ },
+ _hover: {
+ bg: 'rgba(0, 0, 0, 0.8)',
+ color: 'white',
+ _disabled: {
+ bg: 'rgba(0, 0, 0, 0.7)',
+ },
+ },
+ _loading: {
+ bg: 'rgba(0, 0, 0, 0.8)',
+ _hover: {
+ bg: 'rgba(0, 0, 0, 0.8)',
+ },
+ },
+ },
outline: {
border: '2px solid',
borderColor: 'grey500',
@@ -116,8 +142,20 @@ const Progress = {
track: {
borderRadius: 5,
},
- filledTrack: {
- bg: 'black',
+ },
+ defaultProps: {
+ variant: 'filling',
+ },
+ variants: {
+ filling: {
+ filledTrack: {
+ bg: 'black',
+ },
+ },
+ filled: {
+ filledTrack: {
+ bg: 'green',
+ },
},
},
};
From f0c92948a55d3ebc0558d935bfe5526ba28a2cab Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Tue, 30 Jul 2024 13:17:14 -0600
Subject: [PATCH 3/4] Add LevelingPanel
---
.../client/src/components/LevelingPanel.tsx | 337 ++++++++++++++++++
packages/client/src/components/StatsPanel.tsx | 26 +-
packages/client/src/hooks/useToast.ts | 8 +-
.../client/src/lib/mud/createSystemCalls.ts | 71 +++-
packages/client/src/pages/Character.tsx | 68 ++--
5 files changed, 449 insertions(+), 61 deletions(-)
create mode 100644 packages/client/src/components/LevelingPanel.tsx
diff --git a/packages/client/src/components/LevelingPanel.tsx b/packages/client/src/components/LevelingPanel.tsx
new file mode 100644
index 000000000..96e5f2aa2
--- /dev/null
+++ b/packages/client/src/components/LevelingPanel.tsx
@@ -0,0 +1,337 @@
+import { Button, HStack, Text, VStack } from '@chakra-ui/react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+
+import { useCharacter } from '../contexts/CharacterContext';
+import { useMUD } from '../contexts/MUDContext';
+import { useToast } from '../hooks/useToast';
+import { type Character } from '../utils/types';
+
+export const LevelingPanel = ({
+ canLevel,
+ character,
+}: {
+ canLevel: boolean;
+ character: Character;
+}): JSX.Element => {
+ const { renderError, renderSuccess, renderWarning } = useToast();
+ const {
+ delegatorAddress,
+ systemCalls: { levelCharacter },
+ } = useMUD();
+ const { refreshCharacter } = useCharacter();
+
+ const [abilityPoints, setAbilityPoints] = useState(0);
+ const [newAgility, setNewAgility] = useState(character.agility);
+ const [newIntelligence, setNewIntelligence] = useState(
+ character.intelligence,
+ );
+ const [newStrength, setNewStrength] = useState(character.strength);
+
+ const [isLeveling, setIsLeveling] = useState(false);
+
+ useEffect(() => {
+ if (canLevel) {
+ setAbilityPoints(2);
+ }
+ setNewAgility(character.agility);
+ setNewIntelligence(character.intelligence);
+ setNewStrength(character.strength);
+ }, [canLevel, character.agility, character.intelligence, character.strength]);
+
+ const strengthIncreased = useMemo(
+ () => BigInt(newStrength) > BigInt(character.strength),
+ [newStrength, character.strength],
+ );
+
+ const agilityIncreased = useMemo(
+ () => BigInt(newAgility) > BigInt(character.agility),
+ [newAgility, character.agility],
+ );
+
+ const intelligenceIncreased = useMemo(
+ () => BigInt(newIntelligence) > BigInt(character.intelligence),
+ [newIntelligence, character.intelligence],
+ );
+
+ const onDecrementStat = useCallback(
+ (stat: 'str' | 'agi' | 'int') => {
+ if (isLeveling) return;
+
+ let replenishAbilityPoint = false;
+
+ switch (stat) {
+ case 'str':
+ if (newStrength === character.strength) return;
+ if (strengthIncreased) {
+ replenishAbilityPoint = true;
+ }
+ if (!replenishAbilityPoint && abilityPoints <= 0) {
+ renderWarning('You do not have enough ability points.');
+ return;
+ }
+
+ setNewStrength(prev => (BigInt(prev) - BigInt(1)).toString());
+ break;
+ case 'agi':
+ if (newAgility === character.agility) return;
+ if (agilityIncreased) {
+ replenishAbilityPoint = true;
+ }
+ if (!replenishAbilityPoint && abilityPoints <= 0) {
+ renderWarning('You do not have enough ability points.');
+ return;
+ }
+
+ setNewAgility(prev => (BigInt(prev) - BigInt(1)).toString());
+ break;
+ case 'int':
+ if (newIntelligence === character.intelligence) return;
+ if (intelligenceIncreased) {
+ replenishAbilityPoint = true;
+ }
+ if (!replenishAbilityPoint && abilityPoints <= 0) {
+ renderWarning('You do not have enough ability points.');
+ return;
+ }
+
+ setNewIntelligence(prev => (BigInt(prev) - BigInt(1)).toString());
+ break;
+ default:
+ }
+
+ if (replenishAbilityPoint) {
+ setAbilityPoints(prev => prev + 1);
+ } else {
+ setAbilityPoints(prev => prev - 1);
+ }
+ },
+ [
+ abilityPoints,
+ agilityIncreased,
+ character.agility,
+ character.intelligence,
+ character.strength,
+ intelligenceIncreased,
+ isLeveling,
+ newAgility,
+ newIntelligence,
+ newStrength,
+ renderWarning,
+ strengthIncreased,
+ ],
+ );
+
+ const onIncrementStat = useCallback(
+ (stat: 'str' | 'agi' | 'int') => {
+ if (isLeveling) return;
+ if (abilityPoints <= 0) {
+ renderWarning('You do not have enough ability points.');
+ return;
+ }
+
+ switch (stat) {
+ case 'str':
+ setNewStrength(prev => (BigInt(prev) + BigInt(1)).toString());
+ break;
+ case 'agi':
+ setNewAgility(prev => (BigInt(prev) + BigInt(1)).toString());
+ break;
+ case 'int':
+ setNewIntelligence(prev => (BigInt(prev) + BigInt(1)).toString());
+ break;
+ default:
+ }
+
+ setAbilityPoints(prev => prev - 1);
+ },
+ [abilityPoints, isLeveling, renderWarning],
+ );
+
+ const onLevelCharacter = useCallback(async () => {
+ try {
+ setIsLeveling(true);
+ if (abilityPoints > 0) {
+ renderWarning('You have unused ability points');
+ return;
+ }
+ if (!delegatorAddress) {
+ throw new Error('Missing delegation.');
+ }
+
+ const newStats = {
+ agility: newAgility,
+ baseHp: character.baseHp,
+ currentHp: character.currentHp,
+ class: character.entityClass,
+ experience: character.experience,
+ intelligence: newIntelligence,
+ level: character.level,
+ strength: newStrength,
+ };
+
+ const { error, success } = await levelCharacter(
+ character.characterId,
+ newStats,
+ );
+
+ if (error && !success) {
+ throw new Error(error);
+ }
+
+ await refreshCharacter();
+ renderSuccess('Character leveled up!');
+ } catch (e) {
+ renderError((e as Error)?.message ?? 'Failed to unequip item.', e);
+ } finally {
+ setIsLeveling(false);
+ }
+ }, [
+ abilityPoints,
+ character.baseHp,
+ character.characterId,
+ character.currentHp,
+ character.entityClass,
+ character.experience,
+ character.level,
+ delegatorAddress,
+ levelCharacter,
+ newAgility,
+ newIntelligence,
+ newStrength,
+ refreshCharacter,
+ renderError,
+ renderSuccess,
+ renderWarning,
+ ]);
+
+ return (
+
+
+
+ My Stats
+
+
+ Ability Points: {abilityPoints}
+
+
+
+ Base
+
+
+
+ HP - Hit
+
+ {character.currentHp}/{character.baseHp}
+
+
+
+
+ STR - Strength
+
+ {strengthIncreased && (
+
+ )}
+
+ {newStrength}
+
+ {canLevel && (
+
+ )}
+
+
+
+
+ AGI - Agility
+
+ {agilityIncreased && (
+
+ )}
+
+ {newAgility}
+
+ {canLevel && (
+
+ )}
+
+
+
+
+ INT - Intelligence
+
+ {intelligenceIncreased && (
+
+ )}
+
+ {newIntelligence}
+
+ {canLevel && (
+
+ )}
+
+
+
+ {canLevel && (
+
+ )}
+
+ );
+};
diff --git a/packages/client/src/components/StatsPanel.tsx b/packages/client/src/components/StatsPanel.tsx
index 453859a0c..0f572d5b8 100644
--- a/packages/client/src/components/StatsPanel.tsx
+++ b/packages/client/src/components/StatsPanel.tsx
@@ -32,21 +32,35 @@ export const StatsPanel = (): JSX.Element => {
} = useMUD();
const { character, equippedItems } = useCharacter();
+ const currentLevelXpRequirement =
+ useComponentValue(
+ Levels,
+ character
+ ? encodeEntity(
+ { level: 'uint256' },
+ { level: BigInt(Number(character.level) - 1) },
+ )
+ : undefined,
+ )?.experience ?? BigInt(0);
+
const nextLevelXpRequirement =
useComponentValue(
Levels,
- encodeEntity(
- { level: 'uint256' },
- { level: BigInt(Number(character?.level ?? 0) + 1) },
- ),
+ character
+ ? encodeEntity({ level: 'uint256' }, { level: BigInt(character.level) })
+ : undefined,
)?.experience ?? BigInt(0);
const levelPercent = useMemo(() => {
if (!character) return 0;
+
+ const xpSinceLastLevel =
+ BigInt(character.experience) - currentLevelXpRequirement;
+
const percent =
- (100 * Number(character.experience)) / Number(nextLevelXpRequirement);
+ (100 * Number(xpSinceLastLevel)) / Number(nextLevelXpRequirement);
return percent > 100 ? 100 : percent;
- }, [character, nextLevelXpRequirement]);
+ }, [character, currentLevelXpRequirement, nextLevelXpRequirement]);
if (!(character && equippedItems)) {
return (
diff --git a/packages/client/src/hooks/useToast.ts b/packages/client/src/hooks/useToast.ts
index 92af25ceb..86671e862 100644
--- a/packages/client/src/hooks/useToast.ts
+++ b/packages/client/src/hooks/useToast.ts
@@ -22,10 +22,12 @@ export const useToast = (): {
return;
}
- // eslint-disable-next-line no-console
- console.error(errorLog);
+ if (errorLog) {
+ // eslint-disable-next-line no-console
+ console.error(errorLog);
+ }
- if ((errorLog as Error).message === INSUFFICIENT_FUNDS_MESSAGE) {
+ if ((errorLog as Error)?.message === INSUFFICIENT_FUNDS_MESSAGE) {
toast({
description: (errorLog as Error).message,
position: 'top',
diff --git a/packages/client/src/lib/mud/createSystemCalls.ts b/packages/client/src/lib/mud/createSystemCalls.ts
index a616ba872..78711f475 100644
--- a/packages/client/src/lib/mud/createSystemCalls.ts
+++ b/packages/client/src/lib/mud/createSystemCalls.ts
@@ -29,7 +29,7 @@ import {
} from 'viem';
import { INSUFFICIENT_FUNDS_MESSAGE } from '../../utils/errors';
-import { EncounterType, StatsClasses } from '../../utils/types';
+import { EncounterType, EntityStats, StatsClasses } from '../../utils/types';
import { ClientComponents } from './createClientComponents';
import { SetupNetworkResult } from './setupNetwork';
@@ -290,27 +290,77 @@ export function createSystemCalls(
}
};
+ const levelCharacter = async (
+ characterId: Entity,
+ entityStats: Omit & {
+ class: StatsClasses;
+ },
+ ): SystemCallReturn => {
+ try {
+ const formattedNewStats = {
+ agility: BigInt(entityStats.agility),
+ baseHp: BigInt(entityStats.baseHp),
+ class: entityStats.class,
+ currentHp: BigInt(entityStats.currentHp),
+ experience: BigInt(entityStats.experience),
+ intelligence: BigInt(entityStats.intelligence),
+ level: BigInt(entityStats.level) + BigInt(1),
+ strength: BigInt(entityStats.strength),
+ };
+
+ await publicClient.simulateContract({
+ abi: worldContract.abi,
+ account: delegatorAddress,
+ address: worldContract.address,
+ args: [characterId as `0x${string}`, formattedNewStats],
+ functionName: 'UD__levelCharacter',
+ });
+
+ const tx = await worldContract.write.UD__levelCharacter([
+ characterId as `0x${string}`,
+ formattedNewStats,
+ ]);
+
+ await waitForTransaction(tx);
+
+ const newLevel = getComponentValueStrict(
+ Stats,
+ encodeEntity(
+ { characterId: 'uint256' },
+ { characterId: BigInt(characterId) },
+ ),
+ ).level;
+
+ const success = newLevel === formattedNewStats.level;
+
+ return {
+ error: success ? undefined : 'Failed to level character.',
+ success,
+ };
+ } catch (e) {
+ return {
+ error: getContractError(e as BaseError),
+ success: false,
+ };
+ }
+ };
+
const mintCharacter = async (
account: Address,
name: string,
uri: string,
): SystemCallReturn => {
try {
- await publicClient.simulateContract({
+ const nameHex = stringToHex(name, { size: 32 });
+
+ const simulatedTx = await publicClient.simulateContract({
abi: worldContract.abi,
account: delegatorAddress,
address: worldContract.address,
- args: [account, stringToHex(name, { size: 32 }), uri],
+ args: [account, nameHex, uri],
functionName: 'UD__mintCharacter',
});
- const nameHex = stringToHex(name, { size: 32 });
- const simulatedTx = await worldContract.simulate.UD__mintCharacter([
- account,
- nameHex,
- uri,
- ]);
-
const characterId = simulatedTx.result;
const tx = await worldContract.write.UD__mintCharacter([
@@ -589,6 +639,7 @@ export function createSystemCalls(
endTurn,
enterGame,
equipItems,
+ levelCharacter,
mintCharacter,
move,
rollStats,
diff --git a/packages/client/src/pages/Character.tsx b/packages/client/src/pages/Character.tsx
index 7907b0ac8..22d9e9367 100644
--- a/packages/client/src/pages/Character.tsx
+++ b/packages/client/src/pages/Character.tsx
@@ -38,6 +38,7 @@ import { EditCharacterModal } from '../components/EditCharacterModal';
import { ItemCard } from '../components/ItemCard';
import { ItemEquipModal } from '../components/ItemEquipModal';
import { Level } from '../components/Level';
+import { LevelingPanel } from '../components/LevelingPanel';
import { useCharacter } from '../contexts/CharacterContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
@@ -308,20 +309,38 @@ export const CharacterPage = (): JSX.Element => {
const maxItemsEquipped = equippedWeapons.length === MAX_EQUIPPED_WEAPONS;
+ const currentLevelXpRequirement =
+ useComponentValue(
+ Levels,
+ character
+ ? encodeEntity(
+ { level: 'uint256' },
+ { level: BigInt(Number(character.level) - 1) },
+ )
+ : undefined,
+ )?.experience ?? BigInt(0);
+
const nextLevelXpRequirement =
useComponentValue(
Levels,
- encodeEntity(
- { level: 'uint256' },
- { level: BigInt(Number(character?.level ?? 0) + 1) },
- ),
+ character
+ ? encodeEntity({ level: 'uint256' }, { level: BigInt(character.level) })
+ : undefined,
)?.experience ?? BigInt(0);
const levelPercent = useMemo(() => {
if (!character) return 0;
+
+ const xpSinceLastLevel =
+ BigInt(character.experience) - currentLevelXpRequirement;
const percent =
- (100 * Number(character.experience)) / Number(nextLevelXpRequirement);
+ (100 * Number(xpSinceLastLevel)) / Number(nextLevelXpRequirement);
return percent > 100 ? 100 : percent;
+ }, [character, currentLevelXpRequirement, nextLevelXpRequirement]);
+
+ const canLevel = useMemo(() => {
+ if (!character) return false;
+ return BigInt(character.experience) >= nextLevelXpRequirement;
}, [character, nextLevelXpRequirement]);
if (isLoadingCharacter) {
@@ -416,42 +435,7 @@ export const CharacterPage = (): JSX.Element => {
px={6}
rowStart={{ base: 2, sm: 2, md: 2, lg: 1, xl: 1 }}
>
-
-
-
- My Stats
-
-
- Ability Points: 3
-
-
-
- Base
-
-
-
- HP - Hit
-
- {character.currentHp}/{character.baseHp}
-
-
-
-
- STR - Strength
- {character.strength}
-
-
-
- AGI - Agility
- {character.agility}
-
-
-
- INT - Intelligence
- {character.intelligence}
-
-
-
+
{
- Level 1
+ Level {character.level}
Date: Tue, 30 Jul 2024 13:47:05 -0600
Subject: [PATCH 4/4] Fix ability points bug
---
packages/client/src/components/LevelingPanel.tsx | 2 ++
packages/client/src/pages/Character.tsx | 1 +
packages/contracts/worlds.json | 4 ++--
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/client/src/components/LevelingPanel.tsx b/packages/client/src/components/LevelingPanel.tsx
index 96e5f2aa2..454337d44 100644
--- a/packages/client/src/components/LevelingPanel.tsx
+++ b/packages/client/src/components/LevelingPanel.tsx
@@ -32,6 +32,8 @@ export const LevelingPanel = ({
useEffect(() => {
if (canLevel) {
setAbilityPoints(2);
+ } else {
+ setAbilityPoints(0);
}
setNewAgility(character.agility);
setNewIntelligence(character.intelligence);
diff --git a/packages/client/src/pages/Character.tsx b/packages/client/src/pages/Character.tsx
index 22d9e9367..430723d4c 100644
--- a/packages/client/src/pages/Character.tsx
+++ b/packages/client/src/pages/Character.tsx
@@ -340,6 +340,7 @@ export const CharacterPage = (): JSX.Element => {
const canLevel = useMemo(() => {
if (!character) return false;
+ if (nextLevelXpRequirement === BigInt(0)) return false;
return BigInt(character.experience) >= nextLevelXpRequirement;
}, [character, nextLevelXpRequirement]);
diff --git a/packages/contracts/worlds.json b/packages/contracts/worlds.json
index fe867c3e7..009bbe6e5 100644
--- a/packages/contracts/worlds.json
+++ b/packages/contracts/worlds.json
@@ -3,7 +3,7 @@
"address": "0xec49f248866357cc892edc7d90475e63c8cc33ee"
},
"84532": {
- "address": "0xdf2332face5011cf84e9a97e3903ab786cd2ac6e",
- "blockNumber": 13130230
+ "address": "0x5952c0700e7d46f8d26967ccd4b995a644c38464",
+ "blockNumber": 13299624
}
}
\ No newline at end of file