Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions Chat/backend/config/gameProfiles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
{
"league-of-legends": {
"displayName": "League of Legends",
"processNames": [
"League of Legends.exe",
"LeagueClient.exe",
"LeagueClientUx.exe"
],
"windowTitles": [
"League of Legends"
],
"visionInterval": 5000,
"commentaryStyle": "strategic",
"events": [
"kill",
"death",
"dragon",
"baron",
"tower",
"inhibitor"
],
"clipDuration": 15,
"priority": "high",
"tips": {
"lowHp": "Cuidado! HP baixo, recua!",
"kill": "Boaaa! +1! 🔥",
"death": "Eita, acontece! Vamos recuperar!",
"win": "VITÓRIA! GG WP! 🏆",
"lose": "Próxima a gente pega! 💪"
}
},
"valorant": {
"displayName": "VALORANT",
"processNames": [
"VALORANT.exe",
"VALORANT-Win64-Shipping.exe"
],
"windowTitles": [
"VALORANT"
],
"visionInterval": 3000,
"commentaryStyle": "tactical",
"events": [
"ace",
"clutch",
"plant",
"defuse",
"kill",
"death"
],
"clipDuration": 20,
"priority": "high",
"tips": {
"ace": "ACE! VOCÊ É UM DEMÔNIO! 👹🔥",
"clutch": "CLUTCH MODE ATIVADO! 💪",
"kill": "Eliminou! Continua!",
"death": "Morre não! Mas tá tranquilo.",
"win": "VITÓRIA! Dominação total! 🏆",
"lose": "GG, próxima round!"
}
},
"osu": {
"displayName": "osu!",
"processNames": [
"osu!.exe"
],
"windowTitles": [
"osu!"
],
"visionInterval": 2000,
"commentaryStyle": "energetic",
"events": [
"fc",
"combo-break",
"new-top-play"
],
"clipDuration": 10,
"priority": "medium",
"tips": {
"fc": "FULL COMBO! VOCÊ É INSANO! ⭐✨",
"comboBreak": "Ish, quebrou! Mas continua!",
"newTopPlay": "NOVO TOP PLAY! PARABÉNS! 🎉"
}
},
"minecraft": {
"displayName": "Minecraft",
"processNames": [
"javaw.exe",
"Minecraft.exe"
],
"windowTitles": [
"Minecraft"
],
"visionInterval": 10000,
"commentaryStyle": "chill",
"events": [
"death",
"achievement",
"dragon",
"wither"
],
"clipDuration": 15,
"priority": "low",
"tips": {
"death": "Ah não! Respawna e volta ao fight!",
"achievement": "Achievement desbloqueado! Nice! 🏆",
"dragon": "ENDER DRAGON DERROTADO! LENDÁRIO! 🐉"
}
},
"cs2": {
"displayName": "Counter-Strike 2",
"processNames": [
"cs2.exe"
],
"windowTitles": [
"Counter-Strike 2"
],
"visionInterval": 3000,
"commentaryStyle": "tactical",
"events": [
"ace",
"clutch",
"bomb-plant",
"bomb-defuse"
],
"clipDuration": 20,
"priority": "medium",
"tips": {
"ace": "ACE! VOCÊ É O MELHOR! 🔥",
"clutch": "CLUTCH INSANO! 1vX!",
"win": "VITÓRIA! GG! 🏆"
}
},
"corinthians-watch": {
"displayName": "⚽ Assistindo Corinthians",
"processNames": [
"chrome.exe",
"firefox.exe",
"msedge.exe",
"opera.exe",
"vlc.exe",
"PotPlayerMini64.exe",
"mpc-hc64.exe"
],
"windowTitles": [
"Corinthians",
"Timão",
"SCCP",
"Premiere",
"Globo",
"ESPN",
"Star+",
"YouTube"
],
"visionInterval": 8000,
"commentaryStyle": "passionate",
"events": [
"gol-corinthians",
"gol-adversario",
"defesa",
"chance-perdida",
"falta",
"cartao-amarelo",
"cartao-vermelho",
"escanteio",
"impedimento",
"inicio-jogo",
"fim-jogo"
],
"clipDuration": 30,
"priority": "MÁXIMA",
"tips": {
"golCorinthians": "⚽🖤🤍 GOOOOOOOOOOL DO TIMÃO! VAI CORINTHIANS! 🎉🎊",
"golAdversario": "Ah não... levamos gol. Mas calma, vamos virar! 💪",
"defesa": "CÁSSIO! DEFENDEU! QUE MONSTRO! 🧤✨",
"chancePerdida": "Eita, passou perto! Quase! ⚡",
"falta": "Falta! Juiz marcou. Vamo lá...",
"cartaoAmarelo": "Amarelou! Cuidado agora...",
"cartaoVermelho": "EXPULSO! Eita, complicou! 🟥",
"escanteio": "Escanteio! Bola na área, pode dar gol!",
"impedimento": "Impedimento! Ish, árbitro cortou...",
"inicioJogo": "COMEÇOU O JOGO! VAMO TIMÃO! 🖤🤍⚽",
"fimJogo": "ACABOU! Que jogo! VAI CORINTHIANS! 🏆",
"vitoria": "🏆🖤🤍 VITÓRIA DO TIMÃO! É CAMPEÃO! VAI CORINTHIANS! 🎉🎊🎆",
"empate": "Empatou... mas tá valendo! Ponto é ponto! 💪",
"derrota": "Perdemos hoje... mas o Corinthians nunca desiste! Próximo jogo! 🖤🤍"
}
}
}
51 changes: 42 additions & 9 deletions Chat/backend/services/gamingService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class GamingService {
this.currentGame = null;
this.isGaming = false;
this.gameProfiles = {};
this._profileProcessCache = new Map(); // gameId -> Set(processNamesLower)
this.monitorInterval = null;
this.visionFrequency = 30000; // Default: 30s (idle)

Expand All @@ -37,15 +38,31 @@ class GamingService {
try {
const data = await fs.readFile(profilesPath, 'utf-8');
this.gameProfiles = JSON.parse(data);
this._refreshProcessCache();
console.log('[GAMING] ✅ Loaded game profiles:', Object.keys(this.gameProfiles));
} catch (err) {
console.warn('[GAMING] ⚠️ No game profiles found, using defaults');
this.gameProfiles = this.getDefaultProfiles();
this._refreshProcessCache();
// Criar arquivo de perfis padrão
await this.saveDefaultProfiles(profilesPath);
}
}

/**
* Atualiza o cache de nomes de processo para busca rápida
* @private
*/
_refreshProcessCache() {
this._profileProcessCache.clear();
for (const [gameId, profile] of Object.entries(this.gameProfiles)) {
if (profile.processNames && Array.isArray(profile.processNames)) {
const lowerNames = new Set(profile.processNames.map(name => name.toLowerCase()));
this._profileProcessCache.set(gameId, lowerNames);
}
}
}

/**
* Perfis padrão de jogos
*/
Expand Down Expand Up @@ -222,19 +239,34 @@ class GamingService {
try {
// Windows: usar tasklist para obter processos
const { stdout } = await execAsync('tasklist /FO CSV /NH');
const processes = stdout.split('\n').map(line => {
const match = line.match(/"([^"]+)"/);
return match ? match[1] : '';
});

// Verificar cada perfil de jogo
// Criar Set de processos rodando (lowercase) para busca O(1)
const runningProcesses = new Set(
stdout.split('\n')
.map(line => {
const match = line.match(/"([^"]+)"/);
return match ? match[1].toLowerCase() : '';
})
.filter(name => name !== '')
);

// Verificar cada perfil de jogo (mantendo a ordem de prioridade dos perfis)
for (const [gameId, profile] of Object.entries(this.gameProfiles)) {
for (const processName of profile.processNames) {
if (processes.some(p => p.toLowerCase() === processName.toLowerCase())) {
await this.onGameDetected(gameId, profile);
return;
const lowerNames = this._profileProcessCache.get(gameId);
if (!lowerNames) continue;

let hasGameRunning = false;
for (const name of lowerNames) {
if (runningProcesses.has(name)) {
hasGameRunning = true;
break;
}
}

if (hasGameRunning) {
await this.onGameDetected(gameId, profile);
return;
}
}

// Nenhum jogo detectado
Expand Down Expand Up @@ -350,6 +382,7 @@ class GamingService {
*/
async addGameProfile(gameId, profile) {
this.gameProfiles[gameId] = profile;
this._refreshProcessCache();

const profilesPath = path.join(__dirname, '../config/gameProfiles.json');
await fs.writeFile(profilesPath, JSON.stringify(this.gameProfiles, null, 2));
Expand Down