2
This commit is contained in:
Binary file not shown.
@@ -60,8 +60,8 @@
|
||||
<cooldownTicksRange>600</cooldownTicksRange>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<range>24</range>
|
||||
<warmupTime>0</warmupTime>
|
||||
<range>18</range>
|
||||
<warmupTime>1</warmupTime>
|
||||
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
||||
|
||||
<ai_IsWeapon>true</ai_IsWeapon>
|
||||
@@ -85,36 +85,16 @@
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
|
||||
<range>12</range>
|
||||
<lineWidthEnd>5</lineWidthEnd>
|
||||
<range>18</range>
|
||||
<lineWidthEnd>12</lineWidthEnd>
|
||||
<canHitFilledCells>true</canHitFilledCells>
|
||||
<affectAllies>false</affectAllies>
|
||||
<affectCaster>false</affectCaster>
|
||||
<effecterDef>Fire_Spew</effecterDef>
|
||||
<hitEffecter>Fire_SpewShort</hitEffecter>
|
||||
<castEffecter>Fire_SpewShort</castEffecter>
|
||||
<castEffecterMaintainTicks>60</castEffecterMaintainTicks>
|
||||
<hitEffecter>Fire_Spew</hitEffecter>
|
||||
<hitEffecterMaintainTicks>30</hitEffecterMaintainTicks>
|
||||
</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>
|
||||
</AbilityDef>
|
||||
</Defs>
|
||||
@@ -502,33 +502,6 @@
|
||||
<wornGraphicPath>Wula/Apparel/WULA_Knight_PowerArmor</wornGraphicPath>
|
||||
</apparel>
|
||||
<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>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="WULA_ApparelHelmetBase">
|
||||
@@ -624,30 +597,31 @@
|
||||
<wornGraphicPath>Wula/Apparel/WULA_Heavy_Infantry_PowerArmor</wornGraphicPath>
|
||||
</apparel>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
|
||||
<!-- 基础功能 -->
|
||||
<radius>1.5</radius> <!-- 护盾半径,决定了拦截范围 -->
|
||||
<hitPoints>200</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
|
||||
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
|
||||
|
||||
<!-- 拦截类型 -->
|
||||
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
|
||||
<interceptAirProjectiles>false</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
|
||||
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
|
||||
|
||||
<!-- 视觉与音效 -->
|
||||
<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伤害 -->
|
||||
<disarmedByEmpForTicks>600</disarmedByEmpForTicks> <!-- 被EMP击中后,额外的眩晕/瘫痪时间 (ticks) -->
|
||||
|
||||
<!-- 被动恢复 -->
|
||||
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks> <!-- 未破盾时,每隔多少ticks恢复1点生命值 -->
|
||||
|
||||
<li Class="WulaFallenEmpire.CompProperties_AreaShield">
|
||||
<radius>3</radius>
|
||||
<baseHitPoints>50</baseHitPoints>
|
||||
<rechargeDelay>2400</rechargeDelay>
|
||||
<rechargeHitPointsIntervalTicks>30</rechargeHitPointsIntervalTicks>
|
||||
|
||||
<!-- 效果器配置 -->
|
||||
<absorbEffecter>Interceptor_BlockedProjectile</absorbEffecter>
|
||||
<interceptEffecter>Interceptor_BlockedProjectile</interceptEffecter>
|
||||
<breakEffecter>Shield_Break</breakEffecter>
|
||||
<reactivateEffecter>BulletShieldGenerator_Reactivate</reactivateEffecter>
|
||||
|
||||
<color>(0.9, 0.2, 0.2, 0.2)</color> <!-- 护盾气泡的颜色 (RGBA) -->
|
||||
|
||||
<!-- 拦截设置 -->
|
||||
<interceptGroundProjectiles>true</interceptGroundProjectiles>
|
||||
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles>
|
||||
<interceptAirProjectiles>true</interceptAirProjectiles>
|
||||
|
||||
<!-- 反射设置 -->
|
||||
<canReflect>true</canReflect>
|
||||
<reflectChance>1</reflectChance>
|
||||
<reflectAngleRange>30</reflectAngleRange>
|
||||
<reflectCost>1</reflectCost>
|
||||
<reflectEffecter>Interceptor_BlockedProjectile</reflectEffecter>
|
||||
</li>
|
||||
<li Class="CompProperties_CauseHediff_Apparel">
|
||||
<hediff>WULA_Heavy_Infantry_PowerArmor_Mortar_Hediff</hediff>
|
||||
|
||||
@@ -344,4 +344,17 @@
|
||||
<WULA_TaxCollection.Status>什一税征收:{0} {1} {2}</WULA_TaxCollection.Status>
|
||||
<WULA_TaxCollection.Status.Succeeded>- 征收已完成</WULA_TaxCollection.Status.Succeeded>
|
||||
<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>
|
||||
@@ -21,6 +21,9 @@ namespace WulaFallenEmpire
|
||||
Map map = parent.pawn.MapHeld;
|
||||
if (map == null) return;
|
||||
|
||||
// 播放发射特效(在施法者位置)
|
||||
PlayCastEffecter(map);
|
||||
|
||||
// 获取扇形区域内的所有单元格
|
||||
List<IntVec3> affectedCells = AffectedCells(target);
|
||||
|
||||
@@ -47,29 +50,41 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
affectedTargets.Add(thing);
|
||||
}
|
||||
|
||||
// 根据事物类型进行处理
|
||||
if (thing is Building building)
|
||||
{
|
||||
DestroyBuilding(building);
|
||||
}
|
||||
else if (thing is Pawn targetPawn)
|
||||
{
|
||||
DestroyAllBodyParts(targetPawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个受影响的目标播放命中效果器
|
||||
// 为每个受影响的目标播放命中效果器并处理效果
|
||||
foreach (Thing affectedThing in affectedTargets)
|
||||
{
|
||||
PlayHitEffecter(affectedThing, map);
|
||||
ProcessTarget(affectedThing);
|
||||
}
|
||||
|
||||
// 播放主要效果
|
||||
if (Props.effecterDef != null)
|
||||
}
|
||||
|
||||
private void PlayCastEffecter(Map map)
|
||||
{
|
||||
try
|
||||
{
|
||||
Props.effecterDef.Spawn(target.Cell, map).Cleanup();
|
||||
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 (target == null || target.Destroyed) return;
|
||||
|
||||
// 创建效果器
|
||||
Effecter effecter = Props.hitEffecter.Spawn();
|
||||
|
||||
// 计算效果器方向(从目标指向施法者)
|
||||
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)
|
||||
// 使用与 CompAbilityEffect_EffecterOnTarget 相同的方法
|
||||
Effecter effecter;
|
||||
if (target is Pawn pawnTarget)
|
||||
{
|
||||
map.effecterMaintainer.AddEffecterToMaintain(effecter, target, Props.hitEffecter.maintainTicks);
|
||||
effecter = Props.hitEffecter.Spawn(pawnTarget, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则在适当时间后清理
|
||||
LongEventHandler.ExecuteWhenFinished(delegate
|
||||
{
|
||||
effecter.Cleanup();
|
||||
});
|
||||
effecter = Props.hitEffecter.Spawn(target.Position, map);
|
||||
}
|
||||
|
||||
if (Props.hitEffecterMaintainTicks > 0)
|
||||
{
|
||||
// 使用与参考代码相同的方法来维持效果器
|
||||
parent.AddEffecterToMaintain(effecter, target.Position, Props.hitEffecterMaintainTicks);
|
||||
}
|
||||
else
|
||||
{
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 检查是否影响施法者自己
|
||||
@@ -232,18 +256,8 @@ namespace WulaFallenEmpire
|
||||
|
||||
public override IEnumerable<PreCastAction> GetPreCastActions()
|
||||
{
|
||||
if (Props.effecterDef != null)
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
// 这里不再预先创建效果器,改为在Apply中创建
|
||||
yield break;
|
||||
}
|
||||
|
||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||
|
||||
@@ -7,16 +7,17 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
public float range;
|
||||
public float lineWidthEnd;
|
||||
public EffecterDef effecterDef;
|
||||
public bool canHitFilledCells;
|
||||
|
||||
// 新增:命中效果器
|
||||
// 发射特效(在施法者位置)
|
||||
public EffecterDef castEffecter;
|
||||
public int castEffecterMaintainTicks = 60;
|
||||
|
||||
// 命中特效(在目标位置)
|
||||
public EffecterDef hitEffecter;
|
||||
public int hitEffecterMaintainTicks = 30;
|
||||
|
||||
// 新增:是否影响友方单位
|
||||
public bool canHitFilledCells;
|
||||
public bool affectAllies = false;
|
||||
|
||||
// 新增:是否影响施法者自己
|
||||
public bool affectCaster = false;
|
||||
|
||||
public CompProperties_AbilityAreaDestruction()
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace WulaFallenEmpire
|
||||
public ThingDef turretDef;
|
||||
public float angleOffset;
|
||||
public bool autoAttack = true;
|
||||
public bool defaultEnabled = true; // 新增:默认启用状态
|
||||
public bool defaultEnabled = true;
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
@@ -87,31 +87,28 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
if (!TurretEnabled)
|
||||
{
|
||||
ResetCurrentTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.CanShoot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 优先处理手动目标
|
||||
if (HasManualTarget && CanAttackManualTarget)
|
||||
{
|
||||
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)
|
||||
|
||||
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)
|
||||
@@ -125,14 +122,6 @@ namespace WulaFallenEmpire
|
||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
this.lastAttackedTarget = this.currentTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果手动攻击失败且目标无效,清除手动目标
|
||||
if (HasManualTarget && !CanAttackManualTarget)
|
||||
{
|
||||
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -142,68 +131,30 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
this.burstCooldownTicksLeft--;
|
||||
}
|
||||
|
||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||
{
|
||||
// 如果手动目标无效,清除它
|
||||
if (HasManualTarget && !CanAttackManualTarget)
|
||||
// 自动寻找目标
|
||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
||||
}
|
||||
// 只有在没有有效的手动目标时才寻找新目标
|
||||
if (!HasManualTarget || !CanAttackManualTarget)
|
||||
{
|
||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.burstWarmupTicksLeft = 1;
|
||||
return;
|
||||
}
|
||||
this.burstWarmupTicksLeft = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
this.ResetCurrentTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查是否有手动目标
|
||||
private bool HasManualTarget
|
||||
{
|
||||
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 - 只有设置目标和清除目标按钮
|
||||
|
||||
// 简化的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(),
|
||||
@@ -213,75 +164,33 @@ namespace WulaFallenEmpire
|
||||
toggleAction = () => TurretEnabled = !TurretEnabled,
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
string baseString = base.CompTipStringExtra;
|
||||
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
||||
string targetStatus = "Manual Target: ";
|
||||
LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
||||
if (manualTarget.IsValid)
|
||||
string targetStatus = "Target: ";
|
||||
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
targetStatus += $"{manualTarget.Thing?.LabelCap ?? manualTarget.Cell.ToString()}";
|
||||
if (!CanAttackManualTarget)
|
||||
{
|
||||
targetStatus += " (Unreachable)";
|
||||
}
|
||||
targetStatus += $"{this.currentTarget.Thing?.LabelCap ?? this.currentTarget.Cell.ToString()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
targetStatus += "None";
|
||||
}
|
||||
|
||||
string result = turretStatus + "\n" + targetStatus;
|
||||
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:炮塔启用状态
|
||||
// 炮塔启用状态
|
||||
public bool TurretEnabled
|
||||
{
|
||||
get { return turretEnabled; }
|
||||
@@ -299,7 +208,7 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
get
|
||||
{
|
||||
// 新增:检查炮塔是否启用
|
||||
// 检查炮塔是否启用
|
||||
if (!TurretEnabled)
|
||||
return false;
|
||||
|
||||
@@ -361,7 +270,7 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
base.CompPostMake();
|
||||
this.MakeGun();
|
||||
// 新增:设置默认启用状态
|
||||
// 设置默认启用状态
|
||||
TurretEnabled = Props.defaultEnabled;
|
||||
}
|
||||
|
||||
@@ -384,6 +293,7 @@ namespace WulaFallenEmpire
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetCurrentTarget()
|
||||
{
|
||||
this.currentTarget = LocalTargetInfo.Invalid;
|
||||
@@ -398,7 +308,7 @@ namespace WulaFallenEmpire
|
||||
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
|
||||
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.turretEnabled, "turretEnabled", Props.defaultEnabled, false);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
@@ -415,8 +325,6 @@ namespace WulaFallenEmpire
|
||||
|
||||
private const int StartShootIntervalTicks = 10;
|
||||
|
||||
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
||||
|
||||
public Thing gun;
|
||||
protected int burstCooldownTicksLeft;
|
||||
protected int burstWarmupTicksLeft;
|
||||
@@ -426,10 +334,10 @@ namespace WulaFallenEmpire
|
||||
private int lastAttackTargetTick;
|
||||
private float curRotation;
|
||||
|
||||
// 新增:炮塔启用状态字段
|
||||
// 炮塔启用状态字段
|
||||
private bool turretEnabled = true;
|
||||
|
||||
[Unsaved(false)]
|
||||
public Material turretMat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_NanoRepair.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\Hediff_DamageShield.cs" />
|
||||
<Compile Include="Pawn\Comp_MultiTurretGun.cs" />
|
||||
@@ -225,10 +224,15 @@
|
||||
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
|
||||
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.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\CompProperties_CustomUniqueWeapon.cs" />
|
||||
<Compile Include="ThingComp\Wula_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
|
||||
<Compile Include="ThingComp\Wula_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
|
||||
<Compile Include="ThingComp\WULA_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
|
||||
<Compile Include="ThingComp\WULA_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />
|
||||
|
||||
Reference in New Issue
Block a user