Files
18xchat/assets/Scripts/chat18x/ui/panels/ChatPanel.ts
T
2025-09-11 18:49:27 +08:00

498 lines
14 KiB
TypeScript

import {
_decorator,
Component,
EditBox,
Node,
Label,
Sprite,
UITransform,
VideoPlayer,
Size,
Vec3,
} from "cc";
// 首先加载 polyfills 以确保兼容性
import "../../utils/polyfills";
import { DialogManager } from "../../manager/DialogManager";
import { ChatController, IChatPanelCallback } from "../../core/ChatController";
import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
import Utils from "db://assets/Scripts/Main/Common/Utils";
import { InnerMsgCode } from "db://assets/Scripts/Main/Config/InnerMsgCode";
import { ChatContentsLayout } from "../components/ChatContentsLayout";
import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager";
import GameRootUI from "db://assets/Scripts/Main/Common/GameRootUI";
import { ImagePopup } from "../components/ImagePopup";
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
import LanguageUtils from "../../../Main/Common/LanguageUtils";
import { UITransitionHelper } from "../../utils/UITransitionHelper";
import { VideoEmotion, purchase } from "../../../schema/schema";
import { PayToTalkSubpanel } from "./PayToTalkSubpanel";
import { DataId, DataManager } from "../../data/DataManager";
import { GirlData } from "../../data/GirlData";
import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer";
import { NavigationManager } from "../../manager/NavigationManager";
const { ccclass, property } = _decorator;
@ccclass("ChatPanel")
export class ChatPanel extends li_BaseView implements IChatPanelCallback {
manager: DialogManager = null;
private chatController: ChatController = null;
@property(PayToTalkSubpanel)
payToTalkPanel: PayToTalkSubpanel = null;
@property(EditBox)
editBox: EditBox = null;
@property(Label)
girlName: Label = null;
// @property(Sprite)
// girlImg: Sprite = null;
// 移除直接的 VideoPlayer 引用,改用 SceneBgVideoLayer 单例
// @property(VideoPlayer)
// girlVideo: VideoPlayer = null;
@property(UITransform)
videoArea: UITransform = null;
/** 获取 SceneBgVideoLayer 单例实例 */
private get videoLayer(): SceneBgVideoLayer | null {
return SceneBgVideoLayer.handle;
}
@property(ChatContentsLayout)
layout: ChatContentsLayout = null;
@property(ImagePopup)
popUpImage: ImagePopup = null;
categoryId: string;
id: number;
private _nodeTab: any = {};
nameKey: string;
private currentEmotion: VideoEmotion | null = null;
private onLanguageChangeCallback = () => {
if (!this.girlName) return;
this.girlName.string = LanguageUtils.getText(this.nameKey);
};
openUIDataCT(data) {
const newRoleId =
typeof data === "object" && data.roleId !== undefined
? data.roleId
: data;
// 检查是否是切换角色
const isRoleSwitch = this.id && this.id !== newRoleId;
this.id = newRoleId;
this.categoryId = data.categoryId;
// 如果标记了需要滑入动画,则执行动画
// 延迟一帧执行动画,确保节点已正确加载到场景中
this.scheduleOnce(() => {
UITransitionHelper.slideInFromRight(this.node, 0.3);
}, 0);
// 获取ChatController单例并绑定当前Panel
this.chatController = ChatController.Instance;
this.chatController.bindView(this);
// 初始化或切换ChatController
if (this.id && this.id > 0) {
if (
isRoleSwitch &&
this.chatController.hasRoleData(this.categoryId, this.id)
) {
// 如果是角色切换且有缓存数据,使用switchRole
console.log(
`ChatPanel: Switching from role ${this.chatController.getCurrentRoleId()} to role ${
this.id
}`
);
this.chatController.switchRole(this.categoryId, this.id);
} else {
// 首次初始化或没有缓存数据,使用initialize
console.log(`ChatPanel: Initializing role ${this.id}`);
this.chatController.initialize(this.categoryId, this.id);
}
}
}
targetSize;
videoAreaPos;
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
// 获取 videoArea 的尺寸和位置
this.targetSize = this.videoArea.contentSize;
this.videoAreaPos = new Vec3(540, 1170, 0);
this.register();
this.refresh();
this.payToTalkPanel.node.active = false;
// 检查 SceneBgVideoLayer 是否可用
if (!this.videoLayer) {
console.error("ChatPanel: SceneBgVideoLayer.handle 未初始化");
} else {
console.log("ChatPanel: 成功获取 SceneBgVideoLayer 实例");
}
}
register() {
Utils.addInnerEL(
InnerMsgCode.Chat_DialogRefresh,
this,
this.onDialogUpdate
);
Utils.addInnerEL(
InnerMsgCode.LanguageChange,
this,
this.onLanguageChangeCallback
);
Utils.addInnerEL(
InnerMsgCode.Chat_EmotionInitialized,
this,
this.onEmotionInitialized
);
Utils.addInnerEL(
InnerMsgCode.ChatTotalCountChange,
this,
this.onChatCountChange
);
}
onDestroy(): void {
Utils.removeInnerEL(
InnerMsgCode.Chat_DialogRefresh,
this,
this.onDialogUpdate
);
Utils.removeInnerEL(
InnerMsgCode.LanguageChange,
this,
this.onLanguageChangeCallback
);
Utils.removeInnerEL(
InnerMsgCode.ChatTotalCountChange,
this,
this.onChatCountChange
);
Utils.removeInnerEL(
InnerMsgCode.Chat_EmotionInitialized,
this,
this.onEmotionInitialized
);
// 解绑ChatController(不销毁,因为它是单例)
if (this.chatController) {
this.chatController.unbindView();
this.chatController = null;
}
}
refresh() {
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
this.nameKey = girlData.getGrilName(this.categoryId, this.id);
this.girlName.string = LanguageUtils.getText(this.nameKey);
// 通过ChatController获取商业视频数据
const chatModel = this.chatController.getChatModel();
// Load initial video based on current emotion
if (this.videoLayer) {
// Get current emotion from ChatController
const currentEmotion = this.chatController.getCurrentEmotion();
console.log(
`Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}`
);
// Get appropriate video from ChatModel
const initialVideo =
chatModel.getVideoByEmotion(currentEmotion) ||
chatModel.getDefaultVideo();
if (initialVideo) {
console.log(
`Loading initial video: ${initialVideo} (emotion: ${VideoEmotion[initialVideo]})`
);
// Set current emotion to the loaded video's emotion
this.currentEmotion = this.chatController.getCurrentEmotion();
// 使用新的统一 playVideo 方法,现在它会自动处理缩放和位置
this.videoLayer.playVideo(initialVideo, {
loop: true,
size: this.targetSize,
position: this.videoAreaPos,
});
// 注意:移除了 adjustVideoScale() 调用,因为 playVideo 现在会自动处理缩放和位置
}
} else {
this.currentEmotion = null; // Reset current emotion when no videos available
console.log("No SceneBgVideoLayer available for role", this.id);
}
}
/**
* @deprecated 此方法已废弃,现在由 SceneBgVideoLayer.playVideo() 自动处理缩放和位置
* 保留此方法仅为了兼容性,但不再主动调用
*/
adjustVideoScale() {
console.log(
"ChatPanel: adjustVideoScale() 方法已废弃,缩放和位置由 playVideo() 自动处理"
);
// 如果确实需要手动调整,可以重新调用 playVideo
// this.videoLayer.playVideo(currentVideo, { size, position });
}
onChatCountChange() {
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id);
if (triggerId == 0) return;
this.popUpImage.refresh(this.categoryId, this.id, triggerId);
}
setVideoEnable(enable: boolean) {
if (!this.videoLayer) {
console.error("ChatPanel: SceneBgVideoLayer 不可用");
return;
}
if (enable) {
this.videoLayer.resume();
console.log("ChatPanel: 恢复视频播放");
} else {
this.videoLayer.pause();
console.log("ChatPanel: 暂停视频播放");
}
}
private switchVideoByEmotion(emotion: VideoEmotion): void {
if (!this.videoLayer) {
console.warn("Cannot switch video: missing video player");
return;
}
// Get video data from ChatModel through ChatController
const chatModel = this.chatController.getChatModel();
// Check if emotion has changed
if (this.currentEmotion === emotion) {
console.log(
`Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch`
);
return;
}
// Get video matching the emotion from ChatModel
const matchingVideo = chatModel.getVideoByEmotion(emotion);
if (matchingVideo) {
console.log(
`Switching to video for emotion ${
VideoEmotion[emotion]
}: ${matchingVideo} (from ${
this.currentEmotion !== null
? VideoEmotion[this.currentEmotion]
: "null"
})`
);
this.videoLayer.playVideo(matchingVideo, {
loop: true,
size: this.targetSize,
position: this.videoAreaPos, // 修复:使用正确的位置而不是 Vec3.ZERO
});
// Update current emotion state
this.currentEmotion = emotion;
// 注意:移除了 adjustVideoScale() 调用,因为 playVideo 现在会自动处理缩放和位置
this.setVideoEnable(true);
} else {
console.warn(
`No video found for emotion ${VideoEmotion[emotion]}, falling back to default video`
);
// Fallback to default video from ChatModel
const fallbackVideo = chatModel.getDefaultVideo();
if (fallbackVideo) {
console.log(
`Loading fallback video: ${fallbackVideo} (emotion: ${VideoEmotion[fallbackVideo]})`
);
this.videoLayer.playVideo(fallbackVideo, {
loop: true,
size: this.targetSize,
position: this.videoAreaPos,
});
// Update current emotion state to the fallback video's emotion
this.currentEmotion = this.chatController.getCurrentEmotion();
// 注意:移除了 adjustVideoScale() 调用,因为 playVideo 现在会自动处理缩放和位置
this.setVideoEnable(true);
}
}
}
private onEmotionInitialized(data: {
roleId: number;
emotion: VideoEmotion;
}): void {
// 检查是否是当前角色
if (data.roleId === this.id) {
console.log(
`Emotion initialized for current role ${this.id}: ${
VideoEmotion[data.emotion]
}`
);
// 切换到对应的视频
this.switchVideoByEmotion(data.emotion);
} else {
console.log(
`Emotion initialized for role ${data.roleId} (not current role ${this.id}), ignoring`
);
}
}
onDialogUpdate() {
if (this.layout) {
this.layout.UpdateDialog(DialogManager.getInstance().getDialogs());
}
}
protected onEnable(): void {
if (!this.manager) this.manager = DialogManager.getInstance();
}
public async OnClickSend() {
const str = this.editBox.string;
if (!str || str == "") return;
// 通过ChatController发送消息
const succeed = await this.chatController.sendMessage(str);
if (succeed)
// 清空输入框
this.editBox.string = "";
}
// === IChatPanelCallback 接口实现 ===
/**
* 消息发送开始回调
* @param message 用户发送的消息
*/
public onMessageSent(message: string): void {
console.log("Message sent:", message);
// 消息发送后开始显示加载动画,由DialogManager处理
}
/**
* 接收到AI回复回调
* @param response AI的回复内容
*/
public onMessageReceived(response: string): void {
console.log("Message received:", response);
// AI回复收到后加载动画会被自动移除,可以在这里添加其他UI效果
}
/**
* 对话更新回调
*/
public onDialogUpdated(): void {
// 更新对话显示
this.onDialogUpdate();
}
/**
* 错误处理回调
* @param error 错误信息
*/
public onError(error: Error): void {
console.error("Chat error:", error);
// 出错时加载动画会被自动移除,可以在这里显示错误提示给用户
}
onChatLimitReached(): void {
this.payToTalkPanel.show(this.categoryId, this.id);
}
/**
* 情绪状态更新回调 (实现IChatPanelCallback接口)
* @param emotion 当前情绪状态
*/
public onEmotionUpdated(emotion: VideoEmotion): void {
console.log(
`ChatPanel: Emotion updated to ${VideoEmotion[emotion]} (${emotion})`
);
// Switch video based on the new emotion
this.switchVideoByEmotion(emotion);
// Additional UI updates can be added here based on emotion
// 例如:改变角色表情、背景色、播放相应的动画等
switch (emotion) {
case VideoEmotion.calm_down:
// 平静状态的UI更新
console.log("UI state: Calm");
break;
case VideoEmotion.arousal:
// 兴奋状态的UI更新
console.log("UI state: Arousal");
break;
case VideoEmotion.desire:
// 渴望状态的UI更新
console.log("UI state: Desire");
break;
case VideoEmotion.passion:
// 激情状态的UI更新
console.log("UI state: Passion");
break;
case VideoEmotion.orgasm:
// 高潮状态的UI更新
console.log("UI state: Orgasm");
break;
}
}
returnBtn() {
// Navigate back to GirlDetailPanel with current girlId using smooth transition
NavigationManager.Instance.navigateToGirlDetail(
this.id,
NavigationManager.getTransitionPresets().SLIDE_RIGHT_TO_LEFT,
this
);
}
onClickRecord() {
ViewManager.I.openBundlesView("RecordPanel", this.id);
}
/**
* 手动清除指定角色的数据(调试用)
* @param roleId 角色ID,不传则清除当前角色
*/
public clearRoleDataDebug(roleId?: number): void {
if (!this.chatController) {
console.warn("ChatController not initialized");
return;
}
const targetRoleId = roleId || this.id;
if (!targetRoleId) {
console.warn("No role ID specified");
return;
}
this.chatController.clearRoleData(targetRoleId);
console.log(`Cleared data for role ${targetRoleId}`);
}
}