This commit is contained in:
cay 2026-04-14 10:46:01 +01:00
parent 61043ebdd4
commit 4d114385b3
4 changed files with 83 additions and 62 deletions

View File

@ -67,9 +67,8 @@
/* Locked: dunkle Abdunklung über dem Bildkreis */ /* Locked: dunkle Abdunklung über dem Bildkreis */
.ds-locked .ds-overlay { .ds-locked .ds-overlay {
fill: rgba(0,0,5,.62); fill: rgba(0,0,5,.35);
stroke: rgba(50,60,100,.45); stroke: none;
stroke-width: .8;
} }
.ds-locked .ds-num { .ds-locked .ds-num {
fill: rgba(120,130,160,.38); fill: rgba(120,130,160,.38);
@ -79,9 +78,8 @@
/* Available: transparente Mitte, leuchtender Rand */ /* Available: transparente Mitte, leuchtender Rand */
.ds-available .ds-overlay { .ds-available .ds-overlay {
fill: rgba(40,100,255,.08); fill: transparent;
stroke: rgba(100,200,255,.9); stroke: none;
stroke-width: 1.2;
} }
.ds-available .ds-num { .ds-available .ds-num {
fill: #fff; fill: #fff;
@ -112,9 +110,9 @@
/* Done: grüne Einfärbung */ /* Done: grüne Einfärbung */
.ds-done .ds-overlay { .ds-done .ds-overlay {
fill: rgba(30,160,70,.35); fill: rgba(30,160,70,.55);
stroke: rgba(80,220,110,.85); stroke: rgba(80,220,110,.7);
stroke-width: 1.2; stroke-width: .8;
} }
.ds-done .ds-check { .ds-done .ds-check {
fill: #fff; fill: #fff;

View File

@ -843,18 +843,6 @@ function updateHpDisplay(slot, currentHp, maxHp) {
orbEl.style.boxShadow = `0 0 12px ${color}cc`; 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) { function applyHpFromEvent(data) {
@ -891,6 +879,17 @@ socket.on('avatar_damaged', data => {
animation:dmg-float 2.5s ease forwards;`; animation:dmg-float 2.5s ease forwards;`;
avEl.appendChild(dmg); avEl.appendChild(dmg);
setTimeout(() => dmg.remove(), 2500); 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);
} }
}); });

View File

@ -3,13 +3,13 @@
/* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */ /* ── Positionen (% in viewBox 0 0 100 100) ───────────────── */
const STATIONS = [ const STATIONS = [
{ id:1, cx:49.5, cy:86.5 }, { id:1, cx:48.5, cy:87.0 },
{ id:2, cx:37.0, cy:73.0 }, { id:2, cx:36.5, cy:73.5 },
{ id:3, cx:44.5, cy:62.0 }, { id:3, cx:44.0, cy:63.0 },
{ id:4, cx:54.5, cy:53.5 }, { id:4, cx:53.5, cy:54.5 },
{ id:5, cx:49.0, cy:46.0 }, { id:5, cx:47.5, cy:46.5 },
{ id:6, cx:44.5, cy:37.5 }, { id:6, cx:43.5, cy:38.5 },
{ id:7, cx:50.0, cy:22.0 }, { id:7, cx:49.5, cy:23.0 },
]; ];
const R = 4.8; // Kreis-Radius in SVG-Einheiten const R = 4.8; // Kreis-Radius in SVG-Einheiten
@ -90,40 +90,34 @@ function buildSvg(completed) {
</g>`); </g>`);
} else if (isAvail) { } else if (isAvail) {
// Available: nur pulsierende Ringe über dem Bild-Kreis, kein eigener Kreis
parts.push(` parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}"> <g class="ds-station ${cls}" data-station="${s.id}">
<!-- Pulsierender Ring 1 --> <circle class="ds-ring1" cx="${s.cx}" cy="${s.cy}" r="${R + 1.2}"
<circle class="ds-ring1" cx="${s.cx}" cy="${s.cy}" r="${R + 1.2}"> fill="none" stroke="rgba(80,200,255,.8)" stroke-width="1.2">
<animate attributeName="r" <animate attributeName="r"
values="${R+1.2};${R+2.6};${R+1.2}" dur="2s" repeatCount="indefinite"/> values="${R+1.2};${R+2.8};${R+1.2}" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" <animate attributeName="opacity"
values=".7;0;.7" dur="2s" repeatCount="indefinite"/> values=".8;0;.8" dur="2s" repeatCount="indefinite"/>
</circle> </circle>
<!-- Pulsierender Ring 2 (versetzt) --> <circle class="ds-ring2" cx="${s.cx}" cy="${s.cy}" r="${R + 2}"
<circle class="ds-ring2" cx="${s.cx}" cy="${s.cy}" r="${R + 2}"> fill="none" stroke="rgba(80,200,255,.4)" stroke-width="0.8">
<animate attributeName="r" <animate attributeName="r"
values="${R+2};${R+3.6};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/> values="${R+2};${R+4};${R+2}" dur="2s" begin=".7s" repeatCount="indefinite"/>
<animate attributeName="opacity" <animate attributeName="opacity"
values=".4;0;.4" dur="2s" begin=".7s" repeatCount="indefinite"/> values=".5;0;.5" dur="2s" begin=".7s" repeatCount="indefinite"/>
</circle> </circle>
<!-- Sichtbarer Kreis --> <!-- Transparente Hitbox -->
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<!-- Nummer -->
<text class="ds-num" x="${s.cx}" y="${s.cy}"
text-anchor="middle" dominant-baseline="central"
font-size="${fSize}">${s.id}</text>
<!-- Unsichtbare Hitbox -->
<circle class="ds-hit" cx="${s.cx}" cy="${s.cy}" r="${R + 1.5}" <circle class="ds-hit" cx="${s.cx}" cy="${s.cy}" r="${R + 1.5}"
data-station="${s.id}"/> fill="transparent" stroke="none" data-station="${s.id}"/>
</g>`); </g>`);
} else { } else {
// Locked: unsichtbare Hitfläche, kein eigener Kreis (Bild-Kreis bleibt sichtbar)
parts.push(` parts.push(`
<g class="ds-station ${cls}" data-station="${s.id}"> <g class="ds-station ${cls}" data-station="${s.id}">
<circle class="ds-overlay" cx="${s.cx}" cy="${s.cy}" r="${R}"/> <circle fill="rgba(0,0,0,0.35)" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R}"/>
<text class="ds-num" x="${s.cx}" y="${s.cy}" <circle fill="transparent" stroke="none" cx="${s.cx}" cy="${s.cy}" r="${R + 1}"/>
text-anchor="middle" dominant-baseline="central"
font-size="${fSize}">${s.id}</text>
</g>`); </g>`);
} }
}); });

View File

@ -76,21 +76,41 @@ async function loadAiDeck(station, playerLevel) {
[maxRarity, deckSize] [maxRarity, deckSize]
); );
if (cards.length > 0) { // Wenn nicht genug Karten → Rarity schrittweise erhöhen
console.log(`[HT] KI-Deck Station ${station}: ${cards.length} Karten, max. Rarity ${maxRarity}`); let result = [...cards];
return cards.map(c => ({ ...c, currentCd: 0 }));
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 // Letzter Fallback: alle Karten ohne Rarity-Filter
console.warn(`[HT] Keine Karten mit Rarity <= ${maxRarity} Fallback auf alle Karten`); if (result.length === 0) {
const [all] = await db.query( const [all] = await db.query(
`SELECT id, name, image, attack, defends, cooldown, \`range\`, \`race\`, rarity `SELECT id, name, image, attack, defends, cooldown, \`range\`, \`race\`, rarity
FROM cards FROM cards ORDER BY RAND() LIMIT ?`,
ORDER BY RAND()
LIMIT ?`,
[deckSize] [deckSize]
); );
return all.map(c => ({ ...c, currentCd: 0 })); 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) { } catch (err) {
console.error('[HT] loadAiDeck Fehler:', err); console.error('[HT] loadAiDeck Fehler:', err);
@ -437,8 +457,18 @@ function registerHimmelstorHandlers(io, socket) {
const aiRoom = htAiRooms.get(matchId); const aiRoom = htAiRooms.get(matchId);
if (!aiRoom) return; if (!aiRoom) return;
// Socket + Namen aktualisieren // Socket + Namen + AccountId aktualisieren
aiRoom.playerSocketId = socket.id; 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 myIngameName = data.playerName || 'Du';
const ioRoom = io._arenaRooms?.get(matchId); const ioRoom = io._arenaRooms?.get(matchId);
if (ioRoom) ioRoom.sockets.player1 = socket.id; if (ioRoom) ioRoom.sockets.player1 = socket.id;