-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit df4dd46
Showing
33 changed files
with
16,119 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |
Oops, something went wrong.