Skip to content

Instantly share code, notes, and snippets.

@lukepolo
Last active November 20, 2024 13:45
Show Gist options
  • Save lukepolo/dac28b1d323a42d885092f8b51187cd7 to your computer and use it in GitHub Desktop.
Save lukepolo/dac28b1d323a42d885092f8b51187cd7 to your computer and use it in GitHub Desktop.
record-kills-cs2
import { exec } from 'child_process';
import { OBSWebSocket } from 'obs-websocket-js';
import { parseEvent, parseTicks } from '@laihoe/demoparser2';
// set DefensiveConCommands to 0 in game/csgo_core/gameinfo.gi
async function execCommand(command) {
return new Promise((resolve, reject) => {
console.info(`executing command: ${command}`);
exec(command, (error) => {
if (error) {
// TODO - figure out what todo , probably restart
reject(error);
}
resolve();
});
});
}
const obs = new OBSWebSocket()
void execCommand('flatpak run com.obsproject.Studio');
// Wait until CS2 window is found
let startTime = Date.now();
while (Date.now() - startTime < 30000) {
try {
await execCommand('wmctrl -a "Obs"');
break;
} catch (e) {
console.info('waiting for OBS');
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
await obs.connect();
const outputList = await obs.call('GetOutputList');
// await execCommand('steamcmd +login anonymous +app_update 730 validate +quit');
await execCommand('steam steam://rungameid/730');
// Wait until CS2 window is found
startTime = Date.now();
while (Date.now() - startTime < 30000) {
try {
await execCommand('wmctrl -a "Counter-Strike 2"');
break;
} catch (e) {
console.info('waiting for CS2 window');
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
//TODO - wait till were able to start dmeo
await new Promise(resolve => setTimeout(resolve, 15 * 1000));
const { scenes } = await obs.call('GetSceneList');
if (!scenes.find((scene) => {
return scene.sceneName === 'GameStream';
})) {
await obs.call('CreateScene', { sceneName: 'GameStream' });
}
await obs.call('SetCurrentProgramScene', { sceneName: 'GameStream' });
// Create a browser source for the CS2 HUD
const { sceneItems } = await obs.call('GetSceneItemList', { sceneName: 'GameStream' });
if (!sceneItems.find((item) => {
return item.sourceName === 'CS2 HUD';
})) {
await obs.call('CreateInput', {
inputName: 'CS2 HUD',
inputKind: 'browser_source',
sceneName: 'GameStream',
inputSettings: {
url: 'http://localhost:31982/hud?transparent',
width: 1920,
height: 1080,
reroute_audio: false,
restart_when_active: true
}
});
}
// Create a game capture source for CS2
const { sceneItems: gameSceneItems } = await obs.call('GetSceneItemList', { sceneName: 'GameStream' });
if (!gameSceneItems.find((item) => {
return item.sourceName === 'CS2 Game';
})) {
await obs.call('CreateInput', {
inputName: 'CS2 Game',
inputKind: 'xcomposite_input',
sceneName: 'GameStream',
inputSettings: {
capture_window: '\r\nCounter-Strike 2\r\ncs2',
}
});
}
async function getTickRanges(demoPath, player) {
let kills = parseEvent(demoPath, "player_death", ["last_place_name", "team_name"], ["total_rounds_played", "is_warmup_period"])
let killsNoWarmup = kills.filter(kill => kill.is_warmup_period == false)
let filteredKills = killsNoWarmup.filter(kill => kill.attacker_team_name != kill.user_team_name)
let maxRound = Math.max(...kills.map(o => o.total_rounds_played))
const killsPerRound = {};
for (let round = 0; round <= maxRound; round++){
let killsThisRound = filteredKills.filter(kill => kill.total_rounds_played == round)
for (const item of killsThisRound) {
if(item.attacker_name !== player) {
continue;
}
if(!killsPerRound[round]) {
killsPerRound[round] = [];
}
killsPerRound[round].push({
player: item.attacker_name,
tick: item.tick,
});
}
}
return killsPerRound;
}
async function startDemo() {
console.info('starting demo');
const commands = [
'wmctrl -a "Counter-Strike 2"',
'xdotool key "grave"',
'xdotool key "ctrl+a"',
'xdotool key "Delete"',
'xdotool type "playdemo test"',
'xdotool key Return',
'xdotool key "grave"'
];
for (let i = 0; i < commands.length; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
await execCommand(commands[i]);
}
}
async function clipDemo(demoPath, tick, player) {
let players = parseTicks(demoPath, ["user_id", "team_name"], [tick])
let ctPlayers = players.filter(x => x.team_name == "CT")
let tPlayers = players.filter(x => x.team_name == "TERRORIST")
ctPlayers.sort((a,b) => a.user_id - b.user_id)
tPlayers.sort((a,b) => a.user_id - b.user_id)
let slots = []
slots.push(...ctPlayers)
slots.push(...tPlayers)
for (let i = 1; i < slots.length; i++){
slots[i]["slot_num"] = i
}
const slot = slots.find((slot) => {
return slot.name === player;
});
if(!slot) {
console.info(`player ${player} not found`);
return;
}
// a tick is 64 frames, lets jump back 5 seconds
const tickToJumpTo = tick - (5 * 64);
const commands = [
'wmctrl -a "Counter-Strike 2"',
'xdotool key "grave"',
'xdotool key "ctrl+a"',
'xdotool key "Delete"',
`xdotool type "demo_gototick ${tickToJumpTo}"`,
'xdotool key Return',
`xdotool type "spec_player ${slot.user_id + 1}"`,
'xdotool key Return',
'xdotool key "grave"'
];
for (let i = 0; i < commands.length; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
await execCommand(commands[i]);
}
await obs.call('SetOutputSettings', {
outputName: "simple_file_output",
outputSettings: {
record_folder: "/home/default/Videos",
record_filename: `${player}-${tick}.mp4`
},
});
console.info("START RECORD");
await obs.call('StartRecord');
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
console.info("STOP RECORD");
await obs.call('StopRecord');
}
await startDemo();
// TODO - not sure how long it takes to boot the demo
// wait for 10 seconds
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
const player = process.argv[2];
const demoPath = "/home/default/.steam/steam/steamapps/common/Counter-Strike Global Offensive/game/csgo/test.dem";
for(const [round, ticks] of Object.entries(await getTickRanges(demoPath, player))) {
console.info(`round ${round}`, ticks);
for(const { tick } of ticks) {
try {
await clipDemo(demoPath, tick, player);
} catch (err) {
console.error(`Failed to clip demo at tick ${tick}:`, err.message);
throw err;
}
}
}
console.info("done.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment