import { useEffect, useState } from 'react'; import { api, get } from '../lib/api'; import { PageHeader } from '../components/PageHeader'; import { useToast } from '../components/Toast'; import { Plus, RefreshCw, Trash } from '../components/Icons'; interface ServerSettings { signupsDisabled: boolean; globalFriendsEnabled: boolean; weeklyChallengesCompletedRequired: boolean; profanityFilterDisabled: boolean; allAvatarItemsOwned: boolean; updatedAt: string; } interface WeeklyChallenge { index: number; name: string; config: string; description: string; tooltip: string; } interface WeeklyChallengeSettings { completedRequired: boolean; challenges: WeeklyChallenge[]; reward: WeeklyReward; updatedAt: string; } interface WeeklyReward { giftDropId: number; xp: number; tokens: number; level: number; giftContext: number; giftRarity: number; avatarItemDesc: string; consumableItemDesc: string; equipmentPrefabName: string; equipmentModificationGuid: string; } interface RewardOption { kind: 'avatar' | 'consumable' | 'equipment'; label: string; giftDropId: number; avatarItemDesc: string; consumableItemDesc: string; equipmentPrefabName: string; equipmentModificationGuid: string; } interface RewardOptions { avatarItems: RewardOption[]; consumables: RewardOption[]; equipment: RewardOption[]; } interface WeeklyChallengeTypeOption { value: number; label: string; note: string; } const WEEKLY_CHALLENGE_TYPES: WeeklyChallengeTypeOption[] = [ { value: 0, label: 'Any challenge event', note: 'Base Challenge' }, { value: 2, label: 'Timed buffer', note: 'TimedBufferChallenge' }, { value: 3, label: 'Dynamic float arithmetic', note: 'DynamicFloatArithmeticChallenge' }, { value: 4, label: 'Dynamic int arithmetic', note: 'DynamicIntArithmeticChallenge' }, { value: 6, label: 'Required event type', note: 'RequiredEventTypeChallenge' }, { value: 7, label: 'Required room scene location', note: 'RequiredRoomSceneLocationChallenge' }, { value: 8, label: 'Required enemy type', note: 'RequiredEnemyTypeChallenge' }, { value: 9, label: 'Bool var equals', note: 'BoolVarEqualsChallenge' }, { value: 11, label: 'Disc golf under par', note: 'DiscGolfFinishUnderParChallenge' }, { value: 12, label: 'Required game mode activity', note: 'RequiredGameModeActivityChallenge' }, { value: 13, label: 'Complete game without', note: 'CompleteGameWithoutChallenge' }, { value: 14, label: 'Required gesture', note: 'RequiredGestureChallenge' }, { value: 15, label: 'Hitstreak', note: 'HitstreakChallenge' }, { value: 16, label: 'Hitstreak count', note: 'HitstreakCountChallenge' }, ]; const WEEKLY_TYPE_BY_VALUE = new Map(WEEKLY_CHALLENGE_TYPES.map((type) => [type.value, type])); const DEFAULT_WEEKLY_CHALLENGE_TYPE = 0; const MAX_WEEKLY_CHALLENGE_AMOUNT = 1000; function clampWeeklyAmount(value: number): number { if (!Number.isFinite(value)) return 1; return Math.min(MAX_WEEKLY_CHALLENGE_AMOUNT, Math.max(1, Math.trunc(value))); } function buildWeeklyChallengeConfig(type: number, amount: number): string { const safeType = WEEKLY_TYPE_BY_VALUE.has(type) ? type : DEFAULT_WEEKLY_CHALLENGE_TYPE; const target = clampWeeklyAmount(amount); const child = { ct: safeType }; if (target <= 1) return JSON.stringify(child); return JSON.stringify({ ct: 1, ctc: [child], t: target, cc: 0 }); } function parseWeeklyChallengeConfig(config: string): { type: number; amount: number } { try { const parsed = JSON.parse(config) as unknown; if (!parsed || typeof parsed !== 'object') throw new Error('not an object'); const root = parsed as { ct?: unknown; ctc?: unknown; t?: unknown }; const rootType = typeof root.ct === 'number' ? root.ct : DEFAULT_WEEKLY_CHALLENGE_TYPE; if (rootType === 1 && Array.isArray(root.ctc) && root.ctc.length > 0) { const child = root.ctc[0] as { ct?: unknown }; const childType = typeof child?.ct === 'number' && WEEKLY_TYPE_BY_VALUE.has(child.ct) ? child.ct : DEFAULT_WEEKLY_CHALLENGE_TYPE; const amount = typeof root.t === 'number' ? root.t : 1; return { type: childType, amount: clampWeeklyAmount(amount) }; } return { type: WEEKLY_TYPE_BY_VALUE.has(rootType) ? rootType : DEFAULT_WEEKLY_CHALLENGE_TYPE, amount: 1, }; } catch { return { type: DEFAULT_WEEKLY_CHALLENGE_TYPE, amount: 1 }; } } interface DiscoveredGameConfigSettings { friendsPostGamePromptUnderFriendCount: number; friendsSuggestFriendCodeOnFriendsScreenCount: number; screensForceVerification: boolean; vrForceVerification: boolean; rewardsUseRewardSelection: boolean; rewardsSelectionTimeout: number; roomDetailsPhotoRollEnabled: boolean; loadingNetworkTimeout: number; runningNetworkTimeout: number; synchronizedFieldRemoveDefaultEntries: boolean; renderingDisableSrpBatcher: boolean; splitTestSoftOverrides: string; splitTestHardOverrides: string; splitTestSegmentProbabilities: string; updatedAt: string; } export function Settings({ embedded }: { embedded?: boolean } = {}) { const [settings, setSettings] = useState(null); const [weekly, setWeekly] = useState(null); const [gameConfigs, setGameConfigs] = useState(null); const [rewardOptions, setRewardOptions] = useState({ avatarItems: [], consumables: [], equipment: [], }); const [err, setErr] = useState(null); const [busy, setBusy] = useState(false); const [weeklyBusy, setWeeklyBusy] = useState(false); const [gameConfigBusy, setGameConfigBusy] = useState(false); const toast = useToast(); const load = async () => { try { const [s, w, rewards, configs] = await Promise.all([ get('/settings'), get('/settings/weekly-challenges'), get('/settings/weekly-challenges/reward-options'), get('/settings/gameconfigs'), ]); setSettings(s); setWeekly(w); setRewardOptions(rewards); setGameConfigs(configs); setErr(null); } catch (e) { setErr((e as Error).message); } }; useEffect(() => { void load(); }, []); const toggleSignups = async () => { if (!settings) return; const next = !settings.signupsDisabled; const verb = next ? 'disable' : 'enable'; if (!confirm(`Really ${verb} new account creation? Existing players keep their access either way.`)) return; setBusy(true); try { const updated = await api('/settings/signups', { method: 'POST', body: { Disabled: next }, }); setSettings(updated); toast.push(next ? 'New signups blocked.' : 'New signups allowed.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(false); } }; const toggleGlobalFriends = async () => { if (!settings) return; const next = !settings.globalFriendsEnabled; const verb = next ? 'make everyone friends' : 'turn off all-friends'; if (!confirm( next ? 'Make every account friends with every other account? No friend rows are written — it\'s synthesized live, and connected players refresh instantly. Toggle off to revert.' : `Really ${verb}? Players will revert to their real friends list.`, )) return; setBusy(true); try { const updated = await api('/settings/global-friends', { method: 'POST', body: { Enabled: next }, }); setSettings(updated); toast.push(next ? 'Everyone is now friends — players refreshing live.' : 'All-friends turned off.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(false); } }; const toggleAllAvatarItems = async () => { if (!settings) return; const next = !settings.allAvatarItemsOwned; if (!confirm( next ? 'Give every account every avatar item? The whole wardrobe catalog (plus all hair dyes) shows as owned for everyone. Nothing is written to inventories — it\'s synthesized live, and reverts instantly when you toggle it off. Players see it next time the avatar menu opens.' : 'Turn off all-items? Players revert to only the avatar items they actually own.', )) return; setBusy(true); try { const updated = await api('/settings/all-avatar-items', { method: 'POST', body: { Enabled: next }, }); setSettings(updated); toast.push(next ? 'Everyone now owns all avatar items.' : 'All-items turned off.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(false); } }; const toggleProfanity = async () => { if (!settings) return; const next = !settings.profanityFilterDisabled; const verb = next ? 'turn OFF the profanity filter' : 'turn the profanity filter back on'; if (!confirm( next ? 'Turn off the profanity filter? Room names, invention names and chat will no longer be censored by the server. Toggle back on any time.' : `Really ${verb}? Text will be filtered again server-side.`, )) return; setBusy(true); try { const updated = await api('/settings/profanity', { method: 'POST', body: { Disabled: next }, }); setSettings(updated); toast.push(next ? 'Profanity filter disabled.' : 'Profanity filter enabled.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(false); } }; const updateWeeklyChallenge = (index: number, patch: Partial) => { setWeekly((current) => { if (!current) return current; return { ...current, challenges: current.challenges.map((challenge, i) => i === index ? { ...challenge, ...patch } : challenge), }; }); }; const addWeeklyChallenge = () => { setWeekly((current) => { if (!current) return current; return { ...current, challenges: [ ...current.challenges, { index: current.challenges.length, name: 'New weekly challenge', config: buildWeeklyChallengeConfig(DEFAULT_WEEKLY_CHALLENGE_TYPE, 1), description: '', tooltip: '', }, ], }; }); }; const removeWeeklyChallenge = (index: number) => { setWeekly((current) => { if (!current) return current; const next = current.challenges.filter((_, i) => i !== index); return { ...current, challenges: next.map((challenge, i) => ({ ...challenge, index: i })) }; }); }; const updateWeeklyChallengeConfig = ( index: number, patch: Partial<{ type: number; amount: number }>, ) => { const challenge = weekly?.challenges[index]; if (!challenge) return; const current = parseWeeklyChallengeConfig(challenge.config); const next = { ...current, ...patch }; updateWeeklyChallenge(index, { config: buildWeeklyChallengeConfig(next.type, next.amount), }); }; const updateWeeklyReward = (patch: Partial) => { setWeekly((current) => current ? { ...current, reward: { ...current.reward, ...patch }, } : current); }; const rewardNumber = (key: keyof WeeklyReward, value: string) => { const n = Number.parseInt(value, 10); updateWeeklyReward({ [key]: Number.isFinite(n) ? Math.max(0, n) : 0 } as Partial); }; const allRewardOptions = [ ...rewardOptions.avatarItems.map((option, index) => ({ option, value: `avatar:${index}` })), ...rewardOptions.consumables.map((option, index) => ({ option, value: `consumable:${index}` })), ...rewardOptions.equipment.map((option, index) => ({ option, value: `equipment:${index}` })), ]; const selectedRewardValue = weekly ? allRewardOptions.find(({ option }) => option.avatarItemDesc === weekly.reward.avatarItemDesc && option.consumableItemDesc === weekly.reward.consumableItemDesc && option.equipmentPrefabName === weekly.reward.equipmentPrefabName && option.equipmentModificationGuid === weekly.reward.equipmentModificationGuid)?.value ?? ( weekly.reward.avatarItemDesc || weekly.reward.consumableItemDesc || weekly.reward.equipmentPrefabName || weekly.reward.equipmentModificationGuid ? 'current' : 'none' ) : 'none'; const selectedRewardOption = selectedRewardValue === 'current' ? weekly?.reward : allRewardOptions.find((entry) => entry.value === selectedRewardValue)?.option ?? null; const selectWeeklyReward = (value: string) => { if (value === 'none') { updateWeeklyReward({ giftDropId: 0, avatarItemDesc: '', consumableItemDesc: '', equipmentPrefabName: '', equipmentModificationGuid: '', }); return; } const selected = allRewardOptions.find((entry) => entry.value === value)?.option; if (!selected) return; updateWeeklyReward({ giftDropId: selected.giftDropId, avatarItemDesc: selected.avatarItemDesc, consumableItemDesc: selected.consumableItemDesc, equipmentPrefabName: selected.equipmentPrefabName, equipmentModificationGuid: selected.equipmentModificationGuid, }); }; const resetWeeklyDefaults = async () => { if (!confirm('Reset weekly challenges to the built-in defaults?')) return; setWeeklyBusy(true); try { const updated = await api('/settings/weekly-challenges', { method: 'POST', body: { CompletedRequired: true, Challenges: [] }, }); setWeekly(updated); setSettings((current) => current ? { ...current, weeklyChallengesCompletedRequired: updated.completedRequired, updatedAt: updated.updatedAt, } : current); toast.push('Weekly challenges reset.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setWeeklyBusy(false); } }; const saveWeeklyChallenges = async () => { if (!weekly) return; setWeeklyBusy(true); try { const updated = await api('/settings/weekly-challenges', { method: 'POST', body: { CompletedRequired: weekly.completedRequired, Challenges: weekly.challenges, Reward: weekly.reward, }, }); setWeekly(updated); setSettings((current) => current ? { ...current, weeklyChallengesCompletedRequired: updated.completedRequired, updatedAt: updated.updatedAt, } : current); toast.push('Weekly challenges saved.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setWeeklyBusy(false); } }; const updateGameConfigs = (patch: Partial) => { setGameConfigs((current) => current ? { ...current, ...patch } : current); }; const saveGameConfigs = async (reset = false) => { if (!gameConfigs && !reset) return; setGameConfigBusy(true); try { const body = reset ? defaultGameConfigs() : gameConfigs; const updated = await api('/settings/gameconfigs', { method: 'POST', body, }); setGameConfigs(updated); toast.push(reset ? 'GameConfig settings reset.' : 'GameConfig settings saved.', 'success'); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setGameConfigBusy(false); } }; const refreshBtn = ( ); return (
{embedded ? (
{refreshBtn}
) : ( )} {err && (
{err}
)} {!settings && !weekly && !err && (
Loading…
)} {settings && (

In-game signups

When disabled, the watch's account-creation flow returns an error to the player instead of minting a new account. Existing logins keep working — only brand-new signups are blocked.

Last changed {new Date(settings.updatedAt).toLocaleString()}.

{settings.signupsDisabled ? Signups disabled : Signups allowed}
)} {settings && (

Everyone is friends

When on, every account is treated as a friend of every other account, so players don't have to search and friend-request each other on a small server. Nothing is written to the database — it's synthesized live, and connected players get a refresh signal so their friends list updates without relogging. Turn it off to instantly revert to real friend lists. Blocks are still respected.

Last changed {new Date(settings.updatedAt).toLocaleString()}.

{settings.globalFriendsEnabled ? Everyone is friends : Normal friend lists}
)} {settings && (

Everyone owns all avatar items

When on, every account is reported as owning the entire avatar wardrobe catalog (plus all permanent hair dyes), so the store shows everything as "owned" and every item is equippable. Nothing is written to inventories — it's synthesized live, and turning it off instantly reverts each player to the items they actually own. Players pick up the change the next time the watch loads unlocked items (avatar menu open or next boot). Color variants and player-made custom items aren't included.

Last changed {new Date(settings.updatedAt).toLocaleString()}.

{settings.allAvatarItemsOwned ? Everyone owns everything : Normal wardrobes}
)} {settings && (

Profanity filter

The server-side filter behind api/sanitize/* that the watch calls before letting a player submit a room name, invention name, or chat message. Turn it off to stop censoring text entirely — every check returns "clean" and input is passed through unchanged. Takes effect immediately.

Last changed {new Date(settings.updatedAt).toLocaleString()}.

{settings.profanityFilterDisabled ? Filter off — nothing censored : Filter active}
)} {gameConfigs && ( saveGameConfigs(false)} onReset={() => saveGameConfigs(true)} /> )} {weekly && (

Weekly challenges

These are sent to the watch as the current challenge map. Players start each weekly challenge incomplete until their own progress is reported.

Last changed {new Date(weekly.updatedAt).toLocaleString()}.

Weekly reward

{weekly.reward.xp} XP · {weekly.reward.tokens} tokens
{selectedRewardOption && selectedRewardValue !== 'none' && (
{selectedRewardOption.avatarItemDesc && `avatar: ${selectedRewardOption.avatarItemDesc}`} {selectedRewardOption.consumableItemDesc && `consumable: ${selectedRewardOption.consumableItemDesc}`} {selectedRewardOption.equipmentPrefabName && `equipment: ${selectedRewardOption.equipmentPrefabName} / ${selectedRewardOption.equipmentModificationGuid}`}
)}
{weekly.challenges.map((challenge, index) => (
Challenge {index + 1}
updateWeeklyChallengeConfig(index, patch)} />
))}
)}
); } function WeeklyChallengeConfigForm({ challenge, onChange, }: { challenge: WeeklyChallenge; onChange: (patch: Partial<{ type: number; amount: number }>) => void; }) { const parsed = parseWeeklyChallengeConfig(challenge.config); const selected = WEEKLY_TYPE_BY_VALUE.get(parsed.type) ?? WEEKLY_TYPE_BY_VALUE.get(DEFAULT_WEEKLY_CHALLENGE_TYPE)!; return (
ct {selected.value} {selected.note} {parsed.amount > 1 && wrapped by ct 1 count}
{challenge.config}
); } function DiscoveredGameConfigsPanel({ settings, busy, onChange, onSave, onReset, }: { settings: DiscoveredGameConfigSettings; busy: boolean; onChange: (patch: Partial) => void; onSave: () => void; onReset: () => void; }) { const number = (key: keyof DiscoveredGameConfigSettings, value: string) => { const n = Number.parseFloat(value); onChange({ [key]: Number.isFinite(n) ? n : 0 } as Partial); }; return (

GameConfig

Typed keys confirmed in the 2020.12 client decomp.

Last changed {new Date(settings.updatedAt).toLocaleString()}.

Social

number('friendsPostGamePromptUnderFriendCount', v)} /> number('friendsSuggestFriendCodeOnFriendsScreenCount', v)} />

Account gates

onChange({ screensForceVerification: v })} /> onChange({ vrForceVerification: v })} />

Rewards

onChange({ rewardsUseRewardSelection: v })} /> number('rewardsSelectionTimeout', v)} />

Room details

onChange({ roomDetailsPhotoRollEnabled: v })} />

Networking

number('loadingNetworkTimeout', v)} /> number('runningNetworkTimeout', v)} />

Rendering / sync

onChange({ synchronizedFieldRemoveDefaultEntries: v })} /> onChange({ renderingDisableSrpBatcher: v })} />

Split tests

onChange({ splitTestSoftOverrides: v })} /> onChange({ splitTestHardOverrides: v })} /> onChange({ splitTestSegmentProbabilities: v })} />
); } function NumberConfig({ label, value, onChange, }: { label: string; value: number; onChange: (value: string) => void; }) { return ( ); } function BoolConfig({ label, checked, onChange, }: { label: string; checked: boolean; onChange: (value: boolean) => void; }) { return ( ); } function JsonConfig({ label, value, onChange, }: { label: string; value: string; onChange: (value: string) => void; }) { return (