music: re-enable background loop (default = playful) + profile on/off; coins 2000
- sound.ts: restored startMusic (was a no-op stub) playing the selected track through musicGain (in-game mute still applies); default track switched to "playful" (per user). Music auto-starts on init when enabled. - Profile → Audio: re-added a music on/off toggle (so players can disable it outside the game too); SFX toggle unchanged. - Economy tune: starting coins 1000 → 2000 (mock defaultProfile + server ProfileDto) so new players start a bit richer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ public class ProfileDto
|
|||||||
public long? PlanUntil { get; set; }
|
public long? PlanUntil { get; set; }
|
||||||
public int Level { get; set; } = 1;
|
public int Level { get; set; } = 1;
|
||||||
public int Xp { get; set; }
|
public int Xp { get; set; }
|
||||||
public int Coins { get; set; } = 1000;
|
public int Coins { get; set; } = 2000;
|
||||||
public int Rating { get; set; } = 1000;
|
public int Rating { get; set; } = 1000;
|
||||||
public StatsDto Stats { get; set; } = new();
|
public StatsDto Stats { get; set; } = new();
|
||||||
public List<string> OwnedAvatars { get; set; } = new() { "a-fox", "a-lion" };
|
public List<string> OwnedAvatars { get; set; } = new() { "a-fox", "a-lion" };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Check, ChevronLeft, Crown, Eye, EyeOff, Gift, Lock, LogOut, MapPin, Pencil, Search, Star, Upload, Users, Volume2 } from "lucide-react";
|
import { Check, ChevronLeft, Crown, Eye, EyeOff, Gift, Lock, LogOut, MapPin, Music, Pencil, Search, Star, Upload, Users, Volume2 } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
||||||
import { RankBadge } from "@/components/online/RankBadge";
|
import { RankBadge } from "@/components/online/RankBadge";
|
||||||
@@ -667,11 +667,12 @@ function CityRewardCard() {
|
|||||||
|
|
||||||
function SoundSettings() {
|
function SoundSettings() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { sfx, toggleSfx } = useSoundStore();
|
const { sfx, toggleSfx, music, toggleMusic } = useSoundStore();
|
||||||
return (
|
return (
|
||||||
<div className="panel rounded-2xl p-4">
|
<div className="panel rounded-2xl p-4">
|
||||||
<h3 className="text-sm font-bold text-cream/80 mb-2">{t("settings.audio")}</h3>
|
<h3 className="text-sm font-bold text-cream/80 mb-2">{t("settings.audio")}</h3>
|
||||||
<ToggleRow icon={<Volume2 className="size-4 text-gold-400" />} label={t("settings.sound")} on={sfx} onClick={toggleSfx} />
|
<ToggleRow icon={<Volume2 className="size-4 text-gold-400" />} label={t("settings.sound")} on={sfx} onClick={toggleSfx} />
|
||||||
|
<ToggleRow icon={<Music className="size-4 text-gold-400" />} label={t("settings.music")} on={music} onClick={toggleMusic} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function defaultProfile(session: AuthSession): UserProfile {
|
|||||||
plan: "free",
|
plan: "free",
|
||||||
level: 1,
|
level: 1,
|
||||||
xp: 0,
|
xp: 0,
|
||||||
coins: 1000,
|
coins: 2000,
|
||||||
rating: 1000,
|
rating: 1000,
|
||||||
stats: {
|
stats: {
|
||||||
games: 0,
|
games: 0,
|
||||||
|
|||||||
+30
-3
@@ -38,7 +38,7 @@ class SoundManager {
|
|||||||
sfxEnabled = loadBool(LS_SFX);
|
sfxEnabled = loadBool(LS_SFX);
|
||||||
musicEnabled = loadBool(LS_MUSIC);
|
musicEnabled = loadBool(LS_MUSIC);
|
||||||
musicTrack: MusicTrack =
|
musicTrack: MusicTrack =
|
||||||
(typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "santoor";
|
(typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "playful";
|
||||||
|
|
||||||
/** Must be called from a user gesture to unlock audio. */
|
/** Must be called from a user gesture to unlock audio. */
|
||||||
init() {
|
init() {
|
||||||
@@ -210,8 +210,35 @@ class SoundManager {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Background music was removed from the game — these are inert no-ops. */
|
/** Start the ambient loop for the current track (plays through musicGain so
|
||||||
startMusic() {}
|
* the in-game mute / music volume apply). Idempotent + needs an unlocked ctx. */
|
||||||
|
startMusic() {
|
||||||
|
if (!this.musicEnabled || this.musicTimer || !this.ctx || !this.musicGain) return;
|
||||||
|
const tick = () => {
|
||||||
|
if (!this.ctx || !this.musicGain) return;
|
||||||
|
const cfg = this.TRACKS[this.musicTrack];
|
||||||
|
const now = this.ctx.currentTime;
|
||||||
|
const freq = cfg.notes[this.step % cfg.notes.length];
|
||||||
|
this.step++;
|
||||||
|
const voice = (f: number, scale = 1) => {
|
||||||
|
const osc = this.ctx!.createOscillator();
|
||||||
|
const g = this.ctx!.createGain();
|
||||||
|
osc.type = cfg.type;
|
||||||
|
osc.frequency.value = f;
|
||||||
|
g.gain.setValueAtTime(0.0001, now);
|
||||||
|
g.gain.linearRampToValueAtTime(cfg.peak * scale, now + cfg.attack);
|
||||||
|
g.gain.exponentialRampToValueAtTime(0.0001, now + cfg.dur);
|
||||||
|
osc.connect(g);
|
||||||
|
g.connect(this.musicGain!);
|
||||||
|
osc.start(now);
|
||||||
|
osc.stop(now + cfg.dur + 0.05);
|
||||||
|
};
|
||||||
|
voice(freq);
|
||||||
|
if (cfg.fifth) voice(freq * 1.5, 0.45); // soft fifth above
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
this.musicTimer = setInterval(tick, this.TRACKS[this.musicTrack].gap);
|
||||||
|
}
|
||||||
|
|
||||||
stopMusic() {
|
stopMusic() {
|
||||||
if (this.musicTimer) {
|
if (this.musicTimer) {
|
||||||
|
|||||||
Reference in New Issue
Block a user