Compare commits
3 Commits
f14b61d7d9
...
2a5f65d393
| Author | SHA1 | Date |
|---|---|---|
|
|
2a5f65d393 | |
|
|
8eebf378ab | |
|
|
81f6a0055a |
|
|
@ -0,0 +1,128 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Simple ES-module → IIFE bundler for GrechkaCraft
|
||||||
|
* Resolves import/export, wraps in single IIFE, no external deps.
|
||||||
|
* Usage: node bundle.js > ../game-bundled.js
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const SRC = path.join(__dirname, 'src');
|
||||||
|
const ENTRY = 'main.js';
|
||||||
|
|
||||||
|
const visited = new Set();
|
||||||
|
const chunks = [];
|
||||||
|
|
||||||
|
// Named export → variable name mapping per file
|
||||||
|
const fileExports = {}; // file -> [{local, exported}]
|
||||||
|
const fileImports = {}; // file -> [{names, from}]
|
||||||
|
|
||||||
|
function resolveImportPath(fromPath, importerDir) {
|
||||||
|
let resolved = path.resolve(importerDir, fromPath);
|
||||||
|
if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.js')) {
|
||||||
|
resolved += '.js';
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectExports(content) {
|
||||||
|
const exports = [];
|
||||||
|
// export { a, b as c }
|
||||||
|
const re1 = /export\s*\{([^}]+)\}/g;
|
||||||
|
let m;
|
||||||
|
while ((m = re1.exec(content)) !== null) {
|
||||||
|
const names = m[1].split(',').map(s => {
|
||||||
|
const parts = s.trim().split(/\s+as\s+/);
|
||||||
|
return { local: parts[0].trim(), exported: (parts[1] || parts[0]).trim() };
|
||||||
|
});
|
||||||
|
exports.push(...names);
|
||||||
|
}
|
||||||
|
// export const/let/var/function/class name
|
||||||
|
const re2 = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
|
||||||
|
while ((m = re2.exec(content)) !== null) {
|
||||||
|
exports.push({ local: m[1], exported: m[1] });
|
||||||
|
}
|
||||||
|
// export default ...
|
||||||
|
if (/export\s+default\s+/.test(content)) {
|
||||||
|
// Find the expression after export default
|
||||||
|
// Simpler: just use __default as name
|
||||||
|
exports.push({ local: '__default__', exported: 'default' });
|
||||||
|
}
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectImports(content) {
|
||||||
|
const imports = [];
|
||||||
|
const re = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
||||||
|
let m;
|
||||||
|
while ((m = re.exec(content)) !== null) {
|
||||||
|
const names = m[1].split(',').map(s => {
|
||||||
|
const parts = s.trim().split(/\s+as\s+/);
|
||||||
|
return { local: (parts[1] || parts[0]).trim(), imported: parts[0].trim() };
|
||||||
|
});
|
||||||
|
imports.push({ names, from: m[2] });
|
||||||
|
}
|
||||||
|
// import default
|
||||||
|
const re2 = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
||||||
|
while ((m = re2.exec(content)) !== null) {
|
||||||
|
imports.push({ names: [{ local: m[1], imported: 'default' }], from: m[2] });
|
||||||
|
}
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFile(filePath) {
|
||||||
|
const relPath = path.relative(SRC, filePath);
|
||||||
|
if (visited.has(relPath)) return;
|
||||||
|
visited.add(relPath);
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
|
||||||
|
// Collect exports/imports BEFORE stripping
|
||||||
|
fileExports[relPath] = collectExports(content);
|
||||||
|
fileImports[relPath] = collectImports(content);
|
||||||
|
|
||||||
|
// Process dependencies first (depth-first)
|
||||||
|
for (const imp of fileImports[relPath]) {
|
||||||
|
const depPath = resolveImportPath(imp.from, dir);
|
||||||
|
processFile(depPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip import statements
|
||||||
|
content = content.replace(/import\s*\{[^}]+\}\s*from\s*['"][^'"]+['"];?/g, '');
|
||||||
|
content = content.replace(/import\s+\w+\s+from\s*['"][^'"]+['"];?/g, '');
|
||||||
|
// Strip export keyword but keep declarations
|
||||||
|
content = content.replace(/export\s+default\s+/, 'const __default__ = ');
|
||||||
|
content = content.replace(/export\s+(const|let|var|function|class)\s/g, '$1 ');
|
||||||
|
content = content.replace(/export\s*\{[^}]*\};?/g, '');
|
||||||
|
|
||||||
|
chunks.push({ relPath, content });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 1: Collect all files, build dependency graph
|
||||||
|
processFile(path.join(SRC, ENTRY));
|
||||||
|
|
||||||
|
// Phase 2: Build rename map
|
||||||
|
// For each file's import { X as Y } from './other.js', we need to know:
|
||||||
|
// What is X called in other.js? Then rename Y → X_original
|
||||||
|
// Build: originalName (in source file) → what it's called elsewhere
|
||||||
|
|
||||||
|
// Simpler approach: since we wrap everything in one scope,
|
||||||
|
// we just need to ensure no name collisions.
|
||||||
|
// For now: trust that most names are unique across modules.
|
||||||
|
// If collision, prefix with module path.
|
||||||
|
|
||||||
|
// Phase 3: Emit
|
||||||
|
let output = '// GrechkaCraft — auto-bundled from ES modules\n';
|
||||||
|
output += '(function() {\n';
|
||||||
|
output += '"use strict";\n\n';
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
output += `// === ${chunk.relPath} ===\n`;
|
||||||
|
output += chunk.content.trim();
|
||||||
|
output += '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
output += '})();\n';
|
||||||
|
|
||||||
|
process.stdout.write(output);
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<title>GrechkaCraft: Multiplayer</title>
|
<title>GrechkaCraft: Multiplayer</title>
|
||||||
<!-- Socket.io Client -->
|
<!-- Socket.io Client -->
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
|
||||||
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
||||||
<link rel="stylesheet" href="style.css?v=6">
|
<link rel="stylesheet" href="style.css?v=6">
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -18,7 +17,6 @@
|
||||||
<div id="stats">
|
<div id="stats">
|
||||||
<div class="row">❤️ <span id="hp">100</span> 🍗 <span id="food">100</span></div>
|
<div class="row">❤️ <span id="hp">100</span> 🍗 <span id="food">100</span></div>
|
||||||
<div class="row">🫁 <span id="o2">100</span></div>
|
<div class="row">🫁 <span id="o2">100</span></div>
|
||||||
<div class="row">🌡️ <span id="temp">15°C</span></div>
|
|
||||||
<div class="row">📍 X:<span id="sx">0</span> Y:<span id="sy">0</span></div>
|
<div class="row">📍 X:<span id="sx">0</span> Y:<span id="sy">0</span></div>
|
||||||
<div class="row">🕒 <span id="tod">День</span></div>
|
<div class="row">🕒 <span id="tod">День</span></div>
|
||||||
<div class="row">🌐 <span id="worldId" style="cursor:pointer; text-decoration:underline;" title="Нажмите, чтобы скопировать ссылку">default</span></div>
|
<div class="row">🌐 <span id="worldId" style="cursor:pointer; text-decoration:underline;" title="Нажмите, чтобы скопировать ссылку">default</span></div>
|
||||||
|
|
@ -95,6 +93,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="game.js?v=50"></script>
|
<script src="game.js?v=24"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ server {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
# CORS for ES modules
|
||||||
add_header Access-Control-Allow-Origin *;
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
|
||||||
location ~* \.(js|mjs|css)$ {
|
location ~* \.(js|mjs|css)$ {
|
||||||
|
|
@ -16,4 +17,4 @@ server {
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
voice-test.html
136
voice-test.html
|
|
@ -1,94 +1,70 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html><head><title>Voice Test</title></head><body>
|
||||||
<head><title>Voice Test</title></head>
|
<h1>Voice Capture Test</h1>
|
||||||
<body style="background:#1a1a2e;color:#eee;font-family:monospace;padding:20px">
|
<button id="btn" style="padding:20px;font-size:24px;background:#2ecc71;color:#fff;border:none;border-radius:12px;cursor:pointer;">Start Mic</button>
|
||||||
<h2>Voice Chat Debug</h2>
|
<div id="log" style="font-family:monospace;white-space:pre;margin-top:20px;"></div>
|
||||||
<div id="log" style="white-space:pre;overflow:auto;max-height:80vh;border:1px solid #444;padding:10px;font-size:12px"></div>
|
|
||||||
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const VOICE_SERVER = 'https://voicegrech.mkn8n.ru';
|
const log = document.getElementById('log');
|
||||||
const logEl = document.getElementById('log');
|
function addLog(msg) { log.textContent += msg + '\n'; console.log(msg); }
|
||||||
function log(msg) { logEl.textContent += new Date().toISOString().substr(11,8) + ' ' + msg + '\n'; logEl.scrollTop = logEl.scrollHeight; }
|
|
||||||
|
|
||||||
log('Starting voice test...');
|
let voiceStream, audioCtx, voiceProcessor, voiceSocket;
|
||||||
|
let debugCount = 0;
|
||||||
|
|
||||||
(async () => {
|
document.getElementById('btn').onclick = async () => {
|
||||||
try {
|
try {
|
||||||
log('Requesting microphone...');
|
addLog('1. Requesting mic...');
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({audio: {echoCancellation:true,noiseSuppression:true,autoGainControl:true}});
|
voiceStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true } });
|
||||||
log('Got mic stream: ' + stream.getTracks().map(t => t.label + ' ' + t.readyState).join(', '));
|
addLog('2. Got stream: ' + voiceStream.getTracks().map(t => t.label + ' ' + t.readyState).join(', '));
|
||||||
|
|
||||||
const audioCtx = new AudioContext({sampleRate: 16000});
|
audioCtx = new AudioContext({ sampleRate: 24000 });
|
||||||
log('AudioContext: state=' + audioCtx.state + ' sampleRate=' + audioCtx.sampleRate);
|
if (audioCtx.state === 'suspended') await audioCtx.resume();
|
||||||
if (audioCtx.state === 'suspended') { await audioCtx.resume(); log('AudioContext resumed: ' + audioCtx.state); }
|
addLog('3. AudioContext: state=' + audioCtx.state + ' sampleRate=' + audioCtx.sampleRate);
|
||||||
|
|
||||||
const source = audioCtx.createMediaStreamSource(stream);
|
const source = audioCtx.createMediaStreamSource(voiceStream);
|
||||||
const analyser = audioCtx.createAnalyser();
|
voiceProcessor = audioCtx.createScriptProcessor(2048, 1, 1);
|
||||||
analyser.fftSize = 2048;
|
addLog('4. ScriptProcessor created');
|
||||||
source.connect(analyser);
|
|
||||||
|
voiceProcessor.onaudioprocess = (e) => {
|
||||||
// Check mic levels
|
debugCount++;
|
||||||
const dataArr = new Float32Array(analyser.fftSize);
|
const pcm = e.inputBuffer.getChannelData(0);
|
||||||
let checkCount = 0;
|
const maxVal = Math.max(...Array.from(pcm).map(Math.abs));
|
||||||
const checkInterval = setInterval(() => {
|
if (debugCount <= 10) addLog('5. onaudioprocess #' + debugCount + ' samples=' + pcm.length + ' max=' + maxVal.toFixed(4));
|
||||||
analyser.getFloatTimeDomainData(dataArr);
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < dataArr.length; i++) sum += dataArr[i] * dataArr[i];
|
|
||||||
const rms = Math.sqrt(sum / dataArr.length);
|
|
||||||
checkCount++;
|
|
||||||
if (checkCount <= 20 || checkCount % 50 === 0) log('Mic RMS: ' + rms.toFixed(6) + (rms > 0.008 ? ' SPEAKING' : ''));
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
log('Connecting to voice server...');
|
|
||||||
const socket = io(VOICE_SERVER, {transports: ['websocket']});
|
|
||||||
|
|
||||||
socket.on('connect', () => {
|
|
||||||
log('SOCKET CONNECTED: ' + socket.id);
|
|
||||||
socket.emit('voice_join', {world_id: 'test', x: 0, y: 0, name: 'Tester', mode: 'world', codec: 'pcm'});
|
|
||||||
log('voice_join sent');
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('connect_error', (e) => {
|
|
||||||
log('SOCKET ERROR: ' + e.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('voice_in', (payload) => {
|
|
||||||
log('RX voice_in from ' + (payload.meta?.from||'?').substring(0,8) + ' codec:' + payload.codec + ' size:' + (payload.data?.byteLength || payload.data?.length || '?'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Capture and send
|
|
||||||
const processor = audioCtx.createScriptProcessor(320, 1, 1);
|
|
||||||
source.connect(processor);
|
|
||||||
processor.connect(audioCtx.destination);
|
|
||||||
|
|
||||||
let seq = 0;
|
|
||||||
let sendCount = 0;
|
|
||||||
processor.onaudioprocess = (e) => {
|
|
||||||
if (!socket.connected) return;
|
|
||||||
const float32 = e.inputBuffer.getChannelData(0);
|
|
||||||
let rms = 0;
|
|
||||||
for (let i = 0; i < float32.length; i++) rms += float32[i] * float32[i];
|
|
||||||
rms = Math.sqrt(rms / float32.length);
|
|
||||||
|
|
||||||
if (rms < 0.005) return; // Skip silence
|
if (!voiceSocket || !voiceSocket.connected) return;
|
||||||
|
const int16 = new Int16Array(pcm.length);
|
||||||
const int16 = new Int16Array(float32.length);
|
for (let i = 0; i < pcm.length; i++) {
|
||||||
for (let i = 0; i < float32.length; i++) {
|
const s = Math.max(-1, Math.min(1, pcm[i]));
|
||||||
const s = Math.max(-1, Math.min(1, float32[i]));
|
|
||||||
int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
||||||
}
|
}
|
||||||
|
voiceSocket.emit('voice_data', int16.buffer);
|
||||||
socket.emit('voice_data', {codec: 'pcm', data: int16.buffer, seq: seq++, speaking: true});
|
|
||||||
sendCount++;
|
|
||||||
if (sendCount <= 5 || sendCount % 50 === 0) log('TX frame #' + sendCount + ' rms:' + rms.toFixed(4) + ' size:' + int16.buffer.byteLength);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const silentGain = audioCtx.createGain();
|
||||||
|
silentGain.gain.value = 0;
|
||||||
|
source.connect(voiceProcessor);
|
||||||
|
voiceProcessor.connect(silentGain);
|
||||||
|
silentGain.connect(audioCtx.destination);
|
||||||
|
addLog('6. Audio chain connected: source→processor→gain(0)→destination');
|
||||||
|
|
||||||
|
addLog('7. Connecting to voice server...');
|
||||||
|
voiceSocket = io('https://voicegrech.mkn8n.ru', { transports: ['websocket'] });
|
||||||
|
voiceSocket.on('connect', () => {
|
||||||
|
addLog('8. Socket connected: ' + voiceSocket.id);
|
||||||
|
voiceSocket.emit('voice_join', { world_id: 'test', x: 0, y: 0, name: 'Tester' });
|
||||||
|
addLog('9. Sent voice_join. Speak into mic — watch onaudioprocess logs above!');
|
||||||
|
});
|
||||||
|
voiceSocket.on('connect_error', (err) => addLog('ERROR: ' + err.message));
|
||||||
|
|
||||||
log('Voice capture active. Speak into mic!');
|
voiceSocket.on('voice_in', (payload) => {
|
||||||
|
addLog('VOICE_IN from ' + payload.meta.name + ' vol=' + payload.volume + ' bytes=' + payload.data.byteLength);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn').textContent = 'Listening...';
|
||||||
|
document.getElementById('btn').style.background = '#e74c3c';
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log('ERROR: ' + e.message);
|
addLog('ERROR: ' + e.message + '\n' + e.stack);
|
||||||
}
|
}
|
||||||
})();
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body></html>
|
||||||
</html>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue