chore(git): update .gitignore to exclude keys, build outputs, logs

This commit is contained in:
2025-09-05 11:45:02 +08:00
parent 138f2235c1
commit 00f0fcb667
66 changed files with 15366 additions and 851 deletions

382
migration_test.kt Normal file
View 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"
}