diff --git a/README.md b/README.md index ec95711..8d0c5ce 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,7 @@ A simple no-frills ACTWebSocket overlay for showing actions pressed in an overlay window. -Check it out online at: https://reyronald.github.io/SkillDisplay?HOST_PORT=ws://127.0.0.1:10501 - -## Installation - -Make sure the 'Using BeforeLogLineRead' box is checked in ACTWebSocket then click the 'Add URL' button to add this URL: -`https://rawrington.github.io/SkillDisplay/` - -## Troubleshooting - -I have only tested and confirmed this working on the QT5.8.0 variant of OverlayProc with ACTWebSocket 1.3.3.9 so if you are having trouble please use these versions. - -If you find any strange bugs, please report to me on here or find me on the ACT FFXIV discord. +Check it out online at: https://reyronald.github.io/SkillDisplay ## Preview diff --git a/src/ACTListener.ts b/src/ACTListener.ts index 5b14b11..26b88b1 100644 --- a/src/ACTListener.ts +++ b/src/ACTListener.ts @@ -1,37 +1,50 @@ -const getHost = () => - /[?&]HOST_PORT=(wss?:\/\/[^&/]+)/.exec(window.location.search) - -type Callback = ( - ...args: (string | { charID: number; charName: string })[] -) => void - type EventData = { type: "broadcast" - msgtype: "Chat" | "SendCharName" +} & ( + | { + msgtype: "ChangeZone" + msg: { + type: "ChangeZone" + zoneID: number + zoneName: string + } + } + | { + msgtype: "SendCharName" + msg: { + charID: number + charName: string + type: "ChangePrimaryPlayer" + } + } + | { + msgtype: "Chat" msg: string } +) -export default function listenToACT(callback: Callback) { - if (!getHost()) return listenOverlayPlugin(callback) - return listenActWebSocket(callback) -} +type Callback = (eventData: EventData) => void // https://github.com/OverlayPlugin/cactbot/blob/main/docs/LogGuide.md -function listenActWebSocket(callback: Callback) { - const host = getHost() - const wsUri = host && host[1] ? `${host[1]}/BeforeLogLineRead` : undefined - if (!wsUri) return () => undefined +export default function listenToACT(callback: Callback) { + const urlSearchParams = new URLSearchParams(window.location.search) + const HOST_PORT = urlSearchParams.get("HOST_PORT") + const hostPort = HOST_PORT || "ws://127.0.0.1:10501" + + const wsUri = `${hostPort}/BeforeLogLineRead` const ws = new WebSocket(wsUri) + ws.onerror = () => ws.close() + ws.onmessage = function (e) { if (e.data === ".") return ws.send(".") - const obj: EventData = JSON.parse(e.data) - if (obj.msgtype === "SendCharName") { - return callback(obj.msg) - } else if (obj.msgtype === "Chat") { - return callback(...obj.msg.split("|")) + const eventData: EventData = JSON.parse(e.data) + if (eventData.msgtype === "SendCharName") { + return callback(eventData) + } else if (eventData.msgtype === "Chat") { + return callback(eventData) } } @@ -39,16 +52,3 @@ function listenActWebSocket(callback: Callback) { ws.close() } } - -function listenOverlayPlugin(callback: Callback) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const listener = (e: any) => { - callback(...e.detail) - } - - document.addEventListener("onLogLine", listener) - - return () => { - document.removeEventListener("onLogLine", listener) - } -} diff --git a/src/App.tsx b/src/App.tsx index ef079c6..2a0817f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ export default function App() { let lastKey = 0 let timeoutId: number | undefined = undefined - const closeFn = listenToACT((...logSplit) => { + const closeFn = listenToACT((eventData) => { const openNewEncounter = () => { setEncounterList((encounterList) => { if ( @@ -62,127 +62,127 @@ export default function App() { }) } - if ( - logSplit.length === 1 && - typeof logSplit[0] !== "string" && - logSplit[0].charID - ) { - selfId = logSplit[0].charID + if (eventData.msgtype === "SendCharName") { + selfId = eventData.msg.charID openNewEncounter() return } - function refineType(_arg: typeof logSplit): asserts _arg is string[] {} - refineType(logSplit) - - const [ - logCode, - logTimestamp, - logParameter1, - logParameter2, - logParameter3, - ability, - ] = logSplit - - if (!handleCodes.has(logCode as LogCode)) return - - switch (logCode) { - case LINE_ID.LogLine: - if (logParameter1 === "0038" && logParameter3 === "end") - openNewEncounter() - return - case LINE_ID.ChangeZone: - currentZone = logParameter2 - return - case LINE_ID.ChangePrimaryPlayer: - selfId = parseInt(logParameter1, 16) - openNewEncounter() - return - case LINE_ID.ActorControl: - if (logParameter2 === "40000012" || logParameter2 === "40000010") + if (eventData.msgtype === "Chat") { + const logSplit = eventData.msg.split("|") + + const [ + logCode, + logTimestamp, + logParameter1, + logParameter2, + logParameter3, + ability, + ] = logSplit + + if (!handleCodes.has(logCode as LogCode)) return + + switch (logCode) { + case LINE_ID.LogLine: + if (logParameter1 === "0038" && logParameter3 === "end") + openNewEncounter() + return + case LINE_ID.ChangeZone: + currentZone = logParameter2 + return + case LINE_ID.ChangePrimaryPlayer: + selfId = parseInt(logParameter1, 16) openNewEncounter() + return + case LINE_ID.ActorControl: + if (logParameter2 === "40000012" || logParameter2 === "40000010") + openNewEncounter() + return + default: + break + } + + if (selfId === undefined) return + + if (parseInt(logParameter1, 16) !== selfId) return + + const actionId = parseInt(logParameter3, 16) + + const isCombatAction = + (actionId >= 9 && actionId <= 30000) || actionId === SPRINT_ACTION_ID + const isCraftingAction = actionId >= 100001 && actionId <= 100300 + const isBugOrDuplicate = + logTimestamp === lastTimestamp && actionId === lastAction + const isItem = ability.startsWith("item_") + + if ( + (!isCombatAction && !isCraftingAction && !isItem) || + isBugOrDuplicate + ) { return - default: - break - } - - if (selfId === undefined) return - - if (parseInt(logParameter1, 16) !== selfId) return - - const actionId = parseInt(logParameter3, 16) - - const isCombatAction = - (actionId >= 9 && actionId <= 30000) || actionId === SPRINT_ACTION_ID - const isCraftingAction = actionId >= 100001 && actionId <= 100300 - const isBugOrDuplicate = - logTimestamp === lastTimestamp && actionId === lastAction - const isItem = ability.startsWith("item_") - - if ( - (!isCombatAction && !isCraftingAction && !isItem) || - isBugOrDuplicate - ) { - return - } - - if (Date.now() - Date.parse(lastTimestamp) > 120000) openNewEncounter() //last action > 120s ago - - lastTimestamp = logTimestamp - lastAction = actionId - - let keyToRemove: number | null = null - - // This is pretty silly but it's the neatest way to handle the updates going - // out at the same time, without finding some way to merge the action lists.... - ReactDOM.unstable_batchedUpdates(() => { - setActionList((actionList) => { - const lastAction = actionList.at(-1) - - keyToRemove = lastAction?.key ?? null - - if (logCode === LINE_ID.NetworkCancelAbility) { - return actionList.slice(0, -1) - } else if (lastAction?.actionId === actionId && lastAction?.casting) { - const nextActionList = actionList.slice() - nextActionList[nextActionList.length - 1] = { - ...lastAction, - casting: false, + } + + if (Date.now() - Date.parse(lastTimestamp) > 120000) openNewEncounter() //last action > 120s ago + + lastTimestamp = logTimestamp + lastAction = actionId + + let keyToRemove: number | null = null + + // This is pretty silly but it's the neatest way to handle the updates going + // out at the same time, without finding some way to merge the action lists.... + ReactDOM.unstable_batchedUpdates(() => { + setActionList((actionList) => { + const lastAction = actionList.at(-1) + + keyToRemove = lastAction?.key ?? null + + if (logCode === LINE_ID.NetworkCancelAbility) { + return actionList.slice(0, -1) + } else if ( + lastAction?.actionId === actionId && + lastAction?.casting + ) { + const nextActionList = actionList.slice() + nextActionList[nextActionList.length - 1] = { + ...lastAction, + casting: false, + } + return nextActionList + } else { + const key = (lastKey % 256) + 1 + lastKey = key + return actionList.concat({ + actionId, + ability, + key, + casting: logCode === LINE_ID.NetworkStartsCasting, + }) } - return nextActionList - } else { - const key = (lastKey % 256) + 1 - lastKey = key - return actionList.concat({ - actionId, - ability, - key, - casting: logCode === LINE_ID.NetworkStartsCasting, - }) - } - }) - setEncounterList((encounterList) => { - if (logCode !== LINE_ID.NetworkAbility) return encounterList - - if (!encounterList[0]) { - encounterList[0] = { - name: currentZone, - actionList: [], + }) + setEncounterList((encounterList) => { + if (logCode !== LINE_ID.NetworkAbility) return encounterList + + if (!encounterList[0]) { + encounterList[0] = { + name: currentZone, + actionList: [], + } } - } - encounterList[0].actionList.push({ actionId, ability }) + encounterList[0].actionList.push({ actionId, ability }) - return encounterList + return encounterList + }) }) - }) - - if (keyToRemove != null) { - timeoutId = window.setTimeout(() => { - setActionList((actionList) => - actionList.filter((action) => action.key !== keyToRemove), - ) - }, 10000) + + if (keyToRemove != null) { + timeoutId = window.setTimeout(() => { + setActionList((actionList) => + actionList.filter((action) => action.key !== keyToRemove), + ) + }, 10000) + } } }) diff --git a/vite.config.ts b/vite.config.ts index 85657d4..b3d4b26 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite" +import react from "@vitejs/plugin-react" // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - base: '/SkillDisplay/' + base: "/SkillDisplay", })