T3组C·生成与编排换代(敌人表行集多行批——P1甲/Q8落码): unit_container.gd spawn行集受理(逐行按各自数量实例化+生成序=文件行序+handle_id连续派发;unit_count_override/enemy_atk_override改逐行数组[下标=行序,短数组缺省-1=该行行值路径,空数组=全行定值旧调用点零适配];行集实例总数≤EnemyTable.ROWSET_INSTANCE_MAX守卫fail-fast[骰后值唯一正确落点];难度修饰对行集全部行同乘F9)+enemy_table.gd补ROWSET_INSTANCE_MAX=4常量(与数量域同位单点收口);battle_orchestrator.gd遭遇骰逐行骰换代(Q8用户裁:每行各掷自己的区间骰,先逐行数量骰再逐行atk骰,单行行集判定/开流/消费序/消费次数逐位一致=零内容期随机流零变化;atk区间逐行同构属每行一骰预批准语义内·代拍板留痕);battle_narrator取首行口径T2已落(T2注释已登记P4乙知情代价);轻闸=冒烟805/0三闸全过+probe_gate含click全绿(残留Godot=0)——零内容期断言逐位零漂移保持
This commit is contained in:
1 parent
8690230573
commit
bc73fa3212
3 files changed
+95
-57
No files matched your search
@@ -178,30 +178,45 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# 2026-09-14 敌方攻击区间批扩展:行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值
|
||||
# (每拍抖动否决——数字可读性锚;中点 = 行值)。encounter 封闭流恰一 scope(数量骰先、atk 骰后);
|
||||
# 挖掘决策域之外——P1 合规;敌人系统 零随机纪律保持(收 override 即确定值)。
|
||||
# 行集数组口径(2026-10-07 行集多行批——本卡 T2 机械适配取首行保持行为零变化:遭遇骰读首行
|
||||
# 数量区间 = 全行集骰域〔多行后首行区间即行集骰域,知情代价登记 敌人系统 v0.15〕;
|
||||
# 每行各骰换代随批 T3)
|
||||
# 遭遇骰(难度曲线与Boss GDD Core Rule 8——2026-09-12 用户拍板):行有 unit_count_max 时按 [unit_count, max] 均匀骰;
|
||||
# 2026-09-14 敌方攻击区间批扩展:行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值
|
||||
# (每拍抖动否决——数字可读性锚;中点 = 行值)。encounter 封闭流恰一 scope(数量骰先、atk 骰后);
|
||||
# 挖掘决策域之外——P1 合规;敌人系统 零随机纪律保持(收 override 即确定值)。
|
||||
# 逐行骰换代(2026-10-07 行集多行批 Q8 用户裁 + P1 补充):混编行集**每行各掷自己的区间骰**——
|
||||
# 先按行序逐行数量骰、再按行序逐行 atk 骰(单行行集 = 既有一次数量骰 + 一次 atk 骰,判定条件、
|
||||
# 开流条件、消费序、消费次数逐位一致——零内容期随机流零变化;区间宽度为零的行不掷 = 「定值不骰」
|
||||
# 既有口径;atk 区间逐行同构 = 行值语义完整,代拍板随批留痕:属「每行一骰」预批准语义内的
|
||||
# 行字段区间骰逐行化,非新增随机源形态)。
|
||||
var erowset_pre: Array = 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 erow_pre: Dictionary = erowset_pre[0]
|
||||
var roll_lo: int = int(erow_pre.get("unit_count", 1))
|
||||
var roll_hi: int = int(erow_pre.get("unit_count_max", roll_lo))
|
||||
var needs_count: bool = roll_hi > roll_lo
|
||||
var needs_atk: bool = erow_pre.has("atk_min")
|
||||
var rolled := roll_lo
|
||||
var rolled_atk := -1
|
||||
var count_overrides: Array = []
|
||||
var atk_overrides: Array = []
|
||||
var needs_count := false
|
||||
var needs_atk := false
|
||||
for r in erowset_pre:
|
||||
if int(r.get("unit_count_max", int(r.get("unit_count", 1)))) > int(r.get("unit_count", 1)):
|
||||
needs_count = true
|
||||
if r.has("atk_min"):
|
||||
needs_atk = true
|
||||
if needs_count or needs_atk:
|
||||
var estream: RandomNumberGenerator = rng.open_stream("encounter")
|
||||
if needs_count:
|
||||
rolled = roll_lo + estream.randi_range(0, roll_hi - roll_lo)
|
||||
for r in erowset_pre:
|
||||
var lo: int = int(r.get("unit_count", 1))
|
||||
var hi: int = int(r.get("unit_count_max", lo))
|
||||
count_overrides.append((lo + estream.randi_range(0, hi - lo)) if hi > lo else lo)
|
||||
if needs_atk:
|
||||
rolled_atk = int(erow_pre.atk_min) + estream.randi_range(0, int(erow_pre.atk_max) - int(erow_pre.atk_min))
|
||||
for r in erowset_pre:
|
||||
if r.has("atk_min"):
|
||||
atk_overrides.append(int(r.atk_min) + estream.randi_range(0, int(r.atk_max) - int(r.atk_min)))
|
||||
else:
|
||||
atk_overrides.append(-1)
|
||||
# E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径)
|
||||
_hero_atk = int(enc.get("hero_atk", int(db.hero.atk)))
|
||||
if not _enemy_system.spawn(int(enc.level_index), int(enc.node_depth), int(enc.hero_hp), rolled,
|
||||
_hero_atk, int(enc.get("hero_max_hp", -1)), rolled_atk):
|
||||
if not _enemy_system.spawn(int(enc.level_index), int(enc.node_depth), int(enc.hero_hp), count_overrides,
|
||||
_hero_atk, int(enc.get("hero_max_hp", -1)), atk_overrides):
|
||||
_fatal("敌人系统 spawn 被拒")
|
||||
return false
|
||||
if not _install_new_board():
|
||||
|
||||
+61
-43
@@ -39,19 +39,21 @@ func _init(db_ref: ConfigDb) -> void:
|
||||
# 此前调用方写 db.hero 表值再由本类读表 = 共享可变单例传参(双写叠加 6→18→54 教训根源)。
|
||||
# enemy_atk_override(2026-09-14 敌方攻击区间批):遭遇骰同流的 spawn 期整战持久 roll 结果
|
||||
# (-1 = 表值定值路径;≥0 = 行值替换后再乘关卡 atk_mult)。敌人系统 零随机纪律保持。
|
||||
func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override: int = -1,
|
||||
atk_override: int = -1, max_hp_override: int = -1, enemy_atk_override: int = -1) -> bool:
|
||||
# 行集受理换代(2026-10-07 行集多行批 P1 甲):unit_count_overrides / enemy_atk_overrides 改**逐行数组**
|
||||
# (下标=行集行序,长度短于行集时缺省 -1 = 该行行值路径;空数组 = 全行定值——旧调用点零适配)。
|
||||
# 逐行实例化、生成序 = 文件行序(行序即契约,敌人系统 v0.15);行集实例总数(各行传入数量求和)
|
||||
# ≤ ENEMY_ROWSET_INSTANCE_MAX 守卫 fail-fast。
|
||||
func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides: Array = [],
|
||||
atk_override: int = -1, max_hp_override: int = -1, enemy_atk_overrides: Array = []) -> bool:
|
||||
if _spawned:
|
||||
push_warning("[敌人系统] Alive 相位重复 spawn(相位守卫)")
|
||||
return false
|
||||
# 行集数组口径(2026-10-07 行集多行批——resolver 返回该坐标全部行;本卡 T2 先机械适配取首行
|
||||
# 保持行为零变化,行集逐行实例化换代随批 T3)
|
||||
# 行集数组口径(2026-10-07 行集多行批——resolver 返回该坐标全部行、文件序)
|
||||
var rowset: Array = db.resolve_enemy_rowset(level_index, node_depth)
|
||||
if rowset.is_empty():
|
||||
push_error("[敌人系统] 遭遇行解析失败/越域 (%d,%d)" % [level_index, node_depth])
|
||||
assert(false, "遭遇行悬空(R1 前置守卫)")
|
||||
return false
|
||||
var row: Dictionary = rowset[0]
|
||||
var eff_atk: int = atk_override if atk_override >= 1 else int(db.hero.atk)
|
||||
var eff_max_hp: int = max_hp_override if max_hp_override >= 1 else int(db.hero.max_hp)
|
||||
if hero_hp > eff_max_hp or hero_hp <= 0:
|
||||
@@ -60,48 +62,64 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
|
||||
# max_hp = 生效上限(非入参 hero_hp)——2026-09-11 关卡扩展批修复:跨战斗携带 HP 时上限不得压扁
|
||||
# (恢复点/治疗包的满血守卫与治疗上限随之失效——切片登记接口语义澄清)
|
||||
_hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "enemy_id": "hero", "armor_type": String(db.hero.get("armor_type")) if db.hero.get("armor_type") else "light", "attack_type": String(db.hero.get("attack_type")) if db.hero.get("attack_type") else "normal"} # 护甲/攻击类型轴快照(2026-09-18 营地扩展前置批——可配置;甲效矩阵消费面)
|
||||
_enemies.clear() # 难度曲线与Boss DifficultyTable 修饰(2026-09-12 结构位兑现):施加于行值、行值快照之前——MVP 空表 = 恒 1.0 无行为(AC-10)
|
||||
_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 hp_mult: float = float(mod.get("hp_mult", 1.0))
|
||||
var atk_mult: float = float(mod.get("atk_mult", 1.0))
|
||||
# unit_count_override ≥1 = 战斗结算 遭遇骰结果(敌人系统 零随机纪律——收到即确定值;-1 = 行值定值路径)
|
||||
# 数量域常量单点 = EnemyTable.UNIT_COUNT_MIN/MAX(2026-10-02 修复批——与表校验同源)
|
||||
var count: int = unit_count_override if unit_count_override >= 1 else int(row.unit_count)
|
||||
if count < EnemyTable.UNIT_COUNT_MIN or count > EnemyTable.UNIT_COUNT_MAX:
|
||||
push_warning("[敌人系统] 实例化数量越域: %d" % count)
|
||||
# 逐行数量定值(敌人系统 零随机纪律——骰定在 战斗结算 遭遇骰层按行完成,本层收确定值;
|
||||
# 行 i 数量 = overrides[i](≥1 时)否则该行 unit_count 行值)
|
||||
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 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:
|
||||
push_warning("[敌人系统] 行 %d 实例化数量越域(单种数域 [%d,%d]): %d" % [row_i, EnemyTable.UNIT_COUNT_MIN, EnemyTable.UNIT_COUNT_MAX, count])
|
||||
return false
|
||||
per_row_counts.append(count)
|
||||
total_instances += count
|
||||
# 行集实例总数守卫(行集构成纪律③——各行骰后数量求和 ≤4;骰后值加载期不可判,此处 fail-fast)
|
||||
if total_instances > EnemyTable.ROWSET_INSTANCE_MAX:
|
||||
push_warning("[敌人系统] 行集实例总数越域(≤%d): %d" % [EnemyTable.ROWSET_INSTANCE_MAX, total_instances])
|
||||
return false
|
||||
for i in range(count):
|
||||
var eff_row_atk: int = enemy_atk_override if enemy_atk_override >= 0 else int(row.atk)
|
||||
# 实例池快照(proposal §2.2:开战把模板快照一份,冷却全零——与"生成拿到当时的表值快照"同一条快照纪律)
|
||||
var pool_snap: Array[Dictionary] = []
|
||||
for entry in row.get("pool", []):
|
||||
var snap_entry := {
|
||||
"skill_id": String(entry.get("skill_id", "")),
|
||||
"weight": int(entry.get("weight", 0)),
|
||||
"cooldown_left": 0,
|
||||
}
|
||||
# Boss 半血解锁技(迭代14④ T3——unlock_hp_pct 随池模板快照进实例池;缺省无字段=无门槛;
|
||||
# unlocked=单向闩标记,初始锁定)
|
||||
if entry.has("unlock_hp_pct"):
|
||||
snap_entry["unlock_hp_pct"] = int(entry.get("unlock_hp_pct", 0))
|
||||
snap_entry["unlocked"] = false
|
||||
pool_snap.append(snap_entry)
|
||||
_enemies.append({
|
||||
"handle_id": i,
|
||||
"enemy_id": String(row.enemy_id),
|
||||
"display_name": String(row.display_name),
|
||||
"hp": maxi(1, int(round(float(row.hp) * hp_mult))),
|
||||
"max_hp": maxi(1, int(round(float(row.hp) * hp_mult))),
|
||||
"atk": maxi(0, int(round(float(eff_row_atk) * atk_mult))),
|
||||
"pool": pool_snap, # 实例池(模板快照 + 冷却状态——独立摇号互不干涉)
|
||||
"pending_skill": "", # 本拍摇定动作(开轮摇号写入;兑现前死亡 = 作废不 commit)
|
||||
"roll_seq": 0, # 单敌摇号拍序(诊断标签——并流后不参与派生,ADR-0007 裁丙 2026-09-27)
|
||||
"basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动)
|
||||
"armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy)
|
||||
"armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批)
|
||||
"alive": true,
|
||||
"is_hero": false,
|
||||
})
|
||||
var handle := 0
|
||||
for row_i in range(rowset.size()):
|
||||
var row: Dictionary = 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)
|
||||
# 实例池快照(proposal §2.2:开战把模板快照一份,冷却全零——与"生成拿到当时的表值快照"同一条快照纪律)
|
||||
var pool_snap: Array[Dictionary] = []
|
||||
for entry in row.get("pool", []):
|
||||
var snap_entry := {
|
||||
"skill_id": String(entry.get("skill_id", "")),
|
||||
"weight": int(entry.get("weight", 0)),
|
||||
"cooldown_left": 0,
|
||||
}
|
||||
# Boss 半血解锁技(迭代14④ T3——unlock_hp_pct 随池模板快照进实例池;缺省无字段=无门槛;
|
||||
# unlocked=单向闩标记,初始锁定)
|
||||
if entry.has("unlock_hp_pct"):
|
||||
snap_entry["unlock_hp_pct"] = int(entry.get("unlock_hp_pct", 0))
|
||||
snap_entry["unlocked"] = false
|
||||
pool_snap.append(snap_entry)
|
||||
_enemies.append({
|
||||
"handle_id": handle,
|
||||
"enemy_id": String(row.enemy_id),
|
||||
"display_name": String(row.display_name),
|
||||
"hp": maxi(1, int(round(float(row.hp) * hp_mult))),
|
||||
"max_hp": maxi(1, int(round(float(row.hp) * hp_mult))),
|
||||
"atk": maxi(0, int(round(float(eff_row_atk) * atk_mult))),
|
||||
"pool": pool_snap, # 实例池(模板快照 + 冷却状态——独立摇号互不干涉)
|
||||
"pending_skill": "", # 本拍摇定动作(开轮摇号写入;兑现前死亡 = 作废不 commit)
|
||||
"roll_seq": 0, # 单敌摇号拍序(诊断标签——并流后不参与派生,ADR-0007 裁丙 2026-09-27)
|
||||
"basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动)
|
||||
"armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy)
|
||||
"armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批)
|
||||
"alive": true,
|
||||
"is_hero": false,
|
||||
})
|
||||
handle += 1
|
||||
_spawned = true
|
||||
_despawned = false
|
||||
return true
|
||||
|
||||
@@ -24,6 +24,11 @@ const ARMOR_TYPES := ["light", "ward", "heavy"]
|
||||
const UNIT_COUNT_MIN := 1
|
||||
const UNIT_COUNT_MAX := 3
|
||||
|
||||
# 行集实例总数上限(2026-10-07 行集多行批 P1 甲——行集构成纪律③:各行开战骰后数量求和 ≤4;
|
||||
# 「总数」= 实例数非行数〔Q9 二裁〕,行数不设上限。骰后值加载期不可判——守卫在 单位容器 spawn,
|
||||
# 常量单点在本表与数量域同位收口)
|
||||
const ROWSET_INSTANCE_MAX := 4
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array[Dictionary] = []
|
||||
|
||||
Reference in new issue
Block a user