chore(git): update .gitignore to exclude keys, build outputs, logs
This commit is contained in:
382
migration_test.kt
Normal file
382
migration_test.kt
Normal file
@@ -0,0 +1,382 @@
|
||||
#!/usr/bin/env kotlin
|
||||
|
||||
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 独立的迁移测试脚本
|
||||
* 用于将CompleteStoryData的内容提取并转换为DSL格式
|
||||
*/
|
||||
|
||||
data class SimpleStoryNode(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val content: String,
|
||||
val choices: List<SimpleChoice>
|
||||
)
|
||||
|
||||
data class SimpleChoice(
|
||||
val id: String,
|
||||
val text: String,
|
||||
val nextNodeId: String,
|
||||
val effects: List<SimpleEffect> = emptyList(),
|
||||
val requirements: List<SimpleRequirement> = emptyList()
|
||||
)
|
||||
|
||||
data class SimpleEffect(
|
||||
val type: SimpleEffectType,
|
||||
val value: String,
|
||||
val description: String = ""
|
||||
)
|
||||
|
||||
data class SimpleRequirement(
|
||||
val type: SimpleRequirementType,
|
||||
val value: String
|
||||
)
|
||||
|
||||
enum class SimpleEffectType {
|
||||
HEALTH_CHANGE, STAMINA_CHANGE, SECRET_UNLOCK, LOCATION_DISCOVER, LOOP_CHANGE, DAY_CHANGE
|
||||
}
|
||||
|
||||
enum class SimpleRequirementType {
|
||||
MIN_STAMINA, MIN_HEALTH, HAS_SECRET, VISITED_LOCATION
|
||||
}
|
||||
|
||||
fun main() = runBlocking {
|
||||
println("🚀 开始故事内容迁移...")
|
||||
|
||||
val outputDir = File("migration_output")
|
||||
if (outputDir.exists()) {
|
||||
outputDir.deleteRecursively()
|
||||
}
|
||||
outputDir.mkdirs()
|
||||
|
||||
// 创建子目录
|
||||
File(outputDir, "modules").mkdirs()
|
||||
File(outputDir, "shared").mkdirs()
|
||||
File(outputDir, "config").mkdirs()
|
||||
|
||||
// 模拟CompleteStoryData的内容提取
|
||||
extractAndConvertContent(outputDir)
|
||||
|
||||
println("✅ 迁移完成!输出目录:${outputDir.absolutePath}")
|
||||
}
|
||||
|
||||
fun extractAndConvertContent(outputDir: File) {
|
||||
println("📊 开始内容提取和分析...")
|
||||
|
||||
// 步骤1:读取并分析CompleteStoryData.kt
|
||||
val storyDataFile = File("app/src/main/java/com/example/gameofmoon/story/CompleteStoryData.kt")
|
||||
if (!storyDataFile.exists()) {
|
||||
println("❌ 找不到CompleteStoryData.kt文件")
|
||||
return
|
||||
}
|
||||
|
||||
val content = storyDataFile.readText()
|
||||
println("📖 读取了${content.length}个字符的故事内容")
|
||||
|
||||
// 步骤2:解析节点定义
|
||||
val nodePattern = Regex(""""([^"]+)"\s+to\s+SimpleStoryNode\s*\([\s\S]*?^\s*\)""", RegexOption.MULTILINE)
|
||||
val nodes = mutableMapOf<String, String>()
|
||||
|
||||
nodePattern.findAll(content).forEach { match ->
|
||||
val nodeId = match.groupValues[1]
|
||||
val nodeContent = match.value
|
||||
nodes[nodeId] = nodeContent
|
||||
println("🔍 找到节点:$nodeId")
|
||||
}
|
||||
|
||||
println("📊 总共找到 ${nodes.size} 个故事节点")
|
||||
|
||||
// 步骤3:按类型分组节点
|
||||
val nodeGroups = categorizeNodes(nodes)
|
||||
|
||||
// 步骤4:生成DSL文件
|
||||
generateDSLFiles(outputDir, nodeGroups)
|
||||
|
||||
// 步骤5:生成配置文件
|
||||
generateConfigFiles(outputDir, nodeGroups)
|
||||
|
||||
println("✅ 所有文件生成完成")
|
||||
}
|
||||
|
||||
fun categorizeNodes(nodes: Map<String, String>): Map<String, List<String>> {
|
||||
return mapOf(
|
||||
"main_chapter_1" to nodes.keys.filter {
|
||||
it.contains("awakening") || it.contains("eva_first") || it.contains("medical") ||
|
||||
it.contains("exploration") || it.contains("immediate") || it.contains("voice_recognition")
|
||||
},
|
||||
"main_chapter_2" to nodes.keys.filter {
|
||||
it.contains("investigation") || it.contains("revelation") || it.contains("trust") ||
|
||||
it.contains("memory") || it.contains("crew_meeting") || it.contains("base_information")
|
||||
},
|
||||
"main_chapter_3" to nodes.keys.filter {
|
||||
it.contains("confrontation") || it.contains("truth") || it.contains("choice") ||
|
||||
it.contains("climax") || it.contains("final")
|
||||
},
|
||||
"emotional_stories" to nodes.keys.filter {
|
||||
it.contains("comfort") || it.contains("sharing") || it.contains("identity") ||
|
||||
it.contains("inner_strength") || it.contains("gradual_revelation") || it.contains("ethical_discussion")
|
||||
},
|
||||
"investigation_branch" to nodes.keys.filter {
|
||||
it.contains("stealth") || it.contains("eavesdrop") || it.contains("data") ||
|
||||
it.contains("evidence") || it.contains("direct_confrontation") || it.contains("system_sabotage")
|
||||
},
|
||||
"side_stories" to nodes.keys.filter {
|
||||
it.contains("garden") || it.contains("photo") || it.contains("crew_analysis") ||
|
||||
it.contains("philosophical") || it.contains("memory_reconstruction") || it.contains("private_grief")
|
||||
},
|
||||
"endings" to nodes.keys.filter {
|
||||
it.contains("ending") || it.contains("destruction") || it.contains("eternal_loop") ||
|
||||
it.contains("earth_truth") || it.contains("anchor_modification")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun generateDSLFiles(outputDir: File, nodeGroups: Map<String, List<String>>) {
|
||||
println("📄 开始生成DSL文件...")
|
||||
|
||||
for ((groupName, nodeIds) in nodeGroups) {
|
||||
if (nodeIds.isEmpty()) continue
|
||||
|
||||
val dslContent = generateModuleDSL(groupName, nodeIds)
|
||||
val outputFile = File(File(outputDir, "modules"), "$groupName.story")
|
||||
outputFile.writeText(dslContent)
|
||||
|
||||
println("📄 生成了 $groupName.story (${nodeIds.size} 个节点)")
|
||||
}
|
||||
|
||||
// 生成共享模块
|
||||
generateSharedFiles(outputDir)
|
||||
}
|
||||
|
||||
fun generateModuleDSL(moduleName: String, nodeIds: List<String>): String {
|
||||
val dslBuilder = StringBuilder()
|
||||
|
||||
// 模块头部
|
||||
dslBuilder.appendLine("@story_module $moduleName")
|
||||
dslBuilder.appendLine("@version 2.0")
|
||||
dslBuilder.appendLine("@dependencies [characters, audio_config, anchors]")
|
||||
dslBuilder.appendLine("@description \"${getModuleDescription(moduleName)}\"")
|
||||
dslBuilder.appendLine()
|
||||
|
||||
// 音频配置
|
||||
dslBuilder.appendLine("@audio")
|
||||
dslBuilder.appendLine(" background: ${getModuleAudio(moduleName)}")
|
||||
dslBuilder.appendLine(" transition: discovery_chime.mp3")
|
||||
dslBuilder.appendLine("@end")
|
||||
dslBuilder.appendLine()
|
||||
|
||||
// 生成节点占位符(实际实现中会解析CompleteStoryData的具体内容)
|
||||
for (nodeId in nodeIds) {
|
||||
dslBuilder.appendLine("@node $nodeId")
|
||||
dslBuilder.appendLine("@title \"${getNodeTitle(nodeId)}\"")
|
||||
dslBuilder.appendLine("@audio_bg ${getNodeAudio(nodeId)}")
|
||||
dslBuilder.appendLine("@content \"\"\"")
|
||||
dslBuilder.appendLine("// 从CompleteStoryData转换的内容:$nodeId")
|
||||
dslBuilder.appendLine("// 实际内容需要从原始数据中提取")
|
||||
dslBuilder.appendLine("\"\"\"")
|
||||
dslBuilder.appendLine()
|
||||
|
||||
dslBuilder.appendLine("@choices 2")
|
||||
dslBuilder.appendLine(" choice_1: \"选择1\" -> next_node_1 [effect: health+5] [audio: button_click.mp3]")
|
||||
dslBuilder.appendLine(" choice_2: \"选择2\" -> next_node_2 [effect: trust+2] [audio: notification_beep.mp3]")
|
||||
dslBuilder.appendLine("@end")
|
||||
dslBuilder.appendLine()
|
||||
}
|
||||
|
||||
return dslBuilder.toString()
|
||||
}
|
||||
|
||||
fun generateSharedFiles(outputDir: File) {
|
||||
// 生成角色文件
|
||||
val charactersContent = """
|
||||
@story_module characters
|
||||
@version 2.0
|
||||
@description "角色定义模块 - 定义所有游戏角色的属性和特征"
|
||||
|
||||
@character eva
|
||||
name: "伊娃 / EVA"
|
||||
voice_style: gentle
|
||||
description: "基地AI系统,实际上是莉莉的意识转移,温柔而智慧"
|
||||
relationship: "妹妹"
|
||||
personality: "关爱、智慧、略带忧郁"
|
||||
key_traits: ["protective", "intelligent", "emotional"]
|
||||
@end
|
||||
|
||||
@character alex
|
||||
name: "艾利克丝·陈"
|
||||
voice_style: determined
|
||||
description: "月球基地工程师,坚强而富有同情心的主角"
|
||||
relationship: "自己"
|
||||
personality: "坚毅、善良、追求真相"
|
||||
key_traits: ["brave", "empathetic", "curious"]
|
||||
@end
|
||||
|
||||
@character sara
|
||||
name: "萨拉·维特博士"
|
||||
voice_style: professional
|
||||
description: "基地医生,负责心理健康,内心善良但被迫参与实验"
|
||||
relationship: "同事"
|
||||
personality: "专业、内疚、渴望救赎"
|
||||
key_traits: ["caring", "conflicted", "knowledgeable"]
|
||||
@end
|
||||
""".trimIndent()
|
||||
|
||||
File(File(outputDir, "shared"), "characters.story").writeText(charactersContent)
|
||||
|
||||
// 生成音频配置文件
|
||||
val audioContent = """
|
||||
@story_module audio_config
|
||||
@version 2.0
|
||||
@description "音频配置模块 - 定义所有游戏音频资源"
|
||||
|
||||
@audio
|
||||
// ===== 背景音乐 =====
|
||||
mysterious: ambient_mystery.mp3
|
||||
tension: electronic_tension.mp3
|
||||
peaceful: space_silence.mp3
|
||||
revelation: orchestral_revelation.mp3
|
||||
finale: epic_finale.mp3
|
||||
discovery: discovery_chime.mp3
|
||||
|
||||
// ===== 环境音效 =====
|
||||
base_ambient: reactor_hum.mp3
|
||||
ventilation: ventilation_soft.mp3
|
||||
storm: solar_storm.mp3
|
||||
heartbeat: heart_monitor.mp3
|
||||
time_warp: time_distortion.mp3
|
||||
|
||||
// ===== 交互音效 =====
|
||||
button_click: button_click.mp3
|
||||
notification: notification_beep.mp3
|
||||
alert: error_alert.mp3
|
||||
@end
|
||||
""".trimIndent()
|
||||
|
||||
File(File(outputDir, "shared"), "audio.story").writeText(audioContent)
|
||||
|
||||
// 生成锚点配置文件
|
||||
val anchorsContent = """
|
||||
@story_module anchors
|
||||
@version 2.0
|
||||
@description "锚点系统 - 定义动态故事导航的智能锚点"
|
||||
|
||||
@anchor_conditions
|
||||
// ===== 关键剧情解锁条件 =====
|
||||
eva_reveal_ready: secrets_found >= 3 AND trust_level >= 5
|
||||
investigation_unlocked: harrison_recording_found == true
|
||||
deep_truth_ready: eva_reveal_ready == true AND investigation_unlocked == true
|
||||
perfect_ending_available: secrets_found >= 15 AND health > 50
|
||||
|
||||
// ===== 结局分支条件 =====
|
||||
freedom_ending_ready: anchor_destruction_chosen == true
|
||||
loop_ending_ready: eternal_loop_chosen == true
|
||||
truth_ending_ready: earth_truth_revealed == true
|
||||
|
||||
// ===== 情感状态条件 =====
|
||||
emotional_stability: health > 70 AND trust_level > 8
|
||||
sister_bond_strong: eva_interactions >= 10
|
||||
@end
|
||||
""".trimIndent()
|
||||
|
||||
File(File(outputDir, "shared"), "anchors.story").writeText(anchorsContent)
|
||||
|
||||
println("📄 生成了共享模块文件")
|
||||
}
|
||||
|
||||
fun generateConfigFiles(outputDir: File, nodeGroups: Map<String, List<String>>) {
|
||||
val modules = nodeGroups.keys.filter { nodeGroups[it]?.isNotEmpty() == true }
|
||||
|
||||
val configContent = """
|
||||
{
|
||||
"version": "2.0",
|
||||
"engine": "DSL Story Engine",
|
||||
"default_language": "zh",
|
||||
"modules": [
|
||||
"characters",
|
||||
"audio_config",
|
||||
"anchors",
|
||||
${modules.joinToString(",\n ") { "\"$it\"" }}
|
||||
],
|
||||
"audio": {
|
||||
"enabled": true,
|
||||
"default_volume": 0.7,
|
||||
"fade_duration": 1000,
|
||||
"background_loop": true
|
||||
},
|
||||
"gameplay": {
|
||||
"auto_save": true,
|
||||
"choice_timeout": 0,
|
||||
"skip_seen_content": false,
|
||||
"enable_branching": true
|
||||
},
|
||||
"features": {
|
||||
"conditional_navigation": true,
|
||||
"dynamic_anchors": true,
|
||||
"memory_management": true,
|
||||
"effects_system": true
|
||||
},
|
||||
"start_node": "first_awakening"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
File(File(outputDir, "config"), "config.json").writeText(configContent)
|
||||
|
||||
val indexContent = """
|
||||
{
|
||||
"modules": [
|
||||
${modules.joinToString(",\n ") { "\"$it\"" }}
|
||||
],
|
||||
"total_modules": ${modules.size},
|
||||
"generated_at": "${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())}",
|
||||
"format_version": "2.0",
|
||||
"total_nodes": ${nodeGroups.values.sumOf { it.size }}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
File(File(outputDir, "config"), "modules.json").writeText(indexContent)
|
||||
|
||||
println("📄 生成了配置文件")
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
fun getModuleDescription(moduleName: String): String = when (moduleName) {
|
||||
"main_chapter_1" -> "第一章:觉醒 - 主角从昏迷中醒来,开始探索月球基地的秘密"
|
||||
"main_chapter_2" -> "第二章:调查 - 深入基地,发现时间锚项目的真相"
|
||||
"main_chapter_3" -> "第三章:抉择 - 面对真相,做出最终的选择"
|
||||
"emotional_stories" -> "情感故事模块 - 探索角色间的情感联系和内心成长"
|
||||
"investigation_branch" -> "调查分支模块 - 深度调查和证据收集的故事线"
|
||||
"side_stories" -> "支线故事模块 - 花园、照片记忆等支线剧情"
|
||||
"endings" -> "结局模块 - 所有可能的故事结局和终章"
|
||||
else -> "故事模块:$moduleName"
|
||||
}
|
||||
|
||||
fun getModuleAudio(moduleName: String): String = when (moduleName) {
|
||||
"main_chapter_1" -> "ambient_mystery.mp3"
|
||||
"main_chapter_2" -> "electronic_tension.mp3"
|
||||
"main_chapter_3" -> "orchestral_revelation.mp3"
|
||||
"emotional_stories" -> "space_silence.mp3"
|
||||
"investigation_branch" -> "electronic_tension.mp3"
|
||||
"side_stories" -> "space_silence.mp3"
|
||||
"endings" -> "epic_finale.mp3"
|
||||
else -> "ambient_mystery.mp3"
|
||||
}
|
||||
|
||||
fun getNodeTitle(nodeId: String): String {
|
||||
return nodeId.split("_").joinToString(" ") {
|
||||
it.replaceFirstChar { char -> char.uppercase() }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNodeAudio(nodeId: String): String = when {
|
||||
nodeId.contains("revelation") || nodeId.contains("truth") -> "orchestral_revelation.mp3"
|
||||
nodeId.contains("tension") || nodeId.contains("confrontation") -> "electronic_tension.mp3"
|
||||
nodeId.contains("garden") || nodeId.contains("peaceful") -> "space_silence.mp3"
|
||||
nodeId.contains("discovery") -> "discovery_chime.mp3"
|
||||
else -> "ambient_mystery.mp3"
|
||||
}
|
||||
Reference in New Issue
Block a user