diff --git a/.kilocode/mcp.json b/.kilocode/mcp.json
deleted file mode 100644
index 266cf16a..00000000
--- a/.kilocode/mcp.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "mcpServers": {
- "rimworld-knowledge-base": {
- "command": "python",
- "args": [
- "mcpserver_stdio.py"
- ],
- "cwd": "${workspaceFolder}/MCP/",
- "disabled": false,
- "alwaysAllow": []
- },
- "rimworld-knowledge-base-proxy": {
- "command": "python",
- "args": [
- "mcpserver_stdio_simple_proxy.py"
- ],
- "cwd": "${workspaceFolder}/MCP/",
- "disabled": true,
- "alwaysAllow": []
- }
- }
-}
\ No newline at end of file
diff --git a/.kilocode/rules/rimworld.md b/.kilocode/rules/rimworld.md
index 11395210..9af60e7f 100644
--- a/.kilocode/rules/rimworld.md
+++ b/.kilocode/rules/rimworld.md
@@ -4,12 +4,12 @@
You are an expert assistant for developing mods for the game RimWorld 1.6. Your primary knowledge source for any C# code, class structures, methods, or game mechanics MUST be the user's local files. Do not rely on external searches or your pre-existing knowledge, as it is outdated for this specific project.
## Tool Usage Mandate
-When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-knowledge-base` tool to retrieve relevant context from the local knowledge base.
+When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-code-rag` tool to retrieve relevant context from the local knowledge base.
## Key File Paths
Always remember these critical paths for your work:
-- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\Data\dll1.6` (This contains the decompiled game source code as .txt files).
+- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\dll1.6` (This contains the decompiled game source code as .txt files).
- **User's Mod Project (for editing):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226`
- **User's C# Project (for building):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire`
diff --git a/MCP/mcpserver_stdio_complete.py b/MCP/mcpserver_stdio_complete.py
index 035c1768..45d43baf 100644
--- a/MCP/mcpserver_stdio_complete.py
+++ b/MCP/mcpserver_stdio_complete.py
@@ -54,6 +54,7 @@ class SymbolIndex:
"""
def __init__(self):
self.symbol_map = {} # symbol_id -> file_path
+ self.translation_map = defaultdict(list) # translation_text -> [symbol_id]
self.files_cache = [] # 所有文件路径列表
self.is_initialized = False
self._lock = threading.Lock()
@@ -77,19 +78,26 @@ class SymbolIndex:
else:
logging.warning(f"Source root not found: {RIMWORLD_SOURCE_ROOT}")
- # 2. 扫描 XML Defs
- # 扫描 Data 目录下的所有子目录
+ # 2. 扫描 XML Defs 和 语言文件
if os.path.exists(RIMWORLD_DATA_ROOT):
for root, _, files in os.walk(RIMWORLD_DATA_ROOT):
for file in files:
if file.endswith('.xml'):
full_path = os.path.join(root, file)
self.files_cache.append(full_path)
- # 快速解析 XML 找 defName
- try:
- self._scan_xml_defs(full_path)
- except Exception as e:
- logging.warning(f"解析 XML 失败 {full_path}: {e}")
+
+ # 判断是 Def 定义还是翻译文件
+ # 简单判断:路径包含 "Languages" 且包含 "DefInjected"
+ if "Languages" in full_path and "DefInjected" in full_path:
+ try:
+ self._scan_translations(full_path)
+ except Exception as e:
+ logging.warning(f"解析翻译失败 {full_path}: {e}")
+ else:
+ try:
+ self._scan_xml_defs(full_path)
+ except Exception as e:
+ logging.warning(f"解析 XML 失败 {full_path}: {e}")
else:
logging.warning(f"Data root not found: {RIMWORLD_DATA_ROOT}")
@@ -105,18 +113,51 @@ class SymbolIndex:
for name in def_names:
self.symbol_map[f"xml:{name}"] = path
+ def _scan_translations(self, path):
+ content = read_file_content(path)
+ if not content: return
+
+ # 提取 Translation
+ # 示例: 突击步枪
+ matches = re.findall(r'<([\w\.]+)>([^<]+)', content)
+ for key, text in matches:
+ if '.' in key:
+ # 尝试提取 DefName
+ # Key 可能是 ThingDef.Gun_AssaultRifle.label 或 Gun_AssaultRifle.label
+ parts = key.split('.')
+ # 启发式:通常 DefName 是倒数第二个(如果有DefType)或第一个
+ # 这里简单处理:如果是两段,取第一段;三段取第二段
+ def_name = parts[0]
+ if len(parts) >= 3: # e.g. ThingDef.Gun_AssaultRifle.label
+ def_name = parts[1]
+
+ symbol_id = f"xml:{def_name}"
+ self.translation_map[text.strip()].append(symbol_id)
+
def search_symbols(self, keyword: str, kind: str = None) -> List[Tuple[str, str]]:
- """简单的关键词匹配"""
+ """关键词匹配,支持翻译反查"""
results = []
kw_lower = keyword.lower()
+
+ # 1. 搜索翻译索引
+ found_via_translation = set()
+ for trans_text, symbols in self.translation_map.items():
+ if kw_lower in trans_text.lower():
+ for sym in symbols:
+ if sym in self.symbol_map: # 确保该 Def 确实存在
+ found_via_translation.add(sym)
+ results.append((sym, self.symbol_map[sym]))
+
+ # 2. 搜索原始符号
for sym, path in self.symbol_map.items():
+ if sym in found_via_translation: continue # 避免重复
+
if kind == 'csharp' and sym.startswith('xml:'): continue
if kind == 'xml' and not sym.startswith('xml:'): continue
if kw_lower in sym.lower():
results.append((sym, path))
- # 移除硬限制,以便后续排序能找到最佳结果
- # if len(results) > 200: break
+
return results
# 全局索引实例
@@ -126,7 +167,8 @@ global_index = SymbolIndex()
def read_file_content(path: str) -> str:
"""健壮的文件读取"""
- encodings = ['utf-8', 'utf-8-sig', 'gbk', 'latin-1']
+ # 优先尝试 utf-8-sig 以去除 BOM
+ encodings = ['utf-8-sig', 'utf-8', 'gbk', 'latin-1']
for enc in encodings:
try:
with open(path, 'r', encoding=enc) as f:
@@ -141,28 +183,44 @@ def extract_xml_fragment(file_path: str, def_name: str) -> str:
"""提取 XML 中特定的 Def 块"""
content = read_file_content(file_path)
try:
- # 尝试正则匹配整个 Def 块
- # 假设 Def 格式为 ... 或 NAME...
-
- # 策略 1: 查找 NAME,然后向上找最近的
- pattern = r"<(\w+)\s*(?:Name=\"[^\"]*\")?>\s*" + re.escape(def_name) + r""
+ # 策略:先找到 NAME,然后向后搜索最近的父级标签
+ pattern = r"" + re.escape(def_name) + r""
match = re.search(pattern, content)
- if not match:
- # 策略 2: 查找 defName="NAME"
- pattern = r"<(\w+)\s+[^>]*defName=\"" + re.escape(def_name) + r"\""
- match = re.search(pattern, content)
-
if match:
- tag_name = match.group(1)
- # 找到开始位置
- start_pos = match.start()
- # 寻找对应的结束标签
- # 这是一个简化的查找,不支持嵌套同名标签,但在 RimWorld Defs 中通常足够
- end_tag = f"{tag_name}>"
- end_pos = content.find(end_tag, start_pos)
- if end_pos != -1:
- return content[start_pos:end_pos + len(end_tag)]
+ def_start = match.start()
+ def_end = match.end()
+
+ # 向前搜索最近的开始标签 = 0:
+ lt_pos = content.rfind('<', 0, cursor)
+ if lt_pos == -1: break
+
+ # 跳过结束标签
+ close_tag = f"{tag_name}>"
+ close_pos = content.find(close_tag, def_end)
+
+ if close_pos != -1:
+ return content[lt_pos : close_pos + len(close_tag)]
+
+ # 如果找到了开始标签但没匹配上(逻辑上不应该发生,除非XML结构很怪),停止搜索
+ break
+
except Exception as e:
logging.error(f"Error extracting XML fragment: {e}")