Files
pixelheros/assets/script/game/map/ItemListComp.ts
panFD afbb349c25 feat(shop): 新增商品/药品卡池与商店系统
1. 新增ICardSet.ts定义商品卡池配置,包含基础/高级恢复类、属性强化类道具
2. 为MissionCardComp添加商品商店逻辑,支持购买记录追踪与显隐切换
3. 重构ItemListComp为通用商店列表组件,适配装备与商品两种商店类型
2026-07-22 21:42:00 +08:00

289 lines
10 KiB
TypeScript
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.
/**
* @file ItemListComp.ts
* @description 商店列表项组件UI 视图层 + 购买逻辑)
*
* 职责:
* 1. 渲染单个商品/装备卡的显示信息(名称、等级、属性词条、图标)。
* 2. 处理购买点击 → 扣费 → 根据卡牌类型派发对应事件触发购买效果。
*
* 支持类型:
* - 装备卡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, CardTriggerType, CKind } from "../common/config/CardSet";
import { EquipPoolList } from "../common/config/EquipSet";
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";
import { MissionEconomy } from "./MissionEconomy";
const { ccclass, property } = _decorator;
/**
* ItemListComp —— 商店列表项(装备/商品通用)
*
* 由 MissionCardComp.populateEquipments() / populateShopItems() 实例化。
* 以图标 + 名称 + 描述/属性词条 + 购买按钮的形式呈现。
*/
@ccclass('ItemListComp')
@ecs.register('ItemListComp', false)
export class ItemListComp extends CCComp {
private debugMode: boolean = false;
// ======================== 编辑器绑定节点 ========================
/** 商品图标节点 */
@property({ type: Node })
private icon_node: Node = null;
/** 背景节点 */
@property({ type: Node })
private bg_node: Node = null;
/** 商品名称 */
@property(Label)
private name_label: Label = null;
/** 描述/属性词条 1 */
@property(Label)
private fied1: Label = null;
/** 描述/属性词条 2 */
@property(Label)
private fied2: Label = null;
/** 描述/属性词条 3 */
@property(Label)
private fied3: Label = null;
/** 等级标签 */
@property(Label)
private level_label: Label = null;
/** 购买按钮节点 */
@property({ type: Node })
private buy_node: Node = null;
// ======================== 运行时状态 ========================
/** 当前商品的卡牌配置 */
private cardData: CardConfig | null = null;
/** 是否已购买 */
private isPurchased: boolean = false;
// ======================== 生命周期 ========================
onLoad() {
this.buy_node?.on(NodeEventType.TOUCH_END, this.onBuyClick, this);
}
onDestroy() {
super.onDestroy();
if (this.buy_node && this.buy_node.isValid) {
this.buy_node.off(NodeEventType.TOUCH_END, this.onBuyClick, this);
}
}
// ======================== 数据初始化 ========================
/**
* 初始化商品卡数据并渲染 UI
*
* @param data 商品/装备卡配置
*/
applyCardData(data: CardConfig): void {
if (!data) return;
this.cardData = data;
this.isPurchased = false;
this.updateUI();
}
// ======================== UI 渲染 ========================
updateUI() {
if (!this.cardData) return;
const isEquip = this.cardData.trigger_type === CardTriggerType.Field;
const isPotion = this.cardData.kind === CKind.Potion;
// 名称:装备从 EquipPoolList 查找,商品直接用 cardData.name
if (this.name_label) {
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 || "";
}
}
// 等级标签(★ 表示卡牌等级)
if (this.level_label) {
const lv = this.cardData.card_lv ?? 1;
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
}
// 描述/属性词条(最多显示 3 条)
const fieldLabels = [this.fied1, this.fied2, this.fied3];
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) {
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);
}
}
// 购买按钮费用显示
if (this.buy_node) {
this.buy_node.active = true;
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
|| this.buy_node.getComponentInChildren(Label);
if (costLabel) {
costLabel.string = `${this.cardData.cost ?? 0}`;
}
}
}
/** 更新图标精灵 */
private updateIcon(node: Node, iconId: string) {
if (!node || !iconId) return;
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (!sprite) return;
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
sprite.spriteFrame = frame || null;
}
}
// ======================== 购买逻辑 ========================
/**
* 购买点击处理:
* 1. 扣除金币(失败则提示并中止)。
* 2. 根据卡牌类型派发对应事件:
* - 装备卡trigger_type=Field→ UseEquipCard → MissEquipComp
* - 商品/药品卡kind=Potion→ UseItemCard → MissSkillsComp一次性 buff 技能)
* 3. 标记已购买,隐藏购买按钮。
*/
private onBuyClick() {
if (!this.cardData || this.isPurchased) return;
oops.audio.playEffect("music/button");
// 扣费
const cost = this.cardData.cost ?? 0;
const success = MissionEconomy.spendCoin(cost);
if (!success) {
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
mLogger.log(this.debugMode, "ItemListComp", "purchase failed: not enough coin", {
uuid: this.cardData.uuid,
cost
});
return;
}
// 根据类型派发对应事件
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;
}
}
/** 标记为已购买状态(用于跨波次保持购买记录) */
setPurchased(): void {
this.isPurchased = true;
if (this.buy_node) {
this.buy_node.active = false;
}
}
/** ECS 组件移除时销毁节点 */
reset() {
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}