2
This commit is contained in:
Binary file not shown.
98
1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml
Normal file
98
1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Defs>
|
||||||
|
<AbilityDef>
|
||||||
|
<defName>Wula_Mech_Mobile_Factory_Produce</defName>
|
||||||
|
<label>生产战争机器</label>
|
||||||
|
<description>立刻生产10台CRm-51"兵蚁",快速组织一道近战阵线。</description>
|
||||||
|
<iconPath>Wula/UI/Abilities/Wula_Mech_Mobile_Factory_Produce</iconPath>
|
||||||
|
<cooldownTicksRange>5000</cooldownTicksRange>
|
||||||
|
<charges>5</charges>
|
||||||
|
<cooldownPerCharge>true</cooldownPerCharge>
|
||||||
|
<aiCanUse>true</aiCanUse>
|
||||||
|
<displayOrder>300</displayOrder>
|
||||||
|
<disableGizmoWhileUndrafted>true</disableGizmoWhileUndrafted>
|
||||||
|
<displayGizmoWhileUndrafted>false</displayGizmoWhileUndrafted>
|
||||||
|
<verbProperties>
|
||||||
|
<verbClass>Verb_CastAbility</verbClass>
|
||||||
|
<range>24</range>
|
||||||
|
<warmupTime>0</warmupTime>
|
||||||
|
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
||||||
|
<violent>false</violent>
|
||||||
|
<targetable>false</targetable>
|
||||||
|
<targetParams>
|
||||||
|
<canTargetSelf>true</canTargetSelf>
|
||||||
|
</targetParams>
|
||||||
|
</verbProperties>
|
||||||
|
<comps>
|
||||||
|
<li Class="WulaFallenEmpire.CompProperties_AbilityLaunchMultiProjectile">
|
||||||
|
<projectileDef>Wula_Mech_Mobile_Factory_Produce_Proj</projectileDef>
|
||||||
|
<numProjectiles>10</numProjectiles>
|
||||||
|
</li>
|
||||||
|
</comps>
|
||||||
|
</AbilityDef>
|
||||||
|
<ThingDef ParentName="BaseGrenadeProjectile">
|
||||||
|
<defName>Wula_Mech_Mobile_Factory_Produce_Proj</defName>
|
||||||
|
<label>CRm-51"兵蚁"</label>
|
||||||
|
<thingClass>Projectile_SpawnsPawnZeroAge</thingClass>
|
||||||
|
<graphicData>
|
||||||
|
<texPath>Wula/Things/WULA_Mech_Flyer/WULA_Mech_Flyer_south</texPath>
|
||||||
|
<graphicClass>Graphic_Single</graphicClass>
|
||||||
|
</graphicData>
|
||||||
|
<projectile>
|
||||||
|
<speed>41</speed>
|
||||||
|
<spawnsPawnKind>WULA_Mech_Flyer</spawnsPawnKind>
|
||||||
|
<tryAdjacentFreeSpaces>true</tryAdjacentFreeSpaces>
|
||||||
|
<damageDef>Bullet</damageDef>
|
||||||
|
<damageAmountBase>1</damageAmountBase>
|
||||||
|
</projectile>
|
||||||
|
</ThingDef>
|
||||||
|
|
||||||
|
<AbilityDef>
|
||||||
|
<defName>WULA_PsiCrusher</defName>
|
||||||
|
<label>灵能粉碎</label>
|
||||||
|
<description>释放纯净的灵能能量,直接摧毁面前扇形区域内的所有目标。</description>
|
||||||
|
<iconPath>UI/Abilities/FireSpew</iconPath>
|
||||||
|
<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>
|
||||||
|
<range>24</range>
|
||||||
|
<warmupTime>0</warmupTime>
|
||||||
|
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
||||||
|
|
||||||
|
<ai_IsWeapon>true</ai_IsWeapon>
|
||||||
|
<ai_ProjectileLaunchingIgnoresMeleeThreats>true</ai_ProjectileLaunchingIgnoresMeleeThreats>
|
||||||
|
|
||||||
|
<requireLineOfSight>false</requireLineOfSight>
|
||||||
|
|
||||||
|
<aimingLineMote>Mote_HellsphereCannon_Aim</aimingLineMote>
|
||||||
|
<aimingChargeMote>Mote_HellsphereCannon_Charge</aimingChargeMote>
|
||||||
|
<aimingChargeMoteOffset>1.07</aimingChargeMoteOffset>
|
||||||
|
<aimingLineMoteFixedLength>32</aimingLineMoteFixedLength>
|
||||||
|
<aimingTargetMote>Mote_HellsphereCannon_Target</aimingTargetMote>
|
||||||
|
|
||||||
|
<targetParams>
|
||||||
|
<canTargetLocations>true</canTargetLocations>
|
||||||
|
<canTargetSelf>false</canTargetSelf>
|
||||||
|
<canTargetPawns>true</canTargetPawns>
|
||||||
|
<canTargetBuildings>true</canTargetBuildings>
|
||||||
|
<canTargetPlants>true</canTargetPlants>
|
||||||
|
</targetParams>
|
||||||
|
</verbProperties>
|
||||||
|
<comps>
|
||||||
|
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
|
||||||
|
<range>12</range>
|
||||||
|
<lineWidthEnd>5</lineWidthEnd>
|
||||||
|
<canHitFilledCells>true</canHitFilledCells>
|
||||||
|
<affectAllies>false</affectAllies>
|
||||||
|
<affectCaster>false</affectCaster>
|
||||||
|
<effecterDef>Fire_Spew</effecterDef>
|
||||||
|
<hitEffecter>Fire_SpewShort</hitEffecter>
|
||||||
|
</li>
|
||||||
|
</comps>
|
||||||
|
</AbilityDef>
|
||||||
|
</Defs>
|
||||||
@@ -231,52 +231,45 @@
|
|||||||
<techHediffsChance>1</techHediffsChance>
|
<techHediffsChance>1</techHediffsChance>
|
||||||
<techHediffsMoney>9999~9999</techHediffsMoney>
|
<techHediffsMoney>9999~9999</techHediffsMoney>
|
||||||
</PawnKindDef>
|
</PawnKindDef>
|
||||||
<AbilityDef>
|
|
||||||
<defName>Wula_Mech_Mobile_Factory_Produce</defName>
|
<PawnKindDef ParentName="HeavyMechanoidKind">
|
||||||
<label>生产战争机器</label>
|
<defName>Wula_Psi_Titan</defName> <!-- 修改了defName以避免冲突 -->
|
||||||
<description>立刻生产10台CRm-51"兵蚁",快速组织一道近战阵线。</description>
|
<label>PAt-6"灵能泰坦"</label>
|
||||||
<iconPath>Wula/UI/Abilities/Wula_Mech_Mobile_Factory_Produce</iconPath>
|
<race>Wula_Psi_Titan</race>
|
||||||
<cooldownTicksRange>5000</cooldownTicksRange>
|
<combatPower>1000</combatPower>
|
||||||
<charges>5</charges>
|
<allowInMechClusters>false</allowInMechClusters>
|
||||||
<cooldownPerCharge>true</cooldownPerCharge>
|
<defaultFactionType>PlayerColony</defaultFactionType>
|
||||||
<aiCanUse>true</aiCanUse>
|
<canMeleeAttack>false</canMeleeAttack>
|
||||||
<displayOrder>300</displayOrder>
|
<isGoodBreacher>true</isGoodBreacher>
|
||||||
<disableGizmoWhileUndrafted>true</disableGizmoWhileUndrafted>
|
|
||||||
<displayGizmoWhileUndrafted>false</displayGizmoWhileUndrafted>
|
<flyingAnimationFramePathPrefix>Wula/Things/Wula_Mech_Mobile_Factory/Flying/Wula_Mech_Mobile_Factory_Flying_</flyingAnimationFramePathPrefix>
|
||||||
<verbProperties>
|
<flyingAnimationDrawSize>1</flyingAnimationDrawSize>
|
||||||
<verbClass>Verb_CastAbility</verbClass>
|
<flyingAnimationFrameCount>1</flyingAnimationFrameCount>
|
||||||
<range>24</range>
|
<flyingAnimationTicksPerFrame>2</flyingAnimationTicksPerFrame>
|
||||||
<warmupTime>0</warmupTime>
|
<flyingAnimationInheritColors>false</flyingAnimationInheritColors>
|
||||||
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
|
|
||||||
<violent>false</violent>
|
<lifeStages>
|
||||||
<targetable>false</targetable>
|
<li>
|
||||||
<targetParams>
|
<bodyGraphicData>
|
||||||
<canTargetSelf>true</canTargetSelf>
|
<texPath>Wula/Things/Wula_Psi_Titan/Bodies/Naked_Thin</texPath>
|
||||||
</targetParams>
|
<maskPath>Wula/Things/WULA_Cat/AllegianceOverlays/None</maskPath>
|
||||||
</verbProperties>
|
<shaderType>CutoutWithOverlay</shaderType>
|
||||||
<comps>
|
<graphicClass>Graphic_Multi</graphicClass>
|
||||||
<li Class="WulaFallenEmpire.CompProperties_AbilityLaunchMultiProjectile">
|
<drawSize>9</drawSize>
|
||||||
<projectileDef>Wula_Mech_Mobile_Factory_Produce_Proj</projectileDef>
|
<shadowData>
|
||||||
<numProjectiles>10</numProjectiles>
|
<volume>(1.4, 1.8, 1.4)</volume>
|
||||||
|
</shadowData>
|
||||||
|
</bodyGraphicData>
|
||||||
</li>
|
</li>
|
||||||
</comps>
|
</lifeStages>
|
||||||
</AbilityDef>
|
<weaponMoney>99999~99999</weaponMoney>
|
||||||
<ThingDef ParentName="BaseGrenadeProjectile">
|
|
||||||
<defName>Wula_Mech_Mobile_Factory_Produce_Proj</defName>
|
<controlGroupPortraitZoom>0.7</controlGroupPortraitZoom>
|
||||||
<label>CRm-51"兵蚁"</label>
|
|
||||||
<thingClass>Projectile_SpawnsPawnZeroAge</thingClass>
|
<abilities>
|
||||||
<graphicData>
|
<li>Wula_Mech_Mobile_Factory_Produce</li>
|
||||||
<texPath>Wula/Things/WULA_Mech_Flyer/WULA_Mech_Flyer_south</texPath>
|
</abilities>
|
||||||
<graphicClass>Graphic_Single</graphicClass>
|
</PawnKindDef>
|
||||||
</graphicData>
|
|
||||||
<projectile>
|
|
||||||
<speed>41</speed>
|
|
||||||
<spawnsPawnKind>WULA_Mech_Flyer</spawnsPawnKind>
|
|
||||||
<tryAdjacentFreeSpaces>true</tryAdjacentFreeSpaces>
|
|
||||||
<damageDef>Bullet</damageDef>
|
|
||||||
<damageAmountBase>1</damageAmountBase>
|
|
||||||
</projectile>
|
|
||||||
</ThingDef>
|
|
||||||
|
|
||||||
<!-- 战斗类乌拉 -->
|
<!-- 战斗类乌拉 -->
|
||||||
<PawnKindDef>
|
<PawnKindDef>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Defs>
|
|
||||||
<!-- 全局资源检查工具 -->
|
|
||||||
<QuestScriptDef>
|
|
||||||
<defName>Util_Wula_GlobalResourceCheck</defName>
|
|
||||||
<root Class="QuestNode_Sequence">
|
|
||||||
<nodes>
|
|
||||||
<!-- 检查全局资源 -->
|
|
||||||
<li Class="WulaFallenEmpire.QuestNode_CheckGlobalResource">
|
|
||||||
<resourceDef>$resourceDef</resourceDef>
|
|
||||||
<requiredCount>$requiredCount</requiredCount>
|
|
||||||
<retryDelayTicks>$retryDelayTicks</retryDelayTicks>
|
|
||||||
<successSignal>$successSignal</successSignal>
|
|
||||||
<failSignal>$failSignal</failSignal>
|
|
||||||
<deductOnSuccess>$deductOnSuccess</deductOnSuccess>
|
|
||||||
<useInputStorage>$useInputStorage</useInputStorage>
|
|
||||||
</li>
|
|
||||||
</nodes>
|
|
||||||
</root>
|
|
||||||
</QuestScriptDef>
|
|
||||||
</Defs>
|
|
||||||
@@ -896,6 +896,10 @@
|
|||||||
<filthCountRange>1~2</filthCountRange>
|
<filthCountRange>1~2</filthCountRange>
|
||||||
</deathAction>
|
</deathAction>
|
||||||
</race>
|
</race>
|
||||||
|
<costList Inherit="False">
|
||||||
|
<Steel>80</Steel>
|
||||||
|
<ComponentIndustrial>1</ComponentIndustrial>
|
||||||
|
</costList>
|
||||||
<recipeMaker>
|
<recipeMaker>
|
||||||
<researchPrerequisite>WULA_Bunker_Drop_Technology</researchPrerequisite>
|
<researchPrerequisite>WULA_Bunker_Drop_Technology</researchPrerequisite>
|
||||||
</recipeMaker>
|
</recipeMaker>
|
||||||
@@ -1441,6 +1445,69 @@
|
|||||||
</li>
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
|
<ThingDef Name="Wula_Mech_Mobile_Factory" ParentName="WULA_BaseMechanoid">
|
||||||
|
<defName>Wula_Psi_Titan</defName> <!-- 修改了defName以避免冲突 -->
|
||||||
|
<label>PAt-6"灵能泰坦"</label>
|
||||||
|
<description>骇人之物</description>
|
||||||
|
<statBases>
|
||||||
|
<BandwidthCost>1</BandwidthCost>
|
||||||
|
<MoveSpeed>1</MoveSpeed>
|
||||||
|
<EnergyShieldEnergyMax>35</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">
|
<ThingDef ParentName="BaseMechanoidWalker">
|
||||||
|
|||||||
@@ -60,6 +60,6 @@
|
|||||||
<li>questName->什一税税收</li>
|
<li>questName->什一税税收</li>
|
||||||
</WULA_Base_Tex_Quest.questNameRules.rulesStrings>
|
</WULA_Base_Tex_Quest.questNameRules.rulesStrings>
|
||||||
<WULA_Base_Tex_Quest.questDescriptionRules.rulesStrings>
|
<WULA_Base_Tex_Quest.questDescriptionRules.rulesStrings>
|
||||||
<li>questDescription->唯死亡和税收不可避免——按时上交什一税是乌拉帝国殖民地的光荣义务。\n\n乌拉帝国的什一税会从殖民地储存在舰队中的资产里面扣除,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将物资输送到位于轨道上的舰队。\n\n你可以快速地准备好税金,乌拉帝国对积极纳税的殖民地会给予更多关照——但是如果一直拖延,则会惹其不快,甚至有可能被定性为叛国!</li>
|
<li>questDescription->唯死亡和税收不可避免——按时上交什一税是乌拉帝国殖民地的光荣义务。\n\n乌拉帝国的什一税会从殖民地储存在舰队中的资产里面扣除,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将物资输送到位于轨道上的舰队。\n\n乌拉帝国对积极纳税的殖民地会给予更多关照——但是如果拖延,则每延期一天都会惹其不快,最后甚至有可能被定性为叛国!</li>
|
||||||
</WULA_Base_Tex_Quest.questDescriptionRules.rulesStrings>
|
</WULA_Base_Tex_Quest.questDescriptionRules.rulesStrings>
|
||||||
</LanguageData>
|
</LanguageData>
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using RimWorld;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class CompAbilityEffect_AreaDestruction : CompAbilityEffect
|
||||||
|
{
|
||||||
|
private readonly List<IntVec3> tmpCells = new List<IntVec3>();
|
||||||
|
private readonly List<Thing> tmpThings = new List<Thing>();
|
||||||
|
|
||||||
|
private new CompProperties_AbilityAreaDestruction Props => (CompProperties_AbilityAreaDestruction)props;
|
||||||
|
|
||||||
|
private Pawn Pawn => parent.pawn;
|
||||||
|
|
||||||
|
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||||
|
{
|
||||||
|
base.Apply(target, dest);
|
||||||
|
|
||||||
|
Map map = parent.pawn.MapHeld;
|
||||||
|
if (map == null) return;
|
||||||
|
|
||||||
|
// 获取扇形区域内的所有单元格
|
||||||
|
List<IntVec3> affectedCells = AffectedCells(target);
|
||||||
|
|
||||||
|
// 记录所有被影响的目标
|
||||||
|
List<Thing> affectedTargets = new List<Thing>();
|
||||||
|
|
||||||
|
foreach (IntVec3 cell in affectedCells)
|
||||||
|
{
|
||||||
|
if (!cell.InBounds(map)) continue;
|
||||||
|
|
||||||
|
// 处理该单元格中的所有事物
|
||||||
|
tmpThings.Clear();
|
||||||
|
tmpThings.AddRange(cell.GetThingList(map));
|
||||||
|
|
||||||
|
foreach (Thing thing in tmpThings)
|
||||||
|
{
|
||||||
|
if (thing == null || thing.Destroyed) continue;
|
||||||
|
|
||||||
|
// 检查是否应该影响这个目标
|
||||||
|
if (!ShouldAffectThing(thing)) continue;
|
||||||
|
|
||||||
|
// 添加到受影响目标列表
|
||||||
|
if (!affectedTargets.Contains(thing))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放主要效果
|
||||||
|
if (Props.effecterDef != null)
|
||||||
|
{
|
||||||
|
Props.effecterDef.Spawn(target.Cell, map).Cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayHitEffecter(Thing target, Map map)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
map.effecterMaintainer.AddEffecterToMaintain(effecter, target, Props.hitEffecter.maintainTicks);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 否则在适当时间后清理
|
||||||
|
LongEventHandler.ExecuteWhenFinished(delegate
|
||||||
|
{
|
||||||
|
effecter.Cleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}");
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"[AreaDestruction] Error playing hit effecter on {target?.Label}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldAffectThing(Thing thing)
|
||||||
|
{
|
||||||
|
// 检查是否影响施法者自己
|
||||||
|
if (thing == Pawn && !Props.affectCaster)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 检查是否影响友方单位
|
||||||
|
if (thing is Pawn targetPawn && targetPawn.Faction != null)
|
||||||
|
{
|
||||||
|
if (!Props.affectAllies && targetPawn.Faction == Pawn.Faction)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 不攻击囚犯(除非设置影响友方)
|
||||||
|
if (targetPawn.IsPrisoner && targetPawn.HostFaction == Pawn.Faction && !Props.affectAllies)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyBuilding(Building building)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (building.Destroyed || !building.Spawned) return;
|
||||||
|
|
||||||
|
// 记录建筑信息用于日志
|
||||||
|
string buildingInfo = $"{building.Label} at {building.Position}";
|
||||||
|
|
||||||
|
// 直接销毁建筑
|
||||||
|
building.Destroy(DestroyMode.Vanish);
|
||||||
|
|
||||||
|
Log.Message($"[AreaDestruction] Destroyed building: {buildingInfo}");
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"[AreaDestruction] Error destroying building {building?.Label}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyAllBodyParts(Pawn targetPawn)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (targetPawn.Destroyed || !targetPawn.Spawned || targetPawn.Dead) return;
|
||||||
|
|
||||||
|
// 记录pawn信息
|
||||||
|
string pawnInfo = $"{targetPawn.Label} at {targetPawn.Position}";
|
||||||
|
|
||||||
|
// 获取所有身体部位(不包括核心部位如躯干、头部)
|
||||||
|
var bodyPartRecords = targetPawn.def.race.body.AllParts;
|
||||||
|
|
||||||
|
int partsDestroyed = 0;
|
||||||
|
foreach (var bodyPartRecord in bodyPartRecords)
|
||||||
|
{
|
||||||
|
// 跳过核心部位以避免立即死亡(可选,根据需求调整)
|
||||||
|
if (IsCoreBodyPart(bodyPartRecord)) continue;
|
||||||
|
|
||||||
|
// 检查该部位是否已经缺失
|
||||||
|
if (!targetPawn.health.hediffSet.PartIsMissing(bodyPartRecord))
|
||||||
|
{
|
||||||
|
// 添加缺失部位hediff
|
||||||
|
targetPawn.health.AddHediff(HediffDefOf.MissingBodyPart, bodyPartRecord);
|
||||||
|
partsDestroyed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果摧毁了任何部位,检查是否应该杀死pawn
|
||||||
|
if (partsDestroyed > 0)
|
||||||
|
{
|
||||||
|
// 检查pawn是否还"活着"(没有核心部位缺失时可能还能存活)
|
||||||
|
CheckPawnViability(targetPawn);
|
||||||
|
|
||||||
|
Log.Message($"[AreaDestruction] Destroyed {partsDestroyed} body parts on {pawnInfo}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"[AreaDestruction] Error destroying body parts on {targetPawn?.Label}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCoreBodyPart(BodyPartRecord bodyPart)
|
||||||
|
{
|
||||||
|
// 定义核心部位,这些部位缺失会导致立即死亡
|
||||||
|
return bodyPart.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 大脑
|
||||||
|
bodyPart.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 心脏
|
||||||
|
bodyPart.def == BodyPartDefOf.Torso; // 躯干
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckPawnViability(Pawn pawn)
|
||||||
|
{
|
||||||
|
// 检查pawn是否还能存活
|
||||||
|
if (pawn.Dead) return;
|
||||||
|
|
||||||
|
// 如果失去了所有肢体,pawn可能会倒下但不会立即死亡
|
||||||
|
bool hasAnyLimbs = false;
|
||||||
|
var allParts = pawn.def.race.body.AllParts;
|
||||||
|
|
||||||
|
foreach (var part in allParts)
|
||||||
|
{
|
||||||
|
if ((part.def.tags.Contains(BodyPartTagDefOf.MovingLimbCore) ||
|
||||||
|
part.def.tags.Contains(BodyPartTagDefOf.ManipulationLimbCore)) &&
|
||||||
|
!pawn.health.hediffSet.PartIsMissing(part))
|
||||||
|
{
|
||||||
|
hasAnyLimbs = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有肢体了,让pawn倒下
|
||||||
|
if (!hasAnyLimbs && pawn.health.capacities.CapableOf(PawnCapacityDefOf.Moving))
|
||||||
|
{
|
||||||
|
pawn.health.forceDowned = true;
|
||||||
|
pawn.health.CheckForStateChange(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||||
|
{
|
||||||
|
GenDraw.DrawFieldEdges(AffectedCells(target), Color.red);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool AICanTargetNow(LocalTargetInfo target)
|
||||||
|
{
|
||||||
|
if (Pawn.Faction != null && !Props.affectAllies)
|
||||||
|
{
|
||||||
|
foreach (IntVec3 cell in AffectedCells(target))
|
||||||
|
{
|
||||||
|
List<Thing> thingList = cell.GetThingList(Pawn.Map);
|
||||||
|
for (int i = 0; i < thingList.Count; i++)
|
||||||
|
{
|
||||||
|
if (thingList[i].Faction == Pawn.Faction)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IntVec3> AffectedCells(LocalTargetInfo target)
|
||||||
|
{
|
||||||
|
tmpCells.Clear();
|
||||||
|
Vector3 casterPos = Pawn.Position.ToVector3Shifted().Yto0();
|
||||||
|
IntVec3 targetCell = target.Cell.ClampInsideMap(Pawn.Map);
|
||||||
|
|
||||||
|
if (Pawn.Position == targetCell)
|
||||||
|
{
|
||||||
|
return tmpCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance = (targetCell - Pawn.Position).LengthHorizontal;
|
||||||
|
float xRatio = (float)(targetCell.x - Pawn.Position.x) / distance;
|
||||||
|
float zRatio = (float)(targetCell.z - Pawn.Position.z) / distance;
|
||||||
|
|
||||||
|
// 计算扇形末端位置
|
||||||
|
targetCell.x = Mathf.RoundToInt((float)Pawn.Position.x + xRatio * Props.range);
|
||||||
|
targetCell.z = Mathf.RoundToInt((float)Pawn.Position.z + zRatio * Props.range);
|
||||||
|
|
||||||
|
float targetAngle = Vector3.SignedAngle(targetCell.ToVector3Shifted().Yto0() - casterPos, Vector3.right, Vector3.up);
|
||||||
|
float halfWidth = Props.lineWidthEnd / 2f;
|
||||||
|
float coneLength = Mathf.Sqrt(Mathf.Pow((targetCell - Pawn.Position).LengthHorizontal, 2f) + Mathf.Pow(halfWidth, 2f));
|
||||||
|
float coneAngle = 57.29578f * Mathf.Asin(halfWidth / coneLength);
|
||||||
|
|
||||||
|
// 遍历范围内的所有单元格
|
||||||
|
int radialCellCount = GenRadial.NumCellsInRadius(Props.range);
|
||||||
|
for (int i = 0; i < radialCellCount; i++)
|
||||||
|
{
|
||||||
|
IntVec3 cell = Pawn.Position + GenRadial.RadialPattern[i];
|
||||||
|
if (CanUseCell(cell) &&
|
||||||
|
Mathf.Abs(Mathf.DeltaAngle(Vector3.SignedAngle(cell.ToVector3Shifted().Yto0() - casterPos, Vector3.right, Vector3.up), targetAngle)) <= coneAngle)
|
||||||
|
{
|
||||||
|
tmpCells.Add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加从施法者到目标直线上的单元格
|
||||||
|
List<IntVec3> lineCells = GenSight.BresenhamCellsBetween(Pawn.Position, targetCell);
|
||||||
|
for (int j = 0; j < lineCells.Count; j++)
|
||||||
|
{
|
||||||
|
IntVec3 lineCell = lineCells[j];
|
||||||
|
if (!tmpCells.Contains(lineCell) && CanUseCell(lineCell))
|
||||||
|
{
|
||||||
|
tmpCells.Add(lineCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpCells;
|
||||||
|
|
||||||
|
bool CanUseCell(IntVec3 c)
|
||||||
|
{
|
||||||
|
if (!c.InBounds(Pawn.Map))
|
||||||
|
return false;
|
||||||
|
if (c == Pawn.Position && !Props.affectCaster)
|
||||||
|
return false;
|
||||||
|
if (!Props.canHitFilledCells && c.Filled(Pawn.Map))
|
||||||
|
return false;
|
||||||
|
if (!c.InHorDistOf(Pawn.Position, Props.range))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ShootLine resultingLine;
|
||||||
|
return parent.verb.TryFindShootLineFromTo(Pawn.Position, c, out resultingLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class CompProperties_AbilityAreaDestruction : CompProperties_AbilityEffect
|
||||||
|
{
|
||||||
|
public float range;
|
||||||
|
public float lineWidthEnd;
|
||||||
|
public EffecterDef effecterDef;
|
||||||
|
public bool canHitFilledCells;
|
||||||
|
|
||||||
|
// 新增:命中效果器
|
||||||
|
public EffecterDef hitEffecter;
|
||||||
|
|
||||||
|
// 新增:是否影响友方单位
|
||||||
|
public bool affectAllies = false;
|
||||||
|
|
||||||
|
// 新增:是否影响施法者自己
|
||||||
|
public bool affectCaster = false;
|
||||||
|
|
||||||
|
public CompProperties_AbilityAreaDestruction()
|
||||||
|
{
|
||||||
|
compClass = typeof(CompAbilityEffect_AreaDestruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,7 +82,7 @@ namespace WulaFallenEmpire.HarmonyPatches
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理反弹计数 - 修复:使用 Thing 的 Destroy 方法
|
// 清理反弹计数
|
||||||
[HarmonyPatch(typeof(Thing), "Destroy")]
|
[HarmonyPatch(typeof(Thing), "Destroy")]
|
||||||
public static class Thing_Destroy_Patch
|
public static class Thing_Destroy_Patch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,6 +87,200 @@ 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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
bool attackSuccess = this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
|
||||||
|
if (attackSuccess)
|
||||||
|
{
|
||||||
|
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||||
|
this.lastAttackedTarget = this.currentTarget;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 如果手动攻击失败且目标无效,清除手动目标
|
||||||
|
if (HasManualTarget && !CanAttackManualTarget)
|
||||||
|
{
|
||||||
|
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (this.burstCooldownTicksLeft > 0)
|
||||||
|
{
|
||||||
|
this.burstCooldownTicksLeft--;
|
||||||
|
}
|
||||||
|
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||||
|
{
|
||||||
|
// 如果手动目标无效,清除它
|
||||||
|
if (HasManualTarget && !CanAttackManualTarget)
|
||||||
|
{
|
||||||
|
VolleyTargetManager.ClearVolleyTarget(Pawn);
|
||||||
|
}
|
||||||
|
// 只有在没有有效的手动目标时才寻找新目标
|
||||||
|
if (!HasManualTarget || !CanAttackManualTarget)
|
||||||
|
{
|
||||||
|
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||||
|
if (this.currentTarget.IsValid)
|
||||||
|
{
|
||||||
|
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 - 只有设置目标和清除目标按钮
|
||||||
|
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_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)
|
||||||
|
{
|
||||||
|
targetStatus += $"{manualTarget.Thing?.LabelCap ?? manualTarget.Cell.ToString()}";
|
||||||
|
if (!CanAttackManualTarget)
|
||||||
|
{
|
||||||
|
targetStatus += " (Unreachable)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetStatus += "None";
|
||||||
|
}
|
||||||
|
string result = turretStatus + "\n" + targetStatus;
|
||||||
|
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 新增:炮塔启用状态
|
// 新增:炮塔启用状态
|
||||||
public bool TurretEnabled
|
public bool TurretEnabled
|
||||||
{
|
{
|
||||||
@@ -190,60 +384,6 @@ namespace WulaFallenEmpire
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CompPostTick(ref float severityAdjustment)
|
|
||||||
{
|
|
||||||
base.CompPostTick(ref severityAdjustment);
|
|
||||||
|
|
||||||
// 新增:只在启用状态下执行攻击逻辑
|
|
||||||
if (!TurretEnabled)
|
|
||||||
{
|
|
||||||
ResetCurrentTarget();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.CanShoot)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
|
||||||
if (this.currentTarget.IsValid)
|
|
||||||
{
|
|
||||||
this.burstWarmupTicksLeft = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.ResetCurrentTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetCurrentTarget()
|
private void ResetCurrentTarget()
|
||||||
{
|
{
|
||||||
this.currentTarget = LocalTargetInfo.Invalid;
|
this.currentTarget = LocalTargetInfo.Invalid;
|
||||||
@@ -273,35 +413,6 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:实现 Gizmo 接口
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:在绘制时显示状态
|
|
||||||
public override string CompTipStringExtra
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
string baseString = base.CompTipStringExtra;
|
|
||||||
string status = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
|
||||||
return string.IsNullOrEmpty(baseString) ? status : baseString + "\n" + status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int StartShootIntervalTicks = 10;
|
private const int StartShootIntervalTicks = 10;
|
||||||
|
|
||||||
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
volleyTargets[pawn] = target;
|
volleyTargets[pawn] = target;
|
||||||
volleyEnabled[pawn] = target.IsValid;
|
volleyEnabled[pawn] = target.IsValid;
|
||||||
|
|
||||||
|
Log.Message($"Set volley target for {pawn.Label}: {target.Thing?.Label ?? target.Cell.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ClearVolleyTarget(Pawn pawn)
|
public static void ClearVolleyTarget(Pawn pawn)
|
||||||
@@ -23,6 +25,8 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
volleyTargets.Remove(pawn);
|
volleyTargets.Remove(pawn);
|
||||||
volleyEnabled.Remove(pawn);
|
volleyEnabled.Remove(pawn);
|
||||||
|
|
||||||
|
Log.Message($"Cleared volley target for {pawn.Label}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LocalTargetInfo GetVolleyTarget(Pawn pawn)
|
public static LocalTargetInfo GetVolleyTarget(Pawn pawn)
|
||||||
@@ -48,11 +52,36 @@ namespace WulaFallenEmpire
|
|||||||
bool current = IsVolleyEnabled(pawn);
|
bool current = IsVolleyEnabled(pawn);
|
||||||
volleyEnabled[pawn] = !current;
|
volleyEnabled[pawn] = !current;
|
||||||
|
|
||||||
|
Log.Message($"Toggled volley for {pawn.Label}: {!current}");
|
||||||
|
|
||||||
// 如果禁用齐射,清除目标
|
// 如果禁用齐射,清除目标
|
||||||
if (!volleyEnabled[pawn])
|
if (!volleyEnabled[pawn])
|
||||||
{
|
{
|
||||||
ClearVolleyTarget(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using HarmonyLib;
|
|||||||
using RimWorld;
|
using RimWorld;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Verse;
|
using Verse;
|
||||||
@@ -11,11 +12,12 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
public class CompProperties_ApparelInterceptor : CompProperties
|
public class CompProperties_ApparelInterceptor : CompProperties
|
||||||
{
|
{
|
||||||
public float radius = 3f; // 仅用于视觉效果
|
public float radius = 3f;
|
||||||
public int startupDelay = 0;
|
public int startupDelay = 0;
|
||||||
public int rechargeDelay = 3200;
|
public int rechargeDelay = 3200;
|
||||||
public int hitPoints = 100;
|
public int hitPoints = 100;
|
||||||
public int maxBounces = 3;
|
public int maxBounces = 3;
|
||||||
|
public float bounceRange = 15f; // 反弹射程
|
||||||
|
|
||||||
public bool interceptGroundProjectiles = false;
|
public bool interceptGroundProjectiles = false;
|
||||||
public bool interceptNonHostileProjectiles = false;
|
public bool interceptNonHostileProjectiles = false;
|
||||||
@@ -78,6 +80,267 @@ namespace WulaFallenEmpire
|
|||||||
public CompProperties_ApparelInterceptor Props => (CompProperties_ApparelInterceptor)props;
|
public CompProperties_ApparelInterceptor Props => (CompProperties_ApparelInterceptor)props;
|
||||||
public Pawn PawnOwner => (parent as Apparel)?.Wearer;
|
public Pawn PawnOwner => (parent as Apparel)?.Wearer;
|
||||||
|
|
||||||
|
|
||||||
|
private void BounceProjectileNew(Projectile originalProjectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (originalProjectile == null || originalProjectile.Destroyed)
|
||||||
|
return;
|
||||||
|
// 计算反弹方向 - 朝穿戴者前方发射
|
||||||
|
Vector3 bounceDirection = CalculateForwardBounceDirection();
|
||||||
|
|
||||||
|
// 计算新目标位置
|
||||||
|
Vector3 newDestination = PawnOwner.Position.ToVector3Shifted() + bounceDirection * Props.bounceRange;
|
||||||
|
IntVec3 targetCell = newDestination.ToIntVec3();
|
||||||
|
// 创建新的抛射体
|
||||||
|
Projectile newProjectile = (Projectile)ThingMaker.MakeThing(originalProjectile.def, null);
|
||||||
|
|
||||||
|
// 使用 Traverse 复制字段
|
||||||
|
CopyProjectileFieldsUsingTraverse(newProjectile, originalProjectile);
|
||||||
|
// 生成新抛射体
|
||||||
|
GenSpawn.Spawn(newProjectile, PawnOwner.Position, PawnOwner.Map);
|
||||||
|
|
||||||
|
// 使用 Traverse 调用 Launch 方法
|
||||||
|
LaunchProjectileUsingTraverse(newProjectile, targetCell, originalProjectile);
|
||||||
|
// 销毁原抛射体
|
||||||
|
originalProjectile.Destroy(DestroyMode.Vanish);
|
||||||
|
// 播放反弹效果
|
||||||
|
PlayBounceEffect(originalProjectile);
|
||||||
|
Log.Message($"[Interceptor] Projectile bounced forward to {targetCell}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Error in BounceProjectileNew: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用 Traverse 复制字段
|
||||||
|
private void CopyProjectileFieldsUsingTraverse(Projectile newProjectile, Projectile originalProjectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Traverse newTraverse = Traverse.Create(newProjectile);
|
||||||
|
Traverse originalTraverse = Traverse.Create(originalProjectile);
|
||||||
|
// 复制所有重要字段
|
||||||
|
newTraverse.Field("launcher").SetValue(originalTraverse.Field("launcher").GetValue());
|
||||||
|
newTraverse.Field("equipment").SetValue(originalTraverse.Field("equipment").GetValue());
|
||||||
|
newTraverse.Field("equipmentDef").SetValue(originalTraverse.Field("equipmentDef").GetValue());
|
||||||
|
newTraverse.Field("damageDefOverride").SetValue(originalTraverse.Field("damageDefOverride").GetValue());
|
||||||
|
newTraverse.Field("targetCoverDef").SetValue(originalTraverse.Field("targetCoverDef").GetValue());
|
||||||
|
|
||||||
|
// 复制额外伤害列表
|
||||||
|
List<ExtraDamage> originalExtraDamages = originalTraverse.Field("extraDamages").GetValue<List<ExtraDamage>>();
|
||||||
|
if (originalExtraDamages != null)
|
||||||
|
{
|
||||||
|
newTraverse.Field("extraDamages").SetValue(new List<ExtraDamage>(originalExtraDamages));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"Error copying projectile fields with Traverse: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用 Traverse 调用 Launch 方法
|
||||||
|
private void LaunchProjectileUsingTraverse(Projectile projectile, IntVec3 targetCell, Projectile originalProjectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Traverse projectileTraverse = Traverse.Create(projectile);
|
||||||
|
Traverse originalTraverse = Traverse.Create(originalProjectile);
|
||||||
|
// 获取 Launch 方法
|
||||||
|
var launchMethod = projectileTraverse.Method("Launch", new object[]
|
||||||
|
{
|
||||||
|
PawnOwner, // 发射者
|
||||||
|
PawnOwner.Position.ToVector3Shifted(), // 发射位置
|
||||||
|
new LocalTargetInfo(targetCell), // 目标位置
|
||||||
|
new LocalTargetInfo(targetCell), // 预期目标
|
||||||
|
originalProjectile.HitFlags, // 命中标志
|
||||||
|
originalTraverse.Field("preventFriendlyFire").GetValue<bool>(), // 防止友军伤害
|
||||||
|
originalTraverse.Field("equipment").GetValue<Thing>(), // 装备
|
||||||
|
originalTraverse.Field("targetCoverDef").GetValue<ThingDef>() // 目标覆盖定义
|
||||||
|
});
|
||||||
|
if (launchMethod.MethodExists())
|
||||||
|
{
|
||||||
|
launchMethod.GetValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error("Launch method not found using Traverse");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Error launching projectile with Traverse: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用反射设置抛射体字段
|
||||||
|
private void SetProjectileFields(Projectile newProjectile, Projectile originalProjectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 获取 Projectile 类型
|
||||||
|
Type projectileType = typeof(Projectile);
|
||||||
|
|
||||||
|
// 设置发射者
|
||||||
|
FieldInfo launcherField = projectileType.GetField("launcher", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
launcherField?.SetValue(newProjectile, originalProjectile.Launcher);
|
||||||
|
|
||||||
|
// 设置装备
|
||||||
|
FieldInfo equipmentField = projectileType.GetField("equipment", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
equipmentField?.SetValue(newProjectile, GetEquipment(originalProjectile));
|
||||||
|
|
||||||
|
// 设置装备定义
|
||||||
|
FieldInfo equipmentDefField = projectileType.GetField("equipmentDef", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
equipmentDefField?.SetValue(newProjectile, GetEquipmentDef(originalProjectile));
|
||||||
|
|
||||||
|
// 设置伤害定义覆盖
|
||||||
|
FieldInfo damageDefOverrideField = projectileType.GetField("damageDefOverride", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
damageDefOverrideField?.SetValue(newProjectile, GetDamageDefOverride(originalProjectile));
|
||||||
|
|
||||||
|
// 设置额外伤害
|
||||||
|
FieldInfo extraDamagesField = projectileType.GetField("extraDamages", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (extraDamagesField != null)
|
||||||
|
{
|
||||||
|
List<ExtraDamage> originalExtraDamages = (List<ExtraDamage>)extraDamagesField.GetValue(originalProjectile);
|
||||||
|
if (originalExtraDamages != null)
|
||||||
|
{
|
||||||
|
List<ExtraDamage> newExtraDamages = new List<ExtraDamage>(originalExtraDamages);
|
||||||
|
extraDamagesField.SetValue(newProjectile, newExtraDamages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置目标覆盖定义
|
||||||
|
FieldInfo targetCoverDefField = projectileType.GetField("targetCoverDef", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
targetCoverDefField?.SetValue(newProjectile, GetTargetCoverDef(originalProjectile));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning($"Error setting projectile fields: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用反射调用 Launch 方法
|
||||||
|
private void LaunchProjectile(Projectile projectile, IntVec3 targetCell, Projectile originalProjectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Type projectileType = typeof(Projectile);
|
||||||
|
|
||||||
|
// 获取 Launch 方法
|
||||||
|
MethodInfo launchMethod = projectileType.GetMethod("Launch", new Type[]
|
||||||
|
{
|
||||||
|
typeof(Thing),
|
||||||
|
typeof(Vector3),
|
||||||
|
typeof(LocalTargetInfo),
|
||||||
|
typeof(LocalTargetInfo),
|
||||||
|
typeof(ProjectileHitFlags),
|
||||||
|
typeof(bool),
|
||||||
|
typeof(Thing),
|
||||||
|
typeof(ThingDef)
|
||||||
|
});
|
||||||
|
if (launchMethod != null)
|
||||||
|
{
|
||||||
|
// 获取原抛射体的命中标志
|
||||||
|
ProjectileHitFlags hitFlags = GetHitFlags(originalProjectile);
|
||||||
|
|
||||||
|
// 获取防止友军伤害设置
|
||||||
|
bool preventFriendlyFire = GetPreventFriendlyFire(originalProjectile);
|
||||||
|
|
||||||
|
// 调用 Launch 方法
|
||||||
|
launchMethod.Invoke(projectile, new object[]
|
||||||
|
{
|
||||||
|
PawnOwner, // 发射者改为护盾穿戴者
|
||||||
|
PawnOwner.Position.ToVector3Shifted(), // 发射位置
|
||||||
|
new LocalTargetInfo(targetCell), // 目标位置
|
||||||
|
new LocalTargetInfo(targetCell), // 预期目标
|
||||||
|
hitFlags,
|
||||||
|
preventFriendlyFire,
|
||||||
|
GetEquipment(originalProjectile), // 装备
|
||||||
|
GetTargetCoverDef(originalProjectile) // 目标覆盖定义
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error("Could not find Launch method on Projectile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Error launching projectile: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用反射获取私有字段值
|
||||||
|
private Thing GetEquipment(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo equipmentField = typeof(Projectile).GetField("equipment", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
return (Thing)equipmentField?.GetValue(projectile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ThingDef GetEquipmentDef(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo equipmentDefField = typeof(Projectile).GetField("equipmentDef", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
return (ThingDef)equipmentDefField?.GetValue(projectile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private DamageDef GetDamageDefOverride(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo damageDefOverrideField = typeof(Projectile).GetField("damageDefOverride", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
return (DamageDef)damageDefOverrideField?.GetValue(projectile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ThingDef GetTargetCoverDef(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo targetCoverDefField = typeof(Projectile).GetField("targetCoverDef", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
return (ThingDef)targetCoverDefField?.GetValue(projectile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ProjectileHitFlags GetHitFlags(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return projectile.HitFlags;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return ProjectileHitFlags.All;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool GetPreventFriendlyFire(Projectile projectile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo preventFriendlyFireField = typeof(Projectile).GetField("preventFriendlyFire", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
return preventFriendlyFireField != null && (bool)preventFriendlyFireField.GetValue(projectile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 主要拦截方法
|
// 主要拦截方法
|
||||||
public bool TryInterceptProjectile(Projectile projectile, Thing hitThing)
|
public bool TryInterceptProjectile(Projectile projectile, Thing hitThing)
|
||||||
{
|
{
|
||||||
@@ -156,8 +419,8 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 反弹抛射体
|
// 反弹抛射体 - 新方法:销毁原抛射体并创建新的
|
||||||
BounceProjectile(projectile);
|
BounceProjectileNew(projectile);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -165,61 +428,17 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BounceProjectile(Projectile projectile)
|
private Vector3 CalculateForwardBounceDirection()
|
||||||
{
|
{
|
||||||
try
|
// 获取穿戴者的朝向
|
||||||
{
|
float pawnRotation = PawnOwner.Rotation.AsAngle;
|
||||||
if (projectile == null || projectile.Destroyed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 计算反弹方向
|
// 添加一些随机偏移,使反弹更自然
|
||||||
Vector3 bounceDirection = CalculateBounceDirection(projectile);
|
float randomOffset = Rand.Range(-30f, 30f);
|
||||||
|
float finalAngle = pawnRotation + randomOffset;
|
||||||
|
|
||||||
// 使用 Traverse 修改抛射体方向
|
// 转换为方向向量
|
||||||
var traverse = Traverse.Create(projectile);
|
return Quaternion.AngleAxis(finalAngle, Vector3.up) * Vector3.forward;
|
||||||
|
|
||||||
// 修改目的地 - 随机弹射
|
|
||||||
Vector3 newDestination = projectile.ExactPosition + bounceDirection * 30f;
|
|
||||||
traverse.Field("destination").SetValue(newDestination);
|
|
||||||
|
|
||||||
// 重置起点为当前位置
|
|
||||||
traverse.Field("origin").SetValue(projectile.ExactPosition);
|
|
||||||
|
|
||||||
// 重新计算飞行时间
|
|
||||||
float distance = (newDestination - projectile.ExactPosition).MagnitudeHorizontal();
|
|
||||||
int newTicks = Mathf.CeilToInt(distance / projectile.def.projectile.SpeedTilesPerTick);
|
|
||||||
traverse.Field("ticksToImpact").SetValue(newTicks);
|
|
||||||
|
|
||||||
// 播放反弹效果
|
|
||||||
PlayBounceEffect(projectile);
|
|
||||||
|
|
||||||
Log.Message($"[Interceptor] Projectile bounced towards {bounceDirection}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error($"Error in BounceProjectile: {ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 CalculateBounceDirection(Projectile projectile)
|
|
||||||
{
|
|
||||||
// 如果有发射者,尝试弹向发射者
|
|
||||||
if (projectile.Launcher != null && projectile.Launcher.Spawned)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Vector3 toLauncher = (projectile.Launcher.Position.ToVector3() - projectile.ExactPosition).normalized;
|
|
||||||
return toLauncher;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 如果计算失败,使用随机方向
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 随机弹射方向
|
|
||||||
float angle = Rand.Range(0f, 360f);
|
|
||||||
return Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayBounceEffect(Projectile projectile)
|
private void PlayBounceEffect(Projectile projectile)
|
||||||
|
|||||||
Reference in New Issue
Block a user