Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f5921c9
added bytes32 playerId
MrDeadCe11 Jun 26, 2024
86111e9
you can now get the token id from the characterId"
MrDeadCe11 Jun 27, 2024
c66e016
implemented monster spawning and mobID system
MrDeadCe11 Jun 27, 2024
fadfaf5
can now look up mob stats with the entityId
MrDeadCe11 Jun 27, 2024
6885be3
final tweaks to id system
MrDeadCe11 Jun 27, 2024
08edbc9
some access control tweaks
MrDeadCe11 Jun 27, 2024
3151811
ch-ch-ch-CHANGES
MrDeadCe11 Jun 30, 2024
0e49b2e
Merge branch 'main' into feat/combat-system
MrDeadCe11 Jun 30, 2024
f1aed4b
giant freaking commit. base physical weapon attack implemented (not …
MrDeadCe11 Jul 1, 2024
304332f
added missing files
MrDeadCe11 Jul 1, 2024
c533c09
moved apply equipment bonuses to items system
MrDeadCe11 Jul 1, 2024
95b396b
some name changes and import changes
MrDeadCe11 Jul 1, 2024
1c854f3
weapons and armor will now remove stat bonus when unequipped
MrDeadCe11 Jul 2, 2024
77821d7
moved item equipping out of items system into its own system
MrDeadCe11 Jul 2, 2024
16ccbaa
tested apply stats
MrDeadCe11 Jul 2, 2024
1fa6632
Merge branch 'main' into feat/combat-system
ECWireless Jul 2, 2024
05e6303
store token id in characters table
MrDeadCe11 Jul 3, 2024
e34567c
added missing files
MrDeadCe11 Jul 3, 2024
9a21fc5
Merge branch 'feat/combat-system' of https://github.com/raid-guild/ul…
MrDeadCe11 Jul 3, 2024
fdcab92
testing combat system execute action is tested. need to store results
MrDeadCe11 Jul 3, 2024
ad3209d
we're doing some damage
MrDeadCe11 Jul 3, 2024
b32cb68
added map system util
MrDeadCe11 Jul 3, 2024
5bbe5c2
Feat/5 add monster generation (#51)
MrDeadCe11 Jul 6, 2024
5ccaae5
added isParticipant view function and some more tests
MrDeadCe11 Jul 6, 2024
80c4c51
missing files
MrDeadCe11 Jul 6, 2024
2cbd6b8
some verification checks on starting combat and ending turns
MrDeadCe11 Jul 6, 2024
11dd56f
added valid pve checks and fixed a test
MrDeadCe11 Jul 6, 2024
e2287cb
more test coverage
MrDeadCe11 Jul 6, 2024
2dc6a02
switched to trakking currnetHp
MrDeadCe11 Jul 7, 2024
9fd993b
combat math somewhat working.
MrDeadCe11 Jul 7, 2024
854a44e
round ends if all attackers are dead
MrDeadCe11 Jul 7, 2024
99f6e3c
Merge branch 'main' into feat/combat-system
ECWireless Jul 7, 2024
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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.20.2
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"solidity.compileUsingRemoteVersion": "latest"
"solidity.compileUsingRemoteVersion": "v0.8.26+commit.8a97fa7a"
}
4 changes: 2 additions & 2 deletions packages/client/src/components/MapPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const MapPanel = (): JSX.Element => {
throw new Error('Character not found.');
}

const success = await spawn(BigInt(character.characterId));
const success = await spawn(character.characterId);

if (!success) {
throw new Error('Contract call failed');
Expand Down Expand Up @@ -136,7 +136,7 @@ export const MapPanel = (): JSX.Element => {
break;
}

const success = await move(BigInt(character.characterId), newX, newY);
const success = await move(character.characterId, newX, newY);

if (!success) {
throw new Error('Contract call failed');
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/StatsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const StatsPanel = (): JSX.Element => {
}

const { goldBalance, image, name } = character;
const { agility, experience, hitPoints, intelligence, strength } =
const { agility, experience, intelligence, maxHitPoints, strength } =
characterStats;

return (
Expand All @@ -76,7 +76,7 @@ export const StatsPanel = (): JSX.Element => {
</Text>
</GridItem>
<GridItem>
<Text>{hitPoints}</Text>
<Text>{maxHitPoints}</Text>
</GridItem>
<GridItem>
<Text fontWeight="bold" size="lg">
Expand Down
169 changes: 116 additions & 53 deletions packages/client/src/components/TileDetailsPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Avatar,
Box,
Flex,
Grid,
Expand All @@ -10,32 +11,39 @@ import {
import { useComponentValue, useEntityQuery } from '@latticexyz/react';
import {
Entity,
getComponentValue,
getComponentValueStrict,
Has,
HasValue,
} from '@latticexyz/recs';
import { decodeEntity, encodeEntity } from '@latticexyz/store-sync/recs';
import { useEffect, useState } from 'react';
import { encodeEntity } from '@latticexyz/store-sync/recs';
import { useCallback, useEffect, useState } from 'react';
import { IoIosArrowForward } from 'react-icons/io';
import { formatEther, getContract, hexToString } from 'viem';
import {
bytesToHex,
formatEther,
getContract,
hexToBytes,
hexToString,
} from 'viem';

import { useCharacter } from '../../contexts/CharacterContext';
import { useMUD } from '../../contexts/MUDContext';
import { fetchMetadataFromUri, uriToHttp } from '../../utils/helpers';
import type { Character } from '../../utils/types';
import { MONSTERS } from './data';
import type { Character, Monster } from '../../utils/types';

const ROW_HEIGHT = { base: 5, md: 8, lg: 10 };

export const TileDetailsPanel = (): JSX.Element => {
const {
components: { Characters, CharacterStats, Position, Spawned },
components: { Characters, Mobs, Position, Spawned, Stats },
delegatorAddress,
network: { publicClient, worldContract },
} = useMUD();
const { character } = useCharacter();

const [otherPlayers, setOtherPlayers] = useState<Character[]>([]);
const [monsters, setMonsters] = useState<Monster[]>([]);

const characterPosition = useComponentValue(
Position,
Expand All @@ -45,32 +53,26 @@ export const TileDetailsPanel = (): JSX.Element => {
),
);

const characterEntities = useEntityQuery([
const allEntities = useEntityQuery([
Has(Spawned),
Has(Characters),
Has(CharacterStats),
HasValue(Position, {
x: characterPosition?.x,
y: characterPosition?.y,
}),
]);

useEffect(() => {
(async (): Promise<void> => {
const getOtherCharacters = useCallback(
async (entities: Entity[]): Promise<void> => {
if (!(delegatorAddress && publicClient && worldContract)) return;

const characters = await Promise.all(
characterEntities.map(async (entity: Entity) => {
const characters: Character[] = await Promise.all(
entities.map(async (entity: Entity) => {
const characterData = getComponentValueStrict(Characters, entity);
const characterStats = getComponentValueStrict(
CharacterStats,
entity,
);
const characterStats = getComponentValueStrict(Stats, entity);

const characterId = decodeEntity(
{ characterId: 'uint256' },
entity,
).characterId.toString();
const entityBytes = hexToBytes(entity.toString() as `0x${string}`);
const tokenBytes = entityBytes.slice(20);
const tokenId = BigInt(bytesToHex(tokenBytes)).toString();

const characterTokenAddress =
await worldContract.read.UD__getCharacterToken();
Expand Down Expand Up @@ -102,7 +104,7 @@ export const TileDetailsPanel = (): JSX.Element => {
});

const metadataURI = await characterToken.read.tokenURI([
BigInt(characterId),
BigInt(tokenId),
]);

const fetachedMetadata = await fetchMetadataFromUri(
Expand Down Expand Up @@ -143,37 +145,93 @@ export const TileDetailsPanel = (): JSX.Element => {

return {
...fetachedMetadata,
goldBalance: formatEther(BigInt(goldBalance)).toString(),
agility: characterStats?.agility.toString() ?? '0',
experience: characterStats?.experience.toString() ?? '0',
characterClass: characterData.class,
characterId,
hitPoints: characterStats?.hitPoints.toString() ?? '0',
characterId: entity,
goldBalance: formatEther(BigInt(goldBalance)).toString(),
experience: characterStats?.experience.toString() ?? '0',
intelligence: characterStats?.intelligence.toString() ?? '0',
maxHitPoints: characterStats?.maxHitPoints.toString() ?? '0',
level: characterStats?.level.toString() ?? '0',
locked: characterData.locked,
name: hexToString(characterData.name as `0x${string}`, {
size: 32,
}),
owner: characterData.owner,
strength: characterStats?.strength.toString() ?? '0',
};
tokenId,
} as Character;
}),
);

setOtherPlayers(characters.filter(c => c.owner !== delegatorAddress));
},
[Characters, Stats, delegatorAddress, publicClient, worldContract],
);

const getMonsters = useCallback(
async (entities: Entity[]): Promise<void> => {
const monsterAndMobIds = entities.map(entity => {
const entityBytes = hexToBytes(entity.toString() as `0x${string}`);
const mobIdBytes = entityBytes.slice(0, 4);
return {
mobId: BigInt(bytesToHex(mobIdBytes)).toString(),
monsterId: entity,
};
});

const _monsters: Monster[] = await Promise.all(
monsterAndMobIds.map(async monsterAndMobId => {
const { monsterId, mobId } = monsterAndMobId;
const mobData = getComponentValueStrict(
Mobs,
encodeEntity({ mobId: 'uint256' }, { mobId: BigInt(mobId) }),
);
const monsterStats = getComponentValueStrict(Stats, monsterId);

const { mobMetadata: metadataURI } = mobData;

const fetachedMetadata = await fetchMetadataFromUri(
uriToHttp(metadataURI)[0],
);

return {
level: monsterStats.level.toString(),
mobId,
monsterId,
...fetachedMetadata,
};
}),
);

setMonsters(_monsters);
},
[Mobs, Stats],
);

useEffect(() => {
(async (): Promise<void> => {
if (!allEntities) return;

const characterEntities: Entity[] = [];
const monsterEntities: Entity[] = [];

await Promise.all(
allEntities.map(async entity => {
const characterData = getComponentValue(Characters, entity);

if (characterData) {
characterEntities.push(entity);
} else {
monsterEntities.push(entity);
}
}),
);

await getOtherCharacters(characterEntities);
await getMonsters(monsterEntities);
})();
}, [
character,
characterEntities,
characterPosition,
Characters,
CharacterStats,
delegatorAddress,
Position,
publicClient,
Spawned,
worldContract,
]);
}, [allEntities, Characters, getMonsters, getOtherCharacters]);

return (
<Box>
Expand All @@ -198,12 +256,18 @@ export const TileDetailsPanel = (): JSX.Element => {
</Grid>
<Grid gap={5} mt={1} templateColumns="repeat(4, 1fr)">
<GridItem colSpan={2}>
{MONSTERS.map((monster, i) => (
<MonsterRow
key={`tile-monster-${i}-${monster.name}`}
monster={monster}
/>
))}
{monsters.length > 0 &&
monsters.map((monster, i) => (
<MonsterRow
key={`tile-monster-${i}-${monster.name}`}
monster={monster}
/>
))}
{monsters.length === 0 && (
<Text size={{ base: '2xs', lg: 'sm' }}>
No monsters in this area
</Text>
)}
</GridItem>

{otherPlayers.length > 0 && (
Expand Down Expand Up @@ -239,10 +303,11 @@ export const TileDetailsPanel = (): JSX.Element => {
);
};

const MonsterRow = ({ monster }: { monster: (typeof MONSTERS)[0] }) => {
const { color, level, name } = monster;
const MonsterRow = ({ monster }: { monster: Monster }) => {
const { level, name } = monster;

const isFighting = monster.name === 'Green Slime';
const color = 'red';
const isFighting = false;

return (
<HStack
Expand Down Expand Up @@ -281,14 +346,13 @@ const PlayerRow = ({ player }: { player: Character }) => {
const { name } = player;

return (
<HStack h={ROW_HEIGHT} justifyContent="start">
<HStack h={ROW_HEIGHT} justifyContent="start" spacing={4}>
<Text size={{ base: '3xs', sm: '2xs', md: 'sm', lg: 'md' }}>{name}</Text>
<Avatar size="xs" src={player.image} />
</HStack>
);
};

// TODO: Remove when character level is dynamic
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const PlayerLevelRow = ({ player }: { player: Character }) => {
const isMobile = useBreakpointValue({ base: true, md: false });

Expand All @@ -303,8 +367,7 @@ const PlayerLevelRow = ({ player }: { player: Character }) => {
_hover={{ borderBottom: '1px solid', cursor: 'pointer' }}
>
<Text size={{ base: '4xs', sm: '3xs', md: 'xs', lg: 'sm' }}>
{/* TODO: Make level dynamic */}
Level 1
Level {player.level}
</Text>
<IoIosArrowForward size={isMobile ? 10 : 20} />
</Flex>
Expand Down
Loading