feat(game): PUA激励台增加HC消耗机制,费用=当前HC×15%,余额不足时禁用,防止无限激励破坏平衡
This commit is contained in:
@@ -21,6 +21,7 @@ interface PuaResult {
|
|||||||
title: string
|
title: string
|
||||||
desc: string
|
desc: string
|
||||||
effect: EffectType
|
effect: EffectType
|
||||||
|
cost?: number // 实际扣除的 HC(前端填写)
|
||||||
}
|
}
|
||||||
|
|
||||||
const EFFECT_META: Record<EffectType, { label: string; color: string; icon: string }> = {
|
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 输入面板 ─────────────────────────────────────────────────────────────
|
// ── PUA 输入面板 ─────────────────────────────────────────────────────────────
|
||||||
function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
function PuaPanel({ gameReady, hc }: { gameReady: boolean; hc: number }) {
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [result, setResult] = useState<PuaResult | null>(null)
|
const [result, setResult] = useState<PuaResult | null>(null)
|
||||||
const [history, setHistory] = useState<PuaResult[]>([])
|
const [history, setHistory] = useState<PuaResult[]>([])
|
||||||
|
const [insufficient, setInsufficient] = useState(false)
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
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 () => {
|
const handleSubmit = useCallback(async () => {
|
||||||
if (!text.trim() || loading || !gameReady) return
|
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)
|
setLoading(true)
|
||||||
setResult(null)
|
setResult(null)
|
||||||
|
setInsufficient(false)
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/pua-score', {
|
const res = await fetch('/api/pua-score', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ text: text.trim() }),
|
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)
|
setResult(data)
|
||||||
setHistory(prev => [data, ...prev].slice(0, 5))
|
setHistory(prev => [data, ...prev].slice(0, 5))
|
||||||
// 通知游戏场景应用 buff
|
// 通知游戏场景应用 buff
|
||||||
@@ -70,7 +102,7 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [text, loading, gameReady])
|
}, [text, loading, gameReady, hc])
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
@@ -115,6 +147,62 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
</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
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
@@ -127,7 +215,7 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: '#0F1B2D',
|
backgroundColor: '#0F1B2D',
|
||||||
border: '1px solid #1e3a5f',
|
border: `1px solid ${canAfford ? '#1e3a5f' : '#7F1D1D'}`,
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
color: '#E2E8F0',
|
color: '#E2E8F0',
|
||||||
fontFamily: 'VT323, monospace',
|
fontFamily: 'VT323, monospace',
|
||||||
@@ -137,29 +225,30 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
outline: 'none',
|
outline: 'none',
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
opacity: gameReady ? 1 : 0.5,
|
opacity: gameReady ? 1 : 0.5,
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 提交按钮 */}
|
{/* 提交按钮 */}
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={loading || !text.trim() || !gameReady}
|
disabled={loading || !text.trim() || !gameReady || !canAfford}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
backgroundColor: loading ? '#1e3a5f' : '#7C3AED',
|
backgroundColor: loading ? '#1e3a5f' : canAfford ? '#7C3AED' : '#4C1D95',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
color: '#E2E8F0',
|
color: '#E2E8F0',
|
||||||
fontFamily: "'Press Start 2P', monospace",
|
fontFamily: "'Press Start 2P', monospace",
|
||||||
fontSize: '8px',
|
fontSize: '8px',
|
||||||
cursor: loading || !text.trim() || !gameReady ? 'not-allowed' : 'pointer',
|
cursor: loading || !text.trim() || !gameReady || !canAfford ? 'not-allowed' : 'pointer',
|
||||||
opacity: !text.trim() || !gameReady ? 0.5 : 1,
|
opacity: !text.trim() || !gameReady || !canAfford ? 0.45 : 1,
|
||||||
transition: 'all 0.15s',
|
transition: 'all 0.15s',
|
||||||
letterSpacing: '0.5px',
|
letterSpacing: '0.5px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '分析中...' : '发起激励 ▶'}
|
{loading ? '分析中...' : !canAfford ? 'HC不足' : `发起激励 -${cost}HC`}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 当前结果 */}
|
{/* 当前结果 */}
|
||||||
@@ -209,7 +298,8 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
}}>
|
}}>
|
||||||
{result.desc}
|
{result.desc}
|
||||||
</div>
|
</div>
|
||||||
{/* 效果标签 */}
|
{/* 效果标签 + 花费 */}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
backgroundColor: `${em.color}22`,
|
backgroundColor: `${em.color}22`,
|
||||||
border: `1px solid ${em.color}55`,
|
border: `1px solid ${em.color}55`,
|
||||||
@@ -218,10 +308,19 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
fontFamily: 'VT323, monospace',
|
fontFamily: 'VT323, monospace',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: em.color,
|
color: em.color,
|
||||||
textAlign: 'center',
|
|
||||||
}}>
|
}}>
|
||||||
{em.label}
|
{em.label}
|
||||||
</div>
|
</div>
|
||||||
|
{result.cost && (
|
||||||
|
<span style={{
|
||||||
|
fontFamily: "'Press Start 2P', monospace",
|
||||||
|
fontSize: '7px',
|
||||||
|
color: '#475569',
|
||||||
|
}}>
|
||||||
|
-{result.cost}HC
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
@@ -274,14 +373,17 @@ function PuaPanel({ gameReady }: { gameReady: boolean }) {
|
|||||||
fontFamily: 'VT323, monospace',
|
fontFamily: 'VT323, monospace',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
color: '#334155',
|
color: '#334155',
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.5,
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
borderTop: '1px solid #1e3a5f',
|
borderTop: '1px solid #1e3a5f',
|
||||||
paddingTop: '8px',
|
paddingTop: '8px',
|
||||||
}}>
|
}}>
|
||||||
|
<span style={{ color: '#475569' }}>费用 = 当前HC × 15%</span><br />
|
||||||
|
<span style={{ color: '#334155' }}>越富越贵,最低20 最高200</span><br />
|
||||||
|
──────────<br />
|
||||||
Enter 快速提交<br />
|
Enter 快速提交<br />
|
||||||
1-3分: 废话翻车<br />
|
1-2分: 废话翻车<br />
|
||||||
4-6分: 小幅加成<br />
|
4-6分: 攻击+HC<br />
|
||||||
7-8分: 攻速暴增<br />
|
7-8分: 攻速暴增<br />
|
||||||
9-10分: 全场狂暴
|
9-10分: 全场狂暴
|
||||||
</div>
|
</div>
|
||||||
@@ -347,7 +449,8 @@ export default function GamePage() {
|
|||||||
gameRef.current = null
|
gameRef.current = null
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower',
|
;['__gameOnHCChange','__gameOnTowerDeselect','__gameSelectTower',
|
||||||
'__gameReady','__gameDifficulty','__gamePuaBuff'].forEach(k => {
|
'__gameReady','__gameDifficulty','__gamePuaBuff',
|
||||||
|
'__gameGetHC','__gameSpendHC'].forEach(k => {
|
||||||
delete (window as any)[k]
|
delete (window as any)[k]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -367,7 +470,7 @@ export default function GamePage() {
|
|||||||
style={{ backgroundColor: '#0A1628' }}
|
style={{ backgroundColor: '#0A1628' }}
|
||||||
/>
|
/>
|
||||||
{/* PUA 激励台(右侧) */}
|
{/* PUA 激励台(右侧) */}
|
||||||
<PuaPanel gameReady={gameReady} />
|
<PuaPanel gameReady={gameReady} hc={hc} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 底部塔选择面板 */}
|
{/* 底部塔选择面板 */}
|
||||||
|
|||||||
@@ -124,13 +124,19 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
audio.startBGM()
|
audio.startBGM()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 注册 PUA buff 接口供 React 层调用
|
// 注册 PUA buff 接口 + HC 查询/扣除接口供 React 层调用
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
;(window as any).__gamePuaBuff = (
|
;(window as any).__gamePuaBuff = (
|
||||||
effect: string, score: number, title: string
|
effect: string, score: number, title: string
|
||||||
) => {
|
) => {
|
||||||
this.applyPuaBuff(effect, score, title)
|
this.applyPuaBuff(effect, score, title)
|
||||||
}
|
}
|
||||||
|
// 查询当前 HC
|
||||||
|
;(window as any).__gameGetHC = () => this.manager.hc
|
||||||
|
// 尝试扣除 HC,成功返回 true,不足返回 false
|
||||||
|
;(window as any).__gameSpendHC = (amount: number): boolean => {
|
||||||
|
return this.manager.spendHC(amount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupInteraction()
|
this.setupInteraction()
|
||||||
|
|||||||
Reference in New Issue
Block a user