From 08ec4fb19abaefa71df58134e66c2cbe4d64f3ab Mon Sep 17 00:00:00 2001 From: Ernie Cook Date: Tue, 3 Mar 2026 00:07:59 -0500 Subject: [PATCH] Add Void Shop and Void Essence currency system - Add Void Essence currency earned from petting (+2) and feeding (+3) - Add Void Shop modal with purchasable items: - Voidling eggs (Wisp, Blob, Spark, Ember, Glitch, Crystal) - Void Treats to make all voidlings happy - Star Fragments for bonus essence - Add 2 new voidling types: Glitch (rare green) and Crystal (rare blue) - Add 'Void Tycoon' achievement for 500 Void Essence - Update biodiversity achievement to require all 6 voidling types - Add Void Essence display in header with shop button --- WORKLOG.md | 7 ++- src/pages/Chamber.tsx | 125 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 0140030..ad64006 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -43,6 +43,11 @@ A whimsical pocket dimension where digital companions called "Voidlings" float a - Added manual time control toggle - Added voidling speed multipliers based on time of day (slower at night) - Voidlings naturally become sleepy at night +- Added Void Essence currency - earned by petting (+2) and feeding (+3) +- Added Void Shop with purchasable voidling eggs, treats, and items +- Added 2 new voidling types: Glitch (rare green) and Crystal (rare blue) +- Added "Void Tycoon" achievement for accumulating 500 Void Essence +- Updated biodiversity achievement to require all 6 voidling types ### Next Steps 1. Add sound effects @@ -52,4 +57,4 @@ A whimsical pocket dimension where digital companions called "Voidlings" float a --- -*Last updated: Session 4 complete - Added petting interaction and achievements system* +*Last updated: Session 5 complete - Added voidling shop with Void Essence currency* diff --git a/src/pages/Chamber.tsx b/src/pages/Chamber.tsx index 0aabf27..3c0af6c 100644 --- a/src/pages/Chamber.tsx +++ b/src/pages/Chamber.tsx @@ -6,7 +6,7 @@ interface Voidling { name: string color: string mood: 'idle' | 'happy' | 'curious' | 'sleepy' - type: 'wisp' | 'blob' | 'spark' | 'ember' + type: 'wisp' | 'blob' | 'spark' | 'ember' | 'glitch' | 'crystal' x: number y: number vx: number @@ -105,6 +105,19 @@ interface UserStats { totalFeedings: number totalVoidlings: number sessions: number + voidEssence: number +} + +interface ShopItem { + id: string + name: string + type: Voidling['type'] + color: string + glow: string + shape: string + cost: number + description: string + icon: string } const VOIDLING_TYPES = [ @@ -112,6 +125,19 @@ const VOIDLING_TYPES = [ { type: 'blob', color: 'bg-cyan-400', glow: 'shadow-cyan-500/50', shape: 'rounded-[40%]', description: 'Bouncy and playful' }, { type: 'spark', color: 'bg-amber-400', glow: 'shadow-amber-500/50', shape: 'rounded-sm', description: 'Energetic and quick' }, { type: 'ember', color: 'bg-rose-400', glow: 'shadow-rose-500/50', shape: 'rounded-full', description: 'Warm and sleepy' }, + { type: 'glitch', color: 'bg-green-400', glow: 'shadow-green-500/50', shape: 'rounded-lg', description: 'Unstable and mysterious' }, + { type: 'crystal', color: 'bg-blue-400', glow: 'shadow-blue-500/50', shape: 'rounded-sm', description: 'Sharp and reflective' }, +] + +const SHOP_ITEMS: ShopItem[] = [ + { id: 'wisp_egg', name: 'Wisp Egg', type: 'wisp', color: 'bg-purple-400', glow: 'shadow-purple-500/50', shape: 'rounded-full', cost: 50, description: 'Hatches into a wisp', icon: '๐Ÿฅš' }, + { id: 'blob_egg', name: 'Blob Egg', type: 'blob', color: 'bg-cyan-400', glow: 'shadow-cyan-500/50', shape: 'rounded-[40%]', cost: 50, description: 'Hatches into a blob', icon: '๐Ÿฅš' }, + { id: 'spark_egg', name: 'Spark Egg', type: 'spark', color: 'bg-amber-400', glow: 'shadow-amber-500/50', shape: 'rounded-sm', cost: 75, description: 'Hatches into a spark', icon: 'โšก' }, + { id: 'ember_egg', name: 'Ember Egg', type: 'ember', color: 'bg-rose-400', glow: 'shadow-rose-500/50', shape: 'rounded-full', cost: 75, description: 'Hatches into an ember', icon: '๐Ÿ”ฅ' }, + { id: 'glitch_egg', name: 'Glitch Egg', type: 'glitch', color: 'bg-green-400', glow: 'shadow-green-500/50', shape: 'rounded-lg', cost: 150, description: 'Rare unstable voidling', icon: '๐Ÿ‘พ' }, + { id: 'crystal_egg', name: 'Crystal Egg', type: 'crystal', color: 'bg-blue-400', glow: 'shadow-blue-500/50', shape: 'rounded-sm', cost: 150, description: 'Rare sharp voidling', icon: '๐Ÿ’Ž' }, + { id: 'treat', name: 'Void Treat', type: 'wisp', color: 'bg-yellow-400', glow: 'shadow-yellow-500/50', shape: 'rounded-full', cost: 20, description: 'All voidlings become happy', icon: '๐Ÿช' }, + { id: 'star_fragment', name: 'Star Fragment', type: 'wisp', color: 'bg-white', glow: 'shadow-white/50', shape: 'rounded-full', cost: 200, description: 'Rare: +100 Void Essence!', icon: 'โญ' }, ] const ACHIEVEMENTS: Achievement[] = [ @@ -120,11 +146,12 @@ const ACHIEVEMENTS: Achievement[] = [ { id: 'fifty_pets', name: 'Void Whisperer', description: 'Pet voidlings 50 times', icon: '๐Ÿ”ฎ', condition: (_v, s) => s.totalPets >= 50 }, { id: 'first_feeding', name: 'Nourisher', description: 'Feed your first voidling', icon: 'โ—•', condition: (_v, s) => s.totalFeedings >= 1 }, { id: 'collector', name: 'Collector', description: 'Have 5 different voidlings', icon: '๐Ÿ“ฆ', condition: (v, _s) => v.length >= 5 }, - { id: 'variety', name: 'Biodiversity', description: 'Have all 4 types of voidlings', icon: '๐ŸŒˆ', condition: (v) => { const types = new Set(v.map(x => x.type)); return types.size >= 4; } }, + { id: 'variety', name: 'Biodiversity', description: 'Have all 6 types of voidlings', icon: '๐ŸŒˆ', condition: (v) => { const types = new Set(v.map(x => x.type)); return types.size >= 6; } }, { id: 'parent', name: 'Protective Parent', description: 'Have 10 voidlings', icon: '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ', condition: (v, _s) => v.length >= 10 }, { id: 'curious', name: 'Curious Mind', description: 'Have a curious voidling', icon: 'โ“', condition: (v) => v.some(x => x.mood === 'curious') }, { id: 'sleepy_boys', name: 'Sleepy Boys', description: 'Have a sleepy voidling', icon: '๐Ÿ˜ด', condition: (v) => v.some(x => x.mood === 'sleepy') }, { id: 'happy_home', name: 'Happy Home', description: 'Have 3 happy voidlings', icon: 'โ˜บ๏ธ', condition: (v) => v.filter(x => x.mood === 'happy').length >= 3 }, + { id: 'rich', name: 'Void Tycoon', description: 'Accumulate 500 Void Essence', icon: '๐Ÿ’ฐ', condition: (_v, s) => s.voidEssence >= 500 }, ] const MOOD_EMOJIS = { @@ -182,10 +209,12 @@ export default function Chamber() { totalFeedings: 0, totalVoidlings: 0, sessions: 1, + voidEssence: 0, }) const [timeOfDay, setTimeOfDay] = useState('night') const [isAutoCycle, setIsAutoCycle] = useState(true) const [cycleProgress, setCycleProgress] = useState(0) + const [showShop, setShowShop] = useState(false) useEffect(() => { const savedVoidlings = localStorage.getItem('voidlings') @@ -411,7 +440,7 @@ export default function Chamber() { return v })) setUserStats(prev => { - const newStats = { ...prev, totalFeedings: prev.totalFeedings + 1 } + const newStats = { ...prev, totalFeedings: prev.totalFeedings + 1, voidEssence: prev.voidEssence + 3 } localStorage.setItem('voidling_stats', JSON.stringify(newStats)) return newStats }) @@ -427,7 +456,7 @@ export default function Chamber() { return v })) setUserStats(prev => { - const newStats = { ...prev, totalPets: prev.totalPets + 1 } + const newStats = { ...prev, totalPets: prev.totalPets + 1, voidEssence: prev.voidEssence + 2 } localStorage.setItem('voidling_stats', JSON.stringify(newStats)) return newStats }) @@ -438,6 +467,43 @@ export default function Chamber() { setVoidlings(voidlings.filter(v => v.id !== id)) } + function purchaseItem(item: ShopItem) { + if (userStats.voidEssence < item.cost) return + + setUserStats(prev => { + const newStats = { ...prev, voidEssence: prev.voidEssence - item.cost } + localStorage.setItem('voidling_stats', JSON.stringify(newStats)) + return newStats + }) + + if (item.id === 'treat') { + setVoidlings(prev => prev.map(v => ({ ...v, mood: 'happy' as const }))) + } else if (item.id === 'star_fragment') { + setUserStats(prev => { + const newStats = { ...prev, voidEssence: prev.voidEssence + 100 } + localStorage.setItem('voidling_stats', JSON.stringify(newStats)) + return newStats + }) + } else { + const pos = getRandomPosition() + const newVoidling: Voidling = { + id: generateId(), + name: '???', + color: item.color, + mood: getRandomMood(), + type: item.type, + x: pos.x, + y: pos.y, + vx: pos.vx, + vy: pos.vy, + pets: 0, + } + setVoidlings([...voidlings, newVoidling]) + setNamingVoidling(newVoidling.id) + setNewName('') + } + } + const getMoodAnimation = (mood: Voidling['mood']) => { switch (mood) { case 'happy': return 'animate-bounce' @@ -522,6 +588,15 @@ export default function Chamber() { ๐Ÿ† Achievements {unlockedAchievements.length}/{ACHIEVEMENTS.length} + + +
+ โ—‡ + Void Essence: {userStats.voidEssence} +
+

Pet and feed your voidlings to earn Void Essence!

+
+ {SHOP_ITEMS.map(item => { + const canAfford = userStats.voidEssence >= item.cost + return ( + + ) + })} +
+ + + )} + {newAchievement && (