feat(shop): 新增商品/药品卡池与商店系统

1. 新增ICardSet.ts定义商品卡池配置,包含基础/高级恢复类、属性强化类道具
2. 为MissionCardComp添加商品商店逻辑,支持购买记录追踪与显隐切换
3. 重构ItemListComp为通用商店列表组件,适配装备与商品两种商店类型
This commit is contained in:
panFD
2026-07-22 21:42:00 +08:00
parent 90db23e7c9
commit afbb349c25
3 changed files with 352 additions and 50 deletions

View File

@@ -1,23 +1,25 @@
/**
* @file ItemListComp.ts
* @description 装备商店单个装备项组件UI 视图层 + 购买逻辑)
* @description 商店列表项组件UI 视图层 + 购买逻辑)
*
* 职责:
* 1. 渲染单个装备卡的显示信息(名称、等级、属性词条、图标)。
* 2. 处理购买点击 → 扣费 → 派发 UseEquipCard 事件触发装备生效
* 1. 渲染单个商品/装备卡的显示信息(名称、等级、属性词条、图标)。
* 2. 处理购买点击 → 扣费 → 根据卡牌类型派发对应事件触发购买效果
*
* 装备使用机制
* 与技能卡使用流程一致——购买后派发 GameEvent.UseEquipCard
* 由 MissEquipComp 接收并创建 EquipBoxComp 实体,
* EquipBoxComp 按 Field 类型被动驻场,属性加成由 FieldSkillHelper 全局聚合。
* 支持类型
* - 装备卡trigger_type=Field→ 派发 UseEquipCard由 MissEquipComp 处理。
* - 商品/药品卡kind=Potion→ 派发 UseItemCard由 MissSkillsComp 处理(一次性 buff
*
* 设计说明:
* 本组件同时服务于装备商店和商品商店,通过 cardData 的 trigger_type / kind 自动判断类型。
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Sprite, Label, NodeEventType } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { CardConfig } from "../common/config/CardSet";
import { CardConfig, CardTriggerType, CKind } from "../common/config/CardSet";
import { EquipPoolList } from "../common/config/EquipSet";
import { FieldSkillSet } from "../common/config/SkillSet";
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
@@ -26,10 +28,10 @@ import { MissionEconomy } from "./MissionEconomy";
const { ccclass, property } = _decorator;
/**
* ItemListComp —— 装备商店单个装备项
* ItemListComp —— 商店列表项(装备/商品通用)
*
* 由 MissionCardComp.populateEquipments() 实例化。
* 以图标 + 名称 + 属性词条 + 购买按钮的形式呈现。
* 由 MissionCardComp.populateEquipments() / populateShopItems() 实例化。
* 以图标 + 名称 + 描述/属性词条 + 购买按钮的形式呈现。
*/
@ccclass('ItemListComp')
@ecs.register('ItemListComp', false)
@@ -38,7 +40,7 @@ export class ItemListComp extends CCComp {
// ======================== 编辑器绑定节点 ========================
/** 装备图标节点 */
/** 商品图标节点 */
@property({ type: Node })
private icon_node: Node = null;
@@ -46,17 +48,17 @@ export class ItemListComp extends CCComp {
@property({ type: Node })
private bg_node: Node = null;
/** 装备名称 */
/** 商品名称 */
@property(Label)
private name_label: Label = null;
/** 属性词条 1 */
/** 描述/属性词条 1 */
@property(Label)
private fied1: Label = null;
/** 属性词条 2 */
/** 描述/属性词条 2 */
@property(Label)
private fied2: Label = null;
/** 属性词条 3 */
/** 描述/属性词条 3 */
@property(Label)
private fied3: Label = null;
@@ -70,7 +72,7 @@ export class ItemListComp extends CCComp {
// ======================== 运行时状态 ========================
/** 当前装备的卡牌配置 */
/** 当前商品的卡牌配置 */
private cardData: CardConfig | null = null;
/** 是否已购买 */
private isPurchased: boolean = false;
@@ -91,9 +93,9 @@ export class ItemListComp extends CCComp {
// ======================== 数据初始化 ========================
/**
* 初始化装备卡数据并渲染 UI
* 初始化商品卡数据并渲染 UI
*
* @param data 装备卡配置(必须为 trigger_type=Field 的装备卡)
* @param data 商品/装备卡配置
*/
applyCardData(data: CardConfig): void {
if (!data) return;
@@ -107,11 +109,17 @@ export class ItemListComp extends CCComp {
updateUI() {
if (!this.cardData) return;
const cardConfig = EquipPoolList.find(c => c.uuid === this.cardData!.uuid);
const isEquip = this.cardData.trigger_type === CardTriggerType.Field;
const isPotion = this.cardData.kind === CKind.Potion;
// 名称
// 名称:装备从 EquipPoolList 查找,商品直接用 cardData.name
if (this.name_label) {
this.name_label.string = cardConfig?.name || "";
if (isEquip) {
const equipConfig = EquipPoolList.find(c => c.uuid === this.cardData!.uuid);
this.name_label.string = equipConfig?.name || this.cardData.name || "";
} else {
this.name_label.string = this.cardData.name || "";
}
}
// 等级标签(★ 表示卡牌等级)
@@ -120,25 +128,63 @@ export class ItemListComp extends CCComp {
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
}
// 属性词条(最多显示 3 条,对应 fied1/fied2/fied3
// 描述/属性词条(最多显示 3 条)
const fieldLabels = [this.fied1, this.fied2, this.fied3];
const fields = this.cardData.field || [];
for (let i = 0; i < fieldLabels.length; i++) {
if (!fieldLabels[i]) continue;
if (i < fields.length) {
const fieldConfig = FieldSkillSet[fields[i]];
fieldLabels[i].string = fieldConfig?.info || "";
fieldLabels[i].node.active = true;
} else {
fieldLabels[i].string = "";
fieldLabels[i].node.active = false;
if (isEquip) {
// 装备:显示 field 词条
const fields = this.cardData.field || [];
for (let i = 0; i < fieldLabels.length; i++) {
if (!fieldLabels[i]) continue;
if (i < fields.length) {
const fieldConfig = FieldSkillSet[fields[i]];
fieldLabels[i].string = fieldConfig?.info || "";
fieldLabels[i].node.active = true;
} else {
fieldLabels[i].string = "";
fieldLabels[i].node.active = false;
}
}
} else if (isPotion) {
// 商品:显示技能描述 + info
const skillConfig = this.cardData.skill ? SkillSet[this.cardData.skill] : null;
const skillInfo = skillConfig?.info || "";
const cardInfo = this.cardData.info || "";
if (fieldLabels[0]) {
fieldLabels[0].string = cardInfo;
fieldLabels[0].node.active = !!cardInfo;
}
if (fieldLabels[1]) {
fieldLabels[1].string = skillInfo;
fieldLabels[1].node.active = !!skillInfo && skillInfo !== cardInfo;
}
if (fieldLabels[2]) {
fieldLabels[2].string = "";
fieldLabels[2].node.active = false;
}
} else {
// 其他类型:显示 info
for (let i = 0; i < fieldLabels.length; i++) {
if (!fieldLabels[i]) continue;
fieldLabels[i].string = i === 0 ? (this.cardData.info || "") : "";
fieldLabels[i].node.active = i === 0 && !!this.cardData.info;
}
}
// 图标取第一个 field 词条图标
if (this.icon_node && fields.length > 0) {
const iconId = FieldSkillSet[fields[0]]?.icon || "";
this.updateIcon(this.icon_node, iconId);
// 图标:装备取第一个 field 词条图标,商品取技能图标
if (this.icon_node) {
let iconId = "";
if (isEquip) {
const fields = this.cardData.field || [];
if (fields.length > 0) {
iconId = FieldSkillSet[fields[0]]?.icon || "";
}
} else if (isPotion && this.cardData.skill) {
iconId = SkillSet[this.cardData.skill]?.icon || "";
}
if (iconId) {
this.updateIcon(this.icon_node, iconId);
}
}
// 购买按钮费用显示
@@ -168,12 +214,10 @@ export class ItemListComp extends CCComp {
/**
* 购买点击处理:
* 1. 扣除金币(失败则提示并中止)。
* 2. 派发 UseSkillCard 事件,触发与技能卡相同的生效流程。
* 2. 根据卡牌类型派发对应事件:
* - 装备卡trigger_type=Field→ UseEquipCard → MissEquipComp
* - 商品/药品卡kind=Potion→ UseItemCard → MissSkillsComp一次性 buff 技能)
* 3. 标记已购买,隐藏购买按钮。
*
* 装备生效链路:
* UseSkillCard → MissSkillsComp.addSkill() → SBox ECS 实体 → SkillBoxComp(Field 类型)
* → FieldSkillHelper.getFieldSkillTotalValue() 全局聚合属性加成
*/
private onBuyClick() {
if (!this.cardData || this.isPurchased) return;
@@ -192,19 +236,39 @@ export class ItemListComp extends CCComp {
return;
}
// 派发 UseEquipCard装备生效流程由 MissEquipComp 接收处理
oops.message.dispatchEvent(GameEvent.UseEquipCard, this.cardData);
// 根据类型派发对应事件
const isEquip = this.cardData.trigger_type === CardTriggerType.Field;
const isPotion = this.cardData.kind === CKind.Potion;
if (isEquip) {
// 装备:被动驻场,由 MissEquipComp 处理
oops.message.dispatchEvent(GameEvent.UseEquipCard, this.cardData);
mLogger.log(this.debugMode, "ItemListComp", "equipment purchased", {
uuid: this.cardData.uuid,
cost
});
} else if (isPotion) {
// 商品/药品:一次性 buff 技能,由 MissSkillsComp 处理
oops.message.dispatchEvent(GameEvent.UseItemCard, this.cardData);
mLogger.log(this.debugMode, "ItemListComp", "item purchased", {
uuid: this.cardData.uuid,
skill: this.cardData.skill,
cost
});
} else {
// 默认走技能卡流程
oops.message.dispatchEvent(GameEvent.UseSkillCard, this.cardData);
mLogger.log(this.debugMode, "ItemListComp", "card purchased", {
uuid: this.cardData.uuid,
cost
});
}
// 标记已购买
this.isPurchased = true;
if (this.buy_node) {
this.buy_node.active = false;
}
mLogger.log(this.debugMode, "ItemListComp", "equipment purchased", {
uuid: this.cardData.uuid,
cost
});
}
/** 标记为已购买状态(用于跨波次保持购买记录) */