// Trade UI — личная торговля (лицом к лицу) import { state } from '../core/state.js'; import { BLOCKS } from '../data/blocks.js'; import { ITEMS } from '../data/items.js'; import { TOOLS } from '../data/tools.js'; import { playSound } from '../audio/sound-engine.js'; import { addChatMessage } from './chat.js'; function itemName(id) { return BLOCKS[id]?.n || TOOLS[id]?.n || ITEMS[id]?.n || id; } function itemIcon(id) { if (BLOCKS[id]) return '🟫'; if (TOOLS[id]) return TOOLS[id].icon || '🔧'; if (ITEMS[id]) return ITEMS[id].icon || '📦'; return '📦'; } let tradeOpen = false; let currentTradeId = null; let myOffer = {}; let partnerOffer = {}; let iConfirmed = false; export function isTradeOpen() { return tradeOpen; } export function initTradeSocket(socket) { // Incoming trade request socket.on('trade_request', (data) => { playSound('click'); // Show trade request modal const overlay = document.createElement('div'); overlay.id = 'tradeRequestOverlay'; overlay.className = 'custom-modal-overlay'; overlay.innerHTML = `
🤝 ${data.from_name} хочет торговать
`; document.getElementById('game').appendChild(overlay); document.getElementById('tradeAcceptBtn').addEventListener('click', () => { overlay.remove(); socket.emit('trade_accept', { from_socket_id: data.from }); }); document.getElementById('tradeDeclineBtn').addEventListener('click', () => { overlay.remove(); socket.emit('trade_decline', { from_socket_id: data.from }); }); }); socket.on('trade_start', (data) => { currentTradeId = data.trade_id; myOffer = {}; partnerOffer = {}; iConfirmed = false; tradeOpen = true; renderTradeUI(data.partner_name); state.tradePanel.style.display = 'block'; }); socket.on('trade_update', (data) => { partnerOffer = data.player1Offer || data.player2Offer || {}; // Determine which offer is the partner's const isP1 = state.socket && state.socket.id === data.player1Offer; // The partner's offer is the one we didn't send // Actually the server sends both — we figure out which is ours // Simpler: just use the data directly if (state.mySocketId) { // We need to know which offer in the update is ours vs partner's // The trade_update event doesn't tell us, but we know myOffer already // So partnerOffer = the one that's not myOffer } renderTradeOffers(); }); socket.on('trade_status', (data) => { // Update confirm buttons const confirmBtn = document.getElementById('tradeConfirmBtn'); if (confirmBtn) { const amIPlayer1 = true; // We need to track this if (iConfirmed) { confirmBtn.textContent = '✅ Ожидание...'; confirmBtn.disabled = true; confirmBtn.style.background = '#555'; } } // Show partner confirmation status const partnerStatus = document.getElementById('partnerConfirmStatus'); if (partnerStatus) { const partnerConfirmed = data.player1Confirm || data.player2Confirm; partnerStatus.textContent = partnerConfirmed ? '✅ Подтвердил' : '⏳ Ещё не подтвердил'; } }); socket.on('trade_complete', (data) => { // Add received items to inventory const received = data.received || {}; for (const [item, qty] of Object.entries(received)) { if (qty > 0) { state.inv[item] = (state.inv[item] || 0) + qty; } } // Remove sent items from inventory const sent = data.sent || {}; for (const [item, qty] of Object.entries(sent)) { if (qty > 0) { state.inv[item] = (state.inv[item] || 0) - qty; if (state.inv[item] < 0) state.inv[item] = 0; } } addChatMessage('Система', '🤝 Сделка завершена!'); closeTrade(); }); socket.on('trade_declined', () => { addChatMessage('Система', 'Торговля отклонена'); closeTrade(); }); socket.on('trade_cancelled', () => { addChatMessage('Система', 'Торговля отменена'); closeTrade(); }); socket.on('trade_error', (data) => { addChatMessage('Система', '❌ ' + data.msg); }); } function renderTradeUI(partnerName) { const panel = state.tradePanel; const content = state.tradeContent; content.innerHTML = `
🤝 Торговля с ${partnerName}
Вы предлагаете:
⇄
Партнёр предлагает:
Ожидание...
⏳ Партнёр ещё не подтвердил
`; document.getElementById('tradeAddItemBtn')?.addEventListener('click', showAddItemDialog); document.getElementById('tradeConfirmBtn')?.addEventListener('click', confirmTrade); document.getElementById('tradeCancelBtn')?.addEventListener('click', cancelTrade); } function renderTradeOffers() { const myItems = document.getElementById('myOfferItems'); if (myItems) { const keys = Object.keys(myOffer).filter(k => myOffer[k] > 0); if (keys.length === 0) { myItems.innerHTML = 'Ничего'; } else { myItems.innerHTML = keys.map(k => `
${itemIcon(k)} ${itemName(k)} x${myOffer[k]}
`).join(''); } } } function showAddItemDialog() { const inv = state.inv; const items = Object.keys(inv).filter(id => inv[id] > 0); if (items.length === 0) { addChatMessage('Система', 'Инвентарь пуст!'); return; } const overlay = document.createElement('div'); overlay.id = 'tradeItemPicker'; overlay.className = 'custom-modal-overlay'; let html = '
Что предложить?
'; for (const id of items) { // Show already offered count const offered = myOffer[id] || 0; const available = inv[id] - offered; if (available <= 0) continue; html += `
${itemIcon(id)} ${itemName(id)} x${available}
`; } html += '
'; overlay.innerHTML = html; document.getElementById('game').appendChild(overlay); overlay.querySelectorAll('.trade-pick-item').forEach(el => { el.addEventListener('click', () => { const itemId = el.dataset.item; // Ask quantity const available = inv[itemId] - (myOffer[itemId] || 0); const qty = Math.min(available, available > 1 ? promptForQty(itemId, available) : 1); if (qty > 0) { myOffer[itemId] = (myOffer[itemId] || 0) + qty; renderTradeOffers(); // Reset confirm when offer changes iConfirmed = false; const btn = document.getElementById('tradeConfirmBtn'); if (btn) { btn.textContent = '✅ Подтвердить'; btn.disabled = false; btn.style.background = '#2ecc71'; } } overlay.remove(); }); }); } function promptForQty(itemId, max) { // Simple: just use 1 for now, can enhance later with a proper dialog const val = parseInt(prompt(`Сколько ${itemName(itemId)}? (макс: ${max})`, '1')); return isNaN(val) || val < 1 ? 1 : Math.min(val, max); } function confirmTrade() { if (!currentTradeId || !state.socket) return; // Deduct offered items from inventory for (const [item, qty] of Object.entries(myOffer)) { if (qty > 0) { if (state.inv[item] < qty) { addChatMessage('Система', `Недостаточно ${itemName(item)}!`); return; } } } // Send offer + confirm state.socket.emit('trade_offer', { trade_id: currentTradeId, offer: myOffer }); // Small delay then confirm setTimeout(() => { state.socket.emit('trade_confirm', { trade_id: currentTradeId }); iConfirmed = true; const btn = document.getElementById('tradeConfirmBtn'); if (btn) { btn.textContent = '✅ Ожидание...'; btn.disabled = true; btn.style.background = '#555'; } }, 200); } function cancelTrade() { if (!currentTradeId || !state.socket) return; state.socket.emit('trade_cancel', { trade_id: currentTradeId }); closeTrade(); } export function closeTrade() { tradeOpen = false; currentTradeId = null; myOffer = {}; partnerOffer = {}; iConfirmed = false; if (state.tradePanel) state.tradePanel.style.display = 'none'; // Remove any pick overlay document.getElementById('tradeItemPicker')?.remove(); } export function requestTradeWithPlayer(targetName) { if (!state.socket || !state.isMultiplayer) { addChatMessage('Система', 'Торговля доступна только в мультиплеере'); return; } state.socket.emit('trade_request', { target_name: targetName }); addChatMessage('Система', `Запрос отправлен ${targetName}...`); }