@@ -30,7 +30,35 @@ export const 解析写真系统状态更新 = (
3030
3131 try {
3232 const parsed = JSON . parse ( match [ 1 ] ) ;
33- return parsed as {
33+
34+ // 兼容两种字段名:LLM可能输出"更新拍摄项目"(数组)或"更新项目状态"(对象/平铺对象)
35+ const raw项目更新 = ( parsed as any ) . 更新拍摄项目 || ( parsed as any ) . 更新项目状态 ;
36+ let 标准化项目更新 : Partial < 拍摄项目状态 > [ ] | undefined ;
37+ if ( raw项目更新 ) {
38+ if ( Array . isArray ( raw项目更新 ) ) {
39+ 标准化项目更新 = raw项目更新 ;
40+ } else if ( typeof raw项目更新 === 'object' ) {
41+ // 判断是平铺对象(含"项目ID"字段)还是嵌套对象({"ID":{数据}})
42+ if ( raw项目更新 . 项目ID || raw项目更新 . id ) {
43+ // 平铺对象:直接作为单个项目更新
44+ 标准化项目更新 = [ raw项目更新 ] ;
45+ } else {
46+ // 嵌套对象:{"项目ID": {数据}}
47+ 标准化项目更新 = Object . entries ( raw项目更新 ) . map ( ( [ id , data ] : [ string , any ] ) => ( {
48+ id,
49+ 项目ID : id ,
50+ ...data ,
51+ } ) ) ;
52+ }
53+ }
54+ }
55+
56+ return {
57+ 更新模特档案 : parsed . 更新模特档案 ,
58+ 更新摄影师档案 : parsed . 更新摄影师档案 ,
59+ 更新拍摄项目 : 标准化项目更新 ,
60+ 新泄露事件 : parsed . 新泄露事件 ,
61+ } as {
3462 更新模特档案 ?: Record < string , Partial < 模特核心状态 > > ;
3563 更新摄影师档案 ?: Record < string , any > ;
3664 更新拍摄项目 ?: Partial < 拍摄项目状态 > [ ] ;
@@ -48,6 +76,165 @@ export const 移除写真系统状态标签 = (rawText: string): string => {
4876 return rawText . replace ( / < 写 真 系 统 状 态 > [ \s \S ] * ?< \/ 写 真 系 统 状 态 > / g, '' ) . trim ( ) ;
4977} ;
5078
79+ /**
80+ * 处理 AI 响应中的写真系统状态更新
81+ * 1. 解析 <写真系统状态> 标签
82+ * 2. 调用回调应用状态变更
83+ * 3. 返回清理后的纯文本(不含状态标签)
84+ */
85+ export const 处理写真系统状态更新 = (
86+ rawAiText : string ,
87+ callback : ( result : NonNullable < ReturnType < typeof 解析写真系统状态更新 > > ) => void
88+ ) : string => {
89+ const 解析结果 = 解析写真系统状态更新 ( rawAiText ) ;
90+ if ( 解析结果 ) {
91+ callback ( 解析结果 ) ;
92+ }
93+ return 移除写真系统状态标签 ( rawAiText ) ;
94+ } ;
95+
96+ /**
97+ * 应用写真系统状态更新到游戏状态
98+ * 支持懒初始化:如果写真系统尚未创建但有状态更新,会自动创建初始系统
99+ */
100+ export const 应用写真系统状态更新 = (
101+ current写真系统 : any ,
102+ 更新 : NonNullable < ReturnType < typeof 解析写真系统状态更新 > >
103+ ) : any => {
104+ // 懒初始化:如果写真系统不存在但有更新,创建初始系统
105+ const 基础系统 = current写真系统 || {
106+ 模特档案 : { } ,
107+ 摄影师档案 : { } ,
108+ 进行中的拍摄项目 : [ ] ,
109+ 历史拍摄记录 : [ ] ,
110+ 泄露事件列表 : [ ] ,
111+ } ;
112+
113+ const 新系统 = { ...基础系统 } ;
114+
115+ // 应用模特档案更新(不存在的自动创建)
116+ if ( 更新 . 更新模特档案 ) {
117+ const 新模特档案 = { ...( 新系统 . 模特档案 || { } ) } ;
118+ for ( const [ id , 档案 ] of Object . entries ( 更新 . 更新模特档案 ) ) {
119+ if ( 新模特档案 [ id ] ) {
120+ 新模特档案 [ id ] = { ...新模特档案 [ id ] , ...档案 } ;
121+ } else {
122+ // 新模特:创建完整最小档案,补充 Dashboard 所需的所有字段
123+ const 基础模特 = {
124+ id,
125+ 姓名 : id ,
126+ 类型 : '素人模特' as const ,
127+ 职业状态 : '新人' as const ,
128+ 保护意识 : '适度保护' as const ,
129+ 信任度 : 50 ,
130+ 安全感 : 60 ,
131+ 自我认同 : 50 ,
132+ 羞耻度 : 50 ,
133+ 拍摄总次数 : 0 ,
134+ 正规拍摄次数 : 0 ,
135+ 擦边拍摄次数 : 0 ,
136+ 越界拍摄次数 : 0 ,
137+ 当前底线 : 'G级' as const ,
138+ 底线历史 : [ ] ,
139+ 被偷拍次数 : 0 ,
140+ 被泄露次数 : 0 ,
141+ 投诉次数 : 0 ,
142+ 累计收入 : 0 ,
143+ 单次报价 : 0 ,
144+ 拍摄经历 : [ ] as any [ ] ,
145+ } ;
146+ 新模特档案 [ id ] = { ...基础模特 , ...档案 } ;
147+ }
148+ }
149+ 新系统 . 模特档案 = 新模特档案 ;
150+ }
151+
152+ // 应用摄影师档案更新(不存在的自动创建)
153+ if ( 更新 . 更新摄影师档案 ) {
154+ const 新摄影师档案 = { ...( 新系统 . 摄影师档案 || { } ) } ;
155+ for ( const [ id , 档案 ] of Object . entries ( 更新 . 更新摄影师档案 ) ) {
156+ if ( 新摄影师档案 [ id ] ) {
157+ 新摄影师档案 [ id ] = { ...新摄影师档案 [ id ] , ...档案 } ;
158+ } else {
159+ // 新摄影师:创建完整最小档案
160+ const 基础摄影师 = {
161+ id,
162+ 姓名 : id ,
163+ 类型 : '独立摄影师' as const ,
164+ 动机 : '纯艺术' as const ,
165+ 信誉 : '普通摄影师' as const ,
166+ 技术水平 : 50 ,
167+ 沟通能力 : 50 ,
168+ 越界倾向 : 30 ,
169+ 偷拍倾向 : 10 ,
170+ 传播倾向 : 10 ,
171+ 口碑评分 : 50 ,
172+ 投诉累计 : 0 ,
173+ 拍摄总次数 : 0 ,
174+ 回头客数量 : 0 ,
175+ 作品发布数量 : 0 ,
176+ 擅长写真类型 : [ ] as any [ ] ,
177+ 擅长拍摄风格 : [ ] as any [ ] ,
178+ } ;
179+ 新摄影师档案 [ id ] = { ...基础摄影师 , ...档案 } ;
180+ }
181+ }
182+ 新系统 . 摄影师档案 = 新摄影师档案 ;
183+ }
184+
185+ // 应用拍摄项目状态更新(不存在的自动创建)
186+ if ( 更新 . 更新拍摄项目 ) {
187+ const 当前项目 = 新系统 . 进行中的拍摄项目 || [ ] ;
188+ const 新项目列表 = [ ...当前项目 ] ;
189+
190+ for ( const raw更新 of 更新 . 更新拍摄项目 as any [ ] ) {
191+ const 项目ID = raw更新 . id || raw更新 . 项目ID ;
192+ const 已有索引 = 新项目列表 . findIndex ( ( p : any ) => p . id === 项目ID || p . 项目ID === 项目ID ) ;
193+
194+ if ( 已有索引 >= 0 ) {
195+ 新项目列表 [ 已有索引 ] = { ...新项目列表 [ 已有索引 ] , ...raw更新 } ;
196+ } else {
197+ // 新项目:创建完整最小项目
198+ const 基础项目 = {
199+ id : 项目ID ,
200+ 项目ID,
201+ 模特Id : Object . keys ( 更新 . 更新模特档案 || { } ) [ 0 ] || 'unknown' ,
202+ 摄影师Id : 'unknown' ,
203+ 约定写真类型 : '商业写真' as const ,
204+ 约定场所 : '影棚' as const ,
205+ 约定风格 : '清新自然' as const ,
206+ 约定尺度 : 'G级' as const ,
207+ 约定服装 : '日常便装' as const ,
208+ 约定交付时间 : 0 ,
209+ 实际场所 : '影棚' as const ,
210+ 实际尺度 : 'G级' as const ,
211+ 实际服装 : '日常便装' as const ,
212+ 当前回合 : 1 ,
213+ 最大回合 : 10 ,
214+ 拍摄阶段 : '未开始' as const ,
215+ 尺度变更历史 : [ ] ,
216+ 越界行为记录 : [ ] ,
217+ 泄露风险评分 : 0 ,
218+ 交付状态 : '待交付' as const ,
219+ 交付方式 : null ,
220+ 后期处理方式 : '纯自然' as const ,
221+ 违规记录 : [ ] ,
222+ } ;
223+ 新项目列表 . push ( { ...基础项目 , ...raw更新 } ) ;
224+ }
225+ }
226+
227+ 新系统 . 进行中的拍摄项目 = 新项目列表 ;
228+ }
229+
230+ // 应用新泄露事件
231+ if ( 更新 . 新泄露事件 ) {
232+ 新系统 . 泄露事件列表 = [ ...( 新系统 . 泄露事件列表 || [ ] ) , ...更新 . 新泄露事件 ] ;
233+ }
234+
235+ return 新系统 ;
236+ } ;
237+
51238/**
52239 * 构建写真约拍 NSFW 运行时参数(供主剧情请求使用)
53240 */
@@ -60,16 +247,26 @@ export const 构建写真NSFW参数 = (state: {
60247 出身背景 ?: {
61248 名称 ?: string ;
62249 } ;
250+ 姓名 ?: string ;
63251 } ;
64252 时代配置ID ?: string ;
253+ 社交列表 ?: Array < { id : string ; 姓名 : string ; [ key : string ] : any } > ;
65254} ) : {
66255 活跃拍摄项目 ?: 拍摄项目状态 ;
67256 模特数量 ?: number ;
68257 摄影师数量 ?: number ;
69258 泄露事件数量 ?: number ;
70259 内容强度 ?: '微暗' | '暧昧' | '露骨' ;
71260 主要玩法层 ?: '经营管理' | '人际关系' | '灰色地带' ;
261+ NPC姓名映射 ?: Record < string , string > ;
262+ 摄影师姓名映射 ?: Record < string , string > ;
72263 启用道德选择 ?: boolean ;
264+ 启用尺度递进 ?: boolean ;
265+ 启用越界识别 ?: boolean ;
266+ 启用安全词系统 ?: boolean ;
267+ 启用照片交付 ?: boolean ;
268+ 启用泄露事件 ?: boolean ;
269+ 泄露事件频率 ?: '低' | '中' | '高' ;
73270} | undefined => {
74271 // 检查时代配置 - 必须是 contemporary_ 开头的现代纪元
75272 const 时代ID = state . 时代配置ID || '' ;
@@ -83,30 +280,50 @@ export const 构建写真NSFW参数 = (state: {
83280 return undefined ;
84281 }
85282
86- // 检查写真系统是否存在
283+ // 写真系统可能为空(新游戏尚未创建约拍项目),但仍需返回基本设置参数
284+ // 让 LLM 知道写真系统已激活,可以在剧情中触发约拍场景
87285 const 写真系统 = state . 写真系统 ;
88- if ( ! 写真系统 ) {
89- return undefined ;
90- }
91286
92287 // 获取活跃拍摄项目
93- const 进行中项目 = 写真系统 . 进行中的拍摄项目 ;
94- const 活跃项目 = 进行中项目 && 进行中项目 . length > 0
95- ? 进行中项目 [ 进行中项目 . length - 1 ]
288+ const 进行中项目 = 写真系统 ? .进行中的拍摄项目 ;
289+ const 活跃项目 = 进行中项目 && 进行中项目 . length > 0
290+ ? 进行中项目 [ 进行中项目 . length - 1 ]
96291 : undefined ;
97292
98- // 统计数量
99- const 模特数量 = 写真系统 . 模特档案 ? Object . keys ( 写真系统 . 模特档案 ) . length : 0 ;
100- const 摄影师数量 = 写真系统 . 摄影师档案 ? Object . keys ( 写真系统 . 摄影师档案 ) . length : 0 ;
101- const 泄露事件数量 = 写真系统 . 泄露事件列表 ?. length || 0 ;
293+ // 统计数量(系统为空时均为 0)
294+ const 模特数量 = 写真系统 ?. 模特档案 ? Object . keys ( 写真系统 . 模特档案 ) . length : 0 ;
295+ const 摄影师数量 = 写真系统 ?. 摄影师档案 ? Object . keys ( 写真系统 . 摄影师档案 ) . length : 0 ;
296+ const 泄露事件数量 = 写真系统 ?. 泄露事件列表 ?. length || 0 ;
297+
298+ // 构建 NPC ID -> 姓名的映射,供 LLM 在输出状态时使用真实姓名
299+ const NPC姓名映射 : Record < string , string > = { } ;
300+ if ( state . 社交列表 ) {
301+ state . 社交列表 . forEach ( npc => {
302+ NPC姓名映射 [ npc . id ] = npc . 姓名 || npc . id ;
303+ } ) ;
304+ }
305+
306+ // 玩家角色如果是摄影师背景,用角色名作为摄影师姓名
307+ const 摄影师姓名映射 : Record < string , string > = { } ;
308+ if ( state . 角色 ?. 姓名 ) {
309+ 摄影师姓名映射 [ 'player' ] = state . 角色 . 姓名 ;
310+ }
102311
103312 return {
104313 活跃拍摄项目 : 活跃项目 ,
105314 模特数量,
106315 摄影师数量,
107316 泄露事件数量,
317+ NPC姓名映射,
318+ 摄影师姓名映射,
108319 内容强度 : nsfw设置 . NSFW内容强度 ,
109320 主要玩法层 : nsfw设置 . 主要玩法层 ,
110321 启用道德选择 : nsfw设置 . 启用道德选择 ,
322+ 启用尺度递进 : nsfw设置 . 启用尺度递进 ,
323+ 启用越界识别 : nsfw设置 . 启用越界识别 ,
324+ 启用安全词系统 : nsfw设置 . 启用安全词系统 ,
325+ 启用照片交付 : nsfw设置 . 启用照片交付 ,
326+ 启用泄露事件 : nsfw设置 . 启用泄露事件 ,
327+ 泄露事件频率 : nsfw设置 . 泄露事件频率 ,
111328 } ;
112329} ;
0 commit comments