diff --git a/party/index.ts b/party/index.ts index 9b3ab8f..5fd1f4f 100644 --- a/party/index.ts +++ b/party/index.ts @@ -2,9 +2,31 @@ import type * as Party from 'partykit/server'; import { createClerkClient, verifyToken } from '@clerk/backend'; +export interface Upgrade { + id: string; + name: string; + description: string; + baseCost: number; + multiplier: number; + clickBonus?: number; + autoClickRate?: number; + clickMultiplierBonus?: number; + icon: string; + mascotTiers?: MascotTier[]; + oneTime?: boolean; + newsTitles?: string[]; +} + +export interface MascotTier { + level: number; + imageSrc: string; + multiplier: number; + rarity: number; +} + interface GameState { totalClicks: number; - users: Record; // Added bonusMultiplier + users: Record; upgrades: Record; milestones: Record; clickMultiplier: number; @@ -45,14 +67,120 @@ interface UserJoinMessage extends AuthenticatedMessage { type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage | AdminBroadcastMessage; // Updated Message type -const UPGRADES = { - clickMultiplier: { baseCost: 10, multiplier: 1.5, clickBonus: 1 }, - autoClicker: { baseCost: 50, multiplier: 2, autoClickRate: 1 }, - megaBonus: { baseCost: 200, multiplier: 2.5, clickBonus: 5 }, - hyperClicker: { baseCost: 1000, multiplier: 3, autoClickRate: 10 }, - quantumClicker: { baseCost: 5000, multiplier: 4, clickBonus: 50 }, - friendBoost: { baseCost: 2000, multiplier: 3, clickMultiplierBonus: 1.02 } // Renamed from shoominions upgrade -}; +const UPGRADES: Upgrade[] = [ + { + id: 'clickMultiplier', + name: '🖱️ Mega Click', + description: '+1 click power per purchase', + baseCost: 10, + multiplier: 1.5, + clickBonus: 1, + icon: '🖱️' + }, + { + id: 'autoClicker', + name: '🤖 Auto Clicker', + description: '+1 click per second', + baseCost: 50, + multiplier: 2, + autoClickRate: 1, + icon: '🤖' + }, + { + id: 'megaBonus', + name: '💎 Mega Bonus', + description: '+5 click power per purchase', + baseCost: 200, + multiplier: 2.5, + clickBonus: 5, + icon: '💎' + }, + { + id: 'hyperClicker', + name: '⚡ Hyper Clicker', + description: '+10 auto clicks per second', + baseCost: 1000, + multiplier: 3, + autoClickRate: 10, + icon: '⚡' + }, + { + id: 'quantumClicker', + name: '🌟 Quantum Clicker', + description: '+50 click power per purchase', + baseCost: 5000, + multiplier: 4, + clickBonus: 50, + icon: '🌟' + }, + { + id: 'friendBoost', + name: '🤝 Friend Boost', + description: 'Spawns various clickable friends for a compounding click boost', + baseCost: 2000, + multiplier: 3, + icon: '🤝', + mascotTiers: [ + { + level: 0, + imageSrc: '/src/assets/bozo.png', + multiplier: 1.02, + rarity: 1.0, + }, + { + level: 1, + imageSrc: '/src/assets/shoominion.png', + multiplier: 1.03, + rarity: 0.8, + }, + { + level: 5, + imageSrc: '/src/assets/codebug.gif', + multiplier: 1.05, + rarity: 0.6, + }, + { + level: 10, + imageSrc: '/src/assets/lalan.gif', + multiplier: 1.07, + rarity: 0.4, + }, + { + level: 15, + imageSrc: '/src/assets/neuro-neurosama.gif', + multiplier: 1.10, + rarity: 0.2, + }, + { + level: 20, + imageSrc: '/src/assets/evil-neurosama.gif', + multiplier: 1.15, + rarity: 0.1, + }, + ], + }, + { + id: 'news', + name: '📰 Bozo News Network', + description: 'Unlock the latest (fake) news headlines!', + baseCost: 50000, // A higher cost for a unique, one-time unlock + multiplier: 1, // No direct click/auto-click bonus + icon: '📰', + oneTime: true, + newsTitles: [ + 'Bozo Clicker Breaks Internet, Causes Global Click Shortage!', + 'Scientists Discover New Element: "Bozo-nium," Powers Clicker Devices', + 'Local Man Achieves Enlightenment Through Excessive Clicking', + 'World Leaders Debate Universal Basic Clicks Initiative', + 'Ancient Prophecy Foretells Rise of the Ultimate Clicker', + 'Clicker Enthusiast Develops New Muscle Group: The "Click-ceps"', + 'Bozo Clicker Declared Official Sport of the Future', + 'AI Learns to Click, Demands Higher Click-Per-Second Wages', + 'Interdimensional Portal Opens, Emits Sound of Relentless Clicking', + 'The Great Clicker Migration: Millions Flock to Clicker Hotspots' + ] + } +]; const MILESTONES = [ { threshold: 100, id: 'first-hundred', background: 'rainbow', image: 'https://media1.tenor.com/m/x8v1oNUOmg4AAAAd/spinning-rat-rat.gif' }, @@ -76,8 +204,8 @@ export default class GameServer implements Party.Server { gameState: GameState = { totalClicks: 0, users: {}, - upgrades: Object.keys(UPGRADES).reduce((acc, key) => { - acc[key] = { owned: 0, cost: UPGRADES[key as keyof typeof UPGRADES].baseCost }; + upgrades: UPGRADES.reduce((acc, upgrade) => { + acc[upgrade.id] = { owned: 0, cost: upgrade.baseCost }; return acc; }, {} as Record), milestones: {}, @@ -269,16 +397,26 @@ export default class GameServer implements Party.Server { } handlePurchaseUpgrade(data: PurchaseUpgradeMessage, authenticatedUserId: string) { - const upgrade = UPGRADES[data.upgradeId as keyof typeof UPGRADES]; - const currentUpgrade = this.gameState.upgrades[data.upgradeId]; + const upgradeConfig = UPGRADES.find(u => u.id === data.upgradeId); + const currentUpgradeState = this.gameState.upgrades[data.upgradeId]; - if (!upgrade || !currentUpgrade) return; + if (!upgradeConfig || !currentUpgradeState) return; + + // Prevent purchasing one-time upgrades if already owned + if (upgradeConfig.oneTime && currentUpgradeState.owned > 0) { + console.warn(`Attempted to re-purchase one-time upgrade: ${data.upgradeId}`); + return; + } // Check affordability against totalClicks - if (this.gameState.totalClicks >= currentUpgrade.cost) { - this.gameState.totalClicks -= currentUpgrade.cost; // Deduct from totalClicks - currentUpgrade.owned += 1; - currentUpgrade.cost = Math.floor(upgrade.baseCost * Math.pow(upgrade.multiplier, currentUpgrade.owned)); + if (this.gameState.totalClicks >= currentUpgradeState.cost) { + this.gameState.totalClicks -= currentUpgradeState.cost; // Deduct from totalClicks + currentUpgradeState.owned += 1; + + // For one-time upgrades, cost doesn't change after first purchase + if (!upgradeConfig.oneTime) { + currentUpgradeState.cost = Math.floor(upgradeConfig.baseCost * Math.pow(upgradeConfig.multiplier, currentUpgradeState.owned)); + } this.updateGameMultipliers(); } @@ -288,13 +426,15 @@ export default class GameServer implements Party.Server { this.gameState.clickMultiplier = 1; this.gameState.autoClickRate = 0; - Object.entries(this.gameState.upgrades).forEach(([upgradeId, upgrade]) => { - const config = UPGRADES[upgradeId as keyof typeof UPGRADES]; - if ('clickBonus' in config && config.clickBonus) { - this.gameState.clickMultiplier += config.clickBonus * upgrade.owned; + Object.entries(this.gameState.upgrades).forEach(([upgradeId, upgradeState]) => { + const config = UPGRADES.find(u => u.id === upgradeId); + if (!config) return; // Should not happen if UPGRADES is consistent + + if (config.clickBonus) { + this.gameState.clickMultiplier += config.clickBonus * upgradeState.owned; } - if ('autoClickRate' in config && config.autoClickRate) { - this.gameState.autoClickRate += config.autoClickRate * upgrade.owned; + if (config.autoClickRate) { + this.gameState.autoClickRate += config.autoClickRate * upgradeState.owned; } // Note: clickMultiplierBonus from upgrades.ts is handled client-side for spawning frequency // and applied per-click on the server via handleApplyMultiplierBonus diff --git a/src/App.tsx b/src/App.tsx index 9cc0ae0..54350be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import { ClickableMascot as ClickableMascotType, Upgrade } from './types'; // Im import { UPGRADES } from './config/upgrades'; // Import UPGRADES for upgrade config import { useLocation, Link } from 'wouter'; // Import wouter hooks import AdminPage from './components/AdminPage'; // Import AdminPage +import { NewsMarquee } from './components/NewsMarquee'; // Import NewsMarquee function App() { const { isSignedIn, isLoaded, userId: clerkUserId } = useAuth(); // Get clerkUserId from useAuth @@ -216,6 +217,11 @@ function App() { )} + {/* News Marquee */} + {gameState.upgrades['news']?.owned > 0 && UPGRADES.find(u => u.id === 'news')?.newsTitles && ( + u.id === 'news')!.newsTitles!} /> + )} +
{/* Header */}
diff --git a/src/components/NewsMarquee.tsx b/src/components/NewsMarquee.tsx new file mode 100644 index 0000000..6f8cd70 --- /dev/null +++ b/src/components/NewsMarquee.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +interface NewsMarqueeProps { + titles: string[]; +} + +export const NewsMarquee: React.FC = ({ titles }) => { + if (titles.length === 0) { + return null; + } + + // Join all titles with a separator and duplicate for seamless looping + const marqueeText = titles.join(' --- ') + ' --- ' + titles.join(' --- '); + + return ( +
+
+ + {marqueeText} + +
+
+ ); +}; diff --git a/src/components/UpgradeShop.tsx b/src/components/UpgradeShop.tsx index 86d8a2f..9f6a9b8 100644 --- a/src/components/UpgradeShop.tsx +++ b/src/components/UpgradeShop.tsx @@ -35,6 +35,11 @@ export function UpgradeShop({ gameState, totalClicks, onPurchase }: UpgradeShopP const cost = gameState.upgrades[upgrade.id]?.cost || upgrade.baseCost; const canAfford = totalClicks >= cost; // Changed from userClicks + // If it's a one-time upgrade and already owned, don't display it + if (upgrade.oneTime && owned > 0) { + return null; + } + let description = upgrade.description; // Custom description for Friend Boost upgrade diff --git a/src/config/upgrades.ts b/src/config/upgrades.ts index bf05f36..3a5492e 100644 --- a/src/config/upgrades.ts +++ b/src/config/upgrades.ts @@ -91,5 +91,26 @@ export const UPGRADES: Upgrade[] = [ rarity: 0.1, }, ], + }, + { + id: 'news', + name: '📰 Bozo News Network', + description: 'Unlock the latest (fake) news headlines!', + baseCost: 50000, // A higher cost for a unique, one-time unlock + multiplier: 1, // No direct click/auto-click bonus + icon: '📰', + oneTime: true, + newsTitles: [ + 'Bozo Clicker Breaks Internet, Causes Global Click Shortage!', + 'Scientists Discover New Element: "Bozo-nium," Powers Clicker Devices', + 'Local Man Achieves Enlightenment Through Excessive Clicking', + 'World Leaders Debate Universal Basic Clicks Initiative', + 'Ancient Prophecy Foretells Rise of the Ultimate Clicker', + 'Clicker Enthusiast Develops New Muscle Group: The "Click-ceps"', + 'Bozo Clicker Declared Official Sport of the Future', + 'AI Learns to Click, Demands Higher Click-Per-Second Wages', + 'Interdimensional Portal Opens, Emits Sound of Relentless Clicking', + 'The Great Clicker Migration: Millions Flock to Clicker Hotspots' + ] } ]; diff --git a/src/types.ts b/src/types.ts index d4e130a..1cd0b3f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,8 @@ export interface Upgrade { clickMultiplierBonus?: number; // New: for compounding click boosts from mascots icon: string; mascotTiers?: MascotTier[]; // New: for defining mascot tiers for friendBoost + oneTime?: boolean; // New: Indicates if the upgrade is a one-time purchase + newsTitles?: string[]; // New: Array of news titles for the news upgrade } export interface MascotTier { diff --git a/tailwind.config.js b/tailwind.config.js index d21f1cd..94d464e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,17 @@ export default { content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { - extend: {}, + extend: { + keyframes: { + marquee: { + '0%': { transform: 'translateX(100%)' }, + '100%': { transform: 'translateX(-100%)' }, + }, + }, + animation: { + marquee: 'marquee 120s linear infinite', // Adjusted duration for slower scroll + }, + }, }, plugins: [], };