1 import React from 'react';
2 import { Button } from 'react-bootstrap';
3 import { useTranslation } from 'react-i18next';
5 import Icon from '../common/Icon';
6 import ToggleSwitch from '../common/ToggleSwitch';
13 } from '../../helpers/alttp-ram';
14 import { computeState } from '../../helpers/tracker';
15 import { useSNES } from '../../hooks/snes';
16 import { useTracker } from '../../hooks/tracker';
18 const AutoTracking = () => {
19 const [enabled, setEnabled] = React.useState(false);
20 const [prizeMap, setPrizeMap] = React.useState(buildPrizeMap());
29 const { config, setAutoState } = useTracker();
30 const { t } = useTranslation();
32 const enable = React.useCallback(() => {
37 const disable = React.useCallback(() => {
42 React.useEffect(() => {
43 const savedSettings = localStorage.getItem('tracker.settings');
45 const settings = JSON.parse(savedSettings);
46 if (settings.autoTrack) {
52 const saveSettings = React.useCallback((newSettings) => {
53 const savedSettings = localStorage.getItem('tracker.settings');
54 const settings = savedSettings
55 ? { ...JSON.parse(savedSettings), ...newSettings }
57 localStorage.setItem('tracker.settings', JSON.stringify(settings));
60 const toggle = React.useCallback(() => {
63 saveSettings({ autoTrack: false });
66 saveSettings({ autoTrack: true });
70 // poll game and push state
71 React.useEffect(() => {
72 if (!enabled || status.error || !status.connected || !status.device) return;
73 const updateState = () => {
74 const saveStart = WRAM_ADDR.SAVE_DATA;
75 const saveSize = SRAM_ADDR.INV_END;
76 sock.current.readWRAM(saveStart, saveSize, (data) => {
77 const computed = computeState(config, data, prizeMap);
78 setAutoState(computed);
81 const fetchPrizes = () => {
82 sock.current.readBytes(RAM_ADDR.PRIZE_MAP, 13, (prizes) => {
83 sock.current.readBytes(RAM_ADDR.CRYSTAL_MAP, 13, (crystals) => {
85 const newMap = buildPrizeMap(prizes, crystals);
86 return JSON.stringify(m) === JSON.stringify(newMap) ? m : newMap;
91 const checkInGame = () => {
92 sock.current.readWRAM(WRAM_ADDR.GAME_MODE, 1, (data) => {
93 if (IN_GAME_MODES.includes(data[0])) {
99 const timer = setInterval(checkInGame, 1000);
101 clearInterval(timer);
103 }, [enabled && !status.error && status.connected && status.device, config, prizeMap, sock]);
105 const statusMsg = React.useMemo(() => {
112 if (!status.connected) {
113 return 'disconnected';
115 if (!status.device) {
119 }, [enabled, status]);
121 return <div className="auto-tracking">
122 {['disconnected', 'error', 'no-device'].includes(statusMsg) ?
124 className="me-2 text-warning"
126 title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
129 {['not-applicable', 'not-in-game'].includes(statusMsg) ?
131 className="me-2 text-info"
133 title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
138 onClick={openSettings}
140 title={t('snes.settings')}
141 variant="outline-secondary"
143 <Icon.SETTINGS title="" />
147 title={t('autoTracking.heading')}
153 export default AutoTracking;