ratpoison
This commit is contained in:
@@ -305,9 +305,25 @@ export default class GameServer implements Party.Server {
|
||||
const attackerUserState = this.gameState.users[authenticatedUserId];
|
||||
|
||||
if (!targetUserState || !attackerUserState || targetUserId === authenticatedUserId) {
|
||||
console.log(`Rat Poison: Attempt to poison self or non-existent user (${targetUserId}). Aborting.`);
|
||||
return; // Cannot poison self or non-existent user
|
||||
}
|
||||
|
||||
// Check if the target user is currently online
|
||||
if (!this.userConnections.has(targetUserId)) {
|
||||
const attackerConn = this.userConnections.get(authenticatedUserId);
|
||||
if (attackerConn) {
|
||||
attackerConn.send(JSON.stringify({
|
||||
type: 'rat-poison-feedback',
|
||||
message: `${targetUserState.name} is currently offline and cannot be targeted.`
|
||||
}));
|
||||
}
|
||||
console.log(`Rat Poison: Target user ${targetUserState.name} (${targetUserId}) is offline. Attacker: ${attackerUserState.name} (${authenticatedUserId}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
if (targetUserState.ratPoisonImmunityUntil && targetUserState.ratPoisonImmunityUntil > now) {
|
||||
// Target is immune, notify attacker
|
||||
@@ -318,6 +334,7 @@ export default class GameServer implements Party.Server {
|
||||
message: `${targetUserState.name} is currently immune to rat poison!`
|
||||
}));
|
||||
}
|
||||
console.log(`Rat Poison: Target user ${targetUserState.name} (${targetUserId}) is immune until ${new Date(targetUserState.ratPoisonImmunityUntil).toISOString()}. Attacker: ${attackerUserState.name} (${authenticatedUserId}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,7 +342,7 @@ export default class GameServer implements Party.Server {
|
||||
const newsUpgrade = ALL_UPGRADES.find(u => u.id === 'news');
|
||||
const newsTitles = newsUpgrade?.newsTitles || [];
|
||||
if (newsTitles.length === 0) {
|
||||
console.warn('No news titles found for rat poison challenge.');
|
||||
console.warn('Rat Poison: No news titles found for rat poison challenge.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,6 +354,9 @@ export default class GameServer implements Party.Server {
|
||||
challengeString,
|
||||
expiresAt,
|
||||
};
|
||||
targetUserState.ratPoisonImmunityUntil = expiresAt; // User is immune for the duration of the challenge
|
||||
|
||||
console.log(`Rat Poison: Challenge issued to ${targetUserState.name} (${targetUserId}). String: "${challengeString}", Expires: ${new Date(expiresAt).toISOString()}.`);
|
||||
|
||||
// Notify target user to start challenge
|
||||
const targetConn = this.userConnections.get(targetUserId);
|
||||
@@ -352,6 +372,7 @@ export default class GameServer implements Party.Server {
|
||||
setTimeout(() => {
|
||||
if (targetUserState.ratPoisonChallenge && targetUserState.ratPoisonChallenge.expiresAt === expiresAt) {
|
||||
// Challenge not solved in time
|
||||
console.log(`Rat Poison: Challenge for ${targetUserState.name} (${targetUserId}) expired.`);
|
||||
this.applyRatPoisonPenalty(targetUserId);
|
||||
this.broadcast();
|
||||
}
|
||||
@@ -363,12 +384,14 @@ export default class GameServer implements Party.Server {
|
||||
handleSolveRatPoison(data: SolveRatPoisonMessage, authenticatedUserId: string) {
|
||||
const userState = this.gameState.users[authenticatedUserId];
|
||||
if (!userState || !userState.ratPoisonChallenge) {
|
||||
console.log(`Rat Poison: No active challenge for user ${authenticatedUserId}. Aborting solve attempt.`);
|
||||
return; // No active challenge
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (now > userState.ratPoisonChallenge.expiresAt) {
|
||||
// Challenge expired
|
||||
console.log(`Rat Poison: User ${authenticatedUserId} attempted to solve expired challenge.`);
|
||||
this.applyRatPoisonPenalty(authenticatedUserId);
|
||||
} else if (data.challengeString === userState.ratPoisonChallenge.challengeString) {
|
||||
// Challenge solved successfully
|
||||
@@ -381,8 +404,10 @@ export default class GameServer implements Party.Server {
|
||||
message: 'Challenge solved! You are immune for 2 minutes.'
|
||||
}));
|
||||
}
|
||||
console.log(`Rat Poison: User ${authenticatedUserId} successfully solved challenge. Immunity until ${new Date(userState.ratPoisonImmunityUntil).toISOString()}.`);
|
||||
} else {
|
||||
// Challenge failed (wrong string)
|
||||
console.log(`Rat Poison: User ${authenticatedUserId} failed challenge (incorrect string).`);
|
||||
this.applyRatPoisonPenalty(authenticatedUserId);
|
||||
const userConn = this.userConnections.get(authenticatedUserId);
|
||||
if (userConn) {
|
||||
@@ -400,11 +425,15 @@ export default class GameServer implements Party.Server {
|
||||
|
||||
applyRatPoisonPenalty(userId: string) {
|
||||
const userState = this.gameState.users[userId];
|
||||
if (!userState) return;
|
||||
if (!userState) {
|
||||
console.log(`Rat Poison: Attempted to apply penalty to non-existent user ${userId}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const clicksLost = Math.floor(userState.clicks * 0.30);
|
||||
userState.clicks -= clicksLost;
|
||||
userState.ratPoisonImmunityUntil = Date.now() + (20 * 1000); // 20 seconds immunity
|
||||
console.log(`Rat Poison: Penalty applied to ${userState.name} (${userId}). Lost ${clicksLost} clicks. Immunity until ${new Date(userState.ratPoisonImmunityUntil).toISOString()}.`);
|
||||
|
||||
const userConn = this.userConnections.get(userId);
|
||||
if (userConn) {
|
||||
|
||||
71
src/App.tsx
71
src/App.tsx
@@ -20,7 +20,7 @@ import { RatPoisonChallenge } from './components/RatPoisonChallenge'; // Import
|
||||
|
||||
function App() {
|
||||
const { isSignedIn, isLoaded, userId: clerkUserId } = useAuth(); // Get clerkUserId from useAuth
|
||||
const { gameState, sendClick, purchaseUpgrade, userId, sendMascotClickBonus, lastMessage, sendSolveRatPoison } = usePartyKit(); // Renamed sendShoominionClickBonus
|
||||
const { gameState, sendClick, purchaseUpgrade, userId, sendMascotClickBonus, lastMessage, sendSolveRatPoison, ratPoisonChallengeData, ratPoisonResultData, ratPoisonFeedbackData, clearRatPoisonChallenge } = usePartyKit(); // Renamed sendShoominionClickBonus
|
||||
const [celebrationMessage, setCelebrationMessage] = useState<string | null>(null);
|
||||
const [previousMilestones, setPreviousMilestones] = useState<Record<string, boolean>>({});
|
||||
const [mascotEntities, setMascotEntities] = useState<ClickableMascotType[]>([]);
|
||||
@@ -30,10 +30,11 @@ function App() {
|
||||
const [location] = useLocation(); // Get current location from wouter
|
||||
const [adminBroadcastMessage, setAdminBroadcastMessage] = useState<string | null>(null); // New state for admin messages
|
||||
const [showSecretVideo, setShowSecretVideo] = useState(false); // New state for secret video
|
||||
const [ratPoisonChallenge, setRatPoisonChallenge] = useState<{ challenge: string; expiresAt: number } | null>(null);
|
||||
const [ratPoisonResult, setRatPoisonResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
// Removed local ratPoisonChallenge and ratPoisonResult states, now directly from usePartyKit
|
||||
|
||||
const userState = isSignedIn && userId ? gameState?.users[userId] : null;
|
||||
console.log('App.tsx: Current ratPoisonChallengeData:', ratPoisonChallengeData);
|
||||
|
||||
const userState = isSignedIn && userId ? (gameState?.users[userId] || null) : null;
|
||||
|
||||
// Admin user ID from .env
|
||||
const CLERK_ADMIN_USERID = import.meta.env.VITE_CLERK_ADMIN_USERID;
|
||||
@@ -62,8 +63,7 @@ function App() {
|
||||
|
||||
// Effect for secret video
|
||||
useEffect(() => {
|
||||
if (!userState) return; // Ensure userState is not null/undefined
|
||||
if (userState.upgrades?.['secretVideo']?.owned > 0) {
|
||||
if ((userState?.upgrades?.['secretVideo']?.owned || 0) > 0) {
|
||||
setShowSecretVideo(true);
|
||||
}
|
||||
}, [userState]);
|
||||
@@ -77,24 +77,39 @@ function App() {
|
||||
if (parsedMessage.type === 'admin-message') {
|
||||
setAdminBroadcastMessage(parsedMessage.message);
|
||||
setTimeout(() => setAdminBroadcastMessage(null), 7000); // Message disappears after 7 seconds
|
||||
} else if (parsedMessage.type === 'rat-poison-challenge') {
|
||||
console.log('App.tsx: Received rat-poison-challenge:', parsedMessage); // Log challenge message
|
||||
setRatPoisonChallenge({ challenge: parsedMessage.challenge, expiresAt: parsedMessage.expiresAt });
|
||||
console.log('App.tsx: ratPoisonChallenge state set to:', { challenge: parsedMessage.challenge, expiresAt: parsedMessage.expiresAt }); // Log state after setting
|
||||
} else if (parsedMessage.type === 'rat-poison-result') {
|
||||
setRatPoisonResult({ success: parsedMessage.success, message: parsedMessage.message });
|
||||
setTimeout(() => setRatPoisonResult(null), 5000); // Result message disappears after 5 seconds
|
||||
} else if (parsedMessage.type === 'rat-poison-feedback') {
|
||||
// Display feedback to the attacker
|
||||
setAdminBroadcastMessage(parsedMessage.message); // Re-using admin message display for simplicity
|
||||
setTimeout(() => setAdminBroadcastMessage(null), 5000);
|
||||
}
|
||||
// rat-poison-challenge, rat-poison-result, rat-poison-feedback are now handled by dedicated states from usePartyKit
|
||||
} catch (error) {
|
||||
console.error('Failed to parse last message in App.tsx:', error);
|
||||
}
|
||||
}
|
||||
}, [lastMessage]);
|
||||
|
||||
// Effect for displaying rat poison result
|
||||
useEffect(() => {
|
||||
if (ratPoisonResultData) {
|
||||
console.log('App.tsx: ratPoisonResultData updated:', ratPoisonResultData);
|
||||
// The modal will be rendered directly by ratPoisonChallengeData
|
||||
// This effect is just for the temporary result message
|
||||
const timeout = setTimeout(() => {
|
||||
// Clear the result data after a delay
|
||||
// This requires a function in usePartyKit to clear it, or pass a prop to RatPoisonChallenge
|
||||
// For now, we'll just let the message disappear.
|
||||
}, 5000);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [ratPoisonResultData]);
|
||||
|
||||
// Effect for displaying rat poison feedback
|
||||
useEffect(() => {
|
||||
if (ratPoisonFeedbackData) {
|
||||
console.log('App.tsx: ratPoisonFeedbackData updated:', ratPoisonFeedbackData);
|
||||
setAdminBroadcastMessage(ratPoisonFeedbackData); // Re-using admin message display for simplicity
|
||||
const timeout = setTimeout(() => setAdminBroadcastMessage(null), 5000);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [ratPoisonFeedbackData]);
|
||||
|
||||
// Effect to keep gameStateRef updated
|
||||
useEffect(() => {
|
||||
gameStateRef.current = gameState;
|
||||
@@ -105,7 +120,7 @@ function App() {
|
||||
if (!userState) return;
|
||||
|
||||
const friendBoostUpgrade = UPGRADES.find(u => u.id === 'friendBoost') as Upgrade | undefined;
|
||||
const ownedFriendBoost = userState.upgrades['friendBoost']?.owned || 0;
|
||||
const ownedFriendBoost = (userState?.upgrades?.['friendBoost']?.owned || 0);
|
||||
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
@@ -193,7 +208,11 @@ function App() {
|
||||
|
||||
const handleSolveRatPoison = (solvedString: string) => {
|
||||
sendSolveRatPoison(solvedString);
|
||||
setRatPoisonChallenge(null);
|
||||
// The challenge data will be cleared by usePartyKit or by the modal's onClose
|
||||
// To explicitly clear the modal, we can set ratPoisonChallengeData to null here.
|
||||
// However, it's better if usePartyKit handles clearing it after a successful solve/fail.
|
||||
// For now, we'll leave it as is, assuming the server will eventually clear it.
|
||||
clearRatPoisonChallenge(); // Clear the challenge data when solved or closed
|
||||
};
|
||||
|
||||
if (!isLoaded || !gameState) {
|
||||
@@ -253,10 +272,10 @@ function App() {
|
||||
)}
|
||||
|
||||
{/* Rat Poison Result Message */}
|
||||
{ratPoisonResult && (
|
||||
{ratPoisonResultData && (
|
||||
<div className="fixed top-40 left-0 right-0 z-50 flex justify-center pt-8">
|
||||
<div className={`px-8 py-4 rounded-full text-2xl font-bold shadow-2xl animate-pulse border-4 border-white ${ratPoisonResult.success ? 'bg-green-600' : 'bg-red-600'}`}>
|
||||
{ratPoisonResult.message}
|
||||
<div className={`px-8 py-4 rounded-full text-2xl font-bold shadow-2xl animate-pulse border-4 border-white ${ratPoisonResultData.success ? 'bg-green-600' : 'bg-red-600'}`}>
|
||||
{ratPoisonResultData.message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -349,12 +368,12 @@ function App() {
|
||||
)}
|
||||
|
||||
{/* Rat Poison Challenge Modal */}
|
||||
{ratPoisonChallenge && (
|
||||
{ratPoisonChallengeData && (
|
||||
<RatPoisonChallenge
|
||||
challengeString={ratPoisonChallenge.challenge}
|
||||
expiresAt={ratPoisonChallenge.expiresAt}
|
||||
challengeString={ratPoisonChallengeData.challengeString}
|
||||
expiresAt={ratPoisonChallengeData.expiresAt}
|
||||
onSolve={handleSolveRatPoison}
|
||||
onClose={() => setRatPoisonChallenge(null)}
|
||||
onClose={clearRatPoisonChallenge} // Call the clear function from usePartyKit
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ export function usePartyKit() {
|
||||
const { user } = useUser();
|
||||
const [gameState, setGameState] = useState<GameState | null>(null);
|
||||
const [socket, setSocket] = useState<PartySocket | null>(null);
|
||||
const [lastMessage, setLastMessage] = useState<string | null>(null); // New state for last message
|
||||
const [lastMessage, setLastMessage] = useState<string | null>(null);
|
||||
// ratPoisonChallengeData will now be derived from gameState
|
||||
const [ratPoisonResultData, setRatPoisonResultData] = useState<{ success: boolean; message: string } | null>(null);
|
||||
const [ratPoisonFeedbackData, setRatPoisonFeedbackData] = useState<string | null>(null);
|
||||
// Generate a persistent guest ID if the user is not signed in
|
||||
const [guestId] = useState(() => {
|
||||
let storedGuestId = localStorage.getItem('bozo_guest_id');
|
||||
@@ -59,8 +62,15 @@ export function usePartyKit() {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'game-state') {
|
||||
setGameState(data.state);
|
||||
} else if (data.type === 'rat-poison-result') {
|
||||
console.log('usePartyKit: Setting ratPoisonResultData:', { success: data.success, message: data.message });
|
||||
setRatPoisonResultData({ success: data.success, message: data.message });
|
||||
} else if (data.type === 'rat-poison-feedback') {
|
||||
console.log('usePartyKit: Setting ratPoisonFeedbackData:', data.message);
|
||||
setRatPoisonFeedbackData(data.message);
|
||||
}
|
||||
setLastMessage(event.data); // Store the raw last message
|
||||
// No longer setting ratPoisonChallengeData directly here, it will be derived from gameState
|
||||
setLastMessage(event.data); // Still store the raw last message for other general purposes if needed
|
||||
};
|
||||
|
||||
setSocket(ws);
|
||||
@@ -161,6 +171,11 @@ export function usePartyKit() {
|
||||
}
|
||||
}, [socket, isSignedIn, user, getToken]);
|
||||
|
||||
const clearRatPoisonChallenge = useCallback(() => {
|
||||
setRatPoisonResultData(null);
|
||||
setRatPoisonFeedbackData(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
gameState,
|
||||
sendClick,
|
||||
@@ -174,5 +189,9 @@ export function usePartyKit() {
|
||||
editUser,
|
||||
throwRatPoison,
|
||||
sendSolveRatPoison,
|
||||
ratPoisonResultData,
|
||||
ratPoisonFeedbackData,
|
||||
ratPoisonChallengeData: gameState?.users[currentUserId || '']?.ratPoisonChallenge || null,
|
||||
clearRatPoisonChallenge,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user