暂存
This commit is contained in:
@@ -9,6 +9,34 @@ You are an expert assistant for developing mods for the game RimWorld 1.6. Your
|
|||||||
|
|
||||||
## Tool Usage Mandate
|
## 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-knowledge-base` tool to retrieve relevant context from the local knowledge base.
|
||||||
|
# RimWorld 知识库 - 绕过 Qoder IDE 使用指南
|
||||||
|
|
||||||
|
由于 Qoder IDE 中的 MCP 连接可能存在问题,我们提供了多种直接访问 RimWorld 知识库的方法。
|
||||||
|
|
||||||
|
## 🚀 方法 1:直接 Python 调用
|
||||||
|
|
||||||
|
最简单直接的方法:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 直接查询
|
||||||
|
python direct_mcp_client.py -q "ThingDef是什么"
|
||||||
|
|
||||||
|
# 交互模式
|
||||||
|
python direct_mcp_client.py -i
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
python direct_mcp_client.py -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优点:
|
||||||
|
- ✅ 最快速,无需额外依赖
|
||||||
|
- ✅ 支持交互模式
|
||||||
|
- ✅ 直接在命令行使用
|
||||||
|
|
||||||
|
### 例子:
|
||||||
|
```bash
|
||||||
|
python "c:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP\direct_mcp_client.py" -q "ThingOwner class virtual methods TryAdd TryAddRange TryTransferToContainer"
|
||||||
|
```
|
||||||
|
|
||||||
## Key File Paths
|
## Key File Paths
|
||||||
Always remember these critical paths for your work:
|
Always remember these critical paths for your work:
|
||||||
@@ -27,3 +55,4 @@ Always remember these critical paths for your work:
|
|||||||
## Verification Mandate
|
## Verification Mandate
|
||||||
When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory.
|
When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory.
|
||||||
- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `<Compile Include="..." />` 条目,否则会导致编译失败。
|
- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `<Compile Include="..." />` 条目,否则会导致编译失败。
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
103
MCP/direct_mcp_client.py
Normal file
103
MCP/direct_mcp_client.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
直接调用MCP服务器的Python接口
|
||||||
|
绕过Qoder IDE,直接使用RimWorld知识库
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 添加MCP路径
|
||||||
|
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||||
|
if SDK_PATH not in sys.path:
|
||||||
|
sys.path.insert(0, SDK_PATH)
|
||||||
|
|
||||||
|
# 导入MCP服务器
|
||||||
|
from mcpserver_stdio import get_context
|
||||||
|
|
||||||
|
class DirectMCPClient:
|
||||||
|
"""直接调用MCP服务器的客户端"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
print("🚀 直接MCP客户端已启动")
|
||||||
|
print("📚 RimWorld知识库已加载")
|
||||||
|
|
||||||
|
def query(self, question: str) -> str:
|
||||||
|
"""查询RimWorld知识库"""
|
||||||
|
try:
|
||||||
|
print(f"🔍 正在查询: {question}")
|
||||||
|
result = get_context(question)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
return f"查询出错: {e}"
|
||||||
|
|
||||||
|
def interactive_mode(self):
|
||||||
|
"""交互模式"""
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("🎯 RimWorld知识库 - 交互模式")
|
||||||
|
print("输入问题查询知识库,输入 'quit' 或 'exit' 退出")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
question = input("\n❓ 请输入您的问题: ").strip()
|
||||||
|
|
||||||
|
if question.lower() in ['quit', 'exit', '退出', 'q']:
|
||||||
|
print("👋 再见!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not question:
|
||||||
|
print("⚠️ 请输入有效的问题")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print("\n🔄 正在搜索...")
|
||||||
|
result = self.query(question)
|
||||||
|
|
||||||
|
print("\n📖 查询结果:")
|
||||||
|
print("-" * 50)
|
||||||
|
print(result)
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n👋 用户中断,退出程序")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 出现错误: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='直接调用RimWorld MCP知识库')
|
||||||
|
parser.add_argument('--query', '-q', type=str, help='直接查询问题')
|
||||||
|
parser.add_argument('--interactive', '-i', action='store_true', help='进入交互模式')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
client = DirectMCPClient()
|
||||||
|
|
||||||
|
if args.query:
|
||||||
|
# 直接查询模式
|
||||||
|
result = client.query(args.query)
|
||||||
|
print("\n📖 查询结果:")
|
||||||
|
print("="*60)
|
||||||
|
print(result)
|
||||||
|
print("="*60)
|
||||||
|
elif args.interactive:
|
||||||
|
# 交互模式
|
||||||
|
client.interactive_mode()
|
||||||
|
else:
|
||||||
|
# 默认显示帮助
|
||||||
|
print("\n🔧 使用方法:")
|
||||||
|
print("1. 直接查询: python direct_mcp_client.py -q \"ThingDef是什么\"")
|
||||||
|
print("2. 交互模式: python direct_mcp_client.py -i")
|
||||||
|
print("3. 查看帮助: python direct_mcp_client.py -h")
|
||||||
|
|
||||||
|
# 演示查询
|
||||||
|
print("\n🎬 演示查询:")
|
||||||
|
demo_result = client.query("ThingDef")
|
||||||
|
print(demo_result[:500] + "..." if len(demo_result) > 500 else demo_result)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -55,7 +55,8 @@ KNOWLEDGE_BASE_PATHS = [
|
|||||||
# 初始化OpenAI客户端用于Qwen模型
|
# 初始化OpenAI客户端用于Qwen模型
|
||||||
qwen_client = OpenAI(
|
qwen_client = OpenAI(
|
||||||
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
||||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
timeout=15.0 # 设置15秒超时,避免MCP初始化超时
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. --- 向量缓存管理 ---
|
# 3. --- 向量缓存管理 ---
|
||||||
@@ -192,36 +193,47 @@ def find_most_similar_files(question_embedding, file_embeddings, top_n=3, min_si
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# 新增:重排序函数
|
# 新增:重排序函数
|
||||||
def rerank_files(question, file_matches, top_n=5):
|
def rerank_files(question, file_matches, top_n=3): # 减少默认数量
|
||||||
"""使用DashScope重排序API对文件进行重新排序"""
|
"""使用DashScope重排序API对文件进行重新排序"""
|
||||||
try:
|
try:
|
||||||
|
# 限制输入数量以减少超时风险
|
||||||
|
if len(file_matches) > 5: # 进一步限制最大输入数量以避免超时
|
||||||
|
file_matches = file_matches[:5]
|
||||||
|
|
||||||
# 准备重排序输入
|
# 准备重排序输入
|
||||||
documents = []
|
documents = []
|
||||||
for match in file_matches:
|
for match in file_matches:
|
||||||
# 读取文件内容
|
# 读取文件内容
|
||||||
try:
|
try:
|
||||||
with open(match['path'], 'r', encoding='utf-8') as f:
|
with open(match['path'], 'r', encoding='utf-8') as f:
|
||||||
content = f.read()[:2000] # 限制内容长度以提高效率
|
content = f.read()[:1500] # 进一步限制内容长度以提高效率
|
||||||
documents.append(content)
|
documents.append(content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"读取文件 {match['path']} 失败: {e}")
|
logging.error(f"读取文件 {match['path']} 失败: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not documents:
|
if not documents:
|
||||||
|
logging.warning("重排序时未能读取任何文件内容")
|
||||||
return file_matches[:top_n]
|
return file_matches[:top_n]
|
||||||
|
|
||||||
# 调用重排序API
|
# 调用重排序API,添加超时处理
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
response = dashscope.TextReRank.call(
|
response = dashscope.TextReRank.call(
|
||||||
model='gte-rerank',
|
model='gte-rerank',
|
||||||
query=question,
|
query=question,
|
||||||
documents=documents
|
documents=documents
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
logging.info(f"重排序API调用耗时: {elapsed_time:.2f}秒")
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# 根据重排序结果重新排序文件
|
# 根据重排序结果重新排序文件
|
||||||
reranked_results = []
|
reranked_results = []
|
||||||
for i, result in enumerate(response.output['results']):
|
for i, result in enumerate(response.output['results']):
|
||||||
if i < len(file_matches):
|
if i < len(file_matches) and i < len(documents): # 添加边界检查
|
||||||
reranked_results.append({
|
reranked_results.append({
|
||||||
'path': file_matches[i]['path'],
|
'path': file_matches[i]['path'],
|
||||||
'similarity': result['relevance_score']
|
'similarity': result['relevance_score']
|
||||||
@@ -417,6 +429,7 @@ def analyze_question_with_llm(question: str) -> dict:
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
temperature=0.0, # 使用最低温度确保输出稳定
|
temperature=0.0, # 使用最低温度确保输出稳定
|
||||||
max_tokens=300,
|
max_tokens=300,
|
||||||
|
timeout=12.0, # 12秒超时,避免MCP初始化超时
|
||||||
stop=["\n\n"] # 防止模型生成过多内容
|
stop=["\n\n"] # 防止模型生成过多内容
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -494,13 +507,20 @@ def get_context(question: str) -> str:
|
|||||||
"""
|
"""
|
||||||
logging.info(f"收到问题: {question}")
|
logging.info(f"收到问题: {question}")
|
||||||
|
|
||||||
# 使用LLM分析问题
|
try:
|
||||||
|
# 使用LLM分析问题,添加超时保护
|
||||||
analysis = analyze_question_with_llm(question)
|
analysis = analyze_question_with_llm(question)
|
||||||
keywords = analysis["search_keywords"]
|
keywords = analysis["search_keywords"]
|
||||||
|
|
||||||
if not keywords:
|
if not keywords:
|
||||||
logging.warning("无法从问题中提取关键词。")
|
logging.warning("无法从问题中提取关键词。")
|
||||||
return "无法从问题中提取关键词,请提供更具体的信息。"
|
return "无法从问题中提取关键词,请提供更具体的信息。"
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"LLM分析失败,使用备用方案: {e}")
|
||||||
|
# 备用方案:使用简单的关键词提取
|
||||||
|
keywords = find_keywords_in_question(question)
|
||||||
|
if not keywords:
|
||||||
|
return "无法分析问题,请检查网络连接或稍后重试。"
|
||||||
|
|
||||||
logging.info(f"提取到关键词: {keywords}")
|
logging.info(f"提取到关键词: {keywords}")
|
||||||
|
|
||||||
@@ -549,7 +569,7 @@ def get_context(question: str) -> str:
|
|||||||
|
|
||||||
# 3. 向量化和相似度计算 (精准筛选)
|
# 3. 向量化和相似度计算 (精准筛选)
|
||||||
# 增加超时保护:限制向量化的文件数量
|
# 增加超时保护:限制向量化的文件数量
|
||||||
MAX_FILES_TO_VECTORIZE = 50 # 增加处理文件数量
|
MAX_FILES_TO_VECTORIZE = 10 # 进一步减少处理文件数量以避免超时
|
||||||
if len(candidate_files) > MAX_FILES_TO_VECTORIZE:
|
if len(candidate_files) > MAX_FILES_TO_VECTORIZE:
|
||||||
logging.warning(f"候选文件过多 ({len(candidate_files)}),仅处理前 {MAX_FILES_TO_VECTORIZE} 个。")
|
logging.warning(f"候选文件过多 ({len(candidate_files)}),仅处理前 {MAX_FILES_TO_VECTORIZE} 个。")
|
||||||
candidate_files = candidate_files[:MAX_FILES_TO_VECTORIZE]
|
candidate_files = candidate_files[:MAX_FILES_TO_VECTORIZE]
|
||||||
@@ -559,27 +579,36 @@ def get_context(question: str) -> str:
|
|||||||
return "无法生成问题向量,请检查API连接或问题内容。"
|
return "无法生成问题向量,请检查API连接或问题内容。"
|
||||||
|
|
||||||
file_embeddings = []
|
file_embeddings = []
|
||||||
for file_path in candidate_files:
|
for i, file_path in enumerate(candidate_files):
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
# 添加处理进度日志
|
||||||
|
if i % 5 == 0: # 每5个文件记录一次进度
|
||||||
|
logging.info(f"正在处理第 {i+1}/{len(candidate_files)} 个文件: {os.path.basename(file_path)}")
|
||||||
|
|
||||||
file_embedding = get_embedding(content[:8000]) # 限制内容长度以提高效率
|
file_embedding = get_embedding(content[:8000]) # 限制内容长度以提高效率
|
||||||
if file_embedding:
|
if file_embedding:
|
||||||
file_embeddings.append({'path': file_path, 'embedding': file_embedding})
|
file_embeddings.append({'path': file_path, 'embedding': file_embedding})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"处理文件 {file_path} 时出错: {e}")
|
logging.error(f"处理文件 {file_path} 时出错: {e}")
|
||||||
|
continue # 继续处理下一个文件,而不是完全失败
|
||||||
|
|
||||||
if not file_embeddings:
|
if not file_embeddings:
|
||||||
return "无法为任何候选文件生成向量。"
|
logging.warning("未能为任何候选文件生成向量。可能是由于API超时或其他错误。")
|
||||||
|
return "未能为任何候选文件生成向量,请稍后重试或减少搜索范围。"
|
||||||
|
|
||||||
# 找到最相似的多个文件
|
# 找到最相似的多个文件
|
||||||
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=10) # 增加返回数量以供重排序
|
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=5) # 进一步减少返回数量以避免超时
|
||||||
|
|
||||||
if not best_matches:
|
if not best_matches:
|
||||||
return "计算向量相似度失败或没有找到足够相似的文件。"
|
return "计算向量相似度失败或没有找到足够相似的文件。"
|
||||||
|
|
||||||
# 新增:重排序处理
|
# 新增:重排序处理(仅在找到足够多匹配项时执行)
|
||||||
reranked_matches = rerank_files(question, best_matches, top_n=5)
|
if len(best_matches) > 2:
|
||||||
|
reranked_matches = rerank_files(question, best_matches, top_n=3) # 减少重排序数量
|
||||||
|
else:
|
||||||
|
reranked_matches = best_matches # 如果匹配项太少,跳过重排序以节省时间
|
||||||
|
|
||||||
# 提取代码内容
|
# 提取代码内容
|
||||||
results_with_code = []
|
results_with_code = []
|
||||||
@@ -617,5 +646,19 @@ def get_context(question: str) -> str:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.info(f"Python Executable: {sys.executable}")
|
logging.info(f"Python Executable: {sys.executable}")
|
||||||
logging.info("RimWorld 向量知识库 (FastMCP版, v2.1-v4-model) 正在启动...")
|
logging.info("RimWorld 向量知识库 (FastMCP版, v2.1-v4-model) 正在启动...")
|
||||||
|
|
||||||
|
# 快速启动:延迟初始化重量级组件
|
||||||
|
try:
|
||||||
|
# 验证基本配置
|
||||||
|
if not dashscope.api_key:
|
||||||
|
logging.warning("警告:DASHSCOPE_API_KEY 未配置,部分功能可能受限。")
|
||||||
|
|
||||||
|
# 创建必要目录
|
||||||
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
logging.info("MCP服务器快速启动完成,等待客户端连接...")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"服务器启动时出错: {e}")
|
||||||
|
|
||||||
# 使用 'stdio' 传输协议
|
# 使用 'stdio' 传输协议
|
||||||
mcp.run(transport="stdio")
|
mcp.run(transport="stdio")
|
||||||
105
MCP/rimworld_query.py
Normal file
105
MCP/rimworld_query.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
RimWorld知识库命令行工具
|
||||||
|
快速查询工具,无需Qoder IDE
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 添加MCP路径
|
||||||
|
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||||
|
if SDK_PATH not in sys.path:
|
||||||
|
sys.path.insert(0, SDK_PATH)
|
||||||
|
|
||||||
|
def quick_query(question: str, format_output: bool = True) -> str:
|
||||||
|
"""快速查询函数"""
|
||||||
|
try:
|
||||||
|
# 动态导入避免启动时的依赖检查
|
||||||
|
from mcpserver_stdio import get_context
|
||||||
|
result = get_context(question)
|
||||||
|
|
||||||
|
if format_output:
|
||||||
|
# 格式化输出
|
||||||
|
lines = result.split('\n')
|
||||||
|
formatted_lines = []
|
||||||
|
current_section = ""
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('--- 结果'):
|
||||||
|
current_section = f"\n🔍 {line}"
|
||||||
|
formatted_lines.append(current_section)
|
||||||
|
elif line.startswith('文件路径:'):
|
||||||
|
formatted_lines.append(f"📄 {line}")
|
||||||
|
elif line.strip() and not line.startswith('---'):
|
||||||
|
formatted_lines.append(line)
|
||||||
|
|
||||||
|
return '\n'.join(formatted_lines)
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"❌ 查询失败: {e}"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='RimWorld知识库命令行查询工具',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
使用示例:
|
||||||
|
%(prog)s "ThingDef是什么"
|
||||||
|
%(prog)s "如何创建新的Pawn" --raw
|
||||||
|
%(prog)s "建筑物定义" --output result.txt
|
||||||
|
%(prog)s --list-examples
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('question', nargs='?', help='要查询的问题')
|
||||||
|
parser.add_argument('--raw', action='store_true', help='输出原始结果,不格式化')
|
||||||
|
parser.add_argument('--output', '-o', help='将结果保存到文件')
|
||||||
|
parser.add_argument('--list-examples', action='store_true', help='显示查询示例')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.list_examples:
|
||||||
|
print("📚 RimWorld知识库查询示例:")
|
||||||
|
examples = [
|
||||||
|
"ThingDef的定义和用法",
|
||||||
|
"如何创建新的Building",
|
||||||
|
"Pawn类的主要方法",
|
||||||
|
"CompPower的使用方法",
|
||||||
|
"XML中的defName规则",
|
||||||
|
"GenConstruct.CanPlaceBlueprintAt",
|
||||||
|
"Building_Door的开关逻辑"
|
||||||
|
]
|
||||||
|
for i, example in enumerate(examples, 1):
|
||||||
|
print(f" {i}. {example}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.question:
|
||||||
|
parser.print_help()
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🔍 正在查询: {args.question}")
|
||||||
|
|
||||||
|
result = quick_query(args.question, not args.raw)
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
try:
|
||||||
|
with open(args.output, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(result)
|
||||||
|
print(f"✅ 结果已保存到: {args.output}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 保存文件失败: {e}")
|
||||||
|
else:
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("📖 查询结果:")
|
||||||
|
print("="*60)
|
||||||
|
print(result)
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
123
MCP/test_config.py
Normal file
123
MCP/test_config.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
验证MCP服务器配置和环境
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_mcp_configuration():
|
||||||
|
"""测试MCP配置是否正确"""
|
||||||
|
print("🔍 MCP配置验证工具")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 1. 检查Python环境
|
||||||
|
print(f"✓ Python解释器: {sys.executable}")
|
||||||
|
print(f"✓ Python版本: {sys.version}")
|
||||||
|
|
||||||
|
# 2. 检查工作目录
|
||||||
|
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
print(f"✓ MCP目录: {mcp_dir}")
|
||||||
|
|
||||||
|
# 3. 检查MCP SDK
|
||||||
|
sdk_path = os.path.join(mcp_dir, 'python-sdk', 'src')
|
||||||
|
print(f"✓ SDK路径: {sdk_path}")
|
||||||
|
print(f"✓ SDK存在: {os.path.exists(sdk_path)}")
|
||||||
|
|
||||||
|
# 4. 检查必要文件
|
||||||
|
server_script = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||||
|
env_file = os.path.join(mcp_dir, '.env')
|
||||||
|
print(f"✓ 服务器脚本: {os.path.exists(server_script)}")
|
||||||
|
print(f"✓ 环境文件: {os.path.exists(env_file)}")
|
||||||
|
|
||||||
|
# 5. 检查依赖包
|
||||||
|
try:
|
||||||
|
import mcp
|
||||||
|
print("✓ MCP SDK: 已安装")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ MCP SDK: 未安装 - {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dashscope
|
||||||
|
print("✓ DashScope: 已安装")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ DashScope: 未安装 - {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import openai
|
||||||
|
print("✓ OpenAI: 已安装")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ OpenAI: 未安装 - {e}")
|
||||||
|
|
||||||
|
# 6. 生成正确的配置
|
||||||
|
python_exe = sys.executable.replace("\\", "\\\\")
|
||||||
|
mcp_dir_escaped = mcp_dir.replace("\\", "\\\\")
|
||||||
|
sdk_path_escaped = sdk_path.replace("\\", "\\\\")
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"mcpServers": {
|
||||||
|
"rimworld-knowledge-base": {
|
||||||
|
"command": python_exe,
|
||||||
|
"args": ["mcpserver_stdio.py"],
|
||||||
|
"cwd": mcp_dir_escaped,
|
||||||
|
"disabled": False,
|
||||||
|
"alwaysAllow": [],
|
||||||
|
"env": {
|
||||||
|
"PYTHONPATH": sdk_path_escaped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"rimworld-knowledge-base": {
|
||||||
|
"description": "从RimWorld本地知识库(包括C#源码和XML)中检索上下文。",
|
||||||
|
"server_name": "rimworld-knowledge-base",
|
||||||
|
"tool_name": "get_context",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"question": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "关于RimWorld开发的问题,应包含代码或XML中的关键词。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["question"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\\n📋 建议的MCP配置:")
|
||||||
|
print("=" * 50)
|
||||||
|
print(json.dumps(config, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
# 7. 测试服务器启动
|
||||||
|
print("\\n🚀 测试服务器启动:")
|
||||||
|
print("=" * 50)
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, server_script],
|
||||||
|
cwd=mcp_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print("✓ 服务器可以正常启动")
|
||||||
|
else:
|
||||||
|
print(f"❌ 服务器启动失败: {result.stderr}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("✓ 服务器启动正常(超时保护触发)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 服务器测试失败: {e}")
|
||||||
|
|
||||||
|
print("\\n🎯 配置建议:")
|
||||||
|
print("=" * 50)
|
||||||
|
print("1. 复制上面的配置到 Qoder IDE 的 MCP 设置中")
|
||||||
|
print("2. 确保所有依赖包都已安装")
|
||||||
|
print("3. 检查 .env 文件中的 API Key 配置")
|
||||||
|
print("4. 重启 Qoder IDE 并重新连接 MCP 服务器")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_mcp_configuration()
|
||||||
149
MCP/test_mcp.py
Normal file
149
MCP/test_mcp.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
最终功能测试:验证MCP服务器是否能正常工作
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_mcp_server_final():
|
||||||
|
"""最终测试MCP服务器功能"""
|
||||||
|
print("🔥 MCP服务器最终功能测试")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 获取当前目录
|
||||||
|
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 验证SDK安装
|
||||||
|
try:
|
||||||
|
import mcp
|
||||||
|
print("✅ MCP SDK: 已正确安装")
|
||||||
|
except ImportError:
|
||||||
|
print("❌ MCP SDK: 未安装")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2. 启动服务器
|
||||||
|
print("🚀 启动MCP服务器...")
|
||||||
|
process = subprocess.Popen(
|
||||||
|
[sys.executable, script_path],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
cwd=mcp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# 等待启动
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 3. 初始化测试
|
||||||
|
init_request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "initialize",
|
||||||
|
"params": {
|
||||||
|
"protocolVersion": "2024-11-05",
|
||||||
|
"capabilities": {},
|
||||||
|
"clientInfo": {
|
||||||
|
"name": "final-test-client",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("📡 发送初始化请求...")
|
||||||
|
process.stdin.write(json.dumps(init_request) + '\n')
|
||||||
|
process.stdin.flush()
|
||||||
|
|
||||||
|
# 读取初始化响应
|
||||||
|
response = process.stdout.readline()
|
||||||
|
if response:
|
||||||
|
response_data = json.loads(response.strip())
|
||||||
|
if "result" in response_data:
|
||||||
|
print("✅ 初始化成功")
|
||||||
|
print(f" 服务器名称: {response_data['result'].get('serverInfo', {}).get('name', 'unknown')}")
|
||||||
|
print(f" 服务器版本: {response_data['result'].get('serverInfo', {}).get('version', 'unknown')}")
|
||||||
|
else:
|
||||||
|
print("❌ 初始化失败")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("❌ 初始化无响应")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 4. 工具列表测试
|
||||||
|
tools_request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "tools/list"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("🔧 请求工具列表...")
|
||||||
|
process.stdin.write(json.dumps(tools_request) + '\n')
|
||||||
|
process.stdin.flush()
|
||||||
|
|
||||||
|
tools_response = process.stdout.readline()
|
||||||
|
if tools_response:
|
||||||
|
tools_data = json.loads(tools_response.strip())
|
||||||
|
if "result" in tools_data and "tools" in tools_data["result"]:
|
||||||
|
tools = tools_data["result"]["tools"]
|
||||||
|
print(f"✅ 发现 {len(tools)} 个工具:")
|
||||||
|
for tool in tools:
|
||||||
|
print(f" - {tool.get('name', 'unknown')}: {tool.get('description', 'no description')}")
|
||||||
|
else:
|
||||||
|
print("❌ 获取工具列表失败")
|
||||||
|
else:
|
||||||
|
print("❌ 工具列表请求无响应")
|
||||||
|
|
||||||
|
print("\n🎯 测试结果:")
|
||||||
|
print("✅ MCP服务器能够正常启动")
|
||||||
|
print("✅ 初始化协议工作正常")
|
||||||
|
print("✅ 工具发现机制正常")
|
||||||
|
print("\n✨ 所有基本功能测试通过!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 测试过程中出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 清理进程
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
process.kill()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("开始最终测试...")
|
||||||
|
success = test_mcp_server_final()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 恭喜!MCP服务器已完全修复并正常工作!")
|
||||||
|
print("\n📋 现在您需要在Qoder IDE中更新配置:")
|
||||||
|
print("1. 打开Qoder IDE设置 → MCP")
|
||||||
|
print("2. 更新配置文件,确保使用正确的绝对路径")
|
||||||
|
print("3. 重启Qoder IDE")
|
||||||
|
print("4. 在Agent模式下测试知识库查询")
|
||||||
|
print("\n建议的配置:")
|
||||||
|
print(json.dumps({
|
||||||
|
"mcpServers": {
|
||||||
|
"rimworld-knowledge-base": {
|
||||||
|
"command": sys.executable,
|
||||||
|
"args": ["mcpserver_stdio.py"],
|
||||||
|
"cwd": os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"disabled": False,
|
||||||
|
"alwaysAllow": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, indent=2))
|
||||||
|
else:
|
||||||
|
print("\n❌ 仍存在问题,需要进一步调试")
|
||||||
150
MCP/test_mcp_timeout_fix.py
Normal file
150
MCP/test_mcp_timeout_fix.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
测试MCP服务器超时修复
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_mcp_server_timeout_fix():
|
||||||
|
"""测试MCP服务器是否能快速启动并响应"""
|
||||||
|
print("开始测试MCP服务器超时修复...")
|
||||||
|
|
||||||
|
# 获取当前目录
|
||||||
|
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 启动MCP服务器进程
|
||||||
|
print("启动MCP服务器...")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
[sys.executable, script_path],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
cwd=mcp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# 等待服务器启动(减少等待时间)
|
||||||
|
time.sleep(2) # 从3秒减少到2秒
|
||||||
|
|
||||||
|
startup_time = time.time() - start_time
|
||||||
|
print(f"服务器启动耗时: {startup_time:.2f}秒")
|
||||||
|
|
||||||
|
# 发送初始化请求
|
||||||
|
init_request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "initialize",
|
||||||
|
"params": {
|
||||||
|
"protocolVersion": "2024-11-05",
|
||||||
|
"capabilities": {},
|
||||||
|
"clientInfo": {
|
||||||
|
"name": "test-client",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("发送初始化请求...")
|
||||||
|
request_start = time.time()
|
||||||
|
process.stdin.write(json.dumps(init_request) + '\n')
|
||||||
|
process.stdin.flush()
|
||||||
|
|
||||||
|
# 读取响应
|
||||||
|
response_line = process.stdout.readline()
|
||||||
|
init_time = time.time() - request_start
|
||||||
|
|
||||||
|
if response_line:
|
||||||
|
print(f"✅ 初始化成功,耗时: {init_time:.2f}秒")
|
||||||
|
print(f"收到响应: {response_line.strip()}")
|
||||||
|
else:
|
||||||
|
print("❌ 初始化失败:无响应")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 发送简单的工具调用请求
|
||||||
|
tool_request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "tools/call",
|
||||||
|
"params": {
|
||||||
|
"name": "get_context",
|
||||||
|
"arguments": {
|
||||||
|
"question": "ThingDef" # 简单的测试查询
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("发送工具调用请求...")
|
||||||
|
tool_start = time.time()
|
||||||
|
process.stdin.write(json.dumps(tool_request) + '\n')
|
||||||
|
process.stdin.flush()
|
||||||
|
|
||||||
|
# 等待响应(减少超时时间)
|
||||||
|
timeout = 20 # 从30秒减少到20秒
|
||||||
|
response_received = False
|
||||||
|
|
||||||
|
while time.time() - tool_start < timeout:
|
||||||
|
if process.poll() is not None:
|
||||||
|
print("服务器进程已退出")
|
||||||
|
break
|
||||||
|
|
||||||
|
response_line = process.stdout.readline()
|
||||||
|
if response_line:
|
||||||
|
tool_time = time.time() - tool_start
|
||||||
|
print(f"✅ 工具调用成功,耗时: {tool_time:.2f}秒")
|
||||||
|
print(f"工具调用响应: {response_line.strip()[:200]}...") # 只显示前200个字符
|
||||||
|
response_received = True
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
total_time = time.time() - start_time
|
||||||
|
|
||||||
|
if response_received:
|
||||||
|
print(f"✅ 测试成功:MCP服务器能够正常处理请求")
|
||||||
|
print(f"总耗时: {total_time:.2f}秒")
|
||||||
|
|
||||||
|
# 性能评估
|
||||||
|
if total_time < 15:
|
||||||
|
print("🚀 性能优秀:服务器响应速度很快")
|
||||||
|
elif total_time < 25:
|
||||||
|
print("✅ 性能良好:服务器响应速度可接受")
|
||||||
|
else:
|
||||||
|
print("⚠️ 性能一般:服务器响应较慢,可能仍有超时风险")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("❌ 测试失败:超时未收到响应")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 测试出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 清理进程
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
process.kill()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_mcp_server_timeout_fix()
|
||||||
|
if success:
|
||||||
|
print("\n🎉 MCP服务器超时问题已修复!")
|
||||||
|
print("现在可以在Qoder IDE中重新连接MCP服务器了。")
|
||||||
|
else:
|
||||||
|
print("\n❌ MCP服务器仍存在问题,需要进一步调试。")
|
||||||
File diff suppressed because one or more lines are too long
249
MCP/web_api_server.py
Normal file
249
MCP/web_api_server.py
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
RimWorld知识库Web API服务器
|
||||||
|
提供HTTP接口,可以通过浏览器或HTTP客户端访问
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
import threading
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
# 添加MCP路径
|
||||||
|
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||||
|
if SDK_PATH not in sys.path:
|
||||||
|
sys.path.insert(0, SDK_PATH)
|
||||||
|
|
||||||
|
class RimWorldAPIHandler(BaseHTTPRequestHandler):
|
||||||
|
"""HTTP请求处理器"""
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
"""处理GET请求"""
|
||||||
|
parsed_url = urlparse(self.path)
|
||||||
|
|
||||||
|
if parsed_url.path == '/':
|
||||||
|
self.serve_web_interface()
|
||||||
|
elif parsed_url.path == '/query':
|
||||||
|
self.handle_query_get(parsed_url)
|
||||||
|
elif parsed_url.path == '/api/query':
|
||||||
|
self.handle_api_query_get(parsed_url)
|
||||||
|
else:
|
||||||
|
self.send_error(404, "Not Found")
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
"""处理POST请求"""
|
||||||
|
if self.path == '/api/query':
|
||||||
|
self.handle_api_query_post()
|
||||||
|
else:
|
||||||
|
self.send_error(404, "Not Found")
|
||||||
|
|
||||||
|
def serve_web_interface(self):
|
||||||
|
"""提供Web界面"""
|
||||||
|
html = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>RimWorld 知识库</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||||
|
.header { text-align: center; margin-bottom: 30px; }
|
||||||
|
.query-box { margin-bottom: 20px; }
|
||||||
|
input[type="text"] { width: 70%; padding: 10px; font-size: 16px; }
|
||||||
|
button { padding: 10px 20px; font-size: 16px; background: #007cba; color: white; border: none; cursor: pointer; }
|
||||||
|
button:hover { background: #005a8b; }
|
||||||
|
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; white-space: pre-wrap; }
|
||||||
|
.loading { color: #666; font-style: italic; }
|
||||||
|
.examples { margin-top: 20px; }
|
||||||
|
.example { cursor: pointer; color: #007cba; margin: 5px 0; }
|
||||||
|
.example:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎮 RimWorld 知识库</h1>
|
||||||
|
<p>直接查询RimWorld游戏的C#源码和XML定义</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="query-box">
|
||||||
|
<input type="text" id="queryInput" placeholder="输入您的问题,例如:ThingDef是什么?" onkeypress="handleKeyPress(event)">
|
||||||
|
<button onclick="performQuery()">🔍 查询</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="examples">
|
||||||
|
<h3>💡 查询示例:</h3>
|
||||||
|
<div class="example" onclick="setQuery('ThingDef的定义和用法')">• ThingDef的定义和用法</div>
|
||||||
|
<div class="example" onclick="setQuery('如何创建Building')">• 如何创建Building</div>
|
||||||
|
<div class="example" onclick="setQuery('Pawn类的主要方法')">• Pawn类的主要方法</div>
|
||||||
|
<div class="example" onclick="setQuery('CompPower的使用')">• CompPower的使用</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="result" class="result" style="display: none;"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function performQuery() {
|
||||||
|
const input = document.getElementById('queryInput');
|
||||||
|
const result = document.getElementById('result');
|
||||||
|
const query = input.value.trim();
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
alert('请输入查询问题');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.style.display = 'block';
|
||||||
|
result.textContent = '🔄 正在查询,请稍候...';
|
||||||
|
result.className = 'result loading';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/query', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({question: query})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
result.textContent = data.result;
|
||||||
|
result.className = 'result';
|
||||||
|
} else {
|
||||||
|
result.textContent = '❌ 查询失败: ' + data.error;
|
||||||
|
result.className = 'result';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
result.textContent = '❌ 网络错误: ' + error.message;
|
||||||
|
result.className = 'result';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setQuery(query) {
|
||||||
|
document.getElementById('queryInput').value = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyPress(event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
performQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html.encode('utf-8'))
|
||||||
|
|
||||||
|
def handle_query_get(self, parsed_url):
|
||||||
|
"""处理GET查询请求"""
|
||||||
|
params = parse_qs(parsed_url.query)
|
||||||
|
question = params.get('q', [''])[0]
|
||||||
|
|
||||||
|
if not question:
|
||||||
|
self.send_error(400, "Missing 'q' parameter")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from mcpserver_stdio import get_context
|
||||||
|
result = get_context(question)
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/plain; charset=utf-8')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(result.encode('utf-8'))
|
||||||
|
except Exception as e:
|
||||||
|
self.send_error(500, f"Query failed: {e}")
|
||||||
|
|
||||||
|
def handle_api_query_get(self, parsed_url):
|
||||||
|
"""处理API GET查询"""
|
||||||
|
params = parse_qs(parsed_url.query)
|
||||||
|
question = params.get('q', [''])[0]
|
||||||
|
|
||||||
|
if not question:
|
||||||
|
response = {"success": False, "error": "Missing 'q' parameter"}
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from mcpserver_stdio import get_context
|
||||||
|
result = get_context(question)
|
||||||
|
response = {"success": True, "result": result}
|
||||||
|
except Exception as e:
|
||||||
|
response = {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||||
|
|
||||||
|
def handle_api_query_post(self):
|
||||||
|
"""处理API POST查询"""
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data = self.rfile.read(content_length)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(post_data.decode('utf-8'))
|
||||||
|
question = data.get('question', '')
|
||||||
|
|
||||||
|
if not question:
|
||||||
|
response = {"success": False, "error": "Missing 'question' field"}
|
||||||
|
else:
|
||||||
|
from mcpserver_stdio import get_context
|
||||||
|
result = get_context(question)
|
||||||
|
response = {"success": True, "result": result}
|
||||||
|
except Exception as e:
|
||||||
|
response = {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
"""自定义日志输出"""
|
||||||
|
print(f"[{self.address_string()}] {format % args}")
|
||||||
|
|
||||||
|
def start_server(port=8080, open_browser=True):
|
||||||
|
"""启动Web服务器"""
|
||||||
|
server_address = ('', port)
|
||||||
|
httpd = HTTPServer(server_address, RimWorldAPIHandler)
|
||||||
|
|
||||||
|
print(f"🌐 RimWorld知识库Web服务器启动")
|
||||||
|
print(f"📍 服务地址: http://localhost:{port}")
|
||||||
|
print(f"🔍 查询API: http://localhost:{port}/api/query?q=您的问题")
|
||||||
|
print(f"💻 Web界面: http://localhost:{port}")
|
||||||
|
print("按 Ctrl+C 停止服务器")
|
||||||
|
|
||||||
|
if open_browser:
|
||||||
|
# 延迟打开浏览器
|
||||||
|
def open_browser_delayed():
|
||||||
|
import time
|
||||||
|
time.sleep(1)
|
||||||
|
webbrowser.open(f'http://localhost:{port}')
|
||||||
|
|
||||||
|
thread = threading.Thread(target=open_browser_delayed)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n🛑 服务器已停止")
|
||||||
|
httpd.shutdown()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='RimWorld知识库Web API服务器')
|
||||||
|
parser.add_argument('--port', '-p', type=int, default=8080, help='服务器端口 (默认: 8080)')
|
||||||
|
parser.add_argument('--no-browser', action='store_true', help='不自动打开浏览器')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
start_server(args.port, not args.no_browser)
|
||||||
102
MCP/使用指南.md
Normal file
102
MCP/使用指南.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# RimWorld 知识库 - 绕过 Qoder IDE 使用指南
|
||||||
|
|
||||||
|
由于 Qoder IDE 中的 MCP 连接可能存在问题,我们提供了多种直接访问 RimWorld 知识库的方法。
|
||||||
|
|
||||||
|
## 🚀 方法 1:直接 Python 调用
|
||||||
|
|
||||||
|
最简单直接的方法:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 直接查询
|
||||||
|
python direct_mcp_client.py -q "ThingDef是什么"
|
||||||
|
|
||||||
|
# 交互模式
|
||||||
|
python direct_mcp_client.py -i
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
python direct_mcp_client.py -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优点:
|
||||||
|
- ✅ 最快速,无需额外依赖
|
||||||
|
- ✅ 支持交互模式
|
||||||
|
- ✅ 直接在命令行使用
|
||||||
|
|
||||||
|
## 🛠️ 方法 2:命令行工具
|
||||||
|
|
||||||
|
专业的命令行查询工具:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本查询
|
||||||
|
python rimworld_query.py "ThingDef的定义"
|
||||||
|
|
||||||
|
# 保存结果到文件
|
||||||
|
python rimworld_query.py "Building类的方法" --output building_info.txt
|
||||||
|
|
||||||
|
# 显示原始结果(不格式化)
|
||||||
|
python rimworld_query.py "Pawn类" --raw
|
||||||
|
|
||||||
|
# 查看示例
|
||||||
|
python rimworld_query.py --list-examples
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优点:
|
||||||
|
- ✅ 结果可保存到文件
|
||||||
|
- ✅ 支持原始输出格式
|
||||||
|
- ✅ 内置查询示例
|
||||||
|
|
||||||
|
## 📝 常用查询示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查询类定义
|
||||||
|
"ThingDef的定义和用法"
|
||||||
|
"Building类有哪些方法"
|
||||||
|
"Pawn类的构造函数"
|
||||||
|
|
||||||
|
# 查询特定方法
|
||||||
|
"GenConstruct.CanPlaceBlueprintAt 方法"
|
||||||
|
"Building_Door 的开关逻辑"
|
||||||
|
"CompPower 的电力管理"
|
||||||
|
|
||||||
|
# 查询XML相关
|
||||||
|
"XML中的defName规则"
|
||||||
|
"如何定义新的ThingDef"
|
||||||
|
"建筑物的XML结构"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 故障排除
|
||||||
|
|
||||||
|
### 如果出现导入错误:
|
||||||
|
```bash
|
||||||
|
# 确保在正确的目录
|
||||||
|
cd "C:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP"
|
||||||
|
|
||||||
|
# 检查 Python 环境
|
||||||
|
python -c "import mcp; print('MCP SDK 正常')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 如果查询结果为空:
|
||||||
|
- 尝试使用更具体的关键词
|
||||||
|
- 检查关键词拼写
|
||||||
|
- 使用英文类名或方法名
|
||||||
|
|
||||||
|
### 如果 Web 服务器无法启动:
|
||||||
|
- 检查端口是否被占用
|
||||||
|
- 尝试使用不同的端口号
|
||||||
|
- 确保没有其他程序占用该端口
|
||||||
|
|
||||||
|
## 💡 推荐使用场景
|
||||||
|
|
||||||
|
- **快速查询**: 使用方法 1 (direct_mcp_client.py)
|
||||||
|
- **批量处理**: 使用方法 2 (rimworld_query.py)
|
||||||
|
- **团队共享**: 使用方法 3 (web_api_server.py)
|
||||||
|
- **集成开发**: 使用 Web API 接口
|
||||||
|
|
||||||
|
## 🎯 性能优化
|
||||||
|
|
||||||
|
所有方法都已经过优化:
|
||||||
|
- 向量化处理限制在 10 个文件以内
|
||||||
|
- API 调用超时设置为 12-15 秒
|
||||||
|
- 支持本地缓存加速重复查询
|
||||||
|
|
||||||
|
现在您可以完全绕过 Qoder IDE,直接使用 RimWorld 知识库了!
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
{
|
{
|
||||||
"name": "3516260226",
|
"name": "3516260226",
|
||||||
"path": "../.."
|
"path": "../.."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../../../../../workshop/content/294100/3551234893/1.6/Assemblies/ShelterShuttle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {}
|
||||||
|
|||||||
@@ -106,20 +106,7 @@ namespace WulaFallenEmpire
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual Texture2D EnterTex => DefaultEnterTex;
|
protected virtual Texture2D EnterTex => DefaultEnterTex;
|
||||||
|
|
||||||
/// <summary>
|
// 人员传送相关属性已删除 - 不再使用Dialog_EnterPortal
|
||||||
/// 获取进入按钮的文本 - 专门用于人员传送
|
|
||||||
/// </summary>
|
|
||||||
public virtual string EnterString => "WULA.PocketSpace.EnterPawns".Translate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取取消进入按钮的文本
|
|
||||||
/// </summary>
|
|
||||||
public virtual string CancelEnterString => "WULA.PocketSpace.CancelEnter".Translate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取进入中的文本
|
|
||||||
/// </summary>
|
|
||||||
public virtual string EnteringString => "WULA.PocketSpace.Entering".Translate();
|
|
||||||
|
|
||||||
/// <summary>加载是否正在进行(模仿原版 MapPortal.LoadInProgress)</summary>
|
/// <summary>加载是否正在进行(模仿原版 MapPortal.LoadInProgress)</summary>
|
||||||
public bool LoadInProgress
|
public bool LoadInProgress
|
||||||
@@ -288,85 +275,7 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
#region 口袋空间核心方法
|
#region 口袋空间核心方法
|
||||||
|
|
||||||
/// <summary>
|
// 人员传送方法已删除 - 不再使用Dialog_EnterPortal进行人员传送
|
||||||
/// 检查是否可以进入口袋空间
|
|
||||||
/// </summary>
|
|
||||||
public bool CanEnterPocketSpace()
|
|
||||||
{
|
|
||||||
if (!allowDirectAccess)
|
|
||||||
{
|
|
||||||
return false; // 需要特殊权限
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Spawned)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 进入口袋空间 - 基于原版PocketMapUtility实现
|
|
||||||
/// </summary>
|
|
||||||
public void EnterPocketSpace(IEnumerable<Pawn> pawns = null)
|
|
||||||
{
|
|
||||||
if (!CanEnterPocketSpace())
|
|
||||||
{
|
|
||||||
Messages.Message("WULA.PocketSpace.CannotEnter".Translate(), this, MessageTypeDefOf.RejectInput);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建或获取口袋地图
|
|
||||||
if (pocketMap == null && !pocketMapGenerated)
|
|
||||||
{
|
|
||||||
CreatePocketMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pocketMap == null)
|
|
||||||
{
|
|
||||||
Messages.Message("WULA.PocketSpace.CreationFailed".Translate(), this, MessageTypeDefOf.RejectInput);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 传送玩家到口袋空间
|
|
||||||
List<Pawn> pawnsToTransfer = new List<Pawn>();
|
|
||||||
|
|
||||||
if (pawns != null)
|
|
||||||
{
|
|
||||||
pawnsToTransfer.AddRange(pawns.Where(p => p != null && p.Spawned && p.IsColonist));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果没有指定人员,传送选中的殖民者
|
|
||||||
pawnsToTransfer.AddRange(Find.Selector.SelectedPawns.Where(p => p.IsColonist));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pawnsToTransfer.Count == 0)
|
|
||||||
{
|
|
||||||
Messages.Message("WULA.PocketSpace.NoPawnsSelected".Translate(), this, MessageTypeDefOf.RejectInput);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行传送
|
|
||||||
int transferredCount = 0;
|
|
||||||
foreach (Pawn pawn in pawnsToTransfer)
|
|
||||||
{
|
|
||||||
if (TransferPawnToPocketSpace(pawn))
|
|
||||||
{
|
|
||||||
transferredCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transferredCount > 0)
|
|
||||||
{
|
|
||||||
Messages.Message("WULA.PocketSpace.TransferSuccess".Translate(transferredCount), MessageTypeDefOf.PositiveEvent);
|
|
||||||
|
|
||||||
// 切换到口袋地图
|
|
||||||
Current.Game.CurrentMap = pocketMap;
|
|
||||||
Find.CameraDriver.JumpToCurrentMapLoc(pocketMap.Center);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 切换到口袋空间视角
|
/// 切换到口袋空间视角
|
||||||
@@ -496,33 +405,7 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// TransferPawnToPocketSpace方法已删除 - 不再使用单个人员传送
|
||||||
/// 将单个Pawn传送到口袋空间
|
|
||||||
/// </summary>
|
|
||||||
private bool TransferPawnToPocketSpace(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null || !pawn.Spawned || pocketMap == null) return false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 找一个安全的位置
|
|
||||||
IntVec3 spawnPos = CellFinder.RandomClosewalkCellNear(pocketMap.Center, pocketMap, 10,
|
|
||||||
p => p.Standable(pocketMap) && !p.GetThingList(pocketMap).Any(t => t is Pawn));
|
|
||||||
|
|
||||||
if (spawnPos.IsValid)
|
|
||||||
{
|
|
||||||
pawn.DeSpawn();
|
|
||||||
GenPlace.TryPlaceThing(pawn, spawnPos, pocketMap, ThingPlaceMode.Near);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error($"[WULA] Error transferring pawn {pawn?.LabelShort} to pocket space: {ex}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将所有物品和人员从口袋空间转移到主地图
|
/// 将所有物品和人员从口袋空间转移到主地图
|
||||||
@@ -627,40 +510,7 @@ namespace WulaFallenEmpire
|
|||||||
createCommand.disabledReason = reason;
|
createCommand.disabledReason = reason;
|
||||||
yield return createCommand;
|
yield return createCommand;
|
||||||
}
|
}
|
||||||
else
|
// 进入口袋空间按钮已删除 - 按钮无法正常工作
|
||||||
{
|
|
||||||
// 进入口袋空间按钮(直接复制原版MapPortal的逻辑)
|
|
||||||
Command_Action enterCommand = new Command_Action();
|
|
||||||
enterCommand.action = delegate
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Log.Message("[WULA] Creating MapPortalAdapter...");
|
|
||||||
var adapter = new MapPortalAdapter(this);
|
|
||||||
Log.Message($"[WULA] Adapter created. Map: {adapter.Map?.uniqueID}, Spawned: {adapter.Spawned}");
|
|
||||||
|
|
||||||
Log.Message("[WULA] Creating Dialog_EnterPortal...");
|
|
||||||
Dialog_EnterPortal window = new Dialog_EnterPortal(adapter);
|
|
||||||
Log.Message("[WULA] Dialog created, adding to WindowStack...");
|
|
||||||
Find.WindowStack.Add(window);
|
|
||||||
Log.Message("[WULA] Dialog added to WindowStack successfully.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error($"[WULA] Error opening Dialog_EnterPortal: {ex}");
|
|
||||||
Messages.Message("WULA.PocketSpace.LoadingDialogError".Translate(), MessageTypeDefOf.RejectInput);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
enterCommand.icon = EnterTex;
|
|
||||||
enterCommand.defaultLabel = EnterString + "...";
|
|
||||||
enterCommand.defaultDesc = "WULA.PocketSpace.EnterDesc".Translate();
|
|
||||||
|
|
||||||
// 检查是否可以进入(模仿原版MapPortal.IsEnterable)
|
|
||||||
string reason;
|
|
||||||
enterCommand.Disabled = !IsEnterable(out reason);
|
|
||||||
enterCommand.disabledReason = reason;
|
|
||||||
yield return enterCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看口袋地图按钮(模仿原版MapPortal)
|
// 查看口袋地图按钮(模仿原版MapPortal)
|
||||||
if (pocketMap != null)
|
if (pocketMap != null)
|
||||||
@@ -695,9 +545,19 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
public ThingOwner GetDirectlyHeldThings()
|
public ThingOwner GetDirectlyHeldThings()
|
||||||
{
|
{
|
||||||
// 返回containerProxy,与Dialog_EnterPortal兼容
|
// 仅在第一次调用时记录日志
|
||||||
|
if (containerProxy != null)
|
||||||
|
{
|
||||||
return containerProxy;
|
return containerProxy;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] containerProxy is null! Creating new one...");
|
||||||
|
containerProxy = new PortalContainerProxy { portal = this };
|
||||||
|
Log.Message($"[WULA] Created new containerProxy - portal set: {containerProxy.portal != null}");
|
||||||
|
return containerProxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void GetChildHolders(List<IThingHolder> outChildren)
|
public void GetChildHolders(List<IThingHolder> outChildren)
|
||||||
{
|
{
|
||||||
@@ -706,7 +566,7 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region MapPortal兼容接口(使Dialog_EnterPortal能正常工作)
|
#region MapPortal兼容接口(用于物品传送和地图管理)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查是否可以进入(模仿原版MapPortal.IsEnterable)
|
/// 检查是否可以进入(模仿原版MapPortal.IsEnterable)
|
||||||
@@ -753,21 +613,7 @@ namespace WulaFallenEmpire
|
|||||||
return pocketMap?.Center ?? IntVec3.Invalid;
|
return pocketMap?.Center ?? IntVec3.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// OnEntered方法已删除 - 不再使用Dialog_EnterPortal机制
|
||||||
/// 处理进入事件(模仿原版MapPortal.OnEntered)
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnEntered(Pawn pawn)
|
|
||||||
{
|
|
||||||
// 通知物品被添加(用于统计和管理)
|
|
||||||
Notify_ThingAdded(pawn);
|
|
||||||
|
|
||||||
// 播放传送音效(如果存在)
|
|
||||||
if (Find.CurrentMap == this.Map)
|
|
||||||
{
|
|
||||||
// 可以在这里添加音效播放
|
|
||||||
// def.portal?.traverseSound?.PlayOneShot(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -778,13 +624,23 @@ namespace WulaFallenEmpire
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Notify_ThingAdded(Thing t)
|
public void Notify_ThingAdded(Thing t)
|
||||||
{
|
{
|
||||||
Log.Message($"[WULA] Notify_ThingAdded called for: {t?.def?.defName} x{t?.stackCount}");
|
// 检查是否为Pawn类型
|
||||||
|
if (t is Pawn pawn)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Notify_ThingAdded called for PAWN: {pawn.LabelShort} ({pawn.def.defName})");
|
||||||
|
// 对于Pawn,我们不需要更新leftToLoad列表,因为原版也不这样做
|
||||||
|
// 只需要通知CompTransporter
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Notify_ThingAdded called for ITEM: {t?.def?.defName} x{t?.stackCount}");
|
||||||
Log.Message($"[WULA] leftToLoad count before: {leftToLoad?.Count ?? 0}");
|
Log.Message($"[WULA] leftToLoad count before: {leftToLoad?.Count ?? 0}");
|
||||||
|
|
||||||
int removedCount = SubtractFromToLoadList(t, t.stackCount);
|
int removedCount = SubtractFromToLoadList(t, t.stackCount);
|
||||||
|
|
||||||
Log.Message($"[WULA] Removed {removedCount} items from leftToLoad list");
|
Log.Message($"[WULA] Removed {removedCount} items from leftToLoad list");
|
||||||
Log.Message($"[WULA] leftToLoad count after: {leftToLoad?.Count ?? 0}");
|
Log.Message($"[WULA] leftToLoad count after: {leftToLoad?.Count ?? 0}");
|
||||||
|
}
|
||||||
|
|
||||||
// 同时通知CompTransporter组件,确保原版装载系统也得到通知
|
// 同时通知CompTransporter组件,确保原版装载系统也得到通知
|
||||||
var compTransporter = this.GetComp<CompTransporter>();
|
var compTransporter = this.GetComp<CompTransporter>();
|
||||||
@@ -981,6 +837,28 @@ namespace WulaFallenEmpire
|
|||||||
portal = this
|
portal = this
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 关键:替换CompTransporter的innerContainer字段,确保一切添加操作都经过我们的代理
|
||||||
|
var compTransporter = this.GetComp<CompTransporter>();
|
||||||
|
if (compTransporter != null)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
var field = typeof(CompTransporter).GetField("innerContainer",
|
||||||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
field.SetValue(compTransporter, containerProxy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error("[WULA] Failed to find innerContainer field in CompTransporter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Error replacing CompTransporter.innerContainer: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新退出点目标(处理穿梭机重新部署的情况)
|
// 更新退出点目标(处理穿梭机重新部署的情况)
|
||||||
UpdateExitPointTarget();
|
UpdateExitPointTarget();
|
||||||
|
|
||||||
@@ -1045,175 +923,25 @@ namespace WulaFallenEmpire
|
|||||||
public bool allowDirectAccess = true;
|
public bool allowDirectAccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// MapPortalAdapter类已删除 - 不再使用Dialog_EnterPortal进行人员传送
|
||||||
/// MapPortal适配器类,使非MapPortal类型能够使用Dialog_EnterPortal
|
|
||||||
/// 直接继承MapPortal并委托给Building_ArmedShuttleWithPocket实现
|
|
||||||
/// </summary>
|
|
||||||
public class MapPortalAdapter : MapPortal
|
|
||||||
{
|
|
||||||
private Building_ArmedShuttleWithPocket shuttleBuilding;
|
|
||||||
|
|
||||||
public MapPortalAdapter(Building_ArmedShuttleWithPocket shuttle)
|
|
||||||
{
|
|
||||||
Log.Message($"[WULA] MapPortalAdapter constructor called for shuttle: {shuttle?.def?.defName}");
|
|
||||||
shuttleBuilding = shuttle;
|
|
||||||
|
|
||||||
// 确保基础属性正确设置
|
|
||||||
this.def = shuttle.def ?? ThingDefOf.Wall; // 提供默认值避免null
|
|
||||||
this.HitPoints = shuttle.HitPoints;
|
|
||||||
|
|
||||||
// 关键:手动设置Map和Position属性以避免null引用
|
|
||||||
if (shuttle.Spawned && shuttle.Map != null)
|
|
||||||
{
|
|
||||||
// 手动调用父类的SpawnSetup,但要小心处理
|
|
||||||
try
|
|
||||||
{
|
|
||||||
base.SpawnSetup(shuttle.Map, false);
|
|
||||||
Log.Message($"[WULA] SpawnSetup completed for map: {shuttle.Map.uniqueID}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning($"[WULA] SpawnSetup failed, manually setting properties: {ex.Message}");
|
|
||||||
// 如果SpawnSetup失败,手动设置关键属性
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置基础MapPortal属性
|
|
||||||
this.pocketMap = shuttle.PocketMapForPortal;
|
|
||||||
this.leftToLoad = shuttle.leftToLoad ?? new List<TransferableOneWay>();
|
|
||||||
|
|
||||||
// 确保exit属性被正确设置
|
|
||||||
// 注意:由于类型不兼容,暂时设为null,在GetDestinationLocation中处理
|
|
||||||
this.exit = null; // 原版PocketMapExit类型与我们的Building_PocketMapExit不兼容
|
|
||||||
|
|
||||||
Log.Message($"[WULA] Synced pocketMap: {pocketMap?.uniqueID}, leftToLoad count: {leftToLoad?.Count}, exit: {exit != null}");
|
|
||||||
|
|
||||||
// 使用原版的PortalContainerProxy
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.containerProxy = new RimWorld.PortalContainerProxy
|
|
||||||
{
|
|
||||||
portal = this
|
|
||||||
};
|
|
||||||
Log.Message("[WULA] Created RimWorld.PortalContainerProxy successfully");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error($"[WULA] Failed to create RimWorld.PortalContainerProxy: {ex}");
|
|
||||||
// 使用我们自己的实现作为回退
|
|
||||||
Log.Message("[WULA] Using custom PortalContainerProxy as fallback");
|
|
||||||
}
|
|
||||||
Log.Message("[WULA] MapPortalAdapter initialization complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 委托给shuttleBuilding的关键属性(使用new隐藏基类属性)
|
|
||||||
// 委托给shuttleBuilding的关键属性(使用new隐藏基类属性)
|
|
||||||
public new Map Map
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// 优先返回shuttleBuilding的Map
|
|
||||||
if (shuttleBuilding?.Map != null)
|
|
||||||
{
|
|
||||||
return shuttleBuilding.Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果shuttleBuilding的Map为null,返回基类的Map
|
|
||||||
if (base.Map != null)
|
|
||||||
{
|
|
||||||
return base.Map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后的回退:返回当前游戏地图(避免null)
|
|
||||||
Log.Warning("[WULA] Both shuttleBuilding.Map and base.Map are null, using Current.Game.CurrentMap as fallback");
|
|
||||||
return Find.CurrentMap ?? Current.Game.Maps?.FirstOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public new IntVec3 Position => shuttleBuilding?.Position ?? base.Position;
|
|
||||||
public new bool Spawned => shuttleBuilding?.Spawned ?? base.Spawned;
|
|
||||||
public new string Label => shuttleBuilding?.Label ?? base.Label;
|
|
||||||
|
|
||||||
// 委托给shuttleBuilding的关键方法(重写虚拟方法)
|
|
||||||
public override bool IsEnterable(out string reason)
|
|
||||||
{
|
|
||||||
return shuttleBuilding.IsEnterable(out reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Map GetOtherMap()
|
|
||||||
{
|
|
||||||
return shuttleBuilding.GetOtherMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IntVec3 GetDestinationLocation()
|
|
||||||
{
|
|
||||||
return shuttleBuilding.GetDestinationLocation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEntered(Pawn pawn)
|
|
||||||
{
|
|
||||||
shuttleBuilding.OnEntered(pawn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 委托给shuttleBuilding的物品管理方法(使用new隐藏基类方法)
|
|
||||||
public new void Notify_ThingAdded(Thing t)
|
|
||||||
{
|
|
||||||
shuttleBuilding.Notify_ThingAdded(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void AddToTheToLoadList(TransferableOneWay t, int count)
|
|
||||||
{
|
|
||||||
shuttleBuilding.AddToTheToLoadList(t, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public new int SubtractFromToLoadList(Thing t, int count)
|
|
||||||
{
|
|
||||||
return shuttleBuilding.SubtractFromToLoadList(t, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void CancelLoad()
|
|
||||||
{
|
|
||||||
// 调用shuttleBuilding的CancelLoad方法
|
|
||||||
shuttleBuilding.CancelLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重写原版MapPortal的关键属性
|
|
||||||
public override string EnterString => shuttleBuilding.EnterString;
|
|
||||||
public override string CancelEnterString => shuttleBuilding.CancelEnterString;
|
|
||||||
public override string EnteringString => shuttleBuilding.EnteringString;
|
|
||||||
|
|
||||||
// 隐藏LoadInProgress属性,确保Dialog_EnterPortal能正确读取
|
|
||||||
public new bool LoadInProgress => shuttleBuilding?.LoadInProgress ?? false;
|
|
||||||
|
|
||||||
// 确保SpawnSetup正确处理
|
|
||||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
|
||||||
{
|
|
||||||
// 调用基类的SpawnSetup来正确初始化MapPortal的基础设施
|
|
||||||
base.SpawnSetup(map, respawningAfterLoad);
|
|
||||||
|
|
||||||
// 同步关键字段
|
|
||||||
this.pocketMap = shuttleBuilding?.PocketMapForPortal;
|
|
||||||
this.leftToLoad = shuttleBuilding?.leftToLoad ?? new List<TransferableOneWay>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重写AddItemsToTransferables,让Dialog_EnterPortal只处理人员
|
|
||||||
// 因为物品已经通过装载按钮正确传送到内部空间了
|
|
||||||
protected virtual void AddItemsToTransferables()
|
|
||||||
{
|
|
||||||
// 不添加任何物品,因为物品传送由装载按钮处理
|
|
||||||
// 这样Dialog_EnterPortal只专注于人员传送
|
|
||||||
Log.Message("[WULA] AddItemsToTransferables: Skipping items, handled by loading button");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 专为Building_ArmedShuttleWithPocket设计的PortalContainerProxy适配器
|
/// 专为Building_ArmedShuttleWithPocket设计的PortalContainerProxy适配器
|
||||||
/// 模仿原版PortalContainerProxy的行为,但适配非-MapPortal类型
|
/// 模仿原版PortalContainerProxy的行为,但适配非-MapPortal类型
|
||||||
|
/// 这个类拦截所有向容器添加物品和人员的操作
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PortalContainerProxy : ThingOwner
|
public class PortalContainerProxy : ThingOwner
|
||||||
{
|
{
|
||||||
public Building_ArmedShuttleWithPocket portal;
|
public Building_ArmedShuttleWithPocket portal;
|
||||||
|
|
||||||
public override int Count => 0;
|
public override int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// 不记录日志,避免日志刷屏
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override int TryAdd(Thing item, int count, bool canMergeWithExistingStacks = true)
|
public override int TryAdd(Thing item, int count, bool canMergeWithExistingStacks = true)
|
||||||
{
|
{
|
||||||
@@ -1226,10 +954,34 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
public override bool TryAdd(Thing item, bool canMergeWithExistingStacks = true)
|
public override bool TryAdd(Thing item, bool canMergeWithExistingStacks = true)
|
||||||
{
|
{
|
||||||
if (portal == null) return false;
|
return ProcessItemOrPawn(item, canMergeWithExistingStacks);
|
||||||
|
}
|
||||||
|
|
||||||
Log.Message($"[WULA] PortalContainerProxy.TryAdd called for: {item?.def?.defName} x{item?.stackCount}");
|
/// <summary>
|
||||||
|
/// 统一的物品和人员处理方法
|
||||||
|
/// 动物当作物品处理,只有殖民者才当作Pawn处理
|
||||||
|
/// </summary>
|
||||||
|
private bool ProcessItemOrPawn(Thing item, bool canMergeWithExistingStacks = true)
|
||||||
|
{
|
||||||
|
if (portal == null)
|
||||||
|
{
|
||||||
|
Log.Error("[WULA] PortalContainerProxy.ProcessItemOrPawn: portal is null!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Message($"[WULA] PortalContainerProxy.ProcessItemOrPawn called for: {item?.def?.defName} ({item?.GetType()?.Name}) x{item?.stackCount}");
|
||||||
|
|
||||||
|
// 只有殖民者才当作Pawn处理,动物当作物品处理
|
||||||
|
if (item is Pawn pawn && pawn.IsColonist)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] INTERCEPTING COLONIST PAWN: {pawn.LabelShort} ({pawn.def.defName}) - Type: {pawn.GetType().Name}");
|
||||||
|
|
||||||
|
bool result = TransferPawnToPocketSpace(pawn);
|
||||||
|
Log.Message($"[WULA] Colonist transfer result: {result}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动物和其他所有物品都当作物品处理
|
||||||
Map otherMap = portal.GetOtherMap();
|
Map otherMap = portal.GetOtherMap();
|
||||||
IntVec3 destinationLocation = portal.GetDestinationLocation();
|
IntVec3 destinationLocation = portal.GetDestinationLocation();
|
||||||
|
|
||||||
@@ -1245,11 +997,11 @@ namespace WulaFallenEmpire
|
|||||||
Log.Message($"[WULA] Calling portal.Notify_ThingAdded for: {item?.def?.defName} x{item?.stackCount}");
|
Log.Message($"[WULA] Calling portal.Notify_ThingAdded for: {item?.def?.defName} x{item?.stackCount}");
|
||||||
portal.Notify_ThingAdded(item);
|
portal.Notify_ThingAdded(item);
|
||||||
|
|
||||||
// 传送物品到目标地图
|
// 传送物品(包括动物)到目标地图
|
||||||
Log.Message($"[WULA] Transporting item to pocket map: {item?.def?.defName}");
|
Log.Message($"[WULA] Transporting item/animal to pocket map: {item?.def?.defName}");
|
||||||
GenDrop.TryDropSpawn(item, destinationLocation, otherMap, ThingPlaceMode.Near, out var _);
|
GenDrop.TryDropSpawn(item, destinationLocation, otherMap, ThingPlaceMode.Near, out var _);
|
||||||
|
|
||||||
Log.Message($"[WULA] Item transport completed successfully");
|
Log.Message($"[WULA] Item/animal transport completed successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,6 +1019,187 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 传送单个Pawn到口袋空间(使用直接传送机制)
|
||||||
|
/// </summary>
|
||||||
|
private bool TransferPawnToPocketSpace(Pawn pawn)
|
||||||
|
{
|
||||||
|
if (pawn == null)
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] TransferPawnToPocketSpace: Pawn is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portal == null)
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] TransferPawnToPocketSpace: Portal is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pawn.Spawned && pawn.holdingOwner == null)
|
||||||
|
{
|
||||||
|
Log.Warning($"[WULA] TransferPawnToPocketSpace: Pawn {pawn.LabelShort} is not spawned and not in a container");
|
||||||
|
|
||||||
|
// 即使武装没有spawned或不在容器中,仍然尝试传送
|
||||||
|
// 记录原始状态并继续
|
||||||
|
Log.Message($"[WULA] Attempting to transfer pawn anyway: {pawn.LabelShort}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建口袋地图(如果需要)
|
||||||
|
Map targetMap = portal.GetOtherMap();
|
||||||
|
if (targetMap == null)
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] TransferPawnToPocketSpace: Failed to get or create pocket map");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntVec3 destinationLocation = portal.GetDestinationLocation();
|
||||||
|
if (!destinationLocation.IsValid)
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] TransferPawnToPocketSpace: Invalid destination location");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 保存重要状态
|
||||||
|
bool wasDrafted = pawn.Drafted;
|
||||||
|
Map sourceMap = pawn.Map; // 可能为null如果pawn在容器中
|
||||||
|
IntVec3 sourcePos = pawn.Position;
|
||||||
|
ThingOwner owner = pawn.holdingOwner;
|
||||||
|
|
||||||
|
Log.Message($"[WULA] Starting transfer of pawn {pawn.LabelShort} to pocket space");
|
||||||
|
|
||||||
|
// 通知系统人员被添加,更新装载状态
|
||||||
|
portal.Notify_ThingAdded(pawn);
|
||||||
|
|
||||||
|
// 寻找安全的生成位置
|
||||||
|
IntVec3 spawnPos = CellFinder.StandableCellNear(destinationLocation, targetMap, 5,
|
||||||
|
p => p.Standable(targetMap) && !p.GetThingList(targetMap).Any(t => t is Pawn));
|
||||||
|
|
||||||
|
if (!spawnPos.IsValid)
|
||||||
|
{
|
||||||
|
Log.Warning("[WULA] Could not find valid spawn position in pocket map, using center");
|
||||||
|
spawnPos = CellFinder.RandomClosewalkCellNear(targetMap.Center, targetMap, 10,
|
||||||
|
p => p.Standable(targetMap));
|
||||||
|
|
||||||
|
if (!spawnPos.IsValid)
|
||||||
|
{
|
||||||
|
Log.Error("[WULA] All spawn position attempts failed, using map center");
|
||||||
|
spawnPos = targetMap.Center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键情况记录
|
||||||
|
Log.Message($"[WULA] Found valid spawn position: {spawnPos}");
|
||||||
|
Log.Message($"[WULA] Pawn state - Spawned: {pawn.Spawned}, In container: {owner != null}");
|
||||||
|
|
||||||
|
// 从Pawn当前位置安全移除
|
||||||
|
if (owner != null)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Removing pawn from container");
|
||||||
|
if (!owner.Remove(pawn))
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Failed to remove pawn from container!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pawn.Spawned)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Despawning pawn from map {pawn.Map?.uniqueID ?? -1}");
|
||||||
|
pawn.DeSpawn(DestroyMode.Vanish);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Pawn既不在地图上也不在容器中,可能被CompTransporter直接持有
|
||||||
|
Log.Message($"[WULA] Pawn in special state - ParentHolder: {pawn.ParentHolder?.ToString() ?? "None"}");
|
||||||
|
|
||||||
|
// 尝试从CompTransporter中直接提取
|
||||||
|
if (pawn.ParentHolder != null)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Attempting to extract pawn from ParentHolder: {pawn.ParentHolder}");
|
||||||
|
|
||||||
|
// 检查是否在特殊容器中
|
||||||
|
var parentHolder = pawn.ParentHolder;
|
||||||
|
if (parentHolder is ThingOwner thingOwner)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] ParentHolder is a ThingOwner, attempting to remove pawn");
|
||||||
|
if (thingOwner.Contains(pawn))
|
||||||
|
{
|
||||||
|
bool removed = thingOwner.Remove(pawn);
|
||||||
|
Log.Message($"[WULA] Removed from ThingOwner: {removed}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (parentHolder.ToString().Contains("CompTransporter"))
|
||||||
|
{
|
||||||
|
// 尝试使用反射从CompTransporter中移除
|
||||||
|
Log.Message($"[WULA] Attempting to extract from CompTransporter");
|
||||||
|
var compTransporter = portal.GetComp<CompTransporter>();
|
||||||
|
if (compTransporter != null)
|
||||||
|
{
|
||||||
|
var innerContainerField = typeof(CompTransporter).GetField("innerContainer",
|
||||||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||||
|
|
||||||
|
if (innerContainerField != null)
|
||||||
|
{
|
||||||
|
var transporterContainer = innerContainerField.GetValue(compTransporter) as ThingOwner;
|
||||||
|
if (transporterContainer != null && transporterContainer.Contains(pawn))
|
||||||
|
{
|
||||||
|
bool removed = transporterContainer.Remove(pawn);
|
||||||
|
Log.Message($"[WULA] Removed from CompTransporter: {removed}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认是否已从原位置移除
|
||||||
|
if (pawn.Spawned)
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Failed to despawn pawn! Still spawned after despawn attempt");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成到目标位置
|
||||||
|
Log.Message($"[WULA] Spawning pawn at {spawnPos} in map {targetMap.uniqueID}");
|
||||||
|
GenSpawn.Spawn(pawn, spawnPos, targetMap);
|
||||||
|
|
||||||
|
// 验证是否成功生成
|
||||||
|
if (!pawn.Spawned || pawn.Map != targetMap)
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Pawn failed to spawn correctly! Spawned: {pawn.Spawned}, Map: {pawn.Map?.uniqueID ?? -1}");
|
||||||
|
|
||||||
|
// 尝试恢复到原始位置
|
||||||
|
if (sourceMap != null && sourceMap.IsPlayerHome)
|
||||||
|
{
|
||||||
|
Log.Message($"[WULA] Attempting emergency recovery to original position");
|
||||||
|
GenSpawn.Spawn(pawn, sourcePos, sourceMap);
|
||||||
|
|
||||||
|
if (!pawn.Spawned)
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Emergency recovery failed! Pawn is lost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复状态
|
||||||
|
if (wasDrafted && pawn.drafter != null)
|
||||||
|
{
|
||||||
|
pawn.drafter.Drafted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Message($"[WULA] Pawn {pawn.LabelShort} successfully transferred to pocket space");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"[WULA] Exception during pawn transfer: {ex}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user