Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/Jennys-Drumste / game-systems.js |
| Date | Not specified in source |
| Repository | Janady13/Jennys-Drumste |
| Revision | 1f946b9e18e552f80f4b2ffd7f59c01d0126aa53 |
| Branch | claude/jenny-drumsets-research-MofSb |
| Path or archive member | game-systems.js |
| Original SHA-256 | 5f20ce0ccfe1dfdb9794f112a2620e431e592cad8f0dbbdc0b593bdf01bef3b3 |
| Git blob SHA | f4a5943c947d344c737db476158c0f820da989dc |
| Reading-copy SHA-256 | 5f20ce0ccfe1dfdb9794f112a2620e431e592cad8f0dbbdc0b593bdf01bef3b3 |
Recovered source transcript
Download complete source text ↓791 lines · 26.7 KB
// ═══════════════════════════════════════════════════════════════════════════════
// DRUMMASTER PRO - GAME SYSTEMS
// Career Mode, Achievements, High Scores, Unlockables
// ═══════════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════════
// PLAYER PROFILE
// ═══════════════════════════════════════════════════════════════════════════════
class PlayerProfile {
constructor() {
this.data = this.load();
}
getDefault() {
return {
name: 'Drummer',
level: 1,
xp: 0,
totalXP: 0,
stars: 0,
coins: 0,
songsPlayed: 0,
totalNotesHit: 0,
totalNotesMissed: 0,
perfectHits: 0,
maxCombo: 0,
totalPlayTime: 0, // in seconds
dateCreated: Date.now(),
lastPlayed: Date.now(),
unlockedSongs: ['seven-nation-army', 'come-as-you-are'],
unlockedKits: ['rock'],
unlockedThemes: ['default'],
equippedKit: 'rock',
equippedTheme: 'default',
achievements: [],
highScores: {},
careerProgress: {
currentTier: 1,
tierProgress: 0,
venuesUnlocked: ['garage']
},
settings: {
volume: 0.8,
sfxVolume: 1.0,
noteSpeed: 1.0,
hitWindow: 'normal', // easy, normal, hard
showKeyHints: true,
autoPlay: false
},
stats: {
kicksHit: 0,
snaresHit: 0,
hihatsHit: 0,
tomsHit: 0,
cymbalsHit: 0,
starPowersUsed: 0,
fiveStarSongs: 0,
fullCombos: 0
}
};
}
load() {
try {
const saved = localStorage.getItem('drummaster_profile');
if (saved) {
return { ...this.getDefault(), ...JSON.parse(saved) };
}
} catch (e) {
console.error('[Profile] Load failed:', e);
}
return this.getDefault();
}
save() {
try {
this.data.lastPlayed = Date.now();
localStorage.setItem('drummaster_profile', JSON.stringify(this.data));
} catch (e) {
console.error('[Profile] Save failed:', e);
}
}
addXP(amount) {
this.data.xp += amount;
this.data.totalXP += amount;
// Level up check
const xpForNextLevel = this.getXPForLevel(this.data.level + 1);
while (this.data.xp >= xpForNextLevel) {
this.data.xp -= xpForNextLevel;
this.data.level++;
this.onLevelUp();
}
this.save();
}
getXPForLevel(level) {
return Math.floor(100 * Math.pow(1.5, level - 1));
}
onLevelUp() {
// Unlock rewards based on level
const rewards = {
2: { coins: 100, unlock: 'song:billie-jean' },
3: { coins: 150, unlock: 'kit:jazz' },
5: { coins: 200, unlock: 'song:smells-like-teen-spirit' },
7: { coins: 250, unlock: 'theme:neon' },
10: { coins: 500, unlock: 'song:enter-sandman' },
15: { coins: 750, unlock: 'kit:electronic' },
20: { coins: 1000, unlock: 'song:hot-for-teacher' }
};
const reward = rewards[this.data.level];
if (reward) {
this.data.coins += reward.coins;
if (reward.unlock) {
const [type, id] = reward.unlock.split(':');
this.unlock(type, id);
}
}
console.log(`[Profile] Level up! Now level ${this.data.level}`);
}
unlock(type, id) {
switch (type) {
case 'song':
if (!this.data.unlockedSongs.includes(id)) {
this.data.unlockedSongs.push(id);
}
break;
case 'kit':
if (!this.data.unlockedKits.includes(id)) {
this.data.unlockedKits.push(id);
}
break;
case 'theme':
if (!this.data.unlockedThemes.includes(id)) {
this.data.unlockedThemes.push(id);
}
break;
}
this.save();
}
addHighScore(songId, score, stars, accuracy, maxCombo) {
if (!this.data.highScores[songId]) {
this.data.highScores[songId] = [];
}
this.data.highScores[songId].push({
score,
stars,
accuracy,
maxCombo,
date: Date.now()
});
// Keep top 5
this.data.highScores[songId].sort((a, b) => b.score - a.score);
this.data.highScores[songId] = this.data.highScores[songId].slice(0, 5);
this.save();
}
getHighScore(songId) {
const scores = this.data.highScores[songId];
return scores && scores.length > 0 ? scores[0] : null;
}
}
// ═══════════════════════════════════════════════════════════════════════════════
// ACHIEVEMENTS SYSTEM
// ═══════════════════════════════════════════════════════════════════════════════
const ACHIEVEMENTS = {
// Getting Started
first_hit: {
id: 'first_hit',
name: 'First Beat',
description: 'Hit your first drum note',
icon: '🥁',
xp: 10,
coins: 5,
secret: false
},
first_song: {
id: 'first_song',
name: 'Rising Star',
description: 'Complete your first song',
icon: '⭐',
xp: 50,
coins: 25,
secret: false
},
first_five_star: {
id: 'first_five_star',
name: 'Perfect Performance',
description: 'Get 5 stars on any song',
icon: '🌟',
xp: 200,
coins: 100,
secret: false
},
// Combo Achievements
combo_10: {
id: 'combo_10',
name: 'Getting Started',
description: 'Reach a 10 note combo',
icon: '🔥',
xp: 20,
coins: 10,
secret: false
},
combo_50: {
id: 'combo_50',
name: 'On Fire',
description: 'Reach a 50 note combo',
icon: '🔥',
xp: 50,
coins: 25,
secret: false
},
combo_100: {
id: 'combo_100',
name: 'Unstoppable',
description: 'Reach a 100 note combo',
icon: '💥',
xp: 100,
coins: 50,
secret: false
},
combo_500: {
id: 'combo_500',
name: 'Legendary Streak',
description: 'Reach a 500 note combo',
icon: '👑',
xp: 500,
coins: 250,
secret: false
},
full_combo: {
id: 'full_combo',
name: 'Full Combo!',
description: 'Complete a song without missing a note',
icon: '💯',
xp: 300,
coins: 150,
secret: false
},
// Accuracy
accuracy_80: {
id: 'accuracy_80',
name: 'Solid Timing',
description: 'Complete a song with 80%+ accuracy',
icon: '🎯',
xp: 30,
coins: 15,
secret: false
},
accuracy_90: {
id: 'accuracy_90',
name: 'Sharp Shooter',
description: 'Complete a song with 90%+ accuracy',
icon: '🎯',
xp: 75,
coins: 40,
secret: false
},
accuracy_95: {
id: 'accuracy_95',
name: 'Precision Master',
description: 'Complete a song with 95%+ accuracy',
icon: '💎',
xp: 150,
coins: 75,
secret: false
},
accuracy_100: {
id: 'accuracy_100',
name: 'Flawless',
description: 'Complete a song with 100% accuracy',
icon: '✨',
xp: 500,
coins: 250,
secret: false
},
// Songs Played
songs_5: {
id: 'songs_5',
name: 'Getting Into It',
description: 'Complete 5 songs',
icon: '🎵',
xp: 50,
coins: 25,
secret: false
},
songs_25: {
id: 'songs_25',
name: 'Regular Player',
description: 'Complete 25 songs',
icon: '🎶',
xp: 150,
coins: 75,
secret: false
},
songs_100: {
id: 'songs_100',
name: 'Dedicated Drummer',
description: 'Complete 100 songs',
icon: '🏆',
xp: 500,
coins: 250,
secret: false
},
// Star Power
first_star_power: {
id: 'first_star_power',
name: 'Superstar',
description: 'Activate Star Power for the first time',
icon: '⚡',
xp: 25,
coins: 10,
secret: false
},
star_power_10: {
id: 'star_power_10',
name: 'Power User',
description: 'Activate Star Power 10 times',
icon: '⚡',
xp: 100,
coins: 50,
secret: false
},
// Practice
practice_1_hour: {
id: 'practice_1_hour',
name: 'Practice Makes Perfect',
description: 'Spend 1 hour practicing',
icon: '⏰',
xp: 100,
coins: 50,
secret: false
},
practice_10_hours: {
id: 'practice_10_hours',
name: 'Dedicated Learner',
description: 'Spend 10 hours practicing',
icon: '📚',
xp: 500,
coins: 250,
secret: false
},
// Special
midnight_drummer: {
id: 'midnight_drummer',
name: 'Midnight Drummer',
description: 'Play at midnight',
icon: '🌙',
xp: 50,
coins: 25,
secret: true
},
speed_demon: {
id: 'speed_demon',
name: 'Speed Demon',
description: 'Complete a song at 200+ BPM',
icon: '🏎️',
xp: 200,
coins: 100,
secret: false
},
comeback_kid: {
id: 'comeback_kid',
name: 'Comeback Kid',
description: 'Recover from below 20% crowd meter to win',
icon: '💪',
xp: 150,
coins: 75,
secret: true
},
// Drums
kick_master: {
id: 'kick_master',
name: 'Kick Master',
description: 'Hit 1000 kick drums',
icon: '👢',
xp: 100,
coins: 50,
secret: false
},
snare_master: {
id: 'snare_master',
name: 'Snare Master',
description: 'Hit 1000 snare drums',
icon: '🥁',
xp: 100,
coins: 50,
secret: false
},
cymbal_master: {
id: 'cymbal_master',
name: 'Cymbal Master',
description: 'Hit 1000 cymbals',
icon: '🔔',
xp: 100,
coins: 50,
secret: false
}
};
class AchievementSystem {
constructor(profile) {
this.profile = profile;
this.pendingNotifications = [];
}
check(achievementId) {
if (this.profile.data.achievements.includes(achievementId)) {
return false; // Already unlocked
}
const achievement = ACHIEVEMENTS[achievementId];
if (!achievement) return false;
this.unlock(achievementId);
return true;
}
unlock(achievementId) {
const achievement = ACHIEVEMENTS[achievementId];
if (!achievement) return;
this.profile.data.achievements.push(achievementId);
this.profile.addXP(achievement.xp);
this.profile.data.coins += achievement.coins;
this.profile.save();
this.pendingNotifications.push(achievement);
console.log(`[Achievement] Unlocked: ${achievement.name}`);
}
checkAllConditions(gameStats) {
const p = this.profile.data;
// First hit
if (p.totalNotesHit >= 1) this.check('first_hit');
// Songs
if (p.songsPlayed >= 1) this.check('first_song');
if (p.songsPlayed >= 5) this.check('songs_5');
if (p.songsPlayed >= 25) this.check('songs_25');
if (p.songsPlayed >= 100) this.check('songs_100');
// Combo
if (p.maxCombo >= 10) this.check('combo_10');
if (p.maxCombo >= 50) this.check('combo_50');
if (p.maxCombo >= 100) this.check('combo_100');
if (p.maxCombo >= 500) this.check('combo_500');
// Stars
if (p.stats.fiveStarSongs >= 1) this.check('first_five_star');
// Full combo
if (p.stats.fullCombos >= 1) this.check('full_combo');
// Star Power
if (p.stats.starPowersUsed >= 1) this.check('first_star_power');
if (p.stats.starPowersUsed >= 10) this.check('star_power_10');
// Drums
if (p.stats.kicksHit >= 1000) this.check('kick_master');
if (p.stats.snaresHit >= 1000) this.check('snare_master');
if (p.stats.cymbalsHit >= 1000) this.check('cymbal_master');
// Time
if (p.totalPlayTime >= 3600) this.check('practice_1_hour');
if (p.totalPlayTime >= 36000) this.check('practice_10_hours');
// Midnight
const hour = new Date().getHours();
if (hour === 0) this.check('midnight_drummer');
// Game-specific checks
if (gameStats) {
if (gameStats.accuracy >= 80) this.check('accuracy_80');
if (gameStats.accuracy >= 90) this.check('accuracy_90');
if (gameStats.accuracy >= 95) this.check('accuracy_95');
if (gameStats.accuracy >= 100) this.check('accuracy_100');
if (gameStats.bpm >= 200) this.check('speed_demon');
if (gameStats.lowestCrowd < 20 && gameStats.won) this.check('comeback_kid');
}
}
getNextNotification() {
return this.pendingNotifications.shift();
}
getProgress(achievementId) {
const p = this.profile.data;
const targets = {
combo_10: { current: p.maxCombo, target: 10 },
combo_50: { current: p.maxCombo, target: 50 },
combo_100: { current: p.maxCombo, target: 100 },
combo_500: { current: p.maxCombo, target: 500 },
songs_5: { current: p.songsPlayed, target: 5 },
songs_25: { current: p.songsPlayed, target: 25 },
songs_100: { current: p.songsPlayed, target: 100 },
kick_master: { current: p.stats.kicksHit, target: 1000 },
snare_master: { current: p.stats.snaresHit, target: 1000 },
cymbal_master: { current: p.stats.cymbalsHit, target: 1000 },
practice_1_hour: { current: Math.floor(p.totalPlayTime / 60), target: 60 },
practice_10_hours: { current: Math.floor(p.totalPlayTime / 60), target: 600 }
};
return targets[achievementId] || null;
}
}
// ═══════════════════════════════════════════════════════════════════════════════
// CAREER MODE
// ═══════════════════════════════════════════════════════════════════════════════
const CAREER_VENUES = {
garage: {
id: 'garage',
name: 'The Garage',
description: 'Your humble beginnings',
image: '🏠',
tier: 1,
requiredStars: 0,
songs: ['seven-nation-army', 'come-as-you-are'],
reward: { coins: 100, unlock: null }
},
local_bar: {
id: 'local_bar',
name: 'Local Bar',
description: 'Your first real gig',
image: '🍺',
tier: 1,
requiredStars: 5,
songs: ['billie-jean', 'blinding-lights'],
reward: { coins: 200, unlock: 'kit:jazz' }
},
club: {
id: 'club',
name: 'The Underground',
description: 'A popular local club',
image: '🎤',
tier: 2,
requiredStars: 15,
songs: ['smells-like-teen-spirit', 'back-in-black'],
reward: { coins: 300, unlock: 'theme:club' }
},
theater: {
id: 'theater',
name: 'City Theater',
description: 'The big time',
image: '🎭',
tier: 2,
requiredStars: 30,
songs: ['enter-sandman', 'uptown-funk'],
reward: { coins: 500, unlock: 'kit:electronic' }
},
arena: {
id: 'arena',
name: 'Metro Arena',
description: 'Thousands of fans',
image: '🏟️',
tier: 3,
requiredStars: 50,
songs: ['hot-for-teacher', 'toxicity'],
reward: { coins: 1000, unlock: 'theme:arena' }
},
stadium: {
id: 'stadium',
name: 'World Stadium',
description: 'Legendary status',
image: '🌍',
tier: 3,
requiredStars: 80,
songs: ['yyz', 'moby-dick'],
reward: { coins: 2500, unlock: 'kit:metal' }
}
};
class CareerMode {
constructor(profile) {
this.profile = profile;
this.venues = CAREER_VENUES;
}
getUnlockedVenues() {
const totalStars = this.profile.data.stars;
return Object.values(this.venues).filter(v => v.requiredStars <= totalStars);
}
getNextVenue() {
const totalStars = this.profile.data.stars;
const locked = Object.values(this.venues)
.filter(v => v.requiredStars > totalStars)
.sort((a, b) => a.requiredStars - b.requiredStars);
return locked[0] || null;
}
getVenueProgress(venueId) {
const venue = this.venues[venueId];
if (!venue) return null;
const profile = this.profile.data;
let starsEarned = 0;
let songsCompleted = 0;
venue.songs.forEach(songId => {
const highScore = profile.highScores[songId];
if (highScore && highScore.length > 0) {
starsEarned += highScore[0].stars;
songsCompleted++;
}
});
return {
venue,
starsEarned,
maxStars: venue.songs.length * 5,
songsCompleted,
totalSongs: venue.songs.length,
complete: songsCompleted === venue.songs.length
};
}
completeVenue(venueId) {
const progress = this.getVenueProgress(venueId);
if (!progress || !progress.complete) return null;
const venue = this.venues[venueId];
const reward = venue.reward;
this.profile.data.coins += reward.coins;
if (reward.unlock) {
const [type, id] = reward.unlock.split(':');
this.profile.unlock(type, id);
}
if (!this.profile.data.careerProgress.venuesUnlocked.includes(venueId)) {
this.profile.data.careerProgress.venuesUnlocked.push(venueId);
}
this.profile.save();
return reward;
}
getCurrentTier() {
const stars = this.profile.data.stars;
if (stars >= 80) return 3;
if (stars >= 30) return 2;
return 1;
}
}
// ═══════════════════════════════════════════════════════════════════════════════
// DAILY CHALLENGES
// ═══════════════════════════════════════════════════════════════════════════════
class DailyChallenges {
constructor(profile) {
this.profile = profile;
this.challenges = this.generateChallenges();
}
generateChallenges() {
// Use date as seed for consistent daily challenges
const today = new Date().toDateString();
const seed = today.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
const challengePool = [
{ type: 'combo', target: 50, description: 'Reach a 50 combo', xp: 50, coins: 25 },
{ type: 'accuracy', target: 90, description: 'Complete a song with 90%+ accuracy', xp: 75, coins: 40 },
{ type: 'songs', target: 3, description: 'Complete 3 songs', xp: 60, coins: 30 },
{ type: 'stars', target: 10, description: 'Earn 10 stars total', xp: 100, coins: 50 },
{ type: 'perfect', target: 100, description: 'Hit 100 perfect notes', xp: 80, coins: 40 },
{ type: 'starpower', target: 3, description: 'Activate Star Power 3 times', xp: 40, coins: 20 },
{ type: 'fivestar', target: 1, description: 'Get 5 stars on any song', xp: 150, coins: 75 },
{ type: 'nodrop', target: 1, description: 'Complete a song without dropping combo below 10', xp: 100, coins: 50 }
];
// Pick 3 challenges based on seed
const selected = [];
for (let i = 0; i < 3; i++) {
const index = (seed + i * 7) % challengePool.length;
selected.push({ ...challengePool[index], id: `daily_${i}`, progress: 0 });
}
return selected;
}
updateProgress(type, value) {
this.challenges.forEach(c => {
if (c.type === type) {
c.progress = Math.min(c.target, c.progress + value);
}
});
}
checkCompletion() {
const completed = [];
this.challenges.forEach(c => {
if (c.progress >= c.target && !c.claimed) {
completed.push(c);
}
});
return completed;
}
claimReward(challengeId) {
const challenge = this.challenges.find(c => c.id === challengeId);
if (!challenge || challenge.claimed || challenge.progress < challenge.target) {
return null;
}
challenge.claimed = true;
this.profile.addXP(challenge.xp);
this.profile.data.coins += challenge.coins;
this.profile.save();
return { xp: challenge.xp, coins: challenge.coins };
}
}
// ═══════════════════════════════════════════════════════════════════════════════
// EXTENDED SONG DATABASE
// ═══════════════════════════════════════════════════════════════════════════════
const EXTENDED_SONGS = [
// Easy
{ id: 'we-will-rock-you', title: 'We Will Rock You', artist: 'Queen', bpm: 81, difficulty: 'easy', youtubeId: '-tJYN-eG1zk', tier: 1 },
{ id: 'another-one-bites', title: 'Another One Bites the Dust', artist: 'Queen', bpm: 110, difficulty: 'easy', youtubeId: 'rY0WxgSXdEE', tier: 1 },
{ id: 'smoke-on-water', title: 'Smoke on the Water', artist: 'Deep Purple', bpm: 112, difficulty: 'easy', youtubeId: 'zUwEIt9ez7M', tier: 1 },
// Medium
{ id: 'highway-to-hell', title: 'Highway to Hell', artist: 'AC/DC', bpm: 116, difficulty: 'medium', youtubeId: 'l482T0yNkeo', tier: 1 },
{ id: 'crazy-train', title: 'Crazy Train', artist: 'Ozzy Osbourne', bpm: 138, difficulty: 'medium', youtubeId: 'tMDFv5m18Pw', tier: 2 },
{ id: 'sweet-child', title: "Sweet Child O' Mine", artist: "Guns N' Roses", bpm: 126, difficulty: 'medium', youtubeId: '1w7OgIMMRc4', tier: 2 },
{ id: 'pour-some-sugar', title: 'Pour Some Sugar on Me', artist: 'Def Leppard', bpm: 93, difficulty: 'medium', youtubeId: '0UIB9Y4OFPs', tier: 2 },
{ id: 'livin-on-prayer', title: "Livin' on a Prayer", artist: 'Bon Jovi', bpm: 123, difficulty: 'medium', youtubeId: 'lDK9QqIzhwk', tier: 2 },
// Hard
{ id: 'hot-for-teacher', title: 'Hot for Teacher', artist: 'Van Halen', bpm: 252, difficulty: 'hard', youtubeId: '6M4_Ommfvv0', tier: 3 },
{ id: 'toxicity', title: 'Toxicity', artist: 'System of a Down', bpm: 134, difficulty: 'hard', youtubeId: 'iywaBOMvYLI', tier: 3 },
{ id: 'chop-suey', title: 'Chop Suey!', artist: 'System of a Down', bpm: 127, difficulty: 'hard', youtubeId: 'CSvFpBOe8eY', tier: 3 },
{ id: 'master-of-puppets', title: 'Master of Puppets', artist: 'Metallica', bpm: 212, difficulty: 'hard', youtubeId: 'E0ozmU9cJDg', tier: 3 },
{ id: 'battery', title: 'Battery', artist: 'Metallica', bpm: 196, difficulty: 'hard', youtubeId: 'md3B3I7Nmvw', tier: 3 },
// Expert
{ id: 'yyz', title: 'YYZ', artist: 'Rush', bpm: 198, difficulty: 'expert', youtubeId: 'LdpMpfp-J_I', tier: 3 },
{ id: 'moby-dick', title: 'Moby Dick', artist: 'Led Zeppelin', bpm: 168, difficulty: 'expert', youtubeId: 'r9-42mu1D9Y', tier: 3 },
{ id: 'tom-sawyer', title: 'Tom Sawyer', artist: 'Rush', bpm: 180, difficulty: 'expert', youtubeId: 'auLBLk4ibAk', tier: 3 },
{ id: 'painkiller', title: 'Painkiller', artist: 'Judas Priest', bpm: 228, difficulty: 'expert', youtubeId: 'nM__lPTWThU', tier: 3 }
];
// ═══════════════════════════════════════════════════════════════════════════════
// GLOBAL EXPORTS
// ═══════════════════════════════════════════════════════════════════════════════
window.PlayerProfile = PlayerProfile;
window.AchievementSystem = AchievementSystem;
window.CareerMode = CareerMode;
window.DailyChallenges = DailyChallenges;
window.ACHIEVEMENTS = ACHIEVEMENTS;
window.CAREER_VENUES = CAREER_VENUES;
window.EXTENDED_SONGS = EXTENDED_SONGS;
// Auto-initialize
window.playerProfile = new PlayerProfile();
window.achievements = new AchievementSystem(window.playerProfile);
window.careerMode = new CareerMode(window.playerProfile);
window.dailyChallenges = new DailyChallenges(window.playerProfile);
console.log('[GameSystems] Loaded - Profile, Achievements, Career Mode, Daily Challenges');
console.log(`[GameSystems] Player Level: ${window.playerProfile.data.level}, XP: ${window.playerProfile.data.xp}`);