Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
b0ink committed Jan 15, 2023
0 parents commit df4dd46
Show file tree
Hide file tree
Showing 33 changed files with 16,119 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules

## Packaged executable: (npm run build)
dist/
archive
build/

.DS_Store
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# CS:GO Chaos Mod Voting Overlay

### Installation
- Download the latest release and run the `setup.exe`

OR
### Running this locally
1. Install the node modules by running `npm i`.
2. Run `npm run compile` and wait for the app to compile.
3. Run `npm start`.
- To build the app into a .exe, run `npm run build`
- You can use `npm run watch` in a separate terminal to automatically compile the app on every save, you can then run `npm start` to start the app every time.

### Usage
- Enter your Twitch or YouTube details and then Servers connection details
- Ensure that your server's launch options uses the `-usercon` paramater to allow RCON connections.
- You can type `status` while on the csgo server to get the ip and port.
- The server's RCON password is either defined in your `server.cfg` or as a launch option parameter.
- If both connections are successful, two green ticks will be displayed and the `Open Voting` button will appear, clicking that will bring up the overlay.

<p align="center">
<img src="https://csgochaosmod.com/gallery/twitch-overlay/App_1.PNG" width="250" title="Twitch Setup">
<img src="https://csgochaosmod.com/gallery/twitch-overlay/App_2.PNG" width="250" title="YouTube Setup">
<img src="https://csgochaosmod.com/gallery/twitch-overlay/Voting_3.PNG" width="250" title="Voting Panel">
</p>

### OBS
Below is a setup on keying out the green from the voting panel, apply a Colour Key filter to the window capture of the voting panel.

<p align="center">
<img src="https://csgochaosmod.com/gallery/twitch-overlay/OBS_1.PNG" width="550" title="Setup">
<img src="https://csgochaosmod.com/gallery/twitch-overlay/OBS_2.PNG" width="550" title="Setup">
</p>

### Known Issues
- Due to the nature of the RCON connection, the app may crash or stop sending requests when the map changes. Simply restart the app and re-connect after a map change.
- Often the voting overlay may not update its UI if its minimised or behind another window, ensure it is visible on top of other windows, this may require a second monitor.
36 changes: 36 additions & 0 deletions app/Controllers/Chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const _this = module.exports;

const { app } = require("electron");
const { ipcMain } = require("electron/main");

const EffectsController = require("./Effects");

_this.VoteTracker = {};
_this.CanVote = true;

_this.SaveVote = (message, userIdentifier) => {
let vote = parseInt(message);
if (vote > 0 && vote <= 8 && _this.VoteTracker[userIdentifier] !== true) {
if (!_this.CanVote) return;
try {
if ([1, 2, 3, 4].includes(parseInt(EffectsController.Effects[0].index))) {
if (vote > 4) return;
} else {
if (vote < 5) return;
}
if (vote > 4) vote = vote - 4;
EffectsController.Effects[vote - 1].votes += 1;
if (app.isPackaged) {
_this.VoteTracker[userIdentifier] = true;
}
} catch (e) {
_this.VoteTracker[userIdentifier] = false;
}
}
};

/* REMOTES */

ipcMain.handle("Chat_CanVote", async (event, data) => {
return _this.CanVote;
});
160 changes: 160 additions & 0 deletions app/Controllers/Effects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
const { app } = require("electron");
const { ipcMain } = require("electron/main");

const _this = module.exports;

const Chat = require("./Chat");

_this.VotingWindowOpen = false;
_this.LastPlayedEffect = "";
_this.VotingEnabled = false;
_this.HideEffectList = false;
_this.NextEffectTime = 0;
_this.Effects = [];
_this.HighlightWinningEffect = false;
_this.ProportionalVoting = true;

const Rcon = require("./Rcon");
const Twitch = require("./Twitch");
const Youtube = require("./Youtube");

let finalCheckTimer = null;
let disableVoteTimer = null;
let delayEffectTimer = null;

setInterval(() => {
if (!Rcon.IsRconConnected()) return;
if (!_this.VotingWindowOpen) return;
GetServerData();
}, 1000);

const GetServerData = async () => {
await Rcon.GetEffectData(GetWinningEffect())
.then((data) => {
data = ParseServerData(data);
data.LastPlayedEffect = data.lastPlayedEffect;
if (data.newEffectTime != _this.NextEffectTime) {
/* New set of effects */

let effects = data.effects;
if (!effects || !Array.isArray(effects) || data.newEffectTime <= 0) {
_this.HideEffectList = true;
_this.Effects = [];
_this.HighlightWinningEffect = false;
clearTimeout(finalCheckTimer);
clearTimeout(disableVoteTimer);
clearTimeout(delayEffectTimer);
return;
} else {
delayEffectTimer = setTimeout(() => {
for (let effect of effects) {
effect.votes = 0;
}
_this.Effects = effects;
Chat.VoteTracker = {};
Chat.CanVote = true;
}, 1000);

_this.NextEffectTime = data.newEffectTime;

let interval = Math.floor(data.newEffectTime) - Math.floor(Date.now() / 1000);
finalCheckTimer = setTimeout(GetServerData, interval * 1000);

disableVoteTimer = setTimeout(() => {
/* Disable votes 1 second before pulling new effect */
Chat.CanVote = false;
}, interval * 1000 - 1000);

_this.LastPlayedEffect = data.lastPlayedEffect;
_this.HighlightWinningEffect = true;
}
}

_this.VotingEnabled = data.twitchEnabled;
_this.HideEffectList = data.hideEffectList;
})
.catch((e) => {
//TODO: handle failed attempts
});
};

const ParseServerData = (data) => {
try {
data = JSON.parse(data);
return data;
} catch (e) {
return null;
}
};

const GetWinningEffect = () => {
if (_this.ProportionalVoting) {
/* Get effect based of proportional votes */
let sortedEffects = [..._this.Effects];
sortedEffects = sortedEffects.sort(function (a, b) {
return a.votes - b.votes;
});
let totalVotes = 0;
for (let effect of sortedEffects) {
console.log(effect.votes);
totalVotes += effect.votes;
}
if (sortedEffects.length < 4) {
return "Random";
}
let check1 = sortedEffects[0].votes;
let check2 = check1 + sortedEffects[1].votes;
let check3 = check2 + sortedEffects[2].votes;
let check4 = check3 + sortedEffects[3].votes;
/* between 1 and totalVotes (inclusive) */
let rand = Math.floor(Math.random() * totalVotes) + 1;
let fctn = "";
if (rand <= check1) {
fctn = sortedEffects[0].function;
} else if (rand <= check2) {
fctn = sortedEffects[1].function;
} else if (rand <= check3) {
fctn = sortedEffects[2].function;
} else if (rand <= check4) {
fctn = sortedEffects[3].function;
} else {
fctn = "random";
}
return fctn;
} else {
/* Get highest voted effect */
let vote = 0;
let fctn = "";

for (let effect of _this.Effects) {
if (effect.votes >= vote) {
vote = effect.votes;
fctn = effect.function;
}
}

return fctn ? fctn : "RANDOM";
}
};

/* REMOTES */

ipcMain.handle("Effects_GetEffects", async (event, data) => {
return _this.Effects;
});

ipcMain.handle("Effects_IsVotingEnabled", async (event, data) => {
return _this.VotingEnabled;
});

ipcMain.handle("Effects_ShouldHideEffectList", async (event, data) => {
return _this.HideEffectList;
});

ipcMain.handle("Effects_GetLastPlayedEffect", async (event, data) => {
if (_this.HighlightWinningEffect) {
_this.HighlightWinningEffect = false;
return _this.LastPlayedEffect;
}
return false;
});
91 changes: 91 additions & 0 deletions app/Controllers/Preferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const _this = module.exports;
const { ipcMain, safeStorage } = require("electron");
const Store = require("electron-store");
const store = new Store();

const GetStoredValue = (key, returnEmptyOnFail = true) => {
try {
let info = safeStorage.decryptString(Buffer.from(JSON.parse(store.get(configindex[key]))));
if (info) return info;
} catch (e) {
if (returnEmptyOnFail) {
return "";
} else {
return store.get(configindex[key]);
}
}
};

const configindex = {
"username": "1",
"channelname": "2",
"twitchpassword": "3",
"serverip": "4",
"port": "5",
"serverpassword": "6",
"youtubeChannelID": "11",
"remember-choice": "7",
"always-save-passwords": "8",
"votingWindow-location": "9",
"setupWindow-location": "10",
};


_this.GetSavedLogin = function () {
let config = {
username: GetStoredValue("username"),
channelname: GetStoredValue("channelname"),
twitchpassword: GetStoredValue("twitchpassword"),
serverip: GetStoredValue("serverip"),
port: GetStoredValue("port"),
serverpassword: GetStoredValue("serverpassword"),
youtubeChannelID: GetStoredValue("youtubeChannelID"),
rememberChoice: GetStoredValue("remember-choice", false),
allowSave: GetStoredValue("always-save-passwords", false),
};
return config;
};

_this.SaveLogin = function (data, savePasswords = true, rememberChoice = false) {
if (data.username) store.set(configindex["username"], JSON.stringify(safeStorage.encryptString(data.username)));
if (data.channelname) store.set(configindex["channelname"], JSON.stringify(safeStorage.encryptString(data.channelname)));
if (data.serverip) store.set(configindex["serverip"], JSON.stringify(safeStorage.encryptString(data.serverip)));
if (data.port) store.set(configindex["port"], JSON.stringify(safeStorage.encryptString(data.port)));
if (savePasswords) {
if (data.youtubeChannelID) store.set(configindex["youtubeChannelID"], JSON.stringify(safeStorage.encryptString(data.youtubeChannelID)));
if (data.twitchpassword) store.set(configindex["twitchpassword"], JSON.stringify(safeStorage.encryptString(data.twitchpassword)));
if (data.serverpassword) store.set(configindex["serverpassword"], JSON.stringify(safeStorage.encryptString(data.serverpassword)));
} else {
// clear passwords
store.set(configindex["twitchpassword"], "");
store.set(configindex["serverpassword"], "");
}
store.set(configindex["always-save-passwords"], savePasswords);
store.set(configindex["remember-choice"], rememberChoice);
};

_this.GetWindowLocation = (windowName) => {
let position = store.get(configindex[`${windowName}-location`]);
try {
return {
x: parseInt(position.split(" ")[0]),
y: parseInt(position.split(" ")[1]),
};
} catch (e) {
return { x: null, y: null };
}
};

_this.SaveWindowLocation = (windowName, x, y) => {
store.set(configindex[`${windowName}-location`], `${x} ${y}`);
};

/* REMOTES */

ipcMain.handle("Preferences_SaveDetails", async (event, data) => {
_this.SaveLogin(data, data.allowSave, data.rememberChoice);
});

ipcMain.handle("Preferences_GetDetails", async (event, data) => {
return _this.GetSavedLogin();
});
Loading

0 comments on commit df4dd46

Please sign in to comment.