Merge branch 'main' of https://git.ra3battle.cn/Kalospacer/WulaFallenEmpireRW
This commit is contained in:
Binary file not shown.
@@ -80,6 +80,15 @@
|
|||||||
<ammoCountPerCharge>2</ammoCountPerCharge>
|
<ammoCountPerCharge>2</ammoCountPerCharge>
|
||||||
<baseReloadTicks>60</baseReloadTicks>
|
<baseReloadTicks>60</baseReloadTicks>
|
||||||
</li>
|
</li>
|
||||||
|
<li Class="WulaFallenEmpire.CompProperties_CustomUniqueWeapon">
|
||||||
|
<forcedTraits>
|
||||||
|
<li>WULA_DamagePsychicScaling</li>
|
||||||
|
</forcedTraits>
|
||||||
|
<numTraitsRange>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
</numTraitsRange>
|
||||||
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
<tradeability>None</tradeability>
|
<tradeability>None</tradeability>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
@@ -246,6 +255,15 @@
|
|||||||
<ammoCountPerCharge>2</ammoCountPerCharge>
|
<ammoCountPerCharge>2</ammoCountPerCharge>
|
||||||
<baseReloadTicks>60</baseReloadTicks>
|
<baseReloadTicks>60</baseReloadTicks>
|
||||||
</li>
|
</li>
|
||||||
|
<li Class="WulaFallenEmpire.CompProperties_CustomUniqueWeapon">
|
||||||
|
<forcedTraits>
|
||||||
|
<li>WULA_DamagePsychicScaling</li>
|
||||||
|
</forcedTraits>
|
||||||
|
<numTraitsRange>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
</numTraitsRange>
|
||||||
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
<tradeability>None</tradeability>
|
<tradeability>None</tradeability>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
@@ -409,6 +427,15 @@
|
|||||||
<ammoCountPerCharge>20</ammoCountPerCharge>
|
<ammoCountPerCharge>20</ammoCountPerCharge>
|
||||||
<baseReloadTicks>60</baseReloadTicks>
|
<baseReloadTicks>60</baseReloadTicks>
|
||||||
</li>
|
</li>
|
||||||
|
<li Class="WulaFallenEmpire.CompProperties_CustomUniqueWeapon">
|
||||||
|
<forcedTraits>
|
||||||
|
<li>WULA_DamagePsychicScaling</li>
|
||||||
|
</forcedTraits>
|
||||||
|
<numTraitsRange>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
</numTraitsRange>
|
||||||
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
<tradeability>None</tradeability>
|
<tradeability>None</tradeability>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
|
|||||||
@@ -1106,12 +1106,12 @@
|
|||||||
</costList>
|
</costList>
|
||||||
<verbs>
|
<verbs>
|
||||||
<li>
|
<li>
|
||||||
<verbClass>Verb_Shoot</verbClass>
|
<verbClass>WulaFallenEmpire.Verb_ShootShotgun</verbClass>
|
||||||
<hasStandardCommand>true</hasStandardCommand>
|
<hasStandardCommand>true</hasStandardCommand>
|
||||||
<defaultProjectile>WULA_Bullet_StarDrift_Shotgun_Spear</defaultProjectile>
|
<defaultProjectile>WULA_Bullet_StarDrift_Shotgun_Spear</defaultProjectile>
|
||||||
<warmupTime>0.2</warmupTime>
|
<warmupTime>0.2</warmupTime>
|
||||||
<range>15</range>
|
<range>15</range>
|
||||||
<burstShotCount>6</burstShotCount>
|
<burstShotCount>1</burstShotCount>
|
||||||
<ticksBetweenBurstShots>3</ticksBetweenBurstShots>
|
<ticksBetweenBurstShots>3</ticksBetweenBurstShots>
|
||||||
<soundCast>ChargeLance_Fire</soundCast>
|
<soundCast>ChargeLance_Fire</soundCast>
|
||||||
<soundCastTail>GunTail_Heavy</soundCastTail>
|
<soundCastTail>GunTail_Heavy</soundCastTail>
|
||||||
@@ -1133,6 +1133,11 @@
|
|||||||
<armorPenetrationBase>0.65</armorPenetrationBase>
|
<armorPenetrationBase>0.65</armorPenetrationBase>
|
||||||
<speed>55</speed>
|
<speed>55</speed>
|
||||||
</projectile>
|
</projectile>
|
||||||
|
<modExtensions>
|
||||||
|
<li Class="WulaFallenEmpire.ShotgunExtension">
|
||||||
|
<pelletCount>6</pelletCount>
|
||||||
|
</li>
|
||||||
|
</modExtensions>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
|
|
||||||
<!-- 机枪 -->
|
<!-- 机枪 -->
|
||||||
|
|||||||
10
1.6/Defs/WeaponTraitDefs/WULA_WeaponCategoryDefs.xml
Normal file
10
1.6/Defs/WeaponTraitDefs/WULA_WeaponCategoryDefs.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Defs>
|
||||||
|
|
||||||
|
<WeaponCategoryDef>
|
||||||
|
<defName>WULA_Psychic</defName>
|
||||||
|
<label>灵能</label>
|
||||||
|
<description>与心灵能量相互作用的武器。</description>
|
||||||
|
</WeaponCategoryDef>
|
||||||
|
|
||||||
|
</Defs>
|
||||||
16
1.6/Defs/WeaponTraitDefs/WULA_WeaponTraitDefs.xml
Normal file
16
1.6/Defs/WeaponTraitDefs/WULA_WeaponTraitDefs.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Defs>
|
||||||
|
|
||||||
|
<WeaponTraitDef>
|
||||||
|
<defName>WULA_DamagePsychicScaling</defName>
|
||||||
|
<label>灵能增幅</label>
|
||||||
|
<description>这把武器的伤害会随着使用者的心灵敏感度而变化。</description>
|
||||||
|
<commonality>1</commonality>
|
||||||
|
<weaponCategory>WULA_Psychic</weaponCategory>
|
||||||
|
<statOffsets>
|
||||||
|
</statOffsets>
|
||||||
|
<statFactors>
|
||||||
|
</statFactors>
|
||||||
|
</WeaponTraitDef>
|
||||||
|
|
||||||
|
</Defs>
|
||||||
@@ -45,28 +45,32 @@ KNOWLEDGE_BASE_PATHS = [
|
|||||||
r"C:\Steam\steamapps\common\RimWorld\Data"
|
r"C:\Steam\steamapps\common\RimWorld\Data"
|
||||||
]
|
]
|
||||||
|
|
||||||
# 3. --- 缓存管理 ---
|
# 3. --- 缓存管理 (分文件存储) ---
|
||||||
def load_cache():
|
def load_cache_for_keyword(keyword: str):
|
||||||
"""加载缓存文件"""
|
"""为指定关键词加载缓存文件。"""
|
||||||
if os.path.exists(CACHE_FILE_PATH):
|
# 清理关键词,使其适合作为文件名
|
||||||
try:
|
safe_filename = "".join(c for c in keyword if c.isalnum() or c in ('_', '-')).rstrip()
|
||||||
with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f:
|
cache_file = os.path.join(CACHE_DIR, f"{safe_filename}.txt")
|
||||||
return json.load(f)
|
|
||||||
except (json.JSONDecodeError, IOError) as e:
|
if os.path.exists(cache_file):
|
||||||
logging.error(f"读取缓存文件失败: {e}")
|
try:
|
||||||
return {}
|
with open(cache_file, 'r', encoding='utf-8') as f:
|
||||||
return {}
|
return f.read()
|
||||||
|
except IOError as e:
|
||||||
|
logging.error(f"读取缓存文件 {cache_file} 失败: {e}")
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
def save_cache(cache_data):
|
def save_cache_for_keyword(keyword: str, data: str):
|
||||||
"""保存缓存到文件"""
|
"""为指定关键词保存缓存到单独的文件。"""
|
||||||
try:
|
safe_filename = "".join(c for c in keyword if c.isalnum() or c in ('_', '-')).rstrip()
|
||||||
with open(CACHE_FILE_PATH, 'w', encoding='utf-8') as f:
|
cache_file = os.path.join(CACHE_DIR, f"{safe_filename}.txt")
|
||||||
json.dump(cache_data, f, ensure_ascii=False, indent=4)
|
|
||||||
except IOError as e:
|
try:
|
||||||
logging.error(f"写入缓存文件失败: {e}")
|
with open(cache_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(data)
|
||||||
# 加载初始缓存
|
except IOError as e:
|
||||||
knowledge_cache = load_cache()
|
logging.error(f"写入缓存文件 {cache_file} 失败: {e}")
|
||||||
|
|
||||||
# 4. --- 向量化与相似度计算 ---
|
# 4. --- 向量化与相似度计算 ---
|
||||||
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
||||||
@@ -87,23 +91,121 @@ def get_embedding(text: str):
|
|||||||
logging.error(f"调用向量API时出错: {e}", exc_info=True)
|
logging.error(f"调用向量API时出错: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def find_most_similar_file(question_embedding, file_embeddings):
|
def find_most_similar_files(question_embedding, file_embeddings, top_n=3, min_similarity=0.5):
|
||||||
"""在文件向量中找到与问题向量最相似的一个"""
|
"""在文件向量中找到与问题向量最相似的 top_n 个文件。"""
|
||||||
if not question_embedding or not file_embeddings:
|
if not question_embedding or not file_embeddings:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
# 将文件嵌入列表转换为NumPy数组
|
file_vectors = np.array([emb['embedding'] for emb in file_embeddings])
|
||||||
file_vectors = np.array([emb['embedding'] for emb in file_embeddings])
|
question_vector = np.array(question_embedding).reshape(1, -1)
|
||||||
question_vector = np.array(question_embedding).reshape(1, -1)
|
|
||||||
|
similarities = cosine_similarity(question_vector, file_vectors)[0]
|
||||||
# 计算余弦相似度
|
|
||||||
similarities = cosine_similarity(question_vector, file_vectors)[0]
|
# 获取排序后的索引
|
||||||
|
sorted_indices = np.argsort(similarities)[::-1]
|
||||||
# 找到最相似的文件的索引
|
|
||||||
most_similar_index = np.argmax(similarities)
|
# 筛选出最相关的结果
|
||||||
|
results = []
|
||||||
# 返回最相似的文件路径
|
for i in sorted_indices:
|
||||||
return file_embeddings[most_similar_index]['path']
|
similarity_score = similarities[i]
|
||||||
|
if similarity_score >= min_similarity and len(results) < top_n:
|
||||||
|
results.append({
|
||||||
|
'path': file_embeddings[i]['path'],
|
||||||
|
'similarity': similarity_score
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def extract_relevant_code(file_path, keyword):
|
||||||
|
"""从文件中智能提取包含关键词的完整代码块 (C#类 或 XML Def)。"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
lines = content.split('\n')
|
||||||
|
keyword_lower = keyword.lower()
|
||||||
|
|
||||||
|
found_line_index = -1
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if keyword_lower in line.lower():
|
||||||
|
found_line_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if found_line_index == -1:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 根据文件类型选择提取策略
|
||||||
|
if file_path.endswith(('.cs', '.txt')):
|
||||||
|
# C# 提取策略:寻找完整的类
|
||||||
|
return extract_csharp_class(lines, found_line_index)
|
||||||
|
elif file_path.endswith('.xml'):
|
||||||
|
# XML 提取策略:寻找完整的 Def
|
||||||
|
return extract_xml_def(lines, found_line_index)
|
||||||
|
else:
|
||||||
|
return "" # 不支持的文件类型
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"提取代码时出错 {file_path}: {e}")
|
||||||
|
return f"# Error reading file: {e}"
|
||||||
|
|
||||||
|
def extract_csharp_class(lines, start_index):
|
||||||
|
"""从C#代码行中提取完整的类定义。"""
|
||||||
|
# 向上找到 class 声明
|
||||||
|
class_start_index = -1
|
||||||
|
brace_level_at_class_start = -1
|
||||||
|
for i in range(start_index, -1, -1):
|
||||||
|
line = lines[i]
|
||||||
|
if 'class ' in line:
|
||||||
|
class_start_index = i
|
||||||
|
brace_level_at_class_start = line.count('{') - line.count('}')
|
||||||
|
break
|
||||||
|
|
||||||
|
if class_start_index == -1: return "" # 没找到类
|
||||||
|
|
||||||
|
# 从 class 声明开始,向下找到匹配的 '}'
|
||||||
|
brace_count = brace_level_at_class_start
|
||||||
|
class_end_index = -1
|
||||||
|
for i in range(class_start_index + 1, len(lines)):
|
||||||
|
line = lines[i]
|
||||||
|
brace_count += line.count('{')
|
||||||
|
brace_count -= line.count('}')
|
||||||
|
if brace_count <= 0: # 找到匹配的闭合括号
|
||||||
|
class_end_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if class_end_index != -1:
|
||||||
|
return "\n".join(lines[class_start_index:class_end_index+1])
|
||||||
|
return "" # 未找到完整的类块
|
||||||
|
|
||||||
|
def extract_xml_def(lines, start_index):
|
||||||
|
"""从XML行中提取完整的Def块。"""
|
||||||
|
import re
|
||||||
|
# 向上找到 <DefName> 或 <defName>
|
||||||
|
def_start_index = -1
|
||||||
|
def_tag = ""
|
||||||
|
for i in range(start_index, -1, -1):
|
||||||
|
line = lines[i].strip()
|
||||||
|
match = re.match(r'<(\w+)\s+.*>', line) or re.match(r'<(\w+)>', line)
|
||||||
|
if match and ('Def' in match.group(1) or 'def' in match.group(1)):
|
||||||
|
# 这是一个简化的判断,实际中可能需要更复杂的逻辑
|
||||||
|
def_start_index = i
|
||||||
|
def_tag = match.group(1)
|
||||||
|
break
|
||||||
|
|
||||||
|
if def_start_index == -1: return ""
|
||||||
|
|
||||||
|
# 向下找到匹配的 </DefName>
|
||||||
|
def_end_index = -1
|
||||||
|
for i in range(def_start_index + 1, len(lines)):
|
||||||
|
if f'</{def_tag}>' in lines[i]:
|
||||||
|
def_end_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if def_end_index != -1:
|
||||||
|
return "\n".join(lines[def_start_index:def_end_index+1])
|
||||||
|
return ""
|
||||||
|
|
||||||
# 5. --- 核心功能函数 ---
|
# 5. --- 核心功能函数 ---
|
||||||
def find_files_with_keyword(roots, keyword, extensions=['.xml', '.cs', '.txt']):
|
def find_files_with_keyword(roots, keyword, extensions=['.xml', '.cs', '.txt']):
|
||||||
@@ -183,8 +285,8 @@ mcp = FastMCP(
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def get_context(question: str) -> str:
|
def get_context(question: str) -> str:
|
||||||
"""
|
"""
|
||||||
根据问题中的关键词和向量相似度,在RimWorld知识库中搜索最相关的XML或C#文件。
|
根据问题中的关键词和向量相似度,在RimWorld知识库中搜索最相关的多个代码片段,
|
||||||
返回最匹配的文件路径。
|
并将其整合后返回。
|
||||||
"""
|
"""
|
||||||
logging.info(f"收到问题: {question}")
|
logging.info(f"收到问题: {question}")
|
||||||
keyword = find_keyword_in_question(question)
|
keyword = find_keyword_in_question(question)
|
||||||
@@ -194,11 +296,11 @@ def get_context(question: str) -> str:
|
|||||||
|
|
||||||
logging.info(f"提取到关键词: {keyword}")
|
logging.info(f"提取到关键词: {keyword}")
|
||||||
|
|
||||||
# 1. 检查缓存
|
# 1. 检查缓存 (新逻辑)
|
||||||
if keyword in knowledge_cache:
|
cached_result = load_cache_for_keyword(keyword)
|
||||||
cached_path = knowledge_cache[keyword]
|
if cached_result:
|
||||||
logging.info(f"缓存命中: 关键词 '{keyword}' -> {cached_path}")
|
logging.info(f"缓存命中: 关键词 '{keyword}'")
|
||||||
return f"根据知识库缓存,与 '{keyword}' 最相关的定义文件是:\n{cached_path}"
|
return cached_result
|
||||||
|
|
||||||
logging.info(f"缓存未命中,开始实时搜索: {keyword}")
|
logging.info(f"缓存未命中,开始实时搜索: {keyword}")
|
||||||
|
|
||||||
@@ -221,7 +323,6 @@ def get_context(question: str) -> str:
|
|||||||
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()
|
||||||
# v4模型支持更长的输入
|
|
||||||
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})
|
||||||
@@ -231,18 +332,50 @@ def get_context(question: str) -> str:
|
|||||||
if not file_embeddings:
|
if not file_embeddings:
|
||||||
return "无法为任何候选文件生成向量。"
|
return "无法为任何候选文件生成向量。"
|
||||||
|
|
||||||
# 找到最相似的文件
|
# 找到最相似的多个文件
|
||||||
best_match_path = find_most_similar_file(question_embedding, file_embeddings)
|
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=3)
|
||||||
|
|
||||||
if not best_match_path:
|
if not best_matches:
|
||||||
return "计算向量相似度失败。"
|
return "计算向量相似度失败或没有找到足够相似的文件。"
|
||||||
|
|
||||||
# 4. 更新缓存并返回结果
|
# 4. 提取代码并格式化输出
|
||||||
logging.info(f"向量搜索完成。最匹配的文件是: {best_match_path}")
|
output_parts = [f"根据向量相似度分析,与 '{keyword}' 最相关的代码定义如下:\n"]
|
||||||
knowledge_cache[keyword] = best_match_path
|
|
||||||
save_cache(knowledge_cache)
|
|
||||||
|
|
||||||
return f"根据向量相似度分析,与 '{keyword}' 最相关的定义文件是:\n{best_match_path}"
|
for match in best_matches:
|
||||||
|
file_path = match['path']
|
||||||
|
similarity = match['similarity']
|
||||||
|
|
||||||
|
# 智能提取代码块
|
||||||
|
code_block = extract_relevant_code(file_path, keyword)
|
||||||
|
|
||||||
|
# 如果提取失败,则跳过这个文件
|
||||||
|
if not code_block or code_block.startswith("# Error"):
|
||||||
|
logging.warning(f"未能从 {file_path} 提取到完整的代码块。")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 确定语言类型用于markdown高亮
|
||||||
|
lang = "csharp" if file_path.endswith(('.cs', '.txt')) else "xml"
|
||||||
|
|
||||||
|
output_parts.append(
|
||||||
|
f"---\n"
|
||||||
|
f"**文件路径:** `{file_path}`\n"
|
||||||
|
f"**相似度:** {similarity:.4f}\n\n"
|
||||||
|
f"```{lang}\n"
|
||||||
|
f"{code_block}\n"
|
||||||
|
f"```"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果没有任何代码块被成功提取
|
||||||
|
if len(output_parts) <= 1:
|
||||||
|
return f"虽然找到了相似的文件,但无法在其中提取到关于 '{keyword}' 的完整代码块。"
|
||||||
|
|
||||||
|
final_output = "\n".join(output_parts)
|
||||||
|
|
||||||
|
# 5. 更新缓存并返回结果
|
||||||
|
logging.info(f"向量搜索完成。找到了 {len(best_matches)} 个匹配项并成功提取了代码。")
|
||||||
|
save_cache_for_keyword(keyword, final_output)
|
||||||
|
|
||||||
|
return final_output
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"处理请求时发生意外错误: {e}", exc_info=True)
|
logging.error(f"处理请求时发生意外错误: {e}", exc_info=True)
|
||||||
|
|||||||
154
Source/WulaFallenEmpire/CompCustomUniqueWeapon.cs
Normal file
154
Source/WulaFallenEmpire/CompCustomUniqueWeapon.cs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
using RimWorld;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class CompCustomUniqueWeapon : CompUniqueWeapon
|
||||||
|
{
|
||||||
|
// 使用 'new' 关键字来明确隐藏基类成员,解决 CS0108 警告
|
||||||
|
public new CompProperties_CustomUniqueWeapon Props => (CompProperties_CustomUniqueWeapon)props;
|
||||||
|
|
||||||
|
private List<WeaponTraitDef> customTraits = new List<WeaponTraitDef>();
|
||||||
|
|
||||||
|
// 使用 'new' 关键字隐藏基类属性,解决 CS0506 错误
|
||||||
|
public new List<WeaponTraitDef> TraitsListForReading => customTraits;
|
||||||
|
|
||||||
|
// PostExposeData 是 virtual 的,保留 override
|
||||||
|
public override void PostExposeData()
|
||||||
|
{
|
||||||
|
base.PostExposeData();
|
||||||
|
Scribe_Collections.Look(ref customTraits, "customTraits", LookMode.Def);
|
||||||
|
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||||
|
{
|
||||||
|
if (customTraits == null) customTraits = new List<WeaponTraitDef>();
|
||||||
|
SetupCustomTraits(fromSave: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPostMake 是 virtual 的,保留 override
|
||||||
|
public override void PostPostMake()
|
||||||
|
{
|
||||||
|
InitializeCustomTraits();
|
||||||
|
if (parent.TryGetComp<CompQuality>(out var comp))
|
||||||
|
{
|
||||||
|
comp.SetQuality(QualityUtility.GenerateQuality(QualityGenerator.Super), ArtGenerationContext.Outsider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeCustomTraits()
|
||||||
|
{
|
||||||
|
if (customTraits == null) customTraits = new List<WeaponTraitDef>();
|
||||||
|
customTraits.Clear();
|
||||||
|
|
||||||
|
if (Props.forcedTraits != null)
|
||||||
|
{
|
||||||
|
foreach (var traitToForce in Props.forcedTraits)
|
||||||
|
{
|
||||||
|
if (customTraits.All(t => !t.Overlaps(traitToForce)))
|
||||||
|
{
|
||||||
|
customTraits.Add(traitToForce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IntRange traitRange = Props.numTraitsRange ?? new IntRange(1, 3);
|
||||||
|
int totalTraitsTarget = Mathf.Max(customTraits.Count, traitRange.RandomInRange);
|
||||||
|
int missingTraits = totalTraitsTarget - customTraits.Count;
|
||||||
|
|
||||||
|
if (missingTraits > 0)
|
||||||
|
{
|
||||||
|
// CanAddTrait 现在是我们自己的 'new' 方法
|
||||||
|
IEnumerable<WeaponTraitDef> possibleTraits = DefDatabase<WeaponTraitDef>.AllDefs.Where(CanAddTrait);
|
||||||
|
for (int i = 0; i < missingTraits; i++)
|
||||||
|
{
|
||||||
|
if (!possibleTraits.Any()) break;
|
||||||
|
|
||||||
|
var chosenTrait = possibleTraits.RandomElementByWeight(t => t.commonality);
|
||||||
|
customTraits.Add(chosenTrait);
|
||||||
|
|
||||||
|
possibleTraits = possibleTraits.Where(t => t != chosenTrait && !t.Overlaps(chosenTrait));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupCustomTraits(fromSave: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupCustomTraits(bool fromSave)
|
||||||
|
{
|
||||||
|
foreach (WeaponTraitDef trait in customTraits)
|
||||||
|
{
|
||||||
|
if (trait.abilityProps != null && parent.GetComp<CompEquippableAbilityReloadable>() is CompEquippableAbilityReloadable comp)
|
||||||
|
{
|
||||||
|
comp.props = trait.abilityProps;
|
||||||
|
if (!fromSave)
|
||||||
|
{
|
||||||
|
comp.Notify_PropsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 'new' 关键字隐藏基类方法,解决 CS0506 错误
|
||||||
|
public new bool CanAddTrait(WeaponTraitDef trait)
|
||||||
|
{
|
||||||
|
if (customTraits.Any(t => t == trait || t.Overlaps(t)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Props.weaponCategories != null && Props.weaponCategories.Any() && !Props.weaponCategories.Contains(trait.weaponCategory))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (customTraits.Count == 0 && !trait.canGenerateAlone)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 下面的方法都是 virtual 的,保留 override ---
|
||||||
|
|
||||||
|
public override string TransformLabel(string label) => label;
|
||||||
|
public override Color? ForceColor() => null;
|
||||||
|
|
||||||
|
public override float GetStatOffset(StatDef stat) => customTraits.Sum(t => t.statOffsets.GetStatOffsetFromList(stat));
|
||||||
|
public override float GetStatFactor(StatDef stat) => customTraits.Aggregate(1f, (current, t) => current * t.statFactors.GetStatFactorFromList(stat));
|
||||||
|
|
||||||
|
public override string CompInspectStringExtra()
|
||||||
|
{
|
||||||
|
if (customTraits.NullOrEmpty()) return null;
|
||||||
|
return "WeaponTraits".Translate() + ": " + customTraits.Select(t => t.label).ToCommaList().CapitalizeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string CompTipStringExtra()
|
||||||
|
{
|
||||||
|
if (customTraits.NullOrEmpty()) return base.CompTipStringExtra();
|
||||||
|
return "WeaponTraits".Translate() + ": " + customTraits.Select(t => t.label).ToCommaList().CapitalizeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<StatDrawEntry> SpecialDisplayStats()
|
||||||
|
{
|
||||||
|
if (customTraits.NullOrEmpty()) yield break;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("Stat_ThingUniqueWeaponTrait_Desc".Translate());
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
for (int i = 0; i < customTraits.Count; i++)
|
||||||
|
{
|
||||||
|
WeaponTraitDef trait = customTraits[i];
|
||||||
|
builder.AppendLine(trait.LabelCap.Colorize(ColorLibrary.Yellow));
|
||||||
|
builder.AppendLine(trait.description);
|
||||||
|
if (i < customTraits.Count - 1) builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new StatDrawEntry(
|
||||||
|
parent.def.IsMeleeWeapon ? StatCategoryDefOf.Weapon_Melee : StatCategoryDefOf.Weapon_Ranged,
|
||||||
|
"Stat_ThingUniqueWeaponTrait_Label".Translate(),
|
||||||
|
customTraits.Select(t => t.label).ToCommaList().CapitalizeFirst(),
|
||||||
|
builder.ToString(),
|
||||||
|
1104);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Source/WulaFallenEmpire/CompProperties_CustomUniqueWeapon.cs
Normal file
21
Source/WulaFallenEmpire/CompProperties_CustomUniqueWeapon.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Verse;
|
||||||
|
using RimWorld;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class CompProperties_CustomUniqueWeapon : CompProperties_UniqueWeapon
|
||||||
|
{
|
||||||
|
// A list of traits that will always be added to the weapon.
|
||||||
|
public List<WeaponTraitDef> forcedTraits;
|
||||||
|
|
||||||
|
// The range of traits to randomly add. If not defined in XML, a default of 1-3 will be used.
|
||||||
|
public IntRange? numTraitsRange;
|
||||||
|
|
||||||
|
public CompProperties_CustomUniqueWeapon()
|
||||||
|
{
|
||||||
|
// Point to the implementation of our custom logic.
|
||||||
|
this.compClass = typeof(CompCustomUniqueWeapon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Building_Wula_DarkEnergy_Engine.cs" />
|
<Compile Include="Building_Wula_DarkEnergy_Engine.cs" />
|
||||||
<Compile Include="CompApparelInterceptor.cs" />
|
<Compile Include="CompApparelInterceptor.cs" />
|
||||||
|
<Compile Include="CompCustomUniqueWeapon.cs" />
|
||||||
|
<Compile Include="CompProperties_CustomUniqueWeapon.cs" />
|
||||||
<Compile Include="CompPsychicScaling.cs" />
|
<Compile Include="CompPsychicScaling.cs" />
|
||||||
<Compile Include="CompUseEffect_FixAllHealthConditions.cs" />
|
<Compile Include="CompUseEffect_FixAllHealthConditions.cs" />
|
||||||
<Compile Include="CompUseEffect_PassionTrainer.cs" />
|
<Compile Include="CompUseEffect_PassionTrainer.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user