Files
gplan-2/game/data/schemas/shop_table.gd
T

188 lines
8.7 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
## 商店表 schema:GP 掉落/关级加值/商品/共享池货架行族 + 爬价因子的类型化读取(行值 provisional;垂直切片验证:商店房 GP 购买兑现局内成长)。
## 消费面一律走 ConfigDB.shop_* 访问器,禁直读行集。
## 行族不记库存:货架=上限即库存,账本单点在冒险侧;products+加法定价=legacy 面(shop=off 对照),现役走 pool_shelf。
class_name ShopTable
extends Resource
@export var gp_drops: Array[Dictionary] = [] # Array[Dictionary] 键 tier
@export var level_gp_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
@export var products: Array[Dictionary] = [] # Array[Dictionary] 键 product_id
@export var price_level_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
@export var pool_shelf: Array[Dictionary] = [] # Array[Dictionary] 键 kind_id
@export var price_a: float = 0.0 # 进店已购次数爬价因子(provisional——0 = 未投值即致命域)
@export var price_b: float = 0.0 # 跨关递增因子(provisional)
@export var relic_base_price: float = 15.0 # relic 保底卡基价(provisional——池令牌非货架种,词表外专枝)
@export var shelf_size: int = 0 # 货架位数(零哨兵——缺字段越域 [1,6] 即加载致命)
const GP_TIERS := ["normal", "elite", "boss"]
const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"]
const SHELF_KINDS := ["attack", "max_hp", "armor", "ap", "feat_ticket", "heal"] # 货架候选词表(四切边后种 + 专长凭证行 + heal 补回复〔默认原则:分类内成员都入该渠道池〕)
func validate() -> Array:
var errs: Array[Dictionary] = []
_validate_families(errs)
_validate_gp_drops(errs)
_validate_level_bonus(errs)
_validate_products(errs)
_validate_pool_shelf(errs)
_validate_price_factors(errs)
return errs
# 行族空护栏(legacy 四轴 + pool_shelf 皆为 ConfigDB 消费面——缺族即致命)
func _validate_families(errs: Array[Dictionary]) -> void:
for family in ["gp_drops", "level_gp_bonus", "products", "price_level_bonus", "pool_shelf"]:
if get(family).is_empty():
errs.append({"category": "empty_table", "message": "ShopTable 行族为空: %s" % family})
# gp_drops:tier 词表封闭 + 掷定域 + 均值锚 + tier 唯一
func _validate_gp_drops(errs: Array[Dictionary]) -> void:
var seen_tier := {}
for row in gp_drops:
var tier: String = row.get("tier", "")
if not GP_TIERS.has(tier):
errs.append({"category": "enum", "message": "gp_drops tier 出域 {normal, elite, boss}: %s" % tier})
if seen_tier.has(tier):
errs.append({"category": "dup_key", "message": "gp_drops tier 重复: %s" % tier})
seen_tier[tier] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "gp_drops %s display_name 空" % tier})
var rmin: int = int(row.get("roll_min", -1))
var mean: int = int(row.get("mean", -1))
var rmax: int = int(row.get("roll_max", -1))
if rmin < 0 or mean < 0 or rmax < 0 or rmax > 999:
errs.append({"category": "domain", "message": "gp_drops %s 掷定域越域 [0,999]: %d..%d..%d" % [tier, rmin, mean, rmax]})
if rmin > mean or mean > rmax:
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 递增 + 加值域
func _validate_level_bonus(errs: Array[Dictionary]) -> void:
for family in ["level_gp_bonus", "price_level_bonus"]:
var arr: Array[Variant] = get(family)
var seen := {}
for row in arr:
var li: int = int(row.get("level_index", 0))
if seen.has(li):
errs.append({"category": "dup_key", "message": "%s level_index 重复: %d" % [family, li]})
seen[li] = true
var bonus: int = int(row.get("bonus", -1))
if bonus < 0 or bonus > 999:
errs.append({"category": "domain", "message": "%s level %d bonus 越域 [0,999]: %d" % [family, li, bonus]})
for i in arr.size():
var li: int = int(arr[i].get("level_index", 0))
if li != i + 1:
errs.append({"category": "structural", "message": "%s level_index 不连续(须从 1 连续递增): 位 %d = %d" % [family, i + 1, li]})
break
# products:product_id 唯一 + attr 词表 + 价格域 [1,999]
func _validate_products(errs: Array[Dictionary]) -> void:
var seen_pid := {}
for row in products:
var pid: String = row.get("product_id", "")
if pid == "":
errs.append({"category": "domain", "message": "products product_id 空"})
continue
if seen_pid.has(pid):
errs.append({"category": "dup_key", "message": "products product_id 重复: %s" % pid})
seen_pid[pid] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "%s display_name 空" % pid})
var attr: String = row.get("attr", "")
if not PRODUCT_ATTRS.has(attr):
errs.append({"category": "enum", "message": "%s attr 出域 {atk, max_hp, armor_class}: %s" % [pid, attr]})
var flat: int = int(row.get("flat_value", 0))
if flat < 1 or flat > 999:
errs.append({"category": "domain", "message": "%s flat_value 越域 [1,999]: %d" % [pid, flat]})
var base: int = int(row.get("base_price", 0))
if base < 1 or base > 999:
errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [pid, base]})
var inc: int = int(row.get("price_increment", -1))
if inc < 0 or inc > 999:
errs.append({"category": "domain", "message": "%s price_increment 越域 [0,999]: %d" % [pid, inc]})
# pool_shelf:kind_id 词表封闭(四切边后种)+ 唯一 + display_name + base_price 域 [1,999]
func _validate_pool_shelf(errs: Array[Dictionary]) -> void:
var seen_kind := {}
for row in pool_shelf:
var kind: String = row.get("kind_id", "")
if not SHELF_KINDS.has(kind):
errs.append({"category": "enum", "message": "pool_shelf kind_id 出域(四切边后种封闭词表): %s" % kind})
if seen_kind.has(kind):
errs.append({"category": "dup_key", "message": "pool_shelf kind_id 重复: %s" % kind})
seen_kind[kind] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "pool_shelf %s display_name 空" % kind})
var base: int = int(row.get("base_price", 0))
if base < 1 or base > 999:
errs.append({"category": "domain", "message": "pool_shelf %s base_price 越域 [1,999]: %d" % [kind, base]})
# 价格因子域(乘法语义——A/b 均 ≥1 单调不降;上界防手滑投值;provisional 值本体归难度曲线与Boss)
func _validate_price_factors(errs: Array[Dictionary]) -> void:
if price_a < 1.0 or price_a > 3.0:
errs.append({"category": "domain", "message": "price_a 越域 [1.0,3.0]: %f" % price_a})
if price_b < 1.0 or price_b > 2.0:
errs.append({"category": "domain", "message": "price_b 越域 [1.0,2.0]: %f" % price_b})
if relic_base_price < 1.0 or relic_base_price > 99.0:
errs.append({"category": "domain", "message": "relic_base_price 越域 [1.0,99.0]: %f" % relic_base_price})
if shelf_size < 1 or shelf_size > 6:
errs.append({"category": "domain", "message": "shelf_size 越域 [1,6]: %d" % shelf_size})
# ---- 行族访问器(自 ConfigDB 归位——行集解析单点;消费面 db.run.shop.<m> 直连)----
## GP 掉落行单点(键 tier;缺行=空字典——掉落按 0 兑付的兜底在调用方)
func gp_drop_row(tier: String) -> Dictionary:
for row in gp_drops:
if String(row.get("tier", "")) == tier:
return row
return {}
## 关卡 GP 加成单点(键 level_index;缺行=0)
func level_gp_bonus_at(level_index: int) -> int:
for row in level_gp_bonus:
if int(row.get("level_index", -1)) == level_index:
return int(row.get("bonus", 0))
return 0
## 价格关卡加成单点(键 level_index;缺行=0)
func price_level_bonus_at(level_index: int) -> int:
for row in price_level_bonus:
if int(row.get("level_index", -1)) == level_index:
return int(row.get("bonus", 0))
return 0
## 商品行族({product_id, display_name, attr, flat_value, base_price, price_increment})
func product_rows() -> Array:
return products
## 商品行单点(键 product_id;缺行=空字典)
func product_row(product_id: String) -> Dictionary:
for row in products:
if String(row.get("product_id", "")) == product_id:
return row
return {}
## 货架行族({kind_id, display_name, base_price}——行序即抽签/呈现稳定序)
func pool_rows() -> Array:
return pool_shelf
## 货架行单点(键 kind_id;缺行=空字典)
func pool_row(kind_id: String) -> Dictionary:
for row in pool_shelf:
if String(row.get("kind_id", "")) == kind_id:
return row
return {}