feat(迭代27 T5): 波五a 类型闸拉起+存量收口——审计器 0 存量+warning 配置在位
P1-6 类型闸(须 T1-T4 全落后——本波按执行顺序锁殿后): - tools/audit_type_hints.py 新机器闸(V7): R1 无类型容器注解/R2 有值返回缺返回类型/ R3 参数缺类型三规则+行级 # type-hint: ignore 显式白名单通道;产品码 core/data/presentation - 存量收口: 368 处显式 Array/Dictionary 非参数化注解→机械参数化(Array[Variant]/ Dictionary[Variant, Variant] 零行为面);13 处参数真类型(SaveManagerCore/ConfigDb/ BattleOrchestrator/MinesweeperRules/LevelRunner/SettingsStore 等) - 白名单豁免四处: board_generator generate/_attempt/legality/_finalize 的 db 参—— 测量面 _BoardRowStub 冒烟夹具鸭子型注入(typed 参拒 stub) - project.godot 拉起 gdscript/warnings/untyped_declaration=1(V7 源码断言在 smoke _smoke_type_gate) - static_scan 新 S9 类型闸规则;顺修历史 cd 错位(9-24 布局迁移后 /.. 落 game/ 致守卫 恒跳过静默空转——修复后 S1-S4/S9 首次实跑全绿)+S1/S2 增 ADR-0007-exempt 标记豁免通道 (main _seed_value 引擎熵源=文档化豁免面) 验收: 冒烟 1081/0(+V7×2)+run_all 27 套+static_scan 全绿+probe click+render golden+对拍三变体全同
This commit is contained in:
1 parent
8b9e55673f
commit
b605022001
58 files changed
+503
-384
No files matched your search
@@ -30,7 +30,7 @@ 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:
|
||||
var last_round_end: Dictionary[Variant, Variant]:
|
||||
get: return _last_round_end
|
||||
var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start
|
||||
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
|
||||
|
||||
@@ -75,7 +75,7 @@ var _base_override := {"atk": 0, "max_hp": 0, "armor_class": 0}
|
||||
var _first_script := false # 首次脚本模式(关卡节点图生成 v0.4——true = 全关零抽签取池内模板 0;测量夹具恒 false) # 永久强化 flat 注入(元进度实现批——冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板)
|
||||
var _last_treasure_cleared := 0 # additive(迭代 7 T4——最近一次关卡末清空计数,summary 取数面)
|
||||
var _treasure_unlocked := {} # aux 解锁门控集(迭代 7 T5——params.treasure_unlocked 接收面)
|
||||
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||
var _attunement: Array[Variant] = [] # 同调装配集(迭代 11 第①批 T5——params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||
var _attune_refresh_x := 0 # 刷新件余量(迭代 11 第①批 T5b P1 甲——每碰三选一重置=X 表值;优先消耗、不结转)
|
||||
var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值)
|
||||
var _curio_paid_refreshes := 0 # 本次停点已付费刷新次数(2026-10-06 用户令对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构)
|
||||
@@ -89,12 +89,12 @@ var _save_manager = null # 断点承载注入(迭代 8 卡①
|
||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||
# ---- 三选一停点态(迭代 10 奖励重构批)----
|
||||
var _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck)
|
||||
var _curio_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
||||
var _curio_deck: Array[Variant] = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
||||
var _feats: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
|
||||
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||
var _tree_rules: Array[Variant] = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||
var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
|
||||
var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付)
|
||||
var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源)
|
||||
var _keystone_drops: Array[Variant] = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源)
|
||||
var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||||
var _feat_ticket_pity := 0 # 门票保底计数(T5——展示次数:节点开面/货架抽签/刷新重展各计一次;触发清零)
|
||||
var _feat_ticket_sold := 0 # 门票限购计数(T5——选中/买下即 +1;拿到即开不进持有集)
|
||||
@@ -141,12 +141,12 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
# 刷新练习开局注入(迭代 22 秘宝批 T3——refresh_stock_grant 装配即得一次性加仓:
|
||||
# 装配集 kind 分发扫描;只入存量账 _refresh_stock、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
|
||||
for iid_v2 in _attunement:
|
||||
var rp_row: Dictionary = db.treasure_item_row(String(iid_v2))
|
||||
var rp_row: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v2))
|
||||
if (rp_row.get("effect_kinds", []) as Array).has("refresh_stock_grant"):
|
||||
_refresh_stock += int(rp_row.get("magnitude", 0))
|
||||
# 特性开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
|
||||
# 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕)
|
||||
var feat_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
|
||||
var feat_seed: Array[Variant] = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # 迭代 12 T10——A/B 面 rule 键集
|
||||
if not feat_seed.is_empty():
|
||||
_feats = []
|
||||
@@ -179,7 +179,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
master_seed = int(bp.get("master_seed", 0))
|
||||
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
|
||||
ledger = {"hero_hp": int(bp.get("hero_hp", 0)), "gp": int(bp.get("gp", 0))}
|
||||
var bp_bonus: Dictionary = bp.get("bonus", {}) as Dictionary
|
||||
var bp_bonus: Dictionary[Variant, Variant] = bp.get("bonus", {}) as Dictionary
|
||||
run_ledger.bonus = {"atk": maxi(0, int(bp_bonus.get("atk", 0))),
|
||||
"max_hp": maxi(0, int(bp_bonus.get("max_hp", 0))),
|
||||
"armor_class": maxi(0, int(bp_bonus.get("armor_class", 0))),
|
||||
@@ -191,7 +191,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
level_index = int(bp.get("level_index", 1))
|
||||
_pending_next_level = false
|
||||
_reset_curio_state()
|
||||
var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary
|
||||
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
|
||||
for id in _curio_taken.keys():
|
||||
_curio_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||
for id in bp_taken.keys():
|
||||
@@ -200,9 +200,9 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
# 迭代 12 T1——relic 令牌 feat_id 随还原)
|
||||
var bp_deck = bp.get("bonus_deck", null)
|
||||
if bp_deck is Array:
|
||||
var restored: Array = []
|
||||
var restored: Array[Variant] = []
|
||||
for t in (bp_deck as Array):
|
||||
var tok: Dictionary = t as Dictionary
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
|
||||
if String(rtok["kind"]) == "relic":
|
||||
rtok["feat_id"] = String(tok.get("feat_id", ""))
|
||||
@@ -231,7 +231,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # 迭代 12 T10——A/B 面 rule 键集(尾裁微批b 2026-10-08 卡⓪受理闭环:续玩参数面同 start_adventure :135 同式,消费面 set_tree_rules 下传现成零改)
|
||||
# 三选一停点续玩分支(迭代 10——选择态/已兑付态不重跑关卡;奖励选择态不重抽、已兑付不重付)
|
||||
var bp_pending: Dictionary = bp.get("bonus_pending", {}) as Dictionary
|
||||
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
|
||||
_feat_ticket_pity = int(bp.get("feat_ticket_pity", 0)) # T5 扩列——旧档缺键缺省 0(存档兼容)
|
||||
_feat_ticket_sold = int(bp.get("feat_ticket_sold", 0))
|
||||
# 迭代 20 T6/T7——adventure 域计数器回填(缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先)
|
||||
@@ -241,11 +241,11 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
_refresh_stock = maxi(0, int(bp.get("refresh_stock", 0))) # 迭代 20 T4/T7——旧档缺键缺省 0
|
||||
run_ledger.vision_counter = {"used": maxi(0, int(bp.get("vision_counter_used", 0)))} # 迭代 22 秘宝批 T3——真视计数随断点回填(缺键=旧断点缺省 0)
|
||||
_battery_pending = maxi(0, int(bp.get("battery_pending", 0)))
|
||||
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
||||
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
|
||||
if not bp_pending.is_empty():
|
||||
_pending_next_level = true
|
||||
# 候选令牌化迁移(TP0——旧版断点候选为 kind 字符串,按普通档令牌防御归一;单版本周期口径)
|
||||
var migrated: Array = []
|
||||
var migrated: Array[Variant] = []
|
||||
for c in (bp_pending.get("candidates", []) as Array):
|
||||
if c is Dictionary:
|
||||
# T4——feat_id 随迁移(通关渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要)
|
||||
@@ -256,7 +256,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
else:
|
||||
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
|
||||
_curio_pending = {"candidates": migrated}
|
||||
var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
var payload: Dictionary[Variant, Variant] = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
state = "running"
|
||||
curio_choice_presented.emit(payload)
|
||||
return true
|
||||
@@ -299,7 +299,7 @@ func _reset_curio_state() -> void:
|
||||
# 回退映射 rarest→rare/其余→excellent,T3 行换代后回退分支自然休眠);relic 入普通档随新档位框架
|
||||
# 解禁〔旧「永不入普通档」系 TP0 三档时代约束,四级框架未沿用——奇物.md 池结构节无此限制〕)
|
||||
func _build_curio_deck() -> Array:
|
||||
var deck: Array = []
|
||||
var deck: Array[Variant] = []
|
||||
for kind in db.adventure.CURIO_KINDS:
|
||||
for tier in db.adventure.TIERS:
|
||||
for i in int(db.adventure.curio_quota(String(kind), String(tier))):
|
||||
@@ -335,7 +335,7 @@ func effective_max_hp() -> int:
|
||||
|
||||
|
||||
# 断点承载注入(迭代 8 卡①——组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
|
||||
func set_save_manager(sm) -> void:
|
||||
func set_save_manager(sm: SaveManagerCore) -> void:
|
||||
_save_manager = sm
|
||||
|
||||
|
||||
@@ -382,7 +382,7 @@ func skip_empty_curio() -> bool:
|
||||
# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 feat_id;迭代 12 T1 规则改写型特性带)
|
||||
func _find_token(candidates: Array, kind: String, tier: String, feat_id: String = "") -> Dictionary:
|
||||
for t in candidates:
|
||||
var tok: Dictionary = t as Dictionary
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
||||
continue
|
||||
if kind == "relic" and String(tok.get("feat_id", "")) != feat_id:
|
||||
@@ -399,7 +399,7 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||
if state != "running" or _curio_pending.is_empty():
|
||||
push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state)
|
||||
return false
|
||||
var candidates: Array = _curio_pending.get("candidates", []) as Array
|
||||
var candidates: Array[Variant] = _curio_pending.get("candidates", []) as Array
|
||||
if _find_token(candidates, kind, tier, feat_id).is_empty():
|
||||
push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||||
return false
|
||||
@@ -418,7 +418,7 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||
func _feat_payload_keys(feat_id: String) -> Dictionary:
|
||||
if feat_id == "":
|
||||
return {}
|
||||
var row: Dictionary = db.feat_row(feat_id)
|
||||
var row: Dictionary[Variant, Variant] = db.feat_row(feat_id)
|
||||
return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))}
|
||||
|
||||
|
||||
@@ -484,7 +484,7 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 feat_id 精确摘;找不到 = false)
|
||||
func _consume_deck_token(kind: String, tier: String, feat_id: String = "") -> bool:
|
||||
for i in _curio_deck.size():
|
||||
var tok: Dictionary = _curio_deck[i] as Dictionary
|
||||
var tok: Dictionary[Variant, Variant] = _curio_deck[i] as Dictionary
|
||||
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
||||
continue
|
||||
if kind == "relic" and String(tok.get("feat_id", "")) != feat_id:
|
||||
@@ -556,7 +556,7 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String
|
||||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||||
func node_curio_candidates() -> Dictionary:
|
||||
var cands: Array = _draw_curio_candidates("reward_node")
|
||||
var cands: Array[Variant] = _draw_curio_candidates("reward_node")
|
||||
return {"candidates": cands}
|
||||
|
||||
|
||||
@@ -573,7 +573,7 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") ->
|
||||
if _node_curio_pending.is_empty():
|
||||
push_error("[冒险结构] apply_node_curio 拒绝——无待选停点")
|
||||
return false
|
||||
var candidates: Array = _node_curio_pending.get("candidates", []) as Array
|
||||
var candidates: Array[Variant] = _node_curio_pending.get("candidates", []) as Array
|
||||
if _find_token(candidates, kind, tier, feat_id).is_empty():
|
||||
push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||||
return false
|
||||
@@ -589,7 +589,7 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") ->
|
||||
func _seed_attune_charges() -> void:
|
||||
run_ledger.attune_charges = {}
|
||||
for iid in _attunement:
|
||||
var arow: Dictionary = db.treasure_item_row(String(iid))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid))
|
||||
if String(arow.get("form", "")) == "active_charged":
|
||||
run_ledger.attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0)))
|
||||
|
||||
@@ -603,7 +603,7 @@ func _attune_refresh_x_total() -> int:
|
||||
# 的行 magnitude 合计;刷新件类加件零 id 分支)
|
||||
var total := 0
|
||||
for iid_v in _attunement:
|
||||
var arow: Dictionary = db.treasure_item_row(String(iid_v))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
|
||||
if (arow.get("effect_kinds", []) as Array).has("bonus_refresh"):
|
||||
total += int(arow.get("magnitude", 0))
|
||||
return total
|
||||
@@ -632,7 +632,7 @@ func choose_battery_target(item_id: String) -> bool:
|
||||
if not _attunement.has(item_id):
|
||||
push_error("[冒险结构] 电池分配拒绝——件不在装配集: %s" % item_id)
|
||||
return false
|
||||
var arow: Dictionary = db.treasure_item_row(item_id)
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(item_id)
|
||||
if String(arow.get("form", "")) != "active_charged":
|
||||
push_error("[冒险结构] 电池分配拒绝——非主动充能件: %s" % item_id)
|
||||
return false
|
||||
@@ -693,16 +693,16 @@ func _refresh_curio_common(paid: bool) -> bool:
|
||||
else:
|
||||
_refresh_stock -= 1
|
||||
if not _curio_pending.is_empty():
|
||||
var cands: Array = _draw_curio_candidates("level_clear")
|
||||
var cands: Array[Variant] = _draw_curio_candidates("level_clear")
|
||||
_curio_pending["candidates"] = cands.duplicate(true)
|
||||
var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
var payload: Dictionary[Variant, Variant] = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
payload["candidates"] = cands
|
||||
payload["refreshed"] = true # 重发载荷带标——呈现层直落选择面,不再重开胜利总结面(2026-10-06)
|
||||
curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||
_snapshot_breakpoint()
|
||||
return true
|
||||
if not _node_curio_pending.is_empty():
|
||||
var cands2: Array = _draw_curio_candidates("reward_node")
|
||||
var cands2: Array[Variant] = _draw_curio_candidates("reward_node")
|
||||
_node_curio_pending["candidates"] = cands2.duplicate(true)
|
||||
return true
|
||||
return false
|
||||
@@ -743,14 +743,14 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array:
|
||||
var k := String((tok as Dictionary).get("kind", ""))
|
||||
return k != "relic" and k != "feat_ticket")
|
||||
pool = pool.filter(func(tok):
|
||||
var t: Dictionary = tok as Dictionary
|
||||
var t: Dictionary[Variant, Variant] = tok as Dictionary
|
||||
return _curio_cap_ok(String(t.get("kind", "")), String(t.get("feat_id", ""))))
|
||||
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
||||
if channel == "level_clear":
|
||||
# 迭代 20 T5——Boss 渠道按档配比加权抽取(无放回加权抽:逐席按 boss_feat_weight 掷点,
|
||||
# 身份去重照旧;rarity 语义约束兑付面——奇物.md v3.1,配比数值归调参批)
|
||||
var out_w: Array = []
|
||||
var seen_w: Array = []
|
||||
var out_w: Array[Variant] = []
|
||||
var seen_w: Array[Variant] = []
|
||||
var want_w := int(db.adventure.curio_candidate_count)
|
||||
while out_w.size() < want_w and not pool.is_empty():
|
||||
var total_w := 0
|
||||
@@ -765,7 +765,7 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array:
|
||||
if roll_w <= 0:
|
||||
pick_i = i_w
|
||||
break
|
||||
var tok_p: Dictionary = pool[pick_i]
|
||||
var tok_p: Dictionary[Variant, Variant] = pool[pick_i]
|
||||
var ident_p := _token_identity(tok_p)
|
||||
if seen_w.has(ident_p):
|
||||
pool.remove_at(pick_i) # 同身份已出——剔牌重掷(无放回)
|
||||
@@ -788,13 +788,13 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array:
|
||||
var tmp2 = sub[i]
|
||||
sub[i] = sub[j2]
|
||||
sub[j2] = tmp2
|
||||
var out: Array = []
|
||||
var seen: Array = []
|
||||
var out: Array[Variant] = []
|
||||
var seen: Array[Variant] = []
|
||||
# 收集器:全池序取 n_full 格(普通档打底)→ 子池序取 1 格(曝光槽)→ 子池空回退全池续取
|
||||
var want := int(db.adventure.curio_candidate_count)
|
||||
var n_full := maxi(1, want - 1) if want >= 2 else want
|
||||
while out.size() < want:
|
||||
var src: Array = pool
|
||||
var src: Array[Variant] = pool
|
||||
if out.size() >= n_full and not sub.is_empty():
|
||||
src = sub # 曝光槽位——优先子池
|
||||
var placed := false
|
||||
@@ -825,7 +825,7 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array:
|
||||
func _curio_cap_ok(kind: String, feat_id: String = "") -> bool:
|
||||
var cap := 99
|
||||
if kind == "relic":
|
||||
var row: Dictionary = db.feat_row(feat_id)
|
||||
var row: Dictionary[Variant, Variant] = db.feat_row(feat_id)
|
||||
cap = int(row.get("cap", 1)) if not row.is_empty() else 1
|
||||
return _curio_taken.get("relic:" + feat_id, 0) < cap
|
||||
if not db.adventure.CURIO_KINDS.has(kind):
|
||||
@@ -857,7 +857,7 @@ func _feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
|
||||
func _redeem_feat_ticket() -> bool:
|
||||
_feat_ticket_sold += 1
|
||||
_feat_ticket_pity = 0
|
||||
var cands: Array = _draw_curio_candidates("level_clear") # 专长池(与通关渠道同白名单)
|
||||
var cands: Array[Variant] = _draw_curio_candidates("level_clear") # 专长池(与通关渠道同白名单)
|
||||
if cands.is_empty():
|
||||
push_error("[冒险结构] 门票兑付失败——专长池空集")
|
||||
_feat_ticket_sold -= 1
|
||||
@@ -865,7 +865,7 @@ func _redeem_feat_ticket() -> bool:
|
||||
if not _node_curio_pending.is_empty():
|
||||
_node_curio_pending["candidates"] = cands.duplicate(true)
|
||||
return true
|
||||
var payload: Dictionary = {"context": "feat_ticket", "candidates": cands.duplicate(true)}
|
||||
var payload: Dictionary[Variant, Variant] = {"context": "feat_ticket", "candidates": cands.duplicate(true)}
|
||||
_curio_pending = {"candidates": cands.duplicate(true), "payload": payload.duplicate(true)}
|
||||
curio_choice_presented.emit(payload)
|
||||
_snapshot_breakpoint()
|
||||
@@ -879,7 +879,7 @@ func _boss_keystone_drop(li: int) -> void:
|
||||
var first := true
|
||||
if _save_manager != null:
|
||||
first = not _save_manager.boss_first_kill_marked(li)
|
||||
var row: Dictionary = db.meta_boss_keystone_row(li)
|
||||
var row: Dictionary[Variant, Variant] = db.meta_boss_keystone_row(li)
|
||||
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
|
||||
_keystone_drops.append({"level": li, "first": first, "amount": amount})
|
||||
keystone += amount
|
||||
@@ -922,7 +922,7 @@ func _start_current_level() -> bool:
|
||||
_runner.set_hero_override(int(_override[0]), int(_override[1]))
|
||||
# 断点续玩分支(迭代 8 卡①):模板随快照强制(已抽模板零重抽零 RNG)、resume_state 带
|
||||
# 停点节点与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
||||
var resume_state: Dictionary = {}
|
||||
var resume_state: Dictionary[Variant, Variant] = {}
|
||||
var template_pick: int = 0 if _first_script else -1 # 首次脚本 = 每关固定模板 0 零抽签(v0.4 拍板点①);否则逐关抽签
|
||||
if not _resume_bp.is_empty():
|
||||
resume_state = {"node_id": int(_resume_bp.get("node_id", -1)),
|
||||
@@ -993,7 +993,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
_pending_next_level = true
|
||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||
_curio_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零——2026-10-06)
|
||||
var pick_payload: Dictionary = {
|
||||
var pick_payload: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长
|
||||
"heal_gained": heal,
|
||||
@@ -1067,12 +1067,12 @@ func _forward_choices_presented(candidates: Array) -> void:
|
||||
func _snapshot_breakpoint() -> void:
|
||||
if _save_manager == null or state != "running":
|
||||
return
|
||||
var treasure_snapshot: Array = []
|
||||
var treasure_snapshot: Array[Variant] = []
|
||||
if _runner != null and _runner.treasure_inventory != null:
|
||||
treasure_snapshot = _runner.treasure_inventory.snapshot_inventory()
|
||||
# 三选一停点续玩分支 _runner 为 null(关卡已收束、下一关未开)——节点域字段按缺省落档,
|
||||
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照,迭代 10)
|
||||
var bp: Dictionary = {
|
||||
var bp: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"node_id": _runner.current_node_id() if _runner != null else -1,
|
||||
"hero_hp": int(ledger.get("hero_hp", 0)),
|
||||
|
||||
@@ -83,7 +83,7 @@ var _board_seq := 0 # 板序号(本战斗第几张板
|
||||
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
|
||||
var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置)
|
||||
# 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
|
||||
var attunement: Array = [] # 装备中同调件 item_id 集
|
||||
var attunement: Array[Variant] = [] # 装备中同调件 item_id 集
|
||||
var _ledger: RunLedger = null # 共享四本账(迭代 27 T3——begin_battle 经 enc["ledger"] 持引用;缺省新账=调试面)
|
||||
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(迭代 22 T3 通用化——装配集 ap_charge_safe 行扫描;反哺批⑤ 2026-10-12 用户裁:满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
|
||||
# ---- 效果引擎扩签运行态(迭代 20 专长实装批 T1——新效果类型施加面;空订阅零扰动)----
|
||||
@@ -158,7 +158,7 @@ func _flush_pending_reveals(ri: int) -> void:
|
||||
var board: BoardData = minesweeper.current_board()
|
||||
while not _fx_pending_reveals.is_empty():
|
||||
_fx_pending_reveals.pop_back()
|
||||
var cands: Array = []
|
||||
var cands: Array[Variant] = []
|
||||
for c in board.all_cells():
|
||||
if not minesweeper.is_revealed(c) and not board.is_mine(c):
|
||||
cands.append(c)
|
||||
@@ -201,7 +201,7 @@ func _fx_fact(fact: String):
|
||||
hits_n += 1
|
||||
return hits_n
|
||||
"hp_ratio":
|
||||
var hu: Dictionary = _enemy_system.hero_unit()
|
||||
var hu: Dictionary[Variant, Variant] = _enemy_system.hero_unit()
|
||||
return int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))))) # 百分比 int(0-100——when lte 算子 int 截断比较口径)
|
||||
return null
|
||||
|
||||
@@ -228,7 +228,7 @@ func _attune_row(item_id: String) -> Dictionary:
|
||||
func attune_param_sum(kind: String) -> int:
|
||||
var total := 0
|
||||
for iid_v in attunement:
|
||||
var arow: Dictionary = db.treasure_item_row(String(iid_v))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
|
||||
if (arow.get("effect_kinds", []) as Array).has(kind):
|
||||
total += int(arow.get("magnitude", 0))
|
||||
return total
|
||||
@@ -238,7 +238,7 @@ func attune_param_sum(kind: String) -> int:
|
||||
# 首行语义=装配集内至多一件积点制〔新目录恰试炼镐〕)
|
||||
func _attune_charge_safe_row() -> Dictionary:
|
||||
for iid_v in attunement:
|
||||
var arow: Dictionary = db.treasure_item_row(String(iid_v))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
|
||||
if (arow.get("effect_kinds", []) as Array).has("ap_charge_safe"):
|
||||
return arow
|
||||
return {}
|
||||
@@ -250,7 +250,7 @@ func _attune_charge_safe_row() -> Dictionary:
|
||||
func _reseed_attune_charges(reset: String) -> void:
|
||||
for iid_v in attunement:
|
||||
var iid := String(iid_v)
|
||||
var arow: Dictionary = db.treasure_item_row(iid)
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(iid)
|
||||
if String(arow.get("form", "")) != "active_charged":
|
||||
continue
|
||||
if String(arow.get("charge_reset", "adventure")) != reset:
|
||||
@@ -342,12 +342,12 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# 开流条件、消费序、消费次数逐位一致——零内容期随机流零变化;区间宽度为零的行不掷 = 「定值不骰」
|
||||
# 既有口径;atk 区间逐行同构 = 行值语义完整,代拍板随批留痕:属「每行一骰」预批准语义内的
|
||||
# 行字段区间骰逐行化,非新增随机源形态)。
|
||||
var erowset_pre: Array = db.resolve_enemy_rowset(int(enc.level_index), int(enc.node_depth))
|
||||
var erowset_pre: Array[Variant] = db.resolve_enemy_rowset(int(enc.level_index), int(enc.node_depth))
|
||||
if erowset_pre.is_empty():
|
||||
_fatal("敌人表 行集解析为空(%d,%d)" % [int(enc.level_index), int(enc.node_depth)])
|
||||
return false
|
||||
var count_overrides: Array = []
|
||||
var atk_overrides: Array = []
|
||||
var count_overrides: Array[Variant] = []
|
||||
var atk_overrides: Array[Variant] = []
|
||||
var needs_count := false
|
||||
var needs_atk := false
|
||||
for r in erowset_pre:
|
||||
@@ -397,7 +397,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
_ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面
|
||||
_damage.fx_reveal_request = Callable(self, "_fx_enqueue_reveal")
|
||||
_damage.fx_ac_request = Callable(self, "_apply_ac_bonus")
|
||||
var feat_ids: Array = enc.get("feats", []) as Array
|
||||
var feat_ids: Array[Variant] = enc.get("feats", []) as Array
|
||||
# 迭代 20 T3 换代(V14 旧实现摘除)——流派档位质变随重设计退役(奇物.md v1「不设流派标签、
|
||||
# 不设档位机制」):FeatsEngine 求值与 rider_tier 注入拆除,散件单体效果=唯一形态
|
||||
_damage.rider_tier_bonus = 0
|
||||
@@ -406,7 +406,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# 树 rule 键消费(迭代 12 T10 → 内容数据驱动扩展批 T3 换代:Fork 激活侧效果=效果引擎合成
|
||||
# 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1),旧直调拆除。其余 rule 键
|
||||
# =stat 面投影不入战斗(T8 表内容 provisional 口径披露))
|
||||
var tree_rules: Array = enc.get("tree_rules", []) as Array
|
||||
var tree_rules: Array[Variant] = enc.get("tree_rules", []) as Array
|
||||
# 效果引擎订阅重建(内容数据驱动扩展批 T1——每战从持有集/装备集重建,中途获得下一战生效;
|
||||
# 树规则合成订阅同面装载。装载序=feats 表序→treasure 表序→树规则,写死保种子复放。
|
||||
# 迭代 20 T6——adventure 域计数器从共享引用回灌〔跨战持久〕)
|
||||
@@ -466,7 +466,7 @@ func _install_new_board(during_round := false) -> bool:
|
||||
# boss/elite 图命中全局型 → 写 board_variants(_place_variants 见非空即跳 region=替换制);
|
||||
# 全局池空 → 板级字段留空(_place_variants 原路走 region=兜底回落合法态)。
|
||||
_resolve_global_variant(board)
|
||||
var res: Dictionary = minesweeper.install_board(board, during_round)
|
||||
var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round)
|
||||
if not res.accepted:
|
||||
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
|
||||
return false
|
||||
@@ -507,7 +507,7 @@ func _install_new_board(during_round := false) -> bool:
|
||||
func _resolve_global_variant(board: BoardData) -> void:
|
||||
board.board_variants.clear()
|
||||
_reward_economy.board_skew = 0
|
||||
var row: Dictionary = db.global_variant_row(int(encounter.level_index), String(board.board_type),
|
||||
var row: Dictionary[Variant, Variant] = db.global_variant_row(int(encounter.level_index), String(board.board_type),
|
||||
String(encounter.get("node_type", "")))
|
||||
if row.is_empty():
|
||||
return
|
||||
@@ -529,7 +529,7 @@ func _resolve_global_variant(board: BoardData) -> void:
|
||||
func _place_variants(board: BoardData) -> void:
|
||||
if not board.board_variants.is_empty():
|
||||
return
|
||||
var pool: Array = db.variant_pool(int(encounter.level_index), String(board.board_type))
|
||||
var pool: Array[Variant] = db.variant_pool(int(encounter.level_index), String(board.board_type))
|
||||
if pool.is_empty():
|
||||
return
|
||||
var r0 := {}
|
||||
@@ -542,7 +542,7 @@ func _place_variants(board: BoardData) -> void:
|
||||
if candidates.is_empty():
|
||||
return
|
||||
var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次)
|
||||
var vrow: Dictionary = pool[stream.randi_range(0, pool.size() - 1)]
|
||||
var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)]
|
||||
var total: int = int(round(float(candidates.size()) * float(db.variation.percent_of_cells) / 100.0)) \
|
||||
+ (int(encounter.level_index) - 1) * int(db.variation.per_level_bonus)
|
||||
total = mini(total, candidates.size())
|
||||
@@ -560,7 +560,7 @@ func _place_variants(board: BoardData) -> void:
|
||||
break
|
||||
var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)]
|
||||
placed[seed_c] = true
|
||||
var pack: Array = [seed_c]
|
||||
var pack: Array[Variant] = [seed_c]
|
||||
var pack_set := {seed_c: true}
|
||||
while pack.size() < pack_size:
|
||||
var fringe: Array[Vector2i] = []
|
||||
@@ -602,7 +602,7 @@ func _on_cell_revealed(evt: Dictionary) -> void:
|
||||
# 先兑再扣,轮次不因本格结束;授予段已无积点段(本段唯一计数点)。fx_reveal 旁路不计数=
|
||||
# 沿现役缓冲路径口径〔天眼开格不计积点〕。grant_ap 相位防御在 ACTIVE 挖格链内恒过)
|
||||
if String(evt.get("kind", "")) == "safe":
|
||||
var cs_row: Dictionary = _attune_charge_safe_row()
|
||||
var cs_row: Dictionary[Variant, Variant] = _attune_charge_safe_row()
|
||||
if not cs_row.is_empty():
|
||||
_attune_safe_seen += 1
|
||||
var cn_b: int = int(cs_row.get("charge_n", 999))
|
||||
@@ -664,7 +664,7 @@ func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int
|
||||
return snap
|
||||
if not positive and neg_m <= 0:
|
||||
return snap
|
||||
var out: Dictionary = snap.duplicate()
|
||||
var out: Dictionary[Variant, Variant] = snap.duplicate()
|
||||
var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power)))
|
||||
var pct := pos_m if positive else neg_m
|
||||
if positive and combo_cap > 0:
|
||||
@@ -676,10 +676,10 @@ func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int
|
||||
# 伤害型变异过滤(terrain_kind fact 取数面——variation 表 effect=flat_damage 判据;
|
||||
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型(hide_number 族)永不出现在本集)
|
||||
func _harmful_variants(vids: Array) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for vid_v in vids:
|
||||
var vid := String(vid_v)
|
||||
var vrow: Dictionary = db.variation.row_by_id(vid)
|
||||
var vrow: Dictionary[Variant, Variant] = db.variation.row_by_id(vid)
|
||||
if String(vrow.get("effect", "")) == "flat_damage":
|
||||
out.append(vid)
|
||||
return out
|
||||
@@ -688,7 +688,7 @@ func _harmful_variants(vids: Array) -> Array:
|
||||
# 邻接伤害型变异收集(terrain_kind fact 取数面——flag_hit 发射点用:标中旗相邻八格的伤害型变异集;
|
||||
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集)
|
||||
func _harmful_adjacent(cell: Vector2i) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
var board: BoardData = minesweeper.current_board()
|
||||
if board == null:
|
||||
return out
|
||||
@@ -699,7 +699,7 @@ func _harmful_adjacent(cell: Vector2i) -> Array:
|
||||
|
||||
# 效果命中过滤(按效果类型取命中子集——发射点分桶施加面)
|
||||
func _hits_of(hits: Array, kind: String) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for h in hits:
|
||||
if String((h as Dictionary).get("kind", "")) == kind:
|
||||
out.append(h)
|
||||
@@ -725,7 +725,7 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
var board: MinesweeperRules = minesweeper # 恒等包装退役(2026-10-02 修复批——use_treasure 直引公有成员)
|
||||
# 板维度读数(迭代 22 T3 修——Rules 无 width 代理〔潜伏缺陷:旧目录期零直呼覆盖掩盖〕,读 BoardData 本体)
|
||||
var board_b: BoardData = board.current_board()
|
||||
var v: Dictionary = treasure_inventory.validate_use(handle, target,
|
||||
var v: Dictionary[Variant, Variant] = treasure_inventory.validate_use(handle, target,
|
||||
Vector2(board_b.width, board_b.height),
|
||||
func(c: Vector2i): return not board.is_revealed(c))
|
||||
if not bool(v.ok):
|
||||
@@ -736,10 +736,10 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
for h in tu_hits:
|
||||
if String(h.get("kind", "")) == "counter_spend":
|
||||
if not _fx.counter_spend(String(h.get("counter_id", "")), int(h.get("value", 1))):
|
||||
var vrow: Dictionary = db.treasure_item_row(String(v.item_id))
|
||||
var vrow: Dictionary[Variant, Variant] = db.treasure_item_row(String(v.item_id))
|
||||
var cn_info: int = int(vrow.get("charge_n", 0)) # 迭代 22 T3——读受理行自身(旧 dig_charge 硬编码随行退役)
|
||||
return {"ok": false, "reason": "%s:安全格积点未达(%d/%d)" % [String(vrow.get("display_name", "")), _attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]}
|
||||
var kinds: Array = v.kinds
|
||||
var kinds: Array[Variant] = v.kinds
|
||||
if kinds.has("mine_reveal") or kinds.has("reveal_tiles"):
|
||||
# 真视写入(迭代 22 T3——reveal_tiles 新真视 kind 与 mine_reveal 同叠加层通道:
|
||||
# 危险格显示=信息呈现零回写,棋盘事实不可变契约 3)
|
||||
@@ -765,10 +765,10 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
# 爆破工兵包兑付(迭代 13 T8/D6——中立移除雷格:未揭示/无旗/是雷过滤归排雷玩法;
|
||||
# P1 甲邻接数字冻结零重算、清图分母零涉(雷格不入分母);空集拒绝零消耗——
|
||||
# consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现)
|
||||
var blasted: Array = board.remove_mines_in(v.resolved)
|
||||
var blasted: Array[Variant] = board.remove_mines_in(v.resolved)
|
||||
if blasted.is_empty():
|
||||
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除雷格(须未揭示且未插旗)"}
|
||||
var rec: Dictionary = treasure_inventory.consume(handle)
|
||||
var rec: Dictionary[Variant, Variant] = treasure_inventory.consume(handle)
|
||||
# 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用)
|
||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
_ledger.attune_charge_consume(String(rec.get("item_id", "")))
|
||||
@@ -788,7 +788,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
|
||||
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "", halve_pct: int = 0) -> bool:
|
||||
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 treasure_item 保留但零投放:
|
||||
# 无 aux 行入池、CURIO_KINDS 词表已除名);剩余类型走极性字典路由。
|
||||
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||
var row: Dictionary[Variant, Variant] = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||
if row.is_empty():
|
||||
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
||||
return false
|
||||
@@ -831,7 +831,7 @@ func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, r
|
||||
var eff := String(row.effect_id)
|
||||
if eff == "gp_gain" or eff == "gp_loss":
|
||||
return _apply_gp_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
var srow: Dictionary = db.status_effect_row(eff)
|
||||
var srow: Dictionary[Variant, Variant] = db.status_effect_row(eff)
|
||||
# 旗溢价贯穿(迭代14② T4——仅标中旗正向行携带 premium_pct;其余授予路径缺省 100 零扰动):
|
||||
# 应用幅度与呈现行值同乘,容器经 magnitude_override 拿同一快照(抗表重载口径保持)。
|
||||
var prem_pct: int = int(row.get("premium_pct", 100))
|
||||
@@ -980,7 +980,7 @@ func _roll_round_actions() -> void:
|
||||
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
|
||||
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
|
||||
for sid in _enemy_system.check_skill_unlocks(u):
|
||||
var srow_u: Dictionary = db.skill_row(sid)
|
||||
var srow_u: Dictionary[Variant, Variant] = db.skill_row(sid)
|
||||
_emit_row("highlight", "enemy_action", Vector2i(-1, -1), round_index,
|
||||
"%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))],
|
||||
0, {"skill_id": sid}, "skill_unlock")
|
||||
@@ -998,9 +998,9 @@ func _roll_round_actions() -> void:
|
||||
for h in eht_hits:
|
||||
if String(h.get("kind", "")) == "target_attack_bonus":
|
||||
u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0))
|
||||
var entries: Array = _enemy_system.rollable_skills(u)
|
||||
var entries: Array[Variant] = _enemy_system.rollable_skills(u)
|
||||
if _enemy_system.basic_streak(u) >= pity_m:
|
||||
var specials: Array = _enemy_system.special_skills(u)
|
||||
var specials: Array[Variant] = _enemy_system.special_skills(u)
|
||||
if not specials.is_empty():
|
||||
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
|
||||
var stream: RandomNumberGenerator = rng.open_stream("behavior_roll")
|
||||
@@ -1026,7 +1026,7 @@ func _defender_ac(unit: Dictionary) -> int:
|
||||
|
||||
func _pool_effective(effect_id: String) -> int:
|
||||
var side := "hero"
|
||||
var row: Dictionary = db.status_effect_row(effect_id)
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(effect_id)
|
||||
if not row.is_empty():
|
||||
side = String(row.target_side)
|
||||
for eff in _status_effects.query_active(side):
|
||||
@@ -1136,7 +1136,7 @@ func _battle_over(result: String) -> void:
|
||||
if result == "victory":
|
||||
for h in _fx_emit("battle_won", {"round": round_index}):
|
||||
if String(h.get("kind", "")) == "heal":
|
||||
var hu_w: Dictionary = _enemy_system.hero_unit()
|
||||
var hu_w: Dictionary[Variant, Variant] = _enemy_system.hero_unit()
|
||||
hu_w["hp"] = mini(int(hu_w.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_w.get("max_hp", 1))))
|
||||
# bonus_delta 战末汇入(迭代 20 T1——血税双向账:本战累积并入冒险加算区共享引用后清零)+
|
||||
# adventure 域计数器导出回写(T6——共享引用覆写,attune_charges 先例)
|
||||
@@ -1165,7 +1165,7 @@ func _battle_over(result: String) -> void:
|
||||
# + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。
|
||||
func _roll_gp_drop() -> int:
|
||||
var tier := "boss" if String(encounter.get("node_type", "")) == "boss" else "normal"
|
||||
var drops: Dictionary = db.shop_gp_drop_row(tier)
|
||||
var drops: Dictionary[Variant, Variant] = db.shop_gp_drop_row(tier)
|
||||
var roll := 0
|
||||
if drops.is_empty():
|
||||
push_error("[战斗结算] gp_drops 缺行 tier=%s——掉落掷定按 0 兑付" % tier)
|
||||
@@ -1190,18 +1190,18 @@ func _roll_gp_drop() -> int:
|
||||
# 八型批 2026-09-20:ambush_layers 拆除——伏击退役,警觉并入 effects 投影)hero_effects[](增益状态 query_active 投影)
|
||||
func presentation_snapshot() -> Dictionary:
|
||||
var enemies: Array[Dictionary] = []
|
||||
var all: Array = _enemy_system.all_enemies()
|
||||
var all: Array[Variant] = _enemy_system.all_enemies()
|
||||
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行,八型批 2026-09-20)
|
||||
var enemy_effects: Array[Dictionary] = []
|
||||
for eff in _status_effects.query_active("enemy"):
|
||||
var erow: Dictionary = db.status_effect_row(eff.effect_id)
|
||||
var erow: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
|
||||
if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]:
|
||||
enemy_effects.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)})
|
||||
for i in all.size():
|
||||
var u: Dictionary = all[i]
|
||||
var u: Dictionary[Variant, Variant] = all[i]
|
||||
var alive: bool = bool(u.get("alive", false))
|
||||
var pending: String = String(u.get("pending_skill", "")) if alive else ""
|
||||
var pending_row: Dictionary = db.skill_row(pending)
|
||||
var pending_row: Dictionary[Variant, Variant] = db.skill_row(pending)
|
||||
enemies.append({
|
||||
"order": i + 1,
|
||||
"display_name": String(u.display_name),
|
||||
@@ -1218,7 +1218,7 @@ func presentation_snapshot() -> Dictionary:
|
||||
"alive": alive,
|
||||
})
|
||||
var hero := {}
|
||||
var h: Dictionary = _enemy_system.hero_unit()
|
||||
var h: Dictionary[Variant, Variant] = _enemy_system.hero_unit()
|
||||
if not h.is_empty():
|
||||
hero = {"hp": int(h.hp), "max_hp": int(h.max_hp), "atk": int(h.atk), "alive": bool(h.get("alive", false)), "armor_class": _defender_ac(h)}
|
||||
return {
|
||||
|
||||
@@ -106,7 +106,7 @@ func compute_r0() -> Array:
|
||||
var r0 := {}
|
||||
for c in b3:
|
||||
r0[c] = true
|
||||
var queue: Array = b3.duplicate()
|
||||
var queue: Array[Variant] = b3.duplicate()
|
||||
while not queue.is_empty():
|
||||
var c: Vector2i = queue.pop_front()
|
||||
if is_safe(c) and digit_of(c) == 0: # 变异死分支退役(2026-09-18 用户拍板删——与 排雷玩法 展开实现口径归一;变异定稿批按正式语义重接)
|
||||
|
||||
@@ -15,8 +15,8 @@ const METRIC_VERSION := 1
|
||||
|
||||
# rng_stream = 一次 open_stream("board_gen") 实例(一张图的全部取数;重试共用——attempt 边界非 scope 事件)。
|
||||
# db 参数不标具体类型原因:冒烟工具注入 FakeDB 桩(experiment_density.gd 等)——typed 形参会拒收桩表(2026-10-02 注)
|
||||
func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardData:
|
||||
var row: Dictionary = db.resolve_board_gen(L, D)
|
||||
func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardData: # type-hint: ignore(测量面 _BoardRowStub 冒烟夹具鸭子型注入——typed 参拒 stub)
|
||||
var row: Dictionary[Variant, Variant] = db.resolve_board_gen(L, D)
|
||||
if row.is_empty():
|
||||
_fatal("BoardGen 行缺失 (%d,%d)" % [L, D])
|
||||
return null
|
||||
@@ -54,7 +54,7 @@ func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardDat
|
||||
if int(params.r0_cap) > 0 and b.compute_r0().size() > int(params.r0_cap):
|
||||
r0_cap_rejected += 1
|
||||
if legality(b, params, db):
|
||||
var m: Dictionary = measure_inferability(b) # 求解器每合法板恰一次(O3 收口——此前重复调用两次)
|
||||
var m: Dictionary[Variant, Variant] = measure_inferability(b) # 求解器每合法板恰一次(O3 收口——此前重复调用两次)
|
||||
var inf: float = m["inferability"]
|
||||
if best_legal == null or inf > best_legal_score:
|
||||
best_legal = b
|
||||
@@ -80,7 +80,7 @@ func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardDat
|
||||
|
||||
# ==================== 五阶段:单次尝试 ====================
|
||||
|
||||
func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> BoardData:
|
||||
func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> BoardData: # type-hint: ignore(同 generate——测量面 stub 注入)
|
||||
var b := BoardData.new()
|
||||
b.width = params.w
|
||||
b.height = params.h
|
||||
@@ -158,7 +158,7 @@ func measure_inferability(b: BoardData) -> Dictionary:
|
||||
while changed:
|
||||
changed = false
|
||||
# 算子① 连锁:0 格的 8 邻安全格全入 R
|
||||
var frontier: Array = r.keys()
|
||||
var frontier: Array[Variant] = r.keys()
|
||||
for x in frontier:
|
||||
if safe_set.has(x) and b.digit_of(x) == 0:
|
||||
for n in b.neighbors8(x):
|
||||
@@ -183,8 +183,8 @@ func measure_inferability(b: BoardData) -> Dictionary:
|
||||
var derived := []
|
||||
for i in constraints.size():
|
||||
for j in range(i + 1, constraints.size()):
|
||||
var a: Dictionary = constraints[i]
|
||||
var bb: Dictionary = constraints[j]
|
||||
var a: Dictionary[Variant, Variant] = constraints[i]
|
||||
var bb: Dictionary[Variant, Variant] = constraints[j]
|
||||
var sub := _subset_of(a.unknown, bb.unknown)
|
||||
if sub == 1:
|
||||
derived.append(_diff_constraint(a, bb))
|
||||
@@ -264,7 +264,7 @@ func is_clearable(b: BoardData) -> bool:
|
||||
for c in b.safe_cells():
|
||||
safe_set[c] = true
|
||||
var seen := {}
|
||||
var queue: Array = b.spawn_cells().duplicate()
|
||||
var queue: Array[Variant] = b.spawn_cells().duplicate()
|
||||
for c in queue:
|
||||
seen[c] = true
|
||||
while not queue.is_empty():
|
||||
@@ -281,7 +281,7 @@ func is_clearable(b: BoardData) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func legality(b: BoardData, params: Dictionary, db) -> bool:
|
||||
func legality(b: BoardData, params: Dictionary, db) -> bool: # type-hint: ignore(同 generate——测量面 stub 注入)
|
||||
var reasons := _legality_faults(b, params)
|
||||
return reasons.is_empty()
|
||||
|
||||
@@ -331,7 +331,7 @@ func _neighbor_mine_count(b: BoardData, c: Vector2i) -> int:
|
||||
|
||||
# ==================== 产出上报(生成期指标集) ====================
|
||||
|
||||
func _finalize(b: BoardData, params: Dictionary, attempts_used: int, accept_mode: String, _db, r0_cap_rejected: int = 0) -> void:
|
||||
func _finalize(b: BoardData, params: Dictionary, attempts_used: int, accept_mode: String, _db, r0_cap_rejected: int = 0) -> void: # type-hint: ignore(同 generate——测量面 stub 注入)
|
||||
var m := measure_inferability(b)
|
||||
# 产物断言(棋盘生成 出生区安全与反退化布雷):实际雷数 ≤ 目标雷数 N——先布后删只减不增,失败即致命
|
||||
var n_target: int = int(round(float(params.density) * float(params.w * params.h - BoardData.SPAWN_AREA_CELLS)))
|
||||
@@ -364,7 +364,7 @@ func _badness(b: BoardData, params: Dictionary) -> Array:
|
||||
for c in b.safe_cells():
|
||||
safe_set[c] = true
|
||||
var seen := {}
|
||||
var queue: Array = b.spawn_cells().duplicate()
|
||||
var queue: Array[Variant] = b.spawn_cells().duplicate()
|
||||
for c in queue:
|
||||
seen[c] = true
|
||||
while not queue.is_empty():
|
||||
|
||||
@@ -209,7 +209,7 @@ func variant_pool(level_index: int, board_type: String, node_type: String = "")
|
||||
continue
|
||||
if int(row.get("unlock_level", 999)) > level_index:
|
||||
continue
|
||||
var tags: Array = row.get("board_types", [])
|
||||
var tags: Array[Variant] = row.get("board_types", [])
|
||||
if tags.is_empty():
|
||||
pool.append(row)
|
||||
continue
|
||||
|
||||
@@ -77,7 +77,7 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
var li: int = int(lrow.level_index)
|
||||
# 模板池感知(关卡节点图生成 v0.4——池内全模板跨表校验,不只 row.nodes)
|
||||
var node_lists: Array[Array] = []
|
||||
var tpls: Array = lrow.get("templates", [])
|
||||
var tpls: Array[Variant] = lrow.get("templates", [])
|
||||
if tpls.is_empty():
|
||||
node_lists.append(lrow.get("nodes", []))
|
||||
else:
|
||||
@@ -137,7 +137,7 @@ func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable,
|
||||
continue
|
||||
var want_side: String = "enemy" if st == "buff" else "hero"
|
||||
var eff: String = String(row.get("buff_ref", ""))
|
||||
var srow: Dictionary = status_table.row_by_id(eff) if status_table != null else {}
|
||||
var srow: Dictionary[Variant, Variant] = status_table.row_by_id(eff) if status_table != null else {}
|
||||
if not srow.is_empty() and String(srow.get("target_side", "")) != want_side:
|
||||
errs.append({"category": "cast_side", "message": "SkillTable %s(%s)引用 %s 的 target_side=%s ≠ %s(施放类 side 守卫)" % [String(row.get("skill_id", "")), st, eff, String(srow.get("target_side", "")), want_side]})
|
||||
for row in enemy_table.rows:
|
||||
|
||||
@@ -113,7 +113,7 @@ func _fx_fact(fact: String):
|
||||
"dig_streak":
|
||||
return dig_streak
|
||||
"hp_ratio":
|
||||
var hu: Dictionary = units.hero_unit()
|
||||
var hu: Dictionary[Variant, Variant] = units.hero_unit()
|
||||
return int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))))) # 百分比 int(when lte int 截断口径)
|
||||
return null
|
||||
|
||||
@@ -292,9 +292,9 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary:
|
||||
var armor: String = String(target.get("armor_type", "light"))
|
||||
var atk_type: String = String(t.attack_type)
|
||||
var matchup := (atk_type + "_vs_" + armor) if atk_type != "" else ""
|
||||
var ac_hits: Array = (t.context.get("fx_attack_hits", []) as Array)
|
||||
var ac_payload: Dictionary = (t.context.get("fx_attack_payload", {}) as Dictionary)
|
||||
var roll_read: Dictionary = _armor_roll()
|
||||
var ac_hits: Array[Variant] = (t.context.get("fx_attack_hits", []) as Array)
|
||||
var ac_payload: Dictionary[Variant, Variant] = (t.context.get("fx_attack_payload", {}) as Dictionary)
|
||||
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
|
||||
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
||||
# armor_roll 发射点(内容数据驱动扩展批 T1——敌护甲掷骰判定;免掷恒折扣订阅面)
|
||||
var ar_hits := _fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)})
|
||||
@@ -349,8 +349,8 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary:
|
||||
# 基值装配(原 :329-355 逐字):基值+单位标记加伤+矿工重锤增伤面+加法族(attack_bonus/
|
||||
# value_counter 层值)+计数器施加(attack_commit 用后清零行——命中序=声明序)+spike 倍乘。
|
||||
func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float:
|
||||
var ac_hits: Array = ctx["ac_hits"]
|
||||
var ac_payload: Dictionary = ctx["ac_payload"]
|
||||
var ac_hits: Array[Variant] = ctx["ac_hits"]
|
||||
var ac_payload: Dictionary[Variant, Variant] = ctx["ac_payload"]
|
||||
var base_value := float(t.base_value)
|
||||
# 单位标记加伤(迭代 20 T1——处刑台 target_attack_bonus 挂单位字典 fx_atk_bonus 键;
|
||||
# 阈值沿发射点已写入,本处通用消费——对该敌伤害加算)
|
||||
@@ -385,11 +385,11 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float:
|
||||
# 击杀标记/kill 发射+贯穿弹射。返回 {landed, payload, hero_rider}(交付步消费)。
|
||||
func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> Dictionary:
|
||||
var target = ctx["target"]
|
||||
var ac_hits: Array = ctx["ac_hits"]
|
||||
var roll_read: Dictionary = ctx["roll_read"]
|
||||
var ac_hits: Array[Variant] = ctx["ac_hits"]
|
||||
var roll_read: Dictionary[Variant, Variant] = ctx["roll_read"]
|
||||
var atk_type: String = ctx["atk_type"]
|
||||
var rolled: int = DamageMath.rolled_component(base_value, int(ctx["discount"]), bool(ctx["crit"]), int(db.settlement.crit_multiplier), int(ctx["cdelta"]))
|
||||
var payload: Dictionary = {"target": String(target.display_name)}
|
||||
var payload: Dictionary[Variant, Variant] = {"target": String(target.display_name)}
|
||||
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
|
||||
payload["roll"] = int(roll_read.roll)
|
||||
payload["ac"] = int(ctx["threshold"])
|
||||
@@ -440,7 +440,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
|
||||
|
||||
# 交付(原 :403-414 逐字):即时执行族(碎甲震击护甲裂解)/标签装配/明细行发射/兑现登记。
|
||||
func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary) -> void:
|
||||
var payload: Dictionary = redemption["payload"]
|
||||
var payload: Dictionary[Variant, Variant] = redemption["payload"]
|
||||
# 即时执行族:碎甲震击打重甲附加「护甲裂解」(复用 off_guard——零新状态行;apply_effect 域闭/
|
||||
# 重复授予由状态容器自行守卫)
|
||||
for h in (ctx["ac_hits"] as Array):
|
||||
@@ -459,8 +459,8 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
|
||||
# 前态恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
|
||||
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)
|
||||
func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||
var hero: Dictionary = units.hero_unit()
|
||||
var roll_read: Dictionary = _armor_roll()
|
||||
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
||||
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
|
||||
var threshold: int = defender_ac(hero) + int(db.settlement.attack_bonus)
|
||||
var crit: bool = bool(roll_read.crit)
|
||||
var discount: int = _roll_discount(int(roll_read.roll), threshold)
|
||||
@@ -489,7 +489,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||
if shield > 0:
|
||||
total = maxi(int(db.settlement.damage_floor), total - shield)
|
||||
units.apply_damage(hero, total)
|
||||
var payload: Dictionary = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
||||
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
||||
"roll": int(roll_read.roll), "ac": threshold, "tier": discount, "attack_type": String(t.attack_type)}
|
||||
if crit:
|
||||
payload["crit"] = true
|
||||
@@ -529,7 +529,7 @@ func _resolve_board_typed(t: Dictionary) -> void:
|
||||
|
||||
# 棋盘群伤:无类型恒定表值,结账时刻全体存活敌逐个交付(单行汇总——行内 count = 兑现时存活数)
|
||||
func _resolve_board_blast(t: Dictionary) -> void:
|
||||
var alive: Array = units.query_alive("enemy")
|
||||
var alive: Array[Variant] = units.query_alive("enemy")
|
||||
if alive.is_empty():
|
||||
_ineffective_row(t, "无存活敌人")
|
||||
_outcomes[int(t.ticket_id)] = "zero"
|
||||
@@ -546,7 +546,7 @@ func _resolve_board_blast(t: Dictionary) -> void:
|
||||
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
||||
var dealt: int = maxi(int(db.settlement.damage_floor), int(t.base_value)) # 伤害下限全来源(2026-09-20)
|
||||
units.apply_damage(units.hero_unit(), dealt)
|
||||
var extra: Dictionary = {"target": "hero"}
|
||||
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
|
||||
for k in t.context.keys():
|
||||
extra[k] = t.context[k]
|
||||
emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → 主角" % String(t.display), dealt, extra, String(t.highlight))
|
||||
@@ -578,13 +578,13 @@ func defender_ac(unit: Dictionary) -> int:
|
||||
if total < 0:
|
||||
total = int(db.hero.armor_class)
|
||||
for eff in effects.query_active("hero"):
|
||||
var row: Dictionary = db.status_effect_row(eff.effect_id)
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
|
||||
if not row.is_empty() and String(row.unit) == "ac":
|
||||
total += int(eff.effective_value)
|
||||
return total
|
||||
var total_e: int = int(unit.get("armor_class", 0))
|
||||
for eff_e in effects.query_active("enemy"):
|
||||
var erow_ac: Dictionary = db.status_effect_row(eff_e.effect_id)
|
||||
var erow_ac: Dictionary[Variant, Variant] = db.status_effect_row(eff_e.effect_id)
|
||||
if not erow_ac.is_empty() and String(erow_ac.unit) == "ac":
|
||||
total_e += int(eff_e.effective_value)
|
||||
return total_e
|
||||
@@ -595,14 +595,14 @@ func defender_ac(unit: Dictionary) -> int:
|
||||
func _counter_delta(attack_type: String, armor: String) -> int:
|
||||
if attack_type == "":
|
||||
return 0
|
||||
var tbl: Dictionary = db.settlement.armor_effectiveness
|
||||
var row: Dictionary = tbl.get(attack_type, {})
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.armor_effectiveness
|
||||
var row: Dictionary[Variant, Variant] = tbl.get(attack_type, {})
|
||||
return int(row.get(armor, 0))
|
||||
|
||||
|
||||
# 折扣档改善(迭代 12 T4 猎弱本能——折扣表向全额方向移 value 档;查不到当前档 = 不动防御口径)
|
||||
func _discount_shift_up(discount: int, shift: int) -> int:
|
||||
var tbl: Array = db.settlement.roll_tier_discounts
|
||||
var tbl: Array[Variant] = db.settlement.roll_tier_discounts
|
||||
var idx := tbl.find(discount)
|
||||
if idx < 0:
|
||||
return discount
|
||||
@@ -613,7 +613,7 @@ func _discount_shift_up(discount: int, shift: int) -> int:
|
||||
func clamped_pool(pool: String) -> int:
|
||||
var total := 0
|
||||
for eff in effects.query_active("hero") + effects.query_active("enemy"):
|
||||
var row: Dictionary = db.status_effect_row(eff.effect_id)
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
|
||||
if row.is_empty():
|
||||
continue
|
||||
if String(db.settlement.effect_pool_map.get(eff.effect_id, "")) == pool and String(row.unit) == "percent":
|
||||
|
||||
+13
-13
@@ -91,8 +91,8 @@ const DO_KEYS := ["kind", "value", "value_from", "delta", "delta_from", "mult",
|
||||
const ACTIVE_KINDS := ["reveal_tiles", "preview", "grant_ap", "remove_mines", "refresh_pick", "safe_dig"]
|
||||
|
||||
# ---- 订阅集与计数器账本 ----
|
||||
var _triggers: Array = [] # [{source, id, on, when, do}](装载序=声明序,重建时定死)
|
||||
var _counters: Dictionary = {} # counter_id → int(battle 域账本;重建清零;重置点由订阅行声明)
|
||||
var _triggers: Array[Variant] = [] # [{source, id, on, when, do}](装载序=声明序,重建时定死)
|
||||
var _counters: Dictionary[Variant, Variant] = {} # counter_id → int(battle 域账本;重建清零;重置点由订阅行声明)
|
||||
var _reset_on_round_start: Array[String] = [] # round_start 重置点计数器名单(装载时收集)
|
||||
var _adventure_counter_ids: Array[String] = [] # domain=adventure 计数器名单(T6——重建时收集、不清零;
|
||||
# battle 末 export → 冒险账本、begin_battle seed 回灌)
|
||||
@@ -171,7 +171,7 @@ func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void:
|
||||
func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary) -> void:
|
||||
var on := String(tr.get("on", ""))
|
||||
assert(EVENTS.has(on), "[效果引擎] 订阅事件出域: %s(%s %s)" % [on, source, id])
|
||||
var do_d: Dictionary = (tr.get("do", {}) as Dictionary).duplicate(true)
|
||||
var do_d: Dictionary[Variant, Variant] = (tr.get("do", {}) as Dictionary).duplicate(true)
|
||||
# *_from 解析(rebuild 时把行字段引用定影为值——value_from:"magnitude" → value=行 magnitude)
|
||||
for pair in [["value_from", "value"], ["delta_from", "delta"], ["mult_from", "mult"], ["cap_from", "cap"]]:
|
||||
if do_d.has(pair[0]):
|
||||
@@ -179,9 +179,9 @@ func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary)
|
||||
do_d.erase(pair[0])
|
||||
# when value_from 同面解析(行字段引用条件——dawn_order effective_rounds 先例:字面量出行=数值
|
||||
# 单一事实源破面,行引用随 rebuild 定影)
|
||||
var when_d: Array = (tr.get("when", []) as Array).duplicate(true)
|
||||
var when_d: Array[Variant] = (tr.get("when", []) as Array).duplicate(true)
|
||||
for c_v in when_d:
|
||||
var c: Dictionary = c_v as Dictionary
|
||||
var c: Dictionary[Variant, Variant] = c_v as Dictionary
|
||||
if c.has("value_from"):
|
||||
c["value"] = int(row.get(String(c["value_from"]), 0))
|
||||
c.erase("value_from")
|
||||
@@ -206,7 +206,7 @@ func has_effect_on(event: String, kind: String, mode: String = "") -> bool:
|
||||
for tr in _triggers:
|
||||
if String(tr.on) != event:
|
||||
continue
|
||||
var d: Dictionary = tr.do
|
||||
var d: Dictionary[Variant, Variant] = tr.do
|
||||
if String(d.get("kind", "")) == kind and (mode == "" or String(d.get("mode", "")) == mode):
|
||||
# 有 when 时不能预判命中(发射点缺省行为保守按「可能命中」计——命中由 dispatch 定夺)
|
||||
return true
|
||||
@@ -221,7 +221,7 @@ func dispatch(event: String, payload: Dictionary, facts: Callable = Callable())
|
||||
if event == "round_start":
|
||||
for cid in _reset_on_round_start:
|
||||
_counters[cid] = 0
|
||||
var hits: Array = []
|
||||
var hits: Array[Variant] = []
|
||||
for tr in _triggers:
|
||||
if String(tr.on) != event:
|
||||
continue
|
||||
@@ -350,8 +350,8 @@ func _counter_delta_of(matchup: String) -> int:
|
||||
var parts := matchup.split("_vs_")
|
||||
if parts.size() != 2:
|
||||
return 0
|
||||
var tbl: Dictionary = db.settlement.armor_effectiveness
|
||||
var row: Dictionary = tbl.get(String(parts[0]), {})
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.armor_effectiveness
|
||||
var row: Dictionary[Variant, Variant] = tbl.get(String(parts[0]), {})
|
||||
return int(row.get(String(parts[1]), 0))
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ func _counter_delta_of(matchup: String) -> int:
|
||||
static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array, errs: Array) -> bool:
|
||||
var mine_covered := false
|
||||
for tr_v in triggers:
|
||||
var tr: Dictionary = tr_v as Dictionary
|
||||
var tr: Dictionary[Variant, Variant] = tr_v as Dictionary
|
||||
for k in tr.keys():
|
||||
if not ["on", "when", "do"].has(String(k)):
|
||||
errs.append({"category": "zero_flat", "message": "%s trigger 键出白名单: %s" % [rid, String(k)]})
|
||||
@@ -372,7 +372,7 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
|
||||
elif MINE_EVENTS.has(on):
|
||||
mine_covered = true
|
||||
for c_v in (tr.get("when", []) as Array):
|
||||
var c: Dictionary = c_v as Dictionary
|
||||
var c: Dictionary[Variant, Variant] = c_v as Dictionary
|
||||
for k in c.keys():
|
||||
if not WHEN_KEYS.has(String(k)):
|
||||
errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]})
|
||||
@@ -381,7 +381,7 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
|
||||
errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 9 项): %s" % [rid, fact]})
|
||||
if not OPS.has(String(c.get("op", "eq"))):
|
||||
errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]})
|
||||
var do_d: Dictionary = tr.get("do", {}) as Dictionary
|
||||
var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary
|
||||
for k in do_d.keys():
|
||||
if not DO_KEYS.has(String(k)):
|
||||
errs.append({"category": "zero_flat", "message": "%s do 键出白名单: %s(机制轴禁无条件属性加项)" % [rid, String(k)]})
|
||||
@@ -396,7 +396,7 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
|
||||
if fld != "" and not row.has(fld):
|
||||
errs.append({"category": "dangling", "message": "%s do.%s 悬空(行无字段 %s)" % [rid, vf_k, fld]})
|
||||
for c_v in (tr.get("when", []) as Array):
|
||||
var c_w: Dictionary = c_v as Dictionary
|
||||
var c_w: Dictionary[Variant, Variant] = c_v as Dictionary
|
||||
if c_w.has("value_from"):
|
||||
var wfld := String(c_w.get("value_from", ""))
|
||||
if wfld != "" and not row.has(wfld):
|
||||
|
||||
@@ -13,7 +13,7 @@ extends RefCounted
|
||||
static func tiers_for(feat_ids: Array, table: FeatTable) -> Dictionary:
|
||||
var counts := {}
|
||||
for rid in feat_ids:
|
||||
var row: Dictionary = table.row_by_id(String(rid)) if table != null else {}
|
||||
var row: Dictionary[Variant, Variant] = table.row_by_id(String(rid)) if table != null else {}
|
||||
if row.is_empty():
|
||||
continue
|
||||
var arch := String(row.get("archetype", ""))
|
||||
|
||||
+20
-20
@@ -47,7 +47,7 @@ var node_index := -1
|
||||
# ---- 账本载体(冒险结构实现批——漂移②清偿)----
|
||||
# 冒险模式 = AdventureRunner 共享字典(Dictionary 引用共享——hero_hp/gp 属性读写直接落冒险账本);
|
||||
# 单关模式 = 本实例私有账本(start_level 重建,既有单关语义逐位保留)。
|
||||
var _ledger: Dictionary = {"hero_hp": 0, "gp": 0}
|
||||
var _ledger: Dictionary[Variant, Variant] = {"hero_hp": 0, "gp": 0}
|
||||
var _rest_handler: Callable # 治疗封顶回报执行体(默认内置 rest_heal_amount;冒险模式 = AdventureRunner.apply_rest——接口③)
|
||||
var _external := false # 冒险模式标记(钱包收束时机与播种语义分叉点)
|
||||
# hero_hp / gp 属性化(既有读写点零改动——值类型经属性落 _ledger)
|
||||
@@ -63,7 +63,7 @@ var _shop_purchased := {} # product_id → 已购档数(现价
|
||||
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
||||
var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(迭代 7 T3——TreasureItems;跨战斗延续,
|
||||
var _treasure_unlocked := {} # aux 解锁门控集(迭代 7 T5——拍板③甲;空集=零投放)
|
||||
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——adventure params.attunement 下传,begin_battle enc 携带进战斗结算;空集=off 零扰动)
|
||||
var _attunement: Array[Variant] = [] # 同调装配集(迭代 11 第①批 T5——adventure params.attunement 下传,begin_battle enc 携带进战斗结算;空集=off 零扰动)
|
||||
# 关卡末清空+快照计数入 level_finished 载荷)
|
||||
# 两根类型轴(迭代 23 T4——adventure 参数面快照经 set_hero_axes 下传;空串=未注入→锻造配置默认值回退。
|
||||
# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照)
|
||||
@@ -71,11 +71,11 @@ var _hero_attack_type := ""
|
||||
var _hero_armor_type := ""
|
||||
# ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)----
|
||||
var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
|
||||
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
|
||||
var _pool_deck: Array[Variant] = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
|
||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌)
|
||||
var _pool_ticket := Callable() # 门票展示判定 handler(T5——AdventureRunner._feat_ticket_consider;节点与商店共享计数)
|
||||
var _pool_cap_check := Callable() # cap 检查回调(迭代 20 T2——AdventureRunner._curio_cap_ok;案②提前退役)
|
||||
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||
var _shop_shelf: Array[Variant] = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
||||
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
||||
|
||||
@@ -132,7 +132,7 @@ func effective_ac() -> int:
|
||||
# 与 hero.tres 两轴删除前 armor_class=10 行为连续)
|
||||
func _armor_base_ac() -> int:
|
||||
var at: String = _hero_armor_type if _hero_armor_type != "" else db.forge_default_armor_type()
|
||||
var row: Dictionary = db.forge_armor_route_by_type(at)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_armor_route_by_type(at)
|
||||
if row.is_empty():
|
||||
return int(db.hero.armor_class) # 表缺失防御兜底(现役基准)
|
||||
return int(row.get("base_ac", 0))
|
||||
@@ -274,7 +274,7 @@ func _restore_to_stop(node_id: int, visited_ids: Array) -> bool:
|
||||
_phase = Phase.AWAITING_CHOICE
|
||||
_candidates = [entry]
|
||||
return true
|
||||
var stop: Dictionary = {}
|
||||
var stop: Dictionary[Variant, Variant] = {}
|
||||
for node in nodes():
|
||||
if _node_id(node) == node_id:
|
||||
stop = node
|
||||
@@ -330,7 +330,7 @@ var active_template_name := ""
|
||||
var active_template_index := -1 # -1 = 非池形态(旧单图)/未解析
|
||||
|
||||
func _resolve_template_row(row: Dictionary, template_pick: int) -> Dictionary:
|
||||
var templates: Array = row.get("templates", [])
|
||||
var templates: Array[Variant] = row.get("templates", [])
|
||||
active_template_name = ""
|
||||
active_template_index = -1
|
||||
if templates.is_empty():
|
||||
@@ -341,7 +341,7 @@ func _resolve_template_row(row: Dictionary, template_pick: int) -> Dictionary:
|
||||
else:
|
||||
var stream: RandomNumberGenerator = rng.open_stream("node_map")
|
||||
idx = stream.randi_range(0, templates.size() - 1)
|
||||
var tpl: Dictionary = templates[idx]
|
||||
var tpl: Dictionary[Variant, Variant] = templates[idx]
|
||||
active_template_name = String(tpl.get("display_name", ""))
|
||||
active_template_index = idx
|
||||
var out := row.duplicate(true)
|
||||
@@ -385,7 +385,7 @@ func _entry_node() -> Dictionary:
|
||||
func _out_ids(node: Dictionary) -> Array:
|
||||
if node.has("next"):
|
||||
return node.get("next", [])
|
||||
var nodes: Array = nodes()
|
||||
var nodes: Array[Variant] = nodes()
|
||||
var i := nodes.find(node)
|
||||
if i >= 0 and i < nodes.size() - 1:
|
||||
return [i + 1]
|
||||
@@ -627,7 +627,7 @@ func set_attunement(ids: Array) -> void:
|
||||
# 特性持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle
|
||||
# enc.feats 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效
|
||||
# (attune_charges 共享账本同款);空集=零扰动)
|
||||
var _feats: Array = []
|
||||
var _feats: Array[Variant] = []
|
||||
|
||||
|
||||
func set_feats(ids: Array) -> void:
|
||||
@@ -640,7 +640,7 @@ func feats_held() -> Array:
|
||||
|
||||
|
||||
# 树 rule 键集注入(迭代 12 T10——begin_battle enc.tree_rules 携带;Fork 激活侧 effects)
|
||||
var _tree_rules: Array = []
|
||||
var _tree_rules: Array[Variant] = []
|
||||
|
||||
|
||||
func set_tree_rules(rules: Array) -> void:
|
||||
@@ -682,7 +682,7 @@ func set_voucher_grants(rap: int, time_sand: int, insurance: int) -> void:
|
||||
# 已含断点内 attune 记录。原 set_attune_charges/set_vision_counter/set_adventure_counters
|
||||
# 三散 setter 随单类下传承接)
|
||||
func sync_attune_inventory() -> void:
|
||||
var charges: Dictionary = run_ledger.attune_charges
|
||||
var charges: Dictionary[Variant, Variant] = run_ledger.attune_charges
|
||||
if treasure_inventory == null:
|
||||
treasure_inventory = TreasureItems.new(db)
|
||||
for iid in charges:
|
||||
@@ -771,7 +771,7 @@ func purchased_tier(product_id: String) -> int:
|
||||
# 购买(未知商品/属性键出域/余额不足拒绝——返回 false 零副作用):
|
||||
# 出账 → 加算区 += flat_value → 购血同步当前生命(封顶新上限)→ 已购档数 +1
|
||||
func buy_product(product_id: String) -> bool:
|
||||
var prow: Dictionary = db.shop_product_row(product_id)
|
||||
var prow: Dictionary[Variant, Variant] = db.shop_product_row(product_id)
|
||||
if prow.is_empty():
|
||||
push_error("[关卡节点地图] 商店房未知商品: %s" % product_id)
|
||||
return false
|
||||
@@ -826,13 +826,13 @@ func _shop_open_pool() -> void:
|
||||
# 合法态(池空硬闸——买光即枯竭既定后果)。
|
||||
func _shop_draw_shelf() -> void:
|
||||
_shop_shelf = []
|
||||
var sellable: Array = []
|
||||
var sellable: Array[Variant] = []
|
||||
for row in db.shop_pool_rows():
|
||||
var shelf_kind := String(row.get("kind_id", ""))
|
||||
if _pool_cap_check.is_valid() and not bool(_pool_cap_check.call(shelf_kind, "")):
|
||||
continue # 迭代 20 T2 案②——cap 满提前退役(货架候选视图剔除;牌库本体不动)
|
||||
for t in _pool_deck:
|
||||
var tok: Dictionary = t as Dictionary
|
||||
var tok: Dictionary[Variant, Variant] = t as Dictionary
|
||||
if String(tok.get("kind", "")) == shelf_kind:
|
||||
sellable.append({"kind": shelf_kind, "tier": String(tok.get("tier", ""))})
|
||||
if sellable.is_empty():
|
||||
@@ -843,7 +843,7 @@ func _shop_draw_shelf() -> void:
|
||||
var tmp = sellable[i]
|
||||
sellable[i] = sellable[j]
|
||||
sellable[j] = tmp
|
||||
var seen: Array = []
|
||||
var seen: Array[Variant] = []
|
||||
for tok in sellable:
|
||||
var k := String((tok as Dictionary).get("kind", ""))
|
||||
if seen.has(k):
|
||||
@@ -874,7 +874,7 @@ func pool_stock(kind: String) -> int:
|
||||
# A/b 表 knob provisional;核心现算,呈现层禁复算——契约 5)。relic 卡(保底占位——shop 词表外)
|
||||
# 基价 provisional 15 固定锚(台账披露待追认——shop.tres 词表扩列归数值批)。
|
||||
func pool_price(kind: String) -> int:
|
||||
var row: Dictionary = db.shop_pool_row(kind)
|
||||
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
|
||||
if row.is_empty():
|
||||
if kind == "relic":
|
||||
return _spend_discounted(int(ceil(15.0 * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) * pow(db.shop_price_a(), float(_shop_visit_buys)))))
|
||||
@@ -892,11 +892,11 @@ func shop_shelf() -> Array:
|
||||
for tok in _shop_shelf:
|
||||
var kind := String((tok as Dictionary).get("kind", ""))
|
||||
var tier := String((tok as Dictionary).get("tier", ""))
|
||||
var row: Dictionary = db.shop_pool_row(kind)
|
||||
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
|
||||
var display := String(row.get("display_name", kind))
|
||||
var value := int(db.adventure.curio_value(kind, tier))
|
||||
if row.is_empty() and kind == "relic":
|
||||
var rrow: Dictionary = db.feat_row(String(tok.get("feat_id", "")))
|
||||
var rrow: Dictionary[Variant, Variant] = db.feat_row(String(tok.get("feat_id", "")))
|
||||
display = String(rrow.get("display_name", "特性"))
|
||||
value = 0
|
||||
out.append({
|
||||
@@ -924,7 +924,7 @@ func shop_refresh_free_left() -> int:
|
||||
func _attune_shop_refresh_bonus() -> int:
|
||||
var total := 0
|
||||
for iid_v in _attunement:
|
||||
var arow: Dictionary = db.treasure_item_row(String(iid_v))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
|
||||
if (arow.get("effect_kinds", []) as Array).has("shop_refresh_bonus"):
|
||||
total += int(arow.get("magnitude", 0))
|
||||
return total
|
||||
|
||||
@@ -69,7 +69,7 @@ func _treasure_unlock_progress(key: String) -> int:
|
||||
# 钥石购买解锁(营地秘宝匣唯一购买写口):仅购买件(行带 unlock_keystone_cost)可购;
|
||||
# 已解锁/余额不足/条件件误购 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币)。
|
||||
func buy_treasure_unlock(item_id: String) -> bool:
|
||||
var arow: Dictionary = db.treasure_item_row(item_id)
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(item_id)
|
||||
var price := int(arow.get("unlock_keystone_cost", 0))
|
||||
if arow.is_empty() or price < 1 or (arow.get("unlock_requires", []) as Array).size() > 0:
|
||||
return false # 非购买件(条件件/无价行)拒绝
|
||||
@@ -89,12 +89,12 @@ func refresh_treasure_unlocks() -> Array:
|
||||
var iid := String(row.get("item_id", ""))
|
||||
if save.item_unlocked(iid):
|
||||
continue
|
||||
var ureqs: Array = row.get("unlock_requires", []) as Array
|
||||
var ureqs: Array[Variant] = row.get("unlock_requires", []) as Array
|
||||
if ureqs.is_empty():
|
||||
continue # 购买件不入条件刷新面
|
||||
var met := true
|
||||
for req_v in ureqs:
|
||||
var req: Dictionary = req_v as Dictionary
|
||||
var req: Dictionary[Variant, Variant] = req_v as Dictionary
|
||||
if _treasure_unlock_progress(String(req.get("key", ""))) < int(req.get("threshold", 999)):
|
||||
met = false
|
||||
break
|
||||
@@ -113,10 +113,10 @@ func treasure_shop_status() -> Array:
|
||||
for row in db.treasure_attunement_rows():
|
||||
var iid := String(row.get("item_id", ""))
|
||||
var unlocked := save.item_unlocked(iid)
|
||||
var ureqs: Array = row.get("unlock_requires", []) as Array
|
||||
var req_view: Array = []
|
||||
var ureqs: Array[Variant] = row.get("unlock_requires", []) as Array
|
||||
var req_view: Array[Variant] = []
|
||||
for req_v in ureqs:
|
||||
var req: Dictionary = req_v as Dictionary
|
||||
var req: Dictionary[Variant, Variant] = req_v as Dictionary
|
||||
req_view.append({
|
||||
"key": String(req.get("key", "")),
|
||||
"threshold": int(req.get("threshold", 0)),
|
||||
@@ -143,7 +143,7 @@ func treasure_shop_status() -> Array:
|
||||
|
||||
# 路线解锁态派生(不落档——初始持有∨直购∨练满前一档;派生单点只此一处)
|
||||
func forge_route_unlocked(route_id: String) -> bool:
|
||||
var row: Dictionary = db.forge_route(route_id)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
|
||||
if row.is_empty():
|
||||
return false
|
||||
if bool(row.get("initial_owned", false)):
|
||||
@@ -152,7 +152,7 @@ func forge_route_unlocked(route_id: String) -> bool:
|
||||
return true
|
||||
var prereq := String(row.get("prereq_route", ""))
|
||||
if prereq != "":
|
||||
var prow: Dictionary = db.forge_route(prereq)
|
||||
var prow: Dictionary[Variant, Variant] = db.forge_route(prereq)
|
||||
if not prow.is_empty() and save.forge_route_level(prereq) >= int(prow.get("cap", 0)):
|
||||
return true
|
||||
return false
|
||||
@@ -161,7 +161,7 @@ func forge_route_unlocked(route_id: String) -> bool:
|
||||
# 路线购买(几何价=基础价×150%^已购级数——锚表 20/30/45/67/101;等级+1 即时写盘)。
|
||||
# 未解锁/已满级/余额不足/未知路线 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币不写盘)。
|
||||
func forge_buy_level(route_id: String) -> bool:
|
||||
var row: Dictionary = db.forge_route(route_id)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
|
||||
if row.is_empty() or not forge_route_unlocked(route_id):
|
||||
return false
|
||||
var lvl := save.forge_route_level(route_id)
|
||||
@@ -177,7 +177,7 @@ func forge_buy_level(route_id: String) -> bool:
|
||||
# 一次性解锁价直购(并联双通道②)。已解锁(含练满前一档派生)= 拒绝零收费——两通道互不影响:
|
||||
# 未练满也可直购;直购后练满不重复收(练满通道本就是派生、零收费)。初始持有行无解锁面。
|
||||
func forge_buy_unlock(route_id: String) -> bool:
|
||||
var row: Dictionary = db.forge_route(route_id)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
|
||||
if row.is_empty() or bool(row.get("initial_owned", false)):
|
||||
return false
|
||||
if forge_route_unlocked(route_id):
|
||||
@@ -192,7 +192,7 @@ func forge_buy_unlock(route_id: String) -> bool:
|
||||
# 当前武器/甲选中切换(免费——只受理已解锁路线;写存档域+即时写盘。冒险开始锁死归参数面快照,
|
||||
# 契约 8;冒险中无切换入口=UI 面无路径可达本口)
|
||||
func forge_select_weapon(route_id: String) -> bool:
|
||||
var row: Dictionary = db.forge_route(route_id)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(route_id):
|
||||
return false
|
||||
save.set_forge_weapon(route_id)
|
||||
@@ -200,7 +200,7 @@ func forge_select_weapon(route_id: String) -> bool:
|
||||
|
||||
|
||||
func forge_select_armor(route_id: String) -> bool:
|
||||
var row: Dictionary = db.forge_route(route_id)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(route_id):
|
||||
return false
|
||||
save.set_forge_armor(route_id)
|
||||
@@ -211,7 +211,7 @@ func forge_select_armor(route_id: String) -> bool:
|
||||
# 选中态异常(未解锁/表缺行)防御回退 0——注入面只认合法态,禁把脏选中带进冒险。
|
||||
func forge_weapon_atk_bonus() -> int:
|
||||
var rid := save.forge_weapon()
|
||||
var row: Dictionary = db.forge_route(rid)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid):
|
||||
return 0
|
||||
return save.forge_route_level(rid) * int(row.get("per_level_gain", 0))
|
||||
@@ -219,7 +219,7 @@ func forge_weapon_atk_bonus() -> int:
|
||||
|
||||
func forge_armor_ac_bonus() -> int:
|
||||
var rid := save.forge_armor()
|
||||
var row: Dictionary = db.forge_route(rid)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid):
|
||||
return 0
|
||||
return save.forge_route_level(rid) * int(row.get("per_level_gain", 0))
|
||||
@@ -229,7 +229,7 @@ func forge_armor_ac_bonus() -> int:
|
||||
# 默认值与柜台解锁状态解耦:树钥匙未点亮时本函数照常返回默认值(配置常驻可读)。
|
||||
func hero_attack_type() -> String:
|
||||
var rid := save.forge_weapon()
|
||||
var row: Dictionary = db.forge_route(rid)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid):
|
||||
return db.forge_default_attack_type()
|
||||
return String(row.get("type_axis", ""))
|
||||
@@ -237,7 +237,7 @@ func hero_attack_type() -> String:
|
||||
|
||||
func hero_armor_type() -> String:
|
||||
var rid := save.forge_armor()
|
||||
var row: Dictionary = db.forge_route(rid)
|
||||
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid):
|
||||
return db.forge_default_armor_type()
|
||||
return String(row.get("type_axis", ""))
|
||||
@@ -289,7 +289,7 @@ func forge_shop_status() -> Array:
|
||||
func settle_adventure(summary: Dictionary) -> Dictionary:
|
||||
# 首次冒险布尔位置位(关卡节点图生成 v0.4——完成/失败/放弃三结局同经本收束挂点,写入只在收束)
|
||||
save.mark_first_adventure_done()
|
||||
var drops: Array = (summary.get("keystone_drops", []) as Array).duplicate(true)
|
||||
var drops: Array[Variant] = (summary.get("keystone_drops", []) as Array).duplicate(true)
|
||||
save.apply_adventure_summary(summary, drops)
|
||||
save.add_vision_used(int(summary.get("vision_used_delta", 0))) # 迭代 22 T3——真视使用计数收束聚合入档(信标网络行为条件)
|
||||
var new_unlocks := refresh_treasure_unlocks() # 迭代 22 T2——条件解锁刷新换代(旧工坊面退役)
|
||||
@@ -333,7 +333,7 @@ func tree_spent_total() -> int:
|
||||
|
||||
# 节点可购校验(行门槛 + 显式前置 + 币 + cap;free 节点=钥匙不经购买,彩蛋=觉醒不经购买)
|
||||
func tree_can_buy(node_id: String) -> bool:
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
|
||||
if row.is_empty():
|
||||
return false
|
||||
# 彩蛋不经购买(觉醒唯一入口=_check_easter_egg 特化判定——V8 反例:手动购亮=红)
|
||||
@@ -354,7 +354,7 @@ func tree_can_buy(node_id: String) -> bool:
|
||||
return false
|
||||
# 等级阈值前置(R1 机制位——requires_level {node_id: n}:前置节点等级 ≥ n 才可购;
|
||||
# 2026-10-13 用户推翻裁撤案随批重立,现役表零配置=空转零行为影响)
|
||||
var req_lv: Dictionary = row.get("requires_level", {}) as Dictionary
|
||||
var req_lv: Dictionary[Variant, Variant] = row.get("requires_level", {}) as Dictionary
|
||||
for req_id in req_lv.keys():
|
||||
if save.tree_level(String(req_id)) < int(req_lv[req_id]):
|
||||
return false
|
||||
@@ -368,7 +368,7 @@ func tree_can_buy(node_id: String) -> bool:
|
||||
func tree_buy(node_id: String) -> bool:
|
||||
if node_id == "ks_refresh":
|
||||
return buy_attune_refresh()
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
|
||||
if row.is_empty() or not tree_can_buy(node_id):
|
||||
return false
|
||||
var price := int(row.get("price", 0))
|
||||
@@ -393,7 +393,7 @@ func tree_toggle_fork(group: String, side: String) -> bool:
|
||||
|
||||
# Fork 节点购买入口(一节点双态——购=解锁开关并置默认 A 态;重复购买由 cap 经 can_buy 拦截)
|
||||
func tree_buy_fork(node_id: String) -> bool:
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "fork":
|
||||
return tree_buy(node_id)
|
||||
if not tree_can_buy(node_id):
|
||||
@@ -418,15 +418,15 @@ func tree_stat_flat(key: String) -> int:
|
||||
|
||||
|
||||
func tree_rule_keys() -> Array:
|
||||
var out: Array = []
|
||||
var sides: Dictionary = save.fork_sides()
|
||||
var out: Array[Variant] = []
|
||||
var sides: Dictionary[Variant, Variant] = save.fork_sides()
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("kind", "")) != "fork":
|
||||
continue
|
||||
var active := String(sides.get(String(row.get("fork_group", "")), ""))
|
||||
if active == "":
|
||||
continue # 未解锁组零规则输出
|
||||
var fx: Dictionary = row.get("effects_" + active, {}) as Dictionary
|
||||
var fx: Dictionary[Variant, Variant] = row.get("effects_" + active, {}) as Dictionary
|
||||
for k in fx.keys():
|
||||
if not out.has(String(k)):
|
||||
out.append(String(k))
|
||||
@@ -435,7 +435,7 @@ func tree_rule_keys() -> Array:
|
||||
|
||||
# 营地树面投影(CampOverlay _rebuild_tree 数据源——行板只读;账面归本类、拼句归视图同款分工)
|
||||
func tree_camp_rows() -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("kind", "")) == "fork":
|
||||
continue # Fork 行由 fork_groups 段承载
|
||||
@@ -457,19 +457,19 @@ func tree_camp_rows() -> Array:
|
||||
# 行板门槛文案(R1 机制位呈现——requires_level 未达标项拼「需 {前置名} Lv{n}」;达标/未配置=空串。
|
||||
# 只呈现未达标项:达标后锁标消失,不做全量前置罗列)
|
||||
func tree_gate_text(node_id: String) -> String:
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
var rl: Dictionary = row.get("requires_level", {}) as Dictionary
|
||||
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
|
||||
var rl: Dictionary[Variant, Variant] = row.get("requires_level", {}) as Dictionary
|
||||
var parts: Array[String] = []
|
||||
for req_id in rl.keys():
|
||||
if save.tree_level(String(req_id)) < int(rl[req_id]):
|
||||
var req_row: Dictionary = db.tree_node(String(req_id))
|
||||
var req_row: Dictionary[Variant, Variant] = db.tree_node(String(req_id))
|
||||
parts.append("需 %s Lv%d" % [String(req_row.get("display_name", req_id)), int(rl[req_id])])
|
||||
return "、".join(parts)
|
||||
|
||||
|
||||
func tree_fork_groups() -> Array:
|
||||
var out: Array = []
|
||||
var sides: Dictionary = save.fork_sides()
|
||||
var out: Array[Variant] = []
|
||||
var sides: Dictionary[Variant, Variant] = save.fork_sides()
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("kind", "")) != "fork":
|
||||
continue
|
||||
@@ -496,7 +496,7 @@ func _check_easter_egg() -> void:
|
||||
if save.tree_level("easter_ley") > 0:
|
||||
return
|
||||
for nid in ["sp_hp", "sp_hit"]:
|
||||
var row: Dictionary = db.tree_node(nid)
|
||||
var row: Dictionary[Variant, Variant] = db.tree_node(nid)
|
||||
if row.is_empty() or save.tree_level(nid) < int(row.get("cap", 1)):
|
||||
return
|
||||
for i in range(1, 4):
|
||||
@@ -517,9 +517,9 @@ func _check_easter_egg() -> void:
|
||||
# 格 1=普通格/格 2=优秀格/格 3=稀有格递增向下兼容;槽位来源=树同调位节点点亮数,树接口接缝现役)。
|
||||
# 装配关系写存档持久。
|
||||
func toggle_attunement(item_id: String) -> bool:
|
||||
var equipped: Array = save.attunement_equipped()
|
||||
var equipped: Array[Variant] = save.attunement_equipped()
|
||||
if not equipped.has(item_id):
|
||||
var arow: Dictionary = db.treasure_item_row(item_id)
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(item_id)
|
||||
if arow.is_empty() or String(arow.get("form", "")) == "":
|
||||
return false # 非同调行(过渡/退役行)禁装配
|
||||
if not save.item_unlocked(item_id):
|
||||
@@ -538,7 +538,7 @@ func toggle_attunement(item_id: String) -> bool:
|
||||
|
||||
# 营地装配面投影(账面归本类、拼句归 CampOverlay——_camp_data 同款分工)
|
||||
func attune_camp_status() -> Dictionary:
|
||||
var equipped: Array = save.attunement_equipped()
|
||||
var equipped: Array[Variant] = save.attunement_equipped()
|
||||
var items: Array[Dictionary] = []
|
||||
for row in db.treasure_attunement_rows():
|
||||
var iid := String(row.get("item_id", ""))
|
||||
@@ -552,7 +552,7 @@ func attune_camp_status() -> Dictionary:
|
||||
"unlocked": save.item_unlocked(iid),
|
||||
})
|
||||
var lvl := save.attune_refresh_level()
|
||||
var next_row: Dictionary = db.meta_attune_refresh_row(lvl + 1)
|
||||
var next_row: Dictionary[Variant, Variant] = db.meta_attune_refresh_row(lvl + 1)
|
||||
return {
|
||||
"slots": Attunement.slots(db, save),
|
||||
"equipped": equipped,
|
||||
@@ -573,7 +573,7 @@ func attune_refresh_charges() -> int:
|
||||
|
||||
# Y 升级购买(直购价 = 下一级行表值 keystone_cost;已满级/余额不足 false 零副作用——buy_forge 同款)
|
||||
func buy_attune_refresh() -> bool:
|
||||
var next_row: Dictionary = db.meta_attune_refresh_row(save.attune_refresh_level() + 1)
|
||||
var next_row: Dictionary[Variant, Variant] = db.meta_attune_refresh_row(save.attune_refresh_level() + 1)
|
||||
if next_row.is_empty():
|
||||
return false
|
||||
if not save.spend_keystone(int(next_row.get("keystone_cost", 0))):
|
||||
|
||||
@@ -84,7 +84,7 @@ func clear_map_cleared() -> bool:
|
||||
# 移除集=未揭示、无旗、是雷——旗判定基线零回溯;雷格不入清图分母/连锁/C_dig 模拟,判定逻辑零改动) ----
|
||||
|
||||
func remove_mines_in(cells: Array) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for c in cells:
|
||||
var cell: Vector2i = c
|
||||
if not _board.in_bounds(cell) or is_revealed(cell) or has_flag(cell) or not _board.is_mine(cell):
|
||||
@@ -188,11 +188,11 @@ func _install_invariant_faults(b: BoardData) -> Array:
|
||||
|
||||
# 初始展开规范序:触发格 BFS、8 邻行主序(≡ 棋盘生成 F1 R₀ 算子;禁外扩环、禁只开 3×3)
|
||||
func _initial_expansion_order(b: BoardData) -> Array:
|
||||
var order: Array = b.spawn_cells().duplicate()
|
||||
var order: Array[Variant] = b.spawn_cells().duplicate()
|
||||
var seen := {}
|
||||
for c in order:
|
||||
seen[c] = true
|
||||
var queue: Array = order.duplicate()
|
||||
var queue: Array[Variant] = order.duplicate()
|
||||
while not queue.is_empty():
|
||||
var c: Vector2i = queue.pop_front()
|
||||
if b.is_safe(c) and b.digit_of(c) == 0:
|
||||
@@ -240,7 +240,7 @@ func _classify(intent: Dictionary) -> int:
|
||||
var c: Vector2i = intent.get("cell", Vector2i(-9, -9))
|
||||
if _board_face_dig(c) == "" and not _board.is_mine(c):
|
||||
# 模拟 Chain(O, c) 后清图判定——挖旗格同口径:本格旗视为已隐式退役(其余旗照挡)
|
||||
var sim_flags: Dictionary = _flags.duplicate()
|
||||
var sim_flags: Dictionary[Variant, Variant] = _flags.duplicate()
|
||||
sim_flags.erase(c)
|
||||
var chain := _chain_closure(c, sim_flags) # 与真实执行同口径——空旗集捷径会在旗挡连锁时误判 C_dig(排雷玩法:真模拟)
|
||||
var remaining := 0
|
||||
@@ -279,7 +279,7 @@ func _do_dig(c: Vector2i) -> Dictionary:
|
||||
# 计算(旗格为 0 时连锁照常外扩);request_dig 被拒则旗原地保留(事务性),受理后该旗轮末不判
|
||||
# (发射面基线 = 在场旗——标中/标空兑付让位于挖掘本体结局,四情况②④)。
|
||||
var was_flagged := has_flag(c)
|
||||
var eff_flags: Dictionary = _flags
|
||||
var eff_flags: Dictionary[Variant, Variant] = _flags
|
||||
if was_flagged:
|
||||
eff_flags = _flags.duplicate()
|
||||
eff_flags.erase(c)
|
||||
@@ -297,7 +297,7 @@ func _do_dig(c: Vector2i) -> Dictionary:
|
||||
# 事务内执行:首格 + 连锁新格揭示(BFS 规范序,零特判);事件 = O′ ∖ O
|
||||
if was_flagged:
|
||||
_flags.erase(c)
|
||||
var reveal_order: Array = _chain_reveal_order(c, eff_flags)
|
||||
var reveal_order: Array[Variant] = _chain_reveal_order(c, eff_flags)
|
||||
for rc in reveal_order:
|
||||
if _revealed.has(rc):
|
||||
continue # 闭包可穿过已开区——已开格零事件(cell_revealed × |O′ ∖ O|)
|
||||
@@ -361,7 +361,7 @@ func fx_reveal(c: Vector2i) -> bool:
|
||||
# 连锁闭包:μX.[O∪{c} ⊆ X ∧ ∀x∈X:(x∈S ∧ num(x)=0 ⇒ N₈(x)∩S∖G ⊆ X)]——连锁不越旗
|
||||
func _chain_closure(start: Vector2i, flag_set: Dictionary) -> Dictionary:
|
||||
var chain := {start: true}
|
||||
var queue: Array = [start]
|
||||
var queue: Array[Variant] = [start]
|
||||
while not queue.is_empty():
|
||||
var c: Vector2i = queue.pop_front()
|
||||
if _board.is_safe(c) and not flag_set.has(c) and _board.digit_of(c) == 0:
|
||||
@@ -374,9 +374,9 @@ func _chain_closure(start: Vector2i, flag_set: Dictionary) -> Dictionary:
|
||||
|
||||
# 揭示规范序:首格 + 触发格 BFS、8 邻行主序
|
||||
func _chain_reveal_order(start: Vector2i, flag_set: Dictionary) -> Array:
|
||||
var order: Array = [start]
|
||||
var order: Array[Variant] = [start]
|
||||
var seen := {start: true}
|
||||
var queue: Array = [start]
|
||||
var queue: Array[Variant] = [start]
|
||||
while not queue.is_empty():
|
||||
var c: Vector2i = queue.pop_front()
|
||||
if _board.is_safe(c) and not flag_set.has(c) and _board.digit_of(c) == 0:
|
||||
@@ -444,7 +444,7 @@ func compute_move_path(target: Vector2i) -> Array:
|
||||
return []
|
||||
# BFS(8 邻行主序)——路径 = 合法性载体 + HUD框架预览数据源
|
||||
var prev := {_hero: _hero}
|
||||
var queue: Array = [_hero]
|
||||
var queue: Array[Variant] = [_hero]
|
||||
while not queue.is_empty():
|
||||
var c: Vector2i = queue.pop_front()
|
||||
if c == target:
|
||||
@@ -478,7 +478,7 @@ func _do_stop() -> Dictionary:
|
||||
func _emit_flags_batch() -> void:
|
||||
# 判定基线 = 轮次结束事务开始时旗位;行主序枚举(确定性——排序键单点 BoardData.row_major_key,
|
||||
# 2026-10-02 修复批);被拔的不判;诚实重复上报(去重归 战斗结算)
|
||||
var cells: Array = _flags.keys()
|
||||
var cells: Array[Variant] = _flags.keys()
|
||||
cells.sort_custom(func(a, b): return BoardData.row_major_key(a) < BoardData.row_major_key(b))
|
||||
for c in cells:
|
||||
flag_judged.emit({"cell": c, "hit": _board.is_mine(c)})
|
||||
@@ -486,7 +486,7 @@ func _emit_flags_batch() -> void:
|
||||
|
||||
func _emit_round_end_flags_and_payload() -> void:
|
||||
_emit_flags_batch()
|
||||
var payload: Dictionary = action_economy.last_round_end # 逐字段取自 行动经济(读取权 = 接口契约)
|
||||
var payload: Dictionary[Variant, Variant] = action_economy.last_round_end # 逐字段取自 行动经济(读取权 = 接口契约)
|
||||
round_end_board.emit({"path_tag": payload.get("path_tag", ""), "continuation_hint": payload.get("continuation_hint", "")})
|
||||
|
||||
|
||||
|
||||
+14
-14
@@ -72,8 +72,8 @@ func roll_board(eligible: Array) -> bool:
|
||||
for h in _fx.dispatch("reward_roll", {"polarity": "positive", "cell_count": list.size()}):
|
||||
if String(h.get("kind", "")) == "bias_rewards":
|
||||
luck_bias += int(h.get("value", 0))
|
||||
var cell_rows: Array = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰)
|
||||
var clear_rows: Array = _pool_rows("clear_default")
|
||||
var cell_rows: Array[Variant] = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰)
|
||||
var clear_rows: Array[Variant] = _pool_rows("clear_default")
|
||||
# ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布)
|
||||
var draft := {}
|
||||
for e in list:
|
||||
@@ -82,10 +82,10 @@ func roll_board(eligible: Array) -> bool:
|
||||
var clear_rid := weighted_pick(clear_rows, stream.randi_range(0, _pool_weight(clear_rows) - 1))
|
||||
# ③ 区间行 power roll(身份定稿后逐格恰一追加取号,定值行零取号)
|
||||
for cell in draft.keys():
|
||||
var row: Dictionary = _row_by_id(draft[cell])
|
||||
var row: Dictionary[Variant, Variant] = _row_by_id(draft[cell])
|
||||
if row.has("power_min"):
|
||||
_power_roll[cell] = stream.randi_range(int(row.power_min), int(row.power_max))
|
||||
var clear_row: Dictionary = _row_by_id(clear_rid)
|
||||
var clear_row: Dictionary[Variant, Variant] = _row_by_id(clear_rid)
|
||||
if clear_row.has("power_min"):
|
||||
_clear_power = stream.randi_range(int(clear_row.power_min), int(clear_row.power_max))
|
||||
_mapping = draft
|
||||
@@ -114,7 +114,7 @@ func query_identity(cell: Vector2i):
|
||||
if rid == "":
|
||||
_diag("query_identity 清单外坐标(E19——未分配非错误态;封锁格不入清单)")
|
||||
return null
|
||||
var row: Dictionary = _row_by_id(rid)
|
||||
var row: Dictionary[Variant, Variant] = _row_by_id(rid)
|
||||
return {"reward_id": rid, "cell_type": String(row.cell_type), "tier": int(row.tier)}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func grant_clear():
|
||||
if _clear_granted:
|
||||
_diag("grant_clear 同批次重调(E18 幂等兜底)")
|
||||
return _clear_snapshot
|
||||
var row: Dictionary = _row_by_id(_clear_identity)
|
||||
var row: Dictionary[Variant, Variant] = _row_by_id(_clear_identity)
|
||||
_clear_snapshot = _snapshot(row, _clear_power)
|
||||
_clear_granted = true
|
||||
return _clear_snapshot
|
||||
@@ -206,13 +206,13 @@ const SKEW_UP_KINDS := ["attack", "blast", "pierce", "magic"]
|
||||
# 基座换源(首发批 2026-10-04):先经图级偏斜投影再叠件级加法——合成序锚=图级先、件级后
|
||||
#(环境事实先定型、玩家构筑叠其上;两投影均为池内权重面,雷安同池与取号次数零变化)
|
||||
func _biased_cell_rows(bias: int) -> Array:
|
||||
var base: Array = _skewed_cell_rows()
|
||||
var base: Array[Variant] = _skewed_cell_rows()
|
||||
if bias <= 0:
|
||||
return base
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in base:
|
||||
if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))):
|
||||
var biased: Dictionary = row.duplicate()
|
||||
var biased: Dictionary[Variant, Variant] = row.duplicate()
|
||||
biased["weight"] = int(row.weight) + bias
|
||||
out.append(biased)
|
||||
else:
|
||||
@@ -226,9 +226,9 @@ func _biased_cell_rows(bias: int) -> Array:
|
||||
func _skewed_cell_rows() -> Array:
|
||||
if board_skew <= 0:
|
||||
return _rollable_rows()
|
||||
var base: Array = _rollable_rows()
|
||||
var down: Array = []
|
||||
var up: Array = []
|
||||
var base: Array[Variant] = _rollable_rows()
|
||||
var down: Array[Variant] = []
|
||||
var up: Array[Variant] = []
|
||||
for row in base:
|
||||
var k := String(row.get("cell_type", ""))
|
||||
if SKEW_DOWN_KINDS.has(k):
|
||||
@@ -244,7 +244,7 @@ func _skewed_cell_rows() -> Array:
|
||||
var gains := _largest_remainder_split(up, drops_total(drops), _pool_weight(up), drops_total(drops))
|
||||
var di := 0
|
||||
var ui := 0
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in base:
|
||||
var k := String(row.get("cell_type", ""))
|
||||
var w := int(row.weight)
|
||||
@@ -255,7 +255,7 @@ func _skewed_cell_rows() -> Array:
|
||||
w += gains[ui]
|
||||
ui += 1
|
||||
assert(w > 0, "图级偏斜投影产生非正权重(池结构变更须走另批重审)")
|
||||
var skewed: Dictionary = row.duplicate()
|
||||
var skewed: Dictionary[Variant, Variant] = row.duplicate()
|
||||
skewed["weight"] = w
|
||||
out.append(skewed)
|
||||
assert(_pool_weight(out) == _pool_weight(base), "图级偏斜守恒破(偏斜后合计须等于原池)")
|
||||
|
||||
@@ -6,11 +6,11 @@ extends RefCounted
|
||||
# 下传并装配 encounter;战斗侧(BattleOrchestrator)经 encounter["ledger"] 拿本类引用,
|
||||
# 只走下方 read/append 原语——战末 export/开局 seed 时点语义不变(确定性设计原样保留,
|
||||
# 只把「函数签名上不可见的共享可变引用」换成签名可见的显式类)。
|
||||
var attune_charges: Dictionary = {} # 同调主动件充能账(迭代 11 第①批 T6——use_treasure 扣减;断点随局回档)
|
||||
var vision_counter: Dictionary = {} # 真视类使用计数(迭代 22 秘宝批 T3——冒险开始置 {"used":0};
|
||||
var attune_charges: Dictionary[Variant, Variant] = {} # 同调主动件充能账(迭代 11 第①批 T6——use_treasure 扣减;断点随局回档)
|
||||
var vision_counter: Dictionary[Variant, Variant] = {} # 真视类使用计数(迭代 22 秘宝批 T3——冒险开始置 {"used":0};
|
||||
# 缺省空 = 调试/单场战斗不计——原 enc 缺键守卫语义)
|
||||
var adventure_counters: Dictionary = {} # adventure 域效果计数器(迭代 20 T6——rebuild 回灌/战末导出回写目标)
|
||||
var bonus: Dictionary = {} # 统一冒险加算区(冒险开始置四轴零账——商店购买/三选一属性件/战末 bonus_delta 汇入)
|
||||
var adventure_counters: Dictionary[Variant, Variant] = {} # adventure 域效果计数器(迭代 20 T6——rebuild 回灌/战末导出回写目标)
|
||||
var bonus: Dictionary[Variant, Variant] = {} # 统一冒险加算区(冒险开始置四轴零账——商店购买/三选一属性件/战末 bonus_delta 汇入)
|
||||
|
||||
|
||||
# ---- 战斗侧原语(裁点②甲:战斗侧只拿 read/append 两族;宿主侧冒险/关卡域直读字段域) ----
|
||||
|
||||
+16
-16
@@ -118,7 +118,7 @@ func _migrate_v3_v4(data: Dictionary) -> Dictionary:
|
||||
# 补齐;迭代 21 T5 换代:原「三体系迁移」映射体随 migrate_tree 一并删除——开发阶段破坏性废弃
|
||||
# 〔Q3 用户裁〕,换代前结构由 _normalize 的 tree_struct 标记检测废弃重置,本函数只剩缺键补齐)。
|
||||
func _migrate_v4_v5(data: Dictionary) -> Dictionary:
|
||||
var meta: Dictionary = data.get("meta", {}) as Dictionary
|
||||
var meta: Dictionary[Variant, Variant] = data.get("meta", {}) as Dictionary
|
||||
if not meta.has("tree_nodes"):
|
||||
meta["tree_nodes"] = {}
|
||||
if not meta.has("fork_sides"):
|
||||
@@ -330,7 +330,7 @@ func tree_level(node_id: String) -> int:
|
||||
|
||||
|
||||
func set_tree_level(node_id: String, level: int) -> void:
|
||||
var tn: Dictionary = _data["meta"].get("tree_nodes", {}) as Dictionary
|
||||
var tn: Dictionary[Variant, Variant] = _data["meta"].get("tree_nodes", {}) as Dictionary
|
||||
tn[node_id] = maxi(0, level)
|
||||
_data["meta"]["tree_nodes"] = tn
|
||||
|
||||
@@ -344,7 +344,7 @@ func fork_side(group: String) -> String:
|
||||
|
||||
|
||||
func set_fork_side(group: String, side: String) -> void:
|
||||
var fs: Dictionary = _data["meta"].get("fork_sides", {}) as Dictionary
|
||||
var fs: Dictionary[Variant, Variant] = _data["meta"].get("fork_sides", {}) as Dictionary
|
||||
fs[group] = side
|
||||
_data["meta"]["fork_sides"] = fs
|
||||
|
||||
@@ -363,13 +363,13 @@ func forge_routes_snapshot() -> Dictionary:
|
||||
|
||||
|
||||
func forge_route_level(route_id: String) -> int:
|
||||
var lv: Dictionary = _forge_domain().get("levels", {}) as Dictionary
|
||||
var lv: Dictionary[Variant, Variant] = _forge_domain().get("levels", {}) as Dictionary
|
||||
return int(lv.get(route_id, 0))
|
||||
|
||||
|
||||
func set_forge_route_level(route_id: String, level: int) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var lv: Dictionary = dom.get("levels", {}) as Dictionary
|
||||
var dom: Dictionary[Variant, Variant] = _forge_domain()
|
||||
var lv: Dictionary[Variant, Variant] = dom.get("levels", {}) as Dictionary
|
||||
lv[route_id] = maxi(0, level)
|
||||
dom["levels"] = lv
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
@@ -380,8 +380,8 @@ func forge_unlocked_purchases() -> Array:
|
||||
|
||||
|
||||
func add_forge_unlock(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var un: Array = dom.get("unlocked", []) as Array
|
||||
var dom: Dictionary[Variant, Variant] = _forge_domain()
|
||||
var un: Array[Variant] = dom.get("unlocked", []) as Array
|
||||
if not un.has(route_id):
|
||||
un.append(route_id)
|
||||
dom["unlocked"] = un
|
||||
@@ -393,7 +393,7 @@ func forge_weapon() -> String:
|
||||
|
||||
|
||||
func set_forge_weapon(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var dom: Dictionary[Variant, Variant] = _forge_domain()
|
||||
dom["weapon"] = route_id
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
@@ -403,7 +403,7 @@ func forge_armor() -> String:
|
||||
|
||||
|
||||
func set_forge_armor(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var dom: Dictionary[Variant, Variant] = _forge_domain()
|
||||
dom["armor"] = route_id
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
@@ -415,7 +415,7 @@ func attunement_equipped() -> Array:
|
||||
|
||||
|
||||
func set_attunement_equipped(item_ids: Array) -> void:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for v in item_ids:
|
||||
out.append(String(v))
|
||||
_data["meta"]["attunement_equipped"] = out
|
||||
@@ -434,7 +434,7 @@ func set_attune_refresh_level(n: int) -> void:
|
||||
# clear_count(level_index) 逐关访问器零改动复用)
|
||||
func attunement_counters() -> Dictionary:
|
||||
var total := 0
|
||||
var counts: Dictionary = _data["meta"]["level_clear_counts"]
|
||||
var counts: Dictionary[Variant, Variant] = _data["meta"]["level_clear_counts"]
|
||||
for k in counts.keys():
|
||||
total += int(counts[k])
|
||||
return {"clear_count": total, "boss_kill_count": boss_kill_count()}
|
||||
@@ -462,9 +462,9 @@ func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> voi
|
||||
_data["meta"]["level_clear_counts"][key] = int(_data["meta"]["level_clear_counts"].get(key, 0)) + 1
|
||||
_data["meta"]["boss_first_kill"][key] = true
|
||||
_data["meta"]["boss_kill_count"] = boss_kill_count() + cleared # 迭代 11 T2——Boss 恒唯一终点:levels_cleared 即本次冒险 Boss 击杀数(含重复)
|
||||
var entry: Dictionary = summary.duplicate(true)
|
||||
var entry: Dictionary[Variant, Variant] = summary.duplicate(true)
|
||||
entry["boss_drops"] = boss_drops.duplicate(true)
|
||||
var arr: Array = _data["meta"]["recent_summaries"]
|
||||
var arr: Array[Variant] = _data["meta"]["recent_summaries"]
|
||||
arr.push_front(entry)
|
||||
while arr.size() > SUMMARY_CAP:
|
||||
arr.pop_back()
|
||||
@@ -509,10 +509,10 @@ func _corrupt_fallback(reason: String) -> bool:
|
||||
# tree_nodes/fork_sides 清空、钥石余额等他域零触碰(V16 断言面)。
|
||||
func _normalize(data: Dictionary) -> Dictionary[String, Variant]:
|
||||
var merged := _default_data()
|
||||
var def_meta: Dictionary = merged["meta"] # 补齐源引用(先于 data 覆盖取出——data 自带 meta 时 merged["meta"] 被换走,此引用仍指默认值)
|
||||
var def_meta: Dictionary[Variant, Variant] = merged["meta"] # 补齐源引用(先于 data 覆盖取出——data 自带 meta 时 merged["meta"] 被换走,此引用仍指默认值)
|
||||
for key in data:
|
||||
merged[key] = data[key]
|
||||
var meta: Dictionary = merged.get("meta", {})
|
||||
var meta: Dictionary[Variant, Variant] = merged.get("meta", {})
|
||||
if int(meta.get("tree_struct", 0)) < TREE_STRUCT_VERSION:
|
||||
meta["tree_nodes"] = {}
|
||||
meta["fork_sides"] = {}
|
||||
|
||||
@@ -61,7 +61,7 @@ func run(ri: int) -> void:
|
||||
if int(oc._enemy_system.hero_unit().hp) <= 0:
|
||||
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
|
||||
var skill_id := oc._enemy_system.pending_action(u)
|
||||
var srow: Dictionary = oc.db.skill_row(skill_id)
|
||||
var srow: Dictionary[Variant, Variant] = oc.db.skill_row(skill_id)
|
||||
if srow.is_empty():
|
||||
oc._fatal("敌实例待发动作悬空: %s" % skill_id)
|
||||
continue
|
||||
|
||||
@@ -73,7 +73,7 @@ func run(ri: int) -> void:
|
||||
if String(h.get("kind", "")) == "grant_material":
|
||||
_apply_material_hit(h, evt.cell, ri)
|
||||
# 奥术矢(魔能共鸣——每战首次开安全格追加魔法矢;任意真族单次生效)
|
||||
var spell_hits: Array = oc._hits_of(dr_hits, "spawn_spell")
|
||||
var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell")
|
||||
if not spell_hits.is_empty() and not oc._feat_first_dig_used:
|
||||
oc._feat_first_dig_used = true
|
||||
var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell",
|
||||
@@ -104,7 +104,7 @@ func run(ri: int) -> void:
|
||||
else:
|
||||
mine_reduce += int(h.get("value", 0)) # 背水——定值减免
|
||||
"heal":
|
||||
var hu_heal: Dictionary = oc._enemy_system.hero_unit()
|
||||
var hu_heal: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
|
||||
hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1))))
|
||||
"bonus_delta":
|
||||
var axis := String(h.get("axis", ""))
|
||||
@@ -133,9 +133,9 @@ func run(ri: int) -> void:
|
||||
# 雷格恒无变异——布置段排除,双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径,
|
||||
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
|
||||
for evt in oc._facts_reveals:
|
||||
var vids: Array = evt.get("variant_ids", [])
|
||||
var vids: Array[Variant] = evt.get("variant_ids", [])
|
||||
for vid in vids:
|
||||
var vrow: Dictionary = oc.db.variation.row_by_id(String(vid))
|
||||
var vrow: Dictionary[Variant, Variant] = oc.db.variation.row_by_id(String(vid))
|
||||
if String(vrow.get("effect", "")) != "flat_damage":
|
||||
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
|
||||
# 地形变异伤害单据(伤害管线单据化批 2026-09-24)——无类型必中,下限保底在控制器管线内
|
||||
@@ -177,7 +177,7 @@ func run(ri: int) -> void:
|
||||
fs_misses += 1
|
||||
var fs_fx := oc._fx_emit("flag_settle", {"flags_hit_all": fs_hits > 0 and fs_misses == 0,
|
||||
"flags_count": fs_hits, "round": ri})
|
||||
var banner_hits: Array = []
|
||||
var banner_hits: Array[Variant] = []
|
||||
for h in fs_fx:
|
||||
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "attack":
|
||||
banner_hits.append(h)
|
||||
@@ -269,12 +269,12 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
||||
if oc._fx_bonus_delta.has(axis_f):
|
||||
oc._fx_bonus_delta[axis_f] += int(h.get("delta", 0))
|
||||
"remove_mines":
|
||||
var removed: Array = oc.minesweeper.remove_mines_in([cell])
|
||||
var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell])
|
||||
if not removed.is_empty():
|
||||
oc._emit_row("mine_neutralized", "fx_seal_smith", cell, ri, "封印匠·危险格中立移除", 0, {}, "")
|
||||
"preview_area":
|
||||
var board_p: BoardData = oc.minesweeper.current_board()
|
||||
var types: Array = []
|
||||
var types: Array[Variant] = []
|
||||
for n in board_p.neighbors8(cell):
|
||||
var n_snap = oc._reward_economy.query_full_snapshot(n)
|
||||
if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))):
|
||||
|
||||
@@ -33,7 +33,7 @@ func apply_effect(effect_id: String, source: Dictionary, duration_override: int
|
||||
if not _open:
|
||||
push_warning("[增益状态] 域闭授予(EC1)")
|
||||
return false
|
||||
var row: Dictionary = db.status_effect_row(effect_id)
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(effect_id)
|
||||
if row.is_empty():
|
||||
push_warning("[增益状态] 未知 effect_id(EC2): %s" % effect_id)
|
||||
return false
|
||||
@@ -88,7 +88,7 @@ func _new_instance(effect_id: String, layers: int, dur: int, source: Dictionary,
|
||||
func query_active(side: String) -> Array:
|
||||
var out: Array[Dictionary] = []
|
||||
for inst in _instances:
|
||||
var row: Dictionary = db.status_effect_row(inst.effect_id)
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(inst.effect_id)
|
||||
if row.is_empty():
|
||||
continue
|
||||
if String(row.target_side) != side:
|
||||
|
||||
+10
-10
@@ -17,7 +17,7 @@
|
||||
class_name TreasureItems
|
||||
extends RefCounted
|
||||
|
||||
var _items: Array = [] # [{handle, item_id, used}]——受理序;Used 保留只读
|
||||
var _items: Array[Variant] = [] # [{handle, item_id, used}]——受理序;Used 保留只读
|
||||
var _handle_seq := 0 # 受理序句柄(单调递增——零随机)
|
||||
var _reveal_cells := {} # 真视集:Vector2i → true(惩罚格标记——插旗仍走既有 flag 路径)
|
||||
var _preview_cells := {} # 预读集:Vector2i → true(奖励身份徽章呈现挂点)
|
||||
@@ -52,9 +52,9 @@ func add_item(item_id: String, source_class: String, cell: Vector2i, round_index
|
||||
|
||||
# 枚举序=受理序;Used 保留只读;逐件含 usable 板级零信息预判(调用方喂板面回调)
|
||||
func query_inventory(board_has_preview_info: Callable) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for rec in _items:
|
||||
var row: Dictionary = rec.duplicate()
|
||||
var row: Dictionary[Variant, Variant] = rec.duplicate()
|
||||
row["usable"] = _usable_now(String(rec.item_id), board_has_preview_info)
|
||||
out.append(row)
|
||||
return out
|
||||
@@ -92,10 +92,10 @@ func discard_item(handle: int) -> bool:
|
||||
# 真视恒可用(零雷窗口=信息非空);AP 类恒可用(组合判据取 OR——任一效果有信息即可用);
|
||||
# 纯预读行 = 板上还有未开格才可用。(积点件不入使用面——反哺批⑤自动兑付无玩家入口)
|
||||
func _usable_now(item_id: String, board_has_preview_info: Callable) -> bool:
|
||||
var row: Dictionary = _item_row(item_id)
|
||||
var row: Dictionary[Variant, Variant] = _item_row(item_id)
|
||||
if row.is_empty():
|
||||
return false
|
||||
var kinds: Array = row.get("effect_kinds", [])
|
||||
var kinds: Array[Variant] = row.get("effect_kinds", [])
|
||||
if kinds.has("mine_reveal") or kinds.has("ap_grant"):
|
||||
return true
|
||||
return bool(board_has_preview_info.call()) if board_has_preview_info.is_valid() else true
|
||||
@@ -127,11 +127,11 @@ func validate_use(handle: int, target: Vector2i, board_size: Vector2i, cell_unre
|
||||
return _reject("句柄不存在")
|
||||
if bool(rec.used):
|
||||
return _reject("道具已使用(Used 不可逆)")
|
||||
var row: Dictionary = _item_row(String(rec.item_id))
|
||||
var row: Dictionary[Variant, Variant] = _item_row(String(rec.item_id))
|
||||
if row.is_empty() or bool(row.get("retired", false)) or bool(row.get("deferred", false)):
|
||||
return _reject("道具未激活")
|
||||
var kinds: Array = row.get("effect_kinds", [])
|
||||
var resolved: Array = []
|
||||
var kinds: Array[Variant] = row.get("effect_kinds", [])
|
||||
var resolved: Array[Variant] = []
|
||||
if kinds.has("mine_reveal") or kinds.has("reward_preview") or kinds.has("reveal_tiles") or kinds.has("safe_dig"):
|
||||
# 空间效果(迭代 22 秘宝批 T3 扩面——真视 reveal_tiles/天眼复合 safe_dig 入空间解析;
|
||||
# 真视窄域护栏 REVEAL_KINDS 同守):
|
||||
@@ -206,7 +206,7 @@ func consume(handle: int) -> Dictionary:
|
||||
# 随图消亡——停点处无战斗在飞,天然空集)。
|
||||
|
||||
func snapshot_inventory() -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for rec in _items:
|
||||
var cell: Vector2i = rec.get("origin_cell", Vector2i(-1, -1))
|
||||
out.append({
|
||||
@@ -224,7 +224,7 @@ func restore_inventory(items: Array) -> void:
|
||||
_handle_seq = 0
|
||||
for row in items:
|
||||
_handle_seq += 1
|
||||
var cell_arr: Array = row.get("origin_cell", [-1, -1])
|
||||
var cell_arr: Array[Variant] = row.get("origin_cell", [-1, -1])
|
||||
_items.append({
|
||||
"handle": _handle_seq,
|
||||
"item_id": String(row.get("item_id", "")),
|
||||
|
||||
@@ -51,7 +51,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides
|
||||
push_warning("[敌人系统] Alive 相位重复 spawn(相位守卫)")
|
||||
return false
|
||||
# 行集数组口径(2026-10-07 行集多行批——resolver 返回该坐标全部行、文件序)
|
||||
var rowset: Array = db.resolve_enemy_rowset(level_index, node_depth)
|
||||
var rowset: Array[Variant] = db.resolve_enemy_rowset(level_index, node_depth)
|
||||
if rowset.is_empty():
|
||||
push_error("[敌人系统] 遭遇行解析失败/越域 (%d,%d)" % [level_index, node_depth])
|
||||
assert(false, "遭遇行悬空(R1 前置守卫)")
|
||||
@@ -65,7 +65,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides
|
||||
# (恢复点/治疗包的满血守卫与治疗上限随之失效——切片登记接口语义澄清)
|
||||
_hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "enemy_id": "hero", "armor_type": hero_armor_type if hero_armor_type != "" else db.forge_default_armor_type(), "attack_type": hero_attack_type if hero_attack_type != "" else db.forge_default_attack_type()} # 护甲/攻击类型轴快照(迭代 23 T2——hero.tres 两轴删除后改读注入值,空串回退锻造工坊配置默认值 normal/light=行为连续;甲效矩阵消费面)
|
||||
_enemies.clear() # 难度曲线与Boss DifficultyTable 修饰(2026-09-12 结构位兑现):施加于行值、行值快照之前——MVP 空表 = 恒 1.0 无行为(AC-10);混编行集内全部敌种同乘((L,D) 精确匹配一次取值——F9 同乘不改变异,敌人系统 v0.15)
|
||||
var mod: Dictionary = db.resolve_difficulty_modifiers(level_index, node_depth)
|
||||
var mod: Dictionary[Variant, Variant] = db.resolve_difficulty_modifiers(level_index, node_depth)
|
||||
var hp_mult: float = float(mod.get("hp_mult", 1.0))
|
||||
var atk_mult: float = float(mod.get("atk_mult", 1.0))
|
||||
# 逐行数量定值(敌人系统 零随机纪律——骰定在 战斗结算 遭遇骰层按行完成,本层收确定值;
|
||||
@@ -73,7 +73,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides
|
||||
var per_row_counts: Array[int] = []
|
||||
var total_instances := 0
|
||||
for row_i in range(rowset.size()):
|
||||
var row_i_dict: Dictionary = rowset[row_i]
|
||||
var row_i_dict: Dictionary[Variant, Variant] = rowset[row_i]
|
||||
var ovr: int = int(unit_count_overrides[row_i]) if row_i < unit_count_overrides.size() else -1
|
||||
var count: int = ovr if ovr >= 1 else int(row_i_dict.unit_count)
|
||||
if count < EnemyTable.UNIT_COUNT_MIN or count > EnemyTable.UNIT_COUNT_MAX:
|
||||
@@ -87,7 +87,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides
|
||||
return false
|
||||
var handle := 0
|
||||
for row_i in range(rowset.size()):
|
||||
var row: Dictionary = rowset[row_i]
|
||||
var row: Dictionary[Variant, Variant] = rowset[row_i]
|
||||
var eovr: int = int(enemy_atk_overrides[row_i]) if row_i < enemy_atk_overrides.size() else -1
|
||||
for i in range(per_row_counts[row_i]):
|
||||
var eff_row_atk: int = eovr if eovr >= 0 else int(row.atk)
|
||||
@@ -174,7 +174,7 @@ func special_skills(u: Dictionary) -> Array:
|
||||
for e in u.pool:
|
||||
if _entry_locked(e):
|
||||
continue
|
||||
var srow: Dictionary = db.skill_row(String(e.skill_id))
|
||||
var srow: Dictionary[Variant, Variant] = db.skill_row(String(e.skill_id))
|
||||
if not srow.is_empty() and String(srow.type) != "basic":
|
||||
out.append({"skill_id": String(e.skill_id), "weight": int(e.weight)})
|
||||
return out
|
||||
@@ -184,7 +184,7 @@ func special_skills(u: Dictionary) -> Array:
|
||||
# 一次过门、单向闩〔实例标记,不解锁回退〕。门=确定性谓词零随机;返回本拍新解锁的 skill_id 列表
|
||||
# 供编排层出归因行〔每技恰一次——闩已置位不再返回〕)
|
||||
func check_skill_unlocks(u: Dictionary) -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
if u.is_empty() or _despawned or u.get("is_hero", false) or not bool(u.get("alive", false)):
|
||||
return out
|
||||
for e in u.pool:
|
||||
@@ -233,7 +233,7 @@ func commit_action(u: Dictionary) -> void:
|
||||
if sid == "":
|
||||
_diag("commit_action 无待发动作(兑现序违例)")
|
||||
return
|
||||
var srow: Dictionary = db.skill_row(sid)
|
||||
var srow: Dictionary[Variant, Variant] = db.skill_row(sid)
|
||||
var cd: int = int(srow.get("cooldown_beats", 0)) if not srow.is_empty() else 0
|
||||
for e in u.pool:
|
||||
if String(e.skill_id) == sid:
|
||||
|
||||
@@ -88,7 +88,7 @@ func validate() -> Array:
|
||||
# 池模板(敌人技能池批——单表结构校验;跨表构成纪律在 ConfigDB):
|
||||
# 非空池 + 条目 {skill_id 非空, weight > 0} + 池内 skill_id 不重复。
|
||||
# 普攻冷却恒 0(SkillTable schema 锁死)→ 可摇集合恒非空,"空池不可能"由此结构保证。
|
||||
var pool: Array = row.get("pool", [])
|
||||
var pool: Array[Variant] = row.get("pool", [])
|
||||
if pool.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s pool 空池(禁——技能池行必带池模板)" % eid})
|
||||
var pool_ids := {}
|
||||
@@ -96,7 +96,7 @@ func validate() -> Array:
|
||||
if not (entry_v is Dictionary):
|
||||
errs.append({"category": "domain", "message": "%s pool 条目非字典: %s" % [eid, str(entry_v)]})
|
||||
continue
|
||||
var entry: Dictionary = entry_v
|
||||
var entry: Dictionary[Variant, Variant] = entry_v
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if sid == "":
|
||||
errs.append({"category": "domain", "message": "%s pool 条目 skill_id 空" % eid})
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
class_name FeatTable
|
||||
extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary] 键 feat_id
|
||||
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 feat_id
|
||||
|
||||
## 稀有度词表(四级——与令牌池 TIERS 同名词族;令牌档 = rarity 直映射)
|
||||
const RARITIES := ["common", "excellent", "rare", "legendary"]
|
||||
@@ -37,7 +37,7 @@ const TRIGGER_KEYS := ["on", "when", "do"]
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
var errs: Array[Variant] = []
|
||||
if rows.is_empty():
|
||||
return errs # 空表合法态(T1 基建先行——逐件填行)
|
||||
var seen := {}
|
||||
|
||||
@@ -62,7 +62,7 @@ func validate() -> Array:
|
||||
continue
|
||||
# 类型轴:武器=attack_type 域、甲=armor_type 域;每型恰一路线(三武器三甲各一)
|
||||
var axis := String(row.get("type_axis", ""))
|
||||
var domain: Array = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES
|
||||
var domain: Array[Variant] = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES
|
||||
if not domain.has(axis):
|
||||
errs.append({"category": "enum", "message": "%s type_axis 出域 %s: %s" % [rid, domain, axis]})
|
||||
var type_key := "%s|%s" % [kind, axis]
|
||||
|
||||
@@ -49,7 +49,7 @@ func validate() -> Array:
|
||||
# 模板池(关卡节点图生成 v0.4——M-3 迭代 5 T5):row.templates 非空 = 池形态,池内全模板加载期校验
|
||||
# (违规致命——不只抽中的那张);空 = 旧单图形态(row.nodes 直读,向后兼容零迁移)。
|
||||
var node_lists: Array[Array] = [] # [ [上下文标签, nodes 数组], ... ]
|
||||
var templates: Array = row.get("templates", [])
|
||||
var templates: Array[Variant] = row.get("templates", [])
|
||||
if templates.is_empty():
|
||||
node_lists.append(["", row.get("nodes", [])])
|
||||
else:
|
||||
@@ -115,7 +115,7 @@ func _validate_graph(L: int, nodes: Array, is_graph: bool, tag: String = "") ->
|
||||
var nexts: Array[Array] = [] # 每节点出边 id 数组
|
||||
var id_to_idx := {}
|
||||
for i in nodes.size():
|
||||
var node: Dictionary = nodes[i]
|
||||
var node: Dictionary[Variant, Variant] = nodes[i]
|
||||
var nid: int = int(node.get("node_depth", i)) if is_graph else i
|
||||
if is_graph and not node.has("node_depth"):
|
||||
errs.append({"category": "structural", "message": "level %d%s 图形态节点[%d] 缺 node_depth(全节点须编号化)" % [L, tag, i]})
|
||||
@@ -190,7 +190,7 @@ func _validate_graph(L: int, nodes: Array, is_graph: bool, tag: String = "") ->
|
||||
errs.append({"category": "structural", "message": "level %d 节点 %d 沿连线不可达 Boss" % [L, ids[i]]})
|
||||
# ⑧ U2 目检新规则(迭代 5 返工批 2026-09-29):BFS 同深度层节点数上限 5(分支宽度天花板)
|
||||
var layer_of := {}
|
||||
var bfs: Array = [entries[0]]
|
||||
var bfs: Array[Variant] = [entries[0]]
|
||||
layer_of[entries[0]] = 0
|
||||
while not bfs.is_empty():
|
||||
var cur: int = bfs.pop_front()
|
||||
@@ -214,7 +214,7 @@ func _reachable(start: int, adj: Array, n: int) -> Dictionary:
|
||||
var seen := {}
|
||||
if start < 0 or start >= n:
|
||||
return seen
|
||||
var queue: Array = [start]
|
||||
var queue: Array[Variant] = [start]
|
||||
seen[start] = true
|
||||
while not queue.is_empty():
|
||||
var cur: int = queue.pop_front()
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
class_name MetaTreeTable
|
||||
extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary] 键 node_id
|
||||
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 node_id
|
||||
|
||||
const VEINS := ["spine", "forge", "keystone", "attune"]
|
||||
const KINDS := ["stat", "rule", "unlock", "socket", "fork", "easter"]
|
||||
@@ -47,7 +47,7 @@ const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy"]
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
var errs: Array[Variant] = []
|
||||
if rows.is_empty():
|
||||
return errs # 空表合法态(建表先行——内容随换代批落)
|
||||
var seen := {}
|
||||
@@ -81,7 +81,7 @@ func validate() -> Array:
|
||||
var price := int(row.get("price", 0))
|
||||
if price < 0 or price > 9999:
|
||||
errs.append({"category": "domain", "message": "%s price 越域 [0,9999]: %d" % [nid, price]})
|
||||
var fx: Dictionary = row.get("effects", {}) as Dictionary
|
||||
var fx: Dictionary[Variant, Variant] = row.get("effects", {}) as Dictionary
|
||||
for ek in fx.keys():
|
||||
if not (STAT_KEYS.has(String(ek)) or RULE_KEYS.has(String(ek))):
|
||||
errs.append({"category": "zero_flat", "message": "%s effects 键出域(stat 线/rule 词表封闭): %s" % [nid, String(ek)]})
|
||||
@@ -100,7 +100,7 @@ func validate() -> Array:
|
||||
if not fx.is_empty():
|
||||
errs.append({"category": "cross_field", "message": "%s fork 行主 effects 须空(效果只放 a/b 双键)" % nid})
|
||||
for skey in ["effects_a", "effects_b"]:
|
||||
var sd: Dictionary = row.get(skey, {}) as Dictionary
|
||||
var sd: Dictionary[Variant, Variant] = row.get(skey, {}) as Dictionary
|
||||
if sd.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s %s 缺失或空(双态节点须双侧效果)" % [nid, skey]})
|
||||
for k in sd.keys():
|
||||
|
||||
@@ -83,7 +83,7 @@ func validate() -> Array:
|
||||
if d < 1 or d > 99 or (i > 0 and d >= int(roll_tier_discounts[i - 1])):
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_discounts[%d] 越域/非降序(档位不设零档): %d" % [i, d]})
|
||||
for atk_type in ATTACK_TYPE_KEYS:
|
||||
var row: Dictionary = armor_effectiveness.get(atk_type, {})
|
||||
var row: Dictionary[Variant, Variant] = armor_effectiveness.get(atk_type, {})
|
||||
for armor in ARMOR_KEYS:
|
||||
var pct: int = int(row.get(armor, -99))
|
||||
if pct < -3 or pct > 3:
|
||||
|
||||
@@ -57,7 +57,7 @@ func validate() -> Array:
|
||||
errs.append({"category": "cross_field", "message": "gp_drops %s 违反 roll_min(%d) ≤ mean(%d) ≤ roll_max(%d)——均值恒等期望锚" % [tier, rmin, mean, rmax]})
|
||||
# level_gp_bonus / price_level_bonus:level_index 连续从 1 递增 + 加值域
|
||||
for family in ["level_gp_bonus", "price_level_bonus"]:
|
||||
var arr: Array = get(family)
|
||||
var arr: Array[Variant] = get(family)
|
||||
var seen := {}
|
||||
for row in arr:
|
||||
var li: int = int(row.get("level_index", 0))
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
class_name TreasureItemTable
|
||||
extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary] 键 item_id
|
||||
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 item_id
|
||||
|
||||
# 计数键封闭枚举(原 AttunementGrowthTable.COUNTER_KEYS——迭代 21 T5 谓词表退役后就地内联;
|
||||
# 迭代 22 T1 换代:clear_count(旧工坊按关累计渠道)与 C6 通关口径(通关=打通整局冒险)不符,
|
||||
@@ -85,7 +85,7 @@ const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kin
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
var errs: Array[Variant] = []
|
||||
if rows.is_empty():
|
||||
# 空表合法态(2026-09-17 用户拍板——切片期无道具内容;F2 覆盖检查仅对非空清单执行)
|
||||
return errs
|
||||
@@ -104,13 +104,13 @@ func validate() -> Array:
|
||||
var retired: bool = bool(row.get("retired", false))
|
||||
var deferred: bool = bool(row.get("deferred", false))
|
||||
# dimension_tags:五维词表 + 非空(退役/失义行仍须登记维度——语义面保留)
|
||||
var tags: Array = row.get("dimension_tags", [])
|
||||
var tags: Array[Variant] = row.get("dimension_tags", [])
|
||||
if tags.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s dimension_tags 空" % iid})
|
||||
for tag in tags:
|
||||
if not DIMENSIONS.has(String(tag)):
|
||||
errs.append({"category": "enum", "message": "%s dimension_tag 出域 %s: %s" % [iid, str(DIMENSIONS), str(tag)]})
|
||||
var kinds: Array = row.get("effect_kinds", [])
|
||||
var kinds: Array[Variant] = row.get("effect_kinds", [])
|
||||
var target: String = String(row.get("target", "none"))
|
||||
if not TARGETS.has(target):
|
||||
errs.append({"category": "enum", "message": "%s target 出域 %s: %s" % [iid, str(TARGETS), target]})
|
||||
@@ -217,7 +217,7 @@ func validate() -> Array:
|
||||
if not kind_seen.has("ap_charge_safe") and charge_n != 0:
|
||||
errs.append({"category": "cross_field", "message": "%s 非 ap_charge_safe 行 charge_n 应 0,实得 %d" % [iid, charge_n]})
|
||||
# unlock_requires 谓词行:空 = 已解锁(首批全解锁合法态,底稿 §2.2);非空键 ∈ 计数键封闭枚举
|
||||
var ureqs: Array = row.get("unlock_requires", [])
|
||||
var ureqs: Array[Variant] = row.get("unlock_requires", [])
|
||||
var ukeys := {}
|
||||
for req in ureqs:
|
||||
var ukey: String = String(req.get("key", ""))
|
||||
@@ -234,7 +234,7 @@ func validate() -> Array:
|
||||
|
||||
# 同调行词表(form 非空)——装配覆盖层/效果接线/同调位上限的唯一取数口径(迭代 11 第①批 T1)
|
||||
func attunement_rows() -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in rows:
|
||||
if String(row.get("form", "")) != "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
|
||||
out.append(row)
|
||||
@@ -244,7 +244,7 @@ func attunement_rows() -> Array:
|
||||
# 过渡行词表(form 空 = 切片期过渡形态活行)——掉落投放/停点池/过渡覆盖面的取数口径;
|
||||
# 同调行永不入过渡渠道。T7 退役面切换后本口径随渠道消亡。
|
||||
func transitional_rows() -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in rows:
|
||||
if String(row.get("form", "")) == "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
|
||||
out.append(row)
|
||||
@@ -253,7 +253,7 @@ func transitional_rows() -> Array:
|
||||
|
||||
# 活行词表(非 retired 非 deferred)——投放校验/奖励节点池/F2 覆盖面的唯一取数口径
|
||||
func active_rows() -> Array:
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for row in rows:
|
||||
if not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
|
||||
out.append(row)
|
||||
|
||||
@@ -73,9 +73,9 @@ static func phase_text(round_index: int, battle_result: String, waiting: bool, b
|
||||
# 零内容期每坐标恰一行,行为零变化)
|
||||
static func boss_name(runner, db) -> String:
|
||||
if runner != null:
|
||||
var node: Dictionary = runner.current_node()
|
||||
var node: Dictionary[Variant, Variant] = runner.current_node()
|
||||
if String(node.get("type", "")) == "boss":
|
||||
var rowset: Array = db.resolve_enemy_rowset(int(runner.level_row.level_index), int(node.get("node_depth", -1)))
|
||||
var rowset: Array[Variant] = db.resolve_enemy_rowset(int(runner.level_row.level_index), int(node.get("node_depth", -1)))
|
||||
if not rowset.is_empty():
|
||||
return String(rowset[0].get("display_name", ""))
|
||||
return "遗迹守卫"
|
||||
@@ -173,12 +173,12 @@ static func adventure_summary_text(summary: Dictionary) -> String:
|
||||
# 钥石入账明细文案(元进度实现批;迭代 10 改口径——钥石全源 = Boss 逐杀掉落,「大奖励」行退役;
|
||||
# name_lookup = item_id → 显示名投影,由组合根自 meta 表喂入)
|
||||
static func keystone_settle_text(detail: Dictionary, name_lookup: Dictionary = {}) -> String:
|
||||
var parts: Array = []
|
||||
var parts: Array[Variant] = []
|
||||
for d in detail.get("boss_drops", []):
|
||||
parts.append("第 %d 关 Boss %s +%d" % [int(d.get("level", 0)),
|
||||
"首杀" if bool(d.get("first", false)) else "重复杀", int(d.get("amount", 0))])
|
||||
var line := "钥石入账:" + (" · ".join(parts) if not parts.is_empty() else "本局无掉落")
|
||||
var unlocks: Array = detail.get("new_unlocks", [])
|
||||
var unlocks: Array[Variant] = detail.get("new_unlocks", [])
|
||||
if not unlocks.is_empty():
|
||||
var names: Array[String] = []
|
||||
for iid in unlocks:
|
||||
|
||||
@@ -123,8 +123,8 @@ func on_presented(payload: Dictionary) -> void:
|
||||
func present_node_curio() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
_curio_context = "reward_node"
|
||||
var node_draw: Dictionary = adventure.node_curio_candidates()
|
||||
var node_cands: Array = node_draw.get("candidates", []) as Array
|
||||
var node_draw: Dictionary[Variant, Variant] = adventure.node_curio_candidates()
|
||||
var node_cands: Array[Variant] = node_draw.get("candidates", []) as Array
|
||||
if node_cands.is_empty():
|
||||
# 空池降级(2026-10-06 用户令)——不开面、不挂节点停点(present_node_curio 未调即未挂),
|
||||
# 兑付确认直落回图选路(auto-sim「node_entered 回调内立即 continue_after_node」先例)
|
||||
@@ -141,7 +141,7 @@ func present_node_curio() -> void:
|
||||
|
||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复)
|
||||
func show_pick() -> void:
|
||||
var payload: Dictionary = _pending_curio
|
||||
var payload: Dictionary[Variant, Variant] = _pending_curio
|
||||
_open_modal.call(curio_overlay)
|
||||
curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array))
|
||||
var adventure = _adventure_get.call()
|
||||
@@ -151,7 +151,7 @@ func show_pick() -> void:
|
||||
|
||||
|
||||
# 刷新呈现投喂单点(免费余量 X/Y + GP 付费价 + 冒险钱包可付——读数与商店 set_refresh_display 同构)
|
||||
func _feed_refresh_display(adventure) -> void:
|
||||
func _feed_refresh_display(adventure: AdventureRunner) -> void:
|
||||
var runner = _runner_get.call()
|
||||
var gp := int(runner.gp) if runner != null else 0
|
||||
var cost: int = adventure.curio_refresh_gp_cost()
|
||||
@@ -161,7 +161,7 @@ func _feed_refresh_display(adventure) -> void:
|
||||
func _curio_card_texts(candidates: Array) -> Array:
|
||||
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|feat_id" 复合键(同 kind
|
||||
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed)
|
||||
var out: Array = []
|
||||
var out: Array[Variant] = []
|
||||
for tok in candidates:
|
||||
var kind := String((tok as Dictionary).get("kind", ""))
|
||||
var tier := String((tok as Dictionary).get("tier", ""))
|
||||
@@ -203,7 +203,7 @@ func on_refresh() -> void:
|
||||
if not refreshed:
|
||||
return
|
||||
if _curio_context == "reward_node":
|
||||
var st: Dictionary = adventure.node_curio_pending_state()
|
||||
var st: Dictionary[Variant, Variant] = adventure.node_curio_pending_state()
|
||||
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array))
|
||||
_feed_refresh_display(adventure)
|
||||
|
||||
@@ -224,7 +224,7 @@ func on_pick_confirmed(item_id: String) -> void:
|
||||
# T5——节点渠道门票拿到即开:候选已被替换为专长面,本地读在途态重绘(零 emit——context 保持 reward_node,
|
||||
# 后续兑付/确认仍走节点路径回图选路)
|
||||
if _curio_context == "reward_node" and kind == "feat_ticket":
|
||||
var st2: Dictionary = adventure.node_curio_pending_state()
|
||||
var st2: Dictionary[Variant, Variant] = adventure.node_curio_pending_state()
|
||||
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st2.get("candidates", []) as Array))
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func reset() -> void:
|
||||
func _start_battle(level: int, carry_hp: int) -> bool:
|
||||
if not _level_mode or _level_idx == 0:
|
||||
rng.new_adventure(_seed_value.call())
|
||||
var ov: Dictionary = _hero_override.call() # E4:生效值经 encounter 传入——零表写
|
||||
var ov: Dictionary[Variant, Variant] = _hero_override.call() # E4:生效值经 encounter 传入——零表写
|
||||
settlement = BattleOrchestrator.new(db, rng)
|
||||
battle_created.emit(settlement, level, carry_hp)
|
||||
var hp: int = carry_hp if carry_hp > 0 else int(ov.max_hp)
|
||||
|
||||
@@ -23,7 +23,7 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
|
||||
# 共账本〔level_runner bonus{atk,max_hp,armor_class}〕;空加算=整节隐藏〔视图内空态纪律〕。
|
||||
# 呈现只读投影零核心触碰;行文格式=代拍板白名单项)
|
||||
if runner != null and level_active:
|
||||
var b: Dictionary = runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger(P1-4 收口)
|
||||
var b: Dictionary[Variant, Variant] = runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger(P1-4 收口)
|
||||
var lines: Array[String] = []
|
||||
if int(b.get("atk", 0)) != 0:
|
||||
lines.append("攻击 %+d" % int(b.get("atk", 0)))
|
||||
@@ -44,11 +44,11 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
|
||||
info.set_board_variants([]) # 战斗域角标出战斗即隐(首发批 2026-10-04——空态纪律)
|
||||
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
||||
return
|
||||
var s: Dictionary = settlement.presentation_snapshot()
|
||||
var s: Dictionary[Variant, Variant] = settlement.presentation_snapshot()
|
||||
# 地形变异常驻角标投影(全局作用域变异首发批 2026-10-04——板级变异快照 → 文案行;
|
||||
# 空态隐藏=特性段先例;战斗全程常驻防中局遗忘)
|
||||
info.set_board_variants(board_variant_lines(db, s.get("board_variants", [])))
|
||||
var hero: Dictionary = s.hero
|
||||
var hero: Dictionary[Variant, Variant] = s.hero
|
||||
if hero.is_empty() or not bool(hero.get("alive", false)):
|
||||
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
||||
return
|
||||
@@ -60,7 +60,7 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
|
||||
# ---- B 区主角站桩(HP/状态唯一展示面)+ C 区数据框(攻击/AC) ----
|
||||
|
||||
static func _refresh_hero(stage: BattleStageView, info: HeroInfoPanelView, s: Dictionary) -> void:
|
||||
var hero: Dictionary = s.hero
|
||||
var hero: Dictionary[Variant, Variant] = s.hero
|
||||
var hp: int = int(hero.hp)
|
||||
var max_hp: int = int(hero.max_hp)
|
||||
var atk: int = int(hero.atk)
|
||||
@@ -102,7 +102,7 @@ static func feat_holdings_lines(db, held: Array) -> Array[String]:
|
||||
return rlines
|
||||
var counts := {}
|
||||
for rid in held:
|
||||
var rrow: Dictionary = db.feat_row(String(rid))
|
||||
var rrow: Dictionary[Variant, Variant] = db.feat_row(String(rid))
|
||||
var fname := String(rrow.get("display_name", String(rid)))
|
||||
counts[fname] = int(counts.get(fname, 0)) + 1
|
||||
for fname in counts.keys():
|
||||
@@ -118,7 +118,7 @@ static func board_variant_lines(db, ids: Array) -> Array[String]:
|
||||
if db == null or db.variation == null:
|
||||
return lines
|
||||
for vid in ids:
|
||||
var row: Dictionary = db.variation.row_by_id(String(vid))
|
||||
var row: Dictionary[Variant, Variant] = db.variation.row_by_id(String(vid))
|
||||
if not row.is_empty():
|
||||
lines.append(String(row.get("display_name", vid)))
|
||||
lines.append("回复与护甲资源减少")
|
||||
|
||||
+21
-21
@@ -104,7 +104,7 @@ var _bonus = null # 三选一与替换子交互(curio_
|
||||
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
|
||||
|
||||
# 排播队列只读委托面(探针/测试读 main._row_queue——RowPacer 迁移后保留的同名可读面,共享同一 Array 引用)
|
||||
var _row_queue: Array:
|
||||
var _row_queue: Array[Variant]:
|
||||
get:
|
||||
return _row_pacer.pending_rows()
|
||||
|
||||
@@ -159,7 +159,7 @@ func _on_pump_ended() -> void:
|
||||
func _replay_locked_click() -> void:
|
||||
if run_scope.locked_click.is_empty() or not _row_queue.is_empty():
|
||||
return
|
||||
var click: Dictionary = run_scope.locked_click
|
||||
var click: Dictionary[Variant, Variant] = run_scope.locked_click
|
||||
run_scope.locked_click = {}
|
||||
_on_board_action(click["cell"], click["kind"])
|
||||
|
||||
@@ -259,7 +259,7 @@ func _build_ui() -> void:
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
|
||||
# < 商店 < 营地 < 设置 < 状态面板(只读) < 生涯页(只读·迭代 18 卡④) < 确认弹窗 < 调试浮窗;
|
||||
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
|
||||
var overlays: Dictionary = UiAssembler.build_overlays(self, db)
|
||||
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(self, db)
|
||||
legend_overlay = overlays.legend_overlay
|
||||
node_overlay = overlays.node_overlay
|
||||
_end_overlay = overlays.end_overlay
|
||||
@@ -332,7 +332,7 @@ func _build_ui() -> void:
|
||||
_menu_screen.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口
|
||||
_menu_screen.quitbtn.pressed.connect(_quit_game) # 二轮修改批 D1——退出游戏提级一级菜单
|
||||
|
||||
var inter: Dictionary = UiAssembler.build_inter_overlay(self)
|
||||
var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(self)
|
||||
_inter_overlay = inter.overlay
|
||||
_inter_label = inter.label
|
||||
inter.next_btn.pressed.connect(_on_inter_next_pressed)
|
||||
@@ -420,7 +420,7 @@ func _start_adventure_from_menu() -> void:
|
||||
# 互斥确认弹窗化(二轮修改批 A——用户目检裁决「弹窗提示」):有断点时开新冒险弹实底确认框,
|
||||
# 「开新冒险」提交(弃旧断点+写盘)/「取消」回菜单
|
||||
if save_manager.has_breakpoint():
|
||||
var bp: Dictionary = save_manager.breakpoint_snapshot()
|
||||
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
|
||||
confirm_dialog.open("已有未完冒险(第 %d 关)——开新冒险将覆盖旧断点" % maxi(1, int(bp.get("level_index", 1))),
|
||||
"开新冒险", "取消")
|
||||
return
|
||||
@@ -454,7 +454,7 @@ func _refresh_resume_button() -> void:
|
||||
var has: bool = save_manager.has_breakpoint()
|
||||
_resume_btn.visible = has
|
||||
if has:
|
||||
var bp: Dictionary = save_manager.breakpoint_snapshot()
|
||||
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
|
||||
_resume_btn.text = "▶ 继续上局(第 %d 关 · 自最近停点恢复)" % maxi(1, int(bp.get("level_index", 1)))
|
||||
|
||||
|
||||
@@ -462,7 +462,7 @@ func _refresh_resume_button() -> void:
|
||||
func _resume_from_menu() -> void:
|
||||
if not save_manager.has_breakpoint():
|
||||
return
|
||||
var bp: Dictionary = save_manager.breakpoint_snapshot()
|
||||
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
|
||||
_close_modal(debug_overlay) # 开冒险路径显式关浮窗(迭代 18 卡⑦ T2 防护——续玩同口径)
|
||||
_start_overlay.visible = false
|
||||
_begin_session(true)
|
||||
@@ -506,7 +506,7 @@ func _wire_adventure() -> void:
|
||||
# 注:runner 侧 resume_adventure 受理面现收 attunement/attune_refresh_y/tree_rules 三键
|
||||
# (2026-10-08 尾裁微批b 卡⓪受理闭环——tree_rules 受理补齐,传参/受理两侧对齐,披露句退役)。
|
||||
static func build_adventure_params(save_manager_ref, meta_ref, extra: Dictionary = {}) -> Dictionary:
|
||||
var params: Dictionary = {
|
||||
var params: Dictionary[Variant, Variant] = {
|
||||
"hero_override": meta_ref.hero_override() if meta_ref != null else {},
|
||||
"attack_type": meta_ref.hero_attack_type() if meta_ref != null else "",
|
||||
"armor_type": meta_ref.hero_armor_type() if meta_ref != null else "",
|
||||
@@ -548,7 +548,7 @@ func _on_adventure_level_started(level_index: int, display_name: String) -> void
|
||||
# 迭代 19 卡⓪②——开场行改传当前生效值(runner.effective_atk/effective_max_hp 与战斗面板同源:
|
||||
# 表值+永久强化 flat+加算区 flat 后乘调试浮窗 pct,本回调时点 runner 已带全部加成);「含面板加成」
|
||||
# 后缀判断同步改认局外加成——永久强化 atk/max_hp flat 或调试浮窗 pct 任一存在才带,白板局不带
|
||||
var ov: Dictionary = meta.hero_override()
|
||||
var ov: Dictionary[Variant, Variant] = meta.hero_override()
|
||||
log_view.append_bb(Narrator.level_open_line(display_name, int(run_scope.adventure.master_seed),
|
||||
run_scope.runner.effective_atk(), run_scope.runner.effective_max_hp(),
|
||||
int(ov.get("atk", 0)) > 0 or int(ov.get("max_hp", 0)) > 0
|
||||
@@ -664,14 +664,14 @@ func _start_debug_levels() -> void:
|
||||
_fatal_screen("战斗启动失败——见输出日志")
|
||||
|
||||
|
||||
func _on_debug_battle_created(settlement_ref, _level: int, _carry: int) -> void:
|
||||
func _on_debug_battle_created(settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
# begin_battle 前接手 + 接线(ADR-0001:signal.connect 唯一归组合根)。settlement 必须先于 begin_battle
|
||||
# 就位——begin 期间发出的 board_changed 要用新板重建棋盘并触发一次性提示(缺陷①同款时序义务)
|
||||
run_scope.settlement = settlement_ref
|
||||
_wire_battle(settlement_ref)
|
||||
|
||||
|
||||
func _on_debug_battle_ready(settlement_ref, _level: int, _carry: int) -> void:
|
||||
func _on_debug_battle_ready(settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
run_scope.settlement = settlement_ref
|
||||
board_view.rebuild(run_scope.settlement.minesweeper.current_board(), run_scope.settlement.minesweeper)
|
||||
_check_board_hints()
|
||||
@@ -717,7 +717,7 @@ func _variant_mask_lookup(c: Vector2i) -> bool:
|
||||
if b == null:
|
||||
return false
|
||||
for vid in b.variant_ids(c):
|
||||
var vrow: Dictionary = db.variation.row_by_id(String(vid))
|
||||
var vrow: Dictionary[Variant, Variant] = db.variation.row_by_id(String(vid))
|
||||
if String(vrow.get("effect", "")) == "hide_number":
|
||||
return true
|
||||
return false
|
||||
@@ -732,7 +732,7 @@ func _variant_mark_lookup(c: Vector2i) -> String:
|
||||
if b == null:
|
||||
return ""
|
||||
for vid in b.variant_ids(c):
|
||||
var vrow: Dictionary = db.variation.row_by_id(String(vid))
|
||||
var vrow: Dictionary[Variant, Variant] = db.variation.row_by_id(String(vid))
|
||||
if String(vrow.get("effect", "")) == "flat_damage":
|
||||
return "flat_damage"
|
||||
return ""
|
||||
@@ -749,7 +749,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
var result: String = String(payload.get("result", ""))
|
||||
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
|
||||
HudPresenter.battle_end_feedback(stage, ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道(T3 收口批)
|
||||
var decision: Dictionary = _debug.on_battle_finished(payload)
|
||||
var decision: Dictionary[Variant, Variant] = _debug.on_battle_finished(payload)
|
||||
match String(decision.outcome):
|
||||
"next_layer":
|
||||
_inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [
|
||||
@@ -784,7 +784,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
# 此刻必在档,语义=续玩恢复面的合计即存档合计;与 _bonus_summary_text 同款守卫式)
|
||||
func _gp_summary_text(payload: Dictionary) -> String:
|
||||
if run_scope.runner == null:
|
||||
var bp: Dictionary = save_manager.breakpoint_snapshot()
|
||||
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
|
||||
return "战利品 GP +%d · 合计 %d" % [int(payload.get("gp_dropped", 0)), int(bp.get("gp", 0))]
|
||||
return "战利品 GP +%d · 合计 %d" % [int(payload.get("gp_dropped", 0)), int(run_scope.runner.gp)]
|
||||
|
||||
@@ -793,7 +793,7 @@ func _gp_summary_text(payload: Dictionary) -> String:
|
||||
func _bonus_summary_text() -> String:
|
||||
if run_scope.runner == null:
|
||||
return ""
|
||||
var b: Dictionary = run_scope.runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger
|
||||
var b: Dictionary[Variant, Variant] = run_scope.runner.run_ledger.bonus # 迭代 27 T3——加算区住 RunLedger
|
||||
var parts: Array[String] = []
|
||||
if int(b.get("atk", 0)) != 0:
|
||||
parts.append("攻 %+d" % int(b.get("atk", 0)))
|
||||
@@ -806,7 +806,7 @@ func _bonus_summary_text() -> String:
|
||||
|
||||
# ==================== core↔view 接线(战斗信号统一走这里)====================
|
||||
|
||||
func _wire_battle(settlement_ref) -> void:
|
||||
func _wire_battle(settlement_ref: BattleOrchestrator) -> void:
|
||||
settlement_ref.step_row.connect(_on_step_row)
|
||||
settlement_ref.battle_finished.connect(_on_battle_finished)
|
||||
settlement_ref.round_started.connect(_on_round_started)
|
||||
@@ -858,7 +858,7 @@ func _on_round_started(ri: int) -> void:
|
||||
# 开战横幅——板级地形变异预告(全局作用域变异首发批 2026-10-04):首轮快照 board_variants
|
||||
# 非空才发射(Boss 图恒带一型=替换制;普通图空快照零发射);轮横幅下方叠行,view 内判减动效。
|
||||
if ri == 1 and run_scope.settlement != null:
|
||||
var bvs: Array = run_scope.settlement.presentation_snapshot().get("board_variants", [])
|
||||
var bvs: Array[Variant] = run_scope.settlement.presentation_snapshot().get("board_variants", [])
|
||||
if not bvs.is_empty():
|
||||
var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs)
|
||||
if not vlines.is_empty():
|
||||
@@ -1145,7 +1145,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
if treasure_sel != 0 and kind == "dig":
|
||||
_treasure_ui.attempt_use(treasure_sel, cell)
|
||||
return
|
||||
var outcomes: Array = run_scope.settlement.minesweeper.settle_batch([{"action": kind, "cell": cell}])
|
||||
var outcomes: Array[Variant] = run_scope.settlement.minesweeper.settle_batch([{"action": kind, "cell": cell}])
|
||||
var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted)
|
||||
logger.log_action(kind, cell, accepted)
|
||||
if run_scope.runner != null and run_scope.runner.treasure_inventory != null:
|
||||
@@ -1172,7 +1172,7 @@ func _on_stop_pressed() -> void:
|
||||
if _input_locked():
|
||||
return
|
||||
# stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济)
|
||||
var outcomes: Array = run_scope.settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var outcomes: Array[Variant] = run_scope.settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
logger.log_action("stop", Vector2i(-1, -1), not outcomes.is_empty() and bool(outcomes[0].accepted))
|
||||
if not outcomes.is_empty() and String(outcomes[0].get("outcome", "")) == "voided":
|
||||
# voided ≠ 拒绝——独立文案(pattern 1:防静默改判读感欺骗)
|
||||
@@ -1376,5 +1376,5 @@ func _seed_value() -> int:
|
||||
if _seed_edit.text.is_valid_int():
|
||||
return int(_seed_edit.text)
|
||||
if save_manager.is_first_adventure_done():
|
||||
return randi()
|
||||
return randi() # ADR-0007-exempt(非首局空框引擎熵——组合根建种子不进玩法 RNG 消费,static_scan S1/S2 标记豁免)
|
||||
return 42
|
||||
@@ -55,7 +55,7 @@ func has_open() -> bool:
|
||||
func has_open_excluding_tags(exclude_tags: Array) -> bool:
|
||||
for m in _list:
|
||||
if is_instance_valid(m) and m.visible:
|
||||
var mt: Array = _tags.get(m, [])
|
||||
var mt: Array[Variant] = _tags.get(m, [])
|
||||
var excluded := false
|
||||
for t in exclude_tags:
|
||||
if mt.has(t):
|
||||
|
||||
@@ -50,7 +50,7 @@ func log_path() -> String:
|
||||
return _path
|
||||
|
||||
|
||||
func attach_battle(settlement) -> void:
|
||||
func attach_battle(settlement: BattleOrchestrator) -> void:
|
||||
settlement.step_row.connect(_on_step_row)
|
||||
settlement.settlement_snapshot.connect(_on_settlement_snapshot)
|
||||
settlement.battle_finished.connect(_on_battle_finished_log)
|
||||
@@ -94,10 +94,10 @@ func log_action(kind: String, cell: Vector2i, accepted: bool, extra: Dictionary
|
||||
func _on_settlement_snapshot(snap: Dictionary) -> void:
|
||||
_append(snap)
|
||||
# ux_round_exit{path_tag}:对齐上游封闭枚举 {stopped, ap_exhausted, cleared}
|
||||
var facts: Dictionary = snap.get("round_facts", {})
|
||||
var facts: Dictionary[Variant, Variant] = snap.get("round_facts", {})
|
||||
log_ux("ux_round_exit", {"path_tag": String(facts.get("path", ""))})
|
||||
# ux_round_input_ratio:原始输入动作数∶AP 消耗(本轮 AP 配额 − 快照时剩余 = 消耗)
|
||||
var economy: Dictionary = snap.get("economy", {})
|
||||
var economy: Dictionary[Variant, Variant] = snap.get("economy", {})
|
||||
var ap_left: int = int(economy.get("ap", 0))
|
||||
var consumed: int = clampi(ap_per_round - ap_left, 0, ap_per_round)
|
||||
log_ux("ux_round_input_ratio", {"inputs": _ux_inputs, "ap_consumed": consumed,
|
||||
|
||||
@@ -11,7 +11,7 @@ extends Node
|
||||
const PACE_SEGMENT_GAP := 0.3 # 段间阶梯延迟(代拍板:白名单 0.2–0.4s 内取值,追认清单呈报)
|
||||
const PACE_ROW_GAP := 0.22 # 段内间隔(三轮 #3 上调 0.06→0.22——逐条演出可感知,配合数字浮现/状态脉冲)
|
||||
|
||||
var _rows: Array = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行)
|
||||
var _rows: Array[Variant] = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行)
|
||||
var _pace_wait := 0.0 # 距下一行排播的剩余秒(_process 消耗)
|
||||
var _prev_segment := "" # 上一排播行的段型(段间/段内延迟判据)
|
||||
var _was_pumping := false # 排播活跃边沿(T17——末行消费帧触发结算视觉回挖掘态刷新)
|
||||
@@ -75,7 +75,7 @@ func _process(delta: float) -> void:
|
||||
_pace_wait -= delta
|
||||
if _pace_wait > 0.0:
|
||||
return
|
||||
var entry: Dictionary = _rows.pop_front()
|
||||
var entry: Dictionary[Variant, Variant] = _rows.pop_front()
|
||||
_consume_row(entry)
|
||||
var seg := String(entry.get("type", ""))
|
||||
_pace_wait = 0.0 if UiKit.motion_scale < 1.0 else (PACE_SEGMENT_GAP if seg != _prev_segment else PACE_ROW_GAP)
|
||||
|
||||
@@ -17,6 +17,6 @@ var boss_active := false # Boss 遭遇变体(阶段指示附 Boss
|
||||
var was_settling := false # 结算相位上升沿检测(结算轮光带触发——演出链批)
|
||||
var hints_shown := {} # 一次性非模态提示去重(每 run 一次:chord/no_adjacent)
|
||||
var settlement_stop_timer := ArmedTimer.new() # 卡⑥ 结算框挂起计时(T3/D3 P4 乙——窗口 0=挂起到显式回落)
|
||||
var pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||
var pending_candidates: Array[Variant] = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||
var locked_click := {} # 锁期点击缓存(T17 v3.1——最后一次;演播完边沿重放)
|
||||
var last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||
@@ -25,7 +25,7 @@ func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
|
||||
battle_root = VBoxContainer.new()
|
||||
add_child(battle_root)
|
||||
var battle_ui: Dictionary = UiAssembler.build_battle_ui(battle_root, get_node("/root/ConfigDB"))
|
||||
var battle_ui: Dictionary[Variant, Variant] = UiAssembler.build_battle_ui(battle_root, get_node("/root/ConfigDB"))
|
||||
stage = battle_ui.stage
|
||||
log_view = battle_ui.log_view
|
||||
info_panel = battle_ui.info_panel
|
||||
|
||||
@@ -20,7 +20,7 @@ const UiAssembler := preload("res://presentation/ui_assembler.gd") # 构建归
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
|
||||
var start: Dictionary = UiAssembler.build_start_overlay(self, get_node("/root/ConfigDB"))
|
||||
var start: Dictionary[Variant, Variant] = UiAssembler.build_start_overlay(self, get_node("/root/ConfigDB"))
|
||||
start_overlay = start.overlay
|
||||
box = start.box
|
||||
lvbtn = start.lvbtn
|
||||
|
||||
@@ -66,14 +66,14 @@ func on_slot_clicked(slot_handle: int) -> void:
|
||||
if slot_handle < 0:
|
||||
toast.show_toast("自动兑付件:满足条件自动生效,无需手动使用", "info")
|
||||
return
|
||||
var rec: Dictionary = {}
|
||||
var rec: Dictionary[Variant, Variant] = {}
|
||||
for r in inventory_projection():
|
||||
if int(r.get("handle", 0)) == slot_handle:
|
||||
rec = r
|
||||
break
|
||||
if rec.is_empty() or bool(rec.get("used", false)):
|
||||
return
|
||||
var arow: Dictionary = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
if String(arow.get("target", "none")) == "none":
|
||||
attempt_use(slot_handle) # AP 类:显式无目标即用(不再伪造哨兵坐标)
|
||||
return
|
||||
@@ -92,7 +92,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void:
|
||||
if settlement == null:
|
||||
return
|
||||
var target: Vector2i = cell if cell != null else NO_TARGET
|
||||
var v: Dictionary = settlement.use_treasure(handle, target)
|
||||
var v: Dictionary[Variant, Variant] = settlement.use_treasure(handle, target)
|
||||
if not bool(v.ok):
|
||||
toast.show_toast(String(v.get("reason", "无法使用")), "warn")
|
||||
return
|
||||
@@ -100,7 +100,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void:
|
||||
refresh_slots()
|
||||
refresh_overlays()
|
||||
board_view.refresh() # 迭代 13 T8/D6——移除格即时落图(无移除态的件 diff 零变化幂等)
|
||||
var arow: Dictionary = db.treasure_item_row(String(v.get("item_id", "")))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(v.get("item_id", "")))
|
||||
toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
||||
|
||||
|
||||
@@ -115,11 +115,11 @@ func inventory_projection() -> Array:
|
||||
if ms != null:
|
||||
cb = func(_c: Vector2i) -> bool:
|
||||
return ms.has_unrevealed() # 只读查询面(排雷玩法 has_unrevealed——手搓全板双循环收口)
|
||||
var out: Array = []
|
||||
var cs: Dictionary = settlement.attune_convert_state() if settlement != null else {}
|
||||
var out: Array[Variant] = []
|
||||
var cs: Dictionary[Variant, Variant] = settlement.attune_convert_state() if settlement != null else {}
|
||||
var auto_seen := false # 自动兑付件是否已随骑库存出现(恒否——charges=0 不入骑库存;守合成行重复)
|
||||
for rec in runner.treasure_inventory.query_inventory(cb):
|
||||
var arow: Dictionary = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
var dn := String(arow.get("display_name", ""))
|
||||
# 积点进度浮窗行(反哺批⑤——自动兑付件 C 区悬停 tooltip 补 N/M 进度;件 id 锁定=装配行唯一)
|
||||
var note := ""
|
||||
@@ -161,5 +161,5 @@ func refresh_overlays() -> void:
|
||||
var runner = _runner_get.call()
|
||||
if runner == null or runner.treasure_inventory == null or board_view == null:
|
||||
return
|
||||
var ov: Dictionary = runner.treasure_inventory.query_overlays()
|
||||
var ov: Dictionary[Variant, Variant] = runner.treasure_inventory.query_overlays()
|
||||
board_view.set_treasure_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))
|
||||
@@ -130,8 +130,8 @@ func _flush_pending_agg() -> void:
|
||||
if _pending_agg.is_empty():
|
||||
return
|
||||
for key in _pending_agg:
|
||||
var item: Dictionary = _pending_agg[key]
|
||||
var row: Dictionary = item["row"]
|
||||
var item: Dictionary[Variant, Variant] = _pending_agg[key]
|
||||
var row: Dictionary[Variant, Variant] = item["row"]
|
||||
var count: int = item["count"]
|
||||
var bb := _format_row(row, count)
|
||||
_round_rows.append(bb)
|
||||
|
||||
@@ -275,7 +275,7 @@ func _refresh_slab() -> void:
|
||||
|
||||
|
||||
# 战斗启动/换板:按板尺寸重建格单元(minesweeper 引用随之更新)
|
||||
func rebuild(board: BoardData, minesweeper) -> void:
|
||||
func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
|
||||
_board = board
|
||||
_minesweeper = minesweeper
|
||||
for child in _world.get_children():
|
||||
@@ -630,7 +630,7 @@ func _show_path_preview(c: Vector2i) -> void:
|
||||
# D3 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面)
|
||||
if _minesweeper == null:
|
||||
return
|
||||
var path: Array = _minesweeper.compute_move_path(c)
|
||||
var path: Array[Variant] = _minesweeper.compute_move_path(c)
|
||||
for pc in path:
|
||||
if _cells.has(pc):
|
||||
_cells[pc].dot_visible = true
|
||||
@@ -648,7 +648,7 @@ func _update_badge(c: Vector2i) -> String:
|
||||
return ""
|
||||
if _r0.has(c):
|
||||
return "" # R₀ 初始展开格无奖励身份(地图战斗资源 资格域 = S∖R₀)
|
||||
var rec: Dictionary = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面(禁直读 排雷玩法 状态字典)
|
||||
var rec: Dictionary[Variant, Variant] = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面(禁直读 排雷玩法 状态字典)
|
||||
if rec.get("kind", "") != "safe":
|
||||
return ""
|
||||
var ident = _identity_cb.call(c)
|
||||
@@ -704,7 +704,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
|
||||
flag = false
|
||||
u.tex_flag = null
|
||||
if revealed:
|
||||
var rec: Dictionary = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面
|
||||
var rec: Dictionary[Variant, Variant] = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面
|
||||
if rec.get("kind", "") == "mine":
|
||||
bg = COL_FLOOR
|
||||
glyph = "雷"
|
||||
@@ -910,7 +910,7 @@ func _load_board_tiles(board_type: String = "dungeon") -> void:
|
||||
# 方向稿 _v3_darken_floors 同式移植;现行瓦片 L≈0.036 低于带下沿故实为提亮;素材重产归资产批,
|
||||
# 本归一为程序代役(逐批装载时对 freshly-loaded 原图测光,幂等)。归一后逐像素明度顺路采集,
|
||||
# 驱动数字墨族按板自适应(_digit_ink_mode——均值分位同源同像素,禁二次解码)
|
||||
var lumas: Array = []
|
||||
var lumas: Array[Variant] = []
|
||||
for i in range(floors.size()):
|
||||
floors[i] = _normalize_floor_luma(floors[i], 0.10, lumas)
|
||||
var ink_mode := _digit_ink_mode(lumas)
|
||||
|
||||
@@ -238,8 +238,8 @@ func _rebuild_tree(data: Dictionary) -> void:
|
||||
c.queue_free()
|
||||
_tree_buy_btns.clear()
|
||||
_tree_fork_btns.clear()
|
||||
var tree: Array = data.get("tree", []) as Array
|
||||
var forks: Array = data.get("fork_groups", []) as Array
|
||||
var tree: Array[Variant] = data.get("tree", []) as Array
|
||||
var forks: Array[Variant] = data.get("fork_groups", []) as Array
|
||||
var vein_names := {"spine": "·脊柱淬炼·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
|
||||
var last_vein := ""
|
||||
for row in tree:
|
||||
@@ -407,7 +407,7 @@ func refresh_camp(data: Dictionary) -> void:
|
||||
for row in data.get("workshop", []):
|
||||
ws_by_id[String(row.get("item_id", ""))] = row
|
||||
for card in _workshop_rows:
|
||||
var row: Dictionary = ws_by_id.get(String(card.item_id), {})
|
||||
var row: Dictionary[Variant, Variant] = ws_by_id.get(String(card.item_id), {})
|
||||
if row.is_empty():
|
||||
continue
|
||||
var unlocked := bool(row.get("unlocked", false))
|
||||
@@ -422,7 +422,7 @@ func refresh_camp(data: Dictionary) -> void:
|
||||
elif mode == "condition":
|
||||
var parts: Array[String] = []
|
||||
for req_v in (row.get("reqs", []) as Array):
|
||||
var req: Dictionary = req_v as Dictionary
|
||||
var req: Dictionary[Variant, Variant] = req_v as Dictionary
|
||||
var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件"))
|
||||
parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))])
|
||||
prog_text = "、".join(parts)
|
||||
@@ -440,10 +440,10 @@ func refresh_camp(data: Dictionary) -> void:
|
||||
if card.get("buy_btn") != null:
|
||||
card.buy_btn.disabled = balance < int(card.get("price", 0))
|
||||
# 同调装配段状态就地刷(迭代 11 第①批 T4——Slots 点亮/满位禁选/Y 升级行余额联动)
|
||||
var att: Dictionary = data.get("attune", {})
|
||||
var att: Dictionary[Variant, Variant] = data.get("attune", {})
|
||||
if _attune_header_l != null:
|
||||
var slots_n := int(att.get("slots", 0))
|
||||
var eq: Array = att.get("equipped", [])
|
||||
var eq: Array[Variant] = att.get("equipped", [])
|
||||
_attune_header_l.text = "── 同调装配 Slots %d/%d ──" % [eq.size(), slots_n]
|
||||
var eq_set := {}
|
||||
for v in eq:
|
||||
@@ -454,7 +454,7 @@ func refresh_camp(data: Dictionary) -> void:
|
||||
# 满位禁选+未解锁禁装(迭代 22 T4——已装件恒可卸下;unlocked 快照随 rebuild 刷新)
|
||||
card2.btn.disabled = (not on and eq.size() >= slots_n) or (not on and not bool(card2.get("unlocked", false)))
|
||||
if _refresh_l != null:
|
||||
var rf: Dictionary = att.get("refresh", {})
|
||||
var rf: Dictionary[Variant, Variant] = att.get("refresh", {})
|
||||
_refresh_l.text = "三选一刷新 +%d 次/冒险 · Lv%d" % [int(rf.get("charges", 0)), int(rf.get("level", 0))]
|
||||
_refresh_btn.text = "已满级" if bool(rf.get("maxed", false)) else "升级 %d 钥石" % int(rf.get("price", 0))
|
||||
_refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0))
|
||||
@@ -508,7 +508,7 @@ func _rebuild_workshop(data: Dictionary) -> void:
|
||||
# 同调装配段重建(迭代 11 第①批 T4——秘宝匣柜台尾部段〔迭代 21 T6 随柜台迁入〕;Slots 点亮+
|
||||
# 九件行板+Y 升级行,程序占位面)
|
||||
func _rebuild_attune(data: Dictionary) -> void:
|
||||
var att: Dictionary = data.get("attune", {})
|
||||
var att: Dictionary[Variant, Variant] = data.get("attune", {})
|
||||
_attune_header_l = UiKit.label("",UiKit.FS_HINT, true, UiKit.INK)
|
||||
_workshop_holder.add_child(_attune_header_l)
|
||||
_attune_nodes.append(_attune_header_l)
|
||||
@@ -548,7 +548,7 @@ func _rebuild_attune(data: Dictionary) -> void:
|
||||
|
||||
func _rebuild_recent(data: Dictionary) -> void:
|
||||
var lines: Array[String] = []
|
||||
var recent: Array = data.get("recent", [])
|
||||
var recent: Array[Variant] = data.get("recent", [])
|
||||
if recent.is_empty():
|
||||
lines.append(" 最近冒险:(暂无记录——完成或结束一次冒险后入账)")
|
||||
for s in recent:
|
||||
|
||||
@@ -28,7 +28,7 @@ var _keystone_badge: UiKit.KeystoneBadge # 钥石余额(badge 工厂件—
|
||||
|
||||
func _init() -> void:
|
||||
# 全屏页壳(T6/T7 对齐批先例——暖亮底+石纹弱质感+暗角+56px 通栏顶条+内容列)
|
||||
var page: Dictionary = UiKit.page_shell(self, true)
|
||||
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, true)
|
||||
var top: HBoxContainer = page.top_row
|
||||
top.add_child(UiKit.label("生 涯",UiKit.FS_TITLE, true, UiKit.WARM))
|
||||
var top_sp := Control.new()
|
||||
|
||||
@@ -24,8 +24,8 @@ var _refresh_allowance := 0 # 组合根投喂免费余量(X/Y
|
||||
var _refresh_gp_cost := 0 # 组合根投喂 GP 刷新价(免费位用尽后的付费档读数)
|
||||
var _selected := "" # 点选暂存(确认前可改选)
|
||||
var _header := "" # 停点头部文案(选择态保持显示)
|
||||
var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面
|
||||
var _bcards: Array = [] # M7 bcard 引用列(select 高亮=set_selected)
|
||||
var _card_meta: Array[Variant] = [] # open_pick 缓存 [{id, text}]——高亮还原读面
|
||||
var _bcards: Array[Variant] = [] # M7 bcard 引用列(select 高亮=set_selected)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@@ -33,7 +33,7 @@ func _init() -> void:
|
||||
# 根底暗档)——56px 通栏顶条(标题+刷新图标钮/角标)+ 居中入账信息行(GP 汇总行承载)+
|
||||
# bcard 三竖卡(D1 232×352)+ 底部确认主钮与免费选择注记。差异白名单=免费选择/✓章/无价签。
|
||||
# 状态机不变:pick→result。M7 同构裁决遗产保留。
|
||||
var page: Dictionary = UiKit.page_shell(self, false)
|
||||
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, false)
|
||||
var top: HBoxContainer = page.top_row
|
||||
top.add_child(UiKit.label("战利品三选一",UiKit.FS_TITLE, true, UiKit.WARM))
|
||||
var top_sp := Control.new()
|
||||
@@ -137,7 +137,7 @@ func _select_card(id: String) -> void:
|
||||
_selected = id
|
||||
# 选中态=木描边+✓章(demo ⑥;set_selected 逐卡置真值防改选残留)
|
||||
for i in _bcards.size():
|
||||
var meta: Dictionary = _card_meta[i] if i < _card_meta.size() else {}
|
||||
var meta: Dictionary[Variant, Variant] = _card_meta[i] if i < _card_meta.size() else {}
|
||||
(_bcards[i] as UiKit.BCard).set_selected(String(meta.get("id", "")) == id)
|
||||
_confirm_btn.visible = true
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func effective_max_hp() -> int:
|
||||
# ---- 内部构建(标题/关闭固定于浮窗骨架,可扩展列表区居中滚动)----
|
||||
|
||||
func _build() -> void:
|
||||
var shell: Dictionary = UiKit.modal_shell(self, UiKit.BG_OVERLAY, Vector2(640, 0), 8)
|
||||
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, UiKit.BG_OVERLAY, Vector2(640, 0), 8)
|
||||
var box: VBoxContainer = shell.box
|
||||
|
||||
var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM)
|
||||
|
||||
@@ -16,9 +16,9 @@ var _atk_label: Label
|
||||
var _armor_label: Label
|
||||
signal treasure_slot_clicked(handle: int) # 迭代 7 T4——槽点击(组合根消费:AP 类即用/空间类进选中态)
|
||||
var item_slots: HBoxContainer # 秘宝槽(迭代 7 T4 接线——库存条常读+两步交互第一步)
|
||||
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
|
||||
var _slot_panels: Array[Variant] = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
|
||||
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
|
||||
var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
|
||||
var _slot_handles: Array[Variant] = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
|
||||
var _curio_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
|
||||
var _curio_list: VBoxContainer
|
||||
var _feat_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
|
||||
@@ -180,7 +180,7 @@ func set_treasure_inventory(items: Array, selected_handle: int) -> void:
|
||||
slot.add_theme_stylebox_override("panel",
|
||||
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4)))
|
||||
continue
|
||||
var rec: Dictionary = items[i]
|
||||
var rec: Dictionary[Variant, Variant] = items[i]
|
||||
_slot_handles.append(int(rec.get("handle", 0)))
|
||||
var used := bool(rec.get("used", false))
|
||||
var usable := bool(rec.get("usable", true))
|
||||
|
||||
@@ -282,7 +282,7 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新)
|
||||
func open_choices(cands: Array, runner) -> void:
|
||||
func open_choices(cands: Array, runner: LevelRunner) -> void:
|
||||
var was_visible := visible # 进入转场判据(就地刷新/模态栈恢复不重放淡入)
|
||||
_runner = runner
|
||||
_hover_id = -1
|
||||
@@ -400,7 +400,7 @@ func _on_canvas_input(ev: InputEvent) -> void:
|
||||
func _candidate_at(local_pos: Vector2) -> int:
|
||||
if _runner == null:
|
||||
return -1
|
||||
var nodes: Array = _runner.nodes()
|
||||
var nodes: Array[Variant] = _runner.nodes()
|
||||
var pos := _layout(nodes, _canvas)
|
||||
for nid in _cand_ids:
|
||||
var node := _node_by_id(nid, nodes)
|
||||
@@ -427,7 +427,7 @@ func _submit_choice(nid: int) -> void:
|
||||
func press_candidate(index: int) -> void:
|
||||
if index < 0 or index >= _cand_ids.size() or _runner == null:
|
||||
return
|
||||
var nodes: Array = _runner.nodes()
|
||||
var nodes: Array[Variant] = _runner.nodes()
|
||||
var node := _node_by_id(int(_cand_ids[index]), nodes)
|
||||
if node.is_empty():
|
||||
return
|
||||
@@ -447,12 +447,12 @@ func _id_of(node: Dictionary) -> int:
|
||||
func _draw_graph(canvas: Control) -> void:
|
||||
if _runner == null or not visible:
|
||||
return
|
||||
var nodes: Array = _runner.nodes()
|
||||
var nodes: Array[Variant] = _runner.nodes()
|
||||
var pos := _layout(nodes, canvas)
|
||||
var current_id: int = _runner.current_node_id()
|
||||
# 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析
|
||||
var visited: Array = _runner.visited_ids()
|
||||
var cand_ids: Array = _runner.next_candidate_ids()
|
||||
var visited: Array[Variant] = _runner.visited_ids()
|
||||
var cand_ids: Array[Variant] = _runner.next_candidate_ids()
|
||||
# 连线(通道)先行画(方块覆盖其上);悬停预承诺 = 所属通道再增亮(加粗)
|
||||
# T17 修复:选路停点(候选非空)非候选通道降透明——金岔路从结构网分离(干线共线重叠缺陷的
|
||||
# 视觉主因:不同源通道共用同一垂直干线,金边与石色边焊接成「整列可走」假象)
|
||||
@@ -549,7 +549,7 @@ func _assign_layers(nodes: Array, layer: Dictionary, slot: Dictionary) -> void:
|
||||
queue.append(entry)
|
||||
layer[entry] = 0
|
||||
while not queue.is_empty():
|
||||
var cur: Dictionary = queue.pop_front()
|
||||
var cur: Dictionary[Variant, Variant] = queue.pop_front()
|
||||
var l: int = int(layer[cur])
|
||||
layer_counts[l] = int(layer_counts.get(l, 0)) + 1
|
||||
slot[cur] = int(layer_counts[l]) - 1
|
||||
|
||||
@@ -13,7 +13,7 @@ var _btn: Button
|
||||
|
||||
func _init() -> void:
|
||||
# M5(UI 落码批):rest 兑付面板 plate9 石板化+暖金头带字(demo ④休息弹层;暗字上亮板 §7.2)
|
||||
var shell: Dictionary = UiKit.modal_shell(self, Color(0, 0, 0, 0.6), Vector2.ZERO, 14, true,
|
||||
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, Color(0, 0, 0, 0.6), Vector2.ZERO, 14, true,
|
||||
UiKit.panel_skinned(Vector4(26, 18, 26, 18)))
|
||||
var box: VBoxContainer = shell.box
|
||||
_label = UiKit.label("",UiKit.FS_SUB, true, UiKit.INK_TITLE)
|
||||
|
||||
@@ -26,7 +26,7 @@ func _init(store) -> void:
|
||||
|
||||
|
||||
# 探针隔离换注(冒烟/探针禁碰真实 user://settings.cfg——换注后按新 store 重挂控件态)
|
||||
func retarget(store) -> void:
|
||||
func retarget(store: SettingsStore) -> void:
|
||||
_store = store
|
||||
_sync()
|
||||
|
||||
@@ -37,7 +37,7 @@ func _build() -> void:
|
||||
# 减动效与结算演出动画=同一设置(语义命名批合并),一行承载。
|
||||
# T8(UI 设计对齐批 2026-10-06):两列对齐(label 列定宽 140——demo .setrow label/控制两列)+
|
||||
# 后果说明内联随行(demo .why 同排)+返回钮紧凑化(demo .btn9 紧凑——原 216 宽砖)。
|
||||
var shell: Dictionary = UiKit.modal_shell(self, UiKit.BG_BRIGHT, Vector2(560, 0), 8, true,
|
||||
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, UiKit.BG_BRIGHT, Vector2(560, 0), 8, true,
|
||||
UiKit.panel_skinned(Vector4(20, 16, 20, 16)))
|
||||
var box: VBoxContainer = shell.box
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func _init() -> void:
|
||||
# 角标+GP 框)+ 爬价规则行 + bcard 三竖卡居中 + 底部离开钮与池源注记。
|
||||
# M6 遗产:库存余量退役(关卡节点地图 v1.5-a 用户裁);刷新钮呈现位照摆(机制=MC-1 投喂)。
|
||||
# legacy 面(open_shop)行卡同壳呈现(shop=off 对照承载原样)。高内聚边界不变:纯视图 setter 面。
|
||||
var page: Dictionary = UiKit.page_shell(self, true)
|
||||
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, true)
|
||||
var top: HBoxContainer = page.top_row
|
||||
top.add_child(UiKit.label("商 店",UiKit.FS_TITLE, true, UiKit.WARM))
|
||||
var top_sp := Control.new()
|
||||
@@ -110,7 +110,7 @@ func refresh_state(products: Array, gp: int) -> void:
|
||||
for p in products:
|
||||
by_id[String(p.product_id)] = p
|
||||
for card in _card_rows:
|
||||
var p: Dictionary = by_id.get(String(card.product_id), {})
|
||||
var p: Dictionary[Variant, Variant] = by_id.get(String(card.product_id), {})
|
||||
if p.is_empty():
|
||||
continue
|
||||
card.price.text = "现价 %d 金币" % int(p.price)
|
||||
|
||||
@@ -21,7 +21,7 @@ var _tree_label: Label
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var shell: Dictionary = UiKit.modal_shell(self, Color(UiKit.DIM, 0.55), Vector2(560, 0), 10, true,
|
||||
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, Color(UiKit.DIM, 0.55), Vector2(560, 0), 10, true,
|
||||
UiKit.panel_skinned(Vector4(20, 16, 20, 16))) # 石板皮(缺位回退数据板——同设置面先例)
|
||||
var box: VBoxContainer = shell.box
|
||||
|
||||
@@ -103,7 +103,7 @@ func _rebuild_workshop(data: Dictionary) -> void:
|
||||
else:
|
||||
var parts: Array[String] = []
|
||||
for req_v in (row.get("reqs", []) as Array):
|
||||
var req: Dictionary = req_v as Dictionary
|
||||
var req: Dictionary[Variant, Variant] = req_v as Dictionary
|
||||
var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件"))
|
||||
parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))])
|
||||
prog_text = "、".join(parts)
|
||||
|
||||
@@ -165,7 +165,7 @@ func _on_slot_hover(slot: Control) -> void:
|
||||
# tooltip:来源 + 语义 + 剩余(≥13px;悬停只读反馈通道——无点击无写)
|
||||
if not slot.has_meta("eff"):
|
||||
return
|
||||
var eff: Dictionary = slot.get_meta("eff")
|
||||
var eff: Dictionary[Variant, Variant] = slot.get_meta("eff")
|
||||
var eid := String(eff.effect_id)
|
||||
var src: String = String(SOURCE_NAMES.get(String(eff.get("source_class", "")), String(eff.get("source_class", ""))))
|
||||
var semantic: String = String(EFFECT_SEMANTIC.get(eid, String(eff.get("display_name", ""))))
|
||||
|
||||
@@ -14,6 +14,10 @@ config/name="DungeonSweeperVerticalSlice"
|
||||
run/main_scene="res://presentation/main.tscn"
|
||||
config/features=PackedStringArray("4.7")
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[autoload]
|
||||
|
||||
ConfigDB="*res://core/config_db.gd"
|
||||
|
||||
@@ -82,6 +82,7 @@ func _split_tail() -> void:
|
||||
await process_frame
|
||||
await process_frame
|
||||
var autoload_ok: bool = get_root().get_node_or_null("/root/ConfigDB") != null and get_root().get_node_or_null("/root/SaveManager") != null
|
||||
_smoke_type_gate()
|
||||
if autoload_ok:
|
||||
await _smoke_scene_split() # 协程函数必须 await(无 await=发射后不管,首个 await 后即被 quit 截断)
|
||||
await _smoke_run_lifecycle()
|
||||
@@ -91,6 +92,17 @@ func _split_tail() -> void:
|
||||
quit(1 if _failures > 0 else 0)
|
||||
|
||||
|
||||
# ---- 类型闸断言(迭代 27 T5 V7——project.godot warning 配置在位〔源码断言〕+审计器脚本在位;
|
||||
# 审计器归零判定走 static_scan S9 闸机,此处兜配置面)----
|
||||
func _smoke_type_gate() -> void:
|
||||
var pj: String = FileAccess.get_file_as_string("res://project.godot")
|
||||
check(pj.contains("gdscript/warnings/untyped_declaration=1"),
|
||||
"V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕")
|
||||
var scan_src: String = FileAccess.get_file_as_string("res://tests/static_scan.sh")
|
||||
check(scan_src.contains("audit_type_hints.py"),
|
||||
"V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕")
|
||||
|
||||
|
||||
# ---- 三屏场景化断言(迭代 27 T1 V2——独立实例化+关键节点在位+组合根树序/z 序保序)----
|
||||
func _smoke_scene_split() -> void:
|
||||
print("--- 迭代27 T1 三屏场景化(V2:屏壳独立实例化+组合根装配保序)---")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# 正式工程(core/ 存在)时执行;否则优雅跳过(工程在 game/core——2026-09-24 转正结构批后仓库根布局)。
|
||||
# 纪律:新违规案例必须先补本脚本规则再修码(design/dev/code-conventions.md 缺陷-规范对照表)。
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
cd "$(dirname "$0")/../.." # 迭代 27 T5 修正——2026-09-24 布局迁移后 /.. 落 game/ 致 game/core 守卫恒跳过(静默空转遗产),S 闸规则路径均为仓根相对
|
||||
|
||||
if [ ! -d game/core ]; then
|
||||
echo "[static_scan] game/core 不存在(工程未创建)——跳过,退出 0"
|
||||
@@ -13,11 +13,19 @@ fi
|
||||
|
||||
fail=0
|
||||
|
||||
# S9 — 类型闸(迭代 27 T5——code-conventions §4 机器闸:产品码无类型容器/有值返回缺类型/参数缺类型)
|
||||
if python tools/audit_type_hints.py; then
|
||||
echo "[static_scan] S9 ok"
|
||||
else
|
||||
echo "[static_scan] S9 FAIL: 类型闸存量未归零(R1/R2/R3 见上清单)"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# S1/S2 — 玩法代码禁 randomize()/自建 RandomNumberGenerator/全局随机函数(绕开 RngService 的口子)
|
||||
# 2026-09-24 首次实跑收敛(转正结构批):违规签名=裸 randomize/rand* 调用(实例方法 .randi() 排除)
|
||||
# + RngService 之外的 RandomNumberGenerator.new();接收服务注入流的类型标注不属违规(core 五文件 15 处实证)
|
||||
hits=$(grep -rnE '\brandomize[[:space:]]*\(|(^|[^.[:alnum:]_])randi[[:space:]]*\(|(^|[^.[:alnum:]_])randf[[:space:]]*\(|(^|[^.[:alnum:]_])randf_range[[:space:]]*\(' \
|
||||
game/core game/presentation --include='*.gd' 2>/dev/null | grep -vE '^[^:]+:[0-9]+:[[:space:]]*#' | wc -l)
|
||||
game/core game/presentation --include='*.gd' 2>/dev/null | grep -vE '^[^:]+:[0-9]+:[[:space:]]*#' | grep -v 'ADR-0007-exempt' | wc -l)
|
||||
hits2=$(grep -rn 'RandomNumberGenerator\.new()' game/core game/presentation --include='*.gd' 2>/dev/null | grep -vc rng_service)
|
||||
if [ "$hits" -gt 0 ] || [ "$hits2" -gt 0 ]; then echo "[static_scan] S1/S2 FAIL: 全局随机/RNG 自建命中 $hits+$hits2 处"; grep -rnE '\brandomize[[:space:]]*\(|(^|[^.[:alnum:]_])randi[[:space:]]*\(|(^|[^.[:alnum:]_])randf[[:space:]]*\(|(^|[^.[:alnum:]_])randf_range[[:space:]]*\(' game/core game/presentation --include='*.gd' 2>/dev/null; grep -rn 'RandomNumberGenerator\.new()' game/core game/presentation --include='*.gd' 2>/dev/null | grep -v rng_service; fail=1; else echo "[static_scan] S1/S2 ok"; fi
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""audit_type_hints.py — 迭代27 T5 类型闸机器审计(code-conventions §4 语言纪律的机器闸)。
|
||||
|
||||
产品码 = game/core + game/data + game/presentation(smoke/tests 豁免——测量面容器直构是惯例)。
|
||||
三条规则:
|
||||
R1 无类型容器声明:显式 Dictionary/Array 非参数化类型注解(审计 §2.2「73 处」同口径——
|
||||
`:= {}` 推断形态属类型推断合法面不算债)。
|
||||
R2 有值返回缺返回类型:func 无 `->` 且函数体内存在 `return <非空值>`(纯副作用 -> void
|
||||
不属本闸危害域,归静态写法面)。
|
||||
R3 参数缺类型:func 声明参数无 `: Type`(_init 豁免——构造注入面形态自由)。
|
||||
白名单:行内 `# type-hint: ignore` 豁免该行(V7 显式白名单通道)。
|
||||
退出码:0=归零,1=有存量(逐条列 file:line)。用法:python tools/audit_type_hints.py
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SCAN_DIRS = ["game/core", "game/data", "game/presentation"]
|
||||
|
||||
R1A_UNPARAM = re.compile(r"\bvar\s+\w+\s*:\s*(Dictionary|Array)\b(?!\[)")
|
||||
R1B_LITERAL = re.compile(r"^\s*var\s+\w+\s*(:?=)\s*[\{\[]")
|
||||
R1B_TYPED = re.compile(r"^\s*var\s+\w+\s*:\s*[\w\[\]]+")
|
||||
FUNC_DECL = re.compile(r"^(\s*)func\s+(_?\w+)\s*\((.*)\)\s*(->\s*.+)?\s*(#.*)?$")
|
||||
PARAM_SPLIT = re.compile(r",(?![^(\[]*[)\]])")
|
||||
RETURN_VALUE = re.compile(r"^\s*return\s+\S")
|
||||
|
||||
|
||||
def strip_strings(line):
|
||||
line = re.sub(r'"[^"]*"', '""', line)
|
||||
line = re.sub(r"'[^']*'", "''", line)
|
||||
if line.lstrip().startswith("#"):
|
||||
return ""
|
||||
return line.split("#")[0]
|
||||
|
||||
|
||||
def audit_file(path):
|
||||
hits = []
|
||||
lines = path.read_text(encoding="utf-8").split("\n")
|
||||
for idx, raw in enumerate(lines):
|
||||
if "# type-hint: ignore" in raw:
|
||||
continue
|
||||
code = strip_strings(raw)
|
||||
# R1a 显式非参数化容器类型
|
||||
if R1A_UNPARAM.search(code):
|
||||
hits.append((idx + 1, "R1", raw.strip()[:90]))
|
||||
continue
|
||||
fm = FUNC_DECL.match(code)
|
||||
if not fm:
|
||||
continue
|
||||
indent, fname, params, ret = fm.group(1), fm.group(2), fm.group(3), fm.group(4)
|
||||
# R3 参数缺类型(_init 豁免)
|
||||
if fname != "_init" and params.strip():
|
||||
for prm in PARAM_SPLIT.split(params):
|
||||
prm = prm.strip()
|
||||
if prm and ":" not in prm:
|
||||
hits.append((idx + 1, "R3", "%s(参数 %s)" % (raw.strip()[:80], prm.split("=")[0].strip())))
|
||||
# R2 有值返回缺返回类型(函数体 = 缩进更深的后续连续块)
|
||||
if not ret and fname != "_init":
|
||||
body = []
|
||||
j = idx + 1
|
||||
while j < len(lines):
|
||||
l = lines[j]
|
||||
if l.strip() == "":
|
||||
j += 1
|
||||
continue
|
||||
if len(l) - len(l.lstrip("\t")) > len(indent):
|
||||
body.append(strip_strings(l))
|
||||
j += 1
|
||||
continue
|
||||
break
|
||||
if any(RETURN_VALUE.match(b) for b in body):
|
||||
hits.append((idx + 1, "R2", raw.strip()[:90]))
|
||||
return hits
|
||||
|
||||
|
||||
def main():
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
total = 0
|
||||
for d in SCAN_DIRS:
|
||||
for p in sorted((ROOT / d).rglob("*.gd")):
|
||||
for line, rule, text in audit_file(p):
|
||||
print("[%s] %s:%d: %s" % (rule, p.relative_to(ROOT).as_posix(), line, text))
|
||||
total += 1
|
||||
if total:
|
||||
print("[audit_type_hints] FAIL——存量 %d 处(R1 无类型容器/R2 有值返回缺类型/R3 参数缺类型)" % total)
|
||||
return 1
|
||||
print("[audit_type_hints] PASS——产品码类型闸归零(R1/R2/R3)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user