Merge branch 'hediffshield'
This commit is contained in:
Binary file not shown.
30
1.6/1.6/Defs/HediffDefs/Hediffs_WULA_AuraExample.xml
Normal file
30
1.6/1.6/Defs/HediffDefs/Hediffs_WULA_AuraExample.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<HediffDef>
|
||||
<defName>MyWulaAura</defName>
|
||||
<label>乌拉共鸣光环</label>
|
||||
<description>这是一个如何使用HediffComp_GiveHediffsInRangeToRace组件的示例。</description>
|
||||
<hediffClass>HediffWithComps</hediffClass>
|
||||
<comps>
|
||||
<!-- 这里使用我们新创建的C#类的完整名称 -->
|
||||
<li Class="WulaFallenEmpire.HediffCompProperties_GiveHediffsInRangeToRace">
|
||||
<range>10</range>
|
||||
<hediff>PsychicSoothe</hediff> <!-- 举例:要施加的Hediff -->
|
||||
|
||||
<!-- 定义可以被影响的种族列表 -->
|
||||
<targetRaces>
|
||||
<li>WulaSpecies</li> <!-- 您的乌拉种族 -->
|
||||
<li>Human</li> <!-- 也可以添加其他种族 -->
|
||||
</targetRaces>
|
||||
|
||||
<!-- 其他参数和原版一样 -->
|
||||
<targetingParameters>
|
||||
<canTargetAllies>true</canTargetAllies>
|
||||
</targetingParameters>
|
||||
<onlyPawnsInSameFaction>true</onlyPawnsInSameFaction>
|
||||
</li>
|
||||
</comps>
|
||||
</HediffDef>
|
||||
|
||||
</Defs>
|
||||
20
1.6/1.6/Defs/HediffDefs/Hediffs_WULA_DamageShield.xml
Normal file
20
1.6/1.6/Defs/HediffDefs/Hediffs_WULA_DamageShield.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
<HediffDef> <!-- 移除 ParentName -->
|
||||
<defName>WULA_DamageShield</defName>
|
||||
<label>伤害护盾</label>
|
||||
<description>一种特殊的能量护盾,可以抵挡受到的伤害。每层护盾可以抵挡一次伤害。</description>
|
||||
<hediffClass>WulaFallenEmpire.Hediff_DamageShield</hediffClass>
|
||||
<initialSeverity>10</initialSeverity> <!-- 初始层数设置为10 -->
|
||||
<maxSeverity>999</maxSeverity> <!-- 最大层数,可以根据需要调整 -->
|
||||
<defaultLabelColor>(0.6, 0.6, 1.0)</defaultLabelColor>
|
||||
<isBad>false</isBad>
|
||||
<stages>
|
||||
<li>
|
||||
<label>活跃</label>
|
||||
<minSeverity>1</minSeverity>
|
||||
<!-- 这里可以添加一些统计数据偏移,例如增加防御等 -->
|
||||
</li>
|
||||
</stages>
|
||||
</HediffDef>
|
||||
</Defs>
|
||||
36
1.6/1.6/Defs/ThingDefs/ThingDefs_WULA_Items_DamageShield.xml
Normal file
36
1.6/1.6/Defs/ThingDefs/ThingDefs_WULA_Items_DamageShield.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
<ThingDef ParentName="BodyPartProstheticMakeableBase">
|
||||
<defName>WULA_DamageShieldGenerator</defName>
|
||||
<label>乌拉反应护盾发生器</label>
|
||||
<description>一个便携式设备,可以激活并生成一个临时的能量护盾,抵挡即将到来的伤害。</description>
|
||||
<graphicData>
|
||||
<texPath>Wula/Item/WULA_Syhth_Trainer</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
</graphicData>
|
||||
<stackLimit>1</stackLimit>
|
||||
<useHitPoints>true</useHitPoints>
|
||||
<healthAffectsPrice>false</healthAffectsPrice>
|
||||
<statBases>
|
||||
<MaxHitPoints>50</MaxHitPoints>
|
||||
<MarketValue>500</MarketValue>
|
||||
<Mass>0.5</Mass>
|
||||
<WorkToMake>1000</WorkToMake>
|
||||
</statBases>
|
||||
<thingCategories>
|
||||
<li>Items</li>
|
||||
</thingCategories>
|
||||
<tradeability>Sellable</tradeability>
|
||||
<comps>
|
||||
<li Class="CompProperties_Usable">
|
||||
<useJob>UseItem</useJob>
|
||||
<useLabel>使用护盾发生器充能反应护盾</useLabel>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AddDamageShieldCharges">
|
||||
<hediffDef>WULA_DamageShield</hediffDef>
|
||||
<chargesToAdd>100</chargesToAdd> <!-- 每次使用添加 10 层 -->
|
||||
</li>
|
||||
<li Class="CompProperties_UseEffectDestroySelf" />
|
||||
</comps>
|
||||
</ThingDef>
|
||||
</Defs>
|
||||
@@ -17,4 +17,9 @@
|
||||
<WULA_MaintenanceNoEffect>{PAWN_nameDef} 的维护已完成,但{PAWN_nameDef} 没有可修复的受损部位。</WULA_MaintenanceNoEffect>
|
||||
<WULA_NoComponentsToHaul>没有可用的零部件来搬运。</WULA_NoComponentsToHaul>
|
||||
|
||||
<WULA_MessageGainedDamageShieldCharges>{0} 获得了 {1} 层伤害护盾!</WULA_MessageGainedDamageShieldCharges>
|
||||
<WULA_CannotUseOnDeadPawn>无法对已死亡的Pawn使用。</WULA_CannotUseOnDeadPawn>
|
||||
<WULA_DamageShieldMaxChargesReached>伤害护盾已达到最大层数。</WULA_DamageShieldMaxChargesReached>
|
||||
<WULA_DamageShieldChargesDescription>使用:增加 {0} 层伤害护盾</WULA_DamageShieldChargesDescription>
|
||||
|
||||
</LanguageData>
|
||||
@@ -388,7 +388,7 @@ def find_keywords_in_question(question: str) -> list[str]:
|
||||
def analyze_question_with_llm(question: str) -> dict:
|
||||
"""使用Qwen模型分析问题并提取关键词和意图"""
|
||||
try:
|
||||
system_prompt = """你是一个关键词提取机器人,专门用于从 RimWorld 模组开发相关问题中提取精确的搜索关键词。你的任务是识别问题中提到的核心技术术语。
|
||||
system_prompt = """你是一个关键词提取机器人,专门用于从 RimWorld 模组开发相关问题中提取精确的搜索关键词。你的任务是识别问题中提到的核心技术术语,并将它们正确地拆分成独立的关键词。
|
||||
|
||||
严格按照以下格式回复,不要添加任何额外说明:
|
||||
问题类型:[问题分类]
|
||||
@@ -402,7 +402,8 @@ def analyze_question_with_llm(question: str) -> dict:
|
||||
3. 不要添加通用词如"RimWorld"、"游戏"、"定义"、"用法"等
|
||||
4. 不要添加缩写或扩展形式如"Def"、"XML"等除非问题中明确提到
|
||||
5. 只提取具体的技术名词,忽略动词、形容词等
|
||||
6. 关键词之间用英文逗号分隔,不要有空格
|
||||
6. 当遇到用空格连接的多个技术术语时,应将它们拆分为独立的关键词
|
||||
7. 关键词之间用英文逗号分隔,不要有空格
|
||||
|
||||
示例:
|
||||
问题:ThingDef的定义和用法是什么?
|
||||
@@ -417,6 +418,12 @@ def analyze_question_with_llm(question: str) -> dict:
|
||||
关键概念:API 使用
|
||||
搜索关键词:GenExplosion.DoExplosion,Projectile.Launch
|
||||
|
||||
问题:RimWorld Pawn_HealthTracker PreApplyDamage
|
||||
问题类型:API 使用说明
|
||||
关键类/方法名:Pawn_HealthTracker,PreApplyDamage
|
||||
关键概念:伤害处理
|
||||
搜索关键词:Pawn_HealthTracker,PreApplyDamage
|
||||
|
||||
现在请分析以下问题:"""
|
||||
|
||||
messages = [
|
||||
@@ -512,6 +519,16 @@ def get_context(question: str) -> str:
|
||||
analysis = analyze_question_with_llm(question)
|
||||
keywords = analysis["search_keywords"]
|
||||
|
||||
# 确保关键词被正确拆分
|
||||
split_keywords = []
|
||||
for keyword in keywords:
|
||||
# 如果关键词中包含空格,将其拆分为多个关键词
|
||||
if ' ' in keyword:
|
||||
split_keywords.extend(keyword.split())
|
||||
else:
|
||||
split_keywords.append(keyword)
|
||||
keywords = split_keywords
|
||||
|
||||
if not keywords:
|
||||
logging.warning("无法从问题中提取关键词。")
|
||||
return "无法从问题中提取关键词,请提供更具体的信息。"
|
||||
@@ -534,23 +551,32 @@ def get_context(question: str) -> str:
|
||||
|
||||
logging.info(f"缓存未命中,开始实时搜索: {cache_key}")
|
||||
|
||||
# 2. 关键词文件搜索 (分层智能筛选)
|
||||
# 2. 对每个关键词分别执行搜索过程,然后合并结果
|
||||
try:
|
||||
candidate_files = find_files_with_keyword(KNOWLEDGE_BASE_PATHS, keywords)
|
||||
|
||||
if not candidate_files:
|
||||
logging.info(f"未找到与 '{keywords}' 相关的文件。")
|
||||
return f"未在知识库中找到与 '{keywords}' 相关的文件定义。"
|
||||
all_results = []
|
||||
processed_files = set() # 避免重复处理相同文件
|
||||
|
||||
logging.info(f"找到 {len(candidate_files)} 个候选文件,开始向量化处理...")
|
||||
for keyword in keywords:
|
||||
logging.info(f"开始搜索关键词: {keyword}")
|
||||
|
||||
# 为当前关键词搜索文件
|
||||
candidate_files = find_files_with_keyword(KNOWLEDGE_BASE_PATHS, [keyword])
|
||||
|
||||
# 新增:文件名精确匹配优先
|
||||
priority_results = []
|
||||
remaining_files = []
|
||||
for file_path in candidate_files:
|
||||
filename_no_ext = os.path.splitext(os.path.basename(file_path))[0]
|
||||
is_priority = False
|
||||
for keyword in keywords:
|
||||
if not candidate_files:
|
||||
logging.info(f"未找到与 '{keyword}' 相关的文件。")
|
||||
continue
|
||||
|
||||
logging.info(f"找到 {len(candidate_files)} 个候选文件用于关键词 '{keyword}',开始向量化处理...")
|
||||
|
||||
# 文件名精确匹配优先
|
||||
priority_results = []
|
||||
remaining_files = []
|
||||
for file_path in candidate_files:
|
||||
# 避免重复处理相同文件
|
||||
if file_path in processed_files:
|
||||
continue
|
||||
|
||||
filename_no_ext = os.path.splitext(os.path.basename(file_path))[0]
|
||||
if filename_no_ext.lower() == keyword.lower():
|
||||
logging.info(f"文件名精确匹配: {file_path}")
|
||||
code_block = extract_relevant_code(file_path, keyword)
|
||||
@@ -560,79 +586,92 @@ def get_context(question: str) -> str:
|
||||
'similarity': 1.0, # 精确匹配给予最高分
|
||||
'code': code_block
|
||||
})
|
||||
is_priority = True
|
||||
break # 已处理该文件,跳出内层循环
|
||||
if not is_priority:
|
||||
remaining_files.append(file_path)
|
||||
|
||||
candidate_files = remaining_files # 更新候选文件列表,排除已优先处理的文件
|
||||
processed_files.add(file_path)
|
||||
else:
|
||||
remaining_files.append(file_path)
|
||||
|
||||
# 更新候选文件列表,排除已优先处理的文件
|
||||
candidate_files = [f for f in remaining_files if f not in processed_files]
|
||||
|
||||
# 限制向量化的文件数量以避免超时
|
||||
MAX_FILES_TO_VECTORIZE = 5
|
||||
if 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]
|
||||
|
||||
# 3. 向量化和相似度计算 (精准筛选)
|
||||
# 增加超时保护:限制向量化的文件数量
|
||||
MAX_FILES_TO_VECTORIZE = 10 # 进一步减少处理文件数量以避免超时
|
||||
if 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]
|
||||
# 为剩余文件生成向量
|
||||
question_embedding = get_embedding(keyword) # 使用关键词而不是整个问题
|
||||
if not question_embedding:
|
||||
logging.warning(f"无法为关键词 '{keyword}' 生成向量。")
|
||||
# 将优先结果添加到总结果中
|
||||
all_results.extend(priority_results)
|
||||
continue
|
||||
|
||||
question_embedding = get_embedding(question)
|
||||
if not question_embedding:
|
||||
return "无法生成问题向量,请检查API连接或问题内容。"
|
||||
file_embeddings = []
|
||||
for i, file_path in enumerate(candidate_files):
|
||||
try:
|
||||
# 避免重复处理相同文件
|
||||
if file_path in processed_files:
|
||||
continue
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
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]) # 限制内容长度以提高效率
|
||||
if file_embedding:
|
||||
file_embeddings.append({'path': file_path, 'embedding': file_embedding})
|
||||
except Exception as e:
|
||||
logging.error(f"处理文件 {file_path} 时出错: {e}")
|
||||
continue # 继续处理下一个文件,而不是完全失败
|
||||
|
||||
if not file_embeddings and not priority_results:
|
||||
logging.warning(f"未能为关键词 '{keyword}' 的任何候选文件生成向量。")
|
||||
continue
|
||||
|
||||
file_embeddings = []
|
||||
for i, file_path in enumerate(candidate_files):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# 添加处理进度日志
|
||||
if i % 5 == 0: # 每5个文件记录一次进度
|
||||
logging.info(f"正在处理第 {i+1}/{len(candidate_files)} 个文件: {os.path.basename(file_path)}")
|
||||
# 找到最相似的多个文件
|
||||
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=3)
|
||||
|
||||
# 重排序处理
|
||||
if len(best_matches) > 1:
|
||||
reranked_matches = rerank_files(keyword, best_matches, top_n=2) # 减少重排序数量
|
||||
else:
|
||||
reranked_matches = best_matches
|
||||
|
||||
# 提取代码内容
|
||||
results_with_code = []
|
||||
for match in reranked_matches:
|
||||
# 避免重复处理相同文件
|
||||
if match['path'] in processed_files:
|
||||
continue
|
||||
|
||||
file_embedding = get_embedding(content[:8000]) # 限制内容长度以提高效率
|
||||
if file_embedding:
|
||||
file_embeddings.append({'path': file_path, 'embedding': file_embedding})
|
||||
except Exception as e:
|
||||
logging.error(f"处理文件 {file_path} 时出错: {e}")
|
||||
continue # 继续处理下一个文件,而不是完全失败
|
||||
code_block = extract_relevant_code(match['path'], "")
|
||||
if code_block:
|
||||
match['code'] = code_block
|
||||
results_with_code.append(match)
|
||||
processed_files.add(match['path'])
|
||||
|
||||
# 将优先结果和相似度结果合并
|
||||
results_with_code = priority_results + results_with_code
|
||||
|
||||
# 将当前关键词的结果添加到总结果中
|
||||
all_results.extend(results_with_code)
|
||||
|
||||
if not file_embeddings:
|
||||
logging.warning("未能为任何候选文件生成向量。可能是由于API超时或其他错误。")
|
||||
return "未能为任何候选文件生成向量,请稍后重试或减少搜索范围。"
|
||||
|
||||
# 找到最相似的多个文件
|
||||
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=5) # 进一步减少返回数量以避免超时
|
||||
# 检查是否有任何结果
|
||||
if len(all_results) <= 0:
|
||||
return f"未在知识库中找到与 '{keywords}' 相关的文件定义。"
|
||||
|
||||
if not best_matches:
|
||||
return "计算向量相似度失败或没有找到足够相似的文件。"
|
||||
|
||||
# 新增:重排序处理(仅在找到足够多匹配项时执行)
|
||||
if len(best_matches) > 2:
|
||||
reranked_matches = rerank_files(question, best_matches, top_n=3) # 减少重排序数量
|
||||
else:
|
||||
reranked_matches = best_matches # 如果匹配项太少,跳过重排序以节省时间
|
||||
|
||||
# 提取代码内容
|
||||
results_with_code = []
|
||||
for match in reranked_matches:
|
||||
code_block = extract_relevant_code(match['path'], "")
|
||||
if code_block:
|
||||
match['code'] = code_block
|
||||
results_with_code.append(match)
|
||||
|
||||
# 将优先结果添加到结果列表开头
|
||||
results_with_code = priority_results + results_with_code
|
||||
|
||||
if len(results_with_code) <= 0:
|
||||
return f"虽然找到了相似的文件,但无法在其中提取到相关代码块。"
|
||||
|
||||
# 直接返回原始代码结果,而不是使用LLM格式化
|
||||
# 整理最终输出
|
||||
final_output = ""
|
||||
for i, result in enumerate(results_with_code, 1):
|
||||
for i, result in enumerate(all_results, 1):
|
||||
final_output += f"--- 结果 {i} (相似度: {result['similarity']:.3f}) ---\n"
|
||||
final_output += f"文件路径: {result['path']}\n\n"
|
||||
final_output += f"{result['code']}\n\n"
|
||||
|
||||
# 5. 更新缓存并返回结果
|
||||
logging.info(f"向量搜索完成。找到了 {len(results_with_code)} 个匹配项并成功提取了代码。")
|
||||
logging.info(f"向量搜索完成。找到了 {len(all_results)} 个匹配项并成功提取了代码。")
|
||||
save_cache_for_question(question, keywords, final_output)
|
||||
|
||||
return final_output
|
||||
|
||||
File diff suppressed because one or more lines are too long
683
README.md
Normal file
683
README.md
Normal file
@@ -0,0 +1,683 @@
|
||||
# RimWorld Mod: 基于次数的护盾与原版护盾视觉集成
|
||||
|
||||
## 1. 引言
|
||||
|
||||
本Mod旨在为《RimWorld》引入一种新型的护盾机制:基于 Hediff 层数的次数护盾。与原版基于能量的护盾不同,本护盾的抵挡能力由可叠加的“层数”决定,每层护盾可以抵挡一次受到的伤害。同时,为了提供更沉浸和熟悉的体验,我们集成了原版能量护盾(CompShield)的视觉特效和音效,使次数护盾在抵挡伤害时,能够展现出与原版护盾相似的视觉冲击力。
|
||||
|
||||
## 2. 核心概念回顾
|
||||
|
||||
### 2.1 Hediff_DamageShield
|
||||
|
||||
这是我们自定义的 Hediff 类型,它代表了Pawn身上激活的次数护盾。它的核心特性是:
|
||||
- **层数管理**:通过 `ShieldCharges` 属性来跟踪剩余的护盾层数。当Pawn获得护盾时,层数增加;当护盾抵挡伤害时,层数减少。
|
||||
- **自动移除**:当护盾层数归零时,该 Hediff 会自动从Pawn身上移除。
|
||||
- **显示信息**:在Pawn的健康信息界面,会显示当前护盾的剩余层数。
|
||||
|
||||
### 2.2 CompShield
|
||||
|
||||
这是《RimWorld》原版用于实现能量护盾的组件。它通常附加在护盾腰带等物品上,提供以下核心功能:
|
||||
- **能量值**:护盾具有能量储备,受到伤害会消耗能量。
|
||||
- **充能与重置**:能量耗尽后,护盾会进入重置状态,并在一段时间后恢复能量。
|
||||
- **视觉和音效**:护盾拥有独特的视觉表现(如护盾泡泡)和音效(如吸收伤害时的音效)。
|
||||
|
||||
## 3. 实现细节
|
||||
|
||||
### 3.1 伤害抵挡逻辑与护盾渲染 (DRMDamageShield.cs & Hediff_DamageShield.cs)
|
||||
|
||||
**核心思想**:我们利用 `ThingComp` 的 `PostPreApplyDamage` 虚方法来拦截伤害,而不是使用 Harmony Patch `Pawn_HealthTracker.PreApplyDamage`。这将使代码更简洁,更符合 RimWorld 的组件化设计。护盾的视觉渲染也将由这个 `ThingComp` 负责。
|
||||
|
||||
- **`DRMDamageShield.cs`**: 这是一个自定义的 `ThingComp`,它将附加到 Pawn 身上。
|
||||
- **伤害拦截**:它重写了 `PostPreApplyDamage` 方法。当 Pawn 受到伤害时,这个方法会被自动调用。在这里,我们会检查 Pawn 是否拥有 `Hediff_DamageShield` 及其层数,如果满足条件,则消耗层数并设置 `absorbed = true` 来抵挡伤害。
|
||||
- **视觉和音效集成**:在抵挡伤害时,`DRMDamageShield` 会触发原版能量护盾的吸收音效、闪光特效和抖动效果。
|
||||
- **护盾渲染**:`DRMDamageShield` 包含了从 `CompShield` 中提取的护盾泡泡渲染逻辑。它会在 Pawn 身上渲染一个动态的护盾泡泡,其大小和显示状态与 `Hediff_DamageShield` 的层数关联。
|
||||
- **能量同步**:`DRMDamageShield` 的“能量”和“最大能量”属性将直接从 Pawn 身上对应的 `Hediff_DamageShield` 实例中获取其 `ShieldCharges` 和 `def.maxSeverity`。
|
||||
|
||||
- **`Hediff_DamageShield.cs`**:
|
||||
- **动态管理 `DRMDamageShield`**:在 `PostAdd` 方法中,当 `Hediff_DamageShield` 被添加到 Pawn 身上时,它会确保 Pawn 拥有一个 `DRMDamageShield` 实例(如果Pawn还没有)。在 `PostRemoved` 方法中,当 `Hediff_DamageShield` 被移除时,它会禁用或移除对应的 `DRMDamageShield` 实例。
|
||||
- **层数与能量关联**:`Hediff_DamageShield` 的 `ShieldCharges` 属性将作为 `DRMDamageShield` 的能量来源。
|
||||
|
||||
### 3.2 充能方式 (CompUseEffect_AddDamageShieldCharges.cs & WULA_DamageShieldGenerator)
|
||||
|
||||
护盾的充能方式保持不变,通过使用特定的物品来增加护盾层数。
|
||||
|
||||
- **`CompUseEffect_AddDamageShieldCharges`**:这是一个自定义的物品使用效果组件。
|
||||
- 当物品被使用时,它会检查目标Pawn是否拥有 `Hediff_DamageShield`。
|
||||
- 如果Pawn没有该Hediff,则会为其添加一个,并赋予预设的初始层数(例如10层)。
|
||||
- 如果Pawn已有该Hediff,则会在现有层数的基础上增加预设的层数(例如每次使用增加10层)。
|
||||
- **`WULA_DamageShieldGenerator`**:这是定义在XML中的一个物品,它附加了 `CompUseEffect_AddDamageShieldCharges` 组件。玩家可以通过制作或获得这个物品,并对其Pawn使用来获取或补充护盾层数。
|
||||
|
||||
## 4. 代码结构与内容
|
||||
|
||||
以下是本Mod的关键文件及其作用和完整代码内容:
|
||||
|
||||
### 4.1 Hediff_DamageShield.cs (更新)
|
||||
|
||||
此文件定义了基于层数的护盾 Hediff。它将管理护盾层数,并在 Pawn 身上动态添加/移除 `DRMDamageShield`。
|
||||
|
||||
```csharp
|
||||
using Verse;
|
||||
using System.Text;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using HarmonyLib; // Needed for AccessTools if you use it here directly
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Hediff_DamageShield : HediffWithComps
|
||||
{
|
||||
// 伤害抵挡层数
|
||||
public int ShieldCharges
|
||||
{
|
||||
get => (int)severityInt;
|
||||
set => severityInt = value;
|
||||
}
|
||||
|
||||
private DRMDamageShield cachedShieldComp;
|
||||
|
||||
// 获取或创建 DRMDamageShield 组件
|
||||
public DRMDamageShield ShieldComp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedShieldComp == null || cachedShieldComp.parent != pawn)
|
||||
{
|
||||
cachedShieldComp = pawn.GetComp<DRMDamageShield>();
|
||||
if (cachedShieldComp == null)
|
||||
{
|
||||
// 如果没有,动态添加一个
|
||||
cachedShieldComp = (DRMDamageShield)Activator.CreateInstance(typeof(DRMDamageShield));
|
||||
cachedShieldComp.parent = pawn;
|
||||
cachedShieldComp.props = new DRMCompShieldProp(); // 确保有属性,即使是默认的
|
||||
pawn.AllComps.Add(cachedShieldComp);
|
||||
cachedShieldComp.Initialize(cachedShieldComp.props);
|
||||
}
|
||||
}
|
||||
return cachedShieldComp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string LabelInBrackets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ShieldCharges > 0)
|
||||
{
|
||||
return "层数: " + ShieldCharges;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string TipStringExtra
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(base.TipStringExtra);
|
||||
if (ShieldCharges > 0)
|
||||
{
|
||||
sb.AppendLine(" - 每层抵挡一次伤害。当前层数: " + ShieldCharges);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(" - 没有可用的抵挡层数。");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
// severityInt 会自动保存,所以不需要额外处理 ShieldCharges
|
||||
}
|
||||
|
||||
public override void PostAdd(DamageInfo? dinfo)
|
||||
{
|
||||
base.PostAdd(dinfo);
|
||||
// 确保 Pawn 拥有 DRMCompShield 组件
|
||||
DRMDamageShield comp = ShieldComp; // 访问属性以确保组件被添加
|
||||
if (comp != null)
|
||||
{
|
||||
comp.IsActive = true; // 激活护盾组件
|
||||
// 能量同步将在 Tick() 中完成
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostRemoved()
|
||||
{
|
||||
base.PostRemoved();
|
||||
// 禁用护盾组件
|
||||
if (cachedShieldComp != null && cachedShieldComp.parent == pawn)
|
||||
{
|
||||
cachedShieldComp.IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
// 如果层数归零,移除 Hediff
|
||||
if (ShieldCharges <= 0)
|
||||
{
|
||||
pawn.health.RemoveHediff(this);
|
||||
}
|
||||
// 同步能量到 ShieldComp
|
||||
if (ShieldComp != null && ShieldComp.IsActive)
|
||||
{
|
||||
ShieldComp.Energy = ShieldCharges;
|
||||
ShieldComp.MaxEnergy = (int)def.maxSeverity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 DRMDamageShield.cs (新文件)
|
||||
|
||||
此文件定义了自定义的 `ThingComp`,用于处理护盾的渲染和部分行为。它将从 `CompShield` 和 `PlasmaShieldImplant.cs` 中提取渲染和伤害处理逻辑。
|
||||
|
||||
```csharp
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
using System.Reflection; // For AccessTools
|
||||
using HarmonyLib; // For AccessTools
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
// 自定义 CompProperties_Shield 变体
|
||||
public class DRMCompShieldProp : CompProperties
|
||||
{
|
||||
public int startingTicksToReset = 3200;
|
||||
public float minDrawSize = 1.2f;
|
||||
public float maxDrawSize = 1.55f;
|
||||
public float energyLossPerDamage = 0.033f;
|
||||
public float energyOnReset = 0.2f;
|
||||
public bool blocksRangedWeapons = true;
|
||||
|
||||
public DRMCompShieldProp()
|
||||
{
|
||||
compClass = typeof(DRMDamageShield);
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup] // 确保在游戏启动时加载
|
||||
public class DRMDamageShield : ThingComp
|
||||
{
|
||||
// 从 Hediff_DamageShield 获取层数作为能量
|
||||
public float Energy
|
||||
{
|
||||
get
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
return hediff?.ShieldCharges ?? 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.ShieldCharges = (int)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float MaxEnergy
|
||||
{
|
||||
get
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
return hediff?.def.maxSeverity ?? 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
// MaxEnergy 由 HediffDef 控制,这里不需要设置
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsActive = false; // 控制护盾是否激活,由 Hediff_DamageShield 管理
|
||||
|
||||
// 复制自 CompShield
|
||||
protected int ticksToReset = -1;
|
||||
protected int lastKeepDisplayTick = -9999;
|
||||
private Vector3 impactAngleVect;
|
||||
private int lastAbsorbDamageTick = -9999;
|
||||
|
||||
private const float MaxDamagedJitterDist = 0.05f;
|
||||
private const int JitterDurationTicks = 8;
|
||||
private int KeepDisplayingTicks = 1000;
|
||||
|
||||
// 获取原版 CompShield 的 BubbleMat
|
||||
private static readonly Material BubbleMat;
|
||||
|
||||
static DRMDamageShield()
|
||||
{
|
||||
// 使用 Harmony AccessTools 获取 CompShield 的私有静态字段 BubbleMat
|
||||
BubbleMat = (Material)AccessTools.Field(typeof(CompShield), "BubbleMat").GetValue(null);
|
||||
}
|
||||
|
||||
public DRMCompShieldProp Props => (DRMCompShieldProp)props;
|
||||
|
||||
public ShieldState ShieldState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PawnOwner == null || !IsActive || Energy <= 0)
|
||||
{
|
||||
return ShieldState.Disabled;
|
||||
}
|
||||
if (ticksToReset <= 0)
|
||||
{
|
||||
return ShieldState.Active;
|
||||
}
|
||||
return ShieldState.Resetting;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool ShouldDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
Pawn pawnOwner = PawnOwner;
|
||||
if (pawnOwner == null || !pawnOwner.Spawned || pawnOwner.Dead || pawnOwner.Downed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (pawnOwner.InAggroMentalState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pawnOwner.Drafted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pawnOwner.Faction.HostileTo(Faction.OfPlayer) && !pawnOwner.IsPrisoner)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Find.TickManager.TicksGame < lastKeepDisplayTick + KeepDisplayingTicks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected Pawn PawnOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent as Pawn;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref ticksToReset, "ticksToReset", -1);
|
||||
Scribe_Values.Look(ref lastKeepDisplayTick, "lastKeepDisplayTick", 0);
|
||||
Scribe_Values.Look(ref IsActive, "isActive", false);
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
if (PawnOwner == null || !IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShieldState == ShieldState.Resetting)
|
||||
{
|
||||
ticksToReset--;
|
||||
if (ticksToReset <= 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
else if (ShieldState == ShieldState.Active)
|
||||
{
|
||||
// 护盾能量(层数)通过 Hediff_DamageShield 的 Tick 方法管理,这里不需要额外回复
|
||||
// 如果需要自动回复层数,可以在这里实现
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
|
||||
{
|
||||
absorbed = false;
|
||||
// 获取 Hediff_DamageShield 实例
|
||||
Hediff_DamageShield damageShield = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
|
||||
if (ShieldState != ShieldState.Active || !IsActive || damageShield == null || damageShield.ShieldCharges <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是 EMP 伤害,且护盾没有 EMP 抗性(这里假设我们的护盾没有),则直接击穿
|
||||
// 为了简化,我们假设我们的次数盾没有 EMP 抗性,任何 EMP 伤害都会直接击穿
|
||||
if (dinfo.Def == DamageDefOf.EMP)
|
||||
{
|
||||
Energy = 0; // 能量归零
|
||||
Notify_ShieldBreak(); // 触发护盾击穿效果
|
||||
absorbed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是远程或爆炸伤害,且护盾阻挡这些类型
|
||||
if (Props.blocksRangedWeapons && (dinfo.Def.isRanged || dinfo.Def.isExplosive))
|
||||
{
|
||||
// 消耗一层护盾
|
||||
damageShield.ShieldCharges--;
|
||||
|
||||
// 触发护盾吸收效果
|
||||
Notify_DamageAbsorbed(dinfo);
|
||||
|
||||
// 护盾抖动效果
|
||||
PawnOwner.Drawer.renderer.wiggler.SetToCustomRotation(Rand.Range(-0.05f, 0.05f));
|
||||
// 显示抵挡文本
|
||||
Verse.MoteMaker.ThrowText(PawnOwner.DrawPos, PawnOwner.Map, "伤害被护盾抵挡!", Color.cyan, 1.2f);
|
||||
|
||||
absorbed = true; // 伤害被吸收
|
||||
|
||||
// 如果护盾层数归零,触发护盾击穿效果
|
||||
if (damageShield.ShieldCharges <= 0)
|
||||
{
|
||||
Notify_ShieldBreak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify_DamageAbsorbed(DamageInfo dinfo)
|
||||
{
|
||||
// 复制自 CompShield.AbsorbedDamage
|
||||
SoundDefOf.EnergyShield_AbsorbDamage.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
|
||||
impactAngleVect = Vector3Utility.HorizontalVectorFromAngle(dinfo.Angle);
|
||||
Vector3 loc = PawnOwner.TrueCenter() + impactAngleVect.RotatedBy(180f) * 0.5f;
|
||||
float num = Mathf.Min(10f, 2f + dinfo.Amount / 10f);
|
||||
FleckMaker.Static(loc, PawnOwner.Map, FleckDefOf.ExplosionFlash, num);
|
||||
int num2 = (int)num;
|
||||
for (int i = 0; i < num2; i++)
|
||||
{
|
||||
FleckMaker.ThrowDustPuff(loc, PawnOwner.Map, Rand.Range(0.8f, 1.2f));
|
||||
}
|
||||
lastAbsorbDamageTick = Find.TickManager.TicksGame;
|
||||
KeepDisplaying();
|
||||
}
|
||||
|
||||
public void Notify_ShieldBreak()
|
||||
{
|
||||
// 复制自 CompShield.Break
|
||||
if (parent.Spawned)
|
||||
{
|
||||
float scale = Mathf.Lerp(Props.minDrawSize, Props.maxDrawSize, Energy / MaxEnergy); // 根据当前能量比例调整大小
|
||||
EffecterDefOf.Shield_Break.SpawnAttached(parent, parent.MapHeld, scale);
|
||||
FleckMaker.Static(PawnOwner.TrueCenter(), PawnOwner.Map, FleckDefOf.ExplosionFlash, 12f);
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
FleckMaker.ThrowDustPuff(PawnOwner.TrueCenter() + Vector3Utility.HorizontalVectorFromAngle(Rand.Range(0, 360)) * Rand.Range(0.3f, 0.6f), PawnOwner.Map, Rand.Range(0.8f, 1.2f));
|
||||
}
|
||||
}
|
||||
ticksToReset = Props.startingTicksToReset;
|
||||
// 护盾层数归零将由 Hediff_DamageShield 负责移除 Hediff
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// 复制自 CompShield.Reset
|
||||
if (PawnOwner.Spawned)
|
||||
{
|
||||
SoundDefOf.EnergyShield_Reset.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
|
||||
FleckMaker.ThrowLightningGlow(PawnOwner.TrueCenter(), PawnOwner.Map, 3f);
|
||||
}
|
||||
ticksToReset = -1;
|
||||
// 能量恢复由 Hediff_DamageShield 负责,这里不需要设置 Energy
|
||||
// 这里可以添加逻辑,让 Hediff_DamageShield 恢复层数
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.ShieldCharges = (int)hediff.def.initialSeverity; // 重置时恢复到初始层数
|
||||
}
|
||||
}
|
||||
|
||||
public void KeepDisplaying()
|
||||
{
|
||||
lastKeepDisplayTick = Find.TickManager.TicksGame;
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
base.PostDraw();
|
||||
Draw();
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
if (ShieldState == ShieldState.Active && ShouldDisplay)
|
||||
{
|
||||
float num = Mathf.Lerp(Props.minDrawSize, Props.maxDrawSize, Energy / MaxEnergy); // 根据当前能量比例调整大小
|
||||
Vector3 drawPos = PawnOwner.Drawer.DrawPos;
|
||||
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
|
||||
int num2 = Find.TickManager.TicksGame - lastAbsorbDamageTick;
|
||||
if (num2 < JitterDurationTicks) // 使用 JitterDurationTicks
|
||||
{
|
||||
float num3 = (float)(JitterDurationTicks - num2) / JitterDurationTicks * MaxDamagedJitterDist; // 使用 MaxDamagedJitterDist
|
||||
drawPos += impactAngleVect * num3;
|
||||
num -= num3;
|
||||
}
|
||||
float angle = Rand.Range(0, 360);
|
||||
Vector3 s = new Vector3(num, 1f, num);
|
||||
Matrix4x4 matrix = default(Matrix4x4);
|
||||
matrix.SetTRS(drawPos, Quaternion.AngleAxis(angle, Vector3.up), s);
|
||||
Graphics.DrawMesh(MeshPool.plane10, matrix, BubbleMat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 CompUseEffect_AddDamageShieldCharges.cs (不变)
|
||||
|
||||
```csharp
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompUseEffect_AddDamageShieldCharges : CompUseEffect
|
||||
{
|
||||
public CompProperties_AddDamageShieldCharges Props => (CompProperties_AddDamageShieldCharges)props;
|
||||
|
||||
public override void DoEffect(Pawn user)
|
||||
{
|
||||
base.DoEffect(user);
|
||||
|
||||
// 获取或添加 Hediff_DamageShield
|
||||
Hediff_DamageShield damageShield = user.health.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
|
||||
if (damageShield == null)
|
||||
{
|
||||
// 如果没有 Hediff,则添加一个
|
||||
damageShield = (Hediff_DamageShield)HediffMaker.MakeHediff(Props.hediffDef, user);
|
||||
user.health.AddHediff(damageShield);
|
||||
damageShield.ShieldCharges = Props.chargesToAdd; // 设置初始层数
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果已有 Hediff,则增加层数
|
||||
damageShield.ShieldCharges += Props.chargesToAdd;
|
||||
}
|
||||
|
||||
// 确保层数不超过最大值
|
||||
if (damageShield.ShieldCharges > (int)damageShield.def.maxSeverity)
|
||||
{
|
||||
damageShield.ShieldCharges = (int)damageShield.def.maxSeverity;
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
Messages.Message("WULA_MessageGainedDamageShieldCharges".Translate(user.LabelShort, Props.chargesToAdd), user, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
|
||||
// 修正 CanBeUsedBy 方法签名
|
||||
public override AcceptanceReport CanBeUsedBy(Pawn p)
|
||||
{
|
||||
// 确保只能对活着的 Pawn 使用
|
||||
if (p.Dead)
|
||||
{
|
||||
return "WULA_CannotUseOnDeadPawn".Translate();
|
||||
}
|
||||
|
||||
// 检查是否已达到最大层数
|
||||
Hediff_DamageShield damageShield = p.health.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (damageShield != null && damageShield.ShieldCharges >= (int)damageShield.def.maxSeverity)
|
||||
{
|
||||
return "WULA_DamageShieldMaxChargesReached".Translate();
|
||||
}
|
||||
|
||||
return true; // 可以使用
|
||||
}
|
||||
|
||||
// 可以在这里添加 GetDescriptionPart() 来显示描述
|
||||
public override string GetDescriptionPart()
|
||||
{
|
||||
return "WULA_DamageShieldChargesDescription".Translate(Props.chargesToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_AddDamageShieldCharges : CompProperties_UseEffect
|
||||
{
|
||||
public HediffDef hediffDef;
|
||||
public int chargesToAdd;
|
||||
|
||||
public CompProperties_AddDamageShieldCharges()
|
||||
{
|
||||
compClass = typeof(CompUseEffect_AddDamageShieldCharges);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 DamageShieldPatch.cs (将删除)
|
||||
|
||||
此文件将不再需要,因为伤害拦截逻辑已转移到 `DRMDamageShield.cs`。
|
||||
|
||||
```csharp
|
||||
// 此文件将被删除
|
||||
```
|
||||
|
||||
### 4.5 Hediffs_WULA_DamageShield.xml (不变)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
<HediffDef ParentName="HediffWithCompsBase">
|
||||
<defName>WULA_DamageShield</defName>
|
||||
<label>伤害护盾</label>
|
||||
<description>一种特殊的能量护盾,可以抵挡受到的伤害。每层护盾可以抵挡一次伤害。</description>
|
||||
<hediffClass>WulaFallenEmpire.Hediff_DamageShield</hediffClass>
|
||||
<initialSeverity>10</initialSeverity> <!-- 初始层数设置为10 -->
|
||||
<maxSeverity>999</maxSeverity> <!-- 最大层数,可以根据需要调整 -->
|
||||
<tendable>false</tendable>
|
||||
<displayAllParts>false</displayAllParts>
|
||||
<priceImpact>1</priceImpact>
|
||||
<addedSimultaneously>true</addedSimultaneously>
|
||||
<countsAsAddedPartOrImplant>false</countsAsAddedPartOrImplant>
|
||||
<stages>
|
||||
<li>
|
||||
<label>活跃</label>
|
||||
<minSeverity>1</minSeverity>
|
||||
<!-- 这里可以添加一些统计数据偏移,例如增加防御等 -->
|
||||
</li>
|
||||
</stages>
|
||||
<scenarioCanAdd>false</scenarioCanAdd>
|
||||
</HediffDef>
|
||||
</Defs>
|
||||
```
|
||||
|
||||
### 4.6 ThingDefs_WULA_Items_DamageShield.xml (修改)
|
||||
|
||||
此文件将定义新的物品 `WULA_DamageShieldGenerator`,它将使用 `CompProperties_AddDamageShieldCharges`。
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
<ThingDef ParentName="ResourceBase">
|
||||
<defName>WULA_DamageShieldGenerator</defName>
|
||||
<label>伤害护盾发生器</label>
|
||||
<description>一个便携式设备,可以激活并生成一个临时的能量护盾,抵挡即将到来的伤害。</description>
|
||||
<graphicData>
|
||||
<texPath>Things/Item/WULA_DamageShieldGenerator</texPath> <!-- 假设有一个贴图 -->
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
</graphicData>
|
||||
<stackLimit>1</stackLimit>
|
||||
<useHitPoints>true</useHitPoints>
|
||||
<healthAffectsPrice>false</healthAffectsPrice>
|
||||
<statBases>
|
||||
<MaxHitPoints>50</MaxHitPoints>
|
||||
<MarketValue>500</MarketValue>
|
||||
<Mass>0.5</Mass>
|
||||
<WorkToMake>1000</WorkToMake>
|
||||
</statBases>
|
||||
<thingCategories>
|
||||
<li>Items</li>
|
||||
</thingCategories>
|
||||
<tradeability>Sellable</tradeability>
|
||||
<comps>
|
||||
<li Class="CompProperties_Usable">
|
||||
<useJob>UseItem</useJob>
|
||||
<floatMenuCommandLabel>使用伤害护盾发生器</floatMenuCommandLabel>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AddDamageShieldCharges">
|
||||
<hediffDef>WULA_DamageShield</hediffDef>
|
||||
<chargesToAdd>10</chargesToAdd> <!-- 每次使用添加 10 层 -->
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
</Defs>
|
||||
```
|
||||
|
||||
### 4.7 WULA_Keyed.xml (不变)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<LanguageData>
|
||||
<WULA_MessageGainedDamageShieldCharges>{0} 获得了 {1} 层伤害护盾!</WULA_MessageGainedDamageShieldCharges>
|
||||
<WULA_CannotUseOnDeadPawn>无法对已死亡的Pawn使用。</WULA_CannotUseOnDeadPawn>
|
||||
<WULA_DamageShieldMaxChargesReached>伤害护盾已达到最大层数。</WULA_DamageShieldMaxChargesReached>
|
||||
<WULA_DamageShieldChargesDescription>使用:增加 {0} 层伤害护盾</WULA_DamageShieldChargesDescription>
|
||||
</LanguageData>
|
||||
```
|
||||
|
||||
## 5. 安装与测试
|
||||
|
||||
### 5.1 安装 Mod
|
||||
|
||||
1. 将本Mod的文件夹放置在《RimWorld》的Mods目录下。
|
||||
2. 在游戏启动器中激活本Mod。
|
||||
|
||||
### 5.2 游戏内测试
|
||||
|
||||
1. 进入游戏,加载或开始一个殖民地。
|
||||
2. 打开开发者模式(通常按 `~` 键)。
|
||||
3. **生成护盾物品**:在开发者控制台中输入 `spawn WULA_DamageShieldGenerator 1` 来生成一个护盾发生器物品。
|
||||
4. **使用护盾物品**:让Pawn拾取并使用 `WULA_DamageShieldGenerator`。观察Pawn是否获得了 `伤害护盾` Hediff,并且层数是否正确显示。
|
||||
5. **测试伤害抵挡**:让Pawn受到伤害(例如,让敌人攻击,或使用开发者模式中的“伤害”工具)。观察护盾层数是否减少,伤害是否被抵挡,以及是否触发了护盾吸收的音效和闪光特效。
|
||||
6. **测试护盾渲染**:观察Pawn身上是否显示了护盾泡泡。
|
||||
|
||||
## 6. 未来展望
|
||||
|
||||
- **护盾渲染动态化**:使护盾泡泡的视觉表现(例如透明度、大小)与剩余层数更紧密地关联,层数越低,护盾视觉效果越弱。
|
||||
- **充能动画**:为 `WULA_DamageShieldGenerator` 的使用添加充能动画。
|
||||
- **平衡性调整**:根据游戏测试反馈,调整护盾的初始层数、每次充能的层数、以及护盾的最大层数,以达到更好的游戏平衡。
|
||||
- **扩展功能**:
|
||||
- 添加护盾在特定条件下自动充能的机制。
|
||||
- 引入不同类型的次数护盾,具有不同的抵挡特性或额外效果。
|
||||
- 护盾被击穿时的特殊效果。
|
||||
100
Source/HarmonyPatch.md
Normal file
100
Source/HarmonyPatch.md
Normal file
@@ -0,0 +1,100 @@
|
||||
Patching
|
||||
Concept
|
||||
In order to provide your own code to Harmony, you need to define methods that run in the context of the original method. Harmony provides three types of methods that each offer different possibilities.
|
||||
|
||||
Types of patches
|
||||
Two of them, the Prefix patch and the Postfix patch are easy to understand and you can write them as simple static methods.
|
||||
|
||||
Transpiler patches are not methods that are executed together with the original but instead are called in an earlier stage where the instructions of the original are fed into the transpiler so it can process and change them, to finally output the instructions that will build the new original.
|
||||
|
||||
A Finalizer patch is a static method that handles exceptions and can change them. It is the only patch type that is immune to exceptions thrown by the original method or by any applied patches. The other patch types are considered part of the original and may not get executed when an exception occurs.
|
||||
|
||||
Finally, there is the Reverse Patch. It is different from the previous types in that it patches your methods instead of foreign original methods. To use it, you define a stub that looks like the original in some way and patch the original onto your stub which you can easily call from your own code. You can even transpile the result during the process.
|
||||
|
||||
Patches need to be static
|
||||
Patch methods need to be static because Harmony works with multiple users in different assemblies in mind. In order to guarantee the correct patching order, patches are always re-applied as soon as someone wants to change the original. Since it is hard to serialize data in a generic way across assemblies in .NET, Harmony only stores a method pointer to your patch methods so it can use and apply them at a later point again.
|
||||
|
||||
If you need custom state in your patches, it is recommended to use a static variable and store all your patch state in there. Keep in mind that Transpilers are only executed to generate the method so they don't "run" when the original is executed.
|
||||
|
||||
Commonly unsupported use cases
|
||||
Harmony works only in the current AppDomain. Accessing other app domains requires xpc and serialization which is not supported.
|
||||
|
||||
Currently, support for generic types and methods is experimental and can give unexpected results. See Edge Cases for more information.
|
||||
|
||||
When a method is inlined and the code that tries to mark in for not inlining does not work, your patches are not called because there is no method to patch.
|
||||
|
||||
Patch Class
|
||||
With manual patching, you can put your patches anywhere you like since you will refer to them yourself. Patching by annotations simplifies patching by assuming that you set up annotated classes and define your patch methods inside them.
|
||||
|
||||
Layout The class can be static or not, public or private, it doesn't matter. However, in order to make Harmony find it, it must have at least one [HarmonyPatch] attribute. Inside the class you define patches as static methods that either have special names like Prefix or Transpiler or use attributes to define their type. Usually they also include annotations that define their target (the original method you want to patch). It also common to have fields and other helper methods in the class.
|
||||
|
||||
Attribute Inheritance The attributes of the methods in the class inherit the attributes of the class.
|
||||
|
||||
Patch methods
|
||||
Harmony identifies your patch methods and their helper methods by name. If you prefer to name your methods differently, you can use attributes to tell Harmony what your methods are.
|
||||
|
||||
[HarmonyPatch(...)]
|
||||
class Patch
|
||||
{
|
||||
static void Prefix()
|
||||
{
|
||||
// this method uses the name "Prefix", no annotation necessary
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
static void MyOwnName()
|
||||
{
|
||||
// this method is a Postfix as defined by the attribute
|
||||
}
|
||||
}
|
||||
If you prefer manual patching, you can use any method name or class structure you want. You are responsible to retrieve the MethodInfo for the different patch methods and supply them to the Patch() method by wrapping them into HarmonyMethod objects.
|
||||
|
||||
note Patch methods must be static but you can define them public or private. They cannot be dynamic methods but you can write static patch factory methods that return dynamic methods.
|
||||
|
||||
[HarmonyPatch(...)]
|
||||
class Patch
|
||||
{
|
||||
// the return type of factory methods can be either MethodInfo or DynamicMethod
|
||||
[HarmonyPrefix]
|
||||
static MethodInfo PrefixFactory(MethodBase originalMethod)
|
||||
{
|
||||
// return an instance of MethodInfo or an instance of DynamicMethod
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
static MethodInfo PostfixFactory(MethodBase originalMethod)
|
||||
{
|
||||
// return an instance of MethodInfo or an instance of DynamicMethod
|
||||
}
|
||||
}
|
||||
Method names
|
||||
Manual patching knows four main patch types: Prefix, Postfix, Transpiler and Finalizer. If you use attributes for patching, you can also use the helper methods: Prepare, TargetMethod, TargetMethods and Cleanup as explained below.
|
||||
|
||||
Each of those names has a corresponding attribute starting with [Harmony...]. So instead of calling one of your methods "Prepare", you can call it anything and decorate it with a [HarmonyPrepare] attribute.
|
||||
|
||||
Patch method types
|
||||
Both prefix and postfix have specific semantics that are unique to them. They do however share the ability to use a range of injected values as arguments.
|
||||
|
||||
Prefix
|
||||
A prefix is a method that is executed before the original method. It is commonly used to:
|
||||
|
||||
access and edit the arguments of the original method
|
||||
set the result of the original method
|
||||
skip the original method
|
||||
set custom state that can be recalled in the postfix
|
||||
run a piece of code at the beginning that is guaranteed to be executed
|
||||
Postfix
|
||||
A postfix is a method that is executed after the original method. It is commonly used to:
|
||||
|
||||
read or change the result of the original method
|
||||
access the arguments of the original method
|
||||
read custom state from the prefix
|
||||
Transpiler
|
||||
This method defines the transpiler that modifies the code of the original method. Use this in the advanced case where you want to modify the original methods IL codes.
|
||||
|
||||
Finalizer
|
||||
A finalizer is a method that executes after all postfixes. It wraps the original method, all prefixes, and postfixes in try/catch logic and is called either with null (no exception) or with an exception if one occurred. It is commonly used to:
|
||||
|
||||
run a piece of code at the end that is guaranteed to be executed
|
||||
handle exceptions and suppress them
|
||||
handle exceptions and alter them
|
||||
@@ -6,9 +6,6 @@
|
||||
},
|
||||
{
|
||||
"path": "../../../../Data"
|
||||
},
|
||||
{
|
||||
"path": "../../../../dll1.6"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompUseEffect_AddDamageShieldCharges : CompUseEffect
|
||||
{
|
||||
public CompProperties_AddDamageShieldCharges Props => (CompProperties_AddDamageShieldCharges)props;
|
||||
|
||||
public override void DoEffect(Pawn user)
|
||||
{
|
||||
base.DoEffect(user);
|
||||
|
||||
// 获取或添加 Hediff_DamageShield
|
||||
Hediff_DamageShield damageShield = user.health.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
|
||||
if (damageShield == null)
|
||||
{
|
||||
// 如果没有 Hediff,则添加一个
|
||||
damageShield = (Hediff_DamageShield)HediffMaker.MakeHediff(Props.hediffDef, user);
|
||||
user.health.AddHediff(damageShield);
|
||||
damageShield.ShieldCharges = Props.chargesToAdd; // 设置初始层数
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果已有 Hediff,则增加层数
|
||||
damageShield.ShieldCharges += Props.chargesToAdd;
|
||||
}
|
||||
|
||||
// 确保层数不超过最大值
|
||||
if (damageShield.ShieldCharges > (int)damageShield.def.maxSeverity)
|
||||
{
|
||||
damageShield.ShieldCharges = (int)damageShield.def.maxSeverity;
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
Messages.Message("WULA_MessageGainedDamageShieldCharges".Translate(user.LabelShort, Props.chargesToAdd), user, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
|
||||
// 修正 CanBeUsedBy 方法签名
|
||||
public override AcceptanceReport CanBeUsedBy(Pawn p)
|
||||
{
|
||||
// 确保只能对活着的 Pawn 使用
|
||||
if (p.Dead)
|
||||
{
|
||||
return "WULA_CannotUseOnDeadPawn".Translate();
|
||||
}
|
||||
|
||||
// 检查是否已达到最大层数
|
||||
Hediff_DamageShield damageShield = p.health.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (damageShield != null && damageShield.ShieldCharges >= (int)damageShield.def.maxSeverity)
|
||||
{
|
||||
return "WULA_DamageShieldMaxChargesReached".Translate();
|
||||
}
|
||||
|
||||
return true; // 可以使用
|
||||
}
|
||||
|
||||
// 可以在这里添加 GetDescriptionPart() 来显示描述
|
||||
public override string GetDescriptionPart()
|
||||
{
|
||||
return "WULA_DamageShieldChargesDescription".Translate(Props.chargesToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_AddDamageShieldCharges : CompProperties_UseEffect
|
||||
{
|
||||
public HediffDef hediffDef;
|
||||
public int chargesToAdd;
|
||||
|
||||
public CompProperties_AddDamageShieldCharges()
|
||||
{
|
||||
compClass = typeof(CompUseEffect_AddDamageShieldCharges);
|
||||
}
|
||||
}
|
||||
}
|
||||
211
Source/WulaFallenEmpire/DRMDamageShield.cs
Normal file
211
Source/WulaFallenEmpire/DRMDamageShield.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
using HarmonyLib; // For AccessTools
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
// 自定义 CompProperties_Shield 变体
|
||||
public class DRMCompShieldProp : CompProperties
|
||||
{
|
||||
public int startingTicksToReset = 3200;
|
||||
public float minDrawSize = 1.2f;
|
||||
public float maxDrawSize = 1.55f;
|
||||
public float energyLossPerDamage = 0.033f;
|
||||
public float energyOnReset = 0.2f;
|
||||
public bool blocksRangedWeapons = true;
|
||||
|
||||
public DRMCompShieldProp()
|
||||
{
|
||||
compClass = typeof(DRMDamageShield);
|
||||
}
|
||||
}
|
||||
|
||||
public class DRMDamageShield : ThingComp
|
||||
{
|
||||
// 从 Hediff_DamageShield 获取层数作为能量
|
||||
public float Energy
|
||||
{
|
||||
get
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
return hediff?.ShieldCharges ?? 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.ShieldCharges = (int)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float MaxEnergy
|
||||
{
|
||||
get
|
||||
{
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
return hediff?.def.maxSeverity ?? 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
// MaxEnergy 由 HediffDef 控制,这里不需要设置
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsActive = false; // 控制护盾是否激活,由 Hediff_DamageShield 管理
|
||||
|
||||
// 复制自 CompShield
|
||||
protected int ticksToReset = -1;
|
||||
protected int lastKeepDisplayTick = -9999;
|
||||
private Vector3 impactAngleVect;
|
||||
private int lastAbsorbDamageTick = -9999;
|
||||
|
||||
public DRMCompShieldProp Props => (DRMCompShieldProp)props;
|
||||
|
||||
public ShieldState ShieldState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PawnOwner == null || !IsActive || Energy <= 0)
|
||||
{
|
||||
return ShieldState.Disabled;
|
||||
}
|
||||
if (ticksToReset <= 0)
|
||||
{
|
||||
return ShieldState.Active;
|
||||
}
|
||||
return ShieldState.Resetting;
|
||||
}
|
||||
}
|
||||
|
||||
protected Pawn PawnOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent as Pawn;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref ticksToReset, "ticksToReset", -1);
|
||||
Scribe_Values.Look(ref lastKeepDisplayTick, "lastKeepDisplayTick", 0);
|
||||
Scribe_Values.Look(ref IsActive, "isActive", false);
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
if (PawnOwner == null || !IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShieldState == ShieldState.Resetting)
|
||||
{
|
||||
ticksToReset--;
|
||||
if (ticksToReset <= 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
else if (ShieldState == ShieldState.Active)
|
||||
{
|
||||
// 护盾能量(层数)通过 Hediff_DamageShield 的 Tick 方法管理,这里不需要额外回复
|
||||
// 如果需要自动回复层数,可以在这里实现
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
|
||||
{
|
||||
absorbed = false;
|
||||
// 获取 Hediff_DamageShield 实例
|
||||
Hediff_DamageShield damageShield = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
|
||||
if (ShieldState != ShieldState.Active || !IsActive || damageShield == null || damageShield.ShieldCharges <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 我们的护盾阻挡所有伤害类型,但不包含手术
|
||||
// 如果伤害类型不被认为是“有益的”(例如,不是手术),则阻挡
|
||||
if (!dinfo.Def.consideredHelpful)
|
||||
{
|
||||
// 消耗一层护盾
|
||||
damageShield.ShieldCharges--;
|
||||
|
||||
// 触发护盾吸收效果
|
||||
Notify_DamageAbsorbed(dinfo);
|
||||
|
||||
// 护盾抖动效果
|
||||
PawnOwner.Drawer.renderer.wiggler.SetToCustomRotation(Rand.Range(-0.05f, 0.05f));
|
||||
// 移除文字提示
|
||||
// 移除粒子效果
|
||||
|
||||
absorbed = true; // 伤害被吸收
|
||||
|
||||
// 如果护盾层数归零,触发护盾击穿效果
|
||||
if (damageShield.ShieldCharges <= 0)
|
||||
{
|
||||
Notify_ShieldBreak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify_DamageAbsorbed(DamageInfo dinfo)
|
||||
{
|
||||
// 复制自 CompShield.AbsorbedDamage
|
||||
SoundDefOf.EnergyShield_AbsorbDamage.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
|
||||
impactAngleVect = Vector3Utility.HorizontalVectorFromAngle(dinfo.Angle);
|
||||
// 移除 FleckMaker.Static 和 FleckMaker.ThrowDustPuff
|
||||
lastAbsorbDamageTick = Find.TickManager.TicksGame;
|
||||
KeepDisplaying();
|
||||
}
|
||||
|
||||
public void Notify_ShieldBreak()
|
||||
{
|
||||
// 复制自 CompShield.Break
|
||||
if (parent.Spawned)
|
||||
{
|
||||
float scale = Mathf.Lerp(Props.minDrawSize, Props.maxDrawSize, Energy / MaxEnergy); // 根据当前能量比例调整大小
|
||||
EffecterDefOf.Shield_Break.SpawnAttached(parent, parent.MapHeld, scale);
|
||||
// 移除 FleckMaker.Static 和 FleckMaker.ThrowDustPuff
|
||||
}
|
||||
ticksToReset = Props.startingTicksToReset;
|
||||
// 护盾层数归零将由 Hediff_DamageShield 负责移除 Hediff
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// 复制自 CompShield.Reset
|
||||
if (PawnOwner.Spawned)
|
||||
{
|
||||
SoundDefOf.EnergyShield_Reset.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
|
||||
// 移除 FleckMaker.ThrowLightningGlow
|
||||
}
|
||||
ticksToReset = -1;
|
||||
// 能量恢复由 Hediff_DamageShield 负责,这里不需要设置 Energy
|
||||
// 这里可以添加逻辑,让 Hediff_DamageShield 恢复层数
|
||||
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
|
||||
if (hediff != null)
|
||||
{
|
||||
hediff.ShieldCharges = (int)hediff.def.initialSeverity; // 重置时恢复到初始层数
|
||||
}
|
||||
}
|
||||
|
||||
public void KeepDisplaying()
|
||||
{
|
||||
lastKeepDisplayTick = Find.TickManager.TicksGame;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class HediffCompProperties_GiveHediffsInRangeToRace : HediffCompProperties
|
||||
{
|
||||
public float range;
|
||||
public TargetingParameters targetingParameters;
|
||||
public HediffDef hediff;
|
||||
public ThingDef mote;
|
||||
public bool hideMoteWhenNotDrafted;
|
||||
public float initialSeverity = 1f;
|
||||
public bool onlyPawnsInSameFaction = true;
|
||||
public List<ThingDef> targetRaces; // 新增:可配置的目标种族列表
|
||||
|
||||
public HediffCompProperties_GiveHediffsInRangeToRace()
|
||||
{
|
||||
compClass = typeof(HediffComp_GiveHediffsInRangeToRace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class HediffComp_GiveHediffsInRangeToRace : HediffComp
|
||||
{
|
||||
private Mote mote;
|
||||
|
||||
public HediffCompProperties_GiveHediffsInRangeToRace Props => (HediffCompProperties_GiveHediffsInRangeToRace)props;
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
if (!parent.pawn.Awake() || parent.pawn.health == null || parent.pawn.health.InPainShock || !parent.pawn.Spawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Props.hideMoteWhenNotDrafted || parent.pawn.Drafted)
|
||||
{
|
||||
if (Props.mote != null && (mote == null || mote.Destroyed))
|
||||
{
|
||||
mote = MoteMaker.MakeAttachedOverlay(parent.pawn, Props.mote, Vector3.zero);
|
||||
}
|
||||
if (mote != null)
|
||||
{
|
||||
mote.Maintain();
|
||||
}
|
||||
}
|
||||
IReadOnlyList<Pawn> pawns = ((!Props.onlyPawnsInSameFaction || parent.pawn.Faction == null) ? parent.pawn.Map.mapPawns.AllPawnsSpawned : parent.pawn.Map.mapPawns.SpawnedPawnsInFaction(parent.pawn.Faction));
|
||||
foreach (Pawn pawn in pawns)
|
||||
{
|
||||
// 修改点:检查种族是否在我们的目标列表中,如果列表为空或null则不进行任何操作
|
||||
if ((Props.targetRaces.NullOrEmpty() || !Props.targetRaces.Contains(pawn.def)) || pawn.Dead || pawn.health == null || pawn == parent.pawn || !(pawn.Position.DistanceTo(parent.pawn.Position) <= Props.range) || !Props.targetingParameters.CanTarget(pawn))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediff);
|
||||
if (hediff == null)
|
||||
{
|
||||
hediff = pawn.health.AddHediff(Props.hediff, pawn.health.hediffSet.GetBrain());
|
||||
hediff.Severity = Props.initialSeverity;
|
||||
HediffComp_Link hediffComp_Link = hediff.TryGetComp<HediffComp_Link>();
|
||||
if (hediffComp_Link != null)
|
||||
{
|
||||
hediffComp_Link.drawConnection = true;
|
||||
hediffComp_Link.other = parent.pawn;
|
||||
}
|
||||
}
|
||||
HediffComp_Disappears hediffComp_Disappears = hediff.TryGetComp<HediffComp_Disappears>();
|
||||
if (hediffComp_Disappears == null)
|
||||
{
|
||||
Log.Error("HediffComp_GiveHediffsInRangeToRace has a hediff in props which does not have a HediffComp_Disappears");
|
||||
}
|
||||
else
|
||||
{
|
||||
hediffComp_Disappears.ticksToDisappear = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Source/WulaFallenEmpire/Hediff_DamageShield.cs
Normal file
115
Source/WulaFallenEmpire/Hediff_DamageShield.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Verse;
|
||||
using System; // Add for Activator
|
||||
using System.Text;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using HarmonyLib; // Needed for AccessTools if you use it here directly
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Hediff_DamageShield : HediffWithComps
|
||||
{
|
||||
// 伤害抵挡层数
|
||||
public int ShieldCharges
|
||||
{
|
||||
get => (int)severityInt;
|
||||
set => severityInt = value;
|
||||
}
|
||||
|
||||
// 获取或创建 DRMDamageShield 组件
|
||||
public DRMDamageShield ShieldComp
|
||||
{
|
||||
get
|
||||
{
|
||||
DRMDamageShield comp = pawn.GetComp<DRMDamageShield>();
|
||||
if (comp == null)
|
||||
{
|
||||
comp = (DRMDamageShield)Activator.CreateInstance(typeof(DRMDamageShield));
|
||||
comp.parent = pawn;
|
||||
comp.props = new DRMCompShieldProp(); // 确保有属性,即使是默认的
|
||||
pawn.AllComps.Add(comp);
|
||||
comp.Initialize(comp.props);
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string LabelInBrackets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ShieldCharges > 0)
|
||||
{
|
||||
return "层数: " + ShieldCharges;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string TipStringExtra
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(base.TipStringExtra);
|
||||
if (ShieldCharges > 0)
|
||||
{
|
||||
sb.AppendLine(" - 每层抵挡一次伤害。当前层数: " + ShieldCharges);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(" - 没有可用的抵挡层数。");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
// severityInt 会自动保存,所以不需要额外处理 ShieldCharges
|
||||
}
|
||||
|
||||
public override void PostAdd(DamageInfo? dinfo)
|
||||
{
|
||||
base.PostAdd(dinfo);
|
||||
// 确保 Pawn 拥有 DRMCompShield 组件
|
||||
DRMDamageShield comp = ShieldComp; // 访问属性以确保组件被添加
|
||||
if (comp != null)
|
||||
{
|
||||
comp.IsActive = true; // 激活护盾组件
|
||||
// 能量同步将在 Tick() 中完成
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostRemoved()
|
||||
{
|
||||
base.PostRemoved();
|
||||
// 当 Hediff 被移除时,移除对应的 DRMDamageShield 组件
|
||||
DRMDamageShield comp = pawn.GetComp<DRMDamageShield>();
|
||||
if (comp != null)
|
||||
{
|
||||
pawn.AllComps.Remove(comp);
|
||||
comp.IsActive = false; // 确保禁用
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
// 如果层数归零,移除 Hediff
|
||||
if (ShieldCharges <= 0)
|
||||
{
|
||||
pawn.health.RemoveHediff(this);
|
||||
}
|
||||
// 同步能量到 ShieldComp
|
||||
DRMDamageShield comp = pawn.GetComp<DRMDamageShield>(); // 每次 Tick 获取,确保是最新的
|
||||
if (comp != null && comp.IsActive)
|
||||
{
|
||||
comp.Energy = ShieldCharges;
|
||||
comp.MaxEnergy = (int)def.maxSeverity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,11 @@
|
||||
<Compile Include="Verb\VerbProperties_WeaponStealBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootWeaponStealBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootMeltBeam.cs" />
|
||||
<Compile Include="Hediff_DamageShield.cs" />
|
||||
<Compile Include="DRMDamageShield.cs" />
|
||||
<Compile Include="CompUseEffect_AddDamageShieldCharges.cs" />
|
||||
<Compile Include="HediffCompProperties_GiveHediffsInRangeToRace.cs" />
|
||||
<Compile Include="HediffComp_GiveHediffsInRangeToRace.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
Reference in New Issue
Block a user