diff --git a/public/css/daily.css b/public/css/daily.css
index 1d8440c..0a4043b 100644
--- a/public/css/daily.css
+++ b/public/css/daily.css
@@ -67,9 +67,8 @@
/* Locked: dunkle Abdunklung über dem Bildkreis */
.ds-locked .ds-overlay {
- fill: rgba(0,0,5,.62);
- stroke: rgba(50,60,100,.45);
- stroke-width: .8;
+ fill: rgba(0,0,5,.35);
+ stroke: none;
}
.ds-locked .ds-num {
fill: rgba(120,130,160,.38);
@@ -79,9 +78,8 @@
/* Available: transparente Mitte, leuchtender Rand */
.ds-available .ds-overlay {
- fill: rgba(40,100,255,.08);
- stroke: rgba(100,200,255,.9);
- stroke-width: 1.2;
+ fill: transparent;
+ stroke: none;
}
.ds-available .ds-num {
fill: #fff;
@@ -112,9 +110,9 @@
/* Done: grüne Einfärbung */
.ds-done .ds-overlay {
- fill: rgba(30,160,70,.35);
- stroke: rgba(80,220,110,.85);
- stroke-width: 1.2;
+ fill: rgba(30,160,70,.55);
+ stroke: rgba(80,220,110,.7);
+ stroke-width: .8;
}
.ds-done .ds-check {
fill: #fff;
diff --git a/public/js/buildings/1v1.js b/public/js/buildings/1v1.js
index 371d1f9..4355902 100644
--- a/public/js/buildings/1v1.js
+++ b/public/js/buildings/1v1.js
@@ -843,18 +843,6 @@ function updateHpDisplay(slot, currentHp, maxHp) {
orbEl.style.boxShadow = `0 0 12px ${color}cc`;
}
- /* Avatar-Schüttelanimation + roter Flash */
- if (avEl) {
- avEl.style.transition = 'transform 0.07s ease';
- avEl.style.transform = 'scale(1.07)';
- setTimeout(() => { avEl.style.transform = 'scale(0.96)'; }, 70);
- setTimeout(() => { avEl.style.transform = ''; }, 150);
-
- const flash = document.createElement('div');
- flash.style.cssText = 'position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;';
- avEl.appendChild(flash);
- setTimeout(() => flash.remove(), 320);
- }
}
function applyHpFromEvent(data) {
@@ -891,6 +879,17 @@ socket.on('avatar_damaged', data => {
animation:dmg-float 2.5s ease forwards;`;
avEl.appendChild(dmg);
setTimeout(() => dmg.remove(), 2500);
+
+ /* Schüttelanimation + roter Flash NUR bei Treffer */
+ avEl.style.transition = 'transform 0.07s ease';
+ avEl.style.transform = 'scale(1.07)';
+ setTimeout(() => { avEl.style.transform = 'scale(0.96)'; }, 70);
+ setTimeout(() => { avEl.style.transform = ''; }, 150);
+
+ const flash = document.createElement('div');
+ flash.style.cssText = 'position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;';
+ avEl.appendChild(flash);
+ setTimeout(() => flash.remove(), 320);
}
});
diff --git a/public/js/buildings/daily.js b/public/js/buildings/daily.js
index d7d6006..f72c0bb 100644
--- a/public/js/buildings/daily.js
+++ b/public/js/buildings/daily.js
@@ -3,13 +3,13 @@
/* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */
const STATIONS = [
- { id:1, cx:49.5, cy:86.5 },
- { id:2, cx:37.0, cy:73.0 },
- { id:3, cx:44.5, cy:62.0 },
- { id:4, cx:54.5, cy:53.5 },
- { id:5, cx:49.0, cy:46.0 },
- { id:6, cx:44.5, cy:37.5 },
- { id:7, cx:50.0, cy:22.0 },
+ { id:1, cx:48.5, cy:87.0 },
+ { id:2, cx:36.5, cy:73.5 },
+ { id:3, cx:44.0, cy:63.0 },
+ { id:4, cx:53.5, cy:54.5 },
+ { id:5, cx:47.5, cy:46.5 },
+ { id:6, cx:43.5, cy:38.5 },
+ { id:7, cx:49.5, cy:23.0 },
];
const R = 4.8; // Kreis-Radius in SVG-Einheiten
@@ -90,40 +90,34 @@ function buildSvg(completed) {
`);
} else if (isAvail) {
+ // Available: nur pulsierende Ringe über dem Bild-Kreis, kein eigener Kreis
parts.push(`
-
-
+
+ values="${R+1.2};${R+2.8};${R+1.2}" dur="2s" repeatCount="indefinite"/>
+ values=".8;0;.8" dur="2s" repeatCount="indefinite"/>
-
-
+
+ values="${R+2};${R+4};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
+ values=".5;0;.5" dur="2s" begin=".7s" repeatCount="indefinite"/>
-
-
-
- ${s.id}
-
+
+ fill="transparent" stroke="none" data-station="${s.id}"/>
`);
} else {
+ // Locked: unsichtbare Hitfläche, kein eigener Kreis (Bild-Kreis bleibt sichtbar)
parts.push(`
-
- ${s.id}
+
+
`);
}
});
diff --git a/sockets/1vKI_daily.socket.js b/sockets/1vKI_daily.socket.js
index 0d6ec6d..a1291ac 100644
--- a/sockets/1vKI_daily.socket.js
+++ b/sockets/1vKI_daily.socket.js
@@ -76,21 +76,41 @@ async function loadAiDeck(station, playerLevel) {
[maxRarity, deckSize]
);
- if (cards.length > 0) {
- console.log(`[HT] KI-Deck Station ${station}: ${cards.length} Karten, max. Rarity ${maxRarity}`);
- return cards.map(c => ({ ...c, currentCd: 0 }));
+ // Wenn nicht genug Karten → Rarity schrittweise erhöhen
+ let result = [...cards];
+
+ if (result.length < deckSize) {
+ for (let r = maxRarity + 1; r <= 6 && result.length < deckSize; r++) {
+ const [more] = await db.query(
+ `SELECT id, name, image, attack, defends, cooldown, \`range\`, \`race\`, rarity
+ FROM cards WHERE rarity = ? ORDER BY RAND() LIMIT ?`,
+ [r, deckSize - result.length]
+ );
+ result = [...result, ...more];
+ }
}
- // Fallback: beliebige Karten wenn keine mit passender Rarity gefunden
- console.warn(`[HT] Keine Karten mit Rarity <= ${maxRarity} – Fallback auf alle Karten`);
- const [all] = await db.query(
- `SELECT id, name, image, attack, defends, cooldown, \`range\`, \`race\`, rarity
- FROM cards
- ORDER BY RAND()
- LIMIT ?`,
- [deckSize]
- );
- return all.map(c => ({ ...c, currentCd: 0 }));
+ // Letzter Fallback: alle Karten ohne Rarity-Filter
+ if (result.length === 0) {
+ const [all] = await db.query(
+ `SELECT id, name, image, attack, defends, cooldown, \`range\`, \`race\`, rarity
+ FROM cards ORDER BY RAND() LIMIT ?`,
+ [deckSize]
+ );
+ result = all;
+ }
+
+ // Noch immer zu wenig? → vorhandene Karten so oft wiederholen bis deckSize erreicht
+ if (result.length > 0 && result.length < deckSize) {
+ const base = [...result];
+ while (result.length < deckSize) {
+ result.push({ ...base[result.length % base.length] });
+ }
+ console.log(`[HT] KI-Deck aufgefüllt durch Wiederholung: ${base.length} unique → ${result.length} Karten`);
+ }
+
+ console.log(`[HT] KI-Deck Station ${station}: ${result.length} Karten, max. Rarity ${maxRarity}`);
+ return result.map(c => ({ ...c, currentCd: 0 }));
} catch (err) {
console.error('[HT] loadAiDeck Fehler:', err);
@@ -437,8 +457,18 @@ function registerHimmelstorHandlers(io, socket) {
const aiRoom = htAiRooms.get(matchId);
if (!aiRoom) return;
- // Socket + Namen aktualisieren
+ // Socket + Namen + AccountId aktualisieren
aiRoom.playerSocketId = socket.id;
+ // accountId aus arena_join data setzen falls noch nicht gesetzt (Session-Fallback)
+ if (!aiRoom.playerAccountId && data.accountId) {
+ aiRoom.playerAccountId = data.accountId;
+ }
+ // Auch io._arenaRooms synchronisieren
+ const ioRoom2 = io._arenaRooms?.get(matchId);
+ if (ioRoom2 && !ioRoom2.accountIds?.player1 && data.accountId) {
+ if (!ioRoom2.accountIds) ioRoom2.accountIds = {};
+ ioRoom2.accountIds.player1 = data.accountId;
+ }
const myIngameName = data.playerName || 'Du';
const ioRoom = io._arenaRooms?.get(matchId);
if (ioRoom) ioRoom.sockets.player1 = socket.id;