'use client' import { useEffect, useRef, useState, useCallback } from 'react' // ── 塔的完整元数据(用于底部面板 + Tooltip) ────────────────────────────── interface TowerInfo { type: string name: string cost: number color: string img: string // Tooltip 内容 quote: string // 人物一句话名言/自我介绍 atk: string // 攻击类型 dmg: string // 伤害数值 range: string // 射程 speed: string // 攻速 skill: string // 特殊技能名 skillDesc: string // 技能描述 tactics: string // 建议打法 } const TOWER_META: TowerInfo[] = [ { type: 'outsource', name: '外包程序员', cost: 30, color: '#94A3B8', img: '/game-assets/tower-outsource.png', quote: '"能跑就行,文档?没有!"', atk: '近战', dmg: '8', range: '2格', speed: '0.7次/s', skill: 'Bug自助餐', skillDesc: '每次攻击30%概率丢空;5%概率Bug反弹,精力瞬间归零', tactics: '前期凑数用,价格最便宜。不要指望他,放在路口堵一堵就够了。', }, { type: 'intern', name: '00后实习生', cost: 50, color: '#22C55E', img: '/game-assets/tower-intern.png', quote: '"准点下班,法律护身。"', atk: '近战', dmg: '15', range: '1.5格', speed: '1.5次/s', skill: '整顿职场', skillDesc: '每次攻击5%概率直接秒杀普通怪物;每秒有1%概率自动"跑路",退还25HC', tactics: '性价比最高的前排,攒够HC先多建几个。秒杀概率虽低但惊喜不断。', }, { type: 'hrbp', name: 'HRBP', cost: 80, color: '#EC4899', img: '/game-assets/tower-hrbp.png', quote: '"公司是你家,福报靠大家。"', atk: '辅助(无伤害)', dmg: '0', range: '1格光环', speed: '每0.5s触发', skill: '打鸡血', skillDesc: '持续给周围1格内所有塔附加+20%攻速BUFF;每次消耗自身5点精力', tactics: '永远放在塔群中央!搭配P6或实习生使用效果翻倍,但别让她精力耗完。', }, { type: 'ops', name: '运营专员', cost: 90, color: '#8B5CF6', img: '/game-assets/tower-ops.png', quote: '"数据不好看?A/B测!"', atk: '远程 范围溅射', dmg: '18', range: '3格', speed: '1.2次/s', skill: '增长黑客', skillDesc: '每次命中20%概率触发"双倍HC",目标死亡时奖励翻倍;命中范围内相邻怪物也受溅射伤害', tactics: '人多的时候最强,专门对付密集的应届生群体。顺手赚HC。', }, { type: 'ppt', name: 'PPT大师', cost: 100, color: '#F59E0B', img: '/game-assets/tower-ppt.png', quote: '"底层逻辑要打通,顶层设计要对齐。"', atk: 'AOE范围', dmg: '5(范围)', range: '3格圆形', speed: '1.5次/s', skill: '黑话领域', skillDesc: '攻击圆形范围内所有怪物并降低移速40%,持续2秒;越多怪物越划算', tactics: '减速神器!放在路径转弯处效果最佳。搭配P6的DOT伤害组合拳秒杀老员工。', }, { type: 'senior', name: 'P6资深开发', cost: 120, color: '#3B82F6', img: '/game-assets/tower-senior.png', quote: '"这行代码谁写的?别问,是我。"', atk: '远程直线', dmg: '30 + DOT 10/s×3s', range: '5格', speed: '1.0次/s', skill: '代码屎山', skillDesc: '子弹命中后附加DOT(每秒10伤,持续3秒);远程射程5格,直线穿透感', tactics: '主力输出,射程超长。优先打护盾老员工和Boss,DOT能绕过护盾持续掉血。', }, { type: 'pm', name: '产品经理', cost: 160, color: '#06B6D4', img: '/game-assets/tower-pm.png', quote: '"这个需求很简单,下班前能上线吗?"', atk: '远程曲线', dmg: '20', range: '4格', speed: '0.8次/s', skill: '需求变更', skillDesc: '每4次攻击触发"需求变更",将目标强制打回2个路径节点,大幅拖延到达时间', tactics: '最贵但效果独特!把Boss和老员工不断打回去,配合其他输出塔可以让怪走不出去。', }, ] type TowerType = 'outsource' | 'intern' | 'hrbp' | 'ops' | 'ppt' | 'senior' | 'pm' type EffectType = 'attack_boost' | 'speed_boost' | 'money_rain' | 'rage_mode' | 'backfire' interface PuaResult { score: number title: string desc: string effect: EffectType cost?: number similarity?: number similarTo?: string isDuplicate?: boolean penaltyMultiplier?: number } const EFFECT_META: Record = { attack_boost: { label: '攻击力提升', color: '#22C55E', icon: '⚔️' }, speed_boost: { label: '攻速暴增', color: '#FBBF24', icon: '⚡' }, money_rain: { label: 'HC暴击', color: '#A78BFA', icon: '💰' }, rage_mode: { label: '全场狂暴', color: '#FF4E00', icon: '🔥' }, backfire: { label: '废话翻车', color: '#6B7280', icon: '💀' }, } const PUA_PLACEHOLDERS = [ '今天是本季度最关键的一天,大家冲!', '不拼搏,对不起父母的养育之恩...', '996是福报,感恩公司给的机会', '我们要有狼性精神,卷赢对手!', '大家加油,相信自己!', ] // ── 塔的悬浮信息卡片 ───────────────────────────────────────────────────────── function TowerTooltip({ tower, pos, canAfford, }: { tower: TowerInfo pos: { left: number; bottom: number } canAfford: boolean }) { return (
{/* 小三角指示器 */}
{/* 头部:头像 + 名字 + 费用 */}
{/* eslint-disable-next-line @next/next/no-img-element */} {tower.name}
{tower.name}
{tower.cost} HC {!canAfford && '(不足)'}
{/* 名言 */}
{tower.quote}
{/* 数值属性 */}
{[ ['攻击', tower.atk], ['射程', tower.range], ['伤害', tower.dmg], ['攻速', tower.speed], ].map(([label, val]) => (
{label} {val}
))}
{/* 技能 */}
✦ {tower.skill}
{tower.skillDesc}
{/* 打法建议 */}
💡 {tower.tactics}
) } // ── 费用计算:基于当前HC的15%,最低20,最高200,取整到10的倍数 ──────────── function calcPuaCost(hc: number): number { const raw = Math.ceil(hc * 0.15) const rounded = Math.ceil(raw / 10) * 10 // 向上取到整十 return Math.max(20, Math.min(200, rounded)) } // ── PUA 输入面板 ───────────────────────────────────────────────────────────── function PuaPanel({ gameReady, hc, waveStarted }: { gameReady: boolean; hc: number; waveStarted: boolean }) { const [text, setText] = useState('') const [loading, setLoading] = useState(false) const [result, setResult] = useState(null) const [history, setHistory] = useState([]) // 历史原文列表(用于发给服务端做相似度检测) const historyTexts = useRef([]) const [insufficient, setInsufficient] = useState(false) const [dupWarning, setDupWarning] = useState(null) const textareaRef = useRef(null) const placeholder = useRef(PUA_PLACEHOLDERS[Math.floor(Math.random() * PUA_PLACEHOLDERS.length)]).current const cost = calcPuaCost(hc) const canAfford = hc >= cost const handleSubmit = useCallback(async () => { if (!text.trim() || loading || !gameReady || !waveStarted) return const spendHC: ((n: number) => boolean) | undefined = typeof window !== 'undefined' ? (window as any).__gameSpendHC : undefined const currentHC: number = typeof window !== 'undefined' ? ((window as any).__gameGetHC?.() ?? hc) : hc const actualCost = calcPuaCost(currentHC) // 扣除基础费用 if (!spendHC?.(actualCost)) { setInsufficient(true) setTimeout(() => setInsufficient(false), 2000) return } setLoading(true) setResult(null) setDupWarning(null) setInsufficient(false) try { const res = await fetch('/api/pua-score', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text.trim(), history: historyTexts.current, }), }) const data: PuaResult = await res.json() data.cost = actualCost // 处理重复惩罚:额外扣 HC const penalty = data.penaltyMultiplier ?? 0 if (penalty > 0 && data.isDuplicate) { const extraCost = Math.round(actualCost * penalty) const penaltyPaid = spendHC?.(extraCost) ?? false if (penaltyPaid) { data.cost = actualCost + extraCost // 显示重复警告 const sim = data.similarity ?? 0 const level = sim >= 0.8 ? '严重重复' : '内容相似' setDupWarning( `检测到${level}!额外扣除 ${extraCost} HC` + (data.similarTo ? `\n(与"${data.similarTo}"相似)` : '') ) } } setResult(data) // 更新历史原文列表(最近8条) historyTexts.current = [text.trim(), ...historyTexts.current].slice(0, 8) setHistory(prev => [data, ...prev].slice(0, 5)) if (typeof window !== 'undefined') { ;(window as any).__gamePuaBuff?.(data.effect, data.score, data.title) } } catch { setResult({ score: 1, title: '网络故障', desc: 'AI开小差了', effect: 'backfire' }) } finally { setLoading(false) } }, [text, loading, gameReady, hc, waveStarted]) const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSubmit() } } const scoreColor = (s: number) => s >= 9 ? '#FF4E00' : s >= 7 ? '#FBBF24' : s >= 4 ? '#22C55E' : '#6B7280' return (
{/* 标题 */}
PUA 激励台
{!waveStarted ? '⚠ 召唤第一波后才能激励' : '输入打鸡血的话,AI判断鸡血值'}
{/* 费用提示 */}
激励费用
-{cost} HC {!canAfford && ( 余额不足 )}
{/* HC 不足提示 */} {insufficient && (
HC 不足!先去打怪赚钱!
)} {/* 重复惩罚警告 */} {dupWarning && (
{dupWarning}
)} {/* 输入框 */}