1
This commit is contained in:
Binary file not shown.
@@ -55,8 +55,6 @@
|
||||
<writeCombatLog>True</writeCombatLog>
|
||||
<showPsycastEffects>False</showPsycastEffects>
|
||||
<aiCanUse>true</aiCanUse>
|
||||
<ai_SearchAOEForTargets>true</ai_SearchAOEForTargets>
|
||||
<ai_IsIncendiary>true</ai_IsIncendiary>
|
||||
<cooldownTicksRange>600</cooldownTicksRange>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
@@ -64,7 +62,7 @@
|
||||
<warmupTime>1</warmupTime>
|
||||
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
||||
|
||||
<ai_IsWeapon>true</ai_IsWeapon>
|
||||
<ai_IsWeapon>false</ai_IsWeapon>
|
||||
<ai_ProjectileLaunchingIgnoresMeleeThreats>true</ai_ProjectileLaunchingIgnoresMeleeThreats>
|
||||
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
@@ -86,15 +84,112 @@
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
|
||||
<range>18</range>
|
||||
<lineWidthEnd>12</lineWidthEnd>
|
||||
<lineWidthEnd>18</lineWidthEnd>
|
||||
<canHitFilledCells>true</canHitFilledCells>
|
||||
<affectAllies>false</affectAllies>
|
||||
<affectCaster>false</affectCaster>
|
||||
<castEffecter>Fire_SpewShort</castEffecter>
|
||||
<castEffecterMaintainTicks>60</castEffecterMaintainTicks>
|
||||
<hitEffecter>Fire_Spew</hitEffecter>
|
||||
<hitEffecterMaintainTicks>30</hitEffecterMaintainTicks>
|
||||
<castEffecter>WULA_AreaDestruction_Shockwave</castEffecter>
|
||||
<hitEffecter>WULA_AreaDestruction_Hit</hitEffecter>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
<EffecterDef>
|
||||
<defName>WULA_AreaDestruction_Hit</defName>
|
||||
<children>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<moteDef>WULA_Mote_ChargeLanceShot</moteDef>
|
||||
<burstCount>1~4</burstCount>
|
||||
<scale>0.4~0.8</scale>
|
||||
<speed>20~40</speed>
|
||||
<angle>135~225</angle>
|
||||
<positionRadius>0.01</positionRadius>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
</li>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<moteDef>WULA_Mote_ChargeLanceShot</moteDef>
|
||||
<burstCount>2~3</burstCount>
|
||||
<scale>0.4~0.8</scale>
|
||||
<speed>10~20</speed>
|
||||
<angle>135~225</angle>
|
||||
<positionRadius>0.01</positionRadius>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
</li>
|
||||
</children>
|
||||
<offsetTowardsTarget>0.25~0.25</offsetTowardsTarget>
|
||||
<positionRadius>0.1</positionRadius>
|
||||
</EffecterDef>
|
||||
<EffecterDef>
|
||||
<defName>WULA_AreaDestruction_Shockwave</defName>
|
||||
<children>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<fleckDef>Fleck_BlastMechBandShockwave</fleckDef>
|
||||
<burstCount>1</burstCount>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
<absoluteAngle>true</absoluteAngle>
|
||||
<rotation>0~0</rotation>
|
||||
</li>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<moteDef>WULA_AreaDestruction_Mainwave</moteDef>
|
||||
<burstCount>1</burstCount>
|
||||
<scale>1</scale>
|
||||
<speed>20</speed>
|
||||
<angle>0</angle>
|
||||
<positionRadius>0.01</positionRadius>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
</li>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<fleckDef>FlashMechBand</fleckDef>
|
||||
<burstCount>1</burstCount>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
<absoluteAngle>true</absoluteAngle>
|
||||
<rotation>0~0</rotation>
|
||||
</li>
|
||||
<li>
|
||||
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
|
||||
<moteDef>Mote_RedFlashStrong</moteDef>
|
||||
<burstCount>1</burstCount>
|
||||
<spawnLocType>OnSource</spawnLocType>
|
||||
<scale>24</scale>
|
||||
</li>
|
||||
</children>
|
||||
</EffecterDef>
|
||||
<ThingDef ParentName="MoteBase">
|
||||
<defName>WULA_Mote_ChargeLanceShot</defName>
|
||||
<graphicData>
|
||||
<texPath>Things/Projectile/ChargeLanceShot</texPath>
|
||||
<drawSize>(0.75,1.5)</drawSize>
|
||||
<shaderType>MoteGlow</shaderType>
|
||||
<color>(0.6,0.1,0.6,1)</color>
|
||||
</graphicData>
|
||||
<altitudeLayer>Projectile</altitudeLayer>
|
||||
<mote>
|
||||
<fadeInTime>0.2</fadeInTime>
|
||||
<solidTime>0.4</solidTime>
|
||||
<fadeOutTime>0.2</fadeOutTime>
|
||||
<growthRate>-0.8</growthRate>
|
||||
<rotateTowardsMoveDirection>true</rotateTowardsMoveDirection>
|
||||
</mote>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="MoteBase">
|
||||
<defName>WULA_AreaDestruction_Mainwave</defName>
|
||||
<graphicData>
|
||||
<texPath>Wula/Mote/WULA_AreaDestruction_Mainwave</texPath>
|
||||
<drawSize>(15,15)</drawSize>
|
||||
<shaderType>MoteGlow</shaderType>
|
||||
<color>(0.6,0.1,0.6,0.5)</color>
|
||||
</graphicData>
|
||||
<altitudeLayer>Floor</altitudeLayer>
|
||||
<mote>
|
||||
<fadeInTime>0.2</fadeInTime>
|
||||
<solidTime>0.4</solidTime>
|
||||
<fadeOutTime>0.2</fadeOutTime>
|
||||
<growthRate>0.25</growthRate>
|
||||
<rotateTowardsMoveDirection>true</rotateTowardsMoveDirection>
|
||||
</mote>
|
||||
</ThingDef>
|
||||
</Defs>
|
||||
@@ -231,6 +231,44 @@
|
||||
<techHediffsChance>1</techHediffsChance>
|
||||
<techHediffsMoney>9999~9999</techHediffsMoney>
|
||||
</PawnKindDef>
|
||||
<PawnKindDef ParentName="HeavyMechanoidKind">
|
||||
<defName>Wula_Psi_Titan</defName>
|
||||
<label>PAt-52"灵能泰坦"</label>
|
||||
<race>Wula_Psi_Titan</race>
|
||||
<defaultFactionType>PlayerColony</defaultFactionType>
|
||||
<allowInMechClusters>false</allowInMechClusters>
|
||||
<canMeleeAttack>false</canMeleeAttack>
|
||||
<isGoodBreacher>true</isGoodBreacher>
|
||||
<combatPower>1000</combatPower>
|
||||
|
||||
<isBoss>true</isBoss>
|
||||
<maxPerGroup>1</maxPerGroup>
|
||||
|
||||
<flyingAnimationFramePathPrefix>Wula/Things/Wula_Mech_Mobile_Factory/Flying/Wula_Mech_Mobile_Factory_Flying_</flyingAnimationFramePathPrefix>
|
||||
<flyingAnimationDrawSize>1</flyingAnimationDrawSize>
|
||||
<flyingAnimationFrameCount>1</flyingAnimationFrameCount>
|
||||
<flyingAnimationTicksPerFrame>2</flyingAnimationTicksPerFrame>
|
||||
<flyingAnimationInheritColors>false</flyingAnimationInheritColors>
|
||||
|
||||
<lifeStages>
|
||||
<li>
|
||||
<bodyGraphicData>
|
||||
<texPath>Wula/Things/Wula_Psi_Titan/Bodies/Naked_Thin</texPath>
|
||||
<maskPath>Wula/Things/WULA_Cat/AllegianceOverlays/None</maskPath>
|
||||
<!-- <shaderType>TransparentPostLight</shaderType> -->
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<drawSize>10</drawSize>
|
||||
</bodyGraphicData>
|
||||
</li>
|
||||
</lifeStages>
|
||||
<weaponMoney>0</weaponMoney>
|
||||
|
||||
<controlGroupPortraitZoom>0.4</controlGroupPortraitZoom>
|
||||
|
||||
<abilities>
|
||||
<li>WULA_PsiCrusher</li>
|
||||
</abilities>
|
||||
</PawnKindDef>
|
||||
|
||||
<!-- 战斗类乌拉 -->
|
||||
<PawnKindDef>
|
||||
|
||||
@@ -598,8 +598,8 @@
|
||||
</apparel>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AreaShield">
|
||||
<radius>3</radius>
|
||||
<baseHitPoints>50</baseHitPoints>
|
||||
<radius>5</radius>
|
||||
<baseHitPoints>100</baseHitPoints>
|
||||
<rechargeDelay>2400</rechargeDelay>
|
||||
<rechargeHitPointsIntervalTicks>30</rechargeHitPointsIntervalTicks>
|
||||
|
||||
@@ -609,7 +609,7 @@
|
||||
<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) -->
|
||||
|
||||
<!-- 拦截设置 -->
|
||||
<interceptGroundProjectiles>true</interceptGroundProjectiles>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<graphicData>
|
||||
<texPath>Wula/Projectile/WULA_Shrapnel</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
<drawSize>(1,2)</drawSize>
|
||||
<drawSize>(1.5,2)</drawSize>
|
||||
</graphicData>
|
||||
<modExtensions>
|
||||
<li Class="WulaFallenEmpire.TrackingBulletDef">
|
||||
|
||||
@@ -1445,6 +1445,70 @@
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="WULA_BaseMechanoid">
|
||||
<defName>Wula_Psi_Titan</defName>
|
||||
<label>PAt-6"灵能泰坦"</label>
|
||||
<description>由乌拉帝国大教堂所开发的重型灵能机械体,以短距离折跃优雅地穿梭于炮火间,并用灵能盾抵挡敌方射弹侵袭。该机体不仅镌刻了破坏力强大的星光追猎术式用以发起远距离跟踪打击,还拥有一系列改变战局的灵能能力。\n\n但是在近身搏斗中,灵能泰坦是一个可笑的对手,并且它的秘文纹路很容易遭到外力破坏,它的本体无法吸收太多伤害!</description>
|
||||
<uiIconPath>Wula/Things/Wula_Psi_Titan/Wula_Psi_Titan_Icon</uiIconPath>
|
||||
<statBases>
|
||||
<BandwidthCost>1</BandwidthCost>
|
||||
<MoveSpeed>1</MoveSpeed>
|
||||
<EnergyShieldEnergyMax>5</EnergyShieldEnergyMax>
|
||||
|
||||
<MaxFlightTime>9999</MaxFlightTime>
|
||||
<FlightCooldown>0</FlightCooldown>
|
||||
</statBases>
|
||||
<race>
|
||||
<body>Mech_Warqueen</body>
|
||||
<baseBodySize>30</baseBodySize>
|
||||
<lifeStageAges>
|
||||
<li>
|
||||
<def>MechanoidFullyFormed</def>
|
||||
<minAge>0</minAge>
|
||||
<soundWounded>Pawn_Mech_Warqueen_Wounded</soundWounded>
|
||||
<soundDeath>Pawn_Mech_Warqueen_Death</soundDeath>
|
||||
<soundCall>Pawn_Mech_Warqueen_Call</soundCall>
|
||||
</li>
|
||||
</lifeStageAges>
|
||||
<baseHealthScale>25</baseHealthScale>
|
||||
|
||||
<flightStartChanceOnJobStart>1</flightStartChanceOnJobStart>
|
||||
<!-- <thinkTreeConstant>WarUrchinConstant</thinkTreeConstant> -->
|
||||
</race>
|
||||
<tools>
|
||||
<li>
|
||||
<label>碾压</label>
|
||||
<capacities>
|
||||
<li>Blunt</li>
|
||||
</capacities>
|
||||
<power>360</power>
|
||||
<cooldownTime>8</cooldownTime>
|
||||
<linkedBodyPartsGroup>Torso</linkedBodyPartsGroup>
|
||||
<ensureLinkedBodyPartsGroupAlwaysUsable>true</ensureLinkedBodyPartsGroupAlwaysUsable>
|
||||
</li>
|
||||
</tools>
|
||||
<comps>
|
||||
<li Class="CompProperties_Shield">
|
||||
<startingTicksToReset>36000</startingTicksToReset><!-- 10 mins -->
|
||||
<minDrawSize>8.2</minDrawSize>
|
||||
<maxDrawSize>8.4</maxDrawSize>
|
||||
<energyLossPerDamage>0.01</energyLossPerDamage>
|
||||
<energyOnReset>4.0</energyOnReset>
|
||||
<blocksRangedWeapons>false</blocksRangedWeapons>
|
||||
</li>
|
||||
<!-- 飞行组件 -->
|
||||
<li Class="WulaFallenEmpire.CompProperties_PawnFlight">
|
||||
|
||||
<!-- 飞行触发条件:仅在征召时飞行 -->
|
||||
<flightCondition>Drafted</flightCondition>
|
||||
|
||||
<!-- 链接到我们刚刚创建的 AnimationDef -->
|
||||
<flyingAnimationNorth>WULA_Hover_FlyNorth</flyingAnimationNorth>
|
||||
<flyingAnimationEast>WULA_Hover_FlyEast</flyingAnimationEast>
|
||||
<flyingAnimationSouth>WULA_Hover_FlySouth</flyingAnimationSouth>
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
|
||||
<!-- 特殊单位 -->
|
||||
<ThingDef ParentName="BaseMechanoidWalker">
|
||||
|
||||
@@ -346,15 +346,5 @@
|
||||
<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>
|
||||
<ShieldOfflineByMoving>未激活-移动中</ShieldOfflineByMoving>
|
||||
</LanguageData>
|
||||
BIN
Content/Textures/Wula/Mote/WULA_AreaDestruction_Mainwave.png
Normal file
BIN
Content/Textures/Wula/Mote/WULA_AreaDestruction_Mainwave.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 210 KiB |
@@ -21,8 +21,8 @@ namespace WulaFallenEmpire
|
||||
Map map = parent.pawn.MapHeld;
|
||||
if (map == null) return;
|
||||
|
||||
// 播放发射特效(在施法者位置)
|
||||
PlayCastEffecter(map);
|
||||
// 播放发射特效(在施法者位置)- 在释放瞬间播放
|
||||
//PlayCastEffecter(target, map);
|
||||
|
||||
// 获取扇形区域内的所有单元格
|
||||
List<IntVec3> affectedCells = AffectedCells(target);
|
||||
@@ -61,26 +61,26 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCastEffecter(Map map)
|
||||
private void PlayCastEffecter(LocalTargetInfo target, Map map)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Props.castEffecter == null) return;
|
||||
|
||||
// 使用与 CompAbilityEffect_EffecterOnCaster 相同的方法
|
||||
Effecter effecter = Props.castEffecter.Spawn(Pawn, map);
|
||||
// 在释放瞬间创建效果器,确保正确的方向
|
||||
Effecter effecter = Props.castEffecter.Spawn(Pawn.Position, target.Cell, map);
|
||||
|
||||
if (Props.castEffecterMaintainTicks > 0)
|
||||
{
|
||||
// 使用与参考代码相同的方法来维持效果器
|
||||
map.effecterMaintainer.AddEffecterToMaintain(effecter, new TargetInfo(Pawn), Pawn, Props.castEffecterMaintainTicks);
|
||||
parent.AddEffecterToMaintain(effecter, Pawn.Position, target.Cell, Props.castEffecterMaintainTicks, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
Log.Message($"[AreaDestruction] Played cast effecter on caster at {Pawn.Position}");
|
||||
Log.Message($"[AreaDestruction] Played cast effecter from {Pawn.Position} to {target.Cell}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
@@ -95,28 +95,35 @@ namespace WulaFallenEmpire
|
||||
if (Props.hitEffecter == null) return;
|
||||
if (target == null || target.Destroyed) return;
|
||||
|
||||
// 使用与 CompAbilityEffect_EffecterOnTarget 相同的方法
|
||||
Effecter effecter;
|
||||
if (target is Pawn pawnTarget)
|
||||
{
|
||||
effecter = Props.hitEffecter.Spawn(pawnTarget, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
effecter = Props.hitEffecter.Spawn(target.Position, map);
|
||||
}
|
||||
// 计算冲击波方向:从施法者到目标的向量
|
||||
Vector3 directionFromCaster = (target.Position.ToVector3Shifted() - Pawn.Position.ToVector3Shifted()).normalized;
|
||||
|
||||
// 计算反向位置:目标位置 + 反向向量 * 距离
|
||||
// 这样特效会从目标位置向施法者的反方向播放
|
||||
IntVec3 reversePosition = target.Position + new IntVec3(
|
||||
Mathf.RoundToInt(-directionFromCaster.x * 2f),
|
||||
0,
|
||||
Mathf.RoundToInt(-directionFromCaster.z * 2f)
|
||||
);
|
||||
|
||||
// 确保反向位置在地图范围内
|
||||
reversePosition = reversePosition.ClampInsideMap(map);
|
||||
|
||||
// 使用两个位置参数来设置效果器方向
|
||||
// 从目标位置到反向位置,这样特效会向施法者反方向播放
|
||||
Effecter effecter = Props.hitEffecter.Spawn(target.Position, reversePosition, map);
|
||||
|
||||
if (Props.hitEffecterMaintainTicks > 0)
|
||||
{
|
||||
// 使用与参考代码相同的方法来维持效果器
|
||||
parent.AddEffecterToMaintain(effecter, target.Position, Props.hitEffecterMaintainTicks);
|
||||
// 维持效果器
|
||||
parent.AddEffecterToMaintain(effecter, target.Position, reversePosition, Props.hitEffecterMaintainTicks, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
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} with reverse direction to {reversePosition}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
@@ -124,6 +131,21 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<PreCastAction> GetPreCastActions()
|
||||
{
|
||||
if (Props.castEffecter != null)
|
||||
{
|
||||
yield return new PreCastAction
|
||||
{
|
||||
action = delegate (LocalTargetInfo a, LocalTargetInfo b)
|
||||
{
|
||||
parent.AddEffecterToMaintain(Props.castEffecter.Spawn(parent.pawn.Position, a.Cell, parent.pawn.Map), Pawn.Position, a.Cell, 17, Pawn.MapHeld);
|
||||
},
|
||||
ticksAwayFromCast = 17
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessTarget(Thing target)
|
||||
{
|
||||
if (target is Building building)
|
||||
@@ -254,12 +276,6 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<PreCastAction> GetPreCastActions()
|
||||
{
|
||||
// 这里不再预先创建效果器,改为在Apply中创建
|
||||
yield break;
|
||||
}
|
||||
|
||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||
{
|
||||
GenDraw.DrawFieldEdges(AffectedCells(target), Color.red);
|
||||
|
||||
@@ -14,46 +14,45 @@ namespace WulaFallenEmpire
|
||||
|
||||
public static IEnumerable<ThingComp_AreaShield> GetActiveShieldsForMap(Map map)
|
||||
{
|
||||
if (map == null)
|
||||
yield break;
|
||||
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)
|
||||
if (shield?.parent != null && !shield.parent.Destroyed && shield?.Active == true)
|
||||
yield return shield;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateShieldCache()
|
||||
{
|
||||
activeShieldsByMap.Clear();
|
||||
|
||||
foreach (var map in Find.Maps)
|
||||
{
|
||||
if (map == null) continue;
|
||||
var shieldSet = new HashSet<ThingComp_AreaShield>();
|
||||
|
||||
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (pawn.apparel != null)
|
||||
if (pawn?.apparel == null || pawn.Destroyed)
|
||||
continue;
|
||||
foreach (var apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
foreach (var apparel in pawn.apparel.WornApparel)
|
||||
if (apparel == null || apparel.Destroyed)
|
||||
continue;
|
||||
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
|
||||
// 修改:只有立定且激活的护盾才加入缓存
|
||||
if (shield != null && shield.Active && !shield.IsWearerMoving)
|
||||
{
|
||||
// 同时支持普通护盾和反弹护盾
|
||||
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
|
||||
if (shield != null && shield.Active)
|
||||
{
|
||||
shieldSet.Add(shield);
|
||||
}
|
||||
shieldSet.Add(shield);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeShieldsByMap[map] = shieldSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ 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;
|
||||
// 新增:移动状态的颜色
|
||||
private static readonly Texture2D MovingShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.5f, 0.5f, 0.5f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
|
||||
|
||||
public override float GetWidth(float maxWidth) => 140f;
|
||||
|
||||
@@ -28,14 +29,33 @@ namespace WulaFallenEmpire
|
||||
Rect barRect = rect2;
|
||||
barRect.yMin = rect2.y + rect2.height / 2f;
|
||||
float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax;
|
||||
Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false);
|
||||
|
||||
// 修改:根据状态选择不同的状态条
|
||||
Texture2D barTex;
|
||||
TaggedString statusText;
|
||||
|
||||
if (shield.IsOnCooldown)
|
||||
{
|
||||
barTex = EmptyShieldBarTex;
|
||||
statusText = "ShieldOnCooldown".Translate();
|
||||
}
|
||||
else if (shield.IsWearerMoving)
|
||||
{
|
||||
// 移动时显示灰色状态条和"移动中"文本
|
||||
barTex = MovingShieldBarTex;
|
||||
statusText = "ShieldOfflineByMoving".Translate(); // 你可以根据需要修改这个文本
|
||||
}
|
||||
else
|
||||
{
|
||||
barTex = FullShieldBarTex;
|
||||
statusText = new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax);
|
||||
}
|
||||
|
||||
Widgets.FillableBar(barRect, fillPercent, barTex, 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);
|
||||
|
||||
@@ -2,39 +2,118 @@ using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public static class ReflectedProjectileManager
|
||||
{
|
||||
private static Dictionary<Projectile, int> projectilesToDestroy = new Dictionary<Projectile, int>();
|
||||
private const int DESTROY_DELAY_TICKS = 1;
|
||||
|
||||
public static void MarkForDelayedDestroy(Projectile projectile)
|
||||
{
|
||||
if (projectile != null && !projectile.Destroyed)
|
||||
{
|
||||
projectilesToDestroy[projectile] = Find.TickManager.TicksGame + DESTROY_DELAY_TICKS;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Tick()
|
||||
{
|
||||
var toRemove = new List<Projectile>();
|
||||
|
||||
foreach (var kvp in projectilesToDestroy)
|
||||
{
|
||||
if (kvp.Key == null || kvp.Key.Destroyed || Find.TickManager.TicksGame >= kvp.Value)
|
||||
{
|
||||
if (kvp.Key != null && !kvp.Key.Destroyed)
|
||||
{
|
||||
kvp.Key.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
toRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var projectile in toRemove)
|
||||
{
|
||||
projectilesToDestroy.Remove(projectile);
|
||||
}
|
||||
}
|
||||
|
||||
// 在 ReflectedProjectileManager 类中添加这个方法
|
||||
public static bool IsMarkedForDestroy(Projectile projectile)
|
||||
{
|
||||
return projectile != null && projectilesToDestroy.ContainsKey(projectile);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
|
||||
public static class Projectile_CheckForFreeInterceptBetween_Patch
|
||||
{
|
||||
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
|
||||
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos, ref bool __result)
|
||||
{
|
||||
if (__instance.Map == null || __instance.Destroyed)
|
||||
try
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shouldDestroy = false;
|
||||
|
||||
// 使用缓存系统获取激活的护盾
|
||||
foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map))
|
||||
{
|
||||
if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true)
|
||||
// 安全检查
|
||||
if (__instance == null || __instance.Map == null || __instance.Destroyed)
|
||||
{
|
||||
shouldDestroy = true;
|
||||
break; // 只要有一个护盾吸收就销毁
|
||||
return true; // 继续执行原方法
|
||||
}
|
||||
// 如果护盾反射了抛射体,继续检查其他护盾(允许多重反射)
|
||||
}
|
||||
|
||||
if (shouldDestroy)
|
||||
bool shouldDestroy = false;
|
||||
bool wasReflected = false;
|
||||
|
||||
// 使用缓存系统获取激活的护盾
|
||||
foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map))
|
||||
{
|
||||
if (shield == null || shield.parent == null || shield.parent.Destroyed)
|
||||
continue;
|
||||
|
||||
if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true)
|
||||
{
|
||||
shouldDestroy = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查抛射体是否已经被反射(被标记为延迟销毁)
|
||||
if (ReflectedProjectileManager.IsMarkedForDestroy(__instance))
|
||||
{
|
||||
wasReflected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDestroy)
|
||||
{
|
||||
__instance.Destroy(DestroyMode.Vanish);
|
||||
__result = true; // 设置结果为 true 表示已被拦截
|
||||
return false; // 跳过原方法
|
||||
}
|
||||
|
||||
if (wasReflected)
|
||||
{
|
||||
__result = false; // 设置结果为 false 表示未被拦截(因为被反射了)
|
||||
return false; // 跳过原方法
|
||||
}
|
||||
|
||||
return true; // 继续执行原方法
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
__instance.Destroy(DestroyMode.Vanish);
|
||||
return false;
|
||||
Log.Warning($"AreaShield interception error: {ex}");
|
||||
return true; // 出错时继续执行原方法
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
// 添加Tick管理器
|
||||
[HarmonyPatch(typeof(TickManager), "DoSingleTick")]
|
||||
public static class TickManager_DoSingleTick_Patch
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
ReflectedProjectileManager.Tick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,29 @@ namespace WulaFallenEmpire
|
||||
public bool IsOnCooldown => ticksToReset > 0;
|
||||
public int HitPointsMax => Props.baseHitPoints;
|
||||
|
||||
private StunHandler stunner;
|
||||
private bool initialized = false;
|
||||
private StunHandler stunner;
|
||||
|
||||
// 新增:移动状态检测
|
||||
public bool IsWearerMoving
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Wearer == null || !Wearer.Spawned) return false;
|
||||
return Wearer.pather.Moving;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改Active属性:只有在立定时才激活
|
||||
public bool Active
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown)
|
||||
return false;
|
||||
// 新增:只有在立定时才激活
|
||||
if (IsWearerMoving)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -110,38 +124,51 @@ namespace WulaFallenEmpire
|
||||
|
||||
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()))
|
||||
{
|
||||
// 增强安全检查
|
||||
if (!Active || projectile == null || projectile.Destroyed || Wearer == null || Wearer.Map == null)
|
||||
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))
|
||||
if (currentHitPoints <= 0)
|
||||
return false;
|
||||
try
|
||||
{
|
||||
// 反射成功,播放反射特效
|
||||
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
|
||||
ApplyCosts(Props.reflectCost);
|
||||
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; // 销毁抛射体
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
// 普通拦截,播放拦截特效
|
||||
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
|
||||
ApplyCosts();
|
||||
return true; // 销毁抛射体
|
||||
Log.Warning($"Error in TryIntercept: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,79 +177,87 @@ namespace WulaFallenEmpire
|
||||
/// </summary>
|
||||
private bool TryReflectProjectile(Projectile originalProjectile, Vector3 lastExactPos, Vector3 newExactPos)
|
||||
{
|
||||
if (!Props.canReflect) return false;
|
||||
|
||||
// 检查反射概率
|
||||
if (Rand.Value > Props.reflectChance) return false;
|
||||
if (!Props.canReflect || originalProjectile == null || originalProjectile.Destroyed)
|
||||
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;
|
||||
return CreateReflectedProjectile(originalProjectile, reflectDirection, newExactPos);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Warning($"Error reflecting projectile: {ex}");
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建反射后的新抛射体
|
||||
/// </summary>
|
||||
private void CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint)
|
||||
private bool CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (originalProjectile == null || originalProjectile.Destroyed || Wearer == null || Wearer.Map == null)
|
||||
return false;
|
||||
// 计算新的发射位置(护盾位置附近)
|
||||
Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint);
|
||||
|
||||
// 确保位置在地图内
|
||||
IntVec3 spawnCell = spawnPosition.ToIntVec3();
|
||||
if (!spawnCell.InBounds(Wearer.Map))
|
||||
{
|
||||
spawnCell = Wearer.Position;
|
||||
}
|
||||
// 计算新的目标位置
|
||||
Vector3 targetPosition = spawnPosition + reflectDirection * 30f; // 足够远的距离
|
||||
|
||||
Vector3 targetPosition = spawnCell.ToVector3Shifted() + reflectDirection * 30f;
|
||||
IntVec3 targetCell = targetPosition.ToIntVec3();
|
||||
// 创建新的抛射体
|
||||
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnPosition.ToIntVec3(), Wearer.Map);
|
||||
|
||||
// 设置发射者为原抛射体的发射者
|
||||
Thing launcher = originalProjectile.Launcher ?? Wearer;
|
||||
|
||||
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Wearer.Map);
|
||||
if (newProjectile == null)
|
||||
{
|
||||
Log.Warning("Failed to spawn reflected projectile");
|
||||
return false;
|
||||
}
|
||||
// 设置发射者为装备穿戴者
|
||||
Thing launcher = Wearer;
|
||||
// 发射新抛射体
|
||||
newProjectile.Launch(
|
||||
launcher,
|
||||
spawnPosition,
|
||||
new LocalTargetInfo(targetPosition.ToIntVec3()),
|
||||
new LocalTargetInfo(targetPosition.ToIntVec3()),
|
||||
spawnCell.ToVector3Shifted(),
|
||||
new LocalTargetInfo(targetCell),
|
||||
new LocalTargetInfo(targetCell),
|
||||
ProjectileHitFlags.All,
|
||||
false
|
||||
);
|
||||
|
||||
// 复制重要的属性
|
||||
CopyProjectileProperties(originalProjectile, newProjectile);
|
||||
|
||||
// 销毁原抛射体
|
||||
originalProjectile.Destroy(DestroyMode.Vanish);
|
||||
|
||||
Log.Message($"反射抛射体: 从 {spawnPosition} 向 {targetPosition} 发射");
|
||||
// 使用延迟销毁而不是立即销毁
|
||||
ReflectedProjectileManager.MarkForDelayedDestroy(originalProjectile);
|
||||
Log.Message($"反射抛射体: 由 {Wearer?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
|
||||
return true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Warning($"Error creating reflected projectile: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,21 +266,17 @@ namespace WulaFallenEmpire
|
||||
/// </summary>
|
||||
private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint)
|
||||
{
|
||||
if (Wearer == null)
|
||||
return 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();
|
||||
|
||||
return spawnPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -314,12 +345,13 @@ namespace WulaFallenEmpire
|
||||
AreaShieldManager.NotifyShieldStateChanged(this);
|
||||
}
|
||||
|
||||
// 护盾绘制方法
|
||||
// 护盾绘制方法 - 只有在立定时才绘制
|
||||
public override void CompDrawWornExtras()
|
||||
{
|
||||
base.CompDrawWornExtras();
|
||||
|
||||
if (!Active || Wearer?.Map == null || !ShouldDisplay)
|
||||
// 修改:移动时不绘制护盾
|
||||
if (!Active || Wearer?.Map == null || !ShouldDisplay || IsWearerMoving)
|
||||
return;
|
||||
|
||||
Vector3 drawPos = Wearer.Drawer?.DrawPos ?? Wearer.Position.ToVector3Shifted();
|
||||
|
||||
BIN
美术与文本源文件/Wula/Mote/WULA_AreaDestruction_Mainwave.sai2
Normal file
BIN
美术与文本源文件/Wula/Mote/WULA_AreaDestruction_Mainwave.sai2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
Reference in New Issue
Block a user