## VERTICAL SLICE - NOT FOR PRODUCTION ## 增益状态统一容器:三方效果同构实例、单一时限量纲(先兑付后衰减)、按行叠层与授予时快照抗热重载。 ## 实例按个体归属(增益状态 v1.5):主角一桶+每只敌各一桶——全体授予=授予路径逐只各发一条实例, ## 合并只在同体内(同名状态不同个体互不相干),个体死亡即清名下实例(purge_owner 单点)。 ## 零随机零信号——纯被动服务;授予唯一入口 apply_effect,满层拒绝零写入(拒收记未生效行归调用方)。 class_name StatusContainer extends RefCounted const OWNER_HERO := "hero" # 主角个体键(单桶——主角全体状态唯一的归属键) var db: ConfigDb var _open := false var _buckets: Dictionary[Variant, Variant] = {} # owner_key -> Array[Dictionary](桶内插入序);元素 = {effect_id, layers, remaining, source_class, origin_cell, round_index, snap_m, snap_unit, snap_display} # 个体键约定单点(拼写禁散落第二处——授予/死亡清/消费面一律经此导出): # 主角 = OWNER_HERO;敌人 = "e<生成序>"(生成序 = 开战逐只编号 handle_id 的 1 基序,沿顺位轴)。 static func owner_key_for(u: Dictionary) -> String: if bool(u.get("is_hero", false)): return OWNER_HERO assert(u.has("handle_id"), "[增益状态] 敌单位缺 handle_id(个体键导出沿生成序轴——单位违约)") return "e%d" % (int(u.get("handle_id", -1)) + 1) func _init(db_ref: ConfigDb) -> void: db = db_ref func notify_battle_start() -> bool: if _open: push_warning("[增益状态] Active 下重复 notify_battle_start(EC11)") return false _open = true _buckets.clear() return true # 唯一授予入口(挖掘期零直写——战斗结算 结算编排期统一调用)。 # owner_key(个体归属——增益状态 v1.5):实例挂到该个体桶,合并/衰减/查询全在桶内; # duration_override(敌人技能池批——增益技施放):技能行 buff_duration 覆盖状态行时长(-1 = 状态表值)。 # 施放是持续量的 owner(proposal §2.2 字段词汇表"buff_duration 增益持续回合数");其他授予路径传缺省。 func apply_effect(effect_id: String, owner_key: String, source: Dictionary = {}, duration_override: int = -1) -> bool: if not _open: push_warning("[增益状态] 域闭授予(EC1)") return false if owner_key == "": push_warning("[增益状态] 空 owner_key 授予(个体归属违约)") return false return _apply_open(effect_id, owner_key, source, duration_override) # 开域授予主路径(守卫已过——未知行警告与三臂受理;单出口布尔受理) func _apply_open(effect_id: String, owner_key: String, source: Dictionary, duration_override: int) -> bool: var accepted := false var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(effect_id) if row.is_empty(): push_warning("[增益状态] 未知 effect_id(EC2): %s" % effect_id) return false var policy: String = row.stack_policy var dur: int = int(row.duration_settlements) if duration_override < 1 else duration_override var snap := _grant_snapshot(row, source) var bucket: Array[Dictionary] = _bucket_of(owner_key) var hit: Dictionary[Variant, Variant] = {} for inst in bucket: if inst.effect_id == effect_id: hit = inst break match policy: "independent": bucket.append(_new_instance(effect_id, 1, dur, source, snap)) accepted = true "accumulate": # 命中加时长未命中追加(R += D 取本次授予生效时长;L 不变;快照沿首授) if hit.is_empty(): bucket.append(_new_instance(effect_id, 1, dur, source, snap)) else: hit.remaining = int(hit.remaining) + dur accepted = true "stack": # 满层拒零写入·未满加层·未命中追加(stack 授予不触碰 R;拒收由战斗结算记"奖励未生效"行) if hit.is_empty(): bucket.append(_new_instance(effect_id, 1, dur, source, snap)) accepted = true elif int(hit.layers) < int(row.max_stack): hit.layers = int(hit.layers) + 1 accepted = true _: push_warning("[增益状态] 未知 stack_policy") return accepted ## 授予时快照域(抗表重载——EC9;封印溢价贯穿——迭代14② T4 percent 形态同乘快照、缺省路径零扰动) func _grant_snapshot(row: Dictionary, source: Dictionary) -> Dictionary: var snap_m: int = int(row.magnitude) if source.has("magnitude_override"): snap_m = int(source["magnitude_override"]) return {"m": snap_m, "unit": String(row.unit), "display": String(row.display_name)} ## 个体桶取用(缺桶新建——实例只在桶内合并,不同体永不互见) func _bucket_of(owner_key: String) -> Array[Dictionary]: if not _buckets.has(owner_key): var fresh: Array[Dictionary] = [] _buckets[owner_key] = fresh return fresh return _buckets[owner_key] func _new_instance(effect_id: String, layers: int, dur: int, source: Dictionary, snap: Dictionary) -> Dictionary: return { "effect_id": effect_id, "layers": layers, "remaining": dur, "source_class": String(source.get("source_class", "")), "origin_cell": source.get("origin_cell", Vector2i(-1, -1)), "round_index": int(source.get("round_index", 0)), "snap_m": snap["m"], "snap_unit": snap["unit"], "snap_display": snap["display"], } # 只读投影(个体桶插入序稳定枚举;owner_key 随行出户 + 六归因字段 + display_name + 授予时快照) func query_active(owner_key: String) -> Array: var out: Array[Dictionary] = [] for inst in _buckets.get(owner_key, [] as Array[Dictionary]): var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(inst.effect_id) if row.is_empty(): continue out.append({ "owner_key": owner_key, "effect_id": inst.effect_id, "layers": inst.layers, "remaining_settlements": inst.remaining, "source_class": inst.source_class, "origin_cell": inst.origin_cell, "round_index": inst.round_index, "display_name": inst.snap_display, "snap_magnitude": inst.snap_m, "snap_unit": inst.snap_unit, "effective_value": int(inst.layers) * int(inst.snap_m), # L×m 加算(percent 越域不 clamp——收口归 战斗结算) }) return out # 侧汇总投影(主角侧 = hero 桶;敌方侧 = 各存活个体桶并集——死亡个体桶已随 purge_owner 删除, # 并集即全部在册实例;数值与旧阵营共享读法等价,服务计数类汇总消费面) func query_active_side(side: String) -> Array: if side == OWNER_HERO: return query_active(OWNER_HERO) var out: Array[Dictionary] = [] for key in _buckets.keys(): if String(key) != OWNER_HERO: out.append_array(query_active(String(key))) return out # 轮末衰减:先兑付后衰减;逐桶各 −1(每实例每结算恰一次);归 0 移除;返回移除数 k(恰一次执行由 战斗结算 编排信任承载) func decay_round() -> int: if not _open: return 0 # EC13 域闭兜底读法——容器恒空,无独立分支不诊断 var k := 0 for key in _buckets.keys(): var bucket: Array[Dictionary] = _buckets[key] var survivors: Array[Dictionary] = [] for inst in bucket: inst.remaining = int(inst.remaining) - 1 if inst.remaining <= 0: k += 1 else: survivors.append(inst) _buckets[key] = survivors return k # 个体死亡即清(死亡清唯一通道——死亡个体名下实例全部移除,不转移、不并回别处;返回移除数) func purge_owner(owner_key: String) -> int: var removed := (_buckets.get(owner_key, [] as Array[Dictionary]) as Array).size() _buckets.erase(owner_key) return removed func notify_battle_end() -> bool: if not _open: push_warning("[增益状态] BattleClosed 下重复 notify_battle_end(EC11)") return false _open = false _buckets.clear() # 全清不跨战斗 return true