Files
GameOfMoon/migration_test.kt

383 lines
13 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"
}