hzluz
This commit is contained in:
parent
9dde216cbf
commit
67a02cc646
235
app.js
235
app.js
@ -23,6 +23,8 @@ const equipment = require("./routes/equipment");
|
|||||||
const blackmarket = require("./routes/blackmarket");
|
const blackmarket = require("./routes/blackmarket");
|
||||||
const mineRoute = require("./routes/mine_route");
|
const mineRoute = require("./routes/mine_route");
|
||||||
const arenaRoutes = require("./routes/routes_arena");
|
const arenaRoutes = require("./routes/routes_arena");
|
||||||
|
const { registerArenaHandlers } = require("./sockets/arena");
|
||||||
|
const { registerChatHandlers } = require("./sockets/chat");
|
||||||
|
|
||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
|
|
||||||
@ -249,240 +251,13 @@ app.use((req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
Chat + 1v1 Matchmaking System
|
Socket.io Handler
|
||||||
======================== */
|
======================== */
|
||||||
|
|
||||||
let onlineUsers = {};
|
|
||||||
|
|
||||||
// ── 1v1 Matchmaking Pool ─────────────────────────────────────────────────────
|
|
||||||
// Map: socketId → { socket, player: { id, name, level } }
|
|
||||||
const waitingPool = new Map();
|
|
||||||
const LEVEL_RANGE = 5;
|
|
||||||
|
|
||||||
function tryMatchmaking(newSocketId) {
|
|
||||||
const challenger = waitingPool.get(newSocketId);
|
|
||||||
if (!challenger) return;
|
|
||||||
|
|
||||||
for (const [id, entry] of waitingPool) {
|
|
||||||
if (id === newSocketId) continue;
|
|
||||||
|
|
||||||
const levelDiff = Math.abs(entry.player.level - challenger.player.level);
|
|
||||||
if (levelDiff <= LEVEL_RANGE) {
|
|
||||||
// Match gefunden – beide aus dem Pool entfernen
|
|
||||||
waitingPool.delete(newSocketId);
|
|
||||||
waitingPool.delete(id);
|
|
||||||
|
|
||||||
const matchId = `match_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
||||||
|
|
||||||
// Spieler 1 benachrichtigen
|
|
||||||
challenger.socket.emit("match_found", {
|
|
||||||
matchId,
|
|
||||||
opponent: entry.player,
|
|
||||||
mySlot: "player1",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spieler 2 benachrichtigen
|
|
||||||
entry.socket.emit("match_found", {
|
|
||||||
matchId,
|
|
||||||
opponent: challenger.player,
|
|
||||||
mySlot: "player2",
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[1v1] Match gestartet: ${challenger.player.name} (Lvl ${challenger.player.level}) ` +
|
|
||||||
`vs ${entry.player.name} (Lvl ${entry.player.level}) | ID: ${matchId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return; // Nur ein Match pro Aufruf – nächste Iteration startet neu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
console.log("Spieler verbunden:", socket.id);
|
console.log("Spieler verbunden:", socket.id);
|
||||||
|
registerChatHandlers(io, socket);
|
||||||
/* ── Chat: Registrierung ── */
|
registerArenaHandlers(io, socket);
|
||||||
socket.on("register", async (username) => {
|
|
||||||
const [rows] = await db.query(
|
|
||||||
"SELECT ingame_name FROM accounts WHERE username = ?",
|
|
||||||
[username],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rows.length) return;
|
|
||||||
|
|
||||||
const ingameName = rows[0].ingame_name;
|
|
||||||
socket.user = ingameName;
|
|
||||||
onlineUsers[ingameName] = socket.id;
|
|
||||||
io.emit("onlineUsers", Object.keys(onlineUsers));
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── 1v1: Queue beitreten ── */
|
|
||||||
socket.on("join_1v1", (playerData) => {
|
|
||||||
// Doppelt-Eintrag verhindern
|
|
||||||
if (waitingPool.has(socket.id)) return;
|
|
||||||
|
|
||||||
const player = {
|
|
||||||
id: playerData.id,
|
|
||||||
name: playerData.name,
|
|
||||||
level: Number(playerData.level) || 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
waitingPool.set(socket.id, { socket, player });
|
|
||||||
|
|
||||||
socket.emit("queue_status", {
|
|
||||||
status: "waiting",
|
|
||||||
poolSize: waitingPool.size,
|
|
||||||
message: `Suche Gegner (Level ${player.level - LEVEL_RANGE}–${player.level + LEVEL_RANGE})…`,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[1v1] ${player.name} (Lvl ${player.level}) betritt den Pool. Poolgröße: ${waitingPool.size}`);
|
|
||||||
|
|
||||||
tryMatchmaking(socket.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── 1v1: Queue verlassen ── */
|
|
||||||
socket.on("leave_1v1", () => {
|
|
||||||
if (waitingPool.delete(socket.id)) {
|
|
||||||
socket.emit("queue_status", { status: "left" });
|
|
||||||
console.log(`[1v1] Spieler ${socket.id} hat den Pool verlassen.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── 1v1: Bereit-System ── */
|
|
||||||
if (!io._arenaReady) io._arenaReady = new Map(); // matchId → Set of ready slots
|
|
||||||
|
|
||||||
socket.on("player_ready", (data) => {
|
|
||||||
const { matchId, slot } = data;
|
|
||||||
if (!matchId || !slot) return;
|
|
||||||
|
|
||||||
if (!io._arenaReady.has(matchId)) {
|
|
||||||
io._arenaReady.set(matchId, new Set());
|
|
||||||
}
|
|
||||||
|
|
||||||
const readySet = io._arenaReady.get(matchId);
|
|
||||||
readySet.add(slot);
|
|
||||||
|
|
||||||
// Beide Spieler in der Arena-Room benachrichtigen
|
|
||||||
io.to("arena_" + matchId).emit("ready_status", {
|
|
||||||
readyCount: readySet.size,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[1v1] ${slot} ist bereit in Match ${matchId} (${readySet.size}/2)`);
|
|
||||||
|
|
||||||
// Aufräumen wenn beide bereit
|
|
||||||
if (readySet.size >= 2) {
|
|
||||||
io._arenaReady.delete(matchId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("player_surrender", (data) => {
|
|
||||||
const { matchId, slot } = data;
|
|
||||||
console.log(`[1v1] ${slot} hat aufgegeben in Match ${matchId}`);
|
|
||||||
// Aufgabe-Logik kommt hier rein
|
|
||||||
io.to("arena_" + matchId).emit("player_surrendered", { slot });
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── 1v1: Spielfeld-Verbindung (beide Spieler im iframe) ── */
|
|
||||||
// Map: matchId → { player1: socketId, player2: socketId, names: {} }
|
|
||||||
if (!io._arenaRooms) io._arenaRooms = new Map();
|
|
||||||
|
|
||||||
socket.on("arena_join", (data) => {
|
|
||||||
const { matchId, slot } = data;
|
|
||||||
if (!matchId || !slot) return;
|
|
||||||
|
|
||||||
if (!io._arenaRooms.has(matchId)) {
|
|
||||||
io._arenaRooms.set(matchId, { sockets: {}, names: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
const room = io._arenaRooms.get(matchId);
|
|
||||||
room.sockets[slot] = socket.id;
|
|
||||||
room.names[slot] = socket.user || "Spieler";
|
|
||||||
|
|
||||||
socket.join("arena_" + matchId);
|
|
||||||
|
|
||||||
const otherSlot = slot === "player1" ? "player2" : "player1";
|
|
||||||
|
|
||||||
if (room.sockets[otherSlot]) {
|
|
||||||
// Beide sind da → arena_ready an alle im Raum senden
|
|
||||||
io.to("arena_" + matchId).emit("arena_ready", {
|
|
||||||
player1: room.names["player1"] || "Spieler 1",
|
|
||||||
player2: room.names["player2"] || "Spieler 2",
|
|
||||||
});
|
|
||||||
console.log(`[Arena] Match ${matchId} bereit: ${room.names["player1"]} vs ${room.names["player2"]}`);
|
|
||||||
} else {
|
|
||||||
// Erster Spieler → dem Gegner mitteilen sobald er kommt
|
|
||||||
socket.to("arena_" + matchId).emit("arena_opponent_joined", {
|
|
||||||
name: room.names[slot],
|
|
||||||
slot,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── Chat: Nachrichten ── */
|
|
||||||
socket.on("chatMessage", (data) => {
|
|
||||||
if (data.channel === "global") {
|
|
||||||
io.emit("chatMessage", {
|
|
||||||
user: socket.user,
|
|
||||||
message: data.message,
|
|
||||||
channel: "global",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.channel === "guild") {
|
|
||||||
io.to("guild_" + data.guild).emit("chatMessage", {
|
|
||||||
user: socket.user,
|
|
||||||
message: data.message,
|
|
||||||
channel: "guild",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("whisper", (data) => {
|
|
||||||
const targetSocket = onlineUsers[data.to];
|
|
||||||
|
|
||||||
if (!targetSocket) {
|
|
||||||
socket.emit("systemMessage", { message: data.to + " ist offline" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
io.to(targetSocket).emit("chatMessage", {
|
|
||||||
user: socket.user,
|
|
||||||
message: data.message,
|
|
||||||
channel: "private",
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.emit("chatMessage", {
|
|
||||||
user: "(an " + data.to + ")",
|
|
||||||
message: data.message,
|
|
||||||
channel: "private",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("privateMessage", (data) => {
|
|
||||||
const target = onlineUsers[data.to];
|
|
||||||
if (target) {
|
|
||||||
io.to(target).emit("chatMessage", {
|
|
||||||
user: socket.user,
|
|
||||||
message: data.message,
|
|
||||||
channel: "private",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ── Disconnect ── */
|
|
||||||
socket.on("disconnect", () => {
|
|
||||||
// Aus Chat entfernen
|
|
||||||
if (socket.user) {
|
|
||||||
delete onlineUsers[socket.user];
|
|
||||||
io.emit("onlineUsers", Object.keys(onlineUsers));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aus 1v1 Pool entfernen
|
|
||||||
if (waitingPool.delete(socket.id)) {
|
|
||||||
console.log(`[1v1] Spieler ${socket.id} disconnected – aus Pool entfernt.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
|
|||||||
@ -593,3 +593,77 @@ body {
|
|||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Bereit-Timer Box (zentriert im Lock-Overlay) ── */
|
||||||
|
#board-lock-overlay {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#ready-timer-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
background: rgba(10, 8, 5, 0.92);
|
||||||
|
border: 1px solid rgba(255, 215, 80, 0.35);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px 40px;
|
||||||
|
box-shadow: 0 8px 40px rgba(0,0,0,0.8);
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
#ready-timer-label {
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
color: rgba(255, 215, 80, 0.9);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#ready-timer-ring {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.timer-track {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255,255,255,0.1);
|
||||||
|
stroke-width: 6;
|
||||||
|
}
|
||||||
|
.timer-fill {
|
||||||
|
fill: none;
|
||||||
|
stroke: #27ae60;
|
||||||
|
stroke-width: 6;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: stroke-dashoffset 0.9s linear, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
#ready-timer-number {
|
||||||
|
position: absolute;
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 10px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
#ready-timer-sub {
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255,255,255,0.4);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#ready-status-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.ready-pip {
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255,255,255,0.5);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|||||||
199
sockets/arena_socket.js
Normal file
199
sockets/arena_socket.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/* ============================================================
|
||||||
|
sockets/arena.js
|
||||||
|
Alle Socket-Events rund um 1v1 Matchmaking, Spielfeld & Bereit-System
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
const waitingPool = new Map(); // socketId → { socket, player }
|
||||||
|
const LEVEL_RANGE = 5;
|
||||||
|
|
||||||
|
// Werden beim ersten Event lazy initialisiert (auf io gespeichert)
|
||||||
|
// io._arenaRooms → matchId → { sockets, names }
|
||||||
|
// io._arenaReady → matchId → Set of ready slots
|
||||||
|
// io._arenaTimers → matchId → intervalId
|
||||||
|
|
||||||
|
const READY_TIMEOUT = 30; // Sekunden bis Match abgebrochen wird
|
||||||
|
|
||||||
|
function startReadyTimer(io, matchId) {
|
||||||
|
if (!io._arenaTimers) io._arenaTimers = new Map();
|
||||||
|
if (io._arenaTimers.has(matchId)) return; // läuft bereits
|
||||||
|
|
||||||
|
let remaining = READY_TIMEOUT;
|
||||||
|
|
||||||
|
// Sofort ersten Tick senden
|
||||||
|
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
remaining--;
|
||||||
|
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
io._arenaTimers.delete(matchId);
|
||||||
|
|
||||||
|
// Match abbrechen – Funktion noch offen
|
||||||
|
console.log(`[1v1] Match ${matchId} abgebrochen – Zeit abgelaufen.`);
|
||||||
|
io.to("arena_" + matchId).emit("match_cancelled", {
|
||||||
|
reason: "timeout",
|
||||||
|
message: "Zeit abgelaufen – Match wird abgebrochen.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
io._arenaTimers.set(matchId, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopReadyTimer(io, matchId) {
|
||||||
|
if (!io._arenaTimers) return;
|
||||||
|
const interval = io._arenaTimers.get(matchId);
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
io._arenaTimers.delete(matchId);
|
||||||
|
console.log(`[1v1] Timer für Match ${matchId} gestoppt (beide bereit).`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryMatchmaking(io, newSocketId) {
|
||||||
|
const challenger = waitingPool.get(newSocketId);
|
||||||
|
if (!challenger) return;
|
||||||
|
|
||||||
|
for (const [id, entry] of waitingPool) {
|
||||||
|
if (id === newSocketId) continue;
|
||||||
|
|
||||||
|
const levelDiff = Math.abs(entry.player.level - challenger.player.level);
|
||||||
|
if (levelDiff <= LEVEL_RANGE) {
|
||||||
|
waitingPool.delete(newSocketId);
|
||||||
|
waitingPool.delete(id);
|
||||||
|
|
||||||
|
const matchId = `match_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||||
|
|
||||||
|
challenger.socket.emit("match_found", {
|
||||||
|
matchId,
|
||||||
|
opponent: entry.player,
|
||||||
|
mySlot: "player1",
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.socket.emit("match_found", {
|
||||||
|
matchId,
|
||||||
|
opponent: challenger.player,
|
||||||
|
mySlot: "player2",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[1v1] Match: ${challenger.player.name} (Lvl ${challenger.player.level})` +
|
||||||
|
` vs ${entry.player.name} (Lvl ${entry.player.level}) | ${matchId}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerArenaHandlers(io, socket) {
|
||||||
|
|
||||||
|
/* ── Queue beitreten ── */
|
||||||
|
socket.on("join_1v1", (playerData) => {
|
||||||
|
if (waitingPool.has(socket.id)) return;
|
||||||
|
|
||||||
|
const player = {
|
||||||
|
id: playerData.id,
|
||||||
|
name: playerData.name,
|
||||||
|
level: Number(playerData.level) || 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
waitingPool.set(socket.id, { socket, player });
|
||||||
|
|
||||||
|
socket.emit("queue_status", {
|
||||||
|
status: "waiting",
|
||||||
|
poolSize: waitingPool.size,
|
||||||
|
message: `Suche Gegner (Level ${player.level - LEVEL_RANGE}–${player.level + LEVEL_RANGE})…`,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[1v1] ${player.name} (Lvl ${player.level}) im Pool. Größe: ${waitingPool.size}`);
|
||||||
|
tryMatchmaking(io, socket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Queue verlassen ── */
|
||||||
|
socket.on("leave_1v1", () => {
|
||||||
|
if (waitingPool.delete(socket.id)) {
|
||||||
|
socket.emit("queue_status", { status: "left" });
|
||||||
|
console.log(`[1v1] ${socket.id} hat Pool verlassen.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Spielfeld: Spieler betritt Arena-Room ── */
|
||||||
|
socket.on("arena_join", (data) => {
|
||||||
|
const { matchId, slot } = data;
|
||||||
|
if (!matchId || !slot) return;
|
||||||
|
|
||||||
|
if (!io._arenaRooms) io._arenaRooms = new Map();
|
||||||
|
if (!io._arenaRooms.has(matchId)) {
|
||||||
|
io._arenaRooms.set(matchId, { sockets: {}, names: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
const room = io._arenaRooms.get(matchId);
|
||||||
|
room.sockets[slot] = socket.id;
|
||||||
|
room.names[slot] = socket.user || "Spieler";
|
||||||
|
|
||||||
|
socket.join("arena_" + matchId);
|
||||||
|
|
||||||
|
const otherSlot = slot === "player1" ? "player2" : "player1";
|
||||||
|
|
||||||
|
if (room.sockets[otherSlot]) {
|
||||||
|
io.to("arena_" + matchId).emit("arena_ready", {
|
||||||
|
player1: room.names["player1"] || "Spieler 1",
|
||||||
|
player2: room.names["player2"] || "Spieler 2",
|
||||||
|
});
|
||||||
|
console.log(`[Arena] Match ${matchId} bereit: ${room.names["player1"]} vs ${room.names["player2"]}`);
|
||||||
|
|
||||||
|
// 30-Sekunden Bereit-Timer starten
|
||||||
|
startReadyTimer(io, matchId);
|
||||||
|
} else {
|
||||||
|
socket.to("arena_" + matchId).emit("arena_opponent_joined", {
|
||||||
|
name: room.names[slot],
|
||||||
|
slot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Bereit-System ── */
|
||||||
|
socket.on("player_ready", (data) => {
|
||||||
|
const { matchId, slot } = data;
|
||||||
|
if (!matchId || !slot) return;
|
||||||
|
|
||||||
|
if (!io._arenaReady) io._arenaReady = new Map();
|
||||||
|
if (!io._arenaReady.has(matchId)) {
|
||||||
|
io._arenaReady.set(matchId, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
const readySet = io._arenaReady.get(matchId);
|
||||||
|
readySet.add(slot);
|
||||||
|
|
||||||
|
io.to("arena_" + matchId).emit("ready_status", {
|
||||||
|
readyCount: readySet.size,
|
||||||
|
readySlots: Array.from(readySet),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[1v1] ${slot} bereit in ${matchId} (${readySet.size}/2)`);
|
||||||
|
|
||||||
|
if (readySet.size >= 2) {
|
||||||
|
stopReadyTimer(io, matchId);
|
||||||
|
io._arenaReady.delete(matchId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Aufgeben ── */
|
||||||
|
socket.on("player_surrender", (data) => {
|
||||||
|
const { matchId, slot } = data;
|
||||||
|
console.log(`[1v1] ${slot} hat aufgegeben in Match ${matchId}`);
|
||||||
|
// Aufgabe-Logik kommt hier rein
|
||||||
|
io.to("arena_" + matchId).emit("player_surrendered", { slot });
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Disconnect: aus Pool entfernen ── */
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
if (waitingPool.delete(socket.id)) {
|
||||||
|
console.log(`[1v1] ${socket.id} disconnected – aus Pool entfernt.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerArenaHandlers };
|
||||||
89
sockets/chat_socket.js
Normal file
89
sockets/chat_socket.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* ============================================================
|
||||||
|
sockets/chat.js
|
||||||
|
Alle Socket-Events rund um Chat, Whisper & Online-Status
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
const db = require("../database/database");
|
||||||
|
|
||||||
|
const onlineUsers = {}; // ingameName → socketId
|
||||||
|
|
||||||
|
function registerChatHandlers(io, socket) {
|
||||||
|
|
||||||
|
/* ── Registrierung ── */
|
||||||
|
socket.on("register", async (username) => {
|
||||||
|
const [rows] = await db.query(
|
||||||
|
"SELECT ingame_name FROM accounts WHERE username = ?",
|
||||||
|
[username],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rows.length) return;
|
||||||
|
|
||||||
|
const ingameName = rows[0].ingame_name;
|
||||||
|
socket.user = ingameName;
|
||||||
|
onlineUsers[ingameName] = socket.id;
|
||||||
|
io.emit("onlineUsers", Object.keys(onlineUsers));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Chat-Nachrichten ── */
|
||||||
|
socket.on("chatMessage", (data) => {
|
||||||
|
if (data.channel === "global") {
|
||||||
|
io.emit("chatMessage", {
|
||||||
|
user: socket.user,
|
||||||
|
message: data.message,
|
||||||
|
channel: "global",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.channel === "guild") {
|
||||||
|
io.to("guild_" + data.guild).emit("chatMessage", {
|
||||||
|
user: socket.user,
|
||||||
|
message: data.message,
|
||||||
|
channel: "guild",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Flüstern ── */
|
||||||
|
socket.on("whisper", (data) => {
|
||||||
|
const targetSocket = onlineUsers[data.to];
|
||||||
|
|
||||||
|
if (!targetSocket) {
|
||||||
|
socket.emit("systemMessage", { message: data.to + " ist offline" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.to(targetSocket).emit("chatMessage", {
|
||||||
|
user: socket.user,
|
||||||
|
message: data.message,
|
||||||
|
channel: "private",
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit("chatMessage", {
|
||||||
|
user: "(an " + data.to + ")",
|
||||||
|
message: data.message,
|
||||||
|
channel: "private",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Private Nachricht ── */
|
||||||
|
socket.on("privateMessage", (data) => {
|
||||||
|
const target = onlineUsers[data.to];
|
||||||
|
if (target) {
|
||||||
|
io.to(target).emit("chatMessage", {
|
||||||
|
user: socket.user,
|
||||||
|
message: data.message,
|
||||||
|
channel: "private",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Disconnect: aus Online-Liste entfernen ── */
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
if (socket.user) {
|
||||||
|
delete onlineUsers[socket.user];
|
||||||
|
io.emit("onlineUsers", Object.keys(onlineUsers));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerChatHandlers };
|
||||||
@ -42,7 +42,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Spielfeld-Sperre bis beide Spieler bereit sind -->
|
<!-- Spielfeld-Sperre bis beide Spieler bereit sind -->
|
||||||
<div id="board-lock-overlay"></div>
|
<div id="board-lock-overlay">
|
||||||
|
<div id="ready-timer-box">
|
||||||
|
<div id="ready-timer-label">Bereit machen</div>
|
||||||
|
<div id="ready-timer-ring">
|
||||||
|
<svg viewBox="0 0 80 80" width="80" height="80"><circle cx="40" cy="40" r="34" class="timer-track"/><circle cx="40" cy="40" r="34" class="timer-fill" id="timer-circle"/></svg>
|
||||||
|
<div id="ready-timer-number">30</div>
|
||||||
|
</div>
|
||||||
|
<div id="ready-timer-sub">Beide Spieler müssen BEREIT klicken</div>
|
||||||
|
<div id="ready-status-row">
|
||||||
|
<div class="ready-pip" id="pip-player1">⬜ Spieler 1</div>
|
||||||
|
<div class="ready-pip" id="pip-player2">⬜ Spieler 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- LEFT AVATAR (Spieler 1) -->
|
<!-- LEFT AVATAR (Spieler 1) -->
|
||||||
<div class="avatar avatar-left" id="avLeft">
|
<div class="avatar avatar-left" id="avLeft">
|
||||||
@ -240,8 +253,37 @@
|
|||||||
socket.emit("player_ready", { matchId, slot: mySlot });
|
socket.emit("player_ready", { matchId, slot: mySlot });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timer-Kreis: Umfang des Kreises (r=34 → 2*π*34 ≈ 213.6)
|
||||||
|
const CIRCUMFERENCE = 2 * Math.PI * 34;
|
||||||
|
const timerCircle = document.getElementById("timer-circle");
|
||||||
|
if (timerCircle) timerCircle.style.strokeDasharray = CIRCUMFERENCE;
|
||||||
|
|
||||||
|
socket.on("ready_timer", (data) => {
|
||||||
|
const { remaining } = data;
|
||||||
|
const num = document.getElementById("ready-timer-number");
|
||||||
|
if (num) num.textContent = remaining;
|
||||||
|
|
||||||
|
// Kreis-Fortschritt aktualisieren
|
||||||
|
if (timerCircle) {
|
||||||
|
const progress = remaining / 30;
|
||||||
|
const offset = CIRCUMFERENCE * (1 - progress);
|
||||||
|
timerCircle.style.strokeDashoffset = offset;
|
||||||
|
// Farbe: grün → gelb → rot
|
||||||
|
if (remaining > 15) timerCircle.style.stroke = "#27ae60";
|
||||||
|
else if (remaining > 7) timerCircle.style.stroke = "#f39c12";
|
||||||
|
else timerCircle.style.stroke = "#e74c3c";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("ready_status", (data) => {
|
socket.on("ready_status", (data) => {
|
||||||
// data.readyCount = wie viele Spieler bereits bereit sind
|
// Pips aktualisieren
|
||||||
|
const pip1 = document.getElementById("pip-player1");
|
||||||
|
const pip2 = document.getElementById("pip-player2");
|
||||||
|
if (data.readySlots && pip1 && pip2) {
|
||||||
|
if (data.readySlots.includes("player1")) pip1.textContent = "✅ " + (document.getElementById("nameLeft")?.textContent || "Spieler 1");
|
||||||
|
if (data.readySlots.includes("player2")) pip2.textContent = "✅ " + (document.getElementById("nameRight")?.textContent || "Spieler 2");
|
||||||
|
}
|
||||||
|
|
||||||
if (data.readyCount === 2) {
|
if (data.readyCount === 2) {
|
||||||
// Beide bereit → Sperre aufheben
|
// Beide bereit → Sperre aufheben
|
||||||
const lock = document.getElementById("board-lock-overlay");
|
const lock = document.getElementById("board-lock-overlay");
|
||||||
@ -255,6 +297,19 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("match_cancelled", (data) => {
|
||||||
|
const lock = document.getElementById("board-lock-overlay");
|
||||||
|
if (lock) {
|
||||||
|
lock.innerHTML = `
|
||||||
|
<div id="ready-timer-box">
|
||||||
|
<div id="ready-timer-label" style="color:#e74c3c;">⏰ Zeit abgelaufen</div>
|
||||||
|
<div id="ready-timer-sub">${data.message || "Match abgebrochen."}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
// Aufgabe-Logik kommt hier rein
|
||||||
|
});
|
||||||
|
|
||||||
function handleAufgeben() {
|
function handleAufgeben() {
|
||||||
// Funktion offen – hier kann später die Aufgabe-Logik rein
|
// Funktion offen – hier kann später die Aufgabe-Logik rein
|
||||||
socket.emit("player_surrender", { matchId, slot: mySlot });
|
socket.emit("player_surrender", { matchId, slot: mySlot });
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user