diff --git a/audio/mp3/linxia/generic_cold_fine.mp3 b/audio/mp3/linxia/generic_cold_fine.mp3 new file mode 100644 index 0000000..5874344 Binary files /dev/null and b/audio/mp3/linxia/generic_cold_fine.mp3 differ diff --git a/audio/mp3/linxia/generic_cold_know.mp3 b/audio/mp3/linxia/generic_cold_know.mp3 new file mode 100644 index 0000000..7fb0d98 Binary files /dev/null and b/audio/mp3/linxia/generic_cold_know.mp3 differ diff --git a/audio/mp3/linxia/generic_curious.mp3 b/audio/mp3/linxia/generic_curious.mp3 new file mode 100644 index 0000000..c00f4d2 Binary files /dev/null and b/audio/mp3/linxia/generic_curious.mp3 differ diff --git a/audio/mp3/linxia/generic_hmm.mp3 b/audio/mp3/linxia/generic_hmm.mp3 new file mode 100644 index 0000000..9eeb731 Binary files /dev/null and b/audio/mp3/linxia/generic_hmm.mp3 differ diff --git a/audio/mp3/linxia/generic_ok.mp3 b/audio/mp3/linxia/generic_ok.mp3 new file mode 100644 index 0000000..0f02148 Binary files /dev/null and b/audio/mp3/linxia/generic_ok.mp3 differ diff --git a/audio/mp3/linxia/generic_then.mp3 b/audio/mp3/linxia/generic_then.mp3 new file mode 100644 index 0000000..f39c246 Binary files /dev/null and b/audio/mp3/linxia/generic_then.mp3 differ diff --git a/audio/mp3/linxia/generic_warm_ok.mp3 b/audio/mp3/linxia/generic_warm_ok.mp3 new file mode 100644 index 0000000..c4a7599 Binary files /dev/null and b/audio/mp3/linxia/generic_warm_ok.mp3 differ diff --git a/audio/mp3/linxia/msg01_auto.mp3 b/audio/mp3/linxia/msg01_auto.mp3 new file mode 100644 index 0000000..abaf2be Binary files /dev/null and b/audio/mp3/linxia/msg01_auto.mp3 differ diff --git a/audio/mp3/linxia/msg02_cold.mp3 b/audio/mp3/linxia/msg02_cold.mp3 new file mode 100644 index 0000000..0524b3e Binary files /dev/null and b/audio/mp3/linxia/msg02_cold.mp3 differ diff --git a/audio/mp3/linxia/msg02_curious.mp3 b/audio/mp3/linxia/msg02_curious.mp3 new file mode 100644 index 0000000..b0d6fb3 Binary files /dev/null and b/audio/mp3/linxia/msg02_curious.mp3 differ diff --git a/audio/mp3/linxia/msg02_r3_curious.mp3 b/audio/mp3/linxia/msg02_r3_curious.mp3 new file mode 100644 index 0000000..2cee07d Binary files /dev/null and b/audio/mp3/linxia/msg02_r3_curious.mp3 differ diff --git a/audio/mp3/linxia/msg02_warm.mp3 b/audio/mp3/linxia/msg02_warm.mp3 new file mode 100644 index 0000000..fb8931f Binary files /dev/null and b/audio/mp3/linxia/msg02_warm.mp3 differ diff --git a/audio/mp3/linxia/msg03_auto.mp3 b/audio/mp3/linxia/msg03_auto.mp3 new file mode 100644 index 0000000..884d582 Binary files /dev/null and b/audio/mp3/linxia/msg03_auto.mp3 differ diff --git a/audio/mp3/linxia/msg04_cold.mp3 b/audio/mp3/linxia/msg04_cold.mp3 new file mode 100644 index 0000000..288a2a4 Binary files /dev/null and b/audio/mp3/linxia/msg04_cold.mp3 differ diff --git a/audio/mp3/linxia/msg04_curious.mp3 b/audio/mp3/linxia/msg04_curious.mp3 new file mode 100644 index 0000000..c86b6cd Binary files /dev/null and b/audio/mp3/linxia/msg04_curious.mp3 differ diff --git a/audio/mp3/linxia/msg04_r3c_curious.mp3 b/audio/mp3/linxia/msg04_r3c_curious.mp3 new file mode 100644 index 0000000..e830a73 Binary files /dev/null and b/audio/mp3/linxia/msg04_r3c_curious.mp3 differ diff --git a/audio/mp3/linxia/msg04_r3c_warm.mp3 b/audio/mp3/linxia/msg04_r3c_warm.mp3 new file mode 100644 index 0000000..426685f Binary files /dev/null and b/audio/mp3/linxia/msg04_r3c_warm.mp3 differ diff --git a/audio/mp3/linxia/msg04_r3w_cold.mp3 b/audio/mp3/linxia/msg04_r3w_cold.mp3 new file mode 100644 index 0000000..01a368c Binary files /dev/null and b/audio/mp3/linxia/msg04_r3w_cold.mp3 differ diff --git a/audio/mp3/linxia/msg04_r3w_warm.mp3 b/audio/mp3/linxia/msg04_r3w_warm.mp3 new file mode 100644 index 0000000..c3ea80f Binary files /dev/null and b/audio/mp3/linxia/msg04_r3w_warm.mp3 differ diff --git a/audio/mp3/linxia/msg04_warm.mp3 b/audio/mp3/linxia/msg04_warm.mp3 new file mode 100644 index 0000000..53f9cb4 Binary files /dev/null and b/audio/mp3/linxia/msg04_warm.mp3 differ diff --git a/audio/mp3/linxia/msg05_cold.mp3 b/audio/mp3/linxia/msg05_cold.mp3 new file mode 100644 index 0000000..2394aa1 Binary files /dev/null and b/audio/mp3/linxia/msg05_cold.mp3 differ diff --git a/audio/mp3/linxia/msg05_curious.mp3 b/audio/mp3/linxia/msg05_curious.mp3 new file mode 100644 index 0000000..23cd330 Binary files /dev/null and b/audio/mp3/linxia/msg05_curious.mp3 differ diff --git a/audio/mp3/linxia/msg05_warm.mp3 b/audio/mp3/linxia/msg05_warm.mp3 new file mode 100644 index 0000000..488a047 Binary files /dev/null and b/audio/mp3/linxia/msg05_warm.mp3 differ diff --git a/audio/mp3/linxia/msg06_cold.mp3 b/audio/mp3/linxia/msg06_cold.mp3 new file mode 100644 index 0000000..32e449a Binary files /dev/null and b/audio/mp3/linxia/msg06_cold.mp3 differ diff --git a/audio/mp3/linxia/msg06_curious.mp3 b/audio/mp3/linxia/msg06_curious.mp3 new file mode 100644 index 0000000..60955b7 Binary files /dev/null and b/audio/mp3/linxia/msg06_curious.mp3 differ diff --git a/audio/mp3/linxia/msg06_warm.mp3 b/audio/mp3/linxia/msg06_warm.mp3 new file mode 100644 index 0000000..eb10108 Binary files /dev/null and b/audio/mp3/linxia/msg06_warm.mp3 differ diff --git a/audio/mp3/linxia/msg07_cold.mp3 b/audio/mp3/linxia/msg07_cold.mp3 new file mode 100644 index 0000000..3320aa2 Binary files /dev/null and b/audio/mp3/linxia/msg07_cold.mp3 differ diff --git a/audio/mp3/linxia/msg07_curious.mp3 b/audio/mp3/linxia/msg07_curious.mp3 new file mode 100644 index 0000000..f03c32f Binary files /dev/null and b/audio/mp3/linxia/msg07_curious.mp3 differ diff --git a/audio/mp3/linxia/msg07_r3_cold.mp3 b/audio/mp3/linxia/msg07_r3_cold.mp3 new file mode 100644 index 0000000..55b9fdc Binary files /dev/null and b/audio/mp3/linxia/msg07_r3_cold.mp3 differ diff --git a/audio/mp3/linxia/msg07_r3_warm.mp3 b/audio/mp3/linxia/msg07_r3_warm.mp3 new file mode 100644 index 0000000..90b9b94 Binary files /dev/null and b/audio/mp3/linxia/msg07_r3_warm.mp3 differ diff --git a/audio/mp3/linxia/msg07_warm.mp3 b/audio/mp3/linxia/msg07_warm.mp3 new file mode 100644 index 0000000..ae75dca Binary files /dev/null and b/audio/mp3/linxia/msg07_warm.mp3 differ diff --git a/audio/mp3/linxia/msg09_cold.mp3 b/audio/mp3/linxia/msg09_cold.mp3 new file mode 100644 index 0000000..9353257 Binary files /dev/null and b/audio/mp3/linxia/msg09_cold.mp3 differ diff --git a/audio/mp3/linxia/msg09_curious.mp3 b/audio/mp3/linxia/msg09_curious.mp3 new file mode 100644 index 0000000..661197d Binary files /dev/null and b/audio/mp3/linxia/msg09_curious.mp3 differ diff --git a/audio/mp3/linxia/msg09_r3_cold.mp3 b/audio/mp3/linxia/msg09_r3_cold.mp3 new file mode 100644 index 0000000..11d6463 Binary files /dev/null and b/audio/mp3/linxia/msg09_r3_cold.mp3 differ diff --git a/audio/mp3/linxia/msg09_r3_warm.mp3 b/audio/mp3/linxia/msg09_r3_warm.mp3 new file mode 100644 index 0000000..5fb9b0c Binary files /dev/null and b/audio/mp3/linxia/msg09_r3_warm.mp3 differ diff --git a/audio/mp3/linxia/msg09_warm.mp3 b/audio/mp3/linxia/msg09_warm.mp3 new file mode 100644 index 0000000..2958441 Binary files /dev/null and b/audio/mp3/linxia/msg09_warm.mp3 differ diff --git a/audio/mp3/linxia/msg10_call_curious.mp3 b/audio/mp3/linxia/msg10_call_curious.mp3 new file mode 100644 index 0000000..5396657 Binary files /dev/null and b/audio/mp3/linxia/msg10_call_curious.mp3 differ diff --git a/audio/mp3/linxia/msg10_call_warm.mp3 b/audio/mp3/linxia/msg10_call_warm.mp3 new file mode 100644 index 0000000..1eeba6f Binary files /dev/null and b/audio/mp3/linxia/msg10_call_warm.mp3 differ diff --git a/audio/mp3/linxia/msg10_cold.mp3 b/audio/mp3/linxia/msg10_cold.mp3 new file mode 100644 index 0000000..5857fa0 Binary files /dev/null and b/audio/mp3/linxia/msg10_cold.mp3 differ diff --git a/audio/mp3/linxia/msg10_curious.mp3 b/audio/mp3/linxia/msg10_curious.mp3 new file mode 100644 index 0000000..8109401 Binary files /dev/null and b/audio/mp3/linxia/msg10_curious.mp3 differ diff --git a/audio/mp3/linxia/msg10_rw_curious.mp3 b/audio/mp3/linxia/msg10_rw_curious.mp3 new file mode 100644 index 0000000..419afb1 Binary files /dev/null and b/audio/mp3/linxia/msg10_rw_curious.mp3 differ diff --git a/audio/mp3/linxia/msg10_rw_warm.mp3 b/audio/mp3/linxia/msg10_rw_warm.mp3 new file mode 100644 index 0000000..e7cf64d Binary files /dev/null and b/audio/mp3/linxia/msg10_rw_warm.mp3 differ diff --git a/audio/mp3/linxia/msg10_warm.mp3 b/audio/mp3/linxia/msg10_warm.mp3 new file mode 100644 index 0000000..cc761e0 Binary files /dev/null and b/audio/mp3/linxia/msg10_warm.mp3 differ diff --git a/audio/mp3/linxia/msg11_cold.mp3 b/audio/mp3/linxia/msg11_cold.mp3 new file mode 100644 index 0000000..9a07ea1 Binary files /dev/null and b/audio/mp3/linxia/msg11_cold.mp3 differ diff --git a/audio/mp3/linxia/msg11_curious.mp3 b/audio/mp3/linxia/msg11_curious.mp3 new file mode 100644 index 0000000..b5dea9e Binary files /dev/null and b/audio/mp3/linxia/msg11_curious.mp3 differ diff --git a/audio/mp3/linxia/msg11_tap.mp3 b/audio/mp3/linxia/msg11_tap.mp3 new file mode 100644 index 0000000..bdc922a Binary files /dev/null and b/audio/mp3/linxia/msg11_tap.mp3 differ diff --git a/audio/mp3/linxia/msg11_warm.mp3 b/audio/mp3/linxia/msg11_warm.mp3 new file mode 100644 index 0000000..fca5d8a Binary files /dev/null and b/audio/mp3/linxia/msg11_warm.mp3 differ diff --git a/audio/mp3/linxia/msg12_cold.mp3 b/audio/mp3/linxia/msg12_cold.mp3 new file mode 100644 index 0000000..21b6654 Binary files /dev/null and b/audio/mp3/linxia/msg12_cold.mp3 differ diff --git a/audio/mp3/linxia/msg12_curious.mp3 b/audio/mp3/linxia/msg12_curious.mp3 new file mode 100644 index 0000000..1698303 Binary files /dev/null and b/audio/mp3/linxia/msg12_curious.mp3 differ diff --git a/audio/mp3/linxia/msg12_tap.mp3 b/audio/mp3/linxia/msg12_tap.mp3 new file mode 100644 index 0000000..c58c976 Binary files /dev/null and b/audio/mp3/linxia/msg12_tap.mp3 differ diff --git a/audio/mp3/linxia/msg12_warm.mp3 b/audio/mp3/linxia/msg12_warm.mp3 new file mode 100644 index 0000000..54a2998 Binary files /dev/null and b/audio/mp3/linxia/msg12_warm.mp3 differ diff --git a/audio/mp3/linxia/msg13_cold.mp3 b/audio/mp3/linxia/msg13_cold.mp3 new file mode 100644 index 0000000..20ec9f8 Binary files /dev/null and b/audio/mp3/linxia/msg13_cold.mp3 differ diff --git a/audio/mp3/linxia/msg13_curious.mp3 b/audio/mp3/linxia/msg13_curious.mp3 new file mode 100644 index 0000000..53d29db Binary files /dev/null and b/audio/mp3/linxia/msg13_curious.mp3 differ diff --git a/audio/mp3/linxia/msg13_r3_cold.mp3 b/audio/mp3/linxia/msg13_r3_cold.mp3 new file mode 100644 index 0000000..c855113 Binary files /dev/null and b/audio/mp3/linxia/msg13_r3_cold.mp3 differ diff --git a/audio/mp3/linxia/msg13_r3_curious.mp3 b/audio/mp3/linxia/msg13_r3_curious.mp3 new file mode 100644 index 0000000..6951620 Binary files /dev/null and b/audio/mp3/linxia/msg13_r3_curious.mp3 differ diff --git a/audio/mp3/linxia/msg13_r3_warm.mp3 b/audio/mp3/linxia/msg13_r3_warm.mp3 new file mode 100644 index 0000000..446fb87 Binary files /dev/null and b/audio/mp3/linxia/msg13_r3_warm.mp3 differ diff --git a/audio/mp3/linxia/msg13_tap.mp3 b/audio/mp3/linxia/msg13_tap.mp3 new file mode 100644 index 0000000..464c444 Binary files /dev/null and b/audio/mp3/linxia/msg13_tap.mp3 differ diff --git a/audio/mp3/linxia/msg13_warm.mp3 b/audio/mp3/linxia/msg13_warm.mp3 new file mode 100644 index 0000000..e210490 Binary files /dev/null and b/audio/mp3/linxia/msg13_warm.mp3 differ diff --git a/audio/mp3/linxia/msg14_auto.mp3 b/audio/mp3/linxia/msg14_auto.mp3 new file mode 100644 index 0000000..48f831b Binary files /dev/null and b/audio/mp3/linxia/msg14_auto.mp3 differ diff --git a/audio/mp3/react/msg02_r3_curious.mp3 b/audio/mp3/react/msg02_r3_curious.mp3 new file mode 100644 index 0000000..40588bd Binary files /dev/null and b/audio/mp3/react/msg02_r3_curious.mp3 differ diff --git a/audio/mp3/react/msg02_react_cold.mp3 b/audio/mp3/react/msg02_react_cold.mp3 new file mode 100644 index 0000000..ee00440 Binary files /dev/null and b/audio/mp3/react/msg02_react_cold.mp3 differ diff --git a/audio/mp3/react/msg02_react_curious.mp3 b/audio/mp3/react/msg02_react_curious.mp3 new file mode 100644 index 0000000..d2bba1a Binary files /dev/null and b/audio/mp3/react/msg02_react_curious.mp3 differ diff --git a/audio/mp3/react/msg02_react_silent.mp3 b/audio/mp3/react/msg02_react_silent.mp3 new file mode 100644 index 0000000..bc3fc18 Binary files /dev/null and b/audio/mp3/react/msg02_react_silent.mp3 differ diff --git a/audio/mp3/react/msg02_react_tap.mp3 b/audio/mp3/react/msg02_react_tap.mp3 new file mode 100644 index 0000000..a5f271a Binary files /dev/null and b/audio/mp3/react/msg02_react_tap.mp3 differ diff --git a/audio/mp3/react/msg02_react_warm.mp3 b/audio/mp3/react/msg02_react_warm.mp3 new file mode 100644 index 0000000..9119f2a Binary files /dev/null and b/audio/mp3/react/msg02_react_warm.mp3 differ diff --git a/audio/mp3/react/msg04_r3c_nevermind.mp3 b/audio/mp3/react/msg04_r3c_nevermind.mp3 new file mode 100644 index 0000000..c96beb4 Binary files /dev/null and b/audio/mp3/react/msg04_r3c_nevermind.mp3 differ diff --git a/audio/mp3/react/msg04_r3w_good.mp3 b/audio/mp3/react/msg04_r3w_good.mp3 new file mode 100644 index 0000000..23935c2 Binary files /dev/null and b/audio/mp3/react/msg04_r3w_good.mp3 differ diff --git a/audio/mp3/react/msg04_r3w_night.mp3 b/audio/mp3/react/msg04_r3w_night.mp3 new file mode 100644 index 0000000..d193960 Binary files /dev/null and b/audio/mp3/react/msg04_r3w_night.mp3 differ diff --git a/audio/mp3/react/msg04_r3w_sorry.mp3 b/audio/mp3/react/msg04_r3w_sorry.mp3 new file mode 100644 index 0000000..ed7823f Binary files /dev/null and b/audio/mp3/react/msg04_r3w_sorry.mp3 differ diff --git a/audio/mp3/react/msg04_react_cold.mp3 b/audio/mp3/react/msg04_react_cold.mp3 new file mode 100644 index 0000000..4a5332d Binary files /dev/null and b/audio/mp3/react/msg04_react_cold.mp3 differ diff --git a/audio/mp3/react/msg04_react_curious.mp3 b/audio/mp3/react/msg04_react_curious.mp3 new file mode 100644 index 0000000..c8ce1af Binary files /dev/null and b/audio/mp3/react/msg04_react_curious.mp3 differ diff --git a/audio/mp3/react/msg04_react_silent.mp3 b/audio/mp3/react/msg04_react_silent.mp3 new file mode 100644 index 0000000..d64d009 Binary files /dev/null and b/audio/mp3/react/msg04_react_silent.mp3 differ diff --git a/audio/mp3/react/msg04_react_tap.mp3 b/audio/mp3/react/msg04_react_tap.mp3 new file mode 100644 index 0000000..0478ddc Binary files /dev/null and b/audio/mp3/react/msg04_react_tap.mp3 differ diff --git a/audio/mp3/react/msg04_react_warm.mp3 b/audio/mp3/react/msg04_react_warm.mp3 new file mode 100644 index 0000000..949204a Binary files /dev/null and b/audio/mp3/react/msg04_react_warm.mp3 differ diff --git a/audio/mp3/react/msg05_react_curious.mp3 b/audio/mp3/react/msg05_react_curious.mp3 new file mode 100644 index 0000000..5dd21d1 Binary files /dev/null and b/audio/mp3/react/msg05_react_curious.mp3 differ diff --git a/audio/mp3/react/msg05_react_warm.mp3 b/audio/mp3/react/msg05_react_warm.mp3 new file mode 100644 index 0000000..9df788d Binary files /dev/null and b/audio/mp3/react/msg05_react_warm.mp3 differ diff --git a/audio/mp3/react/msg06_react_cold.mp3 b/audio/mp3/react/msg06_react_cold.mp3 new file mode 100644 index 0000000..c2fa4c5 Binary files /dev/null and b/audio/mp3/react/msg06_react_cold.mp3 differ diff --git a/audio/mp3/react/msg06_react_curious.mp3 b/audio/mp3/react/msg06_react_curious.mp3 new file mode 100644 index 0000000..5145b1c Binary files /dev/null and b/audio/mp3/react/msg06_react_curious.mp3 differ diff --git a/audio/mp3/react/msg06_react_silent.mp3 b/audio/mp3/react/msg06_react_silent.mp3 new file mode 100644 index 0000000..f36bea1 Binary files /dev/null and b/audio/mp3/react/msg06_react_silent.mp3 differ diff --git a/audio/mp3/react/msg06_react_tap.mp3 b/audio/mp3/react/msg06_react_tap.mp3 new file mode 100644 index 0000000..bc981d6 Binary files /dev/null and b/audio/mp3/react/msg06_react_tap.mp3 differ diff --git a/audio/mp3/react/msg06_react_warm.mp3 b/audio/mp3/react/msg06_react_warm.mp3 new file mode 100644 index 0000000..1c4300d Binary files /dev/null and b/audio/mp3/react/msg06_react_warm.mp3 differ diff --git a/audio/mp3/react/msg07_r3_cold.mp3 b/audio/mp3/react/msg07_r3_cold.mp3 new file mode 100644 index 0000000..c8de309 Binary files /dev/null and b/audio/mp3/react/msg07_r3_cold.mp3 differ diff --git a/audio/mp3/react/msg07_react_curious.mp3 b/audio/mp3/react/msg07_react_curious.mp3 new file mode 100644 index 0000000..5f7487b Binary files /dev/null and b/audio/mp3/react/msg07_react_curious.mp3 differ diff --git a/audio/mp3/react/msg07_react_tap.mp3 b/audio/mp3/react/msg07_react_tap.mp3 new file mode 100644 index 0000000..3b5cf79 Binary files /dev/null and b/audio/mp3/react/msg07_react_tap.mp3 differ diff --git a/audio/mp3/react/msg07_react_warm.mp3 b/audio/mp3/react/msg07_react_warm.mp3 new file mode 100644 index 0000000..b05f3c0 Binary files /dev/null and b/audio/mp3/react/msg07_react_warm.mp3 differ diff --git a/audio/mp3/react/msg09_r3_cold.mp3 b/audio/mp3/react/msg09_r3_cold.mp3 new file mode 100644 index 0000000..91e3d7a Binary files /dev/null and b/audio/mp3/react/msg09_r3_cold.mp3 differ diff --git a/audio/mp3/react/msg09_r3_warm.mp3 b/audio/mp3/react/msg09_r3_warm.mp3 new file mode 100644 index 0000000..2ea5cae Binary files /dev/null and b/audio/mp3/react/msg09_r3_warm.mp3 differ diff --git a/audio/mp3/react/msg09_react_curious.mp3 b/audio/mp3/react/msg09_react_curious.mp3 new file mode 100644 index 0000000..98a2d48 Binary files /dev/null and b/audio/mp3/react/msg09_react_curious.mp3 differ diff --git a/audio/mp3/react/msg09_react_tap.mp3 b/audio/mp3/react/msg09_react_tap.mp3 new file mode 100644 index 0000000..e449730 Binary files /dev/null and b/audio/mp3/react/msg09_react_tap.mp3 differ diff --git a/audio/mp3/react/msg09_react_warm.mp3 b/audio/mp3/react/msg09_react_warm.mp3 new file mode 100644 index 0000000..4dddb51 Binary files /dev/null and b/audio/mp3/react/msg09_react_warm.mp3 differ diff --git a/audio/mp3/react/msg10_call_curious.mp3 b/audio/mp3/react/msg10_call_curious.mp3 new file mode 100644 index 0000000..2709dcc Binary files /dev/null and b/audio/mp3/react/msg10_call_curious.mp3 differ diff --git a/audio/mp3/react/msg10_call_silent.mp3 b/audio/mp3/react/msg10_call_silent.mp3 new file mode 100644 index 0000000..07e83f1 Binary files /dev/null and b/audio/mp3/react/msg10_call_silent.mp3 differ diff --git a/audio/mp3/react/msg10_call_warm.mp3 b/audio/mp3/react/msg10_call_warm.mp3 new file mode 100644 index 0000000..376f1bd Binary files /dev/null and b/audio/mp3/react/msg10_call_warm.mp3 differ diff --git a/audio/mp3/react/msg10_react_cold.mp3 b/audio/mp3/react/msg10_react_cold.mp3 new file mode 100644 index 0000000..39c4a20 Binary files /dev/null and b/audio/mp3/react/msg10_react_cold.mp3 differ diff --git a/audio/mp3/react/msg10_react_curious.mp3 b/audio/mp3/react/msg10_react_curious.mp3 new file mode 100644 index 0000000..58378eb Binary files /dev/null and b/audio/mp3/react/msg10_react_curious.mp3 differ diff --git a/audio/mp3/react/msg10_react_silent.mp3 b/audio/mp3/react/msg10_react_silent.mp3 new file mode 100644 index 0000000..397068a Binary files /dev/null and b/audio/mp3/react/msg10_react_silent.mp3 differ diff --git a/audio/mp3/react/msg10_react_tap.mp3 b/audio/mp3/react/msg10_react_tap.mp3 new file mode 100644 index 0000000..810ab67 Binary files /dev/null and b/audio/mp3/react/msg10_react_tap.mp3 differ diff --git a/audio/mp3/react/msg10_react_warm.mp3 b/audio/mp3/react/msg10_react_warm.mp3 new file mode 100644 index 0000000..b01c706 Binary files /dev/null and b/audio/mp3/react/msg10_react_warm.mp3 differ diff --git a/audio/mp3/react/msg10_rw_curious.mp3 b/audio/mp3/react/msg10_rw_curious.mp3 new file mode 100644 index 0000000..93aa8c8 Binary files /dev/null and b/audio/mp3/react/msg10_rw_curious.mp3 differ diff --git a/audio/mp3/react/msg10_rw_warm.mp3 b/audio/mp3/react/msg10_rw_warm.mp3 new file mode 100644 index 0000000..b750c4c Binary files /dev/null and b/audio/mp3/react/msg10_rw_warm.mp3 differ diff --git a/audio/mp3/react/msg11_react_cold.mp3 b/audio/mp3/react/msg11_react_cold.mp3 new file mode 100644 index 0000000..2388f64 Binary files /dev/null and b/audio/mp3/react/msg11_react_cold.mp3 differ diff --git a/audio/mp3/react/msg11_react_curious.mp3 b/audio/mp3/react/msg11_react_curious.mp3 new file mode 100644 index 0000000..d6afadc Binary files /dev/null and b/audio/mp3/react/msg11_react_curious.mp3 differ diff --git a/audio/mp3/react/msg11_react_tap.mp3 b/audio/mp3/react/msg11_react_tap.mp3 new file mode 100644 index 0000000..23fd4ae Binary files /dev/null and b/audio/mp3/react/msg11_react_tap.mp3 differ diff --git a/audio/mp3/react/msg11_react_warm.mp3 b/audio/mp3/react/msg11_react_warm.mp3 new file mode 100644 index 0000000..030a833 Binary files /dev/null and b/audio/mp3/react/msg11_react_warm.mp3 differ diff --git a/audio/mp3/react/msg12_react_cold.mp3 b/audio/mp3/react/msg12_react_cold.mp3 new file mode 100644 index 0000000..a34a6a6 Binary files /dev/null and b/audio/mp3/react/msg12_react_cold.mp3 differ diff --git a/audio/mp3/react/msg12_react_curious.mp3 b/audio/mp3/react/msg12_react_curious.mp3 new file mode 100644 index 0000000..d0aa53d Binary files /dev/null and b/audio/mp3/react/msg12_react_curious.mp3 differ diff --git a/audio/mp3/react/msg12_react_silent.mp3 b/audio/mp3/react/msg12_react_silent.mp3 new file mode 100644 index 0000000..68a8b21 Binary files /dev/null and b/audio/mp3/react/msg12_react_silent.mp3 differ diff --git a/audio/mp3/react/msg12_react_tap.mp3 b/audio/mp3/react/msg12_react_tap.mp3 new file mode 100644 index 0000000..5738989 Binary files /dev/null and b/audio/mp3/react/msg12_react_tap.mp3 differ diff --git a/audio/mp3/react/msg12_react_warm.mp3 b/audio/mp3/react/msg12_react_warm.mp3 new file mode 100644 index 0000000..5dfdcbe Binary files /dev/null and b/audio/mp3/react/msg12_react_warm.mp3 differ diff --git a/audio/mp3/react/msg13_r3_cold.mp3 b/audio/mp3/react/msg13_r3_cold.mp3 new file mode 100644 index 0000000..a85421c Binary files /dev/null and b/audio/mp3/react/msg13_r3_cold.mp3 differ diff --git a/audio/mp3/react/msg13_r3_curious.mp3 b/audio/mp3/react/msg13_r3_curious.mp3 new file mode 100644 index 0000000..d3b0dd3 Binary files /dev/null and b/audio/mp3/react/msg13_r3_curious.mp3 differ diff --git a/audio/mp3/react/msg13_r3_warm.mp3 b/audio/mp3/react/msg13_r3_warm.mp3 new file mode 100644 index 0000000..280ee15 Binary files /dev/null and b/audio/mp3/react/msg13_r3_warm.mp3 differ diff --git a/audio/mp3/react/msg13_react_cold.mp3 b/audio/mp3/react/msg13_react_cold.mp3 new file mode 100644 index 0000000..4b770b2 Binary files /dev/null and b/audio/mp3/react/msg13_react_cold.mp3 differ diff --git a/audio/mp3/react/msg13_react_curious.mp3 b/audio/mp3/react/msg13_react_curious.mp3 new file mode 100644 index 0000000..87e971e Binary files /dev/null and b/audio/mp3/react/msg13_react_curious.mp3 differ diff --git a/audio/mp3/react/msg13_react_silent.mp3 b/audio/mp3/react/msg13_react_silent.mp3 new file mode 100644 index 0000000..18e40bb Binary files /dev/null and b/audio/mp3/react/msg13_react_silent.mp3 differ diff --git a/audio/mp3/react/msg13_react_tap.mp3 b/audio/mp3/react/msg13_react_tap.mp3 new file mode 100644 index 0000000..aba5e4c Binary files /dev/null and b/audio/mp3/react/msg13_react_tap.mp3 differ diff --git a/audio/mp3/react/msg13_react_warm.mp3 b/audio/mp3/react/msg13_react_warm.mp3 new file mode 100644 index 0000000..319c0e9 Binary files /dev/null and b/audio/mp3/react/msg13_react_warm.mp3 differ diff --git a/audio/mp3/react/msg14_react_linked.mp3 b/audio/mp3/react/msg14_react_linked.mp3 new file mode 100644 index 0000000..352b96c Binary files /dev/null and b/audio/mp3/react/msg14_react_linked.mp3 differ diff --git a/audio/mp3/react/msg14_react_unlinked.mp3 b/audio/mp3/react/msg14_react_unlinked.mp3 new file mode 100644 index 0000000..0609e42 Binary files /dev/null and b/audio/mp3/react/msg14_react_unlinked.mp3 differ diff --git a/game/js/audio.js b/game/js/audio.js index 6ca2fe7..50b5c27 100644 --- a/game/js/audio.js +++ b/game/js/audio.js @@ -260,6 +260,20 @@ const SFX_UI = { setTimeout(() => Snd.synth({ freq: 523, dur: 100, vol: 0.13 }), 120); }, + // 打字/录音音效(~800ms) + typing() { + for (let i = 0; i < 6; i++) { + setTimeout(() => { + Snd.synth({ freq: 600 + Math.random() * 200, dur: 30, type: 'sine', vol: 0.1 }); + }, i * 120); + } + }, + + // 发送音效 + send() { + Snd.synth({ freq: 400, freq2: 900, dur: 120, type: 'sine', vol: 0.18 }); + }, + // 结局音效 ending() { Snd.synth({ freq: 523, dur: 400, vol: 0.25 }); diff --git a/game/js/engine.js b/game/js/engine.js index d012a51..8dc6cc7 100644 --- a/game/js/engine.js +++ b/game/js/engine.js @@ -1,32 +1,40 @@ -// engine.js — 游戏引擎(玩家驱动,无时间依赖) +// engine.js — 游戏引擎 v2(多轮对话 + 林夏语音 + NPC反应) // -// 状态流:idle → notification/ringing → playing → choosing → idle → …… → ending -// 所有 UI 反馈使用合成短音效(SFX_UI),不使用 TTS 语音播报 +// 状态流:idle → notification/ringing → playing → choosing +// → responding → reacting → [choosing | idle] → …… → ending +// +// 新增状态: +// responding — 林夏说话中(打字音效 + 语音播放) +// reacting — NPC反应播放中 class GameEngine { constructor() { - // 状态(通过 setter 自动更新提示栏) this._state = 'idle'; // 剧情变量 this.vars = { MOM_LINK: false, - HE_BACK: null, // null=未定, true=来了, false=拒了 + HE_BACK: null, GROUP_REPLY: false, - ZHOUNAN_DEPTH: 0, // 0-3 + ZHOUNAN_DEPTH: 0, ZHOUNAN_SHARE: false, SELF_RECORD: false, UNREAD: 0, }; this.profile = new ProfileTracker(); - this.msgState = {}; // msgId → 'unread'|'read'|'replied'|'missed' + this.msgState = {}; this.currentMsg = null; - this._queue = []; // 消息队列 + this._queue = []; this._endingTriggered = false; this._reminderTimer = null; + // v2: 对话系统 + this._currentChoices = null; // 当前可用的选项集 + this._pendingResolve = null; // 用于中断语音播放的 resolve + this._skipRequested = false; // 跳过标志 + this.gest = null; } @@ -43,6 +51,8 @@ class GameEngine { ringing: '单击 接听 · 左滑 拒接', playing: '双击 跳过', choosing: '→温暖 ←冷淡 ↑追问 ↓沉默', + responding: '双击 跳过', + reacting: '双击 跳过', ending: '', }; el.textContent = H[s] || ''; @@ -66,16 +76,48 @@ class GameEngine { async start() { this._queue = [...MESSAGES]; - // 环境音 + BGM await Snd.startAmbience(AMBIENCE_DEFAULT, { vol: 0.15 }); await sleep(1500); Snd.startBGM(`${MUS}/lv11_bgm_m1_main_loop_v1.mp3`, { vol: 0.2 }); await sleep(1500); - // 开始第一条消息 this._next(); } + // ══════════════════════════════════════════ + // 语音播放(异步 + 可中断) + // ══════════════════════════════════════════ + + _playVoiceAsync(urls) { + return new Promise(resolve => { + this._pendingResolve = resolve; + Snd.playVoice(urls, { + onEnd: () => { + this._pendingResolve = null; + resolve(); + }, + }); + }); + } + + _interruptVoice() { + Snd.stopVoice(); + TTS.stop(); + if (this._pendingResolve) { + this._pendingResolve(); + this._pendingResolve = null; + } + } + + // 打字 + 发送音效 + async _playTypingSend() { + SFX_UI.typing(); + await sleep(800); + if (this._skipRequested) return; + SFX_UI.send(); + await sleep(300); + } + // ══════════════════════════════════════════ // 消息队列推进 // ══════════════════════════════════════════ @@ -89,13 +131,13 @@ class GameEngine { return; } - // 消息16需要 MOM_LINK if (msg.requiresMomLink && !this.vars.MOM_LINK) { this.triggerFinalEnding(); return; } this.currentMsg = msg; + this._currentChoices = msg.choices || null; this.msgState[msg.id] = 'unread'; this.vars.UNREAD++; @@ -106,7 +148,6 @@ class GameEngine { } } - // ── 非通话消息通知 ── _notifyMessage(msg) { this.state = 'notification'; Haptic.message(); @@ -114,7 +155,6 @@ class GameEngine { this._startReminder(); } - // ── 来电铃声 ── async _startRinging(msg) { this.state = 'ringing'; Haptic.ring(); @@ -123,10 +163,10 @@ class GameEngine { } // ══════════════════════════════════════════ - // 通话流程 + // 通话流程(异步) // ══════════════════════════════════════════ - _answerCall(msg) { + async _answerCall(msg) { Snd.stopRingtone(); Haptic.stop(); Haptic.confirm(); @@ -140,22 +180,57 @@ class GameEngine { if (msg.stateOnAnswer) this._applyState(msg.stateOnAnswer); if (msg.profileOnAnswer) this.profile.add(msg.profileOnAnswer); - Snd.playVoice(msg.audio, { - onEnd: () => { - if (this.state !== 'playing') return; // 已被跳过 - if (msg.autoHangup && msg.sfxAfter) { - const sfx = Array.isArray(msg.sfxAfter) ? msg.sfxAfter[0] : msg.sfxAfter; - Snd.playVoice([sfx], { onEnd: () => this._proceedToNext() }); - } else if (msg.choices) { - this.state = 'choosing'; - SFX_UI.choiceReady(); - Haptic.confirm(); - this._startReminder(); - } else { - this._proceedToNext(); + this._skipRequested = false; + + // 播放通话内容 + await this._playVoiceAsync(msg.audio); + + // _onDoubleTap 在 'playing' 状态下会直接处理状态转换 + // 所以如果状态已不是 'playing',说明跳过已被处理,直接返回 + if (this.state !== 'playing') return; + + // 自动挂断(MSG-08 未知号码) + if (msg.autoHangup) { + if (msg.sfxAfter) { + const sfx = Array.isArray(msg.sfxAfter) ? msg.sfxAfter[0] : msg.sfxAfter; + await this._playVoiceAsync([sfx]); + if (this.state !== 'playing') return; + } + this._proceedToNext(); + return; + } + + // 自动回复(MSG-01/03/14 妈妈电话、外卖、妈妈第二次电话) + if (msg.autoReply) { + await this._playTypingSend(); + if (this.state !== 'playing') return; + + this.state = 'responding'; + await this._playVoiceAsync([msg.autoReply]); + + // 条件反应(MSG-14:根据 MOM_LINK 播放不同反应) + if (!this._skipRequested && msg.autoReact) { + const val = this.vars[msg.autoReact.condition]; + const reactUrl = val ? msg.autoReact['true'] : msg.autoReact['false']; + if (reactUrl) { + this.state = 'reacting'; + await sleep(500); + if (!this._skipRequested) { + await this._playVoiceAsync([reactUrl]); + } } } - }); + this._proceedToNext(); + return; + } + + // 通话后有选择(MSG-11 阿哲) + if (msg.choices) { + this._enterChoosing(msg.choices); + return; + } + + this._proceedToNext(); } _rejectCall(msg) { @@ -169,7 +244,7 @@ class GameEngine { } // ══════════════════════════════════════════ - // 消息播放 + // 消息播放(异步) // ══════════════════════════════════════════ async _playMessage(msg) { @@ -186,32 +261,27 @@ class GameEngine { return; } - // 文字消息(用 TTS 朗读内容) - if (msg.type === 'wechat_text' && msg.text && (!msg.audio || msg.audio.length === 0)) { - TTS.speak(msg.text).then(() => { - if (this.state === 'playing') this._afterPlayed(msg); - }); - return; + this._skipRequested = false; + + // 文字消息(TTS 朗读) + if (msg.type === 'wechat_text' && msg.text) { + await TTS.speak(msg.text); + } + // 语音消息 + else if (msg.audio?.length > 0) { + await this._playVoiceAsync(msg.audio); } - // 语音消息 - if (msg.audio && msg.audio.length > 0) { - Snd.playVoice(msg.audio, { - onEnd: () => { - if (this.state === 'playing') this._afterPlayed(msg); - } - }); - } else { - this._afterPlayed(msg); - } + if (this._skipRequested) return; + if (this.state !== 'playing') return; + + this._afterPlayed(msg); } - // ── 系统通知特殊处理 ── _playSystemNotification(msg) { Snd.playVoice([msg.systemAudio], { onEnd: () => { if (this.state !== 'playing') return; - // 根据画像播放对应版本 const audio = msg.audioByProfile?.[this.profile.label]; if (audio) { if (msg.stateOnPlay) this._applyState(msg.stateOnPlay); @@ -219,19 +289,18 @@ class GameEngine { setTimeout(() => { if (this.state !== 'playing') return; Snd.playVoice([audio], { - onEnd: () => this._proceedToNext() + onEnd: () => this._proceedToNext(), }); }, 800); } else { this._proceedToNext(); } - } + }, }); } - // ── 消息播放完成 ── _afterPlayed(msg) { - if (this.state !== 'playing') return; // 防止重复调用 + if (this.state !== 'playing') return; if (msg.isEnding) { this.triggerFinalEnding(); @@ -239,28 +308,34 @@ class GameEngine { } if (msg.choices) { - this.state = 'choosing'; - SFX_UI.choiceReady(); - Haptic.confirm(); - this._startReminder(); - } else { - this._proceedToNext(); + this._enterChoosing(msg.choices); + return; } + + this._proceedToNext(); } // ══════════════════════════════════════════ - // 选择回复 + // 选择系统(v2 多轮对话核心) // ══════════════════════════════════════════ - _choose(dir) { - const msg = this.currentMsg; - if (!msg?.choices) { + _enterChoosing(choices) { + this._currentChoices = choices; + this.state = 'choosing'; + SFX_UI.choiceReady(); + Haptic.confirm(); + this._startReminder(); + } + + async _handleChoice(dir) { + const choices = this._currentChoices; + if (!choices) { SFX_UI.error(); Haptic.error(); return; } - const choice = msg.choices[dir]; + const choice = choices[dir]; if (!choice) { SFX_UI.error(); Haptic.error(); @@ -268,8 +343,9 @@ class GameEngine { } this._clearReminder(); + this._skipRequested = false; - // 播放方向对应的情感音效 + // 1. 播放方向对应的情感音效 const sfxMap = { tap: 'tap', right: 'choiceWarm', @@ -280,13 +356,63 @@ class GameEngine { if (sfxMap[dir]) SFX_UI[sfxMap[dir]](); Haptic.select(); + // 2. 应用状态和画像 if (!choice.isSilence) { - this.msgState[msg.id] = 'replied'; + this.msgState[this.currentMsg.id] = 'replied'; } - if (choice.state) this._applyState(choice.state); if (choice.profile) this.profile.add(choice.profile); + // 3. 林夏回应 + this.state = 'responding'; + + if (choice.isSilence) { + // 沉默:短暂停顿 + await sleep(1500); + } else if (choice.linxia) { + // 打字 → 发送 → 林夏语音 + await this._playTypingSend(); + if (!this._skipRequested) { + await this._playVoiceAsync([choice.linxia]); + } + } + + if (this._skipRequested) { + // 跳过了林夏回应 → 如有 followUp 进入选择,否则跳到下一条 + if (choice.followUp?.choices) { + this._enterChoosing(choice.followUp.choices); + return; + } + this._proceedToNext(); + return; + } + + // 4. NPC 反应 + if (choice.npcReact?.length > 0) { + this.state = 'reacting'; + await sleep(400); + if (!this._skipRequested) { + await this._playVoiceAsync(choice.npcReact); + } + } + + if (this._skipRequested) { + if (choice.followUp?.choices) { + this._enterChoosing(choice.followUp.choices); + return; + } + this._proceedToNext(); + return; + } + + // 5. 如有后续对话 → 再次进入选择 + if (choice.followUp?.choices) { + await sleep(300); + this._enterChoosing(choice.followUp.choices); + return; + } + + // 6. 推进到下一条 this._proceedToNext(); } @@ -305,6 +431,7 @@ class GameEngine { async _proceedToNext() { const prev = this.currentMsg; this.currentMsg = null; + this._currentChoices = null; this.state = 'idle'; this._clearReminder(); @@ -318,7 +445,7 @@ class GameEngine { await sleep(3000); } - // 检查结局D:未读过多(14条消息处理后检查) + // 检查结局D:未读过多 const triggered = MESSAGES.length - this._queue.length; if (this.vars.UNREAD >= 8 && triggered >= 14) { this._triggerEnding('D'); @@ -332,7 +459,7 @@ class GameEngine { } // ══════════════════════════════════════════ - // 提醒定时器(玩家不操作时重复提示音) + // 提醒定时器 // ══════════════════════════════════════════ _startReminder() { @@ -379,23 +506,26 @@ class GameEngine { _onTap() { const s = this.state; - if (s === 'ringing') this._answerCall(this.currentMsg); - else if (s === 'notification') { + if (s === 'ringing') { + this._answerCall(this.currentMsg); + } else if (s === 'notification') { SFX_UI.tap(); Haptic.confirm(); this._playMessage(this.currentMsg); + } else if (s === 'choosing') { + this._handleChoice('tap'); } - else if (s === 'choosing') this._choose('tap'); } _onDoubleTap() { const s = this.state; + if (s === 'ringing') { - // 双击来电 = 拒接 this._rejectCall(this.currentMsg); + return; } - else if (s === 'notification') { - // 跳过消息 + + if (s === 'notification') { this._clearReminder(); SFX_UI.skip(); Haptic.confirm(); @@ -403,28 +533,36 @@ class GameEngine { if (msg?.profileOnSkip) this.profile.add(msg.profileOnSkip); else this.profile.add({ avoidance: 1 }); this._proceedToNext(); + return; } - else if (s === 'playing') { - // 跳过当前播放 - Snd.stopVoice(); - TTS.stop(); + + if (s === 'playing') { + this._skipRequested = true; + this._interruptVoice(); SFX_UI.skip(); Haptic.confirm(); const msg = this.currentMsg; - // 直接进入后续流程 if (msg?.isEnding) { this.triggerFinalEnding(); } else if (msg?.choices) { - this.state = 'choosing'; - SFX_UI.choiceReady(); - Haptic.confirm(); - this._startReminder(); + this._enterChoosing(msg.choices); } else { this._proceedToNext(); } + return; } - else if (s === 'choosing') { + + if (s === 'responding' || s === 'reacting') { + this._skipRequested = true; + this._interruptVoice(); + SFX_UI.skip(); + Haptic.confirm(); + return; + } + + if (s === 'choosing') { this._skipChoice(); + return; } } @@ -439,7 +577,6 @@ class GameEngine { if (s === 'notification') { if (dir === 'left') { - // 左滑忽略消息 this._clearReminder(); SFX_UI.skip(); Haptic.confirm(); @@ -450,7 +587,7 @@ class GameEngine { } if (s === 'choosing') { - this._choose(dir); + this._handleChoice(dir); return; } } @@ -497,9 +634,8 @@ class GameEngine { this.state = 'ending'; this._clearReminder(); - Snd.stopVoice(); + this._interruptVoice(); Snd.stopRingtone(); - TTS.stop(); Snd.stopBGM(3000); await sleep(2000); @@ -515,7 +651,6 @@ class GameEngine { if (fn) await fn(); } - // ── 结局A:明天再说 ── async _endingA() { Snd.startAmbience(`${AMB}/amb_kitchen_morning_v1.mp3`, { vol: 0.3 }); await sleep(2000); @@ -525,7 +660,6 @@ class GameEngine { this._showCaption('明天再说。'); } - // ── 结局B:寄出 ── async _endingB() { await sleep(2000); SFX_UI.ending(); @@ -533,7 +667,6 @@ class GameEngine { this._showCaption('寄出。'); } - // ── 结局C:今晚就这样 ── async _endingC() { await sleep(3000); SFX_UI.ending(); @@ -541,7 +674,6 @@ class GameEngine { this._showCaption('今晚就这样。'); } - // ── 结局D:早上好 ── async _endingD() { Haptic.system(); await sleep(2000); @@ -552,7 +684,6 @@ class GameEngine { this._showCaption('早上好。'); } - // ── 结局E(隐藏):好 ── async _endingE() { Haptic.message(); SFX_UI.notify(); diff --git a/game/js/story.js b/game/js/story.js index de18d07..814abaf 100644 --- a/game/js/story.js +++ b/game/js/story.js @@ -1,33 +1,48 @@ -// story.js — 消息数据(玩家驱动,无时间依赖) +// story.js — 消息数据 v2(多轮对话 + 林夏语音 + NPC反应) // // 选择方向的情感含义(全局一致): -// 单击 → 简单回应("嗯") -// 双击 → 不回复 / 跳过 -// 右滑 → 温暖 / 接受 / 开放 -// 左滑 → 冷淡 / 拒绝 / 疏远 -// 上滑 → 好奇 / 主动 / 追问 -// 下滑 → 沉默 / 退缩 +// 单击(tap) → 简单回应 +// 双击 → 不回复 / 跳过 +// 右滑(right) → 温暖 / 接受 / 开放 +// 左滑(left) → 冷淡 / 拒绝 / 疏远 +// 上滑(up) → 好奇 / 主动 / 追问 +// 下滑(down) → 沉默 / 退缩 +// +// 每个 choice 结构: +// linxia: 林夏回应的音频路径(null/缺省 = 沉默) +// npcReact: NPC反应音频数组(缺省 = 无反应) +// isSilence: 沉默选项标记 +// state: 剧情状态变更 +// profile: 画像分数变更 +// followUp: 可选的下一轮对话 { choices: { ... } } -const A = '../audio/mp3'; -const TDIR = `${A}/tts`; -const MUS = `${A}/music`; -const SFX = `${A}/sfx`; -const AMB = `${A}/ambience`; +const A = '../audio/mp3'; +const TDIR = `${A}/tts`; +const LINXIA = `${A}/linxia`; +const REACT = `${A}/react`; +const MUS = `${A}/music`; +const SFX = `${A}/sfx`; +const AMB = `${A}/ambience`; const MESSAGES = [ - // ── 01 · 妈妈 · 电话 ── + // ══════════════════════════════════════════ + // 01 · 妈妈 · 电话(教学:接/拒电话) + // ══════════════════════════════════════════ { id: 1, type: 'call', sender: 'mom', senderName: '妈妈', ringtone: `${MUS}/lv11_ring_mom_v1.mp3`, audio: [`${TDIR}/lv11_2103_mom_call_01_wav_v1.mp3`], + autoReply: `${LINXIA}/msg01_auto.mp3`, stateOnAnswer: { MOM_LINK: true }, profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 1 }, }, - // ── 02 · 小美 · 微信语音×3 ── + // ══════════════════════════════════════════ + // 02 · 小美 · 微信语音 ×3(教学:选择系统) + // ══════════════════════════════════════════ { id: 2, type: 'wechat_voice', sender: 'xiaomei', senderName: '小美', @@ -37,67 +52,190 @@ const MESSAGES = [ `${TDIR}/lv11_2115_xiaomei_voice_03_wav_v1.mp3`, ], choices: { - tap: { label: '好啊。', profile: { engagement: 1 } }, - right: { label: '好啊,明天不见不散。', profile: { engagement: 2 } }, - up: { label: '我不太想出门……', profile: { nostalgia: 1 } }, - left: { label: '你先去,我再说。', profile: { avoidance: 1 } }, - down: { label: '(沉默)', isSilence: true, profile: { avoidance: 2 } }, + right: { + linxia: `${LINXIA}/msg02_warm.mp3`, + npcReact: [`${REACT}/msg02_react_warm.mp3`], + profile: { engagement: 2 }, + }, + left: { + linxia: `${LINXIA}/msg02_cold.mp3`, + npcReact: [`${REACT}/msg02_react_cold.mp3`], + profile: { avoidance: 1 }, + }, + up: { + linxia: `${LINXIA}/msg02_curious.mp3`, + npcReact: [`${REACT}/msg02_react_curious.mp3`], + profile: { engagement: 1 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg02_r3_curious.mp3`, + npcReact: [`${REACT}/msg02_r3_curious.mp3`], + profile: { nostalgia: 1 }, + }, + }, + }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg02_react_silent.mp3`], + profile: { avoidance: 2 }, + }, + tap: { + linxia: `${LINXIA}/generic_hmm.mp3`, + npcReact: [`${REACT}/msg02_react_tap.mp3`], + }, }, }, - // ── 03 · 外卖小哥 · 电话 ── + // ══════════════════════════════════════════ + // 03 · 外卖小哥 · 电话(日常感) + // ══════════════════════════════════════════ { id: 3, type: 'call', sender: 'delivery', senderName: '外卖小哥', ringtone: `${MUS}/lv11_ring_delivery_v1.mp3`, audio: [`${TDIR}/lv11_2120_delivery_call_01_wav_v1.mp3`], + autoReply: `${LINXIA}/msg03_auto.mp3`, profileOnMiss: { avoidance: 1 }, profileOnAnswer: { engagement: 1 }, }, - // ── 04 · 阿哲 · 微信语音 ── + // ══════════════════════════════════════════ + // 04 · 阿哲 · 微信语音(前男友要东西) + // ══════════════════════════════════════════ { id: 4, type: 'wechat_voice', sender: 'azhe', senderName: '阿哲', audio: [`${TDIR}/lv11_2130_azhe_voice_01_wav_v1.mp3`], choices: { - tap: { label: '好,明天寄。', state: { HE_BACK: false }, profile: { engagement: 1 } }, - right: { label: '你那边……都好吗。', profile: { engagement: 2 } }, - up: { label: '你怎么不自己来拿。', profile: { engagement: 2 } }, - left: { label: '(不想理)', profile: { avoidance: 1 } }, - down: { label: '(什么都不说)', isSilence: true, state: { HE_BACK: null }, profile: { avoidance: 2 } }, + right: { + linxia: `${LINXIA}/msg04_warm.mp3`, + npcReact: [`${REACT}/msg04_react_warm.mp3`], + profile: { engagement: 2 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg04_r3w_warm.mp3`, + npcReact: [`${REACT}/msg04_r3w_sorry.mp3`], + }, + left: { + linxia: `${LINXIA}/msg04_r3w_cold.mp3`, + npcReact: [`${REACT}/msg04_r3w_good.mp3`], + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg04_r3w_night.mp3`], + }, + }, + }, + }, + left: { + linxia: `${LINXIA}/msg04_cold.mp3`, + npcReact: [`${REACT}/msg04_react_cold.mp3`], + state: { HE_BACK: false }, + profile: { avoidance: 1 }, + }, + up: { + linxia: `${LINXIA}/msg04_curious.mp3`, + npcReact: [`${REACT}/msg04_react_curious.mp3`], + profile: { engagement: 2 }, + followUp: { + choices: { + up: { + linxia: `${LINXIA}/msg04_r3c_curious.mp3`, + npcReact: [`${REACT}/msg04_r3c_nevermind.mp3`], + state: { HE_BACK: false }, + }, + right: { + linxia: `${LINXIA}/msg04_r3c_warm.mp3`, + }, + }, + }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg04_react_silent.mp3`], + state: { HE_BACK: null }, + profile: { avoidance: 2 }, + }, + tap: { + linxia: `${LINXIA}/generic_ok.mp3`, + npcReact: [`${REACT}/msg04_react_tap.mp3`], + state: { HE_BACK: false }, + profile: { engagement: 1 }, + }, }, }, - // ── 05 · HR王姐 · 微信语音 ── + // ══════════════════════════════════════════ + // 05 · HR王姐 · 微信语音 + // ══════════════════════════════════════════ { id: 5, type: 'wechat_voice', sender: 'hr', senderName: 'HR王姐', audio: [`${TDIR}/lv11_2145_hr_voice_01_wav_v1.mp3`], choices: { - tap: { label: '好的,我知道了。', profile: { engagement: 1 } }, - right: { label: '谢谢王姐。', profile: { engagement: 1 } }, - up: { label: '王姐,有什么建议吗?', profile: { engagement: 2 } }, - left: { label: '……谢谢你。', profile: { nostalgia: 1 } }, - down: { label: '(不回复)', isSilence: true, profile: { avoidance: 1 } }, + right: { + linxia: `${LINXIA}/msg05_warm.mp3`, + npcReact: [`${REACT}/msg05_react_warm.mp3`], + profile: { engagement: 1 }, + }, + left: { + linxia: `${LINXIA}/msg05_cold.mp3`, + }, + up: { + linxia: `${LINXIA}/msg05_curious.mp3`, + npcReact: [`${REACT}/msg05_react_curious.mp3`], + profile: { engagement: 2 }, + }, + down: { + isSilence: true, + profile: { avoidance: 1 }, + }, + tap: { + linxia: `${LINXIA}/generic_ok.mp3`, + }, }, }, - // ── 06 · 安安 · 微信语音 ── + // ══════════════════════════════════════════ + // 06 · 安安 · 微信语音(酒吧邀约) + // ══════════════════════════════════════════ { id: 6, type: 'wechat_voice', sender: 'anan', senderName: '安安', audio: [`${TDIR}/lv11_2200_anan_voice_01_wav_v1.mp3`], choices: { - tap: { label: '不了,你们玩好~', profile: {} }, - right: { label: '在哪儿啊?我……看看吧。', profile: { engagement: 1 } }, - up: { label: '你喝了多少了哈哈。', profile: { engagement: 1 } }, - left: { label: '我今晚不方便。', profile: { avoidance: 1 } }, - down: { label: '(不回复)', isSilence: true, profile: { avoidance: 2 } }, + right: { + linxia: `${LINXIA}/msg06_warm.mp3`, + npcReact: [`${REACT}/msg06_react_warm.mp3`], + profile: { engagement: 2 }, + }, + left: { + linxia: `${LINXIA}/msg06_cold.mp3`, + npcReact: [`${REACT}/msg06_react_cold.mp3`], + }, + up: { + linxia: `${LINXIA}/msg06_curious.mp3`, + npcReact: [`${REACT}/msg06_react_curious.mp3`], + profile: { engagement: 1 }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg06_react_silent.mp3`], + profile: { avoidance: 1 }, + }, + tap: { + linxia: `${LINXIA}/generic_hmm.mp3`, + npcReact: [`${REACT}/msg06_react_tap.mp3`], + }, }, }, - // ── 07 · 大学寝室群 · 5条语音 ── + // ══════════════════════════════════════════ + // 07 · 大学寝室群 · 5条语音 + // ══════════════════════════════════════════ { id: 7, type: 'wechat_voice', sender: 'dorm', senderName: '寝室群', @@ -109,15 +247,49 @@ const MESSAGES = [ `${TDIR}/lv11_2215_dorm_c_voice_01_wav_v1.mp3`, ], choices: { - tap: { label: '恭喜!', state: { GROUP_REPLY: true }, profile: { engagement: 1 } }, - right: { label: '哇,快讲讲!', state: { GROUP_REPLY: true }, profile: { engagement: 2 } }, - up: { label: '最近有点忙,你们聊~', profile: { avoidance: 1 } }, - left: { label: '(不想参与)', profile: { avoidance: 1 } }, - down: { label: '(沉默)', isSilence: true, profile: { avoidance: 2 } }, + right: { + linxia: `${LINXIA}/msg07_warm.mp3`, + npcReact: [`${REACT}/msg07_react_warm.mp3`], + state: { GROUP_REPLY: true }, + profile: { engagement: 2 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg07_r3_warm.mp3`, + profile: { engagement: 1 }, + }, + left: { + linxia: `${LINXIA}/msg07_r3_cold.mp3`, + npcReact: [`${REACT}/msg07_r3_cold.mp3`], + }, + }, + }, + }, + left: { + linxia: `${LINXIA}/msg07_cold.mp3`, + profile: { avoidance: 1 }, + }, + up: { + linxia: `${LINXIA}/msg07_curious.mp3`, + npcReact: [`${REACT}/msg07_react_curious.mp3`], + state: { GROUP_REPLY: true }, + profile: { engagement: 2 }, + }, + down: { + isSilence: true, + profile: { avoidance: 2 }, + }, + tap: { + linxia: `${LINXIA}/generic_hmm.mp3`, + npcReact: [`${REACT}/msg07_react_tap.mp3`], + state: { GROUP_REPLY: true }, + }, }, }, - // ── 08 · 未知号码 · 电话(悬疑)── + // ══════════════════════════════════════════ + // 08 · 未知号码 · 电话(悬疑钩子) + // ══════════════════════════════════════════ { id: 8, type: 'call', sender: 'unknown', senderName: '未知号码', @@ -129,7 +301,9 @@ const MESSAGES = [ profileOnAnswer: { nostalgia: 1 }, }, - // ── 09 · 小美(醉)· 4段语音 ── + // ══════════════════════════════════════════ + // 09 · 小美(醉)· 4段语音(秘密揭露) + // ══════════════════════════════════════════ { id: 9, type: 'wechat_voice', sender: 'xiaomei', senderName: '小美', @@ -141,29 +315,114 @@ const MESSAGES = [ ], bgm: `${MUS}/lv11_bgm_m2_suspense_v1.mp3`, choices: { - tap: { label: '我知道了。', profile: { avoidance: 1 } }, - right: { label: '小美,你还好吗。', profile: { engagement: 2 } }, - up: { label: '你……为什么现在才说。', profile: { engagement: 2 } }, - left: { label: '(不想理她)', profile: { avoidance: 1 } }, - down: { label: '(什么都不说)', isSilence: true, profile: { avoidance: 2, nostalgia: 1 } }, + right: { + linxia: `${LINXIA}/msg09_warm.mp3`, + npcReact: [`${REACT}/msg09_react_warm.mp3`], + profile: { engagement: 2 }, + }, + left: { + linxia: `${LINXIA}/msg09_cold.mp3`, + profile: { avoidance: 1 }, + }, + up: { + linxia: `${LINXIA}/msg09_curious.mp3`, + npcReact: [`${REACT}/msg09_react_curious.mp3`], + profile: { engagement: 2 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg09_r3_warm.mp3`, + npcReact: [`${REACT}/msg09_r3_warm.mp3`], + }, + left: { + linxia: `${LINXIA}/msg09_r3_cold.mp3`, + npcReact: [`${REACT}/msg09_r3_cold.mp3`], + }, + }, + }, + }, + down: { + isSilence: true, + profile: { avoidance: 2, nostalgia: 1 }, + }, + tap: { + linxia: `${LINXIA}/generic_hmm.mp3`, + npcReact: [`${REACT}/msg09_react_tap.mp3`], + profile: { nostalgia: 1 }, + }, }, }, - // ── 10 · 妈妈 · 文字消息 ── + // ══════════════════════════════════════════ + // 10 · 妈妈 · 文字消息(多层嵌套对话) + // ══════════════════════════════════════════ { id: 10, type: 'wechat_text', sender: 'mom', senderName: '妈妈', text: '睡了吗?', choices: { - tap: { label: '嗯。', state: { MOM_LINK: true } }, - right: { label: '没有,怎么了。', state: { MOM_LINK: true }, profile: { engagement: 1 } }, - up: { label: '妈,我想打电话。', state: { MOM_LINK: true }, profile: { engagement: 2 } }, - left: { label: '嗯,要睡了。', state: { MOM_LINK: true }, profile: { avoidance: 1 } }, - down: { label: '(不回复)', isSilence: true, profile: { avoidance: 2 } }, + right: { + linxia: `${LINXIA}/msg10_warm.mp3`, + npcReact: [`${REACT}/msg10_react_warm.mp3`], + state: { MOM_LINK: true }, + profile: { engagement: 2 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg10_rw_warm.mp3`, + npcReact: [`${REACT}/msg10_rw_warm.mp3`], + }, + up: { + linxia: `${LINXIA}/msg10_rw_curious.mp3`, + npcReact: [`${REACT}/msg10_rw_curious.mp3`], + }, + }, + }, + }, + left: { + linxia: `${LINXIA}/msg10_cold.mp3`, + npcReact: [`${REACT}/msg10_react_cold.mp3`], + state: { MOM_LINK: true }, + profile: { avoidance: 1 }, + }, + up: { + linxia: `${LINXIA}/msg10_curious.mp3`, + npcReact: [`${REACT}/msg10_react_curious.mp3`], + state: { MOM_LINK: true }, + profile: { engagement: 3 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg10_call_warm.mp3`, + npcReact: [`${REACT}/msg10_call_warm.mp3`], + }, + up: { + linxia: `${LINXIA}/msg10_call_curious.mp3`, + npcReact: [`${REACT}/msg10_call_curious.mp3`], + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg10_call_silent.mp3`], + }, + }, + }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg10_react_silent.mp3`], + profile: { avoidance: 2 }, + }, + tap: { + linxia: `${LINXIA}/generic_hmm.mp3`, + npcReact: [`${REACT}/msg10_react_tap.mp3`], + state: { MOM_LINK: true }, + }, }, }, - // ── 11 · 阿哲 · 电话(关键分支)── + // ══════════════════════════════════════════ + // 11 · 阿哲 · 电话(关键分支:来不来) + // ══════════════════════════════════════════ { id: 11, type: 'call', sender: 'azhe', senderName: '阿哲', @@ -172,28 +431,88 @@ const MESSAGES = [ profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 1 }, choices: { - tap: { label: '不方便,明天吧。', state: { HE_BACK: false } }, - right: { label: '可以,但只有5分钟。', state: { HE_BACK: true } }, - left: { label: '(挂断)', state: { HE_BACK: false } }, - down: { label: '(沉默挂断)', isSilence: true, state: { HE_BACK: false } }, + right: { + linxia: `${LINXIA}/msg11_warm.mp3`, + npcReact: [`${REACT}/msg11_react_warm.mp3`], + state: { HE_BACK: true }, + }, + left: { + linxia: `${LINXIA}/msg11_cold.mp3`, + npcReact: [`${REACT}/msg11_react_cold.mp3`], + state: { HE_BACK: false }, + }, + up: { + linxia: `${LINXIA}/msg11_curious.mp3`, + npcReact: [`${REACT}/msg11_react_curious.mp3`], + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg11_warm.mp3`, + npcReact: [`${REACT}/msg11_react_warm.mp3`], + state: { HE_BACK: true }, + }, + left: { + linxia: `${LINXIA}/msg11_cold.mp3`, + npcReact: [`${REACT}/msg11_react_cold.mp3`], + state: { HE_BACK: false }, + }, + }, + }, + }, + down: { + isSilence: true, + state: { HE_BACK: false }, + }, + tap: { + linxia: `${LINXIA}/msg11_tap.mp3`, + npcReact: [`${REACT}/msg11_react_tap.mp3`], + state: { HE_BACK: false }, + }, }, }, - // ── 12 · 周南 · 微信语音 ── + // ══════════════════════════════════════════ + // 12 · 周南 · 微信语音(老同学) + // ══════════════════════════════════════════ { id: 12, type: 'wechat_voice', sender: 'zhounan', senderName: '周南', audio: [`${TDIR}/lv11_2330_zhounan_voice_01_wav_v1.mp3`], choices: { - tap: { label: '嗯……你好。', state: { ZHOUNAN_DEPTH: 1 }, profile: { nostalgia: 1 } }, - right: { label: '周南!你还记得我啊!', state: { ZHOUNAN_DEPTH: 1 }, profile: { engagement: 2, nostalgia: 1 } }, - up: { label: '你是……初三那个……?', state: { ZHOUNAN_DEPTH: 1 }, profile: { nostalgia: 2 } }, - left: { label: '(冷淡)', profile: { avoidance: 1 } }, - down: { label: '(不回复)', isSilence: true }, + right: { + linxia: `${LINXIA}/msg12_warm.mp3`, + npcReact: [`${REACT}/msg12_react_warm.mp3`], + state: { ZHOUNAN_DEPTH: 1 }, + profile: { engagement: 2, nostalgia: 1 }, + }, + left: { + linxia: `${LINXIA}/msg12_cold.mp3`, + npcReact: [`${REACT}/msg12_react_cold.mp3`], + state: { ZHOUNAN_DEPTH: 1 }, + profile: { nostalgia: 1 }, + }, + up: { + linxia: `${LINXIA}/msg12_curious.mp3`, + npcReact: [`${REACT}/msg12_react_curious.mp3`], + state: { ZHOUNAN_DEPTH: 1 }, + profile: { nostalgia: 2 }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg12_react_silent.mp3`], + profile: { nostalgia: 1 }, + }, + tap: { + linxia: `${LINXIA}/msg12_tap.mp3`, + npcReact: [`${REACT}/msg12_react_tap.mp3`], + state: { ZHOUNAN_DEPTH: 1 }, + }, }, }, - // ── 13 · 周南 · 4条长语音 ── + // ══════════════════════════════════════════ + // 13 · 周南 · 4条长语音(他的十年) + // ══════════════════════════════════════════ { id: 13, type: 'wechat_voice', sender: 'zhounan', senderName: '周南', @@ -205,26 +524,76 @@ const MESSAGES = [ ], bgm: `${MUS}/lv11_bgm_m3_zhounan_v1.mp3`, choices: { - tap: { label: '谢谢你告诉我。', state: { ZHOUNAN_DEPTH_ADD: 1 } }, - right: { label: '你在北京还好吗?', state: { ZHOUNAN_DEPTH_ADD: 1 }, profile: { engagement: 2 } }, - up: { label: '……生日快乐。', state: { ZHOUNAN_DEPTH_ADD: 1, ZHOUNAN_SHARE: true }, profile: { nostalgia: 2, engagement: 1 } }, - left: { label: '你讲了好多。', state: { ZHOUNAN_DEPTH_ADD: 1 }, profile: { engagement: 2, nostalgia: 1 } }, - down: { label: '(沉默)', isSilence: true, profile: { nostalgia: 1, avoidance: 1 } }, + right: { + linxia: `${LINXIA}/msg13_warm.mp3`, + npcReact: [`${REACT}/msg13_react_warm.mp3`], + state: { ZHOUNAN_DEPTH_ADD: 1 }, + profile: { engagement: 2 }, + followUp: { + choices: { + right: { + linxia: `${LINXIA}/msg13_r3_warm.mp3`, + npcReact: [`${REACT}/msg13_r3_warm.mp3`], + state: { ZHOUNAN_SHARE: true }, + }, + left: { + linxia: `${LINXIA}/msg13_r3_cold.mp3`, + npcReact: [`${REACT}/msg13_r3_cold.mp3`], + }, + up: { + linxia: `${LINXIA}/msg13_r3_curious.mp3`, + npcReact: [`${REACT}/msg13_r3_curious.mp3`], + }, + }, + }, + }, + left: { + linxia: `${LINXIA}/msg13_cold.mp3`, + npcReact: [`${REACT}/msg13_react_cold.mp3`], + state: { ZHOUNAN_DEPTH_ADD: 1 }, + profile: { nostalgia: 1 }, + }, + up: { + linxia: `${LINXIA}/msg13_curious.mp3`, + npcReact: [`${REACT}/msg13_react_curious.mp3`], + state: { ZHOUNAN_DEPTH_ADD: 1, ZHOUNAN_SHARE: true }, + profile: { nostalgia: 2, engagement: 1 }, + }, + down: { + isSilence: true, + npcReact: [`${REACT}/msg13_react_silent.mp3`], + profile: { avoidance: 1 }, + }, + tap: { + linxia: `${LINXIA}/msg13_tap.mp3`, + npcReact: [`${REACT}/msg13_react_tap.mp3`], + state: { ZHOUNAN_DEPTH_ADD: 1 }, + }, }, }, - // ── 14 · 妈妈 · 第二次电话 ── + // ══════════════════════════════════════════ + // 14 · 妈妈 · 第二次电话(自动回应 + 条件反应) + // ══════════════════════════════════════════ { id: 14, type: 'call', sender: 'mom', senderName: '妈妈', ringtone: `${MUS}/lv11_ring_mom_v1.mp3`, audio: [`${TDIR}/lv11_0015_mom_call_01_wav_v1.mp3`], + autoReply: `${LINXIA}/msg14_auto.mp3`, + autoReact: { + condition: 'MOM_LINK', + true: `${REACT}/msg14_react_linked.mp3`, + false: `${REACT}/msg14_react_unlinked.mp3`, + }, stateOnAnswer: { MOM_LINK: true }, profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 2 }, }, - // ── 15 · 时光胶囊 · 系统通知 ── + // ══════════════════════════════════════════ + // 15 · 时光胶囊 · 系统通知 + // ══════════════════════════════════════════ { id: 15, type: 'system_notification', sender: 'self', senderName: '时光胶囊', @@ -239,7 +608,9 @@ const MESSAGES = [ profileOnSkip: { avoidance: 2 }, }, - // ── 16 · 妈妈 · 长语音(结局前奏)── + // ══════════════════════════════════════════ + // 16 · 妈妈 · 长语音(结局前奏) + // ══════════════════════════════════════════ { id: 16, type: 'wechat_voice', sender: 'mom', senderName: '妈妈', diff --git a/gdd/redesign_v2.md b/gdd/redesign_v2.md new file mode 100644 index 0000000..d2cadd9 --- /dev/null +++ b/gdd/redesign_v2.md @@ -0,0 +1,583 @@ +# 《林夏》重构设计文档 v2 + +## 一、问题诊断 + +### 问题1:主角是哑巴 +玩家听完 NPC 语音 → 滑动选择 → 什么声音都没有 → 直接跳到下一个人。 +林夏在整个游戏中没有"说过一句话"。玩家的操作没有任何可听到的结果。 +这不是互动,这是单方面的被动收听。 + +### 问题2:选择没有区别 +温暖/冷淡/追问/沉默 → 改变了一个隐形的分数 → 同样的流程 → 下一条消息。 +玩家听不到、感受不到任何区别。四个方向滑动在体验上完全一样。 +选择只有在产生可感知的不同后果时才有意义。 + +### 根本原因 +游戏缺少**对话的第二段**。真正的对话是: + +``` +她说 → 我答 → 她对我的回答做出反应 +``` + +当前的游戏只有第一段。缺少"我答"和"她的反应"。 + + +## 二、核心设计理念 + +### 一句话总结 +**每次选择都必须产生玩家能听到的、不同的后果。** + +### 三个原则 + +1. **林夏必须有声音** + 玩家做出选择后,林夏要说话(或沉默——但沉默本身也要被对方感知到)。 + +2. **NPC 必须对林夏的回应做出反应** + 温暖的回应 → NPC 继续说,说得更多更深。 + 冷淡的回应 → NPC 尴尬地收住,对话很快结束。 + 追问 → NPC 透露新信息(你在其他路径听不到)。 + 沉默 → NPC 感受到沉默,可能追问、可能放弃。 + +3. **不同的路径 = 不同的内容** + 玩家选温暖和选冷淡,应该听到完全不同的音频。 + 不是"播同一段然后改个隐藏分数",而是真正不同的声音。 + + +## 三、交互模型 + +### 手势(保持 6 种,不变) +| 手势 | 全局含义 | +|--------|------------| +| 单击 | 接受/回应/继续 | +| 双击 | 跳过/忽略 | +| 右滑 | 温暖/靠近 | +| 左滑 | 冷淡/推开 | +| 上滑 | 好奇/追问 | +| 下滑 | 沉默/退缩 | + +### 新的对话流程 + +``` + ┌─────────────────────────────────────────────────┐ + │ [提示音 + 震动] │ + │ 消息到达 │ + │ → 单击播放 / 双击跳过 │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ NPC 语音播放(Round 1) │ + │ "你被甩了我都听说了,明天陪你喝酒" │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ [选择提示音] │ + │ 等待玩家操作 │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ [打字音效 ~1秒] │ + │ [发送音效] │ + │ 林夏的语音播放 │ + │ 右滑 → "好啊,明天不见不散。" │ + │ 左滑 → "再说吧。" │ + │ 上滑 → "你怎么知道的啊?" │ + │ 下滑 → [一秒沉默] │ + │ 单击 → "嗯。" │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ NPC 反应播放(Round 2)—— 因选择而异 │ + │ │ + │ 右滑路径: "行!那我叫上安安!老地方见!" │ + │ 左滑路径: "……好吧,你休息。" │ + │ 上滑路径: "还不是阿哲那个大嘴巴告诉我的……" │ + │ 下滑路径: "??你还活着吗哈哈。算了不打扰你了" │ + │ 单击路径: "嗯是什么意思啊哈哈!行吧你休息" │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ [可选] Round 3: 继续对话(仅追问路径) │ + │ "阿哲到处跟人说你们分手的事……" │ + │ → 再次选择 → 林夏回应 → 对话结束 │ + └──────────┬──────────────────────────────────────┘ + ▼ + ┌─────────────────────────────────────────────────┐ + │ [过渡音效] → 等待 → 下一条消息 │ + └─────────────────────────────────────────────────┘ +``` + +### 关键区别 + +| 方面 | 旧版 | 新版 | +|-----------|------------------------|-------------------------------| +| 林夏的声音 | 无 | 每次选择后都有语音 | +| NPC 反应 | 无,直接跳到下一条 | 根据选择播放不同的反应语音 | +| 选择的感受 | 四个方向一样 | 四个方向各自导向不同的音频内容 | +| 对话长度 | 固定(听完就完) | 温暖/追问路径更长,冷淡/沉默更短 | +| 信息量 | 固定 | 追问路径可以获得独家信息 | + + +## 四、林夏的声音设计 + +### 设计原则 +林夏是一个 24 岁的女生,今晚经历了分手+失业+家庭压力。 +她的声音应该是:**疲惫但清醒、克制但有温度、年轻但不幼稚。** + +### 语音类型 + +#### A. 通用短句(可跨消息复用) +| 编号 | 情感方向 | 台词 | 时长 | 复用场景 | +|------|---------|---------------|--------|-----------------| +| G01 | 单击 | "嗯。" | 0.5s | 任何简单回应 | +| G02 | 单击 | "好。" | 0.5s | 同意类 | +| G03 | 温暖 | "嗯~好啊。" | 1s | 接受邀约 | +| G04 | 温暖 | [轻笑] | 1s | 轻松回应 | +| G05 | 冷淡 | "知道了。" | 0.8s | 敷衍回应 | +| G06 | 冷淡 | "行吧。" | 0.6s | 不情愿同意 | +| G07 | 追问 | "嗯?" | 0.5s | 想听更多 | +| G08 | 追问 | "然后呢?" | 0.8s | 催促继续 | +| G09 | 沉默 | [轻呼吸] | 1.5s | 说不出口 | +| G10 | 沉默 | [沉默+叹气] | 2s | 无力回应 | + +#### B. 场景专属台词(每条消息独有) +每条有选择的消息需要 3-5 条林夏专属台词,详见第五节。 + +### 录制要求 +- 同一个声优录制所有林夏的台词 +- 录音环境安静(模拟深夜卧室发语音的感觉) +- 情绪基调:不是哭,不是崩溃,是一种**撑着的疲惫** +- 总量估算:通用 10 条 + 专属 ~40 条 = **约 50 条短语音,总时长约 2 分钟** + + +## 五、16 条消息重新设计 + +> 每条消息标注:NPC 说什么(Round 1)→ 玩家选择 → 林夏说什么 → NPC 怎么反应(Round 2) +> 追问路径可能延伸到 Round 3。 + +--- + +### MSG-01 妈妈 · 电话 + +**类型**:来电 +**Round 1**(接听后):妈妈说"今晚煮了你爱吃的排骨,你什么时候回来呀。" +**接听后无选择**——这是第一通电话,让玩家熟悉"接电话 → 听完"的流程。 +通话结束后,林夏轻轻说一句"嗯……好。"(自动播放,不需选择) + +**不接** → 直接跳到下一条。妈妈的来电未接。 + +**状态**:接听 → MOM_LINK = true + +--- + +### MSG-02 小美 · 微信语音 ×3(教学关) + +**Round 1**:小美连发三条语音: +- "林夏!你还活着吗!" +- "你被甩了我都听说了——别难过啊" +- "明天陪你喝酒,去不去" + +**选择 + Round 2**: + +| 方向 | 林夏说 | 小美反应 | 效果 | +|------|--------------------------|-----------------------------------------------|--------------| +| 右滑 | "好啊,明天不见不散。" | "行!那我叫上安安!老地方见!别放鸽子啊!" | engagement+2 | +| 左滑 | "再说吧。" | "……好吧。你要是改主意了随时说。" | avoidance+1 | +| 上滑 | "你怎么知道的啊?" | "还不是阿哲自己发朋友圈!你不知道吗?他发了又秒删……" | engagement+1, **独家信息** | +| 下滑 | [沉默 2s] | "??你还活着吗哈哈。好了不烦你了,有事找我。" | avoidance+2 | +| 单击 | "嗯。" | "嗯是什么意思啊哈哈!行吧你休息。" | engagement+0 | + +**追问路径 Round 3**(上滑后): +小美说完"他发了又秒删" → 选择提示音 +- 右滑 → 林夏:"他……发了什么?" → 小美:"就发了一张你们以前的照片,配了一句'值得'。两分钟就删了" → **独家信息** +- 其他 → 对话结束 + +--- + +### MSG-03 外卖小哥 · 电话 + +**类型**:来电 +**Round 1**:外卖小哥:"你好,你的麻辣烫到了,在楼下。" +**接听后自动**:林夏说"好,我下来拿。"(无选择) +**不接** → 漏接。 + +*设计意图*:日常感。即使世界在崩塌,外卖还是要拿。 + +--- + +### MSG-04 阿哲 · 微信语音 + +**Round 1**:阿哲说"那个……我的 AirPods 好像落你那了,方便帮我寄一下吗?" + +| 方向 | 林夏说 | 阿哲反应 | 效果 | +|------|--------------------------|---------------------------------------------|-------------------| +| 右滑 | "你那边……还好吗。" | "我……挺好的。" [停顿] "你呢?" | engagement+2 | +| 左滑 | "知道了。明天寄。" | "嗯……谢了。" | HE_BACK=false, avoidance+1 | +| 上滑 | "你怎么不自己来拿。" | "我……不太方便过去。" [欲言又止] | engagement+2 | +| 下滑 | [沉默 3s] | [等了5秒] "那……我晚点再说吧。" → (10秒后阿哲撤回消息) | HE_BACK=null, avoidance+2 | +| 单击 | "好。" | "谢谢。" | HE_BACK=false, engagement+1 | + +**右滑 Round 3**(阿哲问"你呢?"后): +- 右滑 → 林夏:"不太好。" → 阿哲:"……对不起。" [然后挂断般的沉默] +- 左滑 → 林夏:"挺好的。" → 阿哲:"那就好。" [敷衍,对话结束] +- 下滑 → [沉默] → 阿哲:"……那,晚安。" + +**上滑 Round 3**(阿哲说"不太方便"后): +- 上滑 → 林夏:"为什么。" → 阿哲:"我……" [长停顿] "没什么。你寄吧。" → HE_BACK=false +- 右滑 → 林夏:"算了,不问了。" → 对话结束 + +--- + +### MSG-05 HR王姐 · 微信语音 + +**Round 1**:王姐说"小夏,离职流程周三前办完就行。别有压力。" + +| 方向 | 林夏说 | 王姐反应 | 效果 | +|------|---------------------------|--------------------------------------------------|--------------| +| 右滑 | "谢谢王姐。" | "别客气。你还年轻,出去看看也好。这行业你知道的。" | engagement+1 | +| 左滑 | "好的,知道了。" | [无反应,对话结束] | avoidance+0 | +| 上滑 | "王姐……有什么建议吗?" | "你想听真话?别在这行耗了。你有本事,去试试别的。" → **独家** | engagement+2 | +| 下滑 | [沉默] | [无反应,对话结束] | avoidance+1 | +| 单击 | "好。" | [无反应,对话结束] | engagement+0 | + +--- + +### MSG-06 安安 · 微信语音 + +**Round 1**:安安在酒吧里大喊:"我在 Helens!快来!今天 happy hour!"(背景很吵) + +| 方向 | 林夏说 | 安安反应 | 效果 | +|------|---------------------------|------------------------------------------------|--------------| +| 右滑 | "在哪儿啊?我看看吧。" | "对面那个!我给你留了位置!赶紧赶紧!" | engagement+2 | +| 左滑 | "不了,你们玩好。" | "好吧!那我给你寄一杯!" [大笑] | avoidance+0 | +| 上滑 | "你喝了多少了?" | "才两杯!我清醒得很!……好吧三杯。快来!" | engagement+1 | +| 下滑 | [沉默] | "喂?信号不好吗?好吧以后再约!" | avoidance+1 | +| 单击 | "嗯。" | "嗯是来还是不来啊!算了回头聊!" | engagement+0 | + +--- + +### MSG-07 大学寝室群 · 5条语音 + +**Round 1**:群消息连续播放: +- 室友A:"我升主管了!!!" +- 室友A:"请你们吃饭!!" +- 室友B:"我的天!恭喜!!" +- 室友B:"必须喝一个!" +- 室友C:"@林夏 你也来吧" + +| 方向 | 林夏说 | 群里反应 | 效果 | +|------|--------------------------|------------------------------------------------|-------------------| +| 右滑 | "恭喜恭喜!太厉害了!" | 室友A:"哈哈谢谢!我请客!你最近怎么样啊?" | GROUP_REPLY, engagement+2 | +| 左滑 | "恭喜。" | [群里继续聊别的,没人注意到你的冷淡] | avoidance+1 | +| 上滑 | "怎么升的啊?讲讲!" | 室友A:"说来话长!我们老板走了我就顶上了哈哈" → **信息** | GROUP_REPLY, engagement+2 | +| 下滑 | [沉默,不回群] | [群里继续热闹,但渐渐没人@你了] | avoidance+2 | +| 单击 | [发一个表情] | 室友B:"林夏发表情了哈哈" | GROUP_REPLY, engagement+0 | + +**右滑 Round 3**(室友A 问"你最近怎么样"后): +- 右滑 → 林夏:"挺好的~你请客我去!" → 室友们欢呼 +- 左滑 → 林夏:"还行。" → 室友B:"你是不是有事啊?没事可以跟我们说" → **关心** +- 下滑 → [沉默] → 对话自然结束 + +--- + +### MSG-08 未知号码 · 电话(悬疑) + +**类型**:来电 +**接听后**:只有呼吸声,6 秒后自动挂断。然后心跳音效。 +**无选择**——悬疑钩子。 +**不接** → 未接来电,号码显示"未知"。 + +--- + +### MSG-09 小美(醉)· 语音 + +**BGM 切换为悬疑风格** + +**Round 1**(4 段醉酒语音,断断续续): +- "林夏……我跟你说个事" +- "阿哲那个人……大三的时候追过我" +- "但我没答应他……因为我知道你喜欢他" +- "我到现在都没跟你说……对不起" + +| 方向 | 林夏说 | 小美反应 | 效果 | +|------|----------------------------|-----------------------------------------------|-------------------| +| 右滑 | "小美……你没事吧。" | "我没事……我就是喝多了想告诉你……你别怪我。" [哭] | engagement+2 | +| 左滑 | "我知道了。" | [沉默 3 秒] → 小美撤回了刚才的语音 | avoidance+1 | +| 上滑 | "你……为什么现在才说。" | "因为你今天……我怕你以后恨我……" → **深入** | engagement+2 | +| 下滑 | [沉默] | [10 秒后] 小美撤回了刚才的语音 | avoidance+2, nostalgia+1 | +| 单击 | "嗯。" | "你不生气吗?" [等 3 秒] "算了……当我没说" | nostalgia+1 | + +**上滑 Round 3**(小美说"怕你恨我"后): +- 右滑 → 林夏:"我不恨你。" → 小美 [大哭]:"谢谢你……谢谢你林夏" +- 左滑 → 林夏:"我得想想。" → 小美:"好……对不起" → 对话结束 +- 下滑 → [沉默] → 小美:"你还在吗?" → [长沉默] → 小美撤回 + +--- + +### MSG-10 妈妈 · 文字消息 + +**Round 1**:消息到达提示音。短暂停顿后,TTS 朗读三个字——"睡了吗?" + +| 方向 | 林夏说 | 妈妈反应 | 效果 | +|------|----------------------------|----------------------------------------------|--------------------| +| 右滑 | "没有,怎么了?" | 妈妈发语音:"没事,就是想起来问一下。今天怎么样?" | MOM_LINK, engagement+2 | +| 左滑 | "嗯,要睡了。" | 妈妈发文字:"好,早点睡。"(TTS) | MOM_LINK, avoidance+1 | +| 上滑 | "妈……你能打电话给我吗?" | [电话铃响] 妈妈打来了!→ 接听后妈妈说"怎么了夏夏?" | MOM_LINK, engagement+3 | +| 下滑 | [不回复] | [30 秒后] 妈妈发文字:"看到了回一下。"(TTS) | avoidance+2 | +| 单击 | "嗯。" | 妈妈发文字:"嗯就是睡了?晚安。"(TTS) | MOM_LINK | + +**上滑路径**(妈妈打来电话后): +这是一通真正的电话。妈妈说"怎么了夏夏?你声音不对。" +- 右滑 → 林夏:"没什么……就是有点累。" → 妈妈:"累了就回家歇几天。妈煮了排骨。" +- 上滑 → 林夏:"妈,我辞职了。" → 妈妈 [沉默 2 秒]:"辞了就辞了。回来再说。" +- 下滑 → [沉默,只有呼吸] → 妈妈:"不想说就不说。妈在这呢。" + +**右滑路径**(妈妈问"今天怎么样"后): +- 右滑 → 林夏:"还好。" → 妈妈:"那就好。别太晚了。" +- 上滑 → 林夏:"不太好。" → 妈妈:"怎么了?" → [可继续展开] + +--- + +### MSG-11 阿哲 · 电话(关键分支) + +**类型**:来电 +**Round 1**(接听后):阿哲说"那个……AirPods 的事,我能过去拿吗?就 5 分钟。" + +| 方向 | 林夏说 | 阿哲反应 | 效果 | +|------|-------------------------|------------------------------------------|-----------------| +| 右滑 | "可以。但只有 5 分钟。" | "好,我马上过来。谢谢。" → [挂断] | HE_BACK=true | +| 左滑 | "不方便。我明天寄给你。" | "好吧……那谢了。" → [挂断] | HE_BACK=false | +| 上滑 | "为什么非要今天来?" | "我……明天要出差。" [停顿] "就 5 分钟。" | 继续选择 | +| 下滑 | [沉默 → 挂断音] | [电话断了] | HE_BACK=false | +| 单击 | "不了。" | "好。" → [挂断] | HE_BACK=false | + +**如果 HE_BACK=true** → 消息 12 之前插入门铃声 + 敲门声 + +--- + +### MSG-12 周南 · 微信语音 + +**Round 1**:周南说"林夏,是我,周南。初三坐你后桌那个。好久没联系了,今天突然想到你。" + +| 方向 | 林夏说 | 周南反应 | 效果 | +|------|----------------------------|--------------------------------------------------|---------------------| +| 右滑 | "周南!你还记得我啊!" | "当然记得。你是我们班最安静的那个。" [轻笑] | ZHOUNAN_DEPTH+1, engagement+2 | +| 左滑 | "嗯,你好。" | "嗯……好久不见。你现在在哪呢?" | ZHOUNAN_DEPTH+1, nostalgia+1 | +| 上滑 | "你怎么有我微信的?" | "你忘了吗?毕业那天你加的我。不过你可能早忘了哈哈。" | ZHOUNAN_DEPTH+1, nostalgia+2 | +| 下滑 | [沉默] | "你是不是不记得我了?没关系的。就是突然想说声嗨。" | nostalgia+1 | +| 单击 | "嗯……好久不见。" | "是啊,好久不见。" | ZHOUNAN_DEPTH+1 | + +--- + +### MSG-13 周南 · 长语音 ×4 + +**BGM 切换为周南主题** + +**Round 1**(四段语音,讲他的十年): +- "高考那年我没考好,去复读了" +- "后来上了个二本,学了个不喜欢的专业" +- "毕业回了老家,在我爸店里帮忙" +- "今年刚来北京出差……今天是我生日" + +| 方向 | 林夏说 | 周南反应 | 效果 | +|------|---------------------------|--------------------------------------------------|----------------------| +| 右滑 | "一个人在北京,还好吗?" | "还行吧。习惯了。你呢?你在北京过得好吗?" | ZHOUNAN_DEPTH+1, engagement+2 | +| 左滑 | "你讲了好多。" | "是吗?不好意思,话太多了。" [轻笑] | ZHOUNAN_DEPTH+1, nostalgia+1 | +| 上滑 | "……生日快乐。" | [沉默 2 秒] "谢谢你。你是今天第一个跟我说的。" | ZHOUNAN_DEPTH+1, ZHOUNAN_SHARE, nostalgia+2 | +| 下滑 | [沉默] | "不好意思突然说这么多……那,晚安。" | avoidance+1 | +| 单击 | "嗯,我都听到了。" | "嗯。谢谢你听我说。" | ZHOUNAN_DEPTH+1 | + +**右滑 Round 3**(周南问"你在北京过得好吗"后): +- 右滑 → 林夏:"不太好。今天也是我的坏日子。" → 周南:"那我们算是难兄难妹了。" [轻笑] → **ZHOUNAN_SHARE** +- 左滑 → 林夏:"还行吧。" → 周南:"那就好。" +- 上滑 → 林夏:"你为什么突然联系我?" → 周南:"因为……今天生日嘛,想起以前的事。想起你。" + +--- + +### MSG-14 妈妈 · 第二次电话 + +**类型**:来电 +**Round 1**:妈妈说"夏夏,你不接电话我有点担心。你还好吗?" + +接听后 → 林夏自动说"嗯,我没事。" +妈妈:"真的没事?" → 短暂停顿 +- 如果之前 MOM_LINK=true:"好。那早点睡。妈爱你。" +- 如果之前 MOM_LINK=false:"你今天怎么都不理妈妈?出什么事了吗?" → 玩家可选择 + +**状态**:接听 → MOM_LINK=true + +--- + +### MSG-15 时光胶囊 · 系统通知 + +**特殊消息**:系统提示音 + "3 年前的今晚,你给自己录过一段备忘录。" +- 单击播放 → 根据玩家画像播放不同版本 → SELF_RECORD=true +- 双击跳过 → avoidance+2 + +*无选择交互。这条消息是独白时刻。* + +--- + +### MSG-16 妈妈 · 长语音(结局前奏) + +**条件**:MOM_LINK=true 时播放 +**8 段语音**:妈妈讲她 24 岁那年的故事——1995 年在广州被辞退、被甩、外婆每天打电话问"吃了吗"。 + +*无选择交互。这是全游戏最长的独白,让玩家安静听完。* + +播放结束 → 触发结局判定。 + + +## 六、情感反馈系统 + +### 环境音随情感变化 + +游戏底层跑着环境音轨。根据玩家的累计 profile 分数,环境音逐渐变化: + +| 倾向 | 环境音特征 | 过渡时机 | +|------------|-------------------------------|-----------------| +| engagement | 柔和雨声、远处隐约的音乐、温暖感 | 每 3 条消息检查 | +| avoidance | 安静、时钟滴答、偶尔远处车声 | 每 3 条消息检查 | +| nostalgia | 梦幻混响、远处风铃、slightly reverb | 每 3 条消息检查 | + +### 对话节奏随情感变化 + +| 玩家倾向 | 消息间隔 | 说明 | +|-------------|-----------|--------------------------| +| 多次温暖/追问 | 较短(1.5s)| 夜晚感觉充实,消息来得快 | +| 多次冷淡/沉默 | 较长(3-4s)| 空虚感,等待的时间更长 | + + +## 七、结局系统(不变,但感受更强) + +5 个结局的触发条件保持不变。但因为整个游戏中玩家做了大量有感知的选择,到达结局时会更有"是我导致了这个结局"的感觉。 + +| 结局 | 条件 | 描述 | +|-----|---------------------------|-------------| +| A | MOM_LINK + SELF_RECORD | 明天再说 | +| B | HE_BACK=false + ZHOUNAN≥1 | 寄出 | +| C | 全部已读 + 回复≤2 | 今晚就这样 | +| D | UNREAD≥8 | 早上好 | +| E | ZHOUNAN≥2 + SHARE + profile | 好 | + + +## 八、音频资产需求清单 + +### 林夏语音 +| 类别 | 数量 | 平均时长 | 总时长 | +|--------------|---------|---------|------------| +| 通用短句 | 10 条 | 1s | ~10s | +| 消息专属台词 | ~45 条 | 2s | ~90s | +| **合计** | **~55 条** | | **~100s** | + +### NPC 反应语音(Round 2 & 3) +| 角色 | 条数 | 总时长估算 | +|---------|---------|----------| +| 小美 | 10 条 | ~40s | +| 阿哲 | 8 条 | ~30s | +| 王姐 | 3 条 | ~10s | +| 安安 | 5 条 | ~15s | +| 寝室群 | 8 条 | ~30s | +| 妈妈 | 10 条 | ~40s | +| 周南 | 10 条 | ~40s | +| **合计** | **~54 条** | **~205s** | + +### 总计新增音频 +- 林夏 ~55 条,~100 秒 +- NPC 反应 ~54 条,~205 秒 +- **总计约 109 条语音,约 5 分钟录制量** + +### UI 音效(合成,无需录制) +保持现有合成音效方案:notify, choiceReady, choiceWarm, choiceCold, choiceCurious, choiceSilent, skip, next, ending。 +新增: +- `typing`:打字/录音音效(1-2 秒,合成或使用现有 sfx_keyboard) +- `send`:发送音效(极短,合成) + + +## 九、技术实现概要 + +### 新状态机 + +``` +idle → notification/ringing → playing → choosing → responding → reacting → [choosing | idle] +``` + +新增两个状态: +- **responding**:林夏说话中(播放林夏语音 + 打字/发送音效) +- **reacting**:NPC 反应播放中 + +### 数据结构变更 + +```javascript +// 每条消息的 choices 结构 +choices: { + right: { + linxia: 'audio/linxia/msg02_warm.mp3', // 林夏的回应 + npcReact: ['audio/xiaomei/msg02_react_warm.mp3'], // NPC 的反应 + state: { ... }, + profile: { engagement: 2 }, + followUp: { // 可选:Round 3 继续对话 + audio: ['audio/xiaomei/msg02_followup.mp3'], + choices: { ... } // 递归结构 + } + }, + left: { ... }, + up: { ... }, + down: { ... }, + tap: { ... }, +} +``` + +### 对话流程伪代码 + +```javascript +async _handleChoice(dir) { + const choice = this.currentMsg.choices[dir]; + + // 1. 播放情感音效 + SFX_UI[emotionSfx[dir]](); + + // 2. 打字/发送音效 + this.state = 'responding'; + await Snd.playFXAsync('typing', 1000); + Snd.playFXOnce('send'); + + // 3. 播放林夏的回应 + if (choice.linxia) { + await Snd.playVoiceAsync([choice.linxia]); + } else { + await sleep(1500); // 沉默 + } + + // 4. 播放 NPC 反应 + if (choice.npcReact) { + this.state = 'reacting'; + await Snd.playVoiceAsync(choice.npcReact); + } + + // 5. 如果有后续对话 + if (choice.followUp) { + this.currentMsg = choice.followUp; + this.state = 'playing'; + await Snd.playVoiceAsync(choice.followUp.audio); + if (choice.followUp.choices) { + this.state = 'choosing'; + return; // 等待下一次选择 + } + } + + // 6. 推进到下一条 + this._proceedToNext(); +} +``` + + +## 十、总结 + +| 维度 | 旧版 | 新版 | +|-----------|--------------------|---------------------------------| +| 林夏的存在感 | 隐形 | 有声音、有性格、有情绪变化 | +| 选择的感受 | 四个方向一样 | 听到完全不同的对话和反应 | +| 对话深度 | 单轮 | 2-3 轮,追问路径可获独家信息 | +| 情感反馈 | 无 | 环境音变化 + 对话节奏变化 | +| 新增录音量 | — | ~5 分钟(109 条短语音) | +| 交互复杂度 | 不变 | 6 种手势,含义一致 | diff --git a/tools/generate_voices.py b/tools/generate_voices.py new file mode 100644 index 0000000..1c471e7 --- /dev/null +++ b/tools/generate_voices.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +""" +生成《林夏》v2 全部新增语音资源 + +使用 edge-tts(微软 Azure 语音合成) +运行: python3 tools/generate_voices.py + +角色 → 声优映射: + 林夏 → zh-CN-XiaoxiaoNeural (温暖年轻女声) + 小美 → zh-CN-XiaoyiNeural (活泼年轻女声) + 阿哲 → zh-CN-YunxiNeural (年轻男声) + 王姐 → zh-CN-XiaoxiaoNeural (用较慢语速模拟成熟感) + 安安 → zh-CN-XiaoyiNeural (活泼,用较快语速) + 室友A → zh-CN-XiaoyiNeural + 室友B → zh-CN-XiaoxiaoNeural + 妈妈 → zh-CN-XiaoxiaoNeural (慢速 + 柔和,模拟妈妈) + 周南 → zh-CN-YunyangNeural (沉稳男声) + 外卖小哥 → zh-CN-YunjianNeural (干脆男声) +""" + +import asyncio +import os +import edge_tts + +# ── 输出目录 ── +BASE = os.path.join(os.path.dirname(__file__), '..', 'audio', 'mp3') +DIR_LINXIA = os.path.join(BASE, 'linxia') +DIR_REACT = os.path.join(BASE, 'react') + +# ── 声优配置 ── +V_LINXIA = 'zh-CN-XiaoxiaoNeural' +V_XIAOMEI = 'zh-CN-XiaoyiNeural' +V_AZHE = 'zh-CN-YunxiNeural' +V_WANGJIE = 'zh-CN-XiaoxiaoNeural' # 慢速 +V_ANAN = 'zh-CN-XiaoyiNeural' # 快速 +V_DORM_A = 'zh-CN-XiaoyiNeural' +V_DORM_B = 'zh-CN-XiaoxiaoNeural' +V_MOM = 'zh-CN-XiaoxiaoNeural' # 慢速 + 柔和 +V_ZHOUNAN = 'zh-CN-YunyangNeural' +V_DELIVERY = 'zh-CN-YunjianNeural' + +# ── 语速配置 (rate: "-20%" 较慢, "+10%" 较快) ── +# edge-tts rate 格式: "+0%", "-20%", "+15%" 等 + +# ══════════════════════════════════════════ +# 林夏语音列表 +# ══════════════════════════════════════════ + +LINXIA_LINES = [ + # ── 通用短句 ── + ('generic_hmm', '嗯。', '-5%'), + ('generic_ok', '好。', '-5%'), + ('generic_warm_ok', '嗯,好啊。', '-10%'), + ('generic_cold_know', '知道了。', '+0%'), + ('generic_cold_fine', '行吧。', '+0%'), + ('generic_curious', '嗯?', '-5%'), + ('generic_then', '然后呢?', '-5%'), + + # ── MSG-01 妈妈电话 ── + ('msg01_auto', '嗯……好。', '-15%'), + + # ── MSG-02 小美 ── + ('msg02_warm', '好啊,明天不见不散。', '-5%'), + ('msg02_cold', '再说吧。', '+0%'), + ('msg02_curious', '你怎么知道的啊?', '-5%'), + ('msg02_r3_curious', '他……发了什么?', '-10%'), + + # ── MSG-03 外卖 ── + ('msg03_auto', '好,我下来拿。', '+0%'), + + # ── MSG-04 阿哲 ── + ('msg04_warm', '你那边……还好吗。', '-15%'), + ('msg04_cold', '知道了。明天寄。', '+0%'), + ('msg04_curious', '你怎么不自己来拿。', '-5%'), + ('msg04_r3w_warm', '不太好。', '-20%'), + ('msg04_r3w_cold', '挺好的。', '+0%'), + ('msg04_r3c_curious', '为什么。', '-10%'), + ('msg04_r3c_warm', '算了,不问了。', '-10%'), + + # ── MSG-05 王姐 ── + ('msg05_warm', '谢谢王姐。', '-5%'), + ('msg05_cold', '好的,知道了。', '+0%'), + ('msg05_curious', '王姐……有什么建议吗?', '-10%'), + + # ── MSG-06 安安 ── + ('msg06_warm', '在哪儿啊?我看看吧。', '-5%'), + ('msg06_cold', '不了,你们玩好。', '+0%'), + ('msg06_curious', '你喝了多少了?', '-5%'), + + # ── MSG-07 寝室群 ── + ('msg07_warm', '恭喜恭喜!太厉害了!', '-5%'), + ('msg07_cold', '恭喜。', '+0%'), + ('msg07_curious', '怎么升的啊?讲讲!', '-5%'), + ('msg07_r3_warm', '挺好的,你请客我去!', '-5%'), + ('msg07_r3_cold', '还行。', '+0%'), + + # ── MSG-09 小美(醉)── + ('msg09_warm', '小美……你没事吧。', '-15%'), + ('msg09_cold', '我知道了。', '+0%'), + ('msg09_curious', '你……为什么现在才说。', '-15%'), + ('msg09_r3_warm', '我不恨你。', '-20%'), + ('msg09_r3_cold', '我得想想。', '-10%'), + + # ── MSG-10 妈妈文字 ── + ('msg10_warm', '没有,怎么了?', '-5%'), + ('msg10_cold', '嗯,要睡了。', '-10%'), + ('msg10_curious', '妈……你能打电话给我吗?', '-15%'), + ('msg10_call_warm', '没什么……就是有点累。', '-20%'), + ('msg10_call_curious','妈,我辞职了。', '-15%'), + ('msg10_rw_warm', '还好。', '-10%'), + ('msg10_rw_curious', '不太好。', '-15%'), + + # ── MSG-11 阿哲电话 ── + ('msg11_warm', '可以。但只有五分钟。', '-5%'), + ('msg11_cold', '不方便。我明天寄给你。', '+0%'), + ('msg11_curious', '为什么非要今天来?', '-5%'), + ('msg11_tap', '不了。', '+0%'), + + # ── MSG-12 周南 ── + ('msg12_warm', '周南!你还记得我啊!', '-5%'), + ('msg12_cold', '嗯,你好。', '+0%'), + ('msg12_curious', '你怎么有我微信的?', '-5%'), + ('msg12_tap', '嗯……好久不见。', '-10%'), + + # ── MSG-13 周南长语音 ── + ('msg13_warm', '一个人在北京,还好吗?', '-10%'), + ('msg13_cold', '你讲了好多。', '-5%'), + ('msg13_curious', '生日快乐。', '-15%'), + ('msg13_tap', '嗯,我都听到了。', '-10%'), + ('msg13_r3_warm', '不太好。今天也是我的坏日子。', '-15%'), + ('msg13_r3_cold', '还行吧。', '-5%'), + ('msg13_r3_curious', '你为什么突然联系我?', '-10%'), + + # ── MSG-14 妈妈电话 ── + ('msg14_auto', '嗯,我没事。', '-10%'), +] + +# ══════════════════════════════════════════ +# NPC 反应语音列表 +# ══════════════════════════════════════════ + +NPC_LINES = [ + # ── MSG-02 小美反应 ── + ('msg02_react_warm', V_XIAOMEI, '行!那我叫上安安!老地方见!别放鸽子啊!', '+5%'), + ('msg02_react_cold', V_XIAOMEI, '……好吧。你要是改主意了随时说。', '-5%'), + ('msg02_react_curious', V_XIAOMEI, '还不是阿哲自己发朋友圈!你不知道吗?他发了又秒删……', '+0%'), + ('msg02_react_silent', V_XIAOMEI, '你还活着吗哈哈。好了不烦你了,有事找我。', '+5%'), + ('msg02_react_tap', V_XIAOMEI, '嗯是什么意思啊哈哈!行吧你休息。', '+5%'), + ('msg02_r3_curious', V_XIAOMEI, '就发了一张你们以前的照片,配了一句值得。两分钟就删了。', '-5%'), + + # ── MSG-04 阿哲反应 ── + ('msg04_react_warm', V_AZHE, '我……挺好的。你呢?', '-10%'), + ('msg04_react_cold', V_AZHE, '嗯……谢了。', '-5%'), + ('msg04_react_curious', V_AZHE, '我……不太方便过去。', '-10%'), + ('msg04_react_silent', V_AZHE, '那……我晚点再说吧。', '-10%'), + ('msg04_react_tap', V_AZHE, '谢谢。', '-5%'), + ('msg04_r3w_sorry', V_AZHE, '……对不起。', '-20%'), + ('msg04_r3w_good', V_AZHE, '那就好。', '-5%'), + ('msg04_r3w_night', V_AZHE, '……那,晚安。', '-15%'), + ('msg04_r3c_nevermind', V_AZHE, '我……没什么。你寄吧。', '-10%'), + + # ── MSG-05 王姐反应 ── + ('msg05_react_warm', V_WANGJIE, '别客气。你还年轻,出去看看也好。这行业你知道的。', '-15%'), + ('msg05_react_curious', V_WANGJIE, '你想听真话?别在这行耗了。你有本事,去试试别的。', '-10%'), + + # ── MSG-06 安安反应 ── + ('msg06_react_warm', V_ANAN, '对面那个!我给你留了位置!赶紧赶紧!', '+10%'), + ('msg06_react_cold', V_ANAN, '好吧!那我给你寄一杯!', '+10%'), + ('msg06_react_curious', V_ANAN, '才两杯!我清醒得很!……好吧三杯。快来!', '+5%'), + ('msg06_react_silent', V_ANAN, '喂?信号不好吗?好吧以后再约!', '+5%'), + ('msg06_react_tap', V_ANAN, '嗯是来还是不来啊!算了回头聊!', '+5%'), + + # ── MSG-07 寝室群反应 ── + ('msg07_react_warm', V_DORM_A, '哈哈谢谢!我请客!你最近怎么样啊?', '+5%'), + ('msg07_react_curious', V_DORM_A, '说来话长!我们老板走了我就顶上了哈哈。', '+5%'), + ('msg07_react_tap', V_DORM_B, '林夏发表情了哈哈。', '+0%'), + ('msg07_r3_cold', V_DORM_B, '你是不是有事啊?没事可以跟我们说。', '-5%'), + + # ── MSG-09 小美(醉)反应 ── + ('msg09_react_warm', V_XIAOMEI, '我没事……我就是喝多了想告诉你……你别怪我。', '-15%'), + ('msg09_react_tap', V_XIAOMEI, '你不生气吗?算了……当我没说。', '-10%'), + ('msg09_react_curious', V_XIAOMEI, '因为你今天……我怕你以后恨我……', '-15%'), + ('msg09_r3_warm', V_XIAOMEI, '谢谢你……谢谢你林夏。', '-20%'), + ('msg09_r3_cold', V_XIAOMEI, '好……对不起。', '-15%'), + + # ── MSG-10 妈妈反应 ── + ('msg10_react_warm', V_MOM, '没事,就是想起来问一下。今天怎么样?', '-20%'), + ('msg10_react_curious', V_MOM, '怎么了夏夏?你声音不对。', '-15%'), + ('msg10_call_warm', V_MOM, '累了就回家歇几天。妈煮了排骨。', '-20%'), + ('msg10_call_curious', V_MOM, '辞了就辞了。回来再说。', '-15%'), + ('msg10_call_silent', V_MOM, '不想说就不说。妈在这呢。', '-20%'), + ('msg10_rw_warm', V_MOM, '那就好。别太晚了。', '-15%'), + ('msg10_rw_curious', V_MOM, '怎么了?', '-10%'), + + # ── MSG-11 阿哲反应 ── + ('msg11_react_warm', V_AZHE, '好,我马上过来。谢谢。', '-5%'), + ('msg11_react_cold', V_AZHE, '好吧……那谢了。', '-10%'), + ('msg11_react_curious', V_AZHE, '我……明天要出差。就五分钟。', '-10%'), + ('msg11_react_tap', V_AZHE, '好。', '-5%'), + + # ── MSG-12 周南反应 ── + ('msg12_react_warm', V_ZHOUNAN, '当然记得。你是我们班最安静的那个。', '-10%'), + ('msg12_react_cold', V_ZHOUNAN, '嗯……好久不见。你现在在哪呢?', '-10%'), + ('msg12_react_curious', V_ZHOUNAN, '你忘了吗?毕业那天你加的我。不过你可能早忘了哈哈。', '-5%'), + ('msg12_react_silent', V_ZHOUNAN, '你是不是不记得我了?没关系的。就是突然想说声嗨。', '-10%'), + ('msg12_react_tap', V_ZHOUNAN, '是啊,好久不见。', '-10%'), + + # ── MSG-13 周南反应 ── + ('msg13_react_warm', V_ZHOUNAN, '还行吧。习惯了。你呢?你在北京过得好吗?', '-10%'), + ('msg13_react_cold', V_ZHOUNAN, '是吗?不好意思,话太多了。', '-10%'), + ('msg13_react_curious', V_ZHOUNAN, '谢谢你。你是今天第一个跟我说的。', '-15%'), + ('msg13_react_silent', V_ZHOUNAN, '不好意思突然说这么多……那,晚安。', '-10%'), + ('msg13_react_tap', V_ZHOUNAN, '嗯。谢谢你听我说。', '-10%'), + ('msg13_r3_warm', V_ZHOUNAN, '那我们算是难兄难妹了。', '-5%'), + ('msg13_r3_cold', V_ZHOUNAN, '那就好。', '-5%'), + ('msg13_r3_curious', V_ZHOUNAN, '因为……今天生日嘛,想起以前的事。想起你。', '-15%'), + + # ── MSG-14 妈妈反应 ── + ('msg14_react_linked', V_MOM, '好。那早点睡。妈爱你。', '-20%'), + ('msg14_react_unlinked', V_MOM, '你今天怎么都不理妈妈?出什么事了吗?', '-15%'), +] + + +async def generate_one(voice, text, rate, outpath): + """用 edge-tts 生成单条语音""" + comm = edge_tts.Communicate(text, voice, rate=rate) + await comm.save(outpath) + + +async def main(): + os.makedirs(DIR_LINXIA, exist_ok=True) + os.makedirs(DIR_REACT, exist_ok=True) + + tasks = [] + total = len(LINXIA_LINES) + len(NPC_LINES) + done = 0 + + # ── 生成林夏语音 ── + for name, text, rate in LINXIA_LINES: + outpath = os.path.join(DIR_LINXIA, f'{name}.mp3') + if os.path.exists(outpath): + done += 1 + continue + tasks.append((V_LINXIA, text, rate, outpath, name)) + + # ── 生成 NPC 反应语音 ── + for name, voice, text, rate in NPC_LINES: + outpath = os.path.join(DIR_REACT, f'{name}.mp3') + if os.path.exists(outpath): + done += 1 + continue + tasks.append((voice, text, rate, outpath, name)) + + if not tasks: + print(f'所有 {total} 条语音已存在,无需生成。') + return + + print(f'需要生成 {len(tasks)} 条语音(已跳过 {done} 条已存在的)...\n') + + # 逐条生成(避免并发过高被限流) + for i, (voice, text, rate, outpath, name) in enumerate(tasks): + try: + await generate_one(voice, text, rate, outpath) + print(f' [{i+1}/{len(tasks)}] {name}: "{text}"') + except Exception as e: + print(f' [{i+1}/{len(tasks)}] FAILED {name}: {e}') + + print(f'\n完成!共生成 {len(tasks)} 条语音。') + print(f' 林夏语音目录: {DIR_LINXIA}') + print(f' NPC反应目录: {DIR_REACT}') + + +if __name__ == '__main__': + asyncio.run(main())