feat: 新增装备商店系统,调整英雄站位与抽卡逻辑
1. 重构抽卡函数,新增排除装备卡参数,装备卡改为商店直购 2. 添加装备商店面板,支持装备浏览与购买,跨波次保留购买记录 3. 调整英雄出场点位坐标,优化战场布局 4. 扩展技能卡槽位,新增两行技能槽位置 5. 重构EquipListComp组件,适配装备商店UI逻辑 6. 删除废弃的melist预制件与相关元文件 7. 调整hnode预制件布局参数
This commit is contained in:
@@ -48,23 +48,23 @@
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 446
|
||||
},
|
||||
{
|
||||
"__id__": 448
|
||||
},
|
||||
{
|
||||
"__id__": 450
|
||||
},
|
||||
{
|
||||
"__id__": 452
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 452
|
||||
"__id__": 454
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 390,
|
||||
"y": 480.1,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -117,7 +117,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": 10.984999999999985,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -157,8 +157,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 700,
|
||||
"height": 310
|
||||
"width": 710,
|
||||
"height": 290
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -232,8 +232,8 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": -5,
|
||||
"_bottom": -5,
|
||||
"_top": -5.984999999999999,
|
||||
"_bottom": 15.984999999999998,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -767,8 +767,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -240,
|
||||
"y": 21.879,
|
||||
"x": -257.43,
|
||||
"y": 5.09,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -4254,8 +4254,6 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "66eIq0sFxB84fIWi6zNzGn",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -5171,7 +5169,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 259.779,
|
||||
"y": -113.255,
|
||||
"y": -122.409,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -5573,7 +5571,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 119.425,
|
||||
"y": -113.255,
|
||||
"y": -122.409,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -5977,8 +5975,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 100,
|
||||
"y": 103.621,
|
||||
"x": 92.39950000000002,
|
||||
"y": 86.832,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -6040,7 +6038,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -230,
|
||||
"x": -252.6005,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -6093,7 +6091,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 50,
|
||||
"x": 75,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -6134,7 +6132,7 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 200,
|
||||
"width": 300,
|
||||
"height": 70
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -6557,7 +6555,7 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 100,
|
||||
"width": 150,
|
||||
"height": 30
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -6704,7 +6702,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -121,
|
||||
"x": -93.60050000000001,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -7527,7 +7525,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 53,
|
||||
"x": 80.39949999999999,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -8322,7 +8320,7 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 460,
|
||||
"width": 505.201,
|
||||
"height": 30
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -8348,10 +8346,10 @@
|
||||
"__prefab": {
|
||||
"__id__": 420
|
||||
},
|
||||
"_alignFlags": 0,
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_left": 194.799,
|
||||
"_right": 10,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
@@ -8362,7 +8360,7 @@
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalWidth": 479.9,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
@@ -8441,23 +8439,23 @@
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 439
|
||||
},
|
||||
{
|
||||
"__id__": 441
|
||||
},
|
||||
{
|
||||
"__id__": 443
|
||||
},
|
||||
{
|
||||
"__id__": 445
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 445
|
||||
"__id__": 447
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 100,
|
||||
"y": 0.887,
|
||||
"x": 91.56,
|
||||
"y": -14.037000000000006,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -8509,7 +8507,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -5,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -8550,7 +8548,7 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 900,
|
||||
"width": 1013.76,
|
||||
"height": 310
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -8627,7 +8625,7 @@
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 10,
|
||||
"_right": 0,
|
||||
"_top": -2.5,
|
||||
"_bottom": -2.5,
|
||||
"_horizontalCenter": 0,
|
||||
@@ -8677,14 +8675,17 @@
|
||||
},
|
||||
{
|
||||
"__id__": 436
|
||||
},
|
||||
{
|
||||
"__id__": 438
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 438
|
||||
"__id__": 440
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -5,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -8725,8 +8726,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 430,
|
||||
"height": 90
|
||||
"width": 486.88,
|
||||
"height": 130
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -8807,6 +8808,42 @@
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "a4aoaDD+FBU6Rtemm9p9yx"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 433
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 439
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
"_left": 10,
|
||||
"_right": 10,
|
||||
"_top": 10,
|
||||
"_bottom": 10,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 430,
|
||||
"_originalHeight": 90,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "f5Fxxc5gtDlL6afbVLgZgk"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
@@ -8830,11 +8867,11 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 440
|
||||
"__id__": 442
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 460,
|
||||
"width": 506.88,
|
||||
"height": 150
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -8858,14 +8895,14 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 442
|
||||
"__id__": 444
|
||||
},
|
||||
"_alignFlags": 0,
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_left": 193.12,
|
||||
"_right": 10,
|
||||
"_top": 89.037,
|
||||
"_bottom": 60.962999999999994,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -8874,8 +8911,8 @@
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 0,
|
||||
"_originalWidth": 510,
|
||||
"_originalHeight": 150,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
@@ -8894,7 +8931,7 @@
|
||||
},
|
||||
"_enabled": false,
|
||||
"__prefab": {
|
||||
"__id__": 444
|
||||
"__id__": 446
|
||||
},
|
||||
"_resizeMode": 1,
|
||||
"_layoutType": 3,
|
||||
@@ -8945,11 +8982,11 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 447
|
||||
"__id__": 449
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 700,
|
||||
"width": 710,
|
||||
"height": 300
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -8973,7 +9010,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 449
|
||||
"__id__": 451
|
||||
},
|
||||
"icon_node": {
|
||||
"__id__": 157
|
||||
@@ -9024,13 +9061,13 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 451
|
||||
"__id__": 453
|
||||
},
|
||||
"_alignFlags": 41,
|
||||
"_target": null,
|
||||
"_left": 10,
|
||||
"_right": 10,
|
||||
"_top": 100,
|
||||
"_left": 5,
|
||||
"_right": 5,
|
||||
"_top": 9.9,
|
||||
"_bottom": 350,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -9060,7 +9097,6 @@
|
||||
},
|
||||
"fileId": "d5Q0cgjY1BTIz2JM1k7EZK",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 218
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "0c80c911-642f-423a-8ebb-98aebbbe2ef0",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "melist"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -431,6 +431,7 @@ export const drawCardsByRule = (
|
||||
targetPoolLv?: number
|
||||
wave?: number
|
||||
unique?: boolean
|
||||
excludeEquipment?: boolean
|
||||
} = {}
|
||||
): CardConfig[] => {
|
||||
const count = Math.max(0, Math.floor(options.count ?? 4))
|
||||
@@ -460,6 +461,11 @@ export const drawCardsByRule = (
|
||||
})
|
||||
}
|
||||
|
||||
// 装备卡已改为商店直购,不再参与抽卡
|
||||
if (options.excludeEquipment) {
|
||||
pool = pool.filter(card => !isEquipmentCard(card));
|
||||
}
|
||||
|
||||
const picked = pickCards(pool, count, options.unique)
|
||||
return picked
|
||||
}
|
||||
|
||||
@@ -1,149 +1,219 @@
|
||||
/**
|
||||
* @file EquipListComp.ts
|
||||
* @description 单个技能卡效果控制组件(UI 视图层 + 逻辑层)
|
||||
* @description 装备商店单个装备项组件(UI 视图层 + 购买逻辑)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 表示一张已使用的技能卡在战场上的 **可视化实体**。
|
||||
* 2. 按 trigger_type 类型化分发触发逻辑(即时 / 定时 / 驻场 / 事件型)。
|
||||
* 3. 显示技能图标和剩余触发次数。
|
||||
* 4. 触发结束后自动销毁。
|
||||
* 1. 渲染单个装备卡的显示信息(名称、等级、属性词条、图标)。
|
||||
* 2. 处理购买点击 → 扣费 → 派发 UseSkillCard 事件触发装备生效。
|
||||
*
|
||||
* 触发类型(CardTriggerType):
|
||||
* - Instant (1):init 时立即触发一次(按 t_times 控制次数,跨波次 NewWave 时再次触发)
|
||||
* - Interval (2):监听 FightStart → update 帧驱动按 t_inv 间隔重复触发(按 t_times 控制每波次数)
|
||||
* - Field (3):被动生效,不主动施法(实际由 FieldSkillSet 处理)
|
||||
* - FightStart (4):监听 FightStart 事件,按 trigger_limit 全局累计上限
|
||||
* - FightEnd (5):监听 FightEnd 事件(每波结束派发),按 trigger_limit 全局累计上限
|
||||
* - HeroDead (6):监听 HeroDead 事件(仅英雄阵营派发,怪物死亡不触发)
|
||||
* - HeroCall (7):监听 MasterCalled(主角/技能召唤)+ ReviveSuccess(复活)
|
||||
*
|
||||
* 关键设计:
|
||||
* - 事件型(4-7)统一走 onEventTrigger 入口,仅作触发信号,不读取 payload
|
||||
* - 触发上限:Instant/Interval 按 t_times(每波内),事件型按 trigger_limit(全局)
|
||||
* - 跨波次:keep_waves 控制存活;事件型 trigger_count 不随波次重置
|
||||
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位
|
||||
*
|
||||
* 依赖:
|
||||
* - CardPoolList / CardTriggerType(CardSet)—— 查询技能卡的触发配置
|
||||
* - SkillSet —— 技能静态配置(icon 字段)
|
||||
* - GameEvent —— 各类游戏事件
|
||||
* - smc.mission —— 游戏运行状态
|
||||
* 装备使用机制:
|
||||
* 与技能卡使用流程一致——购买后派发 GameEvent.UseSkillCard,
|
||||
* 由 MissSkillsComp 接收并创建 SkillBoxComp 实体,
|
||||
* SkillBoxComp 按 Field 类型被动驻场,属性加成由 FieldSkillHelper 全局聚合。
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } from "cc";
|
||||
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 { CardPoolList, CardTriggerType } from "../common/config/CardSet";
|
||||
import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillSet";
|
||||
import { CardConfig, CardPoolList } from "../common/config/CardSet";
|
||||
import { FieldSkillSet } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* EquipListComp —— 单个技能卡效果视图 + 逻辑组件
|
||||
* EquipListComp —— 装备商店单个装备项
|
||||
*
|
||||
* 由 MissSkillsComp.addSkill() 实例化并初始化。
|
||||
* 在战场上以图标 + 剩余次数的形式呈现。
|
||||
* 由 MissionCardComp.populateEquipments() 实例化。
|
||||
* 以图标 + 名称 + 属性词条 + 购买按钮的形式呈现。
|
||||
*/
|
||||
@ccclass('EquipListComp')
|
||||
@ecs.register('EquipListComp', false)
|
||||
export class EquipListComp extends CCComp {
|
||||
/** 调试日志开关 */
|
||||
private debugMode: boolean = true;
|
||||
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;
|
||||
// ======================== 技能配置 ========================
|
||||
|
||||
/** 技能 UUID */
|
||||
private s_uuid: number = 0;
|
||||
/** 卡牌等级 */
|
||||
private card_lv: number = 1;
|
||||
/** 是否为即时技能(true=使用后立即触发,false=战斗中定时触发) */
|
||||
private is_instant: boolean = true;
|
||||
/** 总触发次数 */
|
||||
private trigger_times: number = 1;
|
||||
/** 触发间隔(秒,仅持续技能有效) */
|
||||
private trigger_interval: number = 0;
|
||||
/** 维持的波次数(-1表示直到战斗结束,0表示不跨波次,>0表示维持的具体波次数) */
|
||||
private keep_waves: number = 0;
|
||||
/** 技能覆写参数(自定义伤害、Buff等) */
|
||||
private overrides?: SkillOverrides;
|
||||
/** 驻场技能 UUID 列表 */
|
||||
public field: number[] = [];
|
||||
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
|
||||
private card_icon?: string;
|
||||
|
||||
// ======================== 触发类型化扩展 ========================
|
||||
|
||||
/** 触发类型(默认即时,保持向后兼容) */
|
||||
private trigger_type: CardTriggerType = CardTriggerType.Instant;
|
||||
/** 事件型触发的全局次数上限(Infinity 表示无上限) */
|
||||
private trigger_limit: number = Infinity;
|
||||
/** 事件型已触发次数 */
|
||||
private trigger_count: number = 0;
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 已触发次数 */
|
||||
private current_trigger_times: number = 0;
|
||||
/** 当前计时器(秒) */
|
||||
private timer: number = 0;
|
||||
/** 是否处于战斗中(仅战斗中持续技能才计时) */
|
||||
private in_combat: boolean = false;
|
||||
/** 是否已初始化 */
|
||||
private initialized: boolean = false;
|
||||
/** 当前装备的卡牌配置 */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 数据初始化 ========================
|
||||
|
||||
|
||||
init() {
|
||||
|
||||
/**
|
||||
* 初始化装备卡数据并渲染 UI
|
||||
*
|
||||
* @param data 装备卡配置(必须为 trigger_type=Field 的装备卡)
|
||||
*/
|
||||
applyCardData(data: CardConfig): void {
|
||||
if (!data) return;
|
||||
this.cardData = data;
|
||||
this.isPurchased = false;
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
|
||||
private registerTrigger(): void {
|
||||
|
||||
}
|
||||
// ======================== UI 渲染 ========================
|
||||
|
||||
|
||||
|
||||
updateUI() {
|
||||
|
||||
|
||||
if (!this.cardData) return;
|
||||
|
||||
const cardConfig = CardPoolList.find(c => c.uuid === this.cardData!.uuid);
|
||||
|
||||
// 名称
|
||||
if (this.name_label) {
|
||||
this.name_label.string = cardConfig?.name || "";
|
||||
}
|
||||
|
||||
// 等级标签(★ 表示卡牌等级)
|
||||
if (this.level_label) {
|
||||
const lv = this.cardData.card_lv ?? 1;
|
||||
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
|
||||
}
|
||||
|
||||
// 属性词条(最多显示 3 条,对应 fied1/fied2/fied3)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 图标(取第一个 field 词条的图标)
|
||||
if (this.icon_node && fields.length > 0) {
|
||||
const iconId = FieldSkillSet[fields[0]]?.icon || "";
|
||||
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. 派发 UseSkillCard 事件,触发与技能卡相同的生效流程。
|
||||
* 3. 标记已购买,隐藏购买按钮。
|
||||
*
|
||||
* 装备生效链路:
|
||||
* UseSkillCard → MissSkillsComp.addSkill() → SBox ECS 实体 → SkillBoxComp(Field 类型)
|
||||
* → FieldSkillHelper.getFieldSkillTotalValue() 全局聚合属性加成
|
||||
*/
|
||||
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, "EquipListComp", "purchase failed: not enough coin", {
|
||||
uuid: this.cardData.uuid,
|
||||
cost
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 派发 UseSkillCard,装备生效流程与技能卡一致
|
||||
oops.message.dispatchEvent(GameEvent.UseSkillCard, this.cardData);
|
||||
|
||||
// 标记已购买
|
||||
this.isPurchased = true;
|
||||
if (this.buy_node) {
|
||||
this.buy_node.active = false;
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "EquipListComp", "equipment purchased", {
|
||||
uuid: this.cardData.uuid,
|
||||
cost
|
||||
});
|
||||
}
|
||||
|
||||
/** 标记为已购买状态(用于跨波次保持购买记录) */
|
||||
setPurchased(): void {
|
||||
this.isPurchased = true;
|
||||
if (this.buy_node) {
|
||||
this.buy_node.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** ECS 组件移除时销毁节点 */
|
||||
reset() {
|
||||
if (this.node && this.node.isValid) {
|
||||
|
||||
@@ -63,16 +63,16 @@ export class MissSkillsComp extends CCComp {
|
||||
* 第 2 行 y=320:x = -320, -240, -160, -80, 0
|
||||
*/
|
||||
private slots: SkillBoxSlot[] = [
|
||||
{ x: -310, y: 200, used: false, node: null },
|
||||
{ x: -220, y: 200, used: false, node: null },
|
||||
{ x: -130, y: 200, used: false, node: null },
|
||||
{ x: -40, y: 200, used: false, node: null },
|
||||
{ x: 50, y: 200, used: false, node: null },
|
||||
{ x: -310, y: 300, used: false, node: null },
|
||||
{ x: -220, y: 300, used: false, node: null },
|
||||
{ x: -130, y: 300, used: false, node: null },
|
||||
{ x: -40, y: 300, used: false, node: null },
|
||||
{ x: 50, y: 300, used: false, node: null },
|
||||
{ x: -310, y: 400, used: false, node: null },
|
||||
{ x: -220, y: 400, used: false, node: null },
|
||||
{ x: -130, y: 400, used: false, node: null },
|
||||
{ x: -40, y: 400, used: false, node: null },
|
||||
{ x: 50, y: 400, used: false, node: null },
|
||||
];
|
||||
|
||||
/** 注册事件监听 */
|
||||
|
||||
@@ -35,13 +35,14 @@
|
||||
* - smc.vmdata.mission_data —— 局内数据(coin / hero_num / hero_max_num)
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Label, Node, NodeEventType, Prefab, Tween, tween, Vec3 } from "cc";
|
||||
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, Tween, tween, Vec3 } 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 { GameEvent } from "../common/config/GameEvent";
|
||||
import { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
|
||||
import { CardConfig, CardPoolList, CardType, drawCardsByRule, isEquipmentCard, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
|
||||
import { CardComp } from "./CardComp";
|
||||
import { SCardComp } from "./SCardComp";
|
||||
import { EquipListComp } from "./EquipListComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -165,6 +166,8 @@ export class MissionCardComp extends CCComp {
|
||||
private cardComps: CardComp[] = [];
|
||||
/** 技能卡槽控制器缓存 */
|
||||
private skillCardComps: SCardComp[] = [];
|
||||
/** 已购买装备的 UUID 集合(跨波次保持,防止重复购买) */
|
||||
private purchasedEquipUuids: Set<number> = new Set();
|
||||
/** 是否已缓存卡牌面板基准缩放 */
|
||||
private hasCachedCardsBaseScale: boolean = false;
|
||||
/** 卡牌面板基准缩放(从场景读取) */
|
||||
@@ -214,6 +217,12 @@ export class MissionCardComp extends CCComp {
|
||||
if (this.closeHeros && this.closeHeros.isValid) {
|
||||
this.closeHeros.off(NodeEventType.TOUCH_END, this.onCloseHerosClick, this);
|
||||
}
|
||||
if (this.showEquips && this.showEquips.isValid) {
|
||||
this.showEquips.off(NodeEventType.TOUCH_END, this.onShowEquipsClick, this);
|
||||
}
|
||||
if (this.closeEquips && this.closeEquips.isValid) {
|
||||
this.closeEquips.off(NodeEventType.TOUCH_END, this.onCloseEquipsClick, this);
|
||||
}
|
||||
this.unbindEvents();
|
||||
}
|
||||
|
||||
@@ -256,12 +265,17 @@ export class MissionCardComp extends CCComp {
|
||||
const cards = this.buildDrawCards();
|
||||
this.dispatchCardsToSlots(cards);
|
||||
|
||||
// 重置购买记录并填充装备商店
|
||||
this.purchasedEquipUuids.clear();
|
||||
this.populateEquipments();
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
|
||||
}
|
||||
|
||||
/** 任务结束:清空 3 槽 + 英雄面板并隐藏整个节点 */
|
||||
/** 任务结束:清空 3 槽 + 装备商店 + 英雄面板并隐藏整个节点 */
|
||||
onMissionEnd() {
|
||||
this.clearAllCards();
|
||||
this.clearEquipments();
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.active = false;
|
||||
}
|
||||
@@ -319,6 +333,11 @@ export class MissionCardComp extends CCComp {
|
||||
/** 关闭英雄卡池按钮 */
|
||||
this.closeHeros?.on(NodeEventType.TOUCH_END, this.onCloseHerosClick, this);
|
||||
|
||||
/** 装备商店显示/隐藏切换按钮 */
|
||||
this.showEquips?.on(NodeEventType.TOUCH_END, this.onShowEquipsClick, this);
|
||||
/** 关闭装备商店按钮 */
|
||||
this.closeEquips?.on(NodeEventType.TOUCH_END, this.onCloseEquipsClick, this);
|
||||
|
||||
/** 技能卡刷新按钮 */
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
@@ -456,6 +475,12 @@ export class MissionCardComp extends CCComp {
|
||||
// 由 onCardUsed 立即向被使用的槽位补充一张新技能卡。
|
||||
// 弹窗由外部(按钮/阶段切换)控制显隐。
|
||||
|
||||
// 追踪装备购买记录(EquipListComp 购买后也会派发此事件)
|
||||
const usedCard = args as CardConfig;
|
||||
if (usedCard && isEquipmentCard(usedCard)) {
|
||||
this.purchasedEquipUuids.add(usedCard.uuid);
|
||||
}
|
||||
|
||||
// 修复:独立判断 guide2 的关闭 和 guide3 的开启
|
||||
if (!smc.finish_guides.includes(2)) {
|
||||
smc.finish_guides.push(2);
|
||||
@@ -492,6 +517,12 @@ export class MissionCardComp extends CCComp {
|
||||
if (this.closeHeros && this.closeHeros.isValid) {
|
||||
this.closeHeros.off(NodeEventType.TOUCH_END, this.onCloseHerosClick, this);
|
||||
}
|
||||
if (this.showEquips && this.showEquips.isValid) {
|
||||
this.showEquips.off(NodeEventType.TOUCH_END, this.onShowEquipsClick, this);
|
||||
}
|
||||
if (this.closeEquips && this.closeEquips.isValid) {
|
||||
this.closeEquips.off(NodeEventType.TOUCH_END, this.onCloseEquipsClick, this);
|
||||
}
|
||||
if (this.skill_refresh && this.skill_refresh.isValid) {
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
@@ -680,6 +711,71 @@ export class MissionCardComp extends CCComp {
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
}
|
||||
|
||||
// ======================== 装备商店面板 ========================
|
||||
|
||||
/**
|
||||
* 装备商店显隐切换按钮回调:
|
||||
* 切换 equipsPanNode 的 active 状态。
|
||||
*/
|
||||
private onShowEquipsClick() {
|
||||
if (!this.equipsPanNode || !this.equipsPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.equipsPanNode.active = !this.equipsPanNode.active;
|
||||
}
|
||||
|
||||
/** 关闭装备商店按钮回调 */
|
||||
private onCloseEquipsClick() {
|
||||
if (!this.equipsPanNode || !this.equipsPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.equipsPanNode.active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充装备列表:
|
||||
* 从 CardPoolList 获取所有装备卡(trigger_type=Field),
|
||||
* 实例化 equipPrefab 到 equipsBoxNode,
|
||||
* 每个装备项由 EquipListComp 渲染并处理购买。
|
||||
*
|
||||
* 已购买的装备会标记为已购状态(隐藏购买按钮)。
|
||||
*/
|
||||
private populateEquipments() {
|
||||
if (!this.equipsBoxNode || !this.equipPrefab) return;
|
||||
|
||||
// 清空旧列表
|
||||
this.equipsBoxNode.removeAllChildren();
|
||||
|
||||
// 获取所有装备卡
|
||||
const equipments = CardPoolList.filter(c => isEquipmentCard(c));
|
||||
|
||||
for (const equip of equipments) {
|
||||
const node = instantiate(this.equipPrefab);
|
||||
this.equipsBoxNode.addChild(node);
|
||||
const comp = node.getComponent(EquipListComp) || node.addComponent(EquipListComp);
|
||||
comp.applyCardData(equip);
|
||||
|
||||
// 已购买的装备标记为已购
|
||||
if (this.purchasedEquipUuids.has(equip.uuid)) {
|
||||
comp.setPurchased();
|
||||
}
|
||||
}
|
||||
|
||||
// 默认显示装备面板
|
||||
if (this.equipsPanNode) {
|
||||
this.equipsPanNode.active = true;
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "populate equipments", {
|
||||
count: equipments.length
|
||||
});
|
||||
}
|
||||
|
||||
/** 清空装备列表 */
|
||||
private clearEquipments() {
|
||||
if (this.equipsBoxNode && this.equipsBoxNode.isValid) {
|
||||
this.equipsBoxNode.removeAllChildren();
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 技能抽卡按钮回调 ========================
|
||||
private onSkillDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.skill_refresh);
|
||||
@@ -986,11 +1082,13 @@ export class MissionCardComp extends CCComp {
|
||||
private buildSkillDrawCards(): CardConfig[] {
|
||||
const currentWave = this.getCurrentWave();
|
||||
// 使用明确规则的 drawCardsByRule,指定只要 3 张技能卡,并且过滤对应 wave
|
||||
// 装备卡已改为商店直购,不再参与抽卡
|
||||
const cards = drawCardsByRule(1, {
|
||||
count: 3,
|
||||
type: CardType.Skill,
|
||||
wave: currentWave,
|
||||
unique: true // 保证技能牌不重复
|
||||
unique: true, // 保证技能牌不重复
|
||||
excludeEquipment: true, // 排除装备卡
|
||||
});
|
||||
|
||||
if (cards.length >= 3) return cards.slice(0, 3);
|
||||
@@ -1000,7 +1098,8 @@ export class MissionCardComp extends CCComp {
|
||||
count: 3,
|
||||
type: CardType.Skill,
|
||||
wave: currentWave,
|
||||
unique: true
|
||||
unique: true,
|
||||
excludeEquipment: true,
|
||||
});
|
||||
if (fallback.length === 0) break;
|
||||
|
||||
@@ -1345,6 +1444,7 @@ export class MissionCardComp extends CCComp {
|
||||
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
|
||||
this.cardComps = [] as any;
|
||||
this.skillCardComps = [] as any;
|
||||
this.purchasedEquipUuids.clear();
|
||||
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
|
||||
@@ -49,9 +49,9 @@ export class MissionHeroComp extends CCComp {
|
||||
|
||||
/** 硬编码的6个英雄占位点 */
|
||||
public static readonly HERO_POSITIONS: Vec3[] = [
|
||||
v3(-200, BoxSet.GAME_LINE, 0), // index 0 (node_index 1): Top Front 第二
|
||||
v3(-120, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front 第一
|
||||
v3(-280, BoxSet.GAME_LINE, 0), // index 2 (node_index 3): Bot Front 第三
|
||||
v3(-220, BoxSet.GAME_LINE, 0), // index 0 (node_index 1): Top Front 第二
|
||||
v3(-140, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front 第一
|
||||
v3(-300, BoxSet.GAME_LINE, 0), // index 2 (node_index 3): Bot Front 第三
|
||||
// v3(-280, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back
|
||||
// v3(-280, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
// v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
|
||||
|
||||
Reference in New Issue
Block a user