feat(game): PUA激励台增加HC消耗机制,费用=当前HC×15%,余额不足时禁用,防止无限激励破坏平衡

This commit is contained in:
Cloud Bot
2026-03-24 08:22:21 +00:00
parent adda7ae57c
commit d7253c45be
2 changed files with 137 additions and 28 deletions

View File

@@ -21,6 +21,7 @@ interface PuaResult {
title: string
desc: string
effect: EffectType
cost?: number // 实际扣除的 HC前端填写
}
const EFFECT_META: Record<EffectType, { label: string; color: string; icon: string }> = {
@@ -39,26 +40,57 @@ const PUA_PLACEHOLDERS = [
'大家加油,相信自己!',
]
// ── 费用计算基于当前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 }: { gameReady: boolean }) {
function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
const [text, setText] = useState('')
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<PuaResult | null>(null)
const [history, setHistory] = useState<PuaResult[]>([])
const [insufficient, setInsufficient] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const placeholder = PUA_PLACEHOLDERS[Math.floor(Math.random() * PUA_PLACEHOLDERS.length)]
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) return
// 先从游戏扣除 HC扣不到则拒绝
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)
setInsufficient(false)
try {
const res = await fetch('/api/pua-score', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: text.trim() }),
})
const data: PuaResult = await res.json()
const data: PuaResult & { cost?: number } = await res.json()
data.cost = actualCost
setResult(data)
setHistory(prev => [data, ...prev].slice(0, 5))
// 通知游戏场景应用 buff
@@ -70,7 +102,7 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
} finally {
setLoading(false)
}
}, [text, loading, gameReady])
}, [text, loading, gameReady, hc])
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -115,6 +147,62 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
</div>
</div>
{/* 费用提示 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#0a1628',
borderRadius: '6px',
padding: '6px 8px',
border: `1px solid ${insufficient ? '#EF4444' : canAfford ? '#1e3a5f' : '#7F1D1D'}`,
transition: 'border-color 0.2s',
}}>
<span style={{
fontFamily: 'VT323, monospace',
fontSize: '13px',
color: '#64748B',
}}>
</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{
fontFamily: "'Press Start 2P', monospace",
fontSize: '9px',
color: canAfford ? '#A78BFA' : '#EF4444',
transition: 'color 0.2s',
}}>
-{cost} HC
</span>
{!canAfford && (
<span style={{
fontFamily: 'VT323, monospace',
fontSize: '11px',
color: '#EF4444',
}}>
</span>
)}
</div>
</div>
{/* HC 不足闪烁提示 */}
{insufficient && (
<div style={{
backgroundColor: '#7F1D1D',
border: '1px solid #EF4444',
borderRadius: '6px',
padding: '6px 8px',
fontFamily: 'VT323, monospace',
fontSize: '14px',
color: '#FCA5A5',
textAlign: 'center',
animation: 'none',
}}>
HC
</div>
)}
{/* 输入框 */}
<textarea
ref={textareaRef}
@@ -127,7 +215,7 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
style={{
width: '100%',
backgroundColor: '#0F1B2D',
border: '1px solid #1e3a5f',
border: `1px solid ${canAfford ? '#1e3a5f' : '#7F1D1D'}`,
borderRadius: '6px',
color: '#E2E8F0',
fontFamily: 'VT323, monospace',
@@ -137,29 +225,30 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
outline: 'none',
lineHeight: 1.4,
opacity: gameReady ? 1 : 0.5,
transition: 'border-color 0.2s',
}}
/>
{/* 提交按钮 */}
<button
onClick={handleSubmit}
disabled={loading || !text.trim() || !gameReady}
disabled={loading || !text.trim() || !gameReady || !canAfford}
style={{
width: '100%',
padding: '8px',
backgroundColor: loading ? '#1e3a5f' : '#7C3AED',
backgroundColor: loading ? '#1e3a5f' : canAfford ? '#7C3AED' : '#4C1D95',
border: 'none',
borderRadius: '6px',
color: '#E2E8F0',
fontFamily: "'Press Start 2P', monospace",
fontSize: '8px',
cursor: loading || !text.trim() || !gameReady ? 'not-allowed' : 'pointer',
opacity: !text.trim() || !gameReady ? 0.5 : 1,
cursor: loading || !text.trim() || !gameReady || !canAfford ? 'not-allowed' : 'pointer',
opacity: !text.trim() || !gameReady || !canAfford ? 0.45 : 1,
transition: 'all 0.15s',
letterSpacing: '0.5px',
}}
>
{loading ? '分析中...' : '发起激励 ▶'}
{loading ? '分析中...' : !canAfford ? 'HC不足' : `发起激励 -${cost}HC`}
</button>
{/* 当前结果 */}
@@ -209,18 +298,28 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
}}>
{result.desc}
</div>
{/* 效果标签 */}
<div style={{
backgroundColor: `${em.color}22`,
border: `1px solid ${em.color}55`,
borderRadius: '4px',
padding: '3px 6px',
fontFamily: 'VT323, monospace',
fontSize: '12px',
color: em.color,
textAlign: 'center',
}}>
{em.label}
{/* 效果标签 + 花费 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{
backgroundColor: `${em.color}22`,
border: `1px solid ${em.color}55`,
borderRadius: '4px',
padding: '3px 6px',
fontFamily: 'VT323, monospace',
fontSize: '12px',
color: em.color,
}}>
{em.label}
</div>
{result.cost && (
<span style={{
fontFamily: "'Press Start 2P', monospace",
fontSize: '7px',
color: '#475569',
}}>
-{result.cost}HC
</span>
)}
</div>
</div>
)
@@ -274,14 +373,17 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
fontFamily: 'VT323, monospace',
fontSize: '11px',
color: '#334155',
lineHeight: 1.4,
lineHeight: 1.5,
marginTop: 'auto',
borderTop: '1px solid #1e3a5f',
paddingTop: '8px',
}}>
<span style={{ color: '#475569' }}> = HC × 15%</span><br />
<span style={{ color: '#334155' }}>20 200</span><br />
<br />
Enter <br />
1-3: 废话翻车<br />
4-6分: 小幅加成<br />
1-2: 废话翻车<br />
4-6分: 攻击+HC<br />
7-8分: 攻速暴增<br />
9-10分: 全场狂暴
</div>
@@ -347,7 +449,8 @@ export default function GamePage() {
gameRef.current = null
if (typeof window !== 'undefined') {
;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower',
'__gameReady','__gameDifficulty','__gamePuaBuff'].forEach(k => {
'__gameReady','__gameDifficulty','__gamePuaBuff',
'__gameGetHC','__gameSpendHC'].forEach(k => {
delete (window as any)[k]
})
}
@@ -367,7 +470,7 @@ export default function GamePage() {
style={{ backgroundColor: '#0A1628' }}
/>
{/* PUA 激励台(右侧) */}
<PuaPanel gameReady={gameReady} />
<PuaPanel gameReady={gameReady} hc={hc} />
</div>
{/* 底部塔选择面板 */}