import MainControl from "@/Common/MainControl/MainControl"; import CSMessage from "@/Common/Message/CSMessage"; import { CoroutineV2 } from "@/Engine/CatanEngine/CoroutineV2/CoroutineV2"; import { INetResponse } from "@/Engine/CatanEngine/NetManagerV2/Core/INetResponse"; import { NetConnector } from "@/Engine/CatanEngine/NetManagerV2/NetConnector"; import { NetManagerSD } from "@/Engine/CatanEngine/NetManagerV2/NetManagerSD"; import CSSettingsSDV3 from "@/FormTableSD/CSSettingsSDV3"; import { SlotsetTableRow } from "@/FormTableSD/Tables/SlotsetTable"; import { gameObj, useGameItems } from "@/context/GameItemsContext"; import { SDAccountLoginRequest } from "@/define/Request/AccountRequest"; import { SlotInRequest } from "@/define/Request/SlotRequest"; import GameManager from "@/modules/GameManager"; import WheelSelect from "@/components/WheelSelect"; import { Button, InputNumber, Switch } from "antd"; import dayjs from "dayjs"; import React, { useEffect, useState } from "react"; const SDGame = (props: ISDGame) => { const { gameUrl, onClickSlotOut } = props; const { gameData } = useGameItems(); const { nowSlotId } = gameData; const [isOK, setIsOK] = useState(false); const [isSpin, setIsSpin] = useState(false); const [betOptions, setBetOptions] = useState([]); const [bet, setBet] = useState(0); const [delay, setDelay] = useState(0.1); const [isRatioStop, setIsRatioStop] = useState(false); const [ratioStop, setRatioStop] = useState(200); const [isCountStop, setIsCountStop] = useState(false); const [countStop, setCountStop] = useState(200); const [log, setLog] = useState([]); let conn: NetConnector = undefined; function* onLoad(): IterableIterator { reset(); const url: URL = new URL(gameUrl); const queryParameters: URLSearchParams = new URLSearchParams(url.search); const token: string = queryParameters.get("token"); const slotid: number = +queryParameters.get("slotid"); let host: string = queryParameters.get("host"); const port: number = 9005; // NetConnector 對沒帶協定的 host 會用「目前頁面」的協定判斷 ws/wss, // bot 頁面是 http 會誤判成 ws://;改依遊戲網址本身的協定,https 就強制 wss if (url.protocol === "https:" && !/^https?:\/\//.test(host)) { host = `https://${host}`; } GameManager.SlotData.SlotId = slotid; GameManager.SlotData.IsRatioStop = isRatioStop; GameManager.SlotData.RatioStop = ratioStop; GameManager.SlotData.IsCountStop = isCountStop; GameManager.SlotData.CountStop = countStop; conn = new NetConnector(host, port); conn.OnDataReceived.AddCallback(onNetDataReceived); conn.OnDisconnected.AddCallback(onNetDisconnected); loadJSON(slotid); setSlotClass(); NetManagerSD.Initialize(conn); console.log("[SDsocket] connecting..."); AddLog(`連線 SD socket: ${host}:${port}`); yield NetManagerSD.ConnectAsync(); AddLog("SD socket 已連線"); yield* login(token); yield* slotIn(); } function onClose(): void { CoroutineV2.StopCoroutinesBy("Spin"); try { if (conn && conn.IsConnected) { conn.Disconnect(); } } catch (e) { // } reset(); } function reset(): void { GameManager.SlotData.SlotId = undefined; GameManager.SlotData.SlotClass = undefined; GameManager.SlotData.IsSpin = undefined; GameManager.SlotData.NowBet = undefined; GameManager.SlotData.IsRatioStop = undefined; GameManager.SlotData.RatioStop = undefined; GameManager.SlotData.IsCountStop = undefined; GameManager.SlotData.CountStop = undefined; } function* login(token: string): IterableIterator { const req = new SDAccountLoginRequest(token); yield req.SendAsync(true); const resp = req.Result; if (!resp.IsValid) { CSMessage.NetError(resp.Method, resp.Status, "SD Account Login Fail"); AddLog(`SD 登入失敗 [${resp.Method}] status:${resp.Status}`); return; } AddLog("SD 登入成功"); } function* slotIn(): IterableIterator { let req: SlotInRequest = new SlotInRequest(GameManager.SlotData.SlotId); yield req.SendAsync(true); let resp: INetResponse = req.Result; if (resp.IsValid) { const br: number[] = resp.Data["br"]; const db: number = resp.Data["db"]; setBetOptions(br); setBet(br[db]); GameManager.SlotData.NowBet = br[db]; setIsOK(true); AddLog(`機台就緒 押注選項: [${br.join(", ")}]`); } else { AddLog(`SlotIn 失敗 [${resp.Method}] status:${resp.Status}`); } } async function loadJSON(slotid: number) { let slotset: SlotsetTableRow = CSSettingsSDV3.Slotset[slotid]; let formName: string[] = slotset.FormName; let parallel: Promise[] = []; for (let i: number = 0; i < formName.length; i++) { await MainControl.DownloadFormSetting(formName[i]); } // set Form await Promise.all(parallel); } async function setSlotClass() { let slot: any; const slotGroup: typeof import("../../define/Game/Base/Slot") = await import(/* @vite-ignore */`../../define/Game/Base/Slot`); try { slot = slotGroup[`Slot${GameManager.SlotData.SlotId}`]; } catch (error) { // } if (!slot) { slot = slotGroup.SlotBase; } GameManager.SlotData.SlotClass = new slot(GameManager.SlotData.SlotId, AddLog); } async function OnClickSpin() { GameManager.SlotData.IsSpin = true; setIsSpin(true); CoroutineV2.Single(spin()).Start("Spin"); } function OnClickStop() { GameManager.SlotData.IsSpin = false; setIsSpin(false); } function* spin(): IterableIterator { const { player } = gameObj; const { m: money } = player; if (money < GameManager.SlotData.NowBet) { noMoney(); OnClickStop(); return; } if (isCountStop) { if (GameManager.SlotData.CountStop <= 0) { OnClickStop(); return; } GameManager.SlotData.CountStop--; setCountStop((v) => v - 1); } yield* GameManager.SlotData.SlotClass.Spin(bet, OnClickStop); yield CoroutineV2.WaitTime(delay); if (GameManager.SlotData.IsSpin) { yield* spin(); } else { GameManager.SlotData.IsSpin = false; setIsSpin(false); } } function noMoney(): void { const { player } = gameObj; const { m: money } = player; GameManager.SlotData.IsSpin = false; AddLog(`金額不足: ${money}`); } function AddLog(s: string) { const todayTimeStr: string = dayjs().format("YYYY/MM/DD HH:mm:ss"); setLog((v) => { const next: string[] = [`${todayTimeStr} ${s}`, ...v]; return next.length > 100 ? next.slice(0, 100) : next; }); } /**RPC回傳.若協定錯誤斷線.原因也會在這裡收到 */ function onNetDataReceived(resp: INetResponse) { // MainControl.DataReceivedEvent.DispatchCallback([MainControl.DataType.ServerData, resp]); } /**只要連線中斷不管主被動都會走到這裡 */ function onNetDisconnected() { console.warn("[socket] Disconnected"); conn.OnDataReceived.RemoveAllCallbacks(); // MainControl.DataReceivedEvent.DispatchCallback([MainControl.DataType.NetDisconnected]); } useEffect(() => { CoroutineV2.Single(onLoad()).Start(); return onClose; }, []); return (
{/* 左側:控制面板 */}
SD Machine

SLOT #{nowSlotId}

{isSpin ? "spinning" : isOK ? "ready" : "loading"}
{isOK && <>
押注 ({ value: v, label: v.toLocaleString() }))} onChange={(v: number) => { setBet(v); GameManager.SlotData.NowBet = v; }} />
延遲 setDelay(+v || 0)} suffix="秒" />
倍率停 { setIsRatioStop(checked); GameManager.SlotData.IsRatioStop = checked; }} /> {isRatioStop && { const n = +v || 0; setRatioStop(n); GameManager.SlotData.RatioStop = n; }} suffix="倍" /> }
轉數停 { setIsCountStop(checked); GameManager.SlotData.IsCountStop = checked; }} /> {isCountStop && { const n = +v || 0; setCountStop(n); GameManager.SlotData.CountStop = n; }} suffix="轉" /> }
{isSpin ? : }
} {!isOK &&
}
{/* 右側:Log 終端 */}
Log {log.length} 筆
{log.length === 0 &&

等待紀錄

} {log.map((s: string, index: number) =>

{s}

)}
); }; export default SDGame; interface ISDGame { gameUrl: string; onClickSlotOut: () => void; } const layoutStyle: React.CSSProperties = { flex: 1, minHeight: 0, display: "flex", gap: 16, alignItems: "stretch", }; const controlPanelStyle: React.CSSProperties = { width: 340, flexShrink: 0, padding: "24px 24px 20px", display: "flex", flexDirection: "column", overflowY: "auto", }; const rowStyle: React.CSSProperties = { display: "flex", alignItems: "center", gap: 12, marginBottom: 16, }; const rowLabelStyle: React.CSSProperties = { width: 52, flexShrink: 0, }; const logPanelStyle: React.CSSProperties = { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", };