You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
7.2 KiB
175 lines
7.2 KiB
|
3 weeks ago
|
import { Color, Label, Node, Sprite } from 'cc';
|
||
|
|
import { RankBoardEntry, RankBoardScope } from '../../manager/RankBoardManager';
|
||
|
|
import { RankDataManager } from '../../manager/RankDataManager';
|
||
|
|
import { HeadIconManager } from '../../manager/HeadIconManager';
|
||
|
|
import { TableNames } from '../../game/ConfigTableData';
|
||
|
|
import { SpriteLoad } from '../../../mx/components/SpriteLoad';
|
||
|
|
|
||
|
|
/** 排行条目:head / region / name / level / lay(num+星星) / winNum,以及名次背景 */
|
||
|
|
export class RankItemView {
|
||
|
|
/**
|
||
|
|
* 绑定排行条目
|
||
|
|
* @param scope 榜单类型,决定 num / winNum / level / 星星 展示
|
||
|
|
*/
|
||
|
|
static bind(root: Node, data: RankBoardEntry, scope: RankBoardScope, skipBg = false): void {
|
||
|
|
if (!root?.isValid) return;
|
||
|
|
const daily = scope === RankBoardScope.DailyWins;
|
||
|
|
this._bindRankDecor(root, data.rank, skipBg);
|
||
|
|
|
||
|
|
const regionNode = root.find('region');
|
||
|
|
this._setActive(regionNode, true);
|
||
|
|
this._setLabelAutoWidth(regionNode, this._formatRegion(data.region));
|
||
|
|
this._setLabelAutoWidth(root.find('name'), this._formatNickname(data.nickname));
|
||
|
|
|
||
|
|
const lay = root.find('lay');
|
||
|
|
const numNode = root.find('lay/num') ?? root.find('num');
|
||
|
|
const starNode = root.find('lay/星星') ?? root.find('星星');
|
||
|
|
const winNumNode = root.find('winNum');
|
||
|
|
|
||
|
|
this._setActive(root.find('level'), !daily);
|
||
|
|
this._setActive(lay, !daily);
|
||
|
|
this._setActive(numNode, !daily);
|
||
|
|
this._setActive(starNode, !daily);
|
||
|
|
this._setActive(winNumNode, daily);
|
||
|
|
|
||
|
|
if (!daily) {
|
||
|
|
const totalStars = RankDataManager.clampRankStars(data.score);
|
||
|
|
const intraStars = RankDataManager.getIntraTierStars(totalStars);
|
||
|
|
this._setLabel(root.find('level'), this._rankNameByScore(totalStars));
|
||
|
|
this._setLabel(numNode, String(intraStars));
|
||
|
|
} else {
|
||
|
|
this._setLabel(winNumNode, String(data.score ?? 0));
|
||
|
|
}
|
||
|
|
this._bindAvatar(root, data.avatarUrl);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* rank1/2/3 前三名,otherRank 其他名次(rank 写入数字)。
|
||
|
|
* ui_rankItem 另切 bg1~bg4;ui_selfRank 不改背景。
|
||
|
|
*/
|
||
|
|
private static _bindRankDecor(root: Node, rank: number, skipBg: boolean): void {
|
||
|
|
const r = Math.max(0, Math.floor(Number(rank) || 0));
|
||
|
|
const top = r === 1 || r === 2 || r === 3 ? r : 0;
|
||
|
|
if (!skipBg) {
|
||
|
|
this._setActive(root.find('bg1'), top === 1);
|
||
|
|
this._setActive(root.find('bg2'), top === 2);
|
||
|
|
this._setActive(root.find('bg3'), top === 3);
|
||
|
|
this._setActive(root.find('bg4'), top === 0);
|
||
|
|
}
|
||
|
|
this._setActive(root.find('rank1'), top === 1);
|
||
|
|
this._setActive(root.find('rank2'), top === 2);
|
||
|
|
this._setActive(root.find('rank3'), top === 3);
|
||
|
|
const other = root.find('otherRank');
|
||
|
|
this._setActive(other, top === 0);
|
||
|
|
if (other?.isValid && top === 0) {
|
||
|
|
if(r > 100){
|
||
|
|
//100名以上未上榜
|
||
|
|
other.find('rank').active = false;
|
||
|
|
other.find('noRank').active = true;
|
||
|
|
this._setLabel(other.find('noRank'), "未上榜");
|
||
|
|
}else{
|
||
|
|
other.find('rank').active = true;
|
||
|
|
other.find('noRank').active = false;
|
||
|
|
this._setLabel(other.find('rank'), String(r));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _setActive(node: Node | null, active: boolean): void {
|
||
|
|
if (node?.isValid) node.active = active;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 地区展示 */
|
||
|
|
private static _formatRegion(region: string): string {
|
||
|
|
return String(region ?? '').trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 昵称展示最多 7 字 */
|
||
|
|
private static _formatNickname(nickname: string): string {
|
||
|
|
const text = String(nickname ?? '').trim() || '棋手';
|
||
|
|
return text.length > 7 ? text.slice(0, 7) : text;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 用总星数反查 Rank 表段位名 */
|
||
|
|
private static _rankNameByScore(score: number): string {
|
||
|
|
return (RankDataManager.resolveByStars(score)?.name ?? '').trim() || '初出茅庐';
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _setLabel(node: Node | null, text: string): void {
|
||
|
|
if (!node?.isValid) return;
|
||
|
|
const label = node.getComponent(Label);
|
||
|
|
if (label) label.string = text;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** region / name 等左对齐文案:按内容撑开宽度,避免 prefab 默认宽度过窄只显示一字 */
|
||
|
|
private static _setLabelAutoWidth(node: Node | null, text: string): void {
|
||
|
|
if (!node?.isValid) return;
|
||
|
|
const label = node.getComponent(Label);
|
||
|
|
if (!label) return;
|
||
|
|
label.overflow = Label.Overflow.NONE;
|
||
|
|
label.string = text;
|
||
|
|
label.updateRenderData(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** headMask:远程头像用 head,本地头像用 localHead */
|
||
|
|
private static _bindAvatar(root: Node, url: string): void {
|
||
|
|
const headMask = root.find('headMask');
|
||
|
|
const headNode = headMask?.find('head') ?? root.find('head');
|
||
|
|
const localHeadNode = headMask?.find('localHead') ?? null;
|
||
|
|
const avatar = (url ?? '').trim();
|
||
|
|
if (!avatar) {
|
||
|
|
this._clearAvatarNode(headNode);
|
||
|
|
this._clearAvatarNode(localHeadNode);
|
||
|
|
this._setActive(headNode, false);
|
||
|
|
this._setActive(localHeadNode, false);
|
||
|
|
if (headMask?.isValid) headMask.active = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (headMask?.isValid) headMask.active = true;
|
||
|
|
const localId = this._parseLocalHeadIconId(avatar);
|
||
|
|
if (localId > 0) {
|
||
|
|
const row = gg.data.table.getTableData<ITableHeadicon>(TableNames.Headicon, localId);
|
||
|
|
if (!row) {
|
||
|
|
console.error('[RankItemView] Headicon 不存在', localId);
|
||
|
|
this._clearAvatarNode(headNode);
|
||
|
|
this._clearAvatarNode(localHeadNode);
|
||
|
|
this._setActive(headNode, false);
|
||
|
|
this._setActive(localHeadNode, false);
|
||
|
|
if (headMask?.isValid) headMask.active = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._setActive(headNode, false);
|
||
|
|
this._clearAvatarNode(headNode);
|
||
|
|
this._setActive(localHeadNode, true);
|
||
|
|
if (!localHeadNode?.isValid) {
|
||
|
|
console.error('[RankItemView] localHead 节点缺失', localId);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
localHeadNode.getOrAddComponent(SpriteLoad);
|
||
|
|
HeadIconManager.applyRowToNode(localHeadNode, row);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._setActive(localHeadNode, false);
|
||
|
|
this._clearAvatarNode(localHeadNode);
|
||
|
|
this._setActive(headNode, true);
|
||
|
|
if (!headNode?.isValid) return;
|
||
|
|
const sp = headNode.getComponent(Sprite);
|
||
|
|
if (sp) sp.color = Color.WHITE;
|
||
|
|
headNode.getOrAddComponent(SpriteLoad).loadRemote(avatar);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 清空头像节点 SpriteLoad */
|
||
|
|
private static _clearAvatarNode(node: Node | null): void {
|
||
|
|
if (!node?.isValid) return;
|
||
|
|
node.getComponent(SpriteLoad)?.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 纯数字头像字段视为本地 Headicon.id */
|
||
|
|
private static _parseLocalHeadIconId(avatar: string): number {
|
||
|
|
const text = avatar.trim();
|
||
|
|
if (!/^\d+$/.test(text)) return 0;
|
||
|
|
const id = Math.floor(Number(text));
|
||
|
|
return id > 0 ? id : 0;
|
||
|
|
}
|
||
|
|
}
|