Skip to content

Commit 39207e1

Browse files
authored
new onboarding flow (#2672)
* start new onboarding * show random realm hex view if not connected * fix issues * add new no account modal * add no account modal and not logged in message * need input to be > 0 * enable all modules * add account required on confirm popup * refactor for system call callback * refactor + remove console.log * remove console.log * rabbit comments * address comment * knip
1 parent 7689e84 commit 39207e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+714
-493
lines changed

.knip.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
"client/apps/game/src/ui/modules/chat/**",
2525

26+
"client/apps/game-docs/env.ts",
27+
2628
"config/**",
2729
"contracts/**",
2830

client/apps/game/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ yarn.lock
2626
dev-dist
2727
.env
2828
.env.local
29+
*.ts.timestamp-*.mjs

client/apps/game/src/hooks/context/dojo-context.tsx

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,25 @@ import { Position } from "@/types/position";
66
import { OnboardingContainer, StepContainer } from "@/ui/layouts/onboarding";
77
import { OnboardingButton } from "@/ui/layouts/onboarding-button";
88
import { CountdownTimer, LoadingScreen } from "@/ui/modules/loading-screen";
9-
import { ACCOUNT_CHANGE_EVENT, SpectateButton } from "@/ui/modules/onboarding/steps";
9+
import { SpectateButton } from "@/ui/modules/onboarding/steps";
1010
import { displayAddress } from "@/ui/utils/utils";
11+
import { getRandomRealmEntity } from "@/utils/realms";
1112
import { ContractAddress, SetupResult } from "@bibliothecadao/eternum";
12-
import { DojoContext, useQuery } from "@bibliothecadao/react";
13+
import { DojoContext } from "@bibliothecadao/react";
1314
import ControllerConnector from "@cartridge/connector/controller";
14-
import { HasValue, runQuery } from "@dojoengine/recs";
15+
import { getComponentValue, HasValue, runQuery } from "@dojoengine/recs";
1516
import { cairoShortStringToFelt } from "@dojoengine/torii-client";
1617
import { useAccount, useConnect } from "@starknet-react/core";
1718
import { ReactNode, useContext, useEffect, useMemo, useState } from "react";
1819
import { Account, AccountInterface, RpcProvider } from "starknet";
1920
import { Env, env } from "../../../env";
21+
import { useNavigateToHexView } from "../helpers/use-navigate";
22+
import { useNavigateToRealmViewByAccount } from "../helpers/use-navigate-to-realm-view-by-account";
23+
24+
export const NULL_ACCOUNT = {
25+
address: "0x0",
26+
privateKey: "0x0",
27+
} as const;
2028

2129
const requiredEnvs: (keyof Env)[] = [
2230
"VITE_PUBLIC_MASTER_ADDRESS",
@@ -81,7 +89,7 @@ export const DojoProvider = ({ children, value, backgroundImage }: DojoProviderP
8189
<DojoContextProvider
8290
value={value}
8391
masterAccount={masterAccount}
84-
controllerAccount={controllerAccount!}
92+
controllerAccount={controllerAccount || null}
8593
backgroundImage={backgroundImage}
8694
>
8795
{children}
@@ -100,12 +108,10 @@ const DojoContextProvider = ({
100108
controllerAccount: AccountInterface | null;
101109
backgroundImage: string;
102110
}) => {
103-
const setSpectatorMode = useUIStore((state) => state.setSpectatorMode);
104-
const isSpectatorMode = useUIStore((state) => state.isSpectatorMode);
105-
const showBlankOverlay = useUIStore((state) => state.setShowBlankOverlay);
106-
const setAddressName = useAddressStore((state) => state.setAddressName);
111+
useNavigateToRealmViewByAccount(value.components);
107112

108-
const { handleUrlChange } = useQuery();
113+
const showBlankOverlay = useUIStore((state) => state.showBlankOverlay);
114+
const setAddressName = useAddressStore((state) => state.setAddressName);
109115

110116
const currentValue = useContext(DojoContext);
111117
if (currentValue) throw new Error("DojoProvider can only be used once");
@@ -115,6 +121,8 @@ const DojoContextProvider = ({
115121

116122
const [accountsInitialized, setAccountsInitialized] = useState(false);
117123

124+
const navigateToHexView = useNavigateToHexView();
125+
118126
const [retries, setRetries] = useState(0);
119127

120128
const connectWallet = async () => {
@@ -127,14 +135,23 @@ const DojoContextProvider = ({
127135
}
128136
};
129137

138+
const [accountToUse, setAccountToUse] = useState<Account | AccountInterface>(
139+
new Account(value.network.provider.provider, NULL_ACCOUNT.address, NULL_ACCOUNT.privateKey),
140+
);
141+
130142
const onSpectatorModeClick = () => {
131-
setSpectatorMode(true);
132-
handleUrlChange(new Position({ x: 0, y: 0 }).toMapLocationUrl());
133-
window.dispatchEvent(new Event(ACCOUNT_CHANGE_EVENT));
134-
showBlankOverlay(false);
143+
const randomRealmEntity = getRandomRealmEntity(value.components);
144+
const position = randomRealmEntity && getComponentValue(value.components.Position, randomRealmEntity);
145+
position && navigateToHexView(new Position(position));
135146
};
136147

137-
const accountToUse = isSpectatorMode ? new Account(value.network.provider.provider, "0x0", "0x0") : controllerAccount;
148+
useEffect(() => {
149+
if (!controllerAccount) {
150+
setAccountToUse(new Account(value.network.provider.provider, NULL_ACCOUNT.address, NULL_ACCOUNT.privateKey));
151+
} else {
152+
setAccountToUse(controllerAccount);
153+
}
154+
}, [controllerAccount]);
138155

139156
useEffect(() => {
140157
const setUserName = async () => {
@@ -188,7 +205,7 @@ const DojoContextProvider = ({
188205
);
189206
}
190207

191-
if (!isConnected && !isConnecting && !controllerAccount && !isSpectatorMode) {
208+
if (!isConnected && !isConnecting && showBlankOverlay) {
192209
return (
193210
<>
194211
<CountdownTimer backgroundImage={backgroundImage} />
@@ -226,7 +243,7 @@ const DojoContextProvider = ({
226243
...value,
227244
masterAccount,
228245
account: {
229-
account: accountToUse as Account | AccountInterface,
246+
account: accountToUse,
230247
accountDisplay: displayAddress((accountToUse as Account | AccountInterface)?.address || ""),
231248
},
232249
}}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Position } from "@/types/position";
2+
import { getPlayerFirstRealm, getRandomRealmEntity } from "@/utils/realms";
3+
import { ClientComponents, ContractAddress } from "@bibliothecadao/eternum";
4+
import { getComponentValue } from "@dojoengine/recs";
5+
import { useAccount } from "@starknet-react/core";
6+
import { useEffect } from "react";
7+
import { NULL_ACCOUNT } from "../context/dojo-context";
8+
import { useNavigateToHexView } from "./use-navigate";
9+
10+
export const useNavigateToRealmViewByAccount = (components: ClientComponents) => {
11+
const navigateToHexView = useNavigateToHexView();
12+
const { account } = useAccount();
13+
14+
// navigate to random hex view if not connected or to player's first realm if connected
15+
useEffect(() => {
16+
if (!account) {
17+
const randomRealmEntity = getRandomRealmEntity(components);
18+
const position = randomRealmEntity ? getComponentValue(components.Position, randomRealmEntity) : undefined;
19+
navigateToHexView(new Position(position || { x: 0, y: 0 }));
20+
} else {
21+
const playerRealm = getPlayerFirstRealm(components, ContractAddress(account?.address || NULL_ACCOUNT.address));
22+
const position = playerRealm ? getComponentValue(components.Position, playerRealm) : undefined;
23+
position && navigateToHexView(new Position(position));
24+
}
25+
}, [account?.address]);
26+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Position } from "@/types/position";
2+
import { useQuery } from "@bibliothecadao/react";
3+
import { useUIStore } from "../store/use-ui-store";
4+
5+
export const useNavigateToHexView = () => {
6+
const showBlankOverlay = useUIStore((state) => state.setShowBlankOverlay);
7+
const setIsLoadingScreenEnabled = useUIStore((state) => state.setIsLoadingScreenEnabled);
8+
const setPreviewBuilding = useUIStore((state) => state.setPreviewBuilding);
9+
const { handleUrlChange } = useQuery();
10+
11+
return (position: Position) => {
12+
const url = position.toHexLocationUrl();
13+
14+
setIsLoadingScreenEnabled(true);
15+
showBlankOverlay(false);
16+
setPreviewBuilding(null);
17+
handleUrlChange(url);
18+
};
19+
};
20+
21+
export const useNavigateToMapView = () => {
22+
const showBlankOverlay = useUIStore((state) => state.setShowBlankOverlay);
23+
const setPreviewBuilding = useUIStore((state) => state.setPreviewBuilding);
24+
const { handleUrlChange, isMapView } = useQuery();
25+
const setIsLoadingScreenEnabled = useUIStore((state) => state.setIsLoadingScreenEnabled);
26+
27+
return (position: Position) => {
28+
if (!isMapView) {
29+
setIsLoadingScreenEnabled(true);
30+
}
31+
showBlankOverlay(false);
32+
setPreviewBuilding(null);
33+
handleUrlChange(position.toMapLocationUrl());
34+
};
35+
};
Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,33 @@
11
import { useUIStore } from "@/hooks/store/use-ui-store";
22
import { Position as PositionInterface } from "@/types/position";
33
import { UNDEFINED_STRUCTURE_ENTITY_ID } from "@/ui/constants";
4-
import { ContractAddress } from "@bibliothecadao/eternum";
5-
import { useDojo, usePlayerStructures, useQuery } from "@bibliothecadao/react";
4+
import { useDojo, useQuery } from "@bibliothecadao/react";
65
import { Entity, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs";
7-
import { useEffect, useMemo } from "react";
6+
import { useEffect } from "react";
87

98
export const useStructureEntityId = () => {
109
const {
1110
setup: {
12-
components: { Structure, Position, Owner },
11+
components: { Structure, Position },
1312
},
14-
account: { account },
1513
} = useDojo();
1614

17-
const { hexPosition, isMapView } = useQuery();
15+
const { hexPosition } = useQuery();
1816
const setStructureEntityId = useUIStore((state) => state.setStructureEntityId);
19-
const isSpectatorMode = useUIStore((state) => state.isSpectatorMode);
20-
const structureEntityId = useUIStore((state) => state.structureEntityId);
21-
22-
const address = isSpectatorMode ? ContractAddress("0x0") : ContractAddress(account.address);
23-
24-
const structures = usePlayerStructures(ContractAddress(account.address));
25-
26-
const defaultPlayerStructure = useMemo(() => {
27-
return structures[0];
28-
}, [structureEntityId, structures]);
2917

3018
useEffect(() => {
31-
const { x, y } = new PositionInterface({
19+
const position = new PositionInterface({
3220
x: hexPosition.col,
3321
y: hexPosition.row,
3422
}).getContract();
3523

36-
const structureEntity = runQuery([Has(Structure), HasValue(Position, { x, y })])
24+
const structureEntity = runQuery([Has(Structure), HasValue(Position, { x: position.x, y: position.y })])
3725
.values()
3826
.next().value;
3927

4028
const structure = getComponentValue(Structure, structureEntity ?? ("0" as Entity));
41-
const structureOwner = getComponentValue(Owner, structureEntity ?? ("0" as Entity));
42-
43-
const isOwner = structureOwner?.address === BigInt(address);
29+
const newStructureId = structure?.entity_id;
4430

45-
if (isMapView) {
46-
setStructureEntityId(
47-
isOwner ? structureOwner.entity_id : defaultPlayerStructure?.entity_id || UNDEFINED_STRUCTURE_ENTITY_ID,
48-
);
49-
} else {
50-
setStructureEntityId(structure?.entity_id || UNDEFINED_STRUCTURE_ENTITY_ID);
51-
}
52-
}, [defaultPlayerStructure, isMapView, hexPosition, address]);
31+
setStructureEntityId(newStructureId || UNDEFINED_STRUCTURE_ENTITY_ID);
32+
}, [hexPosition]);
5333
};

client/apps/game/src/hooks/store/use-ui-store.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,11 @@ interface UIStore {
6060
setShowMinimap: (show: boolean) => void;
6161
selectedPlayer: ContractAddress | null;
6262
setSelectedPlayer: (player: ContractAddress | null) => void;
63-
isSpectatorMode: boolean;
64-
setSpectatorMode: (enabled: boolean) => void;
6563
hasAcceptedToS: boolean;
6664
setHasAcceptedToS: (accepted: boolean) => void;
6765
showToS: boolean;
6866
setShowToS: (show: boolean) => void;
67+
setModal: (content: React.ReactNode | null, show: boolean) => void;
6968
}
7069

7170
export type AppStore = UIStore & PopupsStore & ThreeStore & BuildModeStore & RealmStore & WorldStore;
@@ -79,7 +78,9 @@ export const useUIStore = create(
7978
showBlurOverlay: false,
8079
setShowBlurOverlay: (show) => set({ showBlurOverlay: show }),
8180
showBlankOverlay: true,
82-
setShowBlankOverlay: (show) => set({ showBlankOverlay: show }),
81+
setShowBlankOverlay: (show) => {
82+
set({ showBlankOverlay: show });
83+
},
8384
isSideMenuOpened: true,
8485
toggleSideMenu: () => set((state) => ({ isSideMenuOpened: !state.isSideMenuOpened })),
8586
isSoundOn: localStorage.getItem("soundEnabled") ? localStorage.getItem("soundEnabled") === "true" : true,
@@ -125,15 +126,14 @@ export const useUIStore = create(
125126
setShowMinimap: (show: boolean) => set({ showMinimap: show }),
126127
selectedPlayer: null,
127128
setSelectedPlayer: (player: ContractAddress | null) => set({ selectedPlayer: player }),
128-
isSpectatorMode: false,
129-
setSpectatorMode: (enabled: boolean) => set({ isSpectatorMode: enabled }),
130129
hasAcceptedToS: localStorage.getItem("hasAcceptedToS") ? localStorage.getItem("hasAcceptedToS") === "true" : false,
131130
setHasAcceptedToS: (accepted: boolean) => {
132131
set({ hasAcceptedToS: accepted });
133132
localStorage.setItem("hasAcceptedToS", String(accepted));
134133
},
135134
showToS: false,
136135
setShowToS: (show: boolean) => set({ showToS: show }),
136+
setModal: (content: React.ReactNode | null, show: boolean) => set({ modalContent: content, showModal: show }),
137137
...createPopupsSlice(set, get),
138138
...createThreeStoreSlice(set, get),
139139
...createBuildModeStoreSlice(set),

client/apps/game/src/main.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useUIStore } from "./hooks/store/use-ui-store";
1818
import "./index.css";
1919
import GameRenderer from "./three/game-renderer";
2020
import { PWAUpdatePopup } from "./ui/components/pwa-update-popup";
21+
import { NoAccountModal } from "./ui/layouts/no-account-modal";
2122
import { LoadingScreen } from "./ui/modules/loading-screen";
2223
import { getRandomBackgroundImage } from "./ui/utils/utils";
2324
import { ETERNUM_CONFIG } from "./utils/config";
@@ -69,10 +70,23 @@ async function init() {
6970

7071
const setupResult = await setup(
7172
{ ...dojoConfig },
72-
{ vrfProviderAddress: env.VITE_PUBLIC_VRF_PROVIDER_ADDRESS, useBurner: env.VITE_PUBLIC_CHAIN === "local" },
73+
{
74+
vrfProviderAddress: env.VITE_PUBLIC_VRF_PROVIDER_ADDRESS,
75+
useBurner: env.VITE_PUBLIC_CHAIN === "local",
76+
},
77+
{
78+
onNoAccount: () => {
79+
state.setModal(null, false);
80+
state.setModal(<NoAccountModal />, true);
81+
},
82+
onError: (error) => {
83+
console.error("System call error:", error);
84+
// Handle other types of errors if needed
85+
},
86+
},
7387
);
7488

75-
const eternumConfig = await ETERNUM_CONFIG();
89+
const eternumConfig = ETERNUM_CONFIG();
7690
configManager.setDojo(setupResult.components, eternumConfig);
7791

7892
await initialSync(setupResult, state);

client/apps/game/src/three/helpers/location-manager.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,4 @@ export class LocationManager {
2424
this.updateUrlParams();
2525
return this.urlParams.has("row") && this.urlParams.has("col");
2626
}
27-
28-
public static updateUrl(url: string) {
29-
window.history.pushState({}, "", url);
30-
window.dispatchEvent(new Event("urlChanged"));
31-
}
3227
}

client/apps/game/src/three/scenes/hexception.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export default class HexceptionScene extends HexagonScene {
137137
this.loadBuildingModels();
138138
this.loadBiomeModels(900);
139139

140-
this.tileManager = new TileManager(this.dojo.components, this.dojo.network.provider, { col: 0, row: 0 });
140+
this.tileManager = new TileManager(this.dojo.components, this.dojo.systemCalls, { col: 0, row: 0 });
141141

142142
this.setup();
143143

@@ -304,13 +304,17 @@ export default class HexceptionScene extends HexagonScene {
304304

305305
const normalizedCoords = { col: hexCoords.col, row: hexCoords.row };
306306
const buildingType = this.buildingPreview?.getPreviewBuilding();
307+
308+
// Check if account exists before allowing actions
309+
const account = useAccountStore.getState().account;
307310
if (buildingType) {
308311
// if building mode
309312
if (!this.tileManager.isHexOccupied(normalizedCoords)) {
310313
this.clearBuildingMode();
311314
try {
312315
await this.tileManager.placeBuilding(
313-
useAccountStore.getState().account!,
316+
account!,
317+
this.state.structureEntityId,
314318
buildingType.type,
315319
normalizedCoords,
316320
buildingType.resource,

0 commit comments

Comments
 (0)