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

190 lines
7.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 增益状态统一容器:三方效果同构实例、单一时限量纲(先兑付后衰减)、按行叠层与授予时快照抗热重载。
## 实例按个体归属(增益状态 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 = {}
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