Files
18xchat/assets/Scripts/chat18x/ui/panels/ChatPanel.ts
T
xionglijia 1507fff7c5 Chat相关表现&逻辑分离,新增ChatController
新增情绪机器人逻辑,
新增情绪切换视频逻辑
2025-08-26 19:41:13 +08:00

454 lines
13 KiB
TypeScript

import {
_decorator,
Component,
EditBox,
Node,
Label,
Sprite,
UITransform,
VideoPlayer,
} 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";
const { ccclass, property } = _decorator;
@ccclass("ChatPanel")
export class ChatPanel extends li_BaseView implements IChatPanelCallback {
manager: DialogManager = null;
private chatController: ChatController = new ChatController();
@property(EditBox)
editBox: EditBox = null;
@property(Label)
girlName: Label = null;
@property(Sprite)
girlImg: Sprite = null;
@property(VideoPlayer)
girlVideo: VideoPlayer = null;
@property(ChatContentsLayout)
layout: ChatContentsLayout = null;
@property(ImagePopup)
popUpImage: ImagePopup = null;
id: number;
private _nodeTab: any = {};
nameKey: string;
private commercialVideos: purchase.CommercialVideo[] = [];
private currentEmotion: VideoEmotion | null = null;
private onLanguageChangeCallback = () => {
if (!this.girlName) return;
this.girlName.string = LanguageUtils.getText(this.nameKey);
};
openUIDataCT(data) {
// 处理新的数据格式,支持过渡动画参数
if (typeof data === "object" && data.roleId !== undefined) {
this.id = data.roleId;
// 如果标记了需要滑入动画,则执行动画
if (data.withSlideTransition && this.node && this.node.isValid) {
// 延迟一帧执行动画,确保节点已正确加载到场景中
this.scheduleOnce(() => {
UITransitionHelper.slideInFromRight(this.node, 0.3);
}, 0);
}
} else {
// 兼容原来的数字格式
this.id = data;
}
// 初始化ChatController
if (this.id && this.id > 0) {
this.chatController.initialize(this.id, this);
}
}
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
this.register();
this.refresh(this.id);
// Add video loaded event callback
if (this.girlVideo) {
this.girlVideo.node.on(
VideoPlayer.EventType.READY_TO_PLAY,
() => {
this.adjustVideoScale();
},
this
);
}
}
register() {
Utils.addInnerEL(
InnerMsgCode.Chat_DialogRefresh,
this,
this.onDialogUpdate
);
Utils.addInnerEL(
InnerMsgCode.LanguageChange,
this,
this.onLanguageChangeCallback
);
Utils.addInnerEL(
InnerMsgCode.Chat_EmotionInitialized,
this,
this.onEmotionInitialized
);
}
onDestroy(): void {
Utils.removeInnerEL(
InnerMsgCode.Chat_DialogRefresh,
this,
this.onDialogUpdate
);
Utils.removeInnerEL(
InnerMsgCode.LanguageChange,
this,
this.onLanguageChangeCallback
);
Utils.removeInnerEL(
InnerMsgCode.Chat_EmotionInitialized,
this,
this.onEmotionInitialized
);
// Cleanup video event listeners
if (this.girlVideo && this.girlVideo.node) {
this.girlVideo.node.off(
VideoPlayer.EventType.READY_TO_PLAY,
this.adjustVideoScale,
this
);
}
// 销毁ChatController
if (this.chatController) {
this.chatController.destroy();
}
}
refresh(id: number) {
this.id = id;
// 通过ChatController获取角色数据
const roleData = this.chatController.getRoleData();
if (!roleData || !roleData.basic) return;
const data = roleData.basic;
const dataDetail = roleData.detail;
this.nameKey = data.nameKey;
this.girlName.string = LanguageUtils.getText(data.nameKey);
ResManager.I.changeBundleSpriteFrame(
this.girlImg,
data.avatarPath,
"Chat18x",
() => {
let sizeTran = this.girlImg.node.parent.getComponent(UITransform);
Utils.adjustBgPixelRatioToSize(
sizeTran.contentSize,
this.girlImg.node,
2
);
}
);
// Store commercialVideos for emotion-based switching
if (dataDetail && dataDetail.commercialVideos) {
this.commercialVideos = dataDetail.commercialVideos;
console.log(`Loaded ${this.commercialVideos.length} videos for role ${this.id}`);
// Load initial video based on current emotion
if (this.commercialVideos.length > 0 && this.girlVideo) {
// Get current emotion from ChatController
let currentEmotion = VideoEmotion.calm_down; // Default fallback
try {
if (this.chatController) {
currentEmotion = this.chatController.getCurrentEmotion();
console.log(`Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}`);
}
} catch (error) {
console.warn("Failed to get current emotion during initialization, using calm_down as default:", error);
}
// Find video matching current emotion or fallback
const initialVideo = this.commercialVideos.find(video => video.emotion === currentEmotion)
|| this.commercialVideos.find(video => video.emotion === VideoEmotion.calm_down)
|| this.commercialVideos[0]; // Final fallback to first video
console.log(`Loading initial video: ${initialVideo.path} (emotion: ${VideoEmotion[initialVideo.emotion]})`);
// Set current emotion to the loaded video's emotion
this.currentEmotion = initialVideo.emotion;
ResManager.I.changeBundleVideo(
this.girlVideo,
initialVideo.path,
"Chat18x"
);
// Also immediately try to adjust scale (in case already loaded)
this.adjustVideoScale();
}
} else {
this.commercialVideos = [];
this.currentEmotion = null; // Reset current emotion when no videos available
console.log("No commercialVideos data available for role", this.id);
}
// if (dataDetail && dataDetail.commercialVideos && dataDetail.commercialVideos.length > 0) {
// let uiTransform: UITransform =
// this._nodeTab.BgFrame.getComponent(UITransform);
// Utils.sendInnerMsg(InnerMsgCode.SimplePlayVide, {
// path: dataDetail.commercialVideos[0].path,
// size: uiTransform.contentSize,
// bgFrameNode: this._nodeTab.BgFrame,
// });
// }
}
adjustVideoScale() {
if (!this.girlVideo || !this.girlVideo.node.parent) {
return;
}
const tryAdjustScale = () => {
const parentTransform =
this.girlVideo.node.parent.getComponent(UITransform);
const videoTransform = this.girlVideo.node.getComponent(UITransform);
if (parentTransform && videoTransform && videoTransform.height > 0) {
const scale = parentTransform.height / videoTransform.height;
this.girlVideo.node.setScale(scale, scale, 1);
console.log(
`ChatPanel Video scaled to: ${scale}, parent height: ${parentTransform.height}, video height: ${videoTransform.height}`
);
return true;
}
return false;
};
// Immediately try once
if (!tryAdjustScale()) {
// If failed, delay and try again
this.scheduleOnce(() => {
if (!tryAdjustScale()) {
// Try again with longer delay
this.scheduleOnce(() => {
tryAdjustScale();
}, 0.5);
}
}, 0.1);
}
}
setVideoEnable(enable: boolean) {
if (!this.girlVideo) return;
this.girlVideo.enabled = enable;
if (enable) {
this.girlVideo.play();
}
}
private switchVideoByEmotion(emotion: VideoEmotion): void {
if (!this.girlVideo || !this.commercialVideos || this.commercialVideos.length === 0) {
console.warn("Cannot switch video: missing video player or commercialVideos data");
return;
}
// Check if emotion has changed
if (this.currentEmotion === emotion) {
console.log(`Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch`);
return;
}
// Find video matching the emotion
const matchingVideo = this.commercialVideos.find(video => video.emotion === emotion);
if (matchingVideo) {
console.log(`Switching to video for emotion ${VideoEmotion[emotion]}: ${matchingVideo.path} (from ${this.currentEmotion !== null ? VideoEmotion[this.currentEmotion] : 'null'})`);
// Load the new video
ResManager.I.changeBundleVideo(
this.girlVideo,
matchingVideo.path,
"Chat18x"
);
// Update current emotion state
this.currentEmotion = emotion;
// Adjust scale and enable playback
this.adjustVideoScale();
this.setVideoEnable(true);
} else {
console.warn(`No video found for emotion ${VideoEmotion[emotion]}, falling back to first available video`);
// Fallback to first video if no match found
if (this.commercialVideos.length > 0) {
const fallbackVideo = this.commercialVideos[0];
console.log(`Loading fallback video: ${fallbackVideo.path} (emotion: ${VideoEmotion[fallbackVideo.emotion]})`);
ResManager.I.changeBundleVideo(
this.girlVideo,
fallbackVideo.path,
"Chat18x"
);
// Update current emotion state to the fallback video's emotion
this.currentEmotion = fallbackVideo.emotion;
this.adjustVideoScale();
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();
GameRootUI.I.hideDefaultView();
}
public async OnClickSend() {
const str = this.editBox.string;
if (!str || str == "") return;
// 清空输入框
this.editBox.string = "";
// 通过ChatController发送消息
await this.chatController.sendMessage(str);
//测试
//this.popUpImage.refresh("Image/1/blur_naked_1");
}
// === IChatPanelCallback 接口实现 ===
/**
* 消息发送开始回调
* @param message 用户发送的消息
*/
public onMessageSent(message: string): void {
console.log("Message sent:", message);
// 可以在这里添加发送中的UI状态显示,比如显示loading等
}
/**
* 接收到AI回复回调
* @param response AI的回复内容
*/
public onMessageReceived(response: string): void {
console.log("Message received:", response);
// 可以在这里添加接收到回复的UI效果,比如播放声音等
}
/**
* 对话更新回调
*/
public onDialogUpdated(): void {
// 更新对话显示
this.onDialogUpdate();
}
/**
* 错误处理回调
* @param error 错误信息
*/
public onError(error: Error): void {
console.error("ChatPanel error:", error);
// 可以在这里显示错误提示给用户
}
/**
* 情绪状态更新回调 (实现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() {
this.onClose();
GameRootUI.I.showDefaultView();
//ViewManager.I.openBundlesView("GirlListPanel");
}
onClickRecord() {
ViewManager.I.openBundlesView("RecordPanel", this.id);
}
}