Add adaptive progression and item expansion
This commit is contained in:
commit
8ab0857ee0
298 files changed
+8587
No files matched your search
+166
@@ -0,0 +1,166 @@
|
||||
// ---- music manager: crossfading loops mapped to game moods ----
|
||||
const Music = {
|
||||
current: null, // { audio, name }
|
||||
fading: null,
|
||||
bossCount: 0,
|
||||
|
||||
TRACKS: {
|
||||
menu: 'Blood Arcade.mp3',
|
||||
floors: [
|
||||
'Rust Hollow Loop.mp3', 'Blood Dice Loop.mp3', 'Crimsonland Requiem.mp3',
|
||||
'Crimsonland Tool.mp3', 'Blood Arcade_Alt.mp3', 'Rust Hollow Loop_Alt.mp3',
|
||||
'Blood Dice Loop_alt.mp3', 'Crimsonland Requiem_alt.mp3', 'Crimsonland Tool_alt.mp3',
|
||||
'Blood Arcade.mp3',
|
||||
],
|
||||
bosses: {
|
||||
bonesaw: ['Bone Saw Loop_Boss.mp3', 'Bone Saw Loop_Alt_Boss.mp3'],
|
||||
gorecrown: ['Gore Crown Loop_Boss.mp3', 'Gore Crown Loop_Alt_Boss.mp3'],
|
||||
knifecrawl: ['Knife Floor Crawl_Boss.mp3', 'Knife Floor Crawl_Alt_Boss.mp3'],
|
||||
vealmother: ['Veal Mother Loop_Boss.mp3', 'Veal Mother Loop_Alt_Boss.mp3'],
|
||||
flenser: ['Flenser Loop_Boss.mp3', 'Flenser Loop_Alt_Boss.mp3'],
|
||||
hookchoir: ['Hook Choir Loop_Boss.mp3', 'Hook Choir Loop_Alt_Boss.mp3'],
|
||||
platefather: ['Plate Father Loop_Boss.mp3', 'Plate Father Loop_Alt_Boss.mp3'],
|
||||
augerprime: ['Auger Prime Loop_Boss.mp3', 'Auger Prime Loop_Alt_Boss.mp3'],
|
||||
scald: ['Scald Loop_Boss.mp3', 'Scald Loop_Alt_Boss.mp3'],
|
||||
},
|
||||
},
|
||||
|
||||
// jukebox: every non-boss track, selectable by the player
|
||||
PLAYLIST: [
|
||||
'Blood Arcade.mp3', 'Blood Arcade_Alt.mp3',
|
||||
'Rust Hollow Loop.mp3', 'Rust Hollow Loop_Alt.mp3',
|
||||
'Blood Dice Loop.mp3', 'Blood Dice Loop_alt.mp3',
|
||||
'Crimsonland Requiem.mp3', 'Crimsonland Requiem_alt.mp3',
|
||||
'Crimsonland Tool.mp3', 'Crimsonland Tool_alt.mp3',
|
||||
'Bone Saw Loop_Boss.mp3', 'Bone Saw Loop_Alt_Boss.mp3',
|
||||
'Gore Crown Loop_Boss.mp3', 'Gore Crown Loop_Alt_Boss.mp3',
|
||||
'Knife Floor Crawl_Boss.mp3', 'Knife Floor Crawl_Alt_Boss.mp3',
|
||||
'Veal Mother Loop_Boss.mp3', 'Veal Mother Loop_Alt_Boss.mp3',
|
||||
'Flenser Loop_Boss.mp3', 'Flenser Loop_Alt_Boss.mp3',
|
||||
'Hook Choir Loop_Boss.mp3', 'Hook Choir Loop_Alt_Boss.mp3',
|
||||
'Plate Father Loop_Boss.mp3', 'Plate Father Loop_Alt_Boss.mp3',
|
||||
'Auger Prime Loop_Boss.mp3', 'Auger Prime Loop_Alt_Boss.mp3',
|
||||
'Scald Loop_Boss.mp3', 'Scald Loop_Alt_Boss.mp3',
|
||||
],
|
||||
override: null, // filename when the player picked a track manually
|
||||
|
||||
pretty(file) {
|
||||
return file.replace(/\.mp3$/i, '').replace(/_Boss$/i, '').replace(/_Alt$/i, ' (Alt)').replace(/_alt$/i, ' (Alt)');
|
||||
},
|
||||
|
||||
// the track the game *should* be playing right now (respects manual override)
|
||||
requestFloorMusic() {
|
||||
if (this.override) this.play(this.override);
|
||||
else this.playFloor(G.floor);
|
||||
},
|
||||
|
||||
makeAudio(name) {
|
||||
const a = new Audio('mp3-music/' + encodeURIComponent(name));
|
||||
a.loop = true;
|
||||
a.volume = 0;
|
||||
a.preload = 'auto';
|
||||
return a;
|
||||
},
|
||||
|
||||
floorTrack(floor) { return this.TRACKS.floors[(floor - 1) % this.TRACKS.floors.length]; },
|
||||
bossTrack(kind) {
|
||||
const pair = this.TRACKS.bosses[kind] || this.TRACKS.bosses.bonesaw;
|
||||
return pair[this.bossCount % 2];
|
||||
},
|
||||
|
||||
play(name) {
|
||||
if (this.current && this.current.name === name) return;
|
||||
if (this.fading) {
|
||||
this.fading.audio.pause();
|
||||
if (typeof Sfx !== 'undefined') Sfx.detachMusic(this.fading.audio);
|
||||
this.fading = null;
|
||||
}
|
||||
if (this.current) this.fading = this.current;
|
||||
const a = this.makeAudio(name);
|
||||
a.muted = G.muted;
|
||||
if (typeof Sfx !== 'undefined') Sfx.attachMusic(a);
|
||||
a.play().catch(() => {}); // browser may block until user gesture; retried on next call
|
||||
this.current = { audio: a, name, retryT: 0 };
|
||||
},
|
||||
|
||||
playMenu() { this.play(this.TRACKS.menu); },
|
||||
playFloor(floor) { this.play(this.floorTrack(floor)); },
|
||||
playBoss(kind) { this.play(this.bossTrack(kind)); this.bossCount++; },
|
||||
|
||||
stop() {
|
||||
if (this.current) {
|
||||
this.current.audio.pause();
|
||||
if (typeof Sfx !== 'undefined') Sfx.detachMusic(this.current.audio);
|
||||
this.current = null;
|
||||
}
|
||||
if (this.fading) {
|
||||
this.fading.audio.pause();
|
||||
if (typeof Sfx !== 'undefined') Sfx.detachMusic(this.fading.audio);
|
||||
this.fading = null;
|
||||
}
|
||||
},
|
||||
|
||||
setMuted(m) {
|
||||
if (this.current) this.current.audio.muted = m;
|
||||
if (this.fading) this.fading.audio.muted = m;
|
||||
if (typeof Sfx !== 'undefined') Sfx.setMusicMuted(m);
|
||||
},
|
||||
|
||||
setVolume(v) {
|
||||
G.musicVol = clamp(v, 0, 1);
|
||||
try { localStorage.setItem('meatslicer_music_volume', String(G.musicVol)); } catch (e) {}
|
||||
if (this.current) this.current.audio.volume = Math.min(this.current.audio.volume, G.musicVol);
|
||||
if (this.fading) this.fading.audio.volume = Math.min(this.fading.audio.volume, G.musicVol);
|
||||
},
|
||||
|
||||
// cycle the jukebox selection: Auto -> track 0 -> ... -> track N -> Auto
|
||||
cycle(dir) {
|
||||
const n = this.PLAYLIST.length;
|
||||
if (G.musicSel === undefined || G.musicSel === null) G.musicSel = -1;
|
||||
G.musicSel += dir;
|
||||
if (G.musicSel >= n) G.musicSel = -1;
|
||||
if (G.musicSel < -1) G.musicSel = n - 1;
|
||||
|
||||
if (G.musicSel === -1) {
|
||||
this.override = null;
|
||||
addToast('MUSIC: AUTO', 'floor rotation');
|
||||
} else {
|
||||
this.override = this.PLAYLIST[G.musicSel];
|
||||
addToast('MUSIC: ' + this.pretty(this.override));
|
||||
}
|
||||
// never interrupt a live boss track — it hands back when the boss dies
|
||||
if (!G.boss) this.requestFloorMusic();
|
||||
Sfx.pickup();
|
||||
},
|
||||
|
||||
update(dt) {
|
||||
const target = G.musicVol === undefined ? 0.55 : G.musicVol;
|
||||
// Tracks created before the AudioContext resumed keep playing directly;
|
||||
// attach once the context is running so large SFX can duck the music bus.
|
||||
if (typeof Sfx !== 'undefined') {
|
||||
if (this.current) Sfx.attachMusic(this.current.audio);
|
||||
if (this.fading) Sfx.attachMusic(this.fading.audio);
|
||||
}
|
||||
if (this.current && this.current.audio.volume < target) {
|
||||
this.current.audio.volume = clamp(this.current.audio.volume + dt * 0.8, 0, target);
|
||||
// If autoplay was blocked, retry at a gentle cadence rather than once
|
||||
// per frame (which can flood promises and the browser media pipeline).
|
||||
this.current.retryT -= dt;
|
||||
if (this.current.audio.paused && this.current.retryT <= 0) {
|
||||
this.current.retryT = 1;
|
||||
this.current.audio.play().catch(() => {});
|
||||
}
|
||||
}
|
||||
if (this.fading) {
|
||||
const v = this.fading.audio.volume - dt * 0.8;
|
||||
if (v <= 0.01) {
|
||||
this.fading.audio.volume = 0;
|
||||
this.fading.audio.pause();
|
||||
if (typeof Sfx !== 'undefined') Sfx.detachMusic(this.fading.audio);
|
||||
this.fading = null;
|
||||
} else {
|
||||
this.fading.audio.volume = v;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user