fixed news
This commit is contained in:
188
party/index.ts
188
party/index.ts
@@ -2,9 +2,31 @@
|
|||||||
import type * as Party from 'partykit/server';
|
import type * as Party from 'partykit/server';
|
||||||
import { createClerkClient, verifyToken } from '@clerk/backend';
|
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 {
|
interface GameState {
|
||||||
totalClicks: number;
|
totalClicks: number;
|
||||||
users: Record<string, { name: string; clicks: number; lastSeen: number; bonusMultiplier: number }>; // Added bonusMultiplier
|
users: Record<string, { name: string; clicks: number; lastSeen: number; bonusMultiplier: number }>;
|
||||||
upgrades: Record<string, { owned: number; cost: number }>;
|
upgrades: Record<string, { owned: number; cost: number }>;
|
||||||
milestones: Record<string, boolean>;
|
milestones: Record<string, boolean>;
|
||||||
clickMultiplier: number;
|
clickMultiplier: number;
|
||||||
@@ -45,14 +67,120 @@ interface UserJoinMessage extends AuthenticatedMessage {
|
|||||||
|
|
||||||
type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage | AdminBroadcastMessage; // Updated Message type
|
type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage | AdminBroadcastMessage; // Updated Message type
|
||||||
|
|
||||||
const UPGRADES = {
|
const UPGRADES: Upgrade[] = [
|
||||||
clickMultiplier: { baseCost: 10, multiplier: 1.5, clickBonus: 1 },
|
{
|
||||||
autoClicker: { baseCost: 50, multiplier: 2, autoClickRate: 1 },
|
id: 'clickMultiplier',
|
||||||
megaBonus: { baseCost: 200, multiplier: 2.5, clickBonus: 5 },
|
name: '🖱️ Mega Click',
|
||||||
hyperClicker: { baseCost: 1000, multiplier: 3, autoClickRate: 10 },
|
description: '+1 click power per purchase',
|
||||||
quantumClicker: { baseCost: 5000, multiplier: 4, clickBonus: 50 },
|
baseCost: 10,
|
||||||
friendBoost: { baseCost: 2000, multiplier: 3, clickMultiplierBonus: 1.02 } // Renamed from shoominions upgrade
|
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 = [
|
const MILESTONES = [
|
||||||
{ threshold: 100, id: 'first-hundred', background: 'rainbow', image: 'https://media1.tenor.com/m/x8v1oNUOmg4AAAAd/spinning-rat-rat.gif' },
|
{ 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 = {
|
gameState: GameState = {
|
||||||
totalClicks: 0,
|
totalClicks: 0,
|
||||||
users: {},
|
users: {},
|
||||||
upgrades: Object.keys(UPGRADES).reduce((acc, key) => {
|
upgrades: UPGRADES.reduce((acc, upgrade) => {
|
||||||
acc[key] = { owned: 0, cost: UPGRADES[key as keyof typeof UPGRADES].baseCost };
|
acc[upgrade.id] = { owned: 0, cost: upgrade.baseCost };
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, { owned: number; cost: number }>),
|
}, {} as Record<string, { owned: number; cost: number }>),
|
||||||
milestones: {},
|
milestones: {},
|
||||||
@@ -269,16 +397,26 @@ export default class GameServer implements Party.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePurchaseUpgrade(data: PurchaseUpgradeMessage, authenticatedUserId: string) {
|
handlePurchaseUpgrade(data: PurchaseUpgradeMessage, authenticatedUserId: string) {
|
||||||
const upgrade = UPGRADES[data.upgradeId as keyof typeof UPGRADES];
|
const upgradeConfig = UPGRADES.find(u => u.id === data.upgradeId);
|
||||||
const currentUpgrade = this.gameState.upgrades[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
|
// Check affordability against totalClicks
|
||||||
if (this.gameState.totalClicks >= currentUpgrade.cost) {
|
if (this.gameState.totalClicks >= currentUpgradeState.cost) {
|
||||||
this.gameState.totalClicks -= currentUpgrade.cost; // Deduct from totalClicks
|
this.gameState.totalClicks -= currentUpgradeState.cost; // Deduct from totalClicks
|
||||||
currentUpgrade.owned += 1;
|
currentUpgradeState.owned += 1;
|
||||||
currentUpgrade.cost = Math.floor(upgrade.baseCost * Math.pow(upgrade.multiplier, currentUpgrade.owned));
|
|
||||||
|
// 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();
|
this.updateGameMultipliers();
|
||||||
}
|
}
|
||||||
@@ -288,13 +426,15 @@ export default class GameServer implements Party.Server {
|
|||||||
this.gameState.clickMultiplier = 1;
|
this.gameState.clickMultiplier = 1;
|
||||||
this.gameState.autoClickRate = 0;
|
this.gameState.autoClickRate = 0;
|
||||||
|
|
||||||
Object.entries(this.gameState.upgrades).forEach(([upgradeId, upgrade]) => {
|
Object.entries(this.gameState.upgrades).forEach(([upgradeId, upgradeState]) => {
|
||||||
const config = UPGRADES[upgradeId as keyof typeof UPGRADES];
|
const config = UPGRADES.find(u => u.id === upgradeId);
|
||||||
if ('clickBonus' in config && config.clickBonus) {
|
if (!config) return; // Should not happen if UPGRADES is consistent
|
||||||
this.gameState.clickMultiplier += config.clickBonus * upgrade.owned;
|
|
||||||
|
if (config.clickBonus) {
|
||||||
|
this.gameState.clickMultiplier += config.clickBonus * upgradeState.owned;
|
||||||
}
|
}
|
||||||
if ('autoClickRate' in config && config.autoClickRate) {
|
if (config.autoClickRate) {
|
||||||
this.gameState.autoClickRate += config.autoClickRate * upgrade.owned;
|
this.gameState.autoClickRate += config.autoClickRate * upgradeState.owned;
|
||||||
}
|
}
|
||||||
// Note: clickMultiplierBonus from upgrades.ts is handled client-side for spawning frequency
|
// Note: clickMultiplierBonus from upgrades.ts is handled client-side for spawning frequency
|
||||||
// and applied per-click on the server via handleApplyMultiplierBonus
|
// and applied per-click on the server via handleApplyMultiplierBonus
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ClickableMascot as ClickableMascotType, Upgrade } from './types'; // Im
|
|||||||
import { UPGRADES } from './config/upgrades'; // Import UPGRADES for upgrade config
|
import { UPGRADES } from './config/upgrades'; // Import UPGRADES for upgrade config
|
||||||
import { useLocation, Link } from 'wouter'; // Import wouter hooks
|
import { useLocation, Link } from 'wouter'; // Import wouter hooks
|
||||||
import AdminPage from './components/AdminPage'; // Import AdminPage
|
import AdminPage from './components/AdminPage'; // Import AdminPage
|
||||||
|
import { NewsMarquee } from './components/NewsMarquee'; // Import NewsMarquee
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isSignedIn, isLoaded, userId: clerkUserId } = useAuth(); // Get clerkUserId from useAuth
|
const { isSignedIn, isLoaded, userId: clerkUserId } = useAuth(); // Get clerkUserId from useAuth
|
||||||
@@ -216,6 +217,11 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* News Marquee */}
|
||||||
|
{gameState.upgrades['news']?.owned > 0 && UPGRADES.find(u => u.id === 'news')?.newsTitles && (
|
||||||
|
<NewsMarquee titles={UPGRADES.find(u => u.id === 'news')!.newsTitles!} />
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-8 relative z-10">
|
<div className="container mx-auto px-4 py-8 relative z-10">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
|||||||
24
src/components/NewsMarquee.tsx
Normal file
24
src/components/NewsMarquee.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface NewsMarqueeProps {
|
||||||
|
titles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewsMarquee: React.FC<NewsMarqueeProps> = ({ 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 (
|
||||||
|
<div className="fixed bottom-0 z-40 h-10 left-0 w-full overflow-hidden bg-gray-900 text-yellow-400 py-2 border-t-2 border-yellow-500">
|
||||||
|
<div className="absolute whitespace-nowrap animate-marquee z-50">
|
||||||
|
<span className="text-lg font-bold px-4">
|
||||||
|
{marqueeText}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -35,6 +35,11 @@ export function UpgradeShop({ gameState, totalClicks, onPurchase }: UpgradeShopP
|
|||||||
const cost = gameState.upgrades[upgrade.id]?.cost || upgrade.baseCost;
|
const cost = gameState.upgrades[upgrade.id]?.cost || upgrade.baseCost;
|
||||||
const canAfford = totalClicks >= cost; // Changed from userClicks
|
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;
|
let description = upgrade.description;
|
||||||
|
|
||||||
// Custom description for Friend Boost upgrade
|
// Custom description for Friend Boost upgrade
|
||||||
|
|||||||
@@ -91,5 +91,26 @@ export const UPGRADES: Upgrade[] = [
|
|||||||
rarity: 0.1,
|
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'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface Upgrade {
|
|||||||
clickMultiplierBonus?: number; // New: for compounding click boosts from mascots
|
clickMultiplierBonus?: number; // New: for compounding click boosts from mascots
|
||||||
icon: string;
|
icon: string;
|
||||||
mascotTiers?: MascotTier[]; // New: for defining mascot tiers for friendBoost
|
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 {
|
export interface MascotTier {
|
||||||
|
|||||||
@@ -2,7 +2,17 @@
|
|||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
theme: {
|
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: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user