dopamine effects
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
@namespace Faraon.Client.Components
|
@namespace Faraon.Client.Components
|
||||||
|
@inject FxService Fx
|
||||||
|
|
||||||
<div class="player-hand-container">
|
<div class="player-hand-container">
|
||||||
@if (SelectedCards.Count > 0 && IsMyTurn)
|
@if (SelectedCards.Count > 0 && IsMyTurn)
|
||||||
@@ -171,6 +172,7 @@
|
|||||||
|
|
||||||
// Ak má viac kariet rovnakej hodnoty -> označíme túto ako 1. a vysunú sa všetky karty danej hodnoty!
|
// Ak má viac kariet rovnakej hodnoty -> označíme túto ako 1. a vysunú sa všetky karty danej hodnoty!
|
||||||
SelectedCards.Add(card);
|
SelectedCards.Add(card);
|
||||||
|
await Fx.PlaySelectStepAsync(1);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,6 +195,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectedCards.Add(card);
|
SelectedCards.Add(card);
|
||||||
|
await Fx.PlaySelectStepAsync(1);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -209,25 +212,28 @@
|
|||||||
{
|
{
|
||||||
// Odznačíme kartu zo zoznamu
|
// Odznačíme kartu zo zoznamu
|
||||||
SelectedCards.Remove(card);
|
SelectedCards.Remove(card);
|
||||||
|
await Fx.PlaySelectStepAsync(Math.Max(1, SelectedCards.Count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Pridáme do poradia ako 2., 3., 4. kartu
|
// Pridáme do poradia ako 2., 3., 4. kartu
|
||||||
SelectedCards.Add(card);
|
SelectedCards.Add(card);
|
||||||
|
await Fx.PlaySelectStepAsync(SelectedCards.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReverseSelectionOrder()
|
private async Task ReverseSelectionOrder()
|
||||||
{
|
{
|
||||||
if (SelectedCards.Count <= 1) return;
|
if (SelectedCards.Count <= 1) return;
|
||||||
SelectedCards.Reverse();
|
SelectedCards.Reverse();
|
||||||
|
await Fx.PlaySelectStepAsync(SelectedCards.Count);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectAllOfRank()
|
private async Task SelectAllOfRank()
|
||||||
{
|
{
|
||||||
if (SelectedCards.Count == 0) return;
|
if (SelectedCards.Count == 0) return;
|
||||||
var rank = SelectedCards[0].Rank;
|
var rank = SelectedCards[0].Rank;
|
||||||
@@ -238,6 +244,7 @@
|
|||||||
SelectedCards.Add(c);
|
SelectedCards.Add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await Fx.PlaySelectStepAsync(SelectedCards.Count);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -283,20 +283,26 @@ else
|
|||||||
break;
|
break;
|
||||||
case GameEventType.SevenStacked:
|
case GameEventType.SevenStacked:
|
||||||
await Fx.PlaySevenStackAsync(Math.Max(1, e.ExtraData / 2));
|
await Fx.PlaySevenStackAsync(Math.Max(1, e.ExtraData / 2));
|
||||||
|
await Fx.TriggerDopamineBannerAsync($"🔥 +{e.ExtraData} KARIET!", "Trestná reťaz sediem", "triple");
|
||||||
break;
|
break;
|
||||||
case GameEventType.AceSkipped:
|
case GameEventType.AceSkipped:
|
||||||
case GameEventType.AceCountered:
|
case GameEventType.AceCountered:
|
||||||
await Fx.PlayAceFreezeAsync();
|
await Fx.TriggerFreezeEffectAsync();
|
||||||
|
break;
|
||||||
|
case GameEventType.PlayerRevived:
|
||||||
|
await Fx.TriggerReviveEffectAsync(e.TargetPlayer?.Name ?? e.SourcePlayer?.Name ?? "Hráč");
|
||||||
break;
|
break;
|
||||||
case GameEventType.SuitChanged:
|
case GameEventType.SuitChanged:
|
||||||
await Fx.PlayHornikChimeAsync();
|
await Fx.PlayHornikChimeAsync();
|
||||||
break;
|
break;
|
||||||
case GameEventType.CardsBurned:
|
case GameEventType.CardsBurned:
|
||||||
await Fx.PlayBurnExplosionAsync();
|
await Fx.PlayBurnExplosionAsync();
|
||||||
|
await Fx.TriggerDopamineBannerAsync("💥 SPÁLENÉ!", "Zhoda 4 kariet na stole", "quad");
|
||||||
break;
|
break;
|
||||||
case GameEventType.PlayerFinished:
|
case GameEventType.PlayerFinished:
|
||||||
|
case GameEventType.MatchWon:
|
||||||
case GameEventType.GameOver:
|
case GameEventType.GameOver:
|
||||||
await Fx.PlayVictoryAsync();
|
await Fx.TriggerJackpotWinAsync(e.SourcePlayer?.Name ?? "Víťaz");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +355,19 @@ else
|
|||||||
string pos = GetPlayerPosition(ViewingPlayer);
|
string pos = GetPlayerPosition(ViewingPlayer);
|
||||||
string deck = Engine.State.Rules.SelectedDeck;
|
string deck = Engine.State.Rules.SelectedDeck;
|
||||||
|
|
||||||
|
if (cards.Count == 2)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(2, "⚡ DVOJIČKA!");
|
||||||
|
}
|
||||||
|
else if (cards.Count == 3)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(3, "🔥 TROJITÝ ÚDER!");
|
||||||
|
}
|
||||||
|
else if (cards.Count >= 4)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(4, "👑 KVADRA / MEGA COMBO!");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var card in cards)
|
foreach (var card in cards)
|
||||||
{
|
{
|
||||||
_ = Fx.AnimateCardPlayAsync(pos, card, deck);
|
_ = Fx.AnimateCardPlayAsync(pos, card, deck);
|
||||||
@@ -466,6 +485,19 @@ else
|
|||||||
var cardsToPlay = decision.Cards ?? (decision.Card != null ? new List<Card> { decision.Card } : new List<Card>());
|
var cardsToPlay = decision.Cards ?? (decision.Card != null ? new List<Card> { decision.Card } : new List<Card>());
|
||||||
if (cardsToPlay.Count > 0)
|
if (cardsToPlay.Count > 0)
|
||||||
{
|
{
|
||||||
|
if (cardsToPlay.Count == 2)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(2, $"⚡ {aiPlayer.Name}: DVOJIČKA!");
|
||||||
|
}
|
||||||
|
else if (cardsToPlay.Count == 3)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(3, $"🔥 {aiPlayer.Name}: TROJITÝ ÚDER!");
|
||||||
|
}
|
||||||
|
else if (cardsToPlay.Count >= 4)
|
||||||
|
{
|
||||||
|
_ = Fx.TriggerComboSlamAsync(4, $"👑 {aiPlayer.Name}: KVADRA!");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var c in cardsToPlay)
|
foreach (var c in cardsToPlay)
|
||||||
{
|
{
|
||||||
_ = Fx.AnimateCardPlayAsync(pos, c, deck);
|
_ = Fx.AnimateCardPlayAsync(pos, c, deck);
|
||||||
|
|||||||
@@ -114,9 +114,57 @@ public class FxService
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask PlaySelectStepAsync(int step)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("FaraonFx.playSelectStep", step); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TriggerComboSlamAsync(int count, string title, string sub = "")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string type = count >= 4 ? "quad" : (count == 3 ? "triple" : "double");
|
||||||
|
await _js.InvokeVoidAsync("FaraonFx.triggerDopamineBanner", title, sub, type);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TriggerFreezeEffectAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _js.InvokeVoidAsync("FaraonFx.triggerDopamineBanner", "❄️ ZMRAZENÝ!", "Stojíš jedno kolo", "freeze");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TriggerReviveEffectAsync(string playerName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _js.InvokeVoidAsync("FaraonFx.triggerDopamineBanner", "⚡ VSTÁVAJ Z MŔTVYCH!", $"{playerName} je späť v hre!", "revive");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TriggerJackpotWinAsync(string winnerName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _js.InvokeVoidAsync("FaraonFx.triggerDopamineBanner", "🏆 FARAÓN!", $"{winnerName} vyhráva hru!", "jackpot");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TriggerDopamineBannerAsync(string title, string subtitle, string type)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("FaraonFx.triggerDopamineBanner", title, subtitle, type); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask<bool> ToggleMuteAsync()
|
public async ValueTask<bool> ToggleMuteAsync()
|
||||||
{
|
{
|
||||||
try { return await _js.InvokeAsync<bool>("FaraonFx.toggleMute"); } catch { return false; }
|
try { return await _js.InvokeAsync<bool>("FaraonFx.toggleMute"); } catch { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1417,19 +1417,139 @@ body, html {
|
|||||||
/* Event Ticker Message */
|
/* Event Ticker Message */
|
||||||
.event-ticker {
|
.event-ticker {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 210px;
|
bottom: calc(var(--card-height) + 65px);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.75);
|
||||||
border: 1px solid rgba(245, 176, 65, 0.4);
|
border: 1px solid rgba(245, 176, 65, 0.5);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding: 5px 16px;
|
padding: 6px 18px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- DOPAMINE POPUP BANNER --- */
|
||||||
|
.dopamine-banner {
|
||||||
|
position: absolute;
|
||||||
|
top: 38%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(0.6);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 500;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: dopamine-pop-in 0.45s cubic-bezier(0.175, 0.885, 0.32, 1.35) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-content {
|
||||||
|
position: relative;
|
||||||
|
padding: 12px 32px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: rgba(10, 15, 25, 0.92);
|
||||||
|
border: 3px solid #ffd700;
|
||||||
|
box-shadow: 0 0 50px rgba(255, 215, 0, 0.6), inset 0 0 25px rgba(255, 215, 0, 0.3);
|
||||||
|
text-align: center;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-title {
|
||||||
|
font-family: 'Cinzel', serif;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 20px #ffd700, 0 4px 10px rgba(0,0,0,0.8);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-sub {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ffd700;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-top: 2px;
|
||||||
|
text-shadow: 0 2px 6px rgba(0,0,0,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Types */
|
||||||
|
.dopamine-double .dopamine-content {
|
||||||
|
border-color: #00e5ff;
|
||||||
|
box-shadow: 0 0 45px rgba(0, 229, 255, 0.7), inset 0 0 20px rgba(0, 229, 255, 0.3);
|
||||||
|
}
|
||||||
|
.dopamine-double .dopamine-title {
|
||||||
|
text-shadow: 0 0 25px #00e5ff, 0 0 40px #00e5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-triple .dopamine-content {
|
||||||
|
border-color: #ff4500;
|
||||||
|
box-shadow: 0 0 55px rgba(255, 69, 0, 0.8), inset 0 0 25px rgba(255, 140, 0, 0.4);
|
||||||
|
}
|
||||||
|
.dopamine-triple .dopamine-title {
|
||||||
|
text-shadow: 0 0 30px #ff4500, 0 0 50px #ff8c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-quad .dopamine-content, .dopamine-mega .dopamine-content {
|
||||||
|
border-color: #ffd700;
|
||||||
|
background: linear-gradient(135deg, rgba(30, 20, 5, 0.95), rgba(15, 10, 0, 0.95));
|
||||||
|
box-shadow: 0 0 80px rgba(255, 215, 0, 0.9), inset 0 0 35px rgba(255, 215, 0, 0.5);
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
.dopamine-quad .dopamine-title {
|
||||||
|
font-size: 2.7rem;
|
||||||
|
text-shadow: 0 0 35px #ffd700, 0 0 60px #ff8c00, 0 0 90px #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-freeze .dopamine-content {
|
||||||
|
border-color: #00ffff;
|
||||||
|
box-shadow: 0 0 60px rgba(0, 255, 255, 0.8), inset 0 0 25px rgba(0, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-revive .dopamine-content {
|
||||||
|
border-color: #ff0055;
|
||||||
|
box-shadow: 0 0 65px rgba(255, 0, 85, 0.9), inset 0 0 30px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-jackpot .dopamine-content {
|
||||||
|
border-color: #ffd700;
|
||||||
|
box-shadow: 0 0 90px rgba(255, 215, 0, 1), inset 0 0 40px rgba(255, 215, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dopamine-fadeout {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -70%) scale(1.2);
|
||||||
|
transition: all 0.35s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dopamine-pop-in {
|
||||||
|
0% { transform: translate(-50%, -50%) scale(0.3); opacity: 0; }
|
||||||
|
60% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HOLOGRAPHIC FOIL SHINE ON CARDS */
|
||||||
|
.game-card.playable::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
background: linear-gradient(115deg, transparent 20%, rgba(255, 255, 255, 0.4) 40%, rgba(255, 215, 0, 0.5) 50%, rgba(0, 229, 255, 0.4) 60%, transparent 80%);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: holo-shine 2.8s infinite linear;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes holo-shine {
|
||||||
|
0% { background-position: -200% -200%; }
|
||||||
|
100% { background-position: 200% 200%; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|||||||
@@ -617,6 +617,271 @@ window.FaraonFx = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- DOPAMINE PROCEDURAL AUDIO ---
|
||||||
|
function playSelectStep(step = 1) {
|
||||||
|
if (isMuted) return;
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const now = ctx.currentTime;
|
||||||
|
// Pentatonic rising scale (C5, E5, G5, C6, E6)
|
||||||
|
const freqs = [523.25, 659.25, 783.99, 1046.50, 1318.51];
|
||||||
|
const freq = freqs[Math.min(step - 1, freqs.length - 1)] || 523.25;
|
||||||
|
|
||||||
|
const osc = ctx.createOscillator();
|
||||||
|
const harm = ctx.createOscillator();
|
||||||
|
const gain = ctx.createGain();
|
||||||
|
|
||||||
|
osc.type = 'triangle';
|
||||||
|
osc.frequency.setValueAtTime(freq, now);
|
||||||
|
osc.frequency.exponentialRampToValueAtTime(freq * 1.02, now + 0.12);
|
||||||
|
|
||||||
|
harm.type = 'sine';
|
||||||
|
harm.frequency.setValueAtTime(freq * 2, now);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.28, now);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.14);
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
harm.connect(gain);
|
||||||
|
gain.connect(ctx.destination);
|
||||||
|
|
||||||
|
osc.start(now);
|
||||||
|
harm.start(now);
|
||||||
|
osc.stop(now + 0.15);
|
||||||
|
harm.stop(now + 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playComboSlam(count = 2) {
|
||||||
|
if (isMuted) return;
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const now = ctx.currentTime;
|
||||||
|
// Heavy 808 Sub-bass drop
|
||||||
|
const sub = ctx.createOscillator();
|
||||||
|
const subGain = ctx.createGain();
|
||||||
|
sub.type = 'sine';
|
||||||
|
sub.frequency.setValueAtTime(140 + count * 20, now);
|
||||||
|
sub.frequency.exponentialRampToValueAtTime(32, now + 0.45);
|
||||||
|
|
||||||
|
subGain.gain.setValueAtTime(0.7, now);
|
||||||
|
subGain.gain.exponentialRampToValueAtTime(0.001, now + 0.45);
|
||||||
|
|
||||||
|
sub.connect(subGain);
|
||||||
|
subGain.connect(ctx.destination);
|
||||||
|
|
||||||
|
sub.start(now);
|
||||||
|
sub.stop(now + 0.46);
|
||||||
|
|
||||||
|
// Metallic punch noise
|
||||||
|
const bufferSize = ctx.sampleRate * 0.12;
|
||||||
|
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
|
||||||
|
const data = buffer.getChannelData(0);
|
||||||
|
for (let i = 0; i < bufferSize; i++) {
|
||||||
|
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (ctx.sampleRate * 0.03));
|
||||||
|
}
|
||||||
|
const noise = ctx.createBufferSource();
|
||||||
|
noise.buffer = buffer;
|
||||||
|
const filter = ctx.createBiquadFilter();
|
||||||
|
filter.type = 'bandpass';
|
||||||
|
filter.frequency.setValueAtTime(1800, now);
|
||||||
|
filter.Q.setValueAtTime(4, now);
|
||||||
|
|
||||||
|
const noiseGain = ctx.createGain();
|
||||||
|
noiseGain.gain.setValueAtTime(0.5, now);
|
||||||
|
noiseGain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
|
||||||
|
|
||||||
|
noise.connect(filter);
|
||||||
|
filter.connect(noiseGain);
|
||||||
|
noiseGain.connect(ctx.destination);
|
||||||
|
noise.start(now);
|
||||||
|
|
||||||
|
// Escalating screen shake
|
||||||
|
triggerScreenShake(count >= 4 ? 16 : (count === 3 ? 10 : 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
function playJackpotCoins() {
|
||||||
|
if (isMuted) return;
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const now = ctx.currentTime;
|
||||||
|
const notes = [987.77, 1174.66, 1318.51, 1567.98, 1760.00, 2093.00, 2349.32, 2793.83];
|
||||||
|
notes.forEach((freq, idx) => {
|
||||||
|
const time = now + idx * 0.06;
|
||||||
|
const osc = ctx.createOscillator();
|
||||||
|
const gain = ctx.createGain();
|
||||||
|
osc.type = 'sine';
|
||||||
|
osc.frequency.setValueAtTime(freq, time);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.22, time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.22);
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(ctx.destination);
|
||||||
|
|
||||||
|
osc.start(time);
|
||||||
|
osc.stop(time + 0.23);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function playReviveSound() {
|
||||||
|
if (isMuted) return;
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const now = ctx.currentTime;
|
||||||
|
const chord = [440, 554.37, 659.25, 880, 1108.73];
|
||||||
|
chord.forEach((freq, i) => {
|
||||||
|
const osc = ctx.createOscillator();
|
||||||
|
const gain = ctx.createGain();
|
||||||
|
osc.type = 'sawtooth';
|
||||||
|
osc.frequency.setValueAtTime(freq, now + i * 0.04);
|
||||||
|
osc.frequency.linearRampToValueAtTime(freq * 1.5, now + 0.5);
|
||||||
|
|
||||||
|
const filter = ctx.createBiquadFilter();
|
||||||
|
filter.type = 'lowpass';
|
||||||
|
filter.frequency.setValueAtTime(800, now);
|
||||||
|
filter.frequency.exponentialRampToValueAtTime(3500, now + 0.4);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.25, now + i * 0.04);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.6);
|
||||||
|
|
||||||
|
osc.connect(filter);
|
||||||
|
filter.connect(gain);
|
||||||
|
gain.connect(ctx.destination);
|
||||||
|
|
||||||
|
osc.start(now + i * 0.04);
|
||||||
|
osc.stop(now + 0.62);
|
||||||
|
});
|
||||||
|
|
||||||
|
triggerScreenShake(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DOPAMINE BANNER SYSTEM ---
|
||||||
|
function triggerDopamineBanner(title, subtitle = '', type = 'combo') {
|
||||||
|
const container = document.getElementById('game-table-container') || document.body;
|
||||||
|
|
||||||
|
// Remove existing banner if any
|
||||||
|
const oldBanner = document.querySelector('.dopamine-banner');
|
||||||
|
if (oldBanner) oldBanner.remove();
|
||||||
|
|
||||||
|
const banner = document.createElement('div');
|
||||||
|
banner.className = `dopamine-banner dopamine-${type}`;
|
||||||
|
banner.innerHTML = `
|
||||||
|
<div class="dopamine-halo"></div>
|
||||||
|
<div class="dopamine-content">
|
||||||
|
<div class="dopamine-title">${title}</div>
|
||||||
|
${subtitle ? `<div class="dopamine-sub">${subtitle}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(banner);
|
||||||
|
|
||||||
|
// Sound & Particles based on type
|
||||||
|
if (type === 'quad' || type === 'mega') {
|
||||||
|
playComboSlam(4);
|
||||||
|
triggerGoldStarExplosion();
|
||||||
|
triggerConfetti();
|
||||||
|
} else if (type === 'triple') {
|
||||||
|
playComboSlam(3);
|
||||||
|
triggerFireParticles(window.innerWidth / 2, window.innerHeight / 2, 45);
|
||||||
|
} else if (type === 'double') {
|
||||||
|
playComboSlam(2);
|
||||||
|
triggerLightningSparks();
|
||||||
|
} else if (type === 'freeze') {
|
||||||
|
playAceFreeze();
|
||||||
|
triggerIceParticles(window.innerWidth / 2, window.innerHeight / 2, 50);
|
||||||
|
} else if (type === 'revive') {
|
||||||
|
playReviveSound();
|
||||||
|
triggerLightningSparks(window.innerWidth / 2, window.innerHeight / 2, 60);
|
||||||
|
} else if (type === 'jackpot') {
|
||||||
|
playJackpotCoins();
|
||||||
|
triggerCoinJackpot();
|
||||||
|
triggerConfetti();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto remove banner after animation
|
||||||
|
setTimeout(() => {
|
||||||
|
banner.classList.add('dopamine-fadeout');
|
||||||
|
setTimeout(() => banner.remove(), 400);
|
||||||
|
}, 1800);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADVANCED PARTICLES ---
|
||||||
|
function triggerCoinJackpot(x, y, count = 70) {
|
||||||
|
if (!particleCanvas) return;
|
||||||
|
const startX = x || window.innerWidth / 2;
|
||||||
|
const startY = y || window.innerHeight / 2;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const angle = -Math.PI / 2 + (Math.random() - 0.5) * 2.2;
|
||||||
|
const speed = 6 + Math.random() * 12;
|
||||||
|
particles.push({
|
||||||
|
x: startX,
|
||||||
|
y: startY,
|
||||||
|
vx: Math.cos(angle) * speed,
|
||||||
|
vy: Math.sin(angle) * speed,
|
||||||
|
gravity: 0.28,
|
||||||
|
size: 10 + Math.random() * 6,
|
||||||
|
aspect: 0.5 + Math.random() * 0.5,
|
||||||
|
color: Math.random() > 0.3 ? '#ffd700' : '#f5b041',
|
||||||
|
shape: 'rect',
|
||||||
|
life: 1.2,
|
||||||
|
decay: 0.012 + Math.random() * 0.008,
|
||||||
|
rotation: Math.random() * Math.PI * 2,
|
||||||
|
vRot: (Math.random() - 0.5) * 0.25
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerLightningSparks(x, y, count = 40) {
|
||||||
|
if (!particleCanvas) return;
|
||||||
|
const colors = ['#00e5ff', '#ffd700', '#ffffff', '#e040fb'];
|
||||||
|
const startX = x || window.innerWidth / 2;
|
||||||
|
const startY = y || window.innerHeight / 2;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const speed = 4 + Math.random() * 10;
|
||||||
|
particles.push({
|
||||||
|
x: startX,
|
||||||
|
y: startY,
|
||||||
|
vx: Math.cos(angle) * speed,
|
||||||
|
vy: Math.sin(angle) * speed,
|
||||||
|
gravity: 0,
|
||||||
|
size: 3 + Math.random() * 4,
|
||||||
|
color: colors[Math.floor(Math.random() * colors.length)],
|
||||||
|
shape: 'spark',
|
||||||
|
life: 0.8,
|
||||||
|
decay: 0.035,
|
||||||
|
rotation: Math.random() * Math.PI
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerGoldStarExplosion(x, y, count = 60) {
|
||||||
|
if (!particleCanvas) return;
|
||||||
|
const startX = x || window.innerWidth / 2;
|
||||||
|
const startY = y || window.innerHeight / 2;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const speed = 3 + Math.random() * 11;
|
||||||
|
particles.push({
|
||||||
|
x: startX,
|
||||||
|
y: startY,
|
||||||
|
vx: Math.cos(angle) * speed,
|
||||||
|
vy: Math.sin(angle) * speed,
|
||||||
|
gravity: 0.06,
|
||||||
|
size: 6 + Math.random() * 6,
|
||||||
|
color: '#ffd700',
|
||||||
|
shape: 'circle',
|
||||||
|
life: 1.1,
|
||||||
|
decay: 0.02,
|
||||||
|
rotation: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleMute() {
|
function toggleMute() {
|
||||||
isMuted = !isMuted;
|
isMuted = !isMuted;
|
||||||
return isMuted;
|
return isMuted;
|
||||||
@@ -631,14 +896,23 @@ window.FaraonFx = (function () {
|
|||||||
playBurnExplosion,
|
playBurnExplosion,
|
||||||
playVictory,
|
playVictory,
|
||||||
playHeartbeat,
|
playHeartbeat,
|
||||||
|
playSelectStep,
|
||||||
|
playComboSlam,
|
||||||
|
playJackpotCoins,
|
||||||
|
playReviveSound,
|
||||||
|
triggerDopamineBanner,
|
||||||
triggerScreenShake,
|
triggerScreenShake,
|
||||||
initParticles,
|
initParticles,
|
||||||
triggerFireParticles,
|
triggerFireParticles,
|
||||||
triggerIceParticles,
|
triggerIceParticles,
|
||||||
triggerBurnExplosion,
|
triggerBurnExplosion,
|
||||||
triggerConfetti,
|
triggerConfetti,
|
||||||
|
triggerCoinJackpot,
|
||||||
|
triggerLightningSparks,
|
||||||
|
triggerGoldStarExplosion,
|
||||||
animateCardPlay,
|
animateCardPlay,
|
||||||
animateCardDraw,
|
animateCardDraw,
|
||||||
toggleMute
|
toggleMute
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user