Files
gplan-2/game/core/action_economy.gd
T

195 lines
7.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
# Date: 2026-09-10
# 行动经济:被动账本(AP 硬重置 / 统一价目 / 三路径结束仲裁 cleared > stopped > ap_exhausted /
# RAP 池战斗域 / 双发射源之 stopped 直发)。相位域 = {Idle, Active, Waiting}。
# 实现 GDD:design/archive/gdd-2026-09-14/a3-action-economy.md(系统 ID A3·行动经济——registry 追溯锚,命名规范见 CLAUDE.md)
class_name ActionEconomy
extends RefCounted
signal round_end_stopped(payload: Dictionary) # 双发射源之停手路径(行动经济 直发;payload 与 排雷玩法 棋盘路径同构)
const PHASE_IDLE := "Idle"
const PHASE_ACTIVE := "Active"
const PHASE_WAITING := "Waiting"
# 相位轴(P6 状态机——code-review 2026-09-29 P2-2):内部 enum + match,外部 phase getter 保持
# String 面(presentation_snapshot 与冒烟链的 "Idle"/"Active"/"Waiting" 对照契约零变化)。
enum Phase { IDLE, ACTIVE, WAITING }
var db: ConfigDb
# 相位与账本只读出口(E5 封装收口 2026-09-13):外部读取经 getter-only 属性(架构文档
# "ap/rap/phase 只读" 的机制化),写入只经 notify_* / request_* 方法通道。
var _phase: int = Phase.IDLE
var phase: String:
get: return _phase_text(_phase)
var _ap := 0
var ap: int:
get: return _ap
var _rap := 0
var rap: int:
get: return _rap
var _last_round_end: Dictionary[String, String] = {} # {path_tag, continuation_hint}——排雷玩法 组包 round_end 的合法数据源
var last_round_end: Dictionary:
get: return _last_round_end
var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
func _init(db_ref: ConfigDb) -> void:
db = db_ref
func _phase_text(p: int) -> String:
match p:
Phase.ACTIVE:
return PHASE_ACTIVE
Phase.WAITING:
return PHASE_WAITING
return PHASE_IDLE
func _cost(kind: String) -> int:
var ae = db.action_economy
match kind:
"dig":
return ae.dig_ap_cost
"flag_place":
return ae.flag_place_ap_cost
"move":
return ae.move_ap_cost
"chain":
return ae.chain_ap_cost
assert(false, "未知动作价目: " + kind)
return 0
# 通用扣减(排雷玩法 用于旗动作;挖握走 request_dig——cleared 仲裁需要 clears_map 参数)
func try_spend(action_kind: String) -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
return _spend_and_arbitrate(action_kind, false)
# 排雷玩法 挖掘专用:chain_size = 连锁附加格数(不含首格);clears_map = 本次动作后全图安全格开完
func request_dig(chain_size: int, clears_map: bool) -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
return _spend_and_arbitrate("dig", clears_map)
func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool:
var cost := _cost(action_kind)
if _ap < cost:
_reject("resource_face")
return false
_ap -= cost
if clears_map:
# cleared 胜出(同帧仲裁 cleared > ap_exhausted);剩余 AP 当场清零作废、不进 RAP(EC7/EC10)
_ap = 0
_finish_round("cleared", "new_board")
elif _ap == 0:
# AP 归 0 瞬间轮次结束——不存在 0-AP 操作窗口
_finish_round("ap_exhausted", "same_board")
return true
# 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)
func request_stop() -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
_rap += _ap
_ap = 0
_finish_round("stopped", "same_board")
round_end_stopped.emit({"path_tag": "stopped", "continuation_hint": "same_board"})
return true
# 轮内刷新(2026-09-12 方案 A 用户拍板;等待期通道 2026-09-15 用户拍板整体移除):唯一入口 =
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
func request_refresh() -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
if _rap < db.action_economy.rap_refresh_cost:
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
_rap -= db.action_economy.rap_refresh_cost
return true
# 战斗结算 回发:结算完成(仅记录编排意图;phase 停留 Waiting——AC-14 门不被绕过)
func notify_settlement_done(next_round_mode: String) -> bool:
if _phase != Phase.WAITING:
_reject("phase")
return false
_settlement_consumed = true
_next_round_mode = next_round_mode
return true
# 下轮行动点修正排队(格面资源八型批 2026-09-20):action 型正负行结算期调用,同轮多次授予累加;
# 相位无关(授予发生在结算段,此时本类可能处于 ACTIVE/WAITING 任一相位)
# 第 9 API(registry 行动经济×① 接口漂移预授权——迭代 7 T2 魔法道具内容批落位):ap_grant 道具的
# 当轮行动点加量(GDD「加行动点效果给当轮」——区别于 queue_ap_delta 的下轮排队语义)。Active 受理、
# 无上限域、零随机;使用道具本身零扣费(成本在获取时已付——捡道具格不出战斗奖励)。
func grant_ap(amount: int) -> bool:
if amount <= 0 or _phase != Phase.ACTIVE:
_reject("grant_ap")
return false
_ap += amount
return true
func queue_ap_delta(delta: int) -> void:
_pending_ap_delta += delta
func pending_ap_delta() -> int:
return _pending_ap_delta
func notify_round_start() -> bool:
if _phase == Phase.IDLE:
pass # 战斗首轮唯一出口
elif _phase == Phase.WAITING and _settlement_consumed:
pass
else:
_reject("phase")
return false
_ap = maxi(0, db.action_economy.ap_per_round + _pending_ap_delta) # 每轮基线 + 排队修正(下限 0)
_pending_ap_delta = 0
_phase = Phase.ACTIVE
_settlement_consumed = false
return true
func notify_combat_end() -> bool:
if _phase != Phase.WAITING:
_reject("phase")
return false
_phase = Phase.IDLE
_rap = 0 # RAP 战斗域清零(用户拍板;HUD框架可见反馈义务)
_ap = 0
return true
func _finish_round(path_tag: String, continuation_hint: String) -> void:
# 实现层断言兜底(行动经济 轮次结束仲裁表):耗尽轮剩余恒为 0;清图轮剩余已当场清零(不进储备池)。
# 两断言守在唯一仲裁出口,未来若在扣费与仲裁之间挪动 AP 写入在此红灯。
assert(path_tag != "ap_exhausted" or _ap == 0, "耗尽轮剩余行动点恒为 0(实现层断言兜底)")
assert(path_tag != "cleared" or _ap == 0, "清图轮剩余行动点当场清零(不进储备池)")
_last_round_end = {"path_tag": path_tag, "continuation_hint": continuation_hint}
_phase = Phase.WAITING
func _reject(source: String) -> void:
if source == "phase":
push_warning("[行动经济] 动作被拒(%s 相位守卫/资源)" % source)
else:
push_warning("[行动经济] 资源不足拒绝")