refactor(CardLiteComp): 重构英雄图鉴卡组件逻辑
1. 移除对全类型卡牌数据的依赖,仅保留英雄卡展示逻辑 2. 简化数据流转,直接通过英雄UUID渲染卡面 3. 更新注释文档与变量命名,统一业务语义 4. 调整预制体节点命名与配置项
This commit is contained in:
@@ -1403,8 +1403,8 @@
|
|||||||
},
|
},
|
||||||
"_lscale": {
|
"_lscale": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": -2.5,
|
"x": -2,
|
||||||
"y": 2.5,
|
"y": 2,
|
||||||
"z": 1
|
"z": 1
|
||||||
},
|
},
|
||||||
"_mobility": 0,
|
"_mobility": 0,
|
||||||
@@ -1976,6 +1976,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "28YQfbOz1OCrINhHQ3NA1a",
|
"fileId": "28YQfbOz1OCrINhHQ3NA1a",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -12693,8 +12695,8 @@
|
|||||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||||
"__expectedType__": "cc.Prefab"
|
"__expectedType__": "cc.Prefab"
|
||||||
},
|
},
|
||||||
"lv_node": {
|
"lv_label": {
|
||||||
"__id__": 125
|
"__id__": 128
|
||||||
},
|
},
|
||||||
"type_node": {
|
"type_node": {
|
||||||
"__id__": 131
|
"__id__": 131
|
||||||
|
|||||||
@@ -3,41 +3,34 @@
|
|||||||
* @description 英雄图鉴卡预制体组件(UI 视图层)
|
* @description 英雄图鉴卡预制体组件(UI 视图层)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
* 1. 管理英雄图鉴中单张卡牌的 **显示**(名称、图标动画、费用、卡池等级、等级)。
|
* 1. 管理英雄图鉴中单张英雄卡牌的 **显示**(名称、图标动画、卡池等级、英雄等级)。
|
||||||
* 2. 接收 CardConfig 数据并渲染对应卡面(英雄 / 技能 / 特殊卡)。
|
* 2. 接收英雄 UUID 并渲染对应卡面。
|
||||||
* 3. 点击卡牌时通过事件通知父组件(HerosListComp)切换选中英雄详情。
|
* 3. 点击卡牌时通过事件通知父组件(HerosListComp)切换选中英雄详情。
|
||||||
*
|
*
|
||||||
* 关键设计:
|
* 关键设计:
|
||||||
* - 轻量版 CardComp,无拖拽使用、无锁定、无费用扣除等交互。
|
* - 轻量版 CardComp,仅用于英雄展示,无拖拽使用、无锁定、无费用扣除等交互。
|
||||||
* - 英雄卡图标加载 idle 动画剪辑并提取首帧静态展示;非英雄卡使用静态图标。
|
* - 英雄卡图标加载 idle 动画剪辑并提取首帧静态展示。
|
||||||
* - 通过 iconVisualToken 防止异步加载竞态。
|
* - 通过 iconVisualToken 防止异步加载竞态。
|
||||||
*
|
*
|
||||||
* 依赖:
|
* 依赖:
|
||||||
* - CardConfig / CardType / CKind 等卡牌数据结构(CardSet)
|
|
||||||
* - HeroInfo(heroSet)—— 用于渲染英雄卡面信息
|
* - HeroInfo(heroSet)—— 用于渲染英雄卡面信息
|
||||||
* - SkillSet(SkillSet)—— 用于渲染技能卡图标
|
* - smc —— 全局单例,访问图标图集缓存与英雄局外等级
|
||||||
* - smc —— 全局单例,访问图标图集缓存
|
|
||||||
*/
|
*/
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
import { _decorator, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteFrame, Tween, tween, UIOpacity, Vec3, resources } from "cc";
|
import { _decorator, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteFrame, Tween, tween, UIOpacity, Vec3, resources } from "cc";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet";
|
|
||||||
import { SkillCardList } from "../common/config/SCardSet";
|
|
||||||
import { HeroInfo } from "../common/config/heroSet";
|
import { HeroInfo } from "../common/config/heroSet";
|
||||||
import { SkillSet } from "../common/config/SkillSet";
|
|
||||||
import { getLvColor, CARD_LV_BG_COLORS } from "../common/config/GameSet";
|
import { getLvColor, CARD_LV_BG_COLORS } from "../common/config/GameSet";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
|
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** card_lv → BG_node 子节点(颜色)名映射已移至 GameSet.CARD_LV_BG_COLORS */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CardLiteComp —— 单张卡牌简单视图组件
|
* CardLiteComp —— 单张英雄卡简单视图组件
|
||||||
*
|
*
|
||||||
* 挂载在英雄图鉴卡预制体上,由 HerosListComp 实例化并管理。
|
* 挂载在英雄图鉴卡预制体上,由 HerosListComp 实例化并管理。
|
||||||
* 负责单卡的渲染与点击选择。
|
* 负责单张英雄卡的渲染与点击选择。
|
||||||
*/
|
*/
|
||||||
@ccclass('CardLiteComp')
|
@ccclass('CardLiteComp')
|
||||||
@ecs.register('CardLiteComp', false)
|
@ecs.register('CardLiteComp', false)
|
||||||
@@ -61,10 +54,7 @@ export class CardLiteComp extends CCComp {
|
|||||||
|
|
||||||
// ======================== 运行时状态 ========================
|
// ======================== 运行时状态 ========================
|
||||||
|
|
||||||
card_cost: number = 0
|
|
||||||
card_type: CardType = CardType.Hero
|
|
||||||
card_uuid: number = 0
|
card_uuid: number = 0
|
||||||
private cardData: CardConfig | null = null;
|
|
||||||
private iconVisualToken: number = 0;
|
private iconVisualToken: number = 0;
|
||||||
private opacityComp: UIOpacity | null = null;
|
private opacityComp: UIOpacity | null = null;
|
||||||
|
|
||||||
@@ -77,9 +67,6 @@ export class CardLiteComp extends CCComp {
|
|||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
|
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
|
||||||
this.opacityComp.opacity = 255;
|
this.opacityComp.opacity = 255;
|
||||||
if (!this.cardData) {
|
|
||||||
this.applyEmptyUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
@@ -94,61 +81,29 @@ export class CardLiteComp extends CCComp {
|
|||||||
// ======================== 对外接口 ========================
|
// ======================== 对外接口 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置卡牌数据并渲染。
|
* 通过英雄 UUID 渲染英雄卡(纯展示场景)。
|
||||||
* @param data 卡牌配置数据
|
* 从 HeroInfo 读取静态配置,从 smc 读取局外等级。
|
||||||
*/
|
|
||||||
setData(data: CardConfig) {
|
|
||||||
if (!data) return;
|
|
||||||
this.cardData = data;
|
|
||||||
this.card_uuid = data.uuid;
|
|
||||||
this.card_type = data.type;
|
|
||||||
this.card_cost = data.cost ?? 0;
|
|
||||||
this.node.active = true;
|
|
||||||
this.applyCardUI();
|
|
||||||
mLogger.log(this.debugMode, "CardLiteComp", "setData", {
|
|
||||||
uuid: this.card_uuid,
|
|
||||||
type: this.card_type,
|
|
||||||
cost: this.card_cost
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过英雄 UUID 直接设置英雄卡(纯展示场景)。
|
|
||||||
* 从 HeroInfo 读取静态配置,不再依赖 CardPoolList。
|
|
||||||
* @param uuid 英雄 UUID
|
* @param uuid 英雄 UUID
|
||||||
*/
|
*/
|
||||||
setHeroByUuid(uuid: number) {
|
setHeroByUuid(uuid: number) {
|
||||||
const hero = HeroInfo[uuid];
|
const hero = HeroInfo[uuid];
|
||||||
if (!hero) return;
|
if (!hero) return;
|
||||||
this.setData({
|
this.card_uuid = uuid;
|
||||||
uuid: hero.uuid,
|
this.node.active = true;
|
||||||
type: CardType.Hero,
|
this.applyHeroUI(hero);
|
||||||
cost: 0,
|
mLogger.log(this.debugMode, "CardLiteComp", "setHeroByUuid", { uuid });
|
||||||
weight: 0,
|
|
||||||
card_lv: hero.card_lv ?? 1,
|
|
||||||
kind: CKind.Hero,
|
|
||||||
hero_lv: smc.getHeroOuterLv(hero.uuid)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询当前是否有卡牌数据 */
|
/** 查询当前是否有英雄数据 */
|
||||||
hasCard(): boolean {
|
hasCard(): boolean {
|
||||||
return !!this.cardData;
|
return this.card_uuid > 0;
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取当前卡牌数据 */
|
|
||||||
getCardData(): CardConfig | null {
|
|
||||||
return this.cardData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空卡牌数据并重置 UI。
|
* 清空英雄数据并重置 UI。
|
||||||
*/
|
*/
|
||||||
clear() {
|
clear() {
|
||||||
this.cardData = null;
|
|
||||||
this.card_uuid = 0;
|
this.card_uuid = 0;
|
||||||
this.card_cost = 0;
|
|
||||||
this.card_type = CardType.Hero;
|
|
||||||
this.applyEmptyUI();
|
this.applyEmptyUI();
|
||||||
this.node.active = false;
|
this.node.active = false;
|
||||||
}
|
}
|
||||||
@@ -176,128 +131,63 @@ export class CardLiteComp extends CCComp {
|
|||||||
|
|
||||||
/** 点击卡牌:触发回调通知父组件 */
|
/** 点击卡牌:触发回调通知父组件 */
|
||||||
private onCardClick(event: EventTouch) {
|
private onCardClick(event: EventTouch) {
|
||||||
if (!this.cardData) return;
|
if (!this.hasCard()) return;
|
||||||
event.propagationStopped = true;
|
event.propagationStopped = true;
|
||||||
if (this.onClickCallback) {
|
if (this.onClickCallback) {
|
||||||
this.onClickCallback(this);
|
this.onClickCallback(this);
|
||||||
}
|
}
|
||||||
mLogger.log(this.debugMode, "CardLiteComp", "card clicked", {
|
mLogger.log(this.debugMode, "CardLiteComp", "card clicked", { uuid: this.card_uuid });
|
||||||
uuid: this.card_uuid,
|
|
||||||
type: this.card_type
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== UI 渲染 ========================
|
// ======================== UI 渲染 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据当前 cardData 渲染卡面。
|
* 渲染英雄卡面。
|
||||||
*
|
*
|
||||||
* 渲染逻辑根据卡牌类型分三路:
|
* 1. 背景:按 card_lv 切换颜色底框(green/blue/purple/yellow/red)。
|
||||||
* 1. 英雄卡:显示英雄名、等级、idle 动画。
|
* 2. 名称:英雄名。
|
||||||
* 2. 技能卡:显示技能名、静态图标。
|
* 3. 等级:局外成长等级(Lv.X),颜色按 getLvColor。
|
||||||
* 3. 特殊卡:显示卡名、静态图标。
|
* 4. 图标:idle 动画首帧静态展示。
|
||||||
*
|
|
||||||
* 同时根据 card_lv 切换背景底框,根据 kind 切换种类标识。
|
|
||||||
*/
|
*/
|
||||||
private applyCardUI() {
|
private applyHeroUI(hero: typeof HeroInfo[number]) {
|
||||||
if (!this.cardData) {
|
|
||||||
this.applyEmptyUI();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.iconVisualToken += 1;
|
this.iconVisualToken += 1;
|
||||||
if (this.opacityComp) this.opacityComp.opacity = 255;
|
if (this.opacityComp) this.opacityComp.opacity = 255;
|
||||||
|
|
||||||
mLogger.log(this.debugMode, "CardLiteComp", "applyCardUI nodes", {
|
// 背景:按 card_lv 切换颜色底框
|
||||||
uuid: this.card_uuid,
|
|
||||||
hasName: !!this.name_node,
|
|
||||||
hasIcon: !!this.icon_node,
|
|
||||||
hasCost: !!this.cost_node,
|
|
||||||
hasBG: !!this.BG_node,
|
|
||||||
hasLvl: !!this.lvl_node,
|
|
||||||
nodeActive: this.node.active,
|
|
||||||
nodeScale: this.node.scale.toString(),
|
|
||||||
nodePos: this.node.position.toString(),
|
|
||||||
parentName: this.node.parent?.name || "none",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 背景:按 card_lv 切换颜色底框(BG_node 下为 green/blue/purple/yellow/red 子节点)
|
|
||||||
if (this.BG_node) {
|
if (this.BG_node) {
|
||||||
const bgLv = Math.min(Math.max(this.cardData.base_card_lv ?? this.cardData.card_lv ?? 1, 1), 5);
|
const bgLv = Math.min(Math.max(hero.card_lv ?? 1, 1), 5);
|
||||||
const color = CARD_LV_BG_COLORS[bgLv - 1];
|
const color = CARD_LV_BG_COLORS[bgLv - 1];
|
||||||
this.BG_node.children.forEach(child => child.active = (child.name === color));
|
this.BG_node.children.forEach(child => child.active = (child.name === color));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.card_type === CardType.Hero) {
|
// 名称
|
||||||
const hero = HeroInfo[this.card_uuid];
|
|
||||||
const heroLv = Math.max(1, smc.getHeroOuterLv(this.card_uuid) || hero?.lv || 1);
|
|
||||||
this.setLabel(this.name_node, `${hero?.name || ""}`);
|
this.setLabel(this.name_node, `${hero?.name || ""}`);
|
||||||
|
|
||||||
|
// 等级:局外成长等级
|
||||||
if (this.lvl_node) {
|
if (this.lvl_node) {
|
||||||
|
const heroLv = Math.max(1, smc.getHeroOuterLv(this.card_uuid) || hero?.lv || 1);
|
||||||
this.lvl_node.string = `Lv.${heroLv}`;
|
this.lvl_node.string = `Lv.${heroLv}`;
|
||||||
this.lvl_node.color = getLvColor(heroLv);
|
|
||||||
this.lvl_node.node.active = true;
|
this.lvl_node.node.active = true;
|
||||||
}
|
}
|
||||||
} else if (this.card_type === CardType.Skill) {
|
|
||||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
|
||||||
const skill = SkillSet[this.card_uuid];
|
|
||||||
const skillCard = SkillCardList.find(c => c.uuid === this.card_uuid) ?? CardPoolList.find(c => c.uuid === this.card_uuid);
|
|
||||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
|
||||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
|
||||||
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
|
|
||||||
} else {
|
|
||||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
|
||||||
// 动态升级卡:显示对应英雄名 + "升级"后缀(注意:升级卡绑定的是 target_hero_eid 而非 uuid,
|
|
||||||
// 此轻量列表场景通常不展示升级卡;为保持渲染健壮性,回退到本卡 uuid 查 HeroInfo)
|
|
||||||
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_eid) {
|
|
||||||
const targetHero = HeroInfo[this.card_uuid];
|
|
||||||
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
|
|
||||||
} else {
|
|
||||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
|
||||||
? SpecialUpgradeCardList[this.card_uuid]
|
|
||||||
: SpecialRefreshCardList[this.card_uuid];
|
|
||||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
|
||||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
|
||||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 费用:英雄卡不显示
|
||||||
if (this.cost_node) {
|
if (this.cost_node) {
|
||||||
this.cost_node.active = true;
|
this.cost_node.active = false;
|
||||||
const numNode = this.cost_node.getChildByName("num");
|
|
||||||
if (numNode) {
|
|
||||||
this.setLabel(numNode, `${this.card_cost}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 图标:idle 动画首帧静态展示(镜像翻转,原图朝右,卡面需朝左)
|
||||||
if (this.icon_node) {
|
if (this.icon_node) {
|
||||||
const iconNode = this.icon_node;
|
this.updateHeroAnimation(this.icon_node, this.card_uuid, this.iconVisualToken);
|
||||||
if (this.card_type === CardType.Hero) {
|
|
||||||
iconNode.setScale(new Vec3(-1.5, 1.5, 1));
|
|
||||||
this.updateHeroAnimation(iconNode, this.card_uuid, this.iconVisualToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iconNode.setScale(new Vec3(1, 1, 1));
|
|
||||||
const iconId = this.resolveCardIconId(this.card_type, this.card_uuid);
|
|
||||||
if (iconId) {
|
|
||||||
this.updateIcon(iconNode, iconId);
|
|
||||||
} else {
|
|
||||||
const sprite = iconNode?.getComponent(Sprite) || iconNode?.getComponentInChildren(Sprite);
|
|
||||||
if (sprite) sprite.spriteFrame = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染空槽状态:清空名称、费用、等级、图标等。
|
* 渲染空槽状态:清空名称、等级、背景、图标等。
|
||||||
*/
|
*/
|
||||||
private applyEmptyUI() {
|
private applyEmptyUI() {
|
||||||
this.iconVisualToken += 1;
|
this.iconVisualToken += 1;
|
||||||
this.setLabel(this.name_node, "");
|
this.setLabel(this.name_node, "");
|
||||||
if (this.cost_node) {
|
if (this.cost_node) {
|
||||||
const numNode = this.cost_node.getChildByName("num");
|
|
||||||
if (numNode) {
|
|
||||||
this.setLabel(numNode, "");
|
|
||||||
}
|
|
||||||
this.cost_node.active = false;
|
this.cost_node.active = false;
|
||||||
}
|
}
|
||||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||||
@@ -305,7 +195,6 @@ export class CardLiteComp extends CCComp {
|
|||||||
this.BG_node.children.forEach(child => child.active = false);
|
this.BG_node.children.forEach(child => child.active = false);
|
||||||
}
|
}
|
||||||
if (this.icon_node) {
|
if (this.icon_node) {
|
||||||
this.icon_node.setScale(new Vec3(1, 1, 1));
|
|
||||||
const sprite = this.icon_node?.getComponent(Sprite) || this.icon_node?.getComponentInChildren(Sprite);
|
const sprite = this.icon_node?.getComponent(Sprite) || this.icon_node?.getComponentInChildren(Sprite);
|
||||||
if (sprite) sprite.spriteFrame = null;
|
if (sprite) sprite.spriteFrame = null;
|
||||||
}
|
}
|
||||||
@@ -337,33 +226,7 @@ export class CardLiteComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新图标:从全局缓存图集中获取对应帧。
|
* 为英雄图标加载 idle 动画剪辑,提取首帧 SpriteFrame 静止展示。
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据卡牌类型和 UUID 解析出图标 ID(在 SpriteAtlas 中的帧名)。
|
|
||||||
*/
|
|
||||||
private resolveCardIconId(type: CardType, uuid: number): string {
|
|
||||||
if (type === CardType.Skill) {
|
|
||||||
return SkillSet[uuid]?.icon || `${uuid}`;
|
|
||||||
}
|
|
||||||
if (type === CardType.Hero) {
|
|
||||||
return HeroInfo[uuid]?.icon || `${uuid}`;
|
|
||||||
}
|
|
||||||
return `${uuid}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为英雄图标加载 idle 动画剪辑,提取首帧 SpriteFrame 静止展示(参考 CHeroComp)。
|
|
||||||
* 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。
|
* 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。
|
||||||
*/
|
*/
|
||||||
private updateHeroAnimation(node: Node, uuid: number, token: number) {
|
private updateHeroAnimation(node: Node, uuid: number, token: number) {
|
||||||
|
|||||||
Reference in New Issue
Block a user