Skip to content

Commit

Permalink
Add livesplit autosplitting
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt-Hurd committed Aug 13, 2023
1 parent a6e9443 commit e9eff96
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 4 deletions.
23 changes: 23 additions & 0 deletions src/components/RunMosaic/RunMosaic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import "@blueprintjs/icons/lib/css/blueprint-icons.css";

import UpcomingDisplay from "../UpcomingDisplay/UpcomingDisplay";
import StorageManager from "../../utils/StorageManager";
import liveSplitService from "../../services/LiveSplitWebSocket";

type RunParams = {
routeUrl: string;
Expand All @@ -30,6 +31,7 @@ const RunMosaic: React.FC = () => {
const { routeUrl } = useParams<RunParams>();
const routeStatus = useSelector(selectRouteStatus);
const dispatch = useAppDispatch();
const liveSplit = liveSplitService;

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
Expand Down Expand Up @@ -67,6 +69,27 @@ const RunMosaic: React.FC = () => {
}
}, [dispatch, routeUrl, routeStatus]);

useEffect(() => {
function handleLoad() {
(async () => {
liveSplit.connect();
})();
}
if (document.readyState === "complete") {
handleLoad();
} else {
window.addEventListener("load", handleLoad);
return () => {
window.removeEventListener("load", handleLoad);
};
}
return () => {
if (liveSplit) {
liveSplit.disconnect();
}
};
}, [liveSplit]);

if (routeStatus !== "succeeded") {
return <div>Loading...</div>;
}
Expand Down
109 changes: 109 additions & 0 deletions src/services/LiveSplitWebSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
class LiveSplitWebSocket {
private static instance: LiveSplitWebSocket;

private socket: WebSocket | null = null;
private readonly url: string = "ws://localhost:16835/livesplit";

private pendingRequests: {
[id: string]: {
resolve: (value: string | PromiseLike<string>) => void;
reject: (reason?: any) => void;
};
} = {};

private constructor() {}

public static getInstance(): LiveSplitWebSocket {
if (!LiveSplitWebSocket.instance) {
LiveSplitWebSocket.instance = new LiveSplitWebSocket();
}
return LiveSplitWebSocket.instance;
}

isConnected(): boolean {
return process.env.NODE_ENV !== "production" && this.socket?.readyState === WebSocket.OPEN;
}

connect() {
this.socket = new WebSocket(this.url);

this.socket.onopen = (event) => {
console.log("WebSocket Connected:", event);
};

this.socket.onerror = (error) => {
console.error("WebSocket Error:", error);
};

this.socket.onclose = (event) => {
console.log("WebSocket Closed:", event);
this.socket = null;
};

this.socket.onmessage = (event) => {
console.log("WebSocket Message Received:", event.data);
const response = JSON.parse(event.data);

if (this.pendingRequests[response.name]) {
const { resolve } = this.pendingRequests[response.name];
resolve(response.data);
delete this.pendingRequests[response.name];
}
};
}

disconnect() {
if (this.socket) {
this.socket.close();
}
}

send(data: string) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(data);
} else {
console.error("WebSocket is not open. Unable to send data.");
}
}

split() {
this.send("split");
}

unsplit() {
this.send("unsplit");
}
getCurrentSplitName(): Promise<string> {
return new Promise((resolve, reject) => {
this.pendingRequests["getcurrentsplitname"] = { resolve, reject };

this.send("getcurrentsplitname");

setTimeout(() => {
if (this.pendingRequests["getcurrentsplitname"]) {
resolve("");
delete this.pendingRequests["getcurrentsplitname"];
}
}, 100);
});
}

getPreviousSplitName(): Promise<string> {
return new Promise((resolve, reject) => {
this.pendingRequests["getprevioussplitname"] = { resolve, reject };

this.send("getprevioussplitname");

setTimeout(() => {
if (this.pendingRequests["getprevioussplitname"]) {
resolve("");
delete this.pendingRequests["getprevioussplitname"];
}
}, 100);
});
}
}

const liveSplitService = LiveSplitWebSocket.getInstance();

export default liveSplitService;
51 changes: 47 additions & 4 deletions src/store/progressSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { RootState } from ".";
import liveSplitService from "../services/LiveSplitWebSocket";

interface ProgressState {
branchIndex: number;
Expand All @@ -11,15 +12,34 @@ const initialState: ProgressState = {
pointIndex: 0,
};

export const incrementProgress = createAsyncThunk("progress/increment", (_, { getState }) => {
export const incrementProgress = createAsyncThunk("progress/increment", async (_, { getState }) => {
const state: RootState = getState() as RootState;
const { progress } = state;
const liveSplit = liveSplitService;

if (state.route.data) {
const point = state.route.data.branches[progress.branchIndex].points[progress.pointIndex];
const thing = state.route.data.things[point.layerId][point.thingId];
if (
liveSplit.isConnected() &&
((thing.type === "Shrine" && point.action === "COMPLETE") || thing.type === "Lightroot")
) {
const currentSplitName = await liveSplit.getCurrentSplitName();
if (currentSplitName.endsWith(thing.name)) {
liveSplit.split();
}
}

if (
progress.branchIndex < state.route.data.branches.length - 1 &&
progress.pointIndex === state.route.data.branches[progress.branchIndex].points.length - 1
) {
if (liveSplit.isConnected()) {
const currentSplitName = await liveSplit.getCurrentSplitName();
if (currentSplitName === state.route.data.branches[progress.branchIndex]?.name) {
liveSplit.split();
}
}
return { ...progress, branchIndex: progress.branchIndex + 1, pointIndex: 0 };
} else if (progress.pointIndex < state.route.data.branches[progress.branchIndex].points.length - 1) {
return { ...progress, pointIndex: progress.pointIndex + 1 };
Expand All @@ -29,17 +49,40 @@ export const incrementProgress = createAsyncThunk("progress/increment", (_, { ge
return progress;
});

export const decrementProgress = createAsyncThunk("progress/decrement", (_, { getState }) => {
export const decrementProgress = createAsyncThunk("progress/decrement", async (_, { getState }) => {
const state: RootState = getState() as RootState;
const { progress } = state;
const liveSplit = liveSplitService;

if (state.route.data) {
let newProgress;
if (progress.branchIndex > 0 && progress.pointIndex === 0) {
const prevBranchLastPointIndex = state.route.data.branches[progress.branchIndex - 1].points.length - 1;
return { ...progress, branchIndex: progress.branchIndex - 1, pointIndex: prevBranchLastPointIndex };
if (liveSplit.isConnected()) {
const previousSplitName = await liveSplit.getPreviousSplitName();
if (previousSplitName === state.route.data.branches[progress.branchIndex - 1]?.name) {
liveSplit.unsplit();
}
}
newProgress = { ...progress, branchIndex: progress.branchIndex - 1, pointIndex: prevBranchLastPointIndex };
} else if (progress.pointIndex > 0) {
return { ...progress, pointIndex: progress.pointIndex - 1 };
newProgress = { ...progress, pointIndex: progress.pointIndex - 1 };
}
if (newProgress) {
const point = state.route.data.branches[newProgress.branchIndex].points[newProgress.pointIndex];
const thing = state.route.data.things[point.layerId][point.thingId];
if (
liveSplit.isConnected() &&
((thing.type === "Shrine" && point.action === "COMPLETE") || thing.type === "Lightroot")
) {
const previousSplitName = await liveSplit.getPreviousSplitName();
if (previousSplitName.endsWith(thing.name)) {
liveSplit.unsplit();
}
}
}

return newProgress;
}

return progress;
Expand Down

0 comments on commit e9eff96

Please sign in to comment.