Live location tracking and playback for the game "manhunt"
1import React, { useCallback, useEffect, useState } from "react";
2import "@fontsource/bungee";
3import {
4 IconBuildingBroadcastTowerFilled,
5 IconHexagonPlusFilled,
6 IconClockFilled
7} from "@tabler/icons-react";
8import { commands, GameSettings, PlayerProfile } from "@/bindings";
9import ProfilePicture from "./ProfilePicture";
10import LoadingCover from "./LoadingCover";
11import { message } from "@tauri-apps/plugin-dialog";
12
13export const defaultSettings: () => GameSettings = () => ({
14 random_seed: Math.floor(Math.random() * 2 ** 32),
15 hiding_time_seconds: 60 * 5,
16 ping_start: "Instant",
17 ping_minutes_interval: 10,
18 powerup_start: "Instant",
19 powerup_chance: 60,
20 powerup_minutes_cooldown: 1,
21 powerup_locations: []
22});
23
24const defaultProfile: PlayerProfile = {
25 display_name: "",
26 pfp_base64: null
27};
28
29export default function MenuScreen() {
30 const [profile, setProfile] = useState<PlayerProfile>(defaultProfile);
31 const [loadingCover, setLoadingCover] = useState<boolean>(false);
32
33 useEffect(() => {
34 let cancel = false;
35 commands.getProfile().then((profile) => {
36 if (!cancel) {
37 setProfile(profile);
38 }
39 });
40 return () => {
41 cancel = true;
42 };
43 }, [setProfile]);
44
45 const startLobby = () => {
46 setLoadingCover(true);
47 setTimeout(() => {
48 commands.startLobby(null, defaultSettings()).catch(() => {
49 setLoadingCover(false);
50 });
51 }, 100);
52 };
53
54 const joinLobby = () => {
55 setLoadingCover(true);
56 const code = window.prompt("Enter join code");
57 if (!code) {
58 setLoadingCover(false);
59 return;
60 }
61 const cleanedCode = code.toUpperCase().trim();
62 commands
63 .checkRoomCode(cleanedCode)
64 .then((valid) => {
65 if (valid) {
66 commands.startLobby(cleanedCode, defaultSettings()).catch(() => {
67 setLoadingCover(false);
68 });
69 } else {
70 message("Invalid Join Code", { kind: "error", title: "Failed to Join" });
71 setLoadingCover(false);
72 }
73 })
74 .catch(() => {
75 setLoadingCover(false);
76 });
77 };
78
79 const onEditName = () => {
80 const newName = window.prompt("Enter New Name");
81 if (!newName) {
82 return;
83 }
84
85 const newProfile = { ...profile, display_name: newName };
86 commands.updateProfile(newProfile);
87 setProfile(newProfile);
88 };
89
90 const onEditPicture = () => {
91 setLoadingCover(true);
92 commands
93 .createProfilePicture()
94 .then((newPic) => {
95 if (newPic) {
96 const newProfile = { ...profile, pfp_base64: newPic };
97 commands.updateProfile(newProfile);
98 setProfile(newProfile);
99 }
100 })
101 .finally(() => {
102 setLoadingCover(false);
103 });
104 };
105
106 return (
107 <>
108 <header>
109 <ProfilePicture
110 onClick={onEditPicture}
111 fallbackName={profile.display_name}
112 src={profile.pfp_base64}
113 />
114 <span className="grow" onClick={onEditName}>
115 Hello, {profile.display_name}
116 </span>
117 </header>
118 <main className="menu">
119 <button onClick={startLobby}>
120 <IconBuildingBroadcastTowerFilled size="5em" />
121 Start Lobby
122 </button>
123 <button onClick={joinLobby}>
124 <IconHexagonPlusFilled size="2.5em" />
125 Join Lobby
126 </button>
127 <button>
128 <IconClockFilled size="1.5em" />
129 Past Games
130 </button>
131 </main>
132 <LoadingCover show={loadingCover} />
133 </>
134 );
135}