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 { PriceType, 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, PanelType } from "../../manager/NavigationManager"; import { GirlService } from "../../network/services/GirlService"; import proto from "db://assets/Scripts/proto/proto.pb.js"; import { TipsPanel } from "./TipsPanel"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; 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 = {}; private waitingAI: Node; nameKey: string; private currentEmotion: VideoEmotion | null = null; private onLanguageChangeCallback = () => { if (!this.girlName) return; this.girlName.string = LanguageUtils.getText(this.nameKey); }; targetSize; videoAreaPos; base: Node; openUIDataCT(data: any) { this.base = data.base; } onLoadCT() { this.node.active = false; this.scheduleOnce(() => { this.node.active = true; }, 0.1); Utils.parseNode(this.node, this._nodeTab); this.waitingAI = this._nodeTab.waitingAI; // 获取 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) { logger.error("ChatPanel: SceneBgVideoLayer.handle 未初始化"); } else { logger.log("ChatPanel: 成功获取 SceneBgVideoLayer 实例"); } this.popUpImage.node.active = false; } register() { 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.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; } } async refresh() { const newRoleId = NavigationManager.Instance.getSelectedGirlId(); const girlData = DataManager.I.getDataById(DataId.Girl); this.waitingAI.active = false; // 检查是否是切换角色 const isRoleSwitch = this.id && this.id !== newRoleId; this.id = newRoleId; this.categoryId = NavigationManager.Instance.getSelectedCategoryId().toString(); const isFree = girlData.getGrilPriceType(this.categoryId.toString(), this.id) == PriceType.free; const isRelease = girlData.getIsRelease( this.categoryId.toString(), this.id ); if (!isFree && !isRelease) { //未解锁 NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", { base: this, category: this.categoryId, id: this.id, }); return; } // 获取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 logger.log( `ChatPanel: Switching from role ${this.chatController.getCurrentRoleId()} to role ${ this.id }` ); this.chatController.switchRole(this.categoryId, this.id); } else { // 首次初始化或没有缓存数据,使用initialize logger.log(`ChatPanel: Initializing role ${this.id}`); this.chatController.initialize(this.categoryId, this.id); } } this.nameKey = girlData.getGrilName(this.categoryId, this.id); if (this.nameKey == "") { const reqData = { id: this.id, }; let res = await GirlService.I.reqGetGirlDetail(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { // 保存数据 girlData.setGirlDetailData(this.categoryId.toString(), res.data); 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(); logger.log( `Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}` ); // Get appropriate video from ChatModel const initialVideo = chatModel.getVideoByEmotion(currentEmotion) || chatModel.getDefaultVideo(); if (initialVideo) { logger.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 logger.log("No SceneBgVideoLayer available for role", this.id); } } /** * @deprecated 此方法已废弃,现在由 SceneBgVideoLayer.playVideo() 自动处理缩放和位置 * 保留此方法仅为了兼容性,但不再主动调用 */ adjustVideoScale() { logger.log( "ChatPanel: adjustVideoScale() 方法已废弃,缩放和位置由 playVideo() 自动处理" ); // 如果确实需要手动调整,可以重新调用 playVideo // this.videoLayer.playVideo(currentVideo, { size, position }); } onChatCountChange() { logger.log("Trigger On ChatCount Change"); const girlData = DataManager.I.getDataById(DataId.Girl); let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id); if (triggerId == 0) return; logger.log("Trigger pop up Image id:" + this.id + "--" + triggerId); this.popUpImage.refresh(this.categoryId, this.id, triggerId); } setVideoEnable(enable: boolean) { if (!this.videoLayer) { logger.error("ChatPanel: SceneBgVideoLayer 不可用"); return; } if (enable) { this.videoLayer.resume(); logger.log("ChatPanel: 恢复视频播放"); } else { this.videoLayer.pause(); logger.log("ChatPanel: 暂停视频播放"); } } private switchVideoByEmotion(emotion: VideoEmotion): void { if (!this.videoLayer) { logger.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) { logger.log( `Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch` ); return; } // Get video matching the emotion from ChatModel const matchingVideo = chatModel.getVideoByEmotion(emotion); if (matchingVideo) { logger.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 { logger.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) { logger.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) { logger.log( `Emotion initialized for current role ${this.id}: ${ VideoEmotion[data.emotion] }` ); // 切换到对应的视频 this.switchVideoByEmotion(data.emotion); } else { logger.log( `Emotion initialized for role ${data.roleId} (not current role ${this.id}), ignoring` ); } } onDialogUpdate(isPlayer: boolean) { if (this.layout) { if (isPlayer) { this.layout.updatePlayerDialog( DialogManager.getInstance().GetPlayerDialog() ); } else { this.layout.updateAIDialogs(DialogManager.getInstance().GetAIDialog()); } } } protected onEnable(): void { if (!this.manager) this.manager = DialogManager.getInstance(); this.base.active = false; this.refresh(); } protected onDisable(): void { this.base.active = true; } isWaiting: boolean = false; public async OnClickSend() { const str = this.editBox.string; if (!str || str == "") { TipsPanel.show(LanguageUtils.getText("chat_error_code_1002")); return; } if (this.isWaiting) { TipsPanel.show(LanguageUtils.getText("chat_error_code_1003")); } // 通过ChatController发送消息 this.isWaiting = true; this.waitingAI.active = true; const succeed = await this.chatController.sendMessage(str); if (succeed) { // 清空输入框 this.editBox.string = ""; } this.isWaiting = false; this.waitingAI.active = false; } // === IChatPanelCallback 接口实现 === /** * 消息发送开始回调 * @param message 用户发送的消息 */ public onMessageSent(message: string): void { logger.log("Message sent:", message); // 消息发送后开始显示加载动画,由DialogManager处理 } /** * 接收到AI回复回调 * @param response AI的回复内容 */ public onMessageReceived(response: string): void { //logger.log("Message received:", response); // AI回复收到后加载动画会被自动移除,可以在这里添加其他UI效果 } /** * 对话更新回调 */ public onDialogUpdated(isPlayer: boolean): void { // 更新对话显示 this.onDialogUpdate(isPlayer); } /** * 错误处理回调 * @param error 错误信息 */ public onError(error: Error): void { logger.error("Chat error:", error); // 出错时加载动画会被自动移除,可以在这里显示错误提示给用户 } onChatLimitReached(): void { this.payToTalkPanel.show(this.categoryId, this.id); TipsPanel.show(LanguageUtils.getText("chat_error_code_1001")); } /** * 情绪状态更新回调 (实现IChatPanelCallback接口) * @param emotion 当前情绪状态 */ public onEmotionUpdated(emotion: VideoEmotion): void { logger.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更新 logger.log("UI state: Calm"); break; case VideoEmotion.arousal: // 兴奋状态的UI更新 logger.log("UI state: Arousal"); break; case VideoEmotion.desire: // 渴望状态的UI更新 logger.log("UI state: Desire"); break; case VideoEmotion.passion: // 激情状态的UI更新 logger.log("UI state: Passion"); break; case VideoEmotion.orgasm: // 高潮状态的UI更新 logger.log("UI state: Orgasm"); break; } } returnBtn() { NavigationManager.Instance.closeSubPanel(this); } onClickRecord() { NavigationManager.Instance.openPopupPanel("RecordPanel", this.id); } /** * 手动清除指定角色的数据(调试用) * @param roleId 角色ID,不传则清除当前角色 */ public clearRoleDataDebug(roleId?: number): void { if (!this.chatController) { logger.warn("ChatController not initialized"); return; } const targetRoleId = roleId || this.id; if (!targetRoleId) { logger.warn("No role ID specified"); return; } this.chatController.clearRoleData(targetRoleId); logger.log(`Cleared data for role ${targetRoleId}`); } }