2
This commit is contained in:
Binary file not shown.
@@ -60,8 +60,8 @@
|
|||||||
<cooldownTicksRange>600</cooldownTicksRange>
|
<cooldownTicksRange>600</cooldownTicksRange>
|
||||||
<verbProperties>
|
<verbProperties>
|
||||||
<verbClass>Verb_CastAbility</verbClass>
|
<verbClass>Verb_CastAbility</verbClass>
|
||||||
<range>24</range>
|
<range>18</range>
|
||||||
<warmupTime>0</warmupTime>
|
<warmupTime>1</warmupTime>
|
||||||
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
||||||
|
|
||||||
<ai_IsWeapon>true</ai_IsWeapon>
|
<ai_IsWeapon>true</ai_IsWeapon>
|
||||||
@@ -85,36 +85,16 @@
|
|||||||
</verbProperties>
|
</verbProperties>
|
||||||
<comps>
|
<comps>
|
||||||
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
|
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
|
||||||
<range>12</range>
|
<range>18</range>
|
||||||
<lineWidthEnd>5</lineWidthEnd>
|
<lineWidthEnd>12</lineWidthEnd>
|
||||||
<canHitFilledCells>true</canHitFilledCells>
|
<canHitFilledCells>true</canHitFilledCells>
|
||||||
<affectAllies>false</affectAllies>
|
<affectAllies>false</affectAllies>
|
||||||
<affectCaster>false</affectCaster>
|
<affectCaster>false</affectCaster>
|
||||||
<effecterDef>Fire_Spew</effecterDef>
|
<castEffecter>Fire_SpewShort</castEffecter>
|
||||||
<hitEffecter>Fire_SpewShort</hitEffecter>
|
<castEffecterMaintainTicks>60</castEffecterMaintainTicks>
|
||||||
|
<hitEffecter>Fire_Spew</hitEffecter>
|
||||||
|
<hitEffecterMaintainTicks>30</hitEffecterMaintainTicks>
|
||||||
</li>
|
</li>
|
||||||
<li Class="CompProperties_AbilityEffecterOnCaster">
|
|
||||||
<effecterDef>Fire_Spew</effecterDef>
|
|
||||||
<maintainTicks>20</maintainTicks> <!-- Long enough for the "2nd wave" to spawn -->
|
|
||||||
</li>
|
|
||||||
<!-- <li Class="CompProperties_AbilityFleckOnTarget">
|
|
||||||
<fleckDef>PsycastPsychicEffect</fleckDef>
|
|
||||||
</li> -->
|
|
||||||
<li Class="CompProperties_AbilityEffecterOnTarget">
|
|
||||||
<effecterDef>Fire_SpewShort</effecterDef>
|
|
||||||
</li>
|
|
||||||
<!-- <li Class="CompProperties_AbilityEffecterOnTarget">
|
|
||||||
<effecterDef>Skip_Exit</effecterDef>
|
|
||||||
<maintainForTicks>60</maintainForTicks>
|
|
||||||
</li>
|
|
||||||
<li Class="CompProperties_AbilityFleckOnTarget">
|
|
||||||
<fleckDefs>
|
|
||||||
<li>PsycastSkipInnerExit</li>
|
|
||||||
<li>PsycastSkipOuterRingExit</li>
|
|
||||||
</fleckDefs>
|
|
||||||
<sound>Psycast_Skip_Exit</sound>
|
|
||||||
<preCastTicks>5</preCastTicks>
|
|
||||||
</li> -->
|
|
||||||
</comps>
|
</comps>
|
||||||
</AbilityDef>
|
</AbilityDef>
|
||||||
</Defs>
|
</Defs>
|
||||||
@@ -502,33 +502,6 @@
|
|||||||
<wornGraphicPath>Wula/Apparel/WULA_Knight_PowerArmor</wornGraphicPath>
|
<wornGraphicPath>Wula/Apparel/WULA_Knight_PowerArmor</wornGraphicPath>
|
||||||
</apparel>
|
</apparel>
|
||||||
<comps>
|
<comps>
|
||||||
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
|
|
||||||
<!-- 基础功能 -->
|
|
||||||
<radius>1</radius> <!-- 护盾半径,决定了拦截范围 -->
|
|
||||||
<hitPoints>50</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
|
|
||||||
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
|
|
||||||
<maxBounces>3</maxBounces>
|
|
||||||
|
|
||||||
<!-- 拦截类型 -->
|
|
||||||
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
|
|
||||||
<interceptAirProjectiles>true</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
|
|
||||||
<interceptNonHostileProjectiles>true</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
|
|
||||||
|
|
||||||
<!-- 视觉与音效 -->
|
|
||||||
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
|
|
||||||
<soundInterceptEffecter>Interceptor_BlockedProjectile</soundInterceptEffecter> <!-- 成功拦截时的音效 -->
|
|
||||||
<soundBreakEffecter>Shield_Break</soundBreakEffecter> <!-- 护盾破裂时的音效 -->
|
|
||||||
<reactivateEffect>BulletShieldGenerator_Reactivate</reactivateEffect> <!-- 护盾冷却结束后恢复的特效 -->
|
|
||||||
|
|
||||||
<drawWithNoSelection>false</drawWithNoSelection>
|
|
||||||
|
|
||||||
<!-- EMP 效果 -->
|
|
||||||
<isImmuneToEMP>false</isImmuneToEMP> <!-- 是否免疫EMP伤害 -->
|
|
||||||
<disarmedByEmpForTicks>600</disarmedByEmpForTicks> <!-- 被EMP击中后,额外的眩晕/瘫痪时间 (ticks) -->
|
|
||||||
|
|
||||||
<!-- 被动恢复 -->
|
|
||||||
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks> <!-- 未破盾时,每隔多少ticks恢复1点生命值 -->
|
|
||||||
</li>
|
|
||||||
</comps>
|
</comps>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
<ThingDef ParentName="WULA_ApparelHelmetBase">
|
<ThingDef ParentName="WULA_ApparelHelmetBase">
|
||||||
@@ -624,30 +597,31 @@
|
|||||||
<wornGraphicPath>Wula/Apparel/WULA_Heavy_Infantry_PowerArmor</wornGraphicPath>
|
<wornGraphicPath>Wula/Apparel/WULA_Heavy_Infantry_PowerArmor</wornGraphicPath>
|
||||||
</apparel>
|
</apparel>
|
||||||
<comps>
|
<comps>
|
||||||
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
|
<li Class="WulaFallenEmpire.CompProperties_AreaShield">
|
||||||
<!-- 基础功能 -->
|
<radius>3</radius>
|
||||||
<radius>1.5</radius> <!-- 护盾半径,决定了拦截范围 -->
|
<baseHitPoints>50</baseHitPoints>
|
||||||
<hitPoints>200</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
|
<rechargeDelay>2400</rechargeDelay>
|
||||||
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
|
<rechargeHitPointsIntervalTicks>30</rechargeHitPointsIntervalTicks>
|
||||||
|
|
||||||
<!-- 拦截类型 -->
|
<!-- 效果器配置 -->
|
||||||
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
|
<absorbEffecter>Interceptor_BlockedProjectile</absorbEffecter>
|
||||||
<interceptAirProjectiles>false</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
|
<interceptEffecter>Interceptor_BlockedProjectile</interceptEffecter>
|
||||||
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
|
<breakEffecter>Shield_Break</breakEffecter>
|
||||||
|
<reactivateEffecter>BulletShieldGenerator_Reactivate</reactivateEffecter>
|
||||||
|
|
||||||
<!-- 视觉与音效 -->
|
<color>(0.9, 0.2, 0.2, 0.2)</color> <!-- 护盾气泡的颜色 (RGBA) -->
|
||||||
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
|
|
||||||
<soundInterceptEffecter>Interceptor_BlockedProjectile</soundInterceptEffecter> <!-- 成功拦截时的音效 -->
|
|
||||||
<soundBreakEffecter>Shield_Break</soundBreakEffecter> <!-- 护盾破裂时的音效 -->
|
|
||||||
<reactivateEffect>BulletShieldGenerator_Reactivate</reactivateEffect> <!-- 护盾冷却结束后恢复的特效 -->
|
|
||||||
|
|
||||||
<!-- EMP 效果 -->
|
<!-- 拦截设置 -->
|
||||||
<isImmuneToEMP>false</isImmuneToEMP> <!-- 是否免疫EMP伤害 -->
|
<interceptGroundProjectiles>true</interceptGroundProjectiles>
|
||||||
<disarmedByEmpForTicks>600</disarmedByEmpForTicks> <!-- 被EMP击中后,额外的眩晕/瘫痪时间 (ticks) -->
|
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles>
|
||||||
|
<interceptAirProjectiles>true</interceptAirProjectiles>
|
||||||
<!-- 被动恢复 -->
|
|
||||||
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks> <!-- 未破盾时,每隔多少ticks恢复1点生命值 -->
|
|
||||||
|
|
||||||
|
<!-- 反射设置 -->
|
||||||
|
<canReflect>true</canReflect>
|
||||||
|
<reflectChance>1</reflectChance>
|
||||||
|
<reflectAngleRange>30</reflectAngleRange>
|
||||||
|
<reflectCost>1</reflectCost>
|
||||||
|
<reflectEffecter>Interceptor_BlockedProjectile</reflectEffecter>
|
||||||
</li>
|
</li>
|
||||||
<li Class="CompProperties_CauseHediff_Apparel">
|
<li Class="CompProperties_CauseHediff_Apparel">
|
||||||
<hediff>WULA_Heavy_Infantry_PowerArmor_Mortar_Hediff</hediff>
|
<hediff>WULA_Heavy_Infantry_PowerArmor_Mortar_Hediff</hediff>
|
||||||
|
|||||||
@@ -344,4 +344,17 @@
|
|||||||
<WULA_TaxCollection.Status>什一税征收:{0} {1} {2}</WULA_TaxCollection.Status>
|
<WULA_TaxCollection.Status>什一税征收:{0} {1} {2}</WULA_TaxCollection.Status>
|
||||||
<WULA_TaxCollection.Status.Succeeded>- 征收已完成</WULA_TaxCollection.Status.Succeeded>
|
<WULA_TaxCollection.Status.Succeeded>- 征收已完成</WULA_TaxCollection.Status.Succeeded>
|
||||||
<WULA_TaxCollection.Status.Failed>- 逾期</WULA_TaxCollection.Status.Failed>
|
<WULA_TaxCollection.Status.Failed>- 逾期</WULA_TaxCollection.Status.Failed>
|
||||||
|
|
||||||
|
<!-- 区域护盾Gizmo文本 -->
|
||||||
|
<AreaShield_Cooldown>冷却中</AreaShield_Cooldown>
|
||||||
|
<AreaShield_Inactive>未激活</AreaShield_Inactive>
|
||||||
|
<AreaShield_Yes>是</AreaShield_Yes>
|
||||||
|
<AreaShield_No>否</AreaShield_No>
|
||||||
|
|
||||||
|
<AreaShield_Tooltip_Basic>区域护盾状态:\n最大生命值:{0}\n保护半径:{1}格\n破碎冷却:{3}秒</AreaShield_Tooltip_Basic>
|
||||||
|
|
||||||
|
<AreaShield_Tooltip_Intercept>拦截设置:\n- 地面投射物:{0}\n- 空中投射物:{1}\n- 非敌对投射物:{2}</AreaShield_Tooltip_Intercept>
|
||||||
|
|
||||||
|
<AreaShield_CooldownRemaining>冷却剩余时间:{0}秒</AreaShield_CooldownRemaining>
|
||||||
|
<AreaShield_InactiveReason>护盾未激活:\n- 装备未被穿戴\n- 穿戴者死亡或倒下\n- 护盾已破碎</AreaShield_InactiveReason>
|
||||||
</LanguageData>
|
</LanguageData>
|
||||||
@@ -21,6 +21,9 @@ namespace WulaFallenEmpire
|
|||||||
Map map = parent.pawn.MapHeld;
|
Map map = parent.pawn.MapHeld;
|
||||||
if (map == null) return;
|
if (map == null) return;
|
||||||
|
|
||||||
|
// 播放发射特效(在施法者位置)
|
||||||
|
PlayCastEffecter(map);
|
||||||
|
|
||||||
// 获取扇形区域内的所有单元格
|
// 获取扇形区域内的所有单元格
|
||||||
List<IntVec3> affectedCells = AffectedCells(target);
|
List<IntVec3> affectedCells = AffectedCells(target);
|
||||||
|
|
||||||
@@ -47,29 +50,41 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
affectedTargets.Add(thing);
|
affectedTargets.Add(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据事物类型进行处理
|
|
||||||
if (thing is Building building)
|
|
||||||
{
|
|
||||||
DestroyBuilding(building);
|
|
||||||
}
|
|
||||||
else if (thing is Pawn targetPawn)
|
|
||||||
{
|
|
||||||
DestroyAllBodyParts(targetPawn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个受影响的目标播放命中效果器
|
// 为每个受影响的目标播放命中效果器并处理效果
|
||||||
foreach (Thing affectedThing in affectedTargets)
|
foreach (Thing affectedThing in affectedTargets)
|
||||||
{
|
{
|
||||||
PlayHitEffecter(affectedThing, map);
|
PlayHitEffecter(affectedThing, map);
|
||||||
|
ProcessTarget(affectedThing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放主要效果
|
private void PlayCastEffecter(Map map)
|
||||||
if (Props.effecterDef != null)
|
|
||||||
{
|
{
|
||||||
Props.effecterDef.Spawn(target.Cell, map).Cleanup();
|
try
|
||||||
|
{
|
||||||
|
if (Props.castEffecter == null) return;
|
||||||
|
|
||||||
|
// 使用与 CompAbilityEffect_EffecterOnCaster 相同的方法
|
||||||
|
Effecter effecter = Props.castEffecter.Spawn(Pawn, map);
|
||||||
|
|
||||||
|
if (Props.castEffecterMaintainTicks > 0)
|
||||||
|
{
|
||||||
|
// 使用与参考代码相同的方法来维持效果器
|
||||||
|
map.effecterMaintainer.AddEffecterToMaintain(effecter, new TargetInfo(Pawn), Pawn, Props.castEffecterMaintainTicks);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
effecter.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Message($"[AreaDestruction] Played cast effecter on caster at {Pawn.Position}");
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"[AreaDestruction] Error playing cast effecter: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,28 +95,25 @@ namespace WulaFallenEmpire
|
|||||||
if (Props.hitEffecter == null) return;
|
if (Props.hitEffecter == null) return;
|
||||||
if (target == null || target.Destroyed) return;
|
if (target == null || target.Destroyed) return;
|
||||||
|
|
||||||
// 创建效果器
|
// 使用与 CompAbilityEffect_EffecterOnTarget 相同的方法
|
||||||
Effecter effecter = Props.hitEffecter.Spawn();
|
Effecter effecter;
|
||||||
|
if (target is Pawn pawnTarget)
|
||||||
// 计算效果器方向(从目标指向施法者)
|
|
||||||
TargetInfo targetInfo = new TargetInfo(target.Position, map, false);
|
|
||||||
TargetInfo casterInfo = new TargetInfo(Pawn.Position, map, false);
|
|
||||||
|
|
||||||
// 触发效果器,方向朝向施法者
|
|
||||||
effecter.Trigger(targetInfo, casterInfo);
|
|
||||||
|
|
||||||
// 如果效果器需要持续维护,添加到维护列表
|
|
||||||
if (Props.hitEffecter.maintainTicks > 0)
|
|
||||||
{
|
{
|
||||||
map.effecterMaintainer.AddEffecterToMaintain(effecter, target, Props.hitEffecter.maintainTicks);
|
effecter = Props.hitEffecter.Spawn(pawnTarget, map);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 否则在适当时间后清理
|
effecter = Props.hitEffecter.Spawn(target.Position, map);
|
||||||
LongEventHandler.ExecuteWhenFinished(delegate
|
}
|
||||||
|
|
||||||
|
if (Props.hitEffecterMaintainTicks > 0)
|
||||||
|
{
|
||||||
|
// 使用与参考代码相同的方法来维持效果器
|
||||||
|
parent.AddEffecterToMaintain(effecter, target.Position, Props.hitEffecterMaintainTicks);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
effecter.Cleanup();
|
effecter.Cleanup();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}");
|
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}");
|
||||||
@@ -112,6 +124,18 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessTarget(Thing target)
|
||||||
|
{
|
||||||
|
if (target is Building building)
|
||||||
|
{
|
||||||
|
DestroyBuilding(building);
|
||||||
|
}
|
||||||
|
else if (target is Pawn targetPawn)
|
||||||
|
{
|
||||||
|
DestroyAllBodyParts(targetPawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ShouldAffectThing(Thing thing)
|
private bool ShouldAffectThing(Thing thing)
|
||||||
{
|
{
|
||||||
// 检查是否影响施法者自己
|
// 检查是否影响施法者自己
|
||||||
@@ -232,18 +256,8 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
public override IEnumerable<PreCastAction> GetPreCastActions()
|
public override IEnumerable<PreCastAction> GetPreCastActions()
|
||||||
{
|
{
|
||||||
if (Props.effecterDef != null)
|
// 这里不再预先创建效果器,改为在Apply中创建
|
||||||
{
|
yield break;
|
||||||
yield return new PreCastAction
|
|
||||||
{
|
|
||||||
action = delegate(LocalTargetInfo a, LocalTargetInfo b)
|
|
||||||
{
|
|
||||||
parent.AddEffecterToMaintain(Props.effecterDef.Spawn(parent.pawn.Position, a.Cell, parent.pawn.Map),
|
|
||||||
Pawn.Position, a.Cell, 17, Pawn.MapHeld);
|
|
||||||
},
|
|
||||||
ticksAwayFromCast = 17
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
public float range;
|
public float range;
|
||||||
public float lineWidthEnd;
|
public float lineWidthEnd;
|
||||||
public EffecterDef effecterDef;
|
|
||||||
public bool canHitFilledCells;
|
|
||||||
|
|
||||||
// 新增:命中效果器
|
// 发射特效(在施法者位置)
|
||||||
|
public EffecterDef castEffecter;
|
||||||
|
public int castEffecterMaintainTicks = 60;
|
||||||
|
|
||||||
|
// 命中特效(在目标位置)
|
||||||
public EffecterDef hitEffecter;
|
public EffecterDef hitEffecter;
|
||||||
|
public int hitEffecterMaintainTicks = 30;
|
||||||
|
|
||||||
// 新增:是否影响友方单位
|
public bool canHitFilledCells;
|
||||||
public bool affectAllies = false;
|
public bool affectAllies = false;
|
||||||
|
|
||||||
// 新增:是否影响施法者自己
|
|
||||||
public bool affectCaster = false;
|
public bool affectCaster = false;
|
||||||
|
|
||||||
public CompProperties_AbilityAreaDestruction()
|
public CompProperties_AbilityAreaDestruction()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace WulaFallenEmpire
|
|||||||
public ThingDef turretDef;
|
public ThingDef turretDef;
|
||||||
public float angleOffset;
|
public float angleOffset;
|
||||||
public bool autoAttack = true;
|
public bool autoAttack = true;
|
||||||
public bool defaultEnabled = true; // 新增:默认启用状态
|
public bool defaultEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StaticConstructorOnStartup]
|
[StaticConstructorOnStartup]
|
||||||
@@ -87,31 +87,28 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void CompPostTick(ref float severityAdjustment)
|
public override void CompPostTick(ref float severityAdjustment)
|
||||||
{
|
{
|
||||||
base.CompPostTick(ref severityAdjustment);
|
base.CompPostTick(ref severityAdjustment);
|
||||||
|
|
||||||
if (!TurretEnabled)
|
if (!TurretEnabled)
|
||||||
{
|
{
|
||||||
ResetCurrentTarget();
|
ResetCurrentTarget();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.CanShoot)
|
if (!this.CanShoot)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 优先处理手动目标
|
|
||||||
if (HasManualTarget && CanAttackManualTarget)
|
if (this.currentTarget.IsValid)
|
||||||
{
|
|
||||||
LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
this.currentTarget = manualTarget;
|
|
||||||
this.curRotation = (manualTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
|
||||||
}
|
|
||||||
else if (this.currentTarget.IsValid)
|
|
||||||
{
|
{
|
||||||
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.AttackVerb.VerbTick();
|
this.AttackVerb.VerbTick();
|
||||||
|
|
||||||
if (this.AttackVerb.state != VerbState.Bursting)
|
if (this.AttackVerb.state != VerbState.Bursting)
|
||||||
{
|
{
|
||||||
if (this.WarmingUp)
|
if (this.WarmingUp)
|
||||||
@@ -125,14 +122,6 @@ namespace WulaFallenEmpire
|
|||||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||||
this.lastAttackedTarget = this.currentTarget;
|
this.lastAttackedTarget = this.currentTarget;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果手动攻击失败且目标无效,清除手动目标
|
|
||||||
if (HasManualTarget && !CanAttackManualTarget)
|
|
||||||
{
|
|
||||||
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,68 +131,30 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
this.burstCooldownTicksLeft--;
|
this.burstCooldownTicksLeft--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||||
{
|
{
|
||||||
// 如果手动目标无效,清除它
|
// 自动寻找目标
|
||||||
if (HasManualTarget && !CanAttackManualTarget)
|
|
||||||
{
|
|
||||||
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
|
||||||
}
|
|
||||||
// 只有在没有有效的手动目标时才寻找新目标
|
|
||||||
if (!HasManualTarget || !CanAttackManualTarget)
|
|
||||||
{
|
|
||||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||||
|
|
||||||
if (this.currentTarget.IsValid)
|
if (this.currentTarget.IsValid)
|
||||||
{
|
{
|
||||||
this.burstWarmupTicksLeft = 1;
|
this.burstWarmupTicksLeft = 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.ResetCurrentTarget();
|
this.ResetCurrentTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 检查是否有手动目标
|
|
||||||
private bool HasManualTarget
|
// 简化的Gizmos - 只有开关按钮
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
return manualTarget.IsValid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 检查是否可以攻击手动目标
|
|
||||||
private bool CanAttackManualTarget
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (!manualTarget.IsValid)
|
|
||||||
return false;
|
|
||||||
// 检查目标是否在射程内
|
|
||||||
float distance = Pawn.Position.DistanceTo(manualTarget.Cell);
|
|
||||||
if (distance > AttackVerb.verbProps.range)
|
|
||||||
return false;
|
|
||||||
// 检查是否可以命中目标
|
|
||||||
if (!AttackVerb.CanHitTarget(manualTarget))
|
|
||||||
return false;
|
|
||||||
// 检查目标是否还活着(如果是生物)
|
|
||||||
if (manualTarget.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed))
|
|
||||||
return false;
|
|
||||||
// 检查目标是否被摧毁(如果是建筑)
|
|
||||||
if (manualTarget.Thing != null && manualTarget.Thing.Destroyed)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 简化的Gizmos - 只有设置目标和清除目标按钮
|
|
||||||
public override IEnumerable<Gizmo> CompGetGizmos()
|
public override IEnumerable<Gizmo> CompGetGizmos()
|
||||||
{
|
{
|
||||||
// 只有 pawn 被选中且是玩家派系时才显示按钮
|
// 只有 pawn 被选中且是玩家派系时才显示按钮
|
||||||
if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn))
|
if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn))
|
||||||
{
|
{
|
||||||
// 原有开关按钮
|
|
||||||
yield return new Command_Toggle
|
yield return new Command_Toggle
|
||||||
{
|
{
|
||||||
defaultLabel = "CommandToggleTurret".Translate(),
|
defaultLabel = "CommandToggleTurret".Translate(),
|
||||||
@@ -213,75 +164,33 @@ namespace WulaFallenEmpire
|
|||||||
toggleAction = () => TurretEnabled = !TurretEnabled,
|
toggleAction = () => TurretEnabled = !TurretEnabled,
|
||||||
hotKey = KeyBindingDefOf.Misc1
|
hotKey = KeyBindingDefOf.Misc1
|
||||||
};
|
};
|
||||||
// 设置目标按钮
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandSetTarget".Translate(),
|
|
||||||
defaultDesc = "CommandSetTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/SetTarget"),
|
|
||||||
action = () =>
|
|
||||||
{
|
|
||||||
Find.Targeter.BeginTargeting(
|
|
||||||
CreateTargetingParameters(),
|
|
||||||
delegate (LocalTargetInfo target)
|
|
||||||
{
|
|
||||||
VolleyTargetManager.SetVolleyTarget(Pawn, target);
|
|
||||||
},
|
|
||||||
Pawn, // caster 参数
|
|
||||||
null, // actionWhenFinished
|
|
||||||
null, // mouseAttachment
|
|
||||||
true // requiresCastedSelected
|
|
||||||
);
|
|
||||||
},
|
|
||||||
hotKey = KeyBindingDefOf.Misc2
|
|
||||||
};
|
|
||||||
// 清除目标按钮(只在有手动目标时显示)
|
|
||||||
LocalTargetInfo currentTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (currentTarget.IsValid)
|
|
||||||
{
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandClearTarget".Translate(),
|
|
||||||
defaultDesc = "CommandClearTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/ClearTarget"),
|
|
||||||
action = () => VolleyTargetManager.ClearVolleyTarget(Pawn),
|
|
||||||
hotKey = KeyBindingDefOf.Misc3
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 创建目标参数
|
// 简化的提示信息
|
||||||
private TargetingParameters CreateTargetingParameters()
|
|
||||||
{
|
|
||||||
return TargetingParameters.ForThing();
|
|
||||||
}
|
|
||||||
// 在提示中显示目标状态
|
|
||||||
public override string CompTipStringExtra
|
public override string CompTipStringExtra
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
string baseString = base.CompTipStringExtra;
|
string baseString = base.CompTipStringExtra;
|
||||||
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
||||||
string targetStatus = "Manual Target: ";
|
string targetStatus = "Target: ";
|
||||||
LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (manualTarget.IsValid)
|
if (this.currentTarget.IsValid)
|
||||||
{
|
{
|
||||||
targetStatus += $"{manualTarget.Thing?.LabelCap ?? manualTarget.Cell.ToString()}";
|
targetStatus += $"{this.currentTarget.Thing?.LabelCap ?? this.currentTarget.Cell.ToString()}";
|
||||||
if (!CanAttackManualTarget)
|
|
||||||
{
|
|
||||||
targetStatus += " (Unreachable)";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
targetStatus += "None";
|
targetStatus += "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
string result = turretStatus + "\n" + targetStatus;
|
string result = turretStatus + "\n" + targetStatus;
|
||||||
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:炮塔启用状态
|
// 炮塔启用状态
|
||||||
public bool TurretEnabled
|
public bool TurretEnabled
|
||||||
{
|
{
|
||||||
get { return turretEnabled; }
|
get { return turretEnabled; }
|
||||||
@@ -299,7 +208,7 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// 新增:检查炮塔是否启用
|
// 检查炮塔是否启用
|
||||||
if (!TurretEnabled)
|
if (!TurretEnabled)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -361,7 +270,7 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
base.CompPostMake();
|
base.CompPostMake();
|
||||||
this.MakeGun();
|
this.MakeGun();
|
||||||
// 新增:设置默认启用状态
|
// 设置默认启用状态
|
||||||
TurretEnabled = Props.defaultEnabled;
|
TurretEnabled = Props.defaultEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,6 +293,7 @@ namespace WulaFallenEmpire
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetCurrentTarget()
|
private void ResetCurrentTarget()
|
||||||
{
|
{
|
||||||
this.currentTarget = LocalTargetInfo.Invalid;
|
this.currentTarget = LocalTargetInfo.Invalid;
|
||||||
@@ -398,7 +308,7 @@ namespace WulaFallenEmpire
|
|||||||
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
|
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
|
||||||
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
|
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
|
||||||
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
|
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
|
||||||
// 新增:保存启用状态
|
// 保存启用状态
|
||||||
Scribe_Values.Look<bool>(ref this.turretEnabled, "turretEnabled", Props.defaultEnabled, false);
|
Scribe_Values.Look<bool>(ref this.turretEnabled, "turretEnabled", Props.defaultEnabled, false);
|
||||||
|
|
||||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||||
@@ -415,8 +325,6 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
private const int StartShootIntervalTicks = 10;
|
private const int StartShootIntervalTicks = 10;
|
||||||
|
|
||||||
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
|
||||||
|
|
||||||
public Thing gun;
|
public Thing gun;
|
||||||
protected int burstCooldownTicksLeft;
|
protected int burstCooldownTicksLeft;
|
||||||
protected int burstWarmupTicksLeft;
|
protected int burstWarmupTicksLeft;
|
||||||
@@ -426,7 +334,7 @@ namespace WulaFallenEmpire
|
|||||||
private int lastAttackTargetTick;
|
private int lastAttackTargetTick;
|
||||||
private float curRotation;
|
private float curRotation;
|
||||||
|
|
||||||
// 新增:炮塔启用状态字段
|
// 炮塔启用状态字段
|
||||||
private bool turretEnabled = true;
|
private bool turretEnabled = true;
|
||||||
|
|
||||||
[Unsaved(false)]
|
[Unsaved(false)]
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Verse;
|
|
||||||
using RimWorld;
|
|
||||||
|
|
||||||
namespace WulaFallenEmpire
|
|
||||||
{
|
|
||||||
public static class VolleyTargetManager
|
|
||||||
{
|
|
||||||
private static Dictionary<Pawn, LocalTargetInfo> volleyTargets = new Dictionary<Pawn, LocalTargetInfo>();
|
|
||||||
private static Dictionary<Pawn, bool> volleyEnabled = new Dictionary<Pawn, bool>();
|
|
||||||
|
|
||||||
public static void SetVolleyTarget(Pawn pawn, LocalTargetInfo target)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
volleyTargets[pawn] = target;
|
|
||||||
volleyEnabled[pawn] = target.IsValid;
|
|
||||||
|
|
||||||
Log.Message($"Set volley target for {pawn.Label}: {target.Thing?.Label ?? target.Cell.ToString()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ClearVolleyTarget(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
volleyTargets.Remove(pawn);
|
|
||||||
volleyEnabled.Remove(pawn);
|
|
||||||
|
|
||||||
Log.Message($"Cleared volley target for {pawn.Label}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LocalTargetInfo GetVolleyTarget(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null || !volleyTargets.ContainsKey(pawn))
|
|
||||||
return LocalTargetInfo.Invalid;
|
|
||||||
|
|
||||||
return volleyTargets[pawn];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsVolleyEnabled(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null || !volleyEnabled.ContainsKey(pawn))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return volleyEnabled[pawn];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ToggleVolley(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
bool current = IsVolleyEnabled(pawn);
|
|
||||||
volleyEnabled[pawn] = !current;
|
|
||||||
|
|
||||||
Log.Message($"Toggled volley for {pawn.Label}: {!current}");
|
|
||||||
|
|
||||||
// 如果禁用齐射,清除目标
|
|
||||||
if (!volleyEnabled[pawn])
|
|
||||||
{
|
|
||||||
ClearVolleyTarget(pawn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:检查齐射目标是否仍然有效
|
|
||||||
public static bool IsVolleyTargetValid(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (!IsVolleyEnabled(pawn))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LocalTargetInfo target = GetVolleyTarget(pawn);
|
|
||||||
if (!target.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 检查目标是否还活着/存在
|
|
||||||
if (target.Thing != null)
|
|
||||||
{
|
|
||||||
if (target.Thing.Destroyed)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (target.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
## 🤖 Assistant
|
|
||||||
|
|
||||||
我来帮您为这两个炮塔类添加齐射目标功能。我们需要创建一个共享的目标系统,让所有炮塔可以协同攻击同一个目标。
|
|
||||||
|
|
||||||
## 1. 首先创建共享目标管理器
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Verse;
|
|
||||||
using RimWorld;
|
|
||||||
|
|
||||||
namespace WulaFallenEmpire
|
|
||||||
{
|
|
||||||
public static class VolleyTargetManager
|
|
||||||
{
|
|
||||||
private static Dictionary<Pawn, LocalTargetInfo> volleyTargets = new Dictionary<Pawn, LocalTargetInfo>();
|
|
||||||
private static Dictionary<Pawn, bool> volleyEnabled = new Dictionary<Pawn, bool>();
|
|
||||||
|
|
||||||
public static void SetVolleyTarget(Pawn pawn, LocalTargetInfo target)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
volleyTargets[pawn] = target;
|
|
||||||
volleyEnabled[pawn] = target.IsValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ClearVolleyTarget(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
volleyTargets.Remove(pawn);
|
|
||||||
volleyEnabled.Remove(pawn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LocalTargetInfo GetVolleyTarget(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null || !volleyTargets.ContainsKey(pawn))
|
|
||||||
return LocalTargetInfo.Invalid;
|
|
||||||
|
|
||||||
return volleyTargets[pawn];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsVolleyEnabled(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null || !volleyEnabled.ContainsKey(pawn))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return volleyEnabled[pawn];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ToggleVolley(Pawn pawn)
|
|
||||||
{
|
|
||||||
if (pawn == null) return;
|
|
||||||
|
|
||||||
bool current = IsVolleyEnabled(pawn);
|
|
||||||
volleyEnabled[pawn] = !current;
|
|
||||||
|
|
||||||
// 如果禁用齐射,清除目标
|
|
||||||
if (!volleyEnabled[pawn])
|
|
||||||
{
|
|
||||||
ClearVolleyTarget(pawn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 修改 HediffComp_TopTurret 添加齐射功能
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
using Verse;
|
|
||||||
using Verse.AI;
|
|
||||||
using RimWorld;
|
|
||||||
|
|
||||||
namespace WulaFallenEmpire
|
|
||||||
{
|
|
||||||
[StaticConstructorOnStartup]
|
|
||||||
public class HediffComp_TopTurret : HediffComp, IAttackTargetSearcher
|
|
||||||
{
|
|
||||||
// ... 保留原有属性和字段 ...
|
|
||||||
|
|
||||||
// 新增:检查是否应该攻击齐射目标
|
|
||||||
private bool ShouldAttackVolleyTarget
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!VolleyTargetManager.IsVolleyEnabled(Pawn))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (!volleyTarget.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 检查目标是否在射程内
|
|
||||||
float distance = Pawn.Position.DistanceTo(volleyTarget.Cell);
|
|
||||||
if (distance > AttackVerb.verbProps.range)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 检查是否可以命中目标
|
|
||||||
return AttackVerb.CanHitTarget(volleyTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CompPostTick(ref float severityAdjustment)
|
|
||||||
{
|
|
||||||
base.CompPostTick(ref severityAdjustment);
|
|
||||||
|
|
||||||
if (!TurretEnabled)
|
|
||||||
{
|
|
||||||
ResetCurrentTarget();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.CanShoot)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:优先处理齐射目标
|
|
||||||
if (ShouldAttackVolleyTarget)
|
|
||||||
{
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
this.currentTarget = volleyTarget;
|
|
||||||
this.curRotation = (volleyTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
|
||||||
}
|
|
||||||
else if (this.currentTarget.IsValid)
|
|
||||||
{
|
|
||||||
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.AttackVerb.VerbTick();
|
|
||||||
if (this.AttackVerb.state != VerbState.Bursting)
|
|
||||||
{
|
|
||||||
if (this.WarmingUp)
|
|
||||||
{
|
|
||||||
this.burstWarmupTicksLeft--;
|
|
||||||
if (this.burstWarmupTicksLeft == 0)
|
|
||||||
{
|
|
||||||
this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
|
|
||||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
|
||||||
this.lastAttackedTarget = this.currentTarget;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (this.burstCooldownTicksLeft > 0)
|
|
||||||
{
|
|
||||||
this.burstCooldownTicksLeft--;
|
|
||||||
}
|
|
||||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
|
||||||
{
|
|
||||||
// 只有在没有齐射目标时才寻找新目标
|
|
||||||
if (!ShouldAttackVolleyTarget)
|
|
||||||
{
|
|
||||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
|
||||||
if (this.currentTarget.IsValid)
|
|
||||||
{
|
|
||||||
this.burstWarmupTicksLeft = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.ResetCurrentTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:齐射Gizmos
|
|
||||||
public override IEnumerable<Gizmo> CompGetGizmos()
|
|
||||||
{
|
|
||||||
// 只有 pawn 被选中且是玩家派系时才显示按钮
|
|
||||||
if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn))
|
|
||||||
{
|
|
||||||
// 原有开关按钮
|
|
||||||
yield return new Command_Toggle
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandToggleTurret".Translate(),
|
|
||||||
defaultDesc = "CommandToggleTurretDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/ToggleTurret"),
|
|
||||||
isActive = () => TurretEnabled,
|
|
||||||
toggleAction = () => TurretEnabled = !TurretEnabled,
|
|
||||||
hotKey = KeyBindingDefOf.Misc1
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增:齐射开关按钮
|
|
||||||
yield return new Command_Toggle
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandToggleVolley".Translate(),
|
|
||||||
defaultDesc = "CommandToggleVolleyDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/VolleyFire"),
|
|
||||||
isActive = () => VolleyTargetManager.IsVolleyEnabled(Pawn),
|
|
||||||
toggleAction = () => VolleyTargetManager.ToggleVolley(Pawn),
|
|
||||||
hotKey = KeyBindingDefOf.Misc2
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增:设置齐射目标按钮(只在齐射启用时显示)
|
|
||||||
if (VolleyTargetManager.IsVolleyEnabled(Pawn))
|
|
||||||
{
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandSetVolleyTarget".Translate(),
|
|
||||||
defaultDesc = "CommandSetVolleyTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/SetTarget"),
|
|
||||||
action = () => Find.Targeter.BeginTargeting(TargetingParameters.ForAttack(),
|
|
||||||
delegate(LocalTargetInfo target)
|
|
||||||
{
|
|
||||||
VolleyTargetManager.SetVolleyTarget(Pawn, target);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"SetVolleyTarget".Translate()),
|
|
||||||
hotKey = KeyBindingDefOf.Misc3
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新增:清除齐射目标按钮
|
|
||||||
LocalTargetInfo currentVolleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (currentVolleyTarget.IsValid)
|
|
||||||
{
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandClearVolleyTarget".Translate(),
|
|
||||||
defaultDesc = "CommandClearVolleyTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/ClearTarget"),
|
|
||||||
action = () => VolleyTargetManager.ClearVolleyTarget(Pawn),
|
|
||||||
hotKey = KeyBindingDefOf.Misc4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:在提示中显示齐射状态
|
|
||||||
public override string CompTipStringExtra
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
string baseString = base.CompTipStringExtra;
|
|
||||||
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
|
||||||
|
|
||||||
string volleyStatus = "Volley: ";
|
|
||||||
if (VolleyTargetManager.IsVolleyEnabled(Pawn))
|
|
||||||
{
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
|
||||||
if (volleyTarget.IsValid)
|
|
||||||
{
|
|
||||||
volleyStatus += $"Targeting {volleyTarget.Thing?.LabelCap ?? volleyTarget.Cell.ToString()}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
volleyStatus += "Enabled (No Target)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
volleyStatus += "Disabled";
|
|
||||||
}
|
|
||||||
|
|
||||||
string result = turretStatus + "\n" + volleyStatus;
|
|
||||||
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... 保留其他原有代码 ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 修改 Comp_MultiTurretGun 添加齐射功能
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Verse;
|
|
||||||
using Verse.AI;
|
|
||||||
using RimWorld;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace WulaFallenEmpire
|
|
||||||
{
|
|
||||||
public class Comp_MultiTurretGun : CompTurretGun
|
|
||||||
{
|
|
||||||
// ... 保留原有属性和字段 ...
|
|
||||||
|
|
||||||
// 新增:检查是否应该攻击齐射目标
|
|
||||||
private bool ShouldAttackVolleyTarget
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (parent is not Pawn pawn)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!VolleyTargetManager.IsVolleyEnabled(pawn))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn);
|
|
||||||
if (!volleyTarget.IsValid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 检查目标是否在射程内
|
|
||||||
float distance = parent.Position.DistanceTo(volleyTarget.Cell);
|
|
||||||
if (distance > AttackVerb.verbProps.range)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 检查是否可以命中目标
|
|
||||||
return AttackVerb.CanHitTarget(volleyTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CompTick()
|
|
||||||
{
|
|
||||||
base.CompTick();
|
|
||||||
|
|
||||||
// 新增:优先处理齐射目标
|
|
||||||
if (ShouldAttackVolleyTarget && parent is Pawn pawn)
|
|
||||||
{
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn);
|
|
||||||
currentTarget = volleyTarget;
|
|
||||||
curRotation = (volleyTarget.Cell.ToVector3Shifted() - parent.DrawPos).AngleFlat() + Props.angleOffset;
|
|
||||||
}
|
|
||||||
else if (!currentTarget.IsValid && burstCooldownTicksLeft <= 0)
|
|
||||||
{
|
|
||||||
// 在其他情况下没有目标且冷却结束时也回正
|
|
||||||
curRotation = parent.Rotation.AsAngle + Props.angleOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:重写目标选择逻辑以支持齐射
|
|
||||||
protected override void TryStartShootSomething(bool canBeginBurstImmediately)
|
|
||||||
{
|
|
||||||
// 优先检查齐射目标
|
|
||||||
if (ShouldAttackVolleyTarget && parent is Pawn pawn)
|
|
||||||
{
|
|
||||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn);
|
|
||||||
if (volleyTarget.IsValid)
|
|
||||||
{
|
|
||||||
currentTarget = volleyTarget;
|
|
||||||
if (canBeginBurstImmediately)
|
|
||||||
{
|
|
||||||
BeginBurst();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
burstWarmupTicksLeft = AttackVerb.verbProps.warmupTime.SecondsToTicks();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有齐射目标,使用原逻辑
|
|
||||||
base.TryStartShootSomething(canBeginBurstImmediately);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:Gizmos
|
|
||||||
public override IEnumerable<Gizmo> CompGetGizmos()
|
|
||||||
{
|
|
||||||
foreach (Gizmo gizmo in base.CompGetGizmos())
|
|
||||||
{
|
|
||||||
yield return gizmo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只有 parent 是 pawn 且是玩家派系时才显示齐射按钮
|
|
||||||
if (parent is Pawn pawn && pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(parent))
|
|
||||||
{
|
|
||||||
// 齐射开关按钮
|
|
||||||
yield return new Command_Toggle
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandToggleVolley".Translate(),
|
|
||||||
defaultDesc = "CommandToggleVolleyDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/VolleyFire"),
|
|
||||||
isActive = () => VolleyTargetManager.IsVolleyEnabled(pawn),
|
|
||||||
toggleAction = () => VolleyTargetManager.ToggleVolley(pawn),
|
|
||||||
hotKey = KeyBindingDefOf.Misc2
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置齐射目标按钮(只在齐射启用时显示)
|
|
||||||
if (VolleyTargetManager.IsVolleyEnabled(pawn))
|
|
||||||
{
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandSetVolleyTarget".Translate(),
|
|
||||||
defaultDesc = "CommandSetVolleyTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/SetTarget"),
|
|
||||||
action = () => Find.Targeter.BeginTargeting(TargetingParameters.ForAttack(),
|
|
||||||
delegate(LocalTargetInfo target)
|
|
||||||
{
|
|
||||||
VolleyTargetManager.SetVolleyTarget(pawn, target);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"SetVolleyTarget".Translate()),
|
|
||||||
hotKey = KeyBindingDefOf.Misc3
|
|
||||||
};
|
|
||||||
|
|
||||||
// 清除齐射目标按钮
|
|
||||||
LocalTargetInfo currentVolleyTarget = VolleyTargetManager.GetVolleyTarget(pawn);
|
|
||||||
if (currentVolleyTarget.IsValid)
|
|
||||||
{
|
|
||||||
yield return new Command_Action
|
|
||||||
{
|
|
||||||
defaultLabel = "CommandClearVolleyTarget".Translate(),
|
|
||||||
defaultDesc = "CommandClearVolleyTargetDesc".Translate(),
|
|
||||||
icon = ContentFinder<Texture2D>.Get("UI/Gizmos/ClearTarget"),
|
|
||||||
action = () => VolleyTargetManager.ClearVolleyTarget(pawn),
|
|
||||||
hotKey = KeyBindingDefOf.Misc4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... 保留其他原有代码 ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 添加翻译字符串
|
|
||||||
|
|
||||||
在您的语言文件中添加:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!-- 在 Keyed 文件夹的 XML 文件中 -->
|
|
||||||
<CommandToggleVolley>Volley Fire</CommandToggleVolley>
|
|
||||||
<CommandToggleVolleyDesc>Enable or disable coordinated volley fire for all turrets.</CommandToggleVolleyDesc>
|
|
||||||
<CommandSetVolleyTarget>Set Volley Target</CommandSetVolleyTarget>
|
|
||||||
<CommandSetVolleyTargetDesc>Set a coordinated target for all turrets to focus fire.</CommandSetVolleyTargetDesc>
|
|
||||||
<CommandClearVolleyTarget>Clear Volley Target</CommandClearVolleyTarget>
|
|
||||||
<CommandClearVolleyTargetDesc>Clear the coordinated volley target.</CommandClearVolleyTargetDesc>
|
|
||||||
<SetVolleyTarget>Set volley target</SetVolleyTarget>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 需要准备的图标
|
|
||||||
|
|
||||||
您需要在 `Textures/UI/Gizmos/` 文件夹中准备以下图标:
|
|
||||||
- `VolleyFire.png` - 齐射开关图标
|
|
||||||
- `SetTarget.png` - 设置目标图标
|
|
||||||
- `ClearTarget.png` - 清除目标图标
|
|
||||||
|
|
||||||
## 功能说明
|
|
||||||
|
|
||||||
### 工作流程:
|
|
||||||
1. **启用齐射模式**:点击齐射开关按钮启用协同攻击
|
|
||||||
2. **设置目标**:在齐射模式下点击设置目标按钮,选择要攻击的目标
|
|
||||||
3. **自动协同**:所有炮塔会优先攻击齐射目标,如果目标在射程内且可攻击
|
|
||||||
4. **智能回退**:如果齐射目标无效或不在射程内,炮塔会恢复原来自动攻击逻辑
|
|
||||||
5. **独立控制**:每个 pawn 有独立的齐射目标和开关状态
|
|
||||||
|
|
||||||
### 优先级系统:
|
|
||||||
1. 齐射目标(如果启用且有效)
|
|
||||||
2. 原有自动攻击目标
|
|
||||||
3. 空闲状态
|
|
||||||
|
|
||||||
### 优势:
|
|
||||||
- **协同攻击**:所有炮塔集中火力攻击同一目标
|
|
||||||
- **灵活控制**:可以随时启用/禁用齐射模式
|
|
||||||
- **兼容性**:不影响原有的单个炮塔开关功能
|
|
||||||
- **智能判断**:自动检查射程和攻击可行性
|
|
||||||
|
|
||||||
这样您就为两个炮塔类添加了完整的齐射目标功能,让多个炮塔可以协同攻击同一个目标!
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public static class AreaShieldManager
|
||||||
|
{
|
||||||
|
private static Dictionary<Map, HashSet<ThingComp_AreaShield>> activeShieldsByMap =
|
||||||
|
new Dictionary<Map, HashSet<ThingComp_AreaShield>>();
|
||||||
|
|
||||||
|
private static int lastUpdateTick = 0;
|
||||||
|
private const int UPDATE_INTERVAL_TICKS = 60;
|
||||||
|
|
||||||
|
public static IEnumerable<ThingComp_AreaShield> GetActiveShieldsForMap(Map map)
|
||||||
|
{
|
||||||
|
if (Find.TickManager.TicksGame - lastUpdateTick > UPDATE_INTERVAL_TICKS)
|
||||||
|
{
|
||||||
|
UpdateShieldCache();
|
||||||
|
lastUpdateTick = Find.TickManager.TicksGame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeShieldsByMap.TryGetValue(map, out var shields))
|
||||||
|
{
|
||||||
|
foreach (var shield in shields)
|
||||||
|
{
|
||||||
|
if (shield?.Active == true)
|
||||||
|
yield return shield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateShieldCache()
|
||||||
|
{
|
||||||
|
activeShieldsByMap.Clear();
|
||||||
|
|
||||||
|
foreach (var map in Find.Maps)
|
||||||
|
{
|
||||||
|
var shieldSet = new HashSet<ThingComp_AreaShield>();
|
||||||
|
|
||||||
|
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
|
||||||
|
{
|
||||||
|
if (pawn.apparel != null)
|
||||||
|
{
|
||||||
|
foreach (var apparel in pawn.apparel.WornApparel)
|
||||||
|
{
|
||||||
|
// 同时支持普通护盾和反弹护盾
|
||||||
|
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
|
||||||
|
if (shield != null && shield.Active)
|
||||||
|
{
|
||||||
|
shieldSet.Add(shield);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeShieldsByMap[map] = shieldSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NotifyShieldStateChanged(ThingComp_AreaShield shield)
|
||||||
|
{
|
||||||
|
if (shield?.Wearer?.Map != null)
|
||||||
|
{
|
||||||
|
lastUpdateTick = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Cleanup()
|
||||||
|
{
|
||||||
|
var mapsToRemove = new List<Map>();
|
||||||
|
foreach (var map in activeShieldsByMap.Keys)
|
||||||
|
{
|
||||||
|
if (map == null || !Find.Maps.Contains(map))
|
||||||
|
mapsToRemove.Add(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var map in mapsToRemove)
|
||||||
|
{
|
||||||
|
activeShieldsByMap.Remove(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class CompProperties_AreaShield : CompProperties
|
||||||
|
{
|
||||||
|
public float radius = 5.9f;
|
||||||
|
public int baseHitPoints = 100;
|
||||||
|
public int rechargeDelay = 3200;
|
||||||
|
public int rechargeHitPointsIntervalTicks = 60;
|
||||||
|
|
||||||
|
public EffecterDef absorbEffecter;
|
||||||
|
public EffecterDef interceptEffecter;
|
||||||
|
public EffecterDef breakEffecter;
|
||||||
|
public EffecterDef reactivateEffecter;
|
||||||
|
|
||||||
|
public Color color = Color.cyan;
|
||||||
|
public int startupDelay = 0;
|
||||||
|
|
||||||
|
// 拦截设置
|
||||||
|
public bool interceptGroundProjectiles = true;
|
||||||
|
public bool interceptNonHostileProjectiles = false;
|
||||||
|
public bool interceptAirProjectiles = true;
|
||||||
|
|
||||||
|
// 反射设置
|
||||||
|
public bool canReflect = false;
|
||||||
|
public float reflectChance = 0.5f;
|
||||||
|
public float reflectAngleRange = 30f;
|
||||||
|
public int reflectCost = 1;
|
||||||
|
public EffecterDef reflectEffecter;
|
||||||
|
|
||||||
|
public CompProperties_AreaShield()
|
||||||
|
{
|
||||||
|
compClass = typeof(ThingComp_AreaShield);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
// Gizmo 类保持不变...
|
||||||
|
[StaticConstructorOnStartup]
|
||||||
|
public class Gizmo_AreaShieldStatus : Gizmo
|
||||||
|
{
|
||||||
|
public ThingComp_AreaShield shield;
|
||||||
|
private static readonly Texture2D FullShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.8f, 0.85f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
|
||||||
|
private static readonly Texture2D EmptyShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.2f, 0.24f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
|
||||||
|
|
||||||
|
public override float GetWidth(float maxWidth) => 140f;
|
||||||
|
|
||||||
|
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||||
|
{
|
||||||
|
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
|
||||||
|
Rect rect2 = rect.ContractedBy(6f);
|
||||||
|
Widgets.DrawWindowBackground(rect);
|
||||||
|
|
||||||
|
Rect labelRect = rect2;
|
||||||
|
labelRect.height = rect.height / 2f;
|
||||||
|
Text.Font = GameFont.Tiny;
|
||||||
|
Widgets.Label(labelRect, shield.parent.LabelCap);
|
||||||
|
|
||||||
|
Rect barRect = rect2;
|
||||||
|
barRect.yMin = rect2.y + rect2.height / 2f;
|
||||||
|
float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax;
|
||||||
|
Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false);
|
||||||
|
|
||||||
|
Text.Font = GameFont.Small;
|
||||||
|
Text.Anchor = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
TaggedString statusText = shield.IsOnCooldown ? "ShieldOnCooldown".Translate() : new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax);
|
||||||
|
Widgets.Label(barRect, statusText);
|
||||||
|
|
||||||
|
Text.Anchor = TextAnchor.UpperLeft;
|
||||||
|
|
||||||
|
return new GizmoResult(GizmoState.Clear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
|
||||||
|
public static class Projectile_CheckForFreeInterceptBetween_Patch
|
||||||
|
{
|
||||||
|
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
|
||||||
|
{
|
||||||
|
if (__instance.Map == null || __instance.Destroyed)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldDestroy = false;
|
||||||
|
|
||||||
|
// 使用缓存系统获取激活的护盾
|
||||||
|
foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map))
|
||||||
|
{
|
||||||
|
if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true)
|
||||||
|
{
|
||||||
|
shouldDestroy = true;
|
||||||
|
break; // 只要有一个护盾吸收就销毁
|
||||||
|
}
|
||||||
|
// 如果护盾反射了抛射体,继续检查其他护盾(允许多重反射)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDestroy)
|
||||||
|
{
|
||||||
|
__instance.Destroy(DestroyMode.Vanish);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 额外的清理补丁
|
||||||
|
[HarmonyPatch(typeof(Map), "FinalizeInit")]
|
||||||
|
public static class Map_FinalizeInit_Patch
|
||||||
|
{
|
||||||
|
public static void Postfix()
|
||||||
|
{
|
||||||
|
AreaShieldManager.Cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse.Sound;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
[StaticConstructorOnStartup]
|
||||||
|
public class ThingComp_AreaShield : ThingComp
|
||||||
|
{
|
||||||
|
private int lastInterceptTicks = -999999;
|
||||||
|
public int ticksToReset = 0;
|
||||||
|
public int currentHitPoints;
|
||||||
|
private bool wasNotAtFullHp = false;
|
||||||
|
private bool wasActiveLastCheck = false;
|
||||||
|
|
||||||
|
// 视觉效果变量
|
||||||
|
private float lastInterceptAngle;
|
||||||
|
private bool drawInterceptCone;
|
||||||
|
|
||||||
|
public CompProperties_AreaShield Props => (CompProperties_AreaShield)props;
|
||||||
|
public Pawn Wearer => (parent as Apparel)?.Wearer;
|
||||||
|
public bool IsOnCooldown => ticksToReset > 0;
|
||||||
|
public int HitPointsMax => Props.baseHitPoints;
|
||||||
|
|
||||||
|
private StunHandler stunner;
|
||||||
|
private bool initialized = false;
|
||||||
|
|
||||||
|
public bool Active
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 材质定义
|
||||||
|
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
|
||||||
|
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
|
||||||
|
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
|
||||||
|
|
||||||
|
public override void PostPostMake()
|
||||||
|
{
|
||||||
|
base.PostPostMake();
|
||||||
|
currentHitPoints = HitPointsMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostExposeData()
|
||||||
|
{
|
||||||
|
base.PostExposeData();
|
||||||
|
Scribe_Values.Look(ref lastInterceptTicks, "lastInterceptTicks", -999999);
|
||||||
|
Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0);
|
||||||
|
Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CompTick()
|
||||||
|
{
|
||||||
|
base.CompTick();
|
||||||
|
|
||||||
|
bool isActive = Active;
|
||||||
|
|
||||||
|
// 检查状态变化并通知管理器
|
||||||
|
if (isActive != wasActiveLastCheck)
|
||||||
|
{
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
wasActiveLastCheck = isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Wearer == null) return;
|
||||||
|
|
||||||
|
if (IsOnCooldown)
|
||||||
|
{
|
||||||
|
ticksToReset--;
|
||||||
|
if (ticksToReset <= 0)
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isActive && currentHitPoints < HitPointsMax)
|
||||||
|
{
|
||||||
|
wasNotAtFullHp = true;
|
||||||
|
if (parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks))
|
||||||
|
{
|
||||||
|
currentHitPoints += 1;
|
||||||
|
if (currentHitPoints > HitPointsMax)
|
||||||
|
currentHitPoints = HitPointsMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (wasNotAtFullHp && currentHitPoints >= HitPointsMax)
|
||||||
|
{
|
||||||
|
wasNotAtFullHp = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCosts(int cost = 1)
|
||||||
|
{
|
||||||
|
currentHitPoints -= cost;
|
||||||
|
if (currentHitPoints <= 0)
|
||||||
|
{
|
||||||
|
Break();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 护盾值变化时通知管理器
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos)
|
||||||
|
{
|
||||||
|
if (!Active) return false;
|
||||||
|
if (currentHitPoints <= 0) return false;
|
||||||
|
|
||||||
|
if (!GenGeo.IntersectLineCircleOutline(Wearer.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectile.def.projectile.flyOverhead && !Props.interceptAirProjectiles) return false;
|
||||||
|
if (!projectile.def.projectile.flyOverhead && !Props.interceptGroundProjectiles) return false;
|
||||||
|
if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Wearer.Faction) && !Props.interceptNonHostileProjectiles) return false;
|
||||||
|
|
||||||
|
lastInterceptTicks = Find.TickManager.TicksGame;
|
||||||
|
|
||||||
|
// 记录拦截角度用于视觉效果
|
||||||
|
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(Wearer.TrueCenter());
|
||||||
|
drawInterceptCone = true;
|
||||||
|
|
||||||
|
// 尝试反射
|
||||||
|
if (Props.canReflect && TryReflectProjectile(projectile, lastExactPos, newExactPos))
|
||||||
|
{
|
||||||
|
// 反射成功,播放反射特效
|
||||||
|
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
|
||||||
|
ApplyCosts(Props.reflectCost);
|
||||||
|
return false; // 不销毁原抛射体,让它继续飞行(我们会在反射中销毁它)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 普通拦截,播放拦截特效
|
||||||
|
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
|
||||||
|
ApplyCosts();
|
||||||
|
return true; // 销毁抛射体
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试反射抛射体 - 现在会创建新的抛射体
|
||||||
|
/// </summary>
|
||||||
|
private bool TryReflectProjectile(Projectile originalProjectile, Vector3 lastExactPos, Vector3 newExactPos)
|
||||||
|
{
|
||||||
|
if (!Props.canReflect) return false;
|
||||||
|
|
||||||
|
// 检查反射概率
|
||||||
|
if (Rand.Value > Props.reflectChance) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 计算入射方向
|
||||||
|
Vector3 incomingDirection = (newExactPos - lastExactPos).normalized;
|
||||||
|
|
||||||
|
// 计算法线方向(从护盾中心到碰撞点)
|
||||||
|
Vector3 normal = (newExactPos - Wearer.DrawPos).normalized;
|
||||||
|
|
||||||
|
// 计算反射方向(镜面反射)
|
||||||
|
Vector3 reflectDirection = Vector3.Reflect(incomingDirection, normal);
|
||||||
|
|
||||||
|
// 添加随机角度偏移
|
||||||
|
float randomAngle = Rand.Range(-Props.reflectAngleRange, Props.reflectAngleRange);
|
||||||
|
reflectDirection = Quaternion.Euler(0, randomAngle, 0) * reflectDirection;
|
||||||
|
|
||||||
|
// 创建新的反射抛射体
|
||||||
|
CreateReflectedProjectile(originalProjectile, reflectDirection, newExactPos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"Error reflecting projectile: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建反射后的新抛射体
|
||||||
|
/// </summary>
|
||||||
|
private void CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 计算新的发射位置(护盾位置附近)
|
||||||
|
Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint);
|
||||||
|
|
||||||
|
// 计算新的目标位置
|
||||||
|
Vector3 targetPosition = spawnPosition + reflectDirection * 30f; // 足够远的距离
|
||||||
|
|
||||||
|
// 创建新的抛射体
|
||||||
|
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnPosition.ToIntVec3(), Wearer.Map);
|
||||||
|
|
||||||
|
// 设置发射者为原抛射体的发射者
|
||||||
|
Thing launcher = originalProjectile.Launcher ?? Wearer;
|
||||||
|
|
||||||
|
// 发射新抛射体
|
||||||
|
newProjectile.Launch(
|
||||||
|
launcher,
|
||||||
|
spawnPosition,
|
||||||
|
new LocalTargetInfo(targetPosition.ToIntVec3()),
|
||||||
|
new LocalTargetInfo(targetPosition.ToIntVec3()),
|
||||||
|
ProjectileHitFlags.All,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// 复制重要的属性
|
||||||
|
CopyProjectileProperties(originalProjectile, newProjectile);
|
||||||
|
|
||||||
|
// 销毁原抛射体
|
||||||
|
originalProjectile.Destroy(DestroyMode.Vanish);
|
||||||
|
|
||||||
|
Log.Message($"反射抛射体: 从 {spawnPosition} 向 {targetPosition} 发射");
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"Error creating reflected projectile: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取反射抛射体的发射位置(护盾边界上)
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint)
|
||||||
|
{
|
||||||
|
// 计算从护盾中心到碰撞点的方向
|
||||||
|
Vector3 directionFromCenter = (collisionPoint - Wearer.DrawPos).normalized;
|
||||||
|
|
||||||
|
// 在护盾边界上生成(稍微向内一点避免立即再次碰撞)
|
||||||
|
float spawnDistance = Props.radius * 0.9f;
|
||||||
|
Vector3 spawnPosition = Wearer.DrawPos + directionFromCenter * spawnDistance;
|
||||||
|
|
||||||
|
// 确保位置在地图内
|
||||||
|
IntVec3 spawnCell = spawnPosition.ToIntVec3();
|
||||||
|
if (!spawnCell.InBounds(Wearer.Map))
|
||||||
|
{
|
||||||
|
spawnCell = Wearer.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spawnCell.ToVector3Shifted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 复制抛射体重要属性
|
||||||
|
/// </summary>
|
||||||
|
private void CopyProjectileProperties(Projectile source, Projectile destination)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sourceTraverse = Traverse.Create(source);
|
||||||
|
var destTraverse = Traverse.Create(destination);
|
||||||
|
|
||||||
|
// 复制伤害属性
|
||||||
|
destTraverse.Field("damageDefOverride").SetValue(source.damageDefOverride);
|
||||||
|
|
||||||
|
// 复制额外伤害
|
||||||
|
if (source.extraDamages != null)
|
||||||
|
{
|
||||||
|
destTraverse.Field("extraDamages").SetValue(new List<ExtraDamage>(source.extraDamages));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制停止力
|
||||||
|
destTraverse.Field("stoppingPower").SetValue(source.stoppingPower);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"Error copying projectile properties: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
|
||||||
|
{
|
||||||
|
absorbed = false;
|
||||||
|
if (!Active || Wearer == null) return;
|
||||||
|
|
||||||
|
if (dinfo.Def.isRanged) return;
|
||||||
|
|
||||||
|
if (dinfo.Instigator != null)
|
||||||
|
{
|
||||||
|
float distance = Wearer.Position.DistanceTo(dinfo.Instigator.Position);
|
||||||
|
if (distance > Props.radius) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHitPoints <= 0) return;
|
||||||
|
|
||||||
|
Props.absorbEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
|
||||||
|
ApplyCosts();
|
||||||
|
absorbed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Break()
|
||||||
|
{
|
||||||
|
Props.breakEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
|
||||||
|
ticksToReset = Props.rechargeDelay;
|
||||||
|
currentHitPoints = 0;
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
if (Wearer != null && Wearer.Spawned)
|
||||||
|
{
|
||||||
|
Props.reactivateEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
|
||||||
|
}
|
||||||
|
currentHitPoints = HitPointsMax;
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 护盾绘制方法
|
||||||
|
public override void CompDrawWornExtras()
|
||||||
|
{
|
||||||
|
base.CompDrawWornExtras();
|
||||||
|
|
||||||
|
if (!Active || Wearer?.Map == null || !ShouldDisplay)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector3 drawPos = Wearer.Drawer?.DrawPos ?? Wearer.Position.ToVector3Shifted();
|
||||||
|
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
|
||||||
|
|
||||||
|
float alpha = GetCurrentAlpha();
|
||||||
|
if (alpha > 0f)
|
||||||
|
{
|
||||||
|
Color color = Props.color;
|
||||||
|
color.a *= alpha;
|
||||||
|
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
|
||||||
|
Matrix4x4 matrix = default;
|
||||||
|
|
||||||
|
float scale = Props.radius * 2f * 1.1601562f;
|
||||||
|
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(scale, 1f, scale));
|
||||||
|
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加拦截锥形效果
|
||||||
|
float coneAlpha = GetCurrentConeAlpha();
|
||||||
|
if (coneAlpha > 0f)
|
||||||
|
{
|
||||||
|
Color color = Props.color;
|
||||||
|
color.a *= coneAlpha;
|
||||||
|
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
|
||||||
|
Matrix4x4 matrix = default;
|
||||||
|
float scale = Props.radius * 2f;
|
||||||
|
matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(scale, 1f, scale));
|
||||||
|
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示条件
|
||||||
|
protected bool ShouldDisplay
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || !Active)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Wearer.Drafted || Wearer.InAggroMentalState ||
|
||||||
|
(Wearer.Faction != null && Wearer.Faction.HostileTo(Faction.OfPlayer) && !Wearer.IsPrisoner))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (Find.Selector.IsSelected(Wearer))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetCurrentAlpha()
|
||||||
|
{
|
||||||
|
float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
|
||||||
|
float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
|
||||||
|
return Mathf.Max(idleAlpha, interceptAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetCurrentConeAlpha()
|
||||||
|
{
|
||||||
|
if (!drawInterceptCone) return 0f;
|
||||||
|
return Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f) * 0.82f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (initialized) return;
|
||||||
|
|
||||||
|
if (stunner == null)
|
||||||
|
stunner = new StunHandler(parent);
|
||||||
|
|
||||||
|
if (currentHitPoints == -1)
|
||||||
|
currentHitPoints = Props.startupDelay > 0 ? 0 : HitPointsMax;
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Gizmo> CompGetWornGizmosExtra()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
if (Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
|
||||||
|
{
|
||||||
|
yield return new Gizmo_AreaShieldStatus { shield = this };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Notify_Equipped(Pawn pawn)
|
||||||
|
{
|
||||||
|
base.Notify_Equipped(pawn);
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Notify_Unequipped(Pawn pawn)
|
||||||
|
{
|
||||||
|
base.Notify_Unequipped(pawn);
|
||||||
|
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,7 +166,6 @@
|
|||||||
<Compile Include="HediffComp\HediffCompProperties_DisappearWithEffect.cs" />
|
<Compile Include="HediffComp\HediffCompProperties_DisappearWithEffect.cs" />
|
||||||
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
|
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
|
||||||
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
|
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
|
||||||
<Compile Include="HediffComp\WULA_HediffComp_TopTurret\VolleyTargetManager.cs" />
|
|
||||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\DRMDamageShield.cs" />
|
<Compile Include="HediffComp\WULA_HediffDamgeShield\DRMDamageShield.cs" />
|
||||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
|
<Compile Include="HediffComp\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
|
||||||
<Compile Include="Pawn\Comp_MultiTurretGun.cs" />
|
<Compile Include="Pawn\Comp_MultiTurretGun.cs" />
|
||||||
@@ -225,10 +224,15 @@
|
|||||||
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
|
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
|
||||||
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.cs" />
|
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.cs" />
|
||||||
<Compile Include="ThingComp\Comp_WeaponRenderDynamic.cs" />
|
<Compile Include="ThingComp\Comp_WeaponRenderDynamic.cs" />
|
||||||
|
<Compile Include="ThingComp\WULA_AreaShield\AreaShieldManager.cs" />
|
||||||
|
<Compile Include="ThingComp\WULA_AreaShield\CompProperties_AreaShield.cs" />
|
||||||
|
<Compile Include="ThingComp\WULA_AreaShield\Gizmo_AreaShieldStatus.cs" />
|
||||||
|
<Compile Include="ThingComp\WULA_AreaShield\Harmony_AreaShieldInterceptor.cs" />
|
||||||
|
<Compile Include="ThingComp\WULA_AreaShield\ThingComp_AreaShield.cs" />
|
||||||
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompCustomUniqueWeapon.cs" />
|
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompCustomUniqueWeapon.cs" />
|
||||||
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompProperties_CustomUniqueWeapon.cs" />
|
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompProperties_CustomUniqueWeapon.cs" />
|
||||||
<Compile Include="ThingComp\Wula_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
|
<Compile Include="ThingComp\WULA_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
|
||||||
<Compile Include="ThingComp\Wula_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
|
<Compile Include="ThingComp\WULA_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
|
||||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
|
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
|
||||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
|
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
|
||||||
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />
|
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user