godcrm/backend/routes/v3/neo16/socketHandler.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

142 lines
4.4 KiB
JavaScript

/**
* 16Neo — Socket.IO handler for multiplayer office
* Manages: room join/leave, player position sync, chat relay, pet state
*/
// In-memory room state (single-server, no Redis needed yet)
const rooms = new Map(); // roomId -> { players: Map<socketId, PlayerState>, pets: Map<petId, PetState> }
function getOrCreateRoom(roomId) {
if (!rooms.has(roomId)) {
rooms.set(roomId, { players: new Map(), pets: new Map() });
}
return rooms.get(roomId);
}
export function initNeo16Socket(io) {
const neo = io.of('/neo16');
neo.on('connection', (socket) => {
let currentRoomId = null;
let playerData = null;
// ── Join room ──────────────────────────────────
socket.on('room:join', ({ roomId, player, pet }) => {
currentRoomId = roomId || 'default-office';
playerData = {
id: socket.id,
name: player?.name || `User-${socket.id.slice(0, 4)}`,
pos: player?.pos || { x: 112, y: 96 },
tilePos: player?.tilePos || { x: 7, y: 6 },
direction: 'down',
state: 'idle',
frame: 0,
color: player?.color || '#457b9d',
};
socket.join(currentRoomId);
const room = getOrCreateRoom(currentRoomId);
room.players.set(socket.id, playerData);
// Register pet if provided
if (pet) {
const petData = {
id: `pet_${socket.id}`,
name: pet.name || 'Pet',
species: pet.species || 'corgi',
pos: pet.pos || { x: 80, y: 96 },
tilePos: pet.tilePos || { x: 5, y: 6 },
direction: 'down',
mood: pet.mood || 'happy',
frame: 0,
ownerId: socket.id,
};
room.pets.set(petData.id, petData);
}
// Send current room state to the joining player
socket.emit('room:state', {
players: Object.fromEntries(room.players),
pets: Object.fromEntries(room.pets),
});
// Notify others
socket.to(currentRoomId).emit('player:joined', {
player: playerData,
pet: pet ? room.pets.get(`pet_${socket.id}`) : null,
});
});
// ── Player position update (throttled by client) ──
socket.on('player:move', ({ pos, tilePos, direction, state, frame }) => {
if (!currentRoomId) return;
const room = getOrCreateRoom(currentRoomId);
const p = room.players.get(socket.id);
if (!p) return;
p.pos = pos;
p.tilePos = tilePos;
p.direction = direction;
p.state = state;
p.frame = frame;
// Broadcast to others in room
socket.to(currentRoomId).volatile.emit('player:moved', {
id: socket.id,
pos, tilePos, direction, state, frame,
});
});
// ── Pet position update ───────────────────────
socket.on('pet:move', ({ petId, pos, tilePos, direction, mood }) => {
if (!currentRoomId) return;
const room = getOrCreateRoom(currentRoomId);
const pet = room.pets.get(petId);
if (!pet || pet.ownerId !== socket.id) return;
pet.pos = pos;
pet.tilePos = tilePos;
pet.direction = direction;
if (mood) pet.mood = mood;
socket.to(currentRoomId).volatile.emit('pet:moved', {
petId, pos, tilePos, direction, mood,
});
});
// ── Chat message ──────────────────────────────
socket.on('chat:message', ({ text }) => {
if (!currentRoomId || !playerData || !text?.trim()) return;
const msg = {
id: `msg_${Date.now()}_${socket.id.slice(0, 4)}`,
authorId: socket.id,
authorName: playerData.name,
text: text.trim().slice(0, 500), // limit length
timestamp: Date.now(),
};
// Broadcast to everyone in room (including sender)
neo.to(currentRoomId).emit('chat:message', msg);
});
// ── Disconnect ────────────────────────────────
socket.on('disconnect', () => {
if (!currentRoomId) return;
const room = rooms.get(currentRoomId);
if (!room) return;
room.players.delete(socket.id);
room.pets.delete(`pet_${socket.id}`);
socket.to(currentRoomId).emit('player:left', { id: socket.id });
// Cleanup empty rooms
if (room.players.size === 0) {
rooms.delete(currentRoomId);
}
});
});
return neo;
}