Chat相关表现&逻辑分离,新增ChatController
新增情绪机器人逻辑, 新增情绪切换视频逻辑
This commit is contained in:
Binary file not shown.
@@ -145,18 +145,60 @@ export default class LanguageUtils {
|
||||
return text;
|
||||
}
|
||||
|
||||
public static getTextWithParams(
|
||||
public static getTextByLanguage(
|
||||
key: string,
|
||||
params: { [key: string]: any },
|
||||
language: LanguageType = LanguageType.EN,
|
||||
defaultText: string = ""
|
||||
): string {
|
||||
let text = this.getText(key, defaultText);
|
||||
|
||||
for (let paramKey in params) {
|
||||
const regex = new RegExp(`{${paramKey}}`, "g");
|
||||
text = text.replace(regex, params[paramKey].toString());
|
||||
if (!this.isInitialized) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
return defaultText;
|
||||
}
|
||||
|
||||
if (this.languageCache.has(key)) {
|
||||
return this.languageCache.get(key);
|
||||
}
|
||||
|
||||
if (!ConfigManager.tables || !ConfigManager.tables.TbLanguage) {
|
||||
console.warn("Language table not loaded yet");
|
||||
return defaultText || key;
|
||||
}
|
||||
|
||||
const languageData = ConfigManager.tables.TbLanguage.get(key);
|
||||
if (!languageData) {
|
||||
console.warn(`Language key not found: ${key}`);
|
||||
return defaultText || key;
|
||||
}
|
||||
|
||||
let text: string = "";
|
||||
switch (language) {
|
||||
case LanguageType.EN:
|
||||
text = languageData.languageEn;
|
||||
break;
|
||||
case LanguageType.CN:
|
||||
text = languageData.languageCn;
|
||||
break;
|
||||
case LanguageType.HI:
|
||||
text = languageData.languageHi;
|
||||
break;
|
||||
case LanguageType.FR:
|
||||
text = languageData.languageFr;
|
||||
break;
|
||||
case LanguageType.DE:
|
||||
text = languageData.languageDe;
|
||||
break;
|
||||
default:
|
||||
text = languageData.languageEn;
|
||||
}
|
||||
|
||||
if (!text || text.length === 0) {
|
||||
text = languageData.languageEn || defaultText || key;
|
||||
}
|
||||
|
||||
this.languageCache.set(key, text);
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum InnerMsgCode {
|
||||
BgVideo_End, //背景视频播放完毕
|
||||
|
||||
Chat_DialogRefresh,
|
||||
Chat_EmotionInitialized,
|
||||
SimplePlayVide,
|
||||
LanguageChange,
|
||||
}
|
||||
|
||||
@@ -60,6 +60,20 @@ export class ApiConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取情绪AI配置
|
||||
*/
|
||||
public getEmotionAIConfig(): AIConfig {
|
||||
const config = ConfigManager.tables.TbGlobalConfig;
|
||||
return {
|
||||
apiKey: config.emotionApiKey,
|
||||
model: config.Model,
|
||||
temperature: config.Temperature,
|
||||
maxTokens: config.MaxTokens,
|
||||
timeout: config.Timeout,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新生成参数
|
||||
* @param temperature 温度参数
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RoleConfigLoader } from "./RoleConfigLoader";
|
||||
import { ChatHistoryManager } from "../manager/ChatHistoryManager";
|
||||
import { ApiConfig } from "./ApiConfigLoader";
|
||||
import { ErrorHandler, ErrorType } from "../utils/ErrorHandler";
|
||||
import { EmotionAIService } from "./EmotionAIService";
|
||||
|
||||
/**
|
||||
* AI聊天服务类
|
||||
@@ -123,6 +124,8 @@ export class ChatAIService {
|
||||
this.currentRoleId = roleId;
|
||||
// 预创建聊天实例
|
||||
this.createOrGetChat(roleId);
|
||||
// 预创建情绪聊天实例
|
||||
EmotionAIService.Instance.ensureEmotionChatExists(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,6 +189,14 @@ export class ChatAIService {
|
||||
role: "model",
|
||||
parts: [{ text: response.text }],
|
||||
});
|
||||
|
||||
// 更新情绪状态
|
||||
try {
|
||||
await EmotionAIService.Instance.updateEmotionFromChat(roleId, message, response.text);
|
||||
} catch (emotionError) {
|
||||
console.warn(`Failed to update emotion for role ${roleId}:`, emotionError);
|
||||
// 情绪更新失败不影响聊天功能
|
||||
}
|
||||
} catch (storageError) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
storageError as Error,
|
||||
@@ -224,6 +235,8 @@ export class ChatAIService {
|
||||
if (this.chatInstances.has(roleId)) {
|
||||
this.chatInstances.delete(roleId);
|
||||
}
|
||||
// 清除情绪聊天历史
|
||||
EmotionAIService.Instance.clearEmotionHistory(roleId);
|
||||
// 清除本地存储的历史
|
||||
ChatHistoryManager.Instance.clearHistory(roleId);
|
||||
console.log(`Cleared chat history for role ${roleId}`);
|
||||
@@ -234,6 +247,8 @@ export class ChatAIService {
|
||||
*/
|
||||
public clearAllChatHistory(): void {
|
||||
this.chatInstances.clear();
|
||||
// 清除所有情绪聊天历史
|
||||
EmotionAIService.Instance.clearAllEmotionHistory();
|
||||
// 清除本地存储的所有历史
|
||||
ChatHistoryManager.Instance.clearAllHistory();
|
||||
console.log("Cleared all chat histories");
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
import { ChatAIService } from "./ChatAIService";
|
||||
import { EmotionAIService } from "./EmotionAIService";
|
||||
import { DialogManager } from "../manager/DialogManager";
|
||||
import { VideoEmotion } from "../../schema/schema";
|
||||
import ConfigManager from "../manager/ConfigManager";
|
||||
import { ErrorHandler, ErrorType } from "../utils/ErrorHandler";
|
||||
|
||||
/**
|
||||
* 聊天控制器接口 - 定义Panel和Controller之间的通信协议
|
||||
*/
|
||||
export interface IChatPanelCallback {
|
||||
/**
|
||||
* 消息发送开始回调
|
||||
* @param message 用户发送的消息
|
||||
*/
|
||||
onMessageSent(message: string): void;
|
||||
|
||||
/**
|
||||
* 接收到AI回复回调
|
||||
* @param response AI的回复内容
|
||||
*/
|
||||
onMessageReceived(response: string): void;
|
||||
|
||||
/**
|
||||
* 情绪状态更新回调
|
||||
* @param emotion 更新后的情绪状态
|
||||
*/
|
||||
onEmotionUpdated(emotion: VideoEmotion): void;
|
||||
|
||||
/**
|
||||
* 对话更新回调
|
||||
*/
|
||||
onDialogUpdated(): void;
|
||||
|
||||
/**
|
||||
* 错误处理回调
|
||||
* @param error 错误信息
|
||||
*/
|
||||
onError(error: Error): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天控制器类
|
||||
*
|
||||
* 负责处理聊天相关的所有业务逻辑,包括:
|
||||
* - 角色数据管理
|
||||
* - 消息发送和接收
|
||||
* - 情绪状态管理
|
||||
* - 与AI服务的交互
|
||||
* - 对话历史管理
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const controller = new ChatController();
|
||||
* controller.initialize(10001, panelCallback);
|
||||
* const response = await controller.sendMessage("Hello");
|
||||
* ```
|
||||
*/
|
||||
export class ChatController {
|
||||
private roleId: number | null = null;
|
||||
private callback: IChatPanelCallback | null = null;
|
||||
private dialogManager: DialogManager | null = null;
|
||||
|
||||
/**
|
||||
* 初始化聊天控制器
|
||||
* @param roleId 角色ID
|
||||
* @param callback 回调接口实现
|
||||
*/
|
||||
public initialize(roleId: number, callback: IChatPanelCallback): void {
|
||||
this.roleId = roleId;
|
||||
this.callback = callback;
|
||||
this.dialogManager = DialogManager.getInstance();
|
||||
|
||||
// 设置当前聊天的角色ID
|
||||
if (roleId && roleId > 0) {
|
||||
ChatAIService.Instance.setCurrentRole(roleId);
|
||||
console.log(`ChatController initialized with role ${roleId}`);
|
||||
} else {
|
||||
const error = new Error(`Invalid roleId: ${roleId}`);
|
||||
this.handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息给AI并处理回复
|
||||
* @param message 用户消息内容
|
||||
* @returns Promise<string | null> AI的回复,失败时返回null
|
||||
*/
|
||||
public async sendMessage(message: string): Promise<string | null> {
|
||||
if (!this.validateSendMessage(message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 通知界面消息发送开始
|
||||
this.callback?.onMessageSent(message);
|
||||
|
||||
// 更新对话显示 - 用户消息
|
||||
this.dialogManager?.updateDialog(true, message, true);
|
||||
this.callback?.onDialogUpdated();
|
||||
|
||||
console.log(`Sending message to role ${this.roleId}: ${message}`);
|
||||
|
||||
// 发送消息给AI服务
|
||||
const response = await ChatAIService.Instance.sendMessage(this.roleId!, message);
|
||||
|
||||
if (response) {
|
||||
// 更新对话显示 - AI回复
|
||||
this.dialogManager?.updateDialog(false, response);
|
||||
this.callback?.onDialogUpdated();
|
||||
|
||||
// 通知界面收到回复
|
||||
this.callback?.onMessageReceived(response);
|
||||
|
||||
// 获取更新后的情绪状态
|
||||
try {
|
||||
const currentEmotion = EmotionAIService.Instance.getCurrentEmotion(this.roleId!);
|
||||
console.log(`Current emotion for role ${this.roleId}: ${VideoEmotion[currentEmotion]}`);
|
||||
|
||||
// 通知界面情绪更新
|
||||
this.callback?.onEmotionUpdated(currentEmotion);
|
||||
} catch (emotionError) {
|
||||
console.warn("Failed to get current emotion:", emotionError);
|
||||
// 情绪获取失败不影响聊天功能
|
||||
}
|
||||
|
||||
console.log(`Response received from role ${this.roleId}: ${response}`);
|
||||
return response;
|
||||
} else {
|
||||
const error = new Error("AI返回了空响应");
|
||||
this.handleError(error);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleApiError(error, "ChatController.sendMessage", {
|
||||
roleId: this.roleId,
|
||||
message: message.substring(0, 100) + "..."
|
||||
});
|
||||
this.handleError(error as Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前角色的情绪状态
|
||||
* @returns VideoEmotion 当前情绪状态
|
||||
*/
|
||||
public getCurrentEmotion(): VideoEmotion {
|
||||
if (!this.roleId) {
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
return EmotionAIService.Instance.getCurrentEmotion(this.roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前角色数据
|
||||
* @returns 角色数据对象,失败时返回null
|
||||
*/
|
||||
public getRoleData(): any {
|
||||
if (!this.roleId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const roleData = ConfigManager.tables.TbGirls.get(this.roleId);
|
||||
const roleDetail = ConfigManager.tables.TbGirlsDetail.get(this.roleId);
|
||||
|
||||
return {
|
||||
basic: roleData,
|
||||
detail: roleDetail
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to get role data for ${this.roleId}:`, error);
|
||||
this.handleError(error as Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前角色的聊天历史
|
||||
*/
|
||||
public clearChatHistory(): void {
|
||||
if (!this.roleId) {
|
||||
console.warn("Cannot clear history: roleId is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ChatAIService.Instance.clearChatHistory(this.roleId);
|
||||
console.log(`Chat history cleared for role ${this.roleId}`);
|
||||
} catch (error) {
|
||||
console.error("Failed to clear chat history:", error);
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前角色ID
|
||||
* @returns 当前角色ID
|
||||
*/
|
||||
public getCurrentRoleId(): number | null {
|
||||
return this.roleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁控制器,清理资源
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.roleId = null;
|
||||
this.callback = null;
|
||||
this.dialogManager = null;
|
||||
console.log("ChatController destroyed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证发送消息的参数
|
||||
* @param message 消息内容
|
||||
* @returns 验证是否通过
|
||||
*/
|
||||
private validateSendMessage(message: string): boolean {
|
||||
if (!this.roleId || this.roleId <= 0) {
|
||||
const error = new Error("角色ID无效");
|
||||
this.handleError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!message || message.trim() === "") {
|
||||
const error = new Error("消息内容不能为空");
|
||||
this.handleError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.callback) {
|
||||
const error = new Error("回调接口未设置");
|
||||
this.handleError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一错误处理
|
||||
* @param error 错误对象
|
||||
*/
|
||||
private handleError(error: Error): void {
|
||||
console.error("ChatController error:", error);
|
||||
this.callback?.onError(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "458edd93-5ff2-4c11-a573-f8985c48cc15",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// 首先加载 polyfills 以确保兼容性
|
||||
import "../utils/polyfills";
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import { RoleConfigLoader } from "./RoleConfigLoader";
|
||||
import { ChatHistoryManager } from "../manager/ChatHistoryManager";
|
||||
import { ApiConfig } from "./ApiConfigLoader";
|
||||
import { ErrorHandler, ErrorType } from "../utils/ErrorHandler";
|
||||
import { VideoEmotion } from "../../schema/schema";
|
||||
import ConfigManager from "../manager/ConfigManager";
|
||||
import LanguageUtils, { LanguageType } from "../../Main/Common/LanguageUtils";
|
||||
import Utils from "../../Main/Common/Utils";
|
||||
import { InnerMsgCode } from "../../Main/Config/InnerMsgCode";
|
||||
|
||||
/**
|
||||
* AI情绪分析服务类
|
||||
*
|
||||
* 基于Google Gemini API实现的情绪分析系统,支持:
|
||||
* - 基于聊天历史的情绪状态分析
|
||||
* - 多角色独立的情绪聊天实例管理
|
||||
* - 返回标准的VideoEmotion枚举值
|
||||
* - 自动初始化历史情绪分析
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const emotionService = EmotionAIService.Instance;
|
||||
* const emotion = await emotionService.analyzeEmotionalState(10001);
|
||||
* console.log(VideoEmotion[emotion]); // "calm_down"
|
||||
* ```
|
||||
*
|
||||
* @author AI Chat System
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export class EmotionAIService {
|
||||
private static _instance: EmotionAIService;
|
||||
private emotionAI: GoogleGenAI;
|
||||
private emotionChatInstances: Map<number, any> = new Map();
|
||||
private currentEmotions: Map<number, VideoEmotion> = new Map();
|
||||
|
||||
private constructor() {
|
||||
const emotionConfig = ApiConfig.Instance.getEmotionAIConfig();
|
||||
|
||||
try {
|
||||
this.emotionAI = new GoogleGenAI({ apiKey: emotionConfig.apiKey });
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ config: { ...emotionConfig, apiKey: "***" } }, // 隐藏API密钥
|
||||
true
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取EmotionAIService的单例实例
|
||||
*
|
||||
* @returns {EmotionAIService} 情绪分析服务实例
|
||||
* @static
|
||||
*/
|
||||
public static get Instance(): EmotionAIService {
|
||||
if (!this._instance) {
|
||||
this._instance = new EmotionAIService();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或获取指定角色的情绪聊天实例
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
private createOrGetEmotionChat(roleId: number): any {
|
||||
if (!this.emotionChatInstances.has(roleId)) {
|
||||
const systemInstruction = RoleConfigLoader.getEmotionInstruction(roleId);
|
||||
const config = ApiConfig.Instance.getEmotionAIConfig();
|
||||
|
||||
const chat = this.emotionAI.chats.create({
|
||||
model: config.model,
|
||||
config: {
|
||||
temperature: config.temperature,
|
||||
systemInstruction: systemInstruction,
|
||||
},
|
||||
});
|
||||
|
||||
this.emotionChatInstances.set(roleId, chat);
|
||||
console.log(`Created new emotion chat instance for role ${roleId}`);
|
||||
|
||||
// 异步分析历史情绪状态
|
||||
this.initializeEmotionAnalysis(roleId);
|
||||
}
|
||||
return this.emotionChatInstances.get(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化情绪分析(检查是否有历史记录并进行分析)
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
private async initializeEmotionAnalysis(roleId: number): Promise<void> {
|
||||
try {
|
||||
// 检查是否有聊天历史
|
||||
const messageCount = ChatHistoryManager.Instance.getMessageCount(roleId);
|
||||
|
||||
let emotionalState: VideoEmotion;
|
||||
|
||||
if (messageCount > 0) {
|
||||
console.log(
|
||||
`Analyzing emotional state for role ${roleId} based on ${messageCount} messages`
|
||||
);
|
||||
|
||||
// 分析历史情绪状态
|
||||
emotionalState = await this.analyzeEmotionalState(roleId);
|
||||
|
||||
console.log(
|
||||
`Initial emotional state for role ${roleId}: ${VideoEmotion[emotionalState]}`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`No history found for role ${roleId}, using default emotion: calm_down`
|
||||
);
|
||||
emotionalState = VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
// 发送情绪初始化完成事件
|
||||
Utils.sendInnerMsg(InnerMsgCode.Chat_EmotionInitialized, {
|
||||
roleId: roleId,
|
||||
emotion: emotionalState
|
||||
});
|
||||
|
||||
console.log(`Emotion initialization completed for role ${roleId}: ${VideoEmotion[emotionalState]}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to initialize emotion analysis for role ${roleId}:`,
|
||||
error
|
||||
);
|
||||
|
||||
// 即使发生错误,也发送默认情绪状态
|
||||
Utils.sendInnerMsg(InnerMsgCode.Chat_EmotionInitialized, {
|
||||
roleId: roleId,
|
||||
emotion: VideoEmotion.calm_down
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化聊天历史用于情绪分析
|
||||
* @param messages 聊天消息数组
|
||||
* @returns 格式化后的分析提示
|
||||
*/
|
||||
private formatHistoryForEmotionAnalysis(messages: any[]): string {
|
||||
if (messages.length === 0) {
|
||||
return "没有聊天历史,请返回默认情绪状态:calm_down";
|
||||
}
|
||||
|
||||
let conversationText =
|
||||
"请分析以下对话的情绪状态,返回对应的VideoEmotion枚举值:\n\n";
|
||||
|
||||
messages.forEach((message, index) => {
|
||||
const speaker = message.role === "user" ? "用户" : "AI";
|
||||
const text = message.parts[0]?.text || "";
|
||||
conversationText += `${speaker}: ${text}\n`;
|
||||
});
|
||||
|
||||
conversationText +=
|
||||
"\n请返回以下枚举值之一:calm_down, arousal, desire, passion, orgasm";
|
||||
|
||||
return conversationText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定角色的情绪AI发送消息并获取分析回复,返回VideoEmotion枚举值
|
||||
* @param roleId 角色ID
|
||||
* @param message 用户发送的消息内容
|
||||
* @returns Promise<VideoEmotion> 情绪AI的分析结果,如果发生错误返回默认值calm_down
|
||||
*/
|
||||
public async sendEmotionMessage(
|
||||
roleId: number,
|
||||
message: string
|
||||
): Promise<VideoEmotion> {
|
||||
// 输入验证
|
||||
if (!roleId || roleId <= 0) {
|
||||
ErrorHandler.Instance.handleValidationError(
|
||||
"roleId",
|
||||
"角色ID必须是正整数",
|
||||
roleId
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
if (!message || message.trim() === "") {
|
||||
ErrorHandler.Instance.handleValidationError(
|
||||
"message",
|
||||
"消息内容不能为空",
|
||||
message
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
try {
|
||||
const emotionChat = this.createOrGetEmotionChat(roleId);
|
||||
const response = await emotionChat.sendMessage({
|
||||
message: message.trim(),
|
||||
});
|
||||
|
||||
if (response && response.text) {
|
||||
console.log(`Emotion analysis from role ${roleId}:`, response.text);
|
||||
return this.parseEmotionResponse(response.text);
|
||||
} else {
|
||||
const warningMsg = `情绪AI返回了空响应 (角色ID: ${roleId})`;
|
||||
ErrorHandler.Instance.handleError(
|
||||
new Error(warningMsg),
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId, message, response },
|
||||
true
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleApiError(error, "sendEmotionMessage", {
|
||||
roleId,
|
||||
message: message.substring(0, 100) + "...",
|
||||
});
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析指定角色的情绪状态(基于最近10条聊天记录)
|
||||
* @param roleId 角色ID
|
||||
* @returns Promise<VideoEmotion> 情绪分析结果
|
||||
*/
|
||||
public async analyzeEmotionalState(roleId: number): Promise<VideoEmotion> {
|
||||
try {
|
||||
// 获取最近10条消息
|
||||
const recentMessages = ChatHistoryManager.Instance.getRecentMessages(
|
||||
roleId,
|
||||
10
|
||||
);
|
||||
|
||||
// 格式化历史记录用于分析
|
||||
const analysisPrompt =
|
||||
this.formatHistoryForEmotionAnalysis(recentMessages);
|
||||
|
||||
// 发送给情绪AI进行分析
|
||||
return await this.sendEmotionMessage(roleId, analysisPrompt);
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId },
|
||||
false
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析情绪AI返回的文本为VideoEmotion枚举值
|
||||
* @param responseText AI返回的文本
|
||||
* @returns VideoEmotion枚举值
|
||||
*/
|
||||
private parseEmotionResponse(responseText: string): VideoEmotion {
|
||||
const text = responseText.toLowerCase().trim();
|
||||
|
||||
if (text.includes("arousal")) {
|
||||
return VideoEmotion.arousal;
|
||||
} else if (text.includes("desire")) {
|
||||
return VideoEmotion.desire;
|
||||
} else if (text.includes("passion")) {
|
||||
return VideoEmotion.passion;
|
||||
} else if (text.includes("orgasm")) {
|
||||
return VideoEmotion.orgasm;
|
||||
} else {
|
||||
return VideoEmotion.calm_down; // 默认值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保指定角色的情绪聊天实例已创建
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
public ensureEmotionChatExists(roleId: number): void {
|
||||
this.createOrGetEmotionChat(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定角色的情绪聊天历史
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
public clearEmotionHistory(roleId: number): void {
|
||||
if (this.emotionChatInstances.has(roleId)) {
|
||||
this.emotionChatInstances.delete(roleId);
|
||||
}
|
||||
console.log(`Cleared emotion chat history for role ${roleId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有情绪聊天历史
|
||||
*/
|
||||
public clearAllEmotionHistory(): void {
|
||||
this.emotionChatInstances.clear();
|
||||
console.log("Cleared all emotion chat histories");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活跃的情绪聊天实例数量
|
||||
*/
|
||||
public getActiveEmotionChatCount(): number {
|
||||
return this.emotionChatInstances.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定角色的当前情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @returns VideoEmotion 当前情绪状态
|
||||
*/
|
||||
public getCurrentEmotion(roleId: number): VideoEmotion {
|
||||
return this.currentEmotions.get(roleId) || VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定角色的情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @param emotion 情绪状态
|
||||
*/
|
||||
private setCurrentEmotion(roleId: number, emotion: VideoEmotion): void {
|
||||
this.currentEmotions.set(roleId, emotion);
|
||||
console.log(`Updated emotion for role ${roleId}: ${VideoEmotion[emotion]}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的英文名
|
||||
* @param roleId 角色ID
|
||||
* @returns 角色英文名,如果未找到返回"AI"
|
||||
*/
|
||||
private getRoleEnglishName(roleId: number): string {
|
||||
try {
|
||||
if (ConfigManager.tables && ConfigManager.tables.TbGirls) {
|
||||
const girlData = ConfigManager.tables.TbGirls.get(roleId);
|
||||
if (girlData && girlData.nameKey) {
|
||||
return (
|
||||
LanguageUtils.getTextByLanguage(
|
||||
girlData.nameKey,
|
||||
LanguageType.EN
|
||||
) || "AI"
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to get role name for ${roleId}:`, error);
|
||||
}
|
||||
return "AI";
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据聊天对话更新情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @param userMessage 用户消息
|
||||
* @param aiResponse AI回复
|
||||
* @returns Promise<VideoEmotion> 更新后的情绪状态
|
||||
*/
|
||||
public async updateEmotionFromChat(
|
||||
roleId: number,
|
||||
userMessage: string,
|
||||
aiResponse: string
|
||||
): Promise<VideoEmotion> {
|
||||
try {
|
||||
// 构建情绪分析提示,包含当前情绪上下文
|
||||
const currentEmotion = this.getCurrentEmotion(roleId);
|
||||
const analysisPrompt = this.formatChatForEmotionAnalysis(
|
||||
roleId,
|
||||
userMessage,
|
||||
aiResponse,
|
||||
currentEmotion
|
||||
);
|
||||
|
||||
// 发送给情绪AI进行分析
|
||||
const newEmotion = await this.sendEmotionMessage(roleId, analysisPrompt);
|
||||
|
||||
// 更新并保存情绪状态
|
||||
this.setCurrentEmotion(roleId, newEmotion);
|
||||
|
||||
return newEmotion;
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId, userMessage: userMessage.substring(0, 50) },
|
||||
false
|
||||
);
|
||||
return this.getCurrentEmotion(roleId); // 返回当前情绪状态
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化聊天对话用于情绪分析
|
||||
* @param roleId 角色ID
|
||||
* @param userMessage 用户消息
|
||||
* @param aiResponse AI回复
|
||||
* @param currentEmotion 当前情绪状态
|
||||
* @returns 格式化后的分析提示
|
||||
*/
|
||||
private formatChatForEmotionAnalysis(
|
||||
roleId: number,
|
||||
userMessage: string,
|
||||
aiResponse: string,
|
||||
currentEmotion: VideoEmotion
|
||||
): string {
|
||||
const roleName = this.getRoleEnglishName(roleId);
|
||||
|
||||
let analysisText = `当前情绪状态: ${VideoEmotion[currentEmotion]}\n\n`;
|
||||
analysisText += `请分析以下最新对话的情绪变化,返回对应的VideoEmotion枚举值:\n\n`;
|
||||
analysisText += `用户: ${userMessage}\n`;
|
||||
analysisText += `${roleName}: ${aiResponse}\n\n`;
|
||||
analysisText += `请基于对话内容和当前情绪状态,返回以下枚举值之一:calm_down, arousal, desire, passion, orgasm`;
|
||||
|
||||
return analysisText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "624f6a81-539f-4347-aef1-d7c98c226a68",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -13,10 +13,26 @@ export class RoleConfigLoader {
|
||||
*/
|
||||
public static getRoleInstruction(roleId: number): string {
|
||||
const AiCharacter = ConfigManager.tables.TbAiCharacters.get(roleId);
|
||||
const globalPrompt = ConfigManager.tables.TbGlobalConfig.girlBasePrompt;
|
||||
const prompt =
|
||||
globalPrompt +
|
||||
"\n" +
|
||||
AiCharacter.basePrompt +
|
||||
"\n" +
|
||||
AiCharacter.additionPrompt;
|
||||
return prompt;
|
||||
}
|
||||
|
||||
return AiCharacter
|
||||
? AiCharacter.systemInstruction
|
||||
: ConfigManager.tables.TbAiCharacters.get(10001).systemInstruction;
|
||||
/**
|
||||
* 根据角色ID获取对应的情绪机器人prompt
|
||||
* @param roleId 角色ID
|
||||
* @returns System Instruction字符串,如果未找到则返回默认Role_1
|
||||
*/
|
||||
public static getEmotionInstruction(roleId: number): string {
|
||||
const AiCharacter = ConfigManager.tables.TbAiCharacters.get(roleId);
|
||||
const globalPrompt = ConfigManager.tables.TbGlobalConfig.EmotionRating;
|
||||
const prompt = globalPrompt + "\n" + AiCharacter.basePrompt;
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,11 +6,12 @@ import {
|
||||
Label,
|
||||
Sprite,
|
||||
UITransform,
|
||||
VideoPlayer,
|
||||
} from "cc";
|
||||
// 首先加载 polyfills 以确保兼容性
|
||||
import "../../utils/polyfills";
|
||||
import { DialogManager } from "../../manager/DialogManager";
|
||||
import { ChatAIService } from "../../core/ChatAIService";
|
||||
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";
|
||||
@@ -20,15 +21,15 @@ import GameRootUI from "db://assets/Scripts/Main/Common/GameRootUI";
|
||||
import { ImagePopup } from "../components/ImagePopup";
|
||||
|
||||
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
||||
import { VideoRoleType } from "db://assets/Scripts/Main/Common/GlobalValue";
|
||||
import ConfigManager from "../../manager/ConfigManager";
|
||||
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 {
|
||||
export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
manager: DialogManager = null;
|
||||
private chatController: ChatController = new ChatController();
|
||||
|
||||
@property(EditBox)
|
||||
editBox: EditBox = null;
|
||||
@@ -39,6 +40,9 @@ export class ChatPanel extends li_BaseView {
|
||||
@property(Sprite)
|
||||
girlImg: Sprite = null;
|
||||
|
||||
@property(VideoPlayer)
|
||||
girlVideo: VideoPlayer = null;
|
||||
|
||||
@property(ChatContentsLayout)
|
||||
layout: ChatContentsLayout = null;
|
||||
|
||||
@@ -49,6 +53,8 @@ export class ChatPanel extends li_BaseView {
|
||||
private _nodeTab: any = {};
|
||||
|
||||
nameKey: string;
|
||||
private commercialVideos: purchase.CommercialVideo[] = [];
|
||||
private currentEmotion: VideoEmotion | null = null;
|
||||
|
||||
private onLanguageChangeCallback = () => {
|
||||
if (!this.girlName) return;
|
||||
@@ -57,9 +63,9 @@ export class ChatPanel extends li_BaseView {
|
||||
|
||||
openUIDataCT(data) {
|
||||
// 处理新的数据格式,支持过渡动画参数
|
||||
if (typeof data === 'object' && data.roleId !== undefined) {
|
||||
if (typeof data === "object" && data.roleId !== undefined) {
|
||||
this.id = data.roleId;
|
||||
|
||||
|
||||
// 如果标记了需要滑入动画,则执行动画
|
||||
if (data.withSlideTransition && this.node && this.node.isValid) {
|
||||
// 延迟一帧执行动画,确保节点已正确加载到场景中
|
||||
@@ -71,10 +77,10 @@ export class ChatPanel extends li_BaseView {
|
||||
// 兼容原来的数字格式
|
||||
this.id = data;
|
||||
}
|
||||
|
||||
// 设置当前聊天的角色ID
|
||||
|
||||
// 初始化ChatController
|
||||
if (this.id && this.id > 0) {
|
||||
ChatAIService.Instance.setCurrentRole(this.id);
|
||||
this.chatController.initialize(this.id, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +89,17 @@ export class ChatPanel extends li_BaseView {
|
||||
|
||||
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(
|
||||
@@ -96,6 +113,12 @@ export class ChatPanel extends li_BaseView {
|
||||
this,
|
||||
this.onLanguageChangeCallback
|
||||
);
|
||||
|
||||
Utils.addInnerEL(
|
||||
InnerMsgCode.Chat_EmotionInitialized,
|
||||
this,
|
||||
this.onEmotionInitialized
|
||||
);
|
||||
}
|
||||
onDestroy(): void {
|
||||
Utils.removeInnerEL(
|
||||
@@ -108,12 +131,37 @@ export class ChatPanel extends li_BaseView {
|
||||
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;
|
||||
const data = ConfigManager.tables.TbGirls.get(this.id);
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
if (!data) return;
|
||||
|
||||
// 通过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(
|
||||
@@ -129,15 +177,172 @@ export class ChatPanel extends li_BaseView {
|
||||
);
|
||||
}
|
||||
);
|
||||
let uiTransform: UITransform =
|
||||
this._nodeTab.BgFrame.getComponent(UITransform);
|
||||
|
||||
Utils.sendInnerMsg(InnerMsgCode.SimplePlayVide, {
|
||||
path: dataDetail.pics[0],
|
||||
size: uiTransform.contentSize,
|
||||
bgFrameNode: this._nodeTab.BgFrame,
|
||||
});
|
||||
// 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());
|
||||
@@ -152,24 +357,90 @@ export class ChatPanel extends li_BaseView {
|
||||
public async OnClickSend() {
|
||||
const str = this.editBox.string;
|
||||
if (!str || str == "") return;
|
||||
console.log("post:" + str);
|
||||
|
||||
// 清空输入框
|
||||
this.editBox.string = "";
|
||||
|
||||
DialogManager.getInstance().updateDialog(true, str, true);
|
||||
|
||||
// 使用新的sendMessage方法,传入角色ID
|
||||
const ret = await ChatAIService.Instance.sendMessage(this.id, str);
|
||||
|
||||
if (this.manager) {
|
||||
this.manager.updateDialog(false, ret);
|
||||
}
|
||||
console.log("ret:" + ret);
|
||||
// 通过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();
|
||||
|
||||
@@ -16,7 +16,7 @@ import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import Utils from "../../../Main/Common/Utils";
|
||||
import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode";
|
||||
import { SimpleToggle } from "../components/SimpleToggle";
|
||||
import { GirlDetail } from "../../../schema/schema";
|
||||
import { GirlDetail, purchase } from "../../../schema/schema";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -151,31 +151,37 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
}
|
||||
this.tags.string = desc;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this.starParent.children[i].active = i < data.starCount;
|
||||
this.starParent.children[i].active = i < 5;
|
||||
}
|
||||
this.descAge.string = data.age.toString();
|
||||
this.descAge.string =
|
||||
LanguageUtils.getText("girldetailpanel.age") + data.age.toString();
|
||||
this.desc.string = dataDetail.detailDesc;
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.avatarVideo,
|
||||
dataDetail.vids[0],
|
||||
dataDetail.commercialVideos[0].path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// 也立即尝试调整(以防已经加载完成)
|
||||
this.adjustVideoScale();
|
||||
this.vids = dataDetail.vids.slice(0);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
this.vids = dataDetail.commercialVideos.slice(0);
|
||||
const paths = dataDetail.commercialImages.map((a) => a.path);
|
||||
this.refreshImgs(paths, true);
|
||||
}
|
||||
|
||||
vids: string[];
|
||||
vids: purchase.CommercialVideo[];
|
||||
bindImgToggle() {
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
const paths = dataDetail.commercialImages.map((a) => a.path);
|
||||
|
||||
this.refreshImgs(paths, true);
|
||||
}
|
||||
|
||||
bindVideoToggle() {
|
||||
this.refreshImgs(this.vids, false);
|
||||
const paths = this.vids.map((a) => a.path);
|
||||
|
||||
this.refreshImgs(paths, false);
|
||||
}
|
||||
|
||||
refreshImgs(paths: string[], isImg: boolean) {
|
||||
|
||||
+120
-16
@@ -60,6 +60,16 @@ export enum PriceType {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export enum VideoEmotion {
|
||||
calm_down = 0,
|
||||
arousal = 1,
|
||||
desire = 2,
|
||||
passion = 3,
|
||||
orgasm = 4,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +79,8 @@ export class AiCharacter {
|
||||
|
||||
constructor(_buf_: ByteBuf) {
|
||||
this.id = _buf_.readInt()
|
||||
this.systemInstruction = _buf_.readString()
|
||||
this.basePrompt = _buf_.readString()
|
||||
this.additionPrompt = _buf_.readString()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,13 +88,18 @@ export class AiCharacter {
|
||||
*/
|
||||
readonly id: number
|
||||
/**
|
||||
* 系统指令
|
||||
* 个人信息基础prompt(聊天&评分都需要)
|
||||
*/
|
||||
readonly systemInstruction: string
|
||||
readonly basePrompt: string
|
||||
/**
|
||||
* 额外信息prompt(聊天&评分都需要)
|
||||
*/
|
||||
readonly additionPrompt: string
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +117,6 @@ export class Girl {
|
||||
this.tagKey = _buf_.readString()
|
||||
this.priceType = _buf_.readInt()
|
||||
this.avatarPath = _buf_.readString()
|
||||
this.starCount = _buf_.readInt()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,10 +147,6 @@ export class Girl {
|
||||
* 头像路径
|
||||
*/
|
||||
readonly avatarPath: string
|
||||
/**
|
||||
* 评价星数
|
||||
*/
|
||||
readonly starCount: number
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
@@ -145,7 +156,6 @@ export class Girl {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +168,8 @@ export class GirlDetail {
|
||||
constructor(_buf_: ByteBuf) {
|
||||
this.id = _buf_.readInt()
|
||||
this.detailDesc = _buf_.readString()
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.pics = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = _buf_.readString(); this.pics.push(_e0);}}
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.vids = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = _buf_.readString(); this.vids.push(_e0);}}
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.commercialImages = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = new purchase.CommercialImage(_buf_); this.commercialImages.push(_e0);}}
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.commercialVideos = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = new purchase.CommercialVideo(_buf_); this.commercialVideos.push(_e0);}}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,19 +181,19 @@ export class GirlDetail {
|
||||
*/
|
||||
readonly detailDesc: string
|
||||
/**
|
||||
* 图片资源列表
|
||||
* 类型
|
||||
*/
|
||||
readonly pics: string[]
|
||||
readonly commercialImages: purchase.CommercialImage[]
|
||||
/**
|
||||
* 视频资源列表
|
||||
*/
|
||||
readonly vids: string[]
|
||||
readonly commercialVideos: purchase.CommercialVideo[]
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
|
||||
|
||||
|
||||
for (let _e of this.commercialImages) { _e?.resolve(tables); }
|
||||
for (let _e of this.commercialVideos) { _e?.resolve(tables); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,18 +205,25 @@ export class GlobalConfig {
|
||||
|
||||
constructor(_buf_: ByteBuf) {
|
||||
this.ApiKey = _buf_.readString()
|
||||
this.emotionApiKey = _buf_.readString()
|
||||
this.Model = _buf_.readString()
|
||||
this.Temperature = _buf_.readFloat()
|
||||
this.MaxTokens = _buf_.readInt()
|
||||
this.Timeout = _buf_.readInt()
|
||||
this.FreeChatTimes = _buf_.readInt()
|
||||
this.GameName = _buf_.readString()
|
||||
this.EmotionRating = _buf_.readString()
|
||||
this.girlBasePrompt = _buf_.readString()
|
||||
}
|
||||
|
||||
/**
|
||||
* api键
|
||||
*/
|
||||
readonly ApiKey: string
|
||||
/**
|
||||
* 情绪判断机器人api
|
||||
*/
|
||||
readonly emotionApiKey: string
|
||||
/**
|
||||
* ai模型
|
||||
*/
|
||||
@@ -228,6 +245,14 @@ export class GlobalConfig {
|
||||
* 游戏名
|
||||
*/
|
||||
readonly GameName: string
|
||||
/**
|
||||
* 情绪评分机器人system_prompt,需拼接角色prompt
|
||||
*/
|
||||
readonly EmotionRating: string
|
||||
/**
|
||||
* ai机器人基础规则
|
||||
*/
|
||||
readonly girlBasePrompt: string
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
@@ -237,6 +262,9 @@ export class GlobalConfig {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +315,70 @@ export class Language {
|
||||
|
||||
|
||||
|
||||
export namespace purchase {
|
||||
export class CommercialImage {
|
||||
|
||||
constructor(_buf_: ByteBuf) {
|
||||
this.imageType = _buf_.readInt()
|
||||
this.imagePrice = _buf_.readFloat()
|
||||
this.path = _buf_.readString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 1=详情页展示<br/>2=聊天触发
|
||||
*/
|
||||
readonly imageType: number
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
readonly imagePrice: number
|
||||
/**
|
||||
* 资源路径
|
||||
*/
|
||||
readonly path: string
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export namespace purchase {
|
||||
export class CommercialVideo {
|
||||
|
||||
constructor(_buf_: ByteBuf) {
|
||||
this.path = _buf_.readString()
|
||||
this.emotion = _buf_.readInt()
|
||||
this.videoPrice = _buf_.readFloat()
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源路径
|
||||
*/
|
||||
readonly path: string
|
||||
/**
|
||||
* 关联情绪
|
||||
*/
|
||||
readonly emotion: VideoEmotion
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
readonly videoPrice: number
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class PurchaseConfig {
|
||||
|
||||
@@ -625,6 +717,10 @@ export class TbGlobalConfig {
|
||||
* api键
|
||||
*/
|
||||
get ApiKey(): string { return this._data.ApiKey; }
|
||||
/**
|
||||
* 情绪判断机器人api
|
||||
*/
|
||||
get emotionApiKey(): string { return this._data.emotionApiKey; }
|
||||
/**
|
||||
* ai模型
|
||||
*/
|
||||
@@ -646,6 +742,14 @@ export class TbGlobalConfig {
|
||||
* 游戏名
|
||||
*/
|
||||
get GameName(): string { return this._data.GameName; }
|
||||
/**
|
||||
* 情绪评分机器人system_prompt,需拼接角色prompt
|
||||
*/
|
||||
get EmotionRating(): string { return this._data.EmotionRating; }
|
||||
/**
|
||||
* ai机器人基础规则
|
||||
*/
|
||||
get girlBasePrompt(): string { return this._data.girlBasePrompt; }
|
||||
|
||||
resolve(tables:Tables) {
|
||||
this._data.resolve(tables)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -163,7 +163,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 1094.75,
|
||||
"y": 1087.5,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -218,8 +218,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -520,
|
||||
"y": -28.75,
|
||||
"x": -510,
|
||||
"y": -83.42099999999982,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -259,8 +259,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 36,
|
||||
"height": 64
|
||||
"width": 89,
|
||||
"height": 90
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -404,9 +404,9 @@
|
||||
},
|
||||
"_alignFlags": 9,
|
||||
"_target": null,
|
||||
"_left": 20,
|
||||
"_left": 30,
|
||||
"_right": 0,
|
||||
"_top": 40,
|
||||
"_top": 75.92099999999982,
|
||||
"_bottom": 30,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -419,7 +419,7 @@
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 64,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 12,
|
||||
"_lockFlags": 4,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -469,7 +469,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -0.9460000000000051,
|
||||
"y": -56.11999999999989,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -607,7 +607,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 20,
|
||||
"_top": 82.4239999999999,
|
||||
"_bottom": 17.180999999999948,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -676,7 +676,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 150.5
|
||||
"height": 165
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -817,7 +817,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -69.10000000000002,
|
||||
"y": -87.5,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -4942,7 +4942,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 2201.8
|
||||
"height": 2165
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5087,7 +5087,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 2201.8
|
||||
"height": 2165
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5240,7 +5240,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 2201.8
|
||||
"height": 2165
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5269,7 +5269,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 138.2,
|
||||
"_top": 175,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 1059,
|
||||
"y": 1060,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -231,7 +231,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -501.7,
|
||||
"y": -52.75199999999995,
|
||||
"y": -53.75199999999995,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -467,7 +467,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 54.803999999999995,
|
||||
"y": -22.478999999999814,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -508,7 +508,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 410.224609375,
|
||||
"height": 112.39200000000001
|
||||
"height": 93.87
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -549,7 +549,7 @@
|
||||
"_actualFontSize": 70,
|
||||
"_fontSize": 70,
|
||||
"_fontFamily": "NotoSans-Regular",
|
||||
"_lineHeight": 89.2,
|
||||
"_lineHeight": 74.5,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -605,7 +605,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_top": 85.54399999999981,
|
||||
"_bottom": 108.25899999999993,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -926,7 +926,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 207.47199999999998,
|
||||
"y": -57.613000000000056,
|
||||
"y": -105.53999999999996,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1240,7 +1240,7 @@
|
||||
"_left": 194.1,
|
||||
"_right": 282.528,
|
||||
"_top": 0,
|
||||
"_bottom": 53.386999999999944,
|
||||
"_bottom": 4.460000000000036,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -1306,7 +1306,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 365.847,
|
||||
"y": -58.52800000000002,
|
||||
"y": -106.45499999999993,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1818,7 +1818,7 @@
|
||||
"_left": 194.1,
|
||||
"_right": 124.15300000000002,
|
||||
"_top": 0,
|
||||
"_bottom": 52.47199999999998,
|
||||
"_bottom": 3.5450000000000728,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -1871,7 +1871,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 29.928999999999974,
|
||||
"y": -54.12300000000005,
|
||||
"y": -103.05000000000018,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1968,8 +1968,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -195.44499999999994,
|
||||
"y": -57.613000000000056,
|
||||
"x": -400.672,
|
||||
"y": -105.53999999999996,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2281,9 +2281,9 @@
|
||||
"_alignFlags": 36,
|
||||
"_target": null,
|
||||
"_left": 194.1,
|
||||
"_right": 685.4449999999999,
|
||||
"_right": 890.672,
|
||||
"_top": 0,
|
||||
"_bottom": 53.386999999999944,
|
||||
"_bottom": 4.460000000000036,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -2330,7 +2330,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 222
|
||||
"height": 220
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -2433,8 +2433,6 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "43dBOZjJBF/btIjHVb4m2d",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -2474,7 +2472,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -86.60000000000002,
|
||||
"y": -110,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2534,7 +2532,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 902.3710000000001,
|
||||
"y": 878.971,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -3811,6 +3809,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "40sHQFKzNMq51+Dtkb1mcc",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -5590,7 +5590,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1060,
|
||||
"height": 1750.8195000000003
|
||||
"height": 1704.0195
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5735,7 +5735,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1060,
|
||||
"height": 1750.8195000000003
|
||||
"height": 1704.0195
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5888,7 +5888,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1080,
|
||||
"height": 2166.8
|
||||
"height": 2120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -5962,7 +5962,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 173.2,
|
||||
"_top": 220,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,4 +6,4 @@ Maman sexyHeiße Mama
category_1004Binding捆绑बंधनBondageFesseln
|
||||
Sana Reddy�娜साना रेड�डी
|
||||
Sana Reddy
|
||||
Sana Reddygirl_10004_nameScarlettScarlettसà¥�कारलेटScarlettScarlettgirl_10001_tag Hot|Horny␍ç�«è¾£|饥渴"सेकà¥�सी|कामà¥�कChaude|Excitée
|
||||
Heiß|Geilgirl_10002_tag%Earth-tone linens,handcrafted jewelry土色亚麻制å“�|手工ç� å®�Yà¤à¥‚रे रंग के लिनन|हसà¥�तनिरà¥�मित गहनेLinge terreux,Bijoux artisanaux'Erdfarbene Leinen,Handgemachter Schmuckgirl_10003_tagSinger-Songwriter,Dancer␍舞者|æŒæ‰‹2गायक-गीतकार|नरà¥�तकीChanteuse-compositrice,Danseuse Sängerin-Songwriterin,Tänzeringirl_10004_tag!RadiantOldSoul,BlueprintsAndBeats!RadiantOldSoul,BlueprintsAndBeats!RadiantOldSoul,BlueprintsAndBeats!RadiantOldSoul,BlueprintsAndBeats!RadiantOldSoul,BlueprintsAndBeats
|
||||
Heiß|Geilgirl_10002_tag%Earth-tone linens,handcrafted jewelry土色亚麻制å“�|手工ç� å®�Yà¤à¥‚रे रंग के लिनन|हसà¥�तनिरà¥�मित गहनेLinge terreux,Bijoux artisanaux'Erdfarbene Leinen,Handgemachter Schmuckgirl_10003_tagSinger-Songwriter,Dancer␍舞者|æŒæ‰‹2गायक-गीतकार|नरà¥�तकीChanteuse-compositrice,Danseuse Sängerin-Songwriterin,Tänzeringirl_10004_tagGoldenDreamer,BlueEyesAndPearlsGoldenDreamer,BlueEyesAndPearlsGoldenDreamer,BlueEyesAndPearlsGoldenDreamer,BlueEyesAndPearlsGoldenDreamer,BlueEyesAndPearls
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 2.9 KiB |
@@ -46,10 +46,10 @@
|
||||
"offsetY": 0,
|
||||
"trimX": 0,
|
||||
"trimY": 0,
|
||||
"width": 36,
|
||||
"height": 64,
|
||||
"rawWidth": 36,
|
||||
"rawHeight": 64,
|
||||
"width": 89,
|
||||
"height": 90,
|
||||
"rawWidth": 89,
|
||||
"rawHeight": 90,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
@@ -61,17 +61,17 @@
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-18,
|
||||
-32,
|
||||
-44.5,
|
||||
-45,
|
||||
0,
|
||||
18,
|
||||
-32,
|
||||
44.5,
|
||||
-45,
|
||||
0,
|
||||
-18,
|
||||
32,
|
||||
-44.5,
|
||||
45,
|
||||
0,
|
||||
18,
|
||||
32,
|
||||
44.5,
|
||||
45,
|
||||
0
|
||||
],
|
||||
"indexes": [
|
||||
@@ -84,12 +84,12 @@
|
||||
],
|
||||
"uv": [
|
||||
0,
|
||||
64,
|
||||
36,
|
||||
64,
|
||||
90,
|
||||
89,
|
||||
90,
|
||||
0,
|
||||
0,
|
||||
36,
|
||||
89,
|
||||
0
|
||||
],
|
||||
"nuv": [
|
||||
@@ -103,13 +103,13 @@
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-18,
|
||||
-32,
|
||||
-44.5,
|
||||
-45,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
18,
|
||||
32,
|
||||
44.5,
|
||||
45,
|
||||
0
|
||||
]
|
||||
},
|
||||
|
||||
+1
-1
Submodule xchat_cfg updated: c9f321db06...d4dd6a5bdf
Reference in New Issue
Block a user