环形轰炸,区域监视,光矛扫射
This commit is contained in:
Binary file not shown.
@@ -635,11 +635,10 @@
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
|
||||
<!-- 炮击 -->
|
||||
<AbilityDef>
|
||||
<defName>WULA_Firepower_Minigun_Strafe</defName>
|
||||
<label>链炮扫射</label>
|
||||
<description>以战舰上的自动链炮对目标区域进行可选方向的扫射,射击速度和冷却都很快,但是威力欠佳。</description>
|
||||
<description>以战舰上的自动链炮对目标区域进行可选方向的扫射,射击速度和冷却都很快,对轻甲目标有效,但是威力欠佳。</description>
|
||||
<!-- <iconPath>ArachnaeSwarm/UI/Abilities/ARA_Spawn_ARA_HiveCorvette_Rocket</iconPath> -->
|
||||
<cooldownTicksRange>1</cooldownTicksRange>
|
||||
<hotKey>Misc12</hotKey>
|
||||
@@ -682,4 +681,127 @@
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
<AbilityDef>
|
||||
<defName>WULA_Firepower_Cannon_Salvo</defName>
|
||||
<label>轻型舰炮齐射</label>
|
||||
<description>指挥战舰侧弦的副炮,进行一轮共计12发炮弹的齐射,造成中规中矩的毁伤。</description>
|
||||
<!-- <iconPath>ArachnaeSwarm/UI/Abilities/ARA_Spawn_ARA_HiveCorvette_Rocket</iconPath> -->
|
||||
<cooldownTicksRange>1</cooldownTicksRange>
|
||||
<hotKey>Misc12</hotKey>
|
||||
<casterMustBeCapableOfViolence>false</casterMustBeCapableOfViolence>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<drawAimPie>false</drawAimPie>
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
<warmupTime>1</warmupTime>
|
||||
<range>120</range>
|
||||
<targetable>true</targetable>
|
||||
<targetParams>
|
||||
<canTargetSelf>false</canTargetSelf>
|
||||
<canTargetLocations>true</canTargetLocations>
|
||||
</targetParams>
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_GlobalFlyOverCooldown">
|
||||
<globalCooldownTicks>1000</globalCooldownTicks>
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AbilityCircularBombardment">
|
||||
<radius>10</radius>
|
||||
<simultaneousLaunches>3</simultaneousLaunches>
|
||||
<launchIntervalTicks>90</launchIntervalTicks>
|
||||
<maxLaunches>12</maxLaunches>
|
||||
<warmupTicks>120</warmupTicks>
|
||||
|
||||
<!-- 独立间隔模式:组内依次发射 -->
|
||||
<useIndependentIntervals>true</useIndependentIntervals>
|
||||
<innerLaunchIntervalTicks>5</innerLaunchIntervalTicks>
|
||||
|
||||
<skyfallerDef>WULA_Firepower_Cannon_Salvo_Skyfaller</skyfallerDef>
|
||||
<showBombardmentArea>true</showBombardmentArea>
|
||||
<showImpactPreview>false</showImpactPreview>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_RequireFlyOverFacility">
|
||||
<!-- <flyOverDef></flyOverDef> -->
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
<facilityNotFoundMessage>需要拥有<color=#BD2F31><i>武器阵列</i></color>设施的战舰在地图上才能进行轨道炮击支援</facilityNotFoundMessage>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
<AbilityDef>
|
||||
<defName>WULA_Firepower_Cannon_Surveillance</defName>
|
||||
<label>轻型舰炮监视</label>
|
||||
<description>指挥战舰侧弦的副炮,监视一个区域30秒,对任何进入范围的敌对目标进行炮击。</description>
|
||||
<!-- <iconPath>ArachnaeSwarm/UI/Abilities/ARA_Spawn_ARA_HiveCorvette_Rocket</iconPath> -->
|
||||
<cooldownTicksRange>1</cooldownTicksRange>
|
||||
<hotKey>Misc12</hotKey>
|
||||
<casterMustBeCapableOfViolence>false</casterMustBeCapableOfViolence>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<drawAimPie>false</drawAimPie>
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
<warmupTime>1</warmupTime>
|
||||
<range>120</range>
|
||||
<targetable>true</targetable>
|
||||
<targetParams>
|
||||
<canTargetSelf>false</canTargetSelf>
|
||||
<canTargetLocations>true</canTargetLocations>
|
||||
</targetParams>
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_GlobalFlyOverCooldown">
|
||||
<globalCooldownTicks>1000</globalCooldownTicks>
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_AbilityCallSkyfaller">
|
||||
<delayTicks>180</delayTicks>
|
||||
<skyfallerDef>WULA_Firepower_Cannon_Surveillance_Skyfaller</skyfallerDef>
|
||||
<previewRadius>12</previewRadius>
|
||||
<previewColor>(0.85,0.85,0.3,0.5)</previewColor>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_RequireFlyOverFacility">
|
||||
<!-- <flyOverDef></flyOverDef> -->
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
<facilityNotFoundMessage>需要拥有<color=#BD2F31><i>武器阵列</i></color>设施的战舰在地图上才能进行轨道炮击支援</facilityNotFoundMessage>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
<AbilityDef>
|
||||
<defName>WULA_Firepower_EnergyLance_Strafe</defName>
|
||||
<label>光矛扫射</label>
|
||||
<description>指挥战舰侧弦的光矛阵列,发射一道光矛横扫战场,造成大量的热能伤害。</description>
|
||||
<!-- <iconPath>ArachnaeSwarm/UI/Abilities/ARA_Spawn_ARA_HiveCorvette_Rocket</iconPath> -->
|
||||
<cooldownTicksRange>1</cooldownTicksRange>
|
||||
<hotKey>Misc12</hotKey>
|
||||
<casterMustBeCapableOfViolence>false</casterMustBeCapableOfViolence>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<drawAimPie>false</drawAimPie>
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
<warmupTime>1</warmupTime>
|
||||
<range>120</range>
|
||||
<targetable>true</targetable>
|
||||
<targetParams>
|
||||
<canTargetSelf>false</canTargetSelf>
|
||||
<canTargetLocations>true</canTargetLocations>
|
||||
</targetParams>
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_GlobalFlyOverCooldown">
|
||||
<globalCooldownTicks>1000</globalCooldownTicks>
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_EnergyLance">
|
||||
<durationTicks>600</durationTicks>
|
||||
<moveDistance>20</moveDistance>
|
||||
<useFixedDistance>true</useFixedDistance>
|
||||
<firesPerTick>3</firesPerTick>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_RequireFlyOverFacility">
|
||||
<!-- <flyOverDef></flyOverDef> -->
|
||||
<requiredFacility>BombardmentFacility</requiredFacility>
|
||||
<facilityNotFoundMessage>需要拥有<color=#BD2F31><i>武器阵列</i></color>设施的战舰在地图上才能进行轨道炮击支援</facilityNotFoundMessage>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
</Defs>
|
||||
@@ -74,6 +74,7 @@
|
||||
<li Class="HediffCompProperties_GiveAbility">
|
||||
<abilityDefs>
|
||||
<li>WULA_Firepower_Minigun_Strafe</li>
|
||||
<li>WULA_Firepower_Cannon_Salvo</li>
|
||||
</abilityDefs>
|
||||
</li>
|
||||
</comps>
|
||||
|
||||
@@ -749,4 +749,187 @@
|
||||
<defaultDamage>25</defaultDamage>
|
||||
<soundExplosion>Explosion_Bomb</soundExplosion>
|
||||
</DamageDef>
|
||||
<ThingDef ParentName="SkyfallerBase">
|
||||
<defName>WULA_Firepower_Cannon_Salvo_Skyfaller</defName>
|
||||
<label>轻型副炮炮弹</label>
|
||||
<size>(1, 1)</size>
|
||||
<graphicData>
|
||||
<texPath>Wula/Projectile/WULA_Bullet_ChargeLanceShot_Red</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
<shaderType>MoteGlow</shaderType>
|
||||
<drawSize>8</drawSize>
|
||||
</graphicData>
|
||||
<skyfaller>
|
||||
<movementType>Accelerate</movementType>
|
||||
<shadow>Things/Skyfaller/SkyfallerShadowDropPod</shadow>
|
||||
<shadowSize>(1, 1)</shadowSize>
|
||||
<anticipationSound>DropPod_Fall</anticipationSound>
|
||||
<anticipationSoundTicks>100</anticipationSoundTicks>
|
||||
<impactSound>Explosion_Bomb</impactSound>
|
||||
<moteSpawnTime>0.05</moteSpawnTime>
|
||||
<motesPerCell>1</motesPerCell>
|
||||
<cameraShake>1</cameraShake>
|
||||
<rotationCurve>
|
||||
<points>
|
||||
<li>(0,180)</li>
|
||||
<li>(1, 181)</li>
|
||||
</points>
|
||||
</rotationCurve>
|
||||
<angleCurve>
|
||||
<points>
|
||||
<li>(0,0)</li>
|
||||
<li>(1, 1)</li>
|
||||
</points>
|
||||
</angleCurve>
|
||||
<explosionRadius>6</explosionRadius>
|
||||
<explosionDamage>Bomb</explosionDamage>
|
||||
<explosionDamageFactor>0.85</explosionDamageFactor>
|
||||
</skyfaller>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="SkyfallerBase">
|
||||
<defName>WULA_Firepower_Cannon_Surveillance_Skyfaller</defName>
|
||||
<label>舰炮监视信标</label>
|
||||
<size>(1,1)</size>
|
||||
<graphicData>
|
||||
<texPath>Wula/Building/WULA_WeaponArmor_Productor_Incoming</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
<shaderType>CutoutFlying</shaderType>
|
||||
<drawSize>(1,1)</drawSize>
|
||||
</graphicData>
|
||||
<skyfaller>
|
||||
<movementType>Accelerate</movementType>
|
||||
<shadow>Things/Skyfaller/SkyfallerShadowDropPod</shadow>
|
||||
<shadowSize>(5, 5)</shadowSize>
|
||||
<anticipationSound>DropPod_Fall</anticipationSound>
|
||||
<anticipationSoundTicks>100</anticipationSoundTicks>
|
||||
<impactSound>Explosion_Vaporize</impactSound>
|
||||
<moteSpawnTime>0.05</moteSpawnTime>
|
||||
<motesPerCell>1</motesPerCell>
|
||||
<cameraShake>1</cameraShake>
|
||||
<angleCurve>
|
||||
<points>
|
||||
<li>(0,0)</li>
|
||||
<li>(1, 1)</li>
|
||||
</points>
|
||||
</angleCurve>
|
||||
<spawnThing>WULA_Firepower_Cannon_Surveillance_Beacon</spawnThing>
|
||||
</skyfaller>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="BuildingBase">
|
||||
<defName>WULA_Firepower_Cannon_Surveillance_Beacon</defName>
|
||||
<label>舰炮监视信标</label>
|
||||
<description>一枚标定监视区的轰炸信标,进入此处的敌对势力都会被乌拉帝国舰队的舰炮狠狠打击。</description>
|
||||
<uiIconPath>Wula/Building/WULA_WeaponArmor_Productor</uiIconPath>
|
||||
<tickerType>Normal</tickerType>
|
||||
<selectable>true</selectable>
|
||||
<useHitPoints>false</useHitPoints>
|
||||
<size>(1,1)</size>
|
||||
<graphicData>
|
||||
<texPath>Wula/Building/WULA_Dropping_Building_Cleanzone</texPath>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<drawSize>(3,3)</drawSize>
|
||||
<damageData>
|
||||
<enabled>false</enabled>
|
||||
</damageData>
|
||||
</graphicData>
|
||||
<altitudeLayer>Building</altitudeLayer>
|
||||
<passability>Standable</passability>
|
||||
<castEdgeShadows>false</castEdgeShadows>
|
||||
<fillPercent>0.5</fillPercent>
|
||||
<canOverlapZones>false</canOverlapZones>
|
||||
<pathCost>0</pathCost>
|
||||
<hasInteractionCell>false</hasInteractionCell>
|
||||
<rotatable>false</rotatable>
|
||||
<statBases>
|
||||
<WorkToBuild>1</WorkToBuild>
|
||||
<Mass>0</Mass>
|
||||
<Flammability>0</Flammability>
|
||||
</statBases>
|
||||
<resourcesFractionWhenDeconstructed>0</resourcesFractionWhenDeconstructed>
|
||||
<building>
|
||||
<destroySound>BuildingDestroyed_Metal_Small</destroySound>
|
||||
</building>
|
||||
<comps>
|
||||
<li Class="CompProperties_OrbitalBeam">
|
||||
<width>0.2</width>
|
||||
<color>(255, 20, 20, 242)</color>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_CustomRadius">
|
||||
<radius>12</radius> <!-- 半径大小 -->
|
||||
<color>(1, 1, 1)</color> <!-- 绿色圆圈 -->
|
||||
<radiusOffset>0</radiusOffset> <!-- 半径偏移 -->
|
||||
<showInGUI>true</showInGUI>
|
||||
<label>射程</label>
|
||||
<description>该信标所标定的舰炮监视范围,进入其中的敌军将遭到炮击</description>
|
||||
<defaultVisible>true</defaultVisible>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_BuildingBombardment">
|
||||
<radius>12</radius>
|
||||
<targetEnemies>true</targetEnemies>
|
||||
<targetNeutrals>false</targetNeutrals>
|
||||
<targetAnimals>false</targetAnimals>
|
||||
<burstCount>3</burstCount>
|
||||
<innerBurstIntervalTicks>15</innerBurstIntervalTicks>
|
||||
<burstIntervalTicks>120</burstIntervalTicks>
|
||||
<randomOffset>3</randomOffset>
|
||||
<skyfallerDef>WULA_Firepower_Cannon_Salvo_Skyfaller</skyfallerDef>
|
||||
</li>
|
||||
<li Class="WulaFallenEmpire.CompProperties_FactionSetter">
|
||||
<!-- <factionDef>Mechanoid</factionDef> 不写默认玩家派系-->
|
||||
<usePlayerFactionIfNull>true</usePlayerFactionIfNull>
|
||||
<overrideExistingFaction>false</overrideExistingFaction>
|
||||
</li>
|
||||
<li Class="CompProperties_Lifespan">
|
||||
<lifespanTicks>3600</lifespanTicks>
|
||||
</li>
|
||||
<li Class="CompProperties_Glower">
|
||||
<glowRadius>6</glowRadius>
|
||||
<glowColor>(0.85,0.85,0.3,0.5)</glowColor>
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="OrbitalStrikeBase">
|
||||
<defName>WULA_EnergyLance_Base</defName>
|
||||
<label>power beam</label>
|
||||
<thingClass>PowerBeam</thingClass>
|
||||
</ThingDef>
|
||||
<ThingDef ParentName="OrbitalStrikeBase">
|
||||
<defName>WULA_EnergyLance_Base</defName>
|
||||
<label>energy lance</label>
|
||||
<thingClass>WulaFallenEmpire.EnergyLance</thingClass>
|
||||
<modExtensions>
|
||||
<li Class="WulaFallenEmpire.EnergyLanceExtension">
|
||||
<damageDef>Flame</damageDef>
|
||||
<igniteFires>true</igniteFires>
|
||||
<fireIgniteChance>0.9</fireIgniteChance>
|
||||
<damageAmountRange>
|
||||
<min>60</min>
|
||||
<max>95</max>
|
||||
</damageAmountRange>
|
||||
<corpseDamageAmountRange>
|
||||
<min>5</min>
|
||||
<max>10</max>
|
||||
</corpseDamageAmountRange>
|
||||
</li>
|
||||
</modExtensions>
|
||||
<comps>
|
||||
<!-- 使用原版的轨道光束组件 -->
|
||||
<li Class="CompProperties_OrbitalBeam">
|
||||
<width>6</width>
|
||||
<color>(255, 200, 50, 242)</color>
|
||||
<sound>OrbitalBeam</sound>
|
||||
</li>
|
||||
<li Class="CompProperties_CameraShaker">
|
||||
<mag>0.02</mag>
|
||||
</li>
|
||||
<li Class="CompProperties_AffectsSky">
|
||||
<skyColors>
|
||||
<sky>(255, 220, 180)</sky>
|
||||
<shadow>(220, 200, 160)</shadow>
|
||||
<overlay>(255, 240, 200)</overlay>
|
||||
<saturation>1.2</saturation>
|
||||
</skyColors>
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
</Defs>
|
||||
@@ -0,0 +1,531 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompAbilityEffect_CircularBombardment : CompAbilityEffect
|
||||
{
|
||||
public new CompProperties_AbilityCircularBombardment Props => (CompProperties_AbilityCircularBombardment)props;
|
||||
|
||||
// 轰炸状态
|
||||
private CircularBombardmentState currentState = CircularBombardmentState.Idle;
|
||||
private List<IntVec3> targetCells = new List<IntVec3>();
|
||||
private List<IntVec3> remainingTargets = new List<IntVec3>();
|
||||
private IntVec3 bombardmentCenter;
|
||||
private int warmupTicksRemaining = 0;
|
||||
private int nextLaunchTick = 0;
|
||||
private int launchesCompleted = 0;
|
||||
|
||||
// 组内间隔状态
|
||||
private List<IntVec3> currentGroupTargets = new List<IntVec3>();
|
||||
private int currentGroupIndex = 0;
|
||||
private int nextInnerLaunchTick = 0;
|
||||
private bool isInGroupLaunch = false;
|
||||
|
||||
// 预览状态
|
||||
private List<IntVec3> currentPreviewCells = new List<IntVec3>();
|
||||
private List<IntVec3> impactPreviewCells = new List<IntVec3>();
|
||||
|
||||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||
{
|
||||
base.Apply(target, dest);
|
||||
|
||||
if (parent.pawn == null || parent.pawn.Map == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Log.Message($"[CircularBombardment] Starting circular bombardment at {target.Cell}");
|
||||
|
||||
// 设置轰炸中心
|
||||
bombardmentCenter = target.Cell;
|
||||
|
||||
// 选择目标格子
|
||||
SelectTargetCells();
|
||||
|
||||
// 初始化剩余目标列表
|
||||
remainingTargets = new List<IntVec3>(targetCells);
|
||||
|
||||
// 开始前摇
|
||||
StartWarmup();
|
||||
|
||||
Log.Message($"[CircularBombardment] Bombardment initialized: {targetCells.Count} targets, " +
|
||||
$"{Props.simultaneousLaunches} simultaneous launches, " +
|
||||
$"independent intervals: {Props.useIndependentIntervals}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[CircularBombardment] Error starting bombardment: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制预览效果
|
||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||
{
|
||||
base.DrawEffectPreview(target);
|
||||
|
||||
if (!Props.showBombardmentArea || parent.pawn == null || parent.pawn.Map == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 计算预览区域
|
||||
CalculatePreviewArea(target.Cell);
|
||||
|
||||
// 绘制轰炸区域预览
|
||||
DrawCircularAreaPreview(target.Cell);
|
||||
|
||||
// 绘制预计落点预览
|
||||
if (Props.showImpactPreview)
|
||||
{
|
||||
DrawImpactPreview(target.Cell);
|
||||
}
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// 忽略预览绘制错误
|
||||
}
|
||||
}
|
||||
|
||||
// 计算预览区域
|
||||
private void CalculatePreviewArea(IntVec3 center)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
currentPreviewCells.Clear();
|
||||
impactPreviewCells.Clear();
|
||||
|
||||
// 计算圆形区域内的所有单元格
|
||||
currentPreviewCells = GenRadial.RadialCellsAround(center, Props.radius, true).ToList();
|
||||
|
||||
// 随机选择一些单元格作为预计落点预览
|
||||
var potentialTargets = currentPreviewCells
|
||||
.Where(cell => cell.InBounds(map))
|
||||
.Where(cell => IsValidTargetCell(cell, map))
|
||||
.ToList();
|
||||
|
||||
// 随机选择预览目标
|
||||
int previewCount = Mathf.Min(Props.maxTargets, potentialTargets.Count);
|
||||
impactPreviewCells = potentialTargets
|
||||
.InRandomOrder()
|
||||
.Take(previewCount)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// 绘制圆形区域预览
|
||||
private void DrawCircularAreaPreview(IntVec3 center)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 绘制圆形区域边界
|
||||
foreach (var cell in currentPreviewCells)
|
||||
{
|
||||
if (cell.InBounds(map))
|
||||
{
|
||||
GenDraw.DrawFieldEdges(new List<IntVec3> { cell }, Props.areaPreviewColor, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制圆形边界线
|
||||
DrawCircularBoundary(center);
|
||||
}
|
||||
|
||||
// 绘制圆形边界
|
||||
private void DrawCircularBoundary(IntVec3 center)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 绘制圆形边界(使用多个线段近似)
|
||||
int segments = 36; // 36段,每段10度
|
||||
float angleStep = 360f / segments;
|
||||
|
||||
Vector3 centerPos = center.ToVector3Shifted();
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
float angle1 = i * angleStep * Mathf.Deg2Rad;
|
||||
float angle2 = (i + 1) * angleStep * Mathf.Deg2Rad;
|
||||
|
||||
Vector3 point1 = centerPos + new Vector3(
|
||||
Mathf.Cos(angle1) * Props.radius,
|
||||
0,
|
||||
Mathf.Sin(angle1) * Props.radius
|
||||
);
|
||||
|
||||
Vector3 point2 = centerPos + new Vector3(
|
||||
Mathf.Cos(angle2) * Props.radius,
|
||||
0,
|
||||
Mathf.Sin(angle2) * Props.radius
|
||||
);
|
||||
|
||||
GenDraw.DrawLineBetween(point1, point2, SimpleColor.Orange, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制预计落点预览
|
||||
private void DrawImpactPreview(IntVec3 center)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
foreach (var cell in impactPreviewCells)
|
||||
{
|
||||
if (cell.InBounds(map))
|
||||
{
|
||||
// 绘制落点标记
|
||||
GenDraw.DrawTargetHighlight(cell);
|
||||
|
||||
// 绘制落点范围指示
|
||||
GenDraw.DrawRadiusRing(cell, 1f, Color.red, (c) => true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择目标格子
|
||||
private void SelectTargetCells()
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 获取圆形区域内的所有单元格
|
||||
var areaCells = GenRadial.RadialCellsAround(bombardmentCenter, Props.radius, true)
|
||||
.Where(cell => cell.InBounds(map))
|
||||
.Where(cell => IsValidTargetCell(cell, map))
|
||||
.ToList();
|
||||
|
||||
var selectedCells = new List<IntVec3>();
|
||||
var missedCells = new List<IntVec3>();
|
||||
|
||||
// 根据概率选择目标格子
|
||||
foreach (var cell in areaCells)
|
||||
{
|
||||
if (Rand.Value <= Props.targetSelectionChance)
|
||||
{
|
||||
selectedCells.Add(cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
missedCells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用最小/最大限制
|
||||
if (selectedCells.Count < Props.minTargets)
|
||||
{
|
||||
// 补充不足的格子
|
||||
int needed = Props.minTargets - selectedCells.Count;
|
||||
if (missedCells.Count > 0)
|
||||
{
|
||||
selectedCells.AddRange(missedCells.InRandomOrder().Take(Mathf.Min(needed, missedCells.Count)));
|
||||
}
|
||||
}
|
||||
else if (selectedCells.Count > Props.maxTargets)
|
||||
{
|
||||
// 随机移除多余的格子
|
||||
selectedCells = selectedCells.InRandomOrder().Take(Props.maxTargets).ToList();
|
||||
}
|
||||
|
||||
targetCells = selectedCells;
|
||||
Log.Message($"[CircularBombardment] Selected {targetCells.Count} target cells from {areaCells.Count} area cells");
|
||||
}
|
||||
|
||||
// 检查单元格是否有效
|
||||
private bool IsValidTargetCell(IntVec3 cell, Map map)
|
||||
{
|
||||
// 检查最小距离
|
||||
if (Props.minDistanceFromCenter > 0)
|
||||
{
|
||||
float distance = Vector3.Distance(cell.ToVector3(), bombardmentCenter.ToVector3());
|
||||
if (distance < Props.minDistanceFromCenter)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查友军误伤
|
||||
if (Props.avoidFriendlyFire)
|
||||
{
|
||||
var pawnsInCell = map.thingGrid.ThingsListAt(cell).OfType<Pawn>();
|
||||
foreach (var pawn in pawnsInCell)
|
||||
{
|
||||
if (pawn.Faction != null && pawn.Faction == parent.pawn.Faction)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查建筑物
|
||||
if (Props.avoidBuildings)
|
||||
{
|
||||
var buildingsInCell = map.thingGrid.ThingsListAt(cell).OfType<Building>();
|
||||
if (buildingsInCell.Any())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void StartWarmup()
|
||||
{
|
||||
currentState = CircularBombardmentState.Warmup;
|
||||
warmupTicksRemaining = Props.warmupTicks;
|
||||
launchesCompleted = 0;
|
||||
currentGroupIndex = 0;
|
||||
|
||||
Log.Message($"[CircularBombardment] Warmup started: {warmupTicksRemaining} ticks remaining");
|
||||
}
|
||||
|
||||
private void UpdateWarmup()
|
||||
{
|
||||
warmupTicksRemaining--;
|
||||
|
||||
if (warmupTicksRemaining <= 0)
|
||||
{
|
||||
// 前摇结束,开始发射
|
||||
currentState = CircularBombardmentState.Launching;
|
||||
nextLaunchTick = Find.TickManager.TicksGame;
|
||||
Log.Message($"[CircularBombardment] Warmup completed, starting launches");
|
||||
}
|
||||
}
|
||||
|
||||
// 开始新的一组发射
|
||||
private void StartNewGroup()
|
||||
{
|
||||
if (remainingTargets.Count == 0 || launchesCompleted >= Props.maxLaunches)
|
||||
{
|
||||
currentState = CircularBombardmentState.Completed;
|
||||
Log.Message($"[CircularBombardment] All launches completed: {launchesCompleted}/{Props.maxLaunches}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 选择本组的目标
|
||||
int groupSize = Mathf.Min(Props.simultaneousLaunches, remainingTargets.Count);
|
||||
currentGroupTargets = remainingTargets.Take(groupSize).ToList();
|
||||
remainingTargets.RemoveRange(0, groupSize);
|
||||
|
||||
if (Props.useIndependentIntervals)
|
||||
{
|
||||
// 启用组内独立间隔
|
||||
isInGroupLaunch = true;
|
||||
nextInnerLaunchTick = Find.TickManager.TicksGame;
|
||||
currentGroupIndex++;
|
||||
|
||||
Log.Message($"[CircularBombardment] Starting group {currentGroupIndex} with {currentGroupTargets.Count} targets, using independent intervals");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 传统模式:同时发射所有目标
|
||||
foreach (var target in currentGroupTargets)
|
||||
{
|
||||
if (launchesCompleted < Props.maxLaunches)
|
||||
{
|
||||
LaunchSkyfaller(target);
|
||||
launchesCompleted++;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置下一组发射时间
|
||||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||||
Log.Message($"[CircularBombardment] Launched group {currentGroupIndex + 1} simultaneously: {currentGroupTargets.Count} targets");
|
||||
}
|
||||
}
|
||||
|
||||
// 更新组内独立发射
|
||||
private void UpdateIndependentGroupLaunch()
|
||||
{
|
||||
if (Find.TickManager.TicksGame < nextInnerLaunchTick)
|
||||
return;
|
||||
|
||||
if (currentGroupTargets.Count == 0)
|
||||
{
|
||||
// 当前组发射完毕
|
||||
isInGroupLaunch = false;
|
||||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||||
Log.Message($"[CircularBombardment] Group {currentGroupIndex} completed");
|
||||
return;
|
||||
}
|
||||
|
||||
// 发射当前目标
|
||||
var target = currentGroupTargets[0];
|
||||
currentGroupTargets.RemoveAt(0);
|
||||
|
||||
LaunchSkyfaller(target);
|
||||
launchesCompleted++;
|
||||
|
||||
// 设置下一个组内发射时间
|
||||
if (currentGroupTargets.Count > 0 && launchesCompleted < Props.maxLaunches)
|
||||
{
|
||||
nextInnerLaunchTick = Find.TickManager.TicksGame + Props.innerLaunchIntervalTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 当前组发射完毕或达到最大发射数量
|
||||
isInGroupLaunch = false;
|
||||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||||
}
|
||||
|
||||
Log.Message($"[CircularBombardment] Launched target in group {currentGroupIndex} ({launchesCompleted}/{Props.maxLaunches})");
|
||||
}
|
||||
|
||||
// 更新发射逻辑
|
||||
private void UpdateLaunching()
|
||||
{
|
||||
if (Props.useIndependentIntervals && isInGroupLaunch)
|
||||
{
|
||||
UpdateIndependentGroupLaunch();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Find.TickManager.TicksGame >= nextLaunchTick)
|
||||
{
|
||||
StartNewGroup();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (launchesCompleted >= Props.maxLaunches ||
|
||||
(remainingTargets.Count == 0 && !isInGroupLaunch))
|
||||
{
|
||||
currentState = CircularBombardmentState.Completed;
|
||||
Log.Message($"[CircularBombardment] Bombardment completed: {launchesCompleted} launches");
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchSkyfaller(IntVec3 targetCell)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Props.skyfallerDef != null)
|
||||
{
|
||||
// 使用 Skyfaller
|
||||
Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(Props.skyfallerDef);
|
||||
GenSpawn.Spawn(skyfaller, targetCell, parent.pawn.Map);
|
||||
}
|
||||
else if (Props.projectileDef != null)
|
||||
{
|
||||
// 使用抛射体作为备用
|
||||
LaunchProjectileAt(targetCell);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"[CircularBombardment] No skyfaller or projectile defined");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[CircularBombardment] Error launching at {targetCell}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchProjectileAt(IntVec3 targetCell)
|
||||
{
|
||||
// 从上方发射抛射体
|
||||
IntVec3 spawnCell = new IntVec3(targetCell.x, 0, targetCell.z);
|
||||
Vector3 spawnPos = spawnCell.ToVector3() + new Vector3(0, 20f, 0);
|
||||
|
||||
Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, spawnCell, parent.pawn.Map);
|
||||
if (projectile != null)
|
||||
{
|
||||
projectile.Launch(
|
||||
parent.pawn,
|
||||
spawnPos,
|
||||
new LocalTargetInfo(targetCell),
|
||||
new LocalTargetInfo(targetCell),
|
||||
ProjectileHitFlags.All,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
// 重置状态
|
||||
currentState = CircularBombardmentState.Idle;
|
||||
targetCells.Clear();
|
||||
remainingTargets.Clear();
|
||||
currentGroupTargets.Clear();
|
||||
currentPreviewCells.Clear();
|
||||
impactPreviewCells.Clear();
|
||||
currentGroupIndex = 0;
|
||||
isInGroupLaunch = false;
|
||||
launchesCompleted = 0;
|
||||
|
||||
Log.Message($"[CircularBombardment] Cleanup completed");
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
if (currentState == CircularBombardmentState.Idle)
|
||||
return;
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case CircularBombardmentState.Warmup:
|
||||
UpdateWarmup();
|
||||
break;
|
||||
|
||||
case CircularBombardmentState.Launching:
|
||||
UpdateLaunching();
|
||||
break;
|
||||
|
||||
case CircularBombardmentState.Completed:
|
||||
Cleanup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
Scribe_Values.Look(ref currentState, "currentState", CircularBombardmentState.Idle);
|
||||
Scribe_Collections.Look(ref targetCells, "targetCells", LookMode.Value);
|
||||
Scribe_Collections.Look(ref remainingTargets, "remainingTargets", LookMode.Value);
|
||||
Scribe_Collections.Look(ref currentGroupTargets, "currentGroupTargets", LookMode.Value);
|
||||
Scribe_Values.Look(ref warmupTicksRemaining, "warmupTicksRemaining", 0);
|
||||
Scribe_Values.Look(ref nextLaunchTick, "nextLaunchTick", 0);
|
||||
Scribe_Values.Look(ref nextInnerLaunchTick, "nextInnerLaunchTick", 0);
|
||||
Scribe_Values.Look(ref launchesCompleted, "launchesCompleted", 0);
|
||||
Scribe_Values.Look(ref currentGroupIndex, "currentGroupIndex", 0);
|
||||
Scribe_Values.Look(ref isInGroupLaunch, "isInGroupLaunch", false);
|
||||
}
|
||||
|
||||
// 技能提示信息
|
||||
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
|
||||
{
|
||||
string baseInfo = $"圆形轰炸: 半径{Props.radius}格";
|
||||
|
||||
if (Props.useIndependentIntervals)
|
||||
{
|
||||
baseInfo += $"\n组内间隔: {Props.innerLaunchIntervalTicks}刻";
|
||||
baseInfo += $"\n每组数量: {Props.simultaneousLaunches}个";
|
||||
}
|
||||
else
|
||||
{
|
||||
baseInfo += $"\n同时发射: {Props.simultaneousLaunches}个";
|
||||
}
|
||||
|
||||
baseInfo += $"\n最大数量: {Props.maxLaunches}个";
|
||||
baseInfo += $"\n组间间隔: {Props.launchIntervalTicks}刻";
|
||||
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
|
||||
{
|
||||
return base.Valid(target, throwMessages) &&
|
||||
parent.pawn != null &&
|
||||
parent.pawn.Map != null &&
|
||||
target.Cell.IsValid &&
|
||||
target.Cell.InBounds(parent.pawn.Map);
|
||||
}
|
||||
}
|
||||
|
||||
// 圆形轰炸状态枚举
|
||||
public enum CircularBombardmentState
|
||||
{
|
||||
Idle,
|
||||
Warmup,
|
||||
Launching,
|
||||
Completed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_AbilityCircularBombardment : CompProperties_AbilityEffect
|
||||
{
|
||||
// 轰炸区域配置
|
||||
public float radius = 10f; // 圆形区域半径
|
||||
public bool useFixedRadius = true; // 是否使用固定半径
|
||||
|
||||
// 目标选择配置
|
||||
public float targetSelectionChance = 0.5f; // 每个位置被选中的概率
|
||||
public int minTargets = 3; // 最小目标数量
|
||||
public int maxTargets = 15; // 最大目标数量
|
||||
|
||||
// 发射配置
|
||||
public int simultaneousLaunches = 2; // 同时发射数量
|
||||
public int launchIntervalTicks = 30; // 组间发射间隔(刻)
|
||||
public int maxLaunches = 10; // 最大发射数量
|
||||
public int warmupTicks = 120; // 前摇时间
|
||||
|
||||
// 组内独立间隔配置
|
||||
public bool useIndependentIntervals = false; // 是否使用独立间隔
|
||||
public int innerLaunchIntervalTicks = 10; // 组内发射间隔(刻)
|
||||
|
||||
// Skyfaller 配置
|
||||
public ThingDef skyfallerDef; // 使用的 Skyfaller
|
||||
public ThingDef projectileDef; // 备用的抛射体定义
|
||||
|
||||
// 视觉效果配置
|
||||
public bool showBombardmentArea = true; // 是否显示轰炸区域
|
||||
public Color areaPreviewColor = new Color(1f, 0.5f, 0.1f, 0.3f); // 区域预览颜色
|
||||
public bool showImpactPreview = true; // 是否显示预计落点
|
||||
|
||||
// 随机分布配置
|
||||
public float minDistanceFromCenter = 0f; // 距离中心的最小距离
|
||||
public bool avoidFriendlyFire = true; // 避免友军误伤
|
||||
public bool avoidBuildings = false; // 避免建筑物
|
||||
|
||||
public CompProperties_AbilityCircularBombardment()
|
||||
{
|
||||
this.compClass = typeof(CompAbilityEffect_CircularBombardment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompAbilityEffect_EnergyLance : CompAbilityEffect_WithDest
|
||||
{
|
||||
public new CompProperties_EnergyLance Props => (CompProperties_EnergyLance)props;
|
||||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||
{
|
||||
base.Apply(target, dest);
|
||||
|
||||
// 计算光束的起点和方向
|
||||
IntVec3 startPos = target.Cell;
|
||||
IntVec3 endPos = dest.Cell;
|
||||
|
||||
// 如果使用固定距离,则从起点向终点方向移动固定距离
|
||||
if (Props.useFixedDistance)
|
||||
{
|
||||
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
|
||||
Vector3 offset = direction * Props.moveDistance;
|
||||
endPos = startPos + new IntVec3(Mathf.RoundToInt(offset.x), 0, Mathf.RoundToInt(offset.z));
|
||||
}
|
||||
|
||||
// 创建移动的能量光束
|
||||
EnergyLance obj = (EnergyLance)GenSpawn.Spawn(ThingDef.Named("EnergyLance"), startPos, parent.pawn.Map);
|
||||
obj.duration = Props.durationTicks;
|
||||
obj.instigator = parent.pawn;
|
||||
obj.startPos = startPos;
|
||||
obj.endPos = endPos;
|
||||
obj.moveDistance = Props.moveDistance;
|
||||
obj.useFixedDistance = Props.useFixedDistance;
|
||||
obj.firesPerTick = Props.firesPerTick;
|
||||
// 不再需要传递伤害范围,因为现在从ModExtension读取
|
||||
obj.StartStrike();
|
||||
|
||||
Log.Message($"[EnergyLance] Created energy lance from {startPos} to {endPos}, distance: {Props.moveDistance}");
|
||||
}
|
||||
|
||||
// 绘制预览效果
|
||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||
{
|
||||
base.DrawEffectPreview(target);
|
||||
|
||||
if (parent.pawn == null || parent.pawn.Map == null || !target.IsValid)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 绘制起点预览
|
||||
GenDraw.DrawTargetHighlight(target.Cell);
|
||||
|
||||
// 如果选择了终点,绘制移动路径预览
|
||||
if (selectedTarget.IsValid)
|
||||
{
|
||||
DrawMovePathPreview(target.Cell, selectedTarget.Cell);
|
||||
}
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// 忽略预览绘制错误
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMovePathPreview(IntVec3 startPos, IntVec3 endPos)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 计算实际终点
|
||||
IntVec3 actualEndPos = endPos;
|
||||
if (Props.useFixedDistance)
|
||||
{
|
||||
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
|
||||
Vector3 offset = direction * Props.moveDistance;
|
||||
actualEndPos = startPos + new IntVec3(Mathf.RoundToInt(offset.x), 0, Mathf.RoundToInt(offset.z));
|
||||
}
|
||||
|
||||
// 绘制移动路径
|
||||
Vector3 startVec = startPos.ToVector3Shifted();
|
||||
Vector3 endVec = actualEndPos.ToVector3Shifted();
|
||||
|
||||
GenDraw.DrawLineBetween(startVec, endVec, SimpleColor.Yellow, 0.2f);
|
||||
|
||||
// 绘制终点预览
|
||||
GenDraw.DrawTargetHighlight(actualEndPos);
|
||||
|
||||
// 绘制作用范围预览(在移动路径上)
|
||||
DrawEffectRangePreview(startPos, actualEndPos);
|
||||
}
|
||||
|
||||
private void DrawEffectRangePreview(IntVec3 startPos, IntVec3 endPos)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 沿着移动路径绘制作用范围
|
||||
Vector3 currentPos = startPos.ToVector3();
|
||||
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
|
||||
float totalDistance = Vector3.Distance(startPos.ToVector3(), endPos.ToVector3());
|
||||
float step = 1f; // 每格绘制
|
||||
|
||||
for (float distance = 0; distance <= totalDistance; distance += step)
|
||||
{
|
||||
Vector3 checkPos = startPos.ToVector3() + direction * distance;
|
||||
IntVec3 checkCell = new IntVec3(Mathf.RoundToInt(checkPos.x), 0, Mathf.RoundToInt(checkPos.z));
|
||||
|
||||
if (checkCell.InBounds(map))
|
||||
{
|
||||
// 绘制作用范围指示
|
||||
GenDraw.DrawRadiusRing(checkCell, 1.5f, Color.red, (c) => true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
|
||||
{
|
||||
string baseInfo = $"能量长矛: 持续{Props.durationTicks}刻";
|
||||
|
||||
if (Props.useFixedDistance)
|
||||
{
|
||||
baseInfo += $"\n移动距离: {Props.moveDistance}格";
|
||||
}
|
||||
|
||||
baseInfo += $"\n选择起点后,再选择移动方向";
|
||||
|
||||
return baseInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_EnergyLance : CompProperties_EffectWithDest
|
||||
{
|
||||
public int durationTicks = 600; // 光束持续时间
|
||||
public float moveDistance = 15f; // 光束移动距离
|
||||
public bool useFixedDistance = true; // 是否使用固定距离
|
||||
|
||||
// 伤害配置
|
||||
public int firesPerTick = 4; // 每刻产生的火焰数量
|
||||
public IntRange flameDamageRange = new IntRange(65, 100); // 火焰伤害范围
|
||||
public IntRange corpseFlameDamageRange = new IntRange(5, 10); // 尸体火焰伤害范围
|
||||
|
||||
public CompProperties_EnergyLance()
|
||||
{
|
||||
this.compClass = typeof(CompAbilityEffect_EnergyLance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class EnergyLance : OrbitalStrike
|
||||
{
|
||||
// 移动相关属性
|
||||
public IntVec3 startPos;
|
||||
public IntVec3 endPos;
|
||||
public float moveDistance;
|
||||
public bool useFixedDistance;
|
||||
|
||||
// 伤害配置
|
||||
public int firesPerTick = 4;
|
||||
|
||||
// ModExtension引用
|
||||
private EnergyLanceExtension extension;
|
||||
|
||||
// 移动状态
|
||||
private Vector3 currentPos;
|
||||
private Vector3 moveDirection;
|
||||
private float moveSpeed;
|
||||
private float traveledDistance;
|
||||
private const float effectRadius = 3f; // 作用半径
|
||||
|
||||
private static List<Thing> tmpThings = new List<Thing>();
|
||||
|
||||
public override void StartStrike()
|
||||
{
|
||||
base.StartStrike();
|
||||
|
||||
// 获取ModExtension
|
||||
extension = def.GetModExtension<EnergyLanceExtension>();
|
||||
if (extension == null)
|
||||
{
|
||||
Log.Error($"[EnergyLance] No EnergyLanceExtension found on {def.defName}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化移动参数
|
||||
currentPos = startPos.ToVector3();
|
||||
|
||||
if (useFixedDistance)
|
||||
{
|
||||
// 从起点向终点方向移动固定距离
|
||||
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
|
||||
moveDirection = direction;
|
||||
moveSpeed = moveDistance / duration; // 根据持续时间计算移动速度
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接从起点移动到终点
|
||||
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3());
|
||||
moveDirection = direction.normalized;
|
||||
moveSpeed = direction.magnitude / duration;
|
||||
}
|
||||
|
||||
traveledDistance = 0f;
|
||||
|
||||
// 创建视觉效果
|
||||
CreateVisualEffect();
|
||||
|
||||
Log.Message($"[EnergyLance] Strike started from {startPos} to {endPos}, " +
|
||||
$"damage: {extension.damageDef.defName}, speed: {moveSpeed}");
|
||||
}
|
||||
|
||||
private void CreateVisualEffect()
|
||||
{
|
||||
// 使用ModExtension中定义的Mote,如果没有则使用默认的PowerBeam
|
||||
if (extension.moteDef != null)
|
||||
{
|
||||
Mote mote = MoteMaker.MakeStaticMote(base.Position, base.Map, extension.moteDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用原版PowerBeam的视觉效果
|
||||
MoteMaker.MakePowerBeamMote(base.Position, base.Map);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
if (!base.Destroyed && extension != null)
|
||||
{
|
||||
// 移动光束
|
||||
MoveBeam();
|
||||
|
||||
// 造成伤害
|
||||
for (int i = 0; i < firesPerTick; i++)
|
||||
{
|
||||
DoBeamDamage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveBeam()
|
||||
{
|
||||
// 计算移动距离
|
||||
float moveThisTick = moveSpeed;
|
||||
|
||||
// 更新位置
|
||||
currentPos += moveDirection * moveThisTick;
|
||||
traveledDistance += moveThisTick;
|
||||
|
||||
// 更新光束的实际位置
|
||||
IntVec3 newCell = new IntVec3(Mathf.RoundToInt(currentPos.x), 0, Mathf.RoundToInt(currentPos.z));
|
||||
if (newCell != base.Position && newCell.InBounds(base.Map))
|
||||
{
|
||||
base.Position = newCell;
|
||||
}
|
||||
|
||||
// 检查是否到达终点
|
||||
if (useFixedDistance && traveledDistance >= moveDistance)
|
||||
{
|
||||
// 固定距离模式:移动指定距离后结束
|
||||
Destroy();
|
||||
Log.Message($"[EnergyLance] Reached fixed distance, destroying");
|
||||
}
|
||||
else if (!useFixedDistance && traveledDistance >= Vector3.Distance(startPos.ToVector3(), endPos.ToVector3()))
|
||||
{
|
||||
// 终点模式:到达终点后结束
|
||||
Destroy();
|
||||
Log.Message($"[EnergyLance] Reached end position, destroying");
|
||||
}
|
||||
}
|
||||
|
||||
private void DoBeamDamage()
|
||||
{
|
||||
if (extension == null) return;
|
||||
|
||||
// 在当前光束位置周围随机选择一个单元格
|
||||
IntVec3 targetCell = (from x in GenRadial.RadialCellsAround(base.Position, effectRadius, useCenter: true)
|
||||
where x.InBounds(base.Map)
|
||||
select x).RandomElementByWeight((IntVec3 x) => 1f - Mathf.Min(x.DistanceTo(base.Position) / effectRadius, 1f) + 0.05f);
|
||||
|
||||
// 尝试在该单元格点火(如果配置了点火)
|
||||
if (extension.igniteFires)
|
||||
{
|
||||
FireUtility.TryStartFireIn(targetCell, base.Map, Rand.Range(0.1f, extension.fireIgniteChance), instigator);
|
||||
}
|
||||
|
||||
// 对该单元格内的物体造成伤害
|
||||
tmpThings.Clear();
|
||||
tmpThings.AddRange(targetCell.GetThingList(base.Map));
|
||||
|
||||
for (int i = 0; i < tmpThings.Count; i++)
|
||||
{
|
||||
Thing thing = tmpThings[i];
|
||||
|
||||
// 检查是否对尸体造成伤害
|
||||
if (!extension.applyDamageToCorpses && thing is Corpse)
|
||||
continue;
|
||||
|
||||
// 计算伤害量
|
||||
int damageAmount = (thing is Corpse) ?
|
||||
extension.corpseDamageAmountRange.RandomInRange :
|
||||
extension.damageAmountRange.RandomInRange;
|
||||
|
||||
Pawn pawn = thing as Pawn;
|
||||
BattleLogEntry_DamageTaken battleLogEntry = null;
|
||||
|
||||
if (pawn != null)
|
||||
{
|
||||
battleLogEntry = new BattleLogEntry_DamageTaken(pawn, RulePackDefOf.DamageEvent_PowerBeam, instigator as Pawn);
|
||||
Find.BattleLog.Add(battleLogEntry);
|
||||
}
|
||||
|
||||
// 使用ModExtension中定义的伤害类型
|
||||
DamageInfo damageInfo = new DamageInfo(extension.damageDef, damageAmount, 0f, -1f, instigator, null, weaponDef);
|
||||
thing.TakeDamage(damageInfo).AssociateWithLog(battleLogEntry);
|
||||
|
||||
Log.Message($"[EnergyLance] Applied {extension.damageDef.defName} damage {damageAmount} to {thing.Label}");
|
||||
}
|
||||
|
||||
tmpThings.Clear();
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_Values.Look(ref startPos, "startPos");
|
||||
Scribe_Values.Look(ref endPos, "endPos");
|
||||
Scribe_Values.Look(ref moveDistance, "moveDistance");
|
||||
Scribe_Values.Look(ref useFixedDistance, "useFixedDistance");
|
||||
Scribe_Values.Look(ref firesPerTick, "firesPerTick", 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class EnergyLanceExtension : DefModExtension
|
||||
{
|
||||
// 伤害类型配置
|
||||
public DamageDef damageDef = DamageDefOf.Flame; // 伤害类型,默认为火焰伤害
|
||||
public bool applyDamageToCorpses = true; // 是否对尸体造成伤害
|
||||
public bool igniteFires = true; // 是否点燃火焰
|
||||
public float fireIgniteChance = 0.8f; // 点燃火焰的概率
|
||||
|
||||
// 伤害量配置
|
||||
public IntRange damageAmountRange = new IntRange(65, 100); // 对生物的伤害范围
|
||||
public IntRange corpseDamageAmountRange = new IntRange(5, 10); // 对尸体的伤害范围
|
||||
|
||||
// 视觉效果配置
|
||||
public ThingDef moteDef; // 使用的Mote类型
|
||||
public SoundDef impactSound; // 撞击音效
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompBuildingEnergyLance : ThingComp
|
||||
{
|
||||
public CompProperties_BuildingEnergyLance Props => (CompProperties_BuildingEnergyLance)props;
|
||||
|
||||
// 状态管理
|
||||
private EnergyLanceState currentState = EnergyLanceState.Idle;
|
||||
private int nextUpdateTick = 0;
|
||||
private int noTargetTicks = 0;
|
||||
|
||||
// 目标管理
|
||||
private Pawn currentTarget = null;
|
||||
private IntVec3 lastTargetPosition = IntVec3.Invalid;
|
||||
|
||||
// EnergyLance实例
|
||||
private GuidedEnergyLance activeLance = null;
|
||||
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
|
||||
if (!respawningAfterLoad)
|
||||
{
|
||||
// 新生成时立即开始搜索目标
|
||||
nextUpdateTick = Find.TickManager.TicksGame;
|
||||
currentState = EnergyLanceState.Searching;
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] Building spawned, starting target search");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case EnergyLanceState.Idle:
|
||||
// 空闲状态,等待下一次更新
|
||||
break;
|
||||
|
||||
case EnergyLanceState.Searching:
|
||||
SearchForTarget();
|
||||
break;
|
||||
|
||||
case EnergyLanceState.Tracking:
|
||||
TrackTarget();
|
||||
break;
|
||||
|
||||
case EnergyLanceState.NoTarget:
|
||||
HandleNoTarget();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchForTarget()
|
||||
{
|
||||
Map map = parent.Map;
|
||||
if (map == null) return;
|
||||
|
||||
// 获取范围内的所有有效目标
|
||||
var potentialTargets = new List<Pawn>();
|
||||
|
||||
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (IsValidTarget(pawn) && IsInRange(pawn.Position))
|
||||
{
|
||||
potentialTargets.Add(pawn);
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialTargets.Count > 0)
|
||||
{
|
||||
// 选择第一个目标(可以改为其他选择逻辑)
|
||||
currentTarget = potentialTargets[0];
|
||||
lastTargetPosition = currentTarget.Position;
|
||||
|
||||
// 创建EnergyLance
|
||||
CreateEnergyLance();
|
||||
|
||||
currentState = EnergyLanceState.Tracking;
|
||||
noTargetTicks = 0;
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] Locked target: {currentTarget.Label}, position: {currentTarget.Position}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有找到目标
|
||||
currentState = EnergyLanceState.NoTarget;
|
||||
Log.Message($"[BuildingEnergyLance] No targets found in range");
|
||||
}
|
||||
}
|
||||
|
||||
private void TrackTarget()
|
||||
{
|
||||
if (currentTarget == null || !currentTarget.Spawned)
|
||||
{
|
||||
// 目标丢失,重新搜索
|
||||
currentState = EnergyLanceState.Searching;
|
||||
currentTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查目标是否仍然有效
|
||||
if (!IsValidTarget(currentTarget) || !IsInRange(currentTarget.Position))
|
||||
{
|
||||
// 目标无效或超出范围,寻找最近的敌人
|
||||
FindNearestTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新目标位置
|
||||
lastTargetPosition = currentTarget.Position;
|
||||
|
||||
// 向EnergyLance发送目标位置
|
||||
if (activeLance != null && !activeLance.Destroyed)
|
||||
{
|
||||
activeLance.UpdateTarget(lastTargetPosition);
|
||||
noTargetTicks = 0;
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] Updated target position: {lastTargetPosition}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// EnergyLance丢失,重新创建
|
||||
CreateEnergyLance();
|
||||
}
|
||||
}
|
||||
|
||||
private void FindNearestTarget()
|
||||
{
|
||||
Map map = parent.Map;
|
||||
if (map == null) return;
|
||||
|
||||
Pawn nearestTarget = null;
|
||||
float nearestDistance = float.MaxValue;
|
||||
|
||||
// 获取当前EnergyLance位置
|
||||
IntVec3 searchCenter = (activeLance != null && !activeLance.Destroyed) ?
|
||||
activeLance.Position : parent.Position;
|
||||
|
||||
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (IsValidTarget(pawn) && IsInRange(pawn.Position))
|
||||
{
|
||||
float distance = Vector3.Distance(searchCenter.ToVector3(), pawn.Position.ToVector3());
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestTarget = pawn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestTarget != null)
|
||||
{
|
||||
currentTarget = nearestTarget;
|
||||
lastTargetPosition = currentTarget.Position;
|
||||
|
||||
// 更新EnergyLance目标
|
||||
if (activeLance != null && !activeLance.Destroyed)
|
||||
{
|
||||
activeLance.UpdateTarget(lastTargetPosition);
|
||||
}
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] Switched to nearest target: {currentTarget.Label}, distance: {nearestDistance}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有找到替代目标
|
||||
currentState = EnergyLanceState.NoTarget;
|
||||
currentTarget = null;
|
||||
Log.Message($"[BuildingEnergyLance] No alternative targets found");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleNoTarget()
|
||||
{
|
||||
noTargetTicks++;
|
||||
|
||||
// 向EnergyLance发送空位置
|
||||
if (activeLance != null && !activeLance.Destroyed)
|
||||
{
|
||||
activeLance.UpdateTarget(IntVec3.Invalid);
|
||||
}
|
||||
|
||||
// 检查是否应该销毁EnergyLance
|
||||
if (noTargetTicks >= Props.maxNoTargetTicks)
|
||||
{
|
||||
if (activeLance != null && !activeLance.Destroyed)
|
||||
{
|
||||
activeLance.Destroy();
|
||||
activeLance = null;
|
||||
}
|
||||
|
||||
// 回到搜索状态
|
||||
currentState = EnergyLanceState.Searching;
|
||||
noTargetTicks = 0;
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] EnergyLance destroyed due to no targets");
|
||||
}
|
||||
else if (noTargetTicks % 60 == 0) // 每60刻检查一次是否有新目标
|
||||
{
|
||||
SearchForTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateEnergyLance()
|
||||
{
|
||||
if (Props.energyLanceDef == null)
|
||||
{
|
||||
Log.Error($"[BuildingEnergyLance] No energyLanceDef configured");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 销毁现有的EnergyLance
|
||||
if (activeLance != null && !activeLance.Destroyed)
|
||||
{
|
||||
activeLance.Destroy();
|
||||
}
|
||||
|
||||
// 创建新的EnergyLance
|
||||
IntVec3 spawnPos = GetLanceSpawnPosition();
|
||||
activeLance = (GuidedEnergyLance)GenSpawn.Spawn(Props.energyLanceDef, spawnPos, parent.Map);
|
||||
|
||||
// 初始化EnergyLance
|
||||
activeLance.duration = Props.energyLanceDuration;
|
||||
activeLance.instigator = parent;
|
||||
activeLance.controlBuilding = this.parent;
|
||||
activeLance.firesPerTick = Props.firesPerTick;
|
||||
|
||||
// 如果有当前目标,设置初始位置
|
||||
if (currentTarget != null)
|
||||
{
|
||||
activeLance.UpdateTarget(currentTarget.Position);
|
||||
}
|
||||
|
||||
activeLance.StartStrike();
|
||||
|
||||
Log.Message($"[BuildingEnergyLance] Created EnergyLance at {spawnPos}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[BuildingEnergyLance] Error creating EnergyLance: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private IntVec3 GetLanceSpawnPosition()
|
||||
{
|
||||
// 在建筑周围寻找一个合适的生成位置
|
||||
Map map = parent.Map;
|
||||
IntVec3 center = parent.Position;
|
||||
|
||||
// 优先选择建筑上方的位置
|
||||
if (center.InBounds(map) && center.Walkable(map))
|
||||
{
|
||||
return center;
|
||||
}
|
||||
|
||||
// 如果建筑位置不可用,在周围寻找
|
||||
foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, 2f, true))
|
||||
{
|
||||
if (cell.InBounds(map) && cell.Walkable(map))
|
||||
{
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都不可用,返回建筑位置
|
||||
return center;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || pawn.Downed || pawn.Dead || !pawn.Spawned)
|
||||
return false;
|
||||
|
||||
// 检查目标类型
|
||||
if (Props.targetEnemies && pawn.HostileTo(parent.Faction))
|
||||
return true;
|
||||
|
||||
if (Props.targetNeutrals && !pawn.HostileTo(parent.Faction) && pawn.Faction != parent.Faction)
|
||||
return true;
|
||||
|
||||
if (Props.targetAnimals && pawn.RaceProps.Animal)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsInRange(IntVec3 position)
|
||||
{
|
||||
float distance = Vector3.Distance(parent.Position.ToVector3(), position.ToVector3());
|
||||
return distance <= Props.radius;
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
int currentTick = Find.TickManager.TicksGame;
|
||||
|
||||
// 检查是否需要更新状态
|
||||
if (currentTick >= nextUpdateTick)
|
||||
{
|
||||
UpdateState();
|
||||
nextUpdateTick = currentTick + Props.updateIntervalTicks;
|
||||
}
|
||||
|
||||
// 检查EnergyLance状态
|
||||
if (activeLance != null && activeLance.Destroyed)
|
||||
{
|
||||
activeLance = null;
|
||||
if (currentState == EnergyLanceState.Tracking)
|
||||
{
|
||||
currentState = EnergyLanceState.Searching;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 外部调用:当EnergyLance需要目标时调用
|
||||
public bool TryGetCurrentTarget(out IntVec3 targetPos)
|
||||
{
|
||||
if (currentTarget != null && IsValidTarget(currentTarget) && IsInRange(currentTarget.Position))
|
||||
{
|
||||
targetPos = currentTarget.Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
targetPos = IntVec3.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
Scribe_Values.Look(ref currentState, "currentState", EnergyLanceState.Idle);
|
||||
Scribe_Values.Look(ref nextUpdateTick, "nextUpdateTick", 0);
|
||||
Scribe_Values.Look(ref noTargetTicks, "noTargetTicks", 0);
|
||||
Scribe_References.Look(ref currentTarget, "currentTarget");
|
||||
Scribe_Values.Look(ref lastTargetPosition, "lastTargetPosition", IntVec3.Invalid);
|
||||
Scribe_References.Look(ref activeLance, "activeLance");
|
||||
}
|
||||
}
|
||||
|
||||
public enum EnergyLanceState
|
||||
{
|
||||
Idle,
|
||||
Searching,
|
||||
Tracking,
|
||||
NoTarget
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_BuildingEnergyLance : CompProperties
|
||||
{
|
||||
// 基础配置
|
||||
public float radius = 20f; // 作用半径
|
||||
public int updateIntervalTicks = 30; // 目标更新间隔(刻)
|
||||
public int maxNoTargetTicks = 120; // 无目标时最大存活时间
|
||||
|
||||
// 目标选择配置
|
||||
public bool targetEnemies = true; // 是否以敌人为目标
|
||||
public bool targetNeutrals = false; // 是否以中立单位为目标
|
||||
public bool targetAnimals = false; // 是否以动物为目标
|
||||
|
||||
// EnergyLance配置
|
||||
public ThingDef energyLanceDef; // 使用的EnergyLance类型
|
||||
public int energyLanceDuration = 600; // EnergyLance基础持续时间
|
||||
public int firesPerTick = 3; // 每刻伤害次数
|
||||
|
||||
public CompProperties_BuildingEnergyLance()
|
||||
{
|
||||
this.compClass = typeof(CompBuildingEnergyLance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class GuidedEnergyLance : OrbitalStrike
|
||||
{
|
||||
// 引导相关属性
|
||||
public Thing controlBuilding; // 控制建筑
|
||||
private IntVec3 currentTarget = IntVec3.Invalid; // 当前目标位置
|
||||
private int lastTargetUpdateTick = -1; // 最后收到目标更新的刻
|
||||
private int maxNoUpdateTicks = 60; // 无更新时的最大存活时间
|
||||
|
||||
// 移动配置
|
||||
public float moveSpeed = 2.0f; // 移动速度(格/秒)
|
||||
public float turnSpeed = 90f; // 转向速度(度/秒)
|
||||
|
||||
// 状态
|
||||
private Vector3 currentVelocity;
|
||||
private bool hasValidTarget = false;
|
||||
|
||||
// ModExtension引用
|
||||
private EnergyLanceExtension extension;
|
||||
|
||||
private static List<Thing> tmpThings = new List<Thing>();
|
||||
|
||||
public override void StartStrike()
|
||||
{
|
||||
base.StartStrike();
|
||||
|
||||
// 获取ModExtension
|
||||
extension = def.GetModExtension<EnergyLanceExtension>();
|
||||
if (extension == null)
|
||||
{
|
||||
Log.Error($"[GuidedEnergyLance] No EnergyLanceExtension found on {def.defName}");
|
||||
return;
|
||||
}
|
||||
|
||||
lastTargetUpdateTick = Find.TickManager.TicksGame;
|
||||
|
||||
// 创建视觉效果
|
||||
CreateVisualEffect();
|
||||
|
||||
Log.Message($"[GuidedEnergyLance] Guided EnergyLance started, controlled by {controlBuilding?.Label ?? "None"}");
|
||||
}
|
||||
|
||||
private void CreateVisualEffect()
|
||||
{
|
||||
// 使用ModExtension中定义的Mote,如果没有则使用默认的PowerBeam
|
||||
if (extension.moteDef != null)
|
||||
{
|
||||
Mote mote = MoteMaker.MakeStaticMote(base.Position, base.Map, extension.moteDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用原版PowerBeam的视觉效果
|
||||
MoteMaker.MakePowerBeamMote(base.Position, base.Map);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
if (!base.Destroyed && extension != null)
|
||||
{
|
||||
// 检查目标更新状态
|
||||
CheckTargetStatus();
|
||||
|
||||
// 移动光束
|
||||
MoveBeam();
|
||||
|
||||
// 造成伤害
|
||||
for (int i = 0; i < firesPerTick; i++)
|
||||
{
|
||||
DoBeamDamage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTargetStatus()
|
||||
{
|
||||
int currentTick = Find.TickManager.TicksGame;
|
||||
int ticksSinceUpdate = currentTick - lastTargetUpdateTick;
|
||||
|
||||
// 检查是否长时间未收到更新
|
||||
if (ticksSinceUpdate >= maxNoUpdateTicks)
|
||||
{
|
||||
Log.Message($"[GuidedEnergyLance] No target updates for {ticksSinceUpdate} ticks, destroying");
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查控制建筑状态
|
||||
if (controlBuilding == null || controlBuilding.Destroyed || !controlBuilding.Spawned)
|
||||
{
|
||||
Log.Message($"[GuidedEnergyLance] Control building lost, destroying");
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveBeam()
|
||||
{
|
||||
if (!hasValidTarget || !currentTarget.IsValid)
|
||||
{
|
||||
// 没有有效目标,缓慢移动或保持原地
|
||||
ApplyMinimalMovement();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算移动方向
|
||||
Vector3 targetDirection = (currentTarget.ToVector3() - base.Position.ToVector3()).normalized;
|
||||
|
||||
// 平滑转向
|
||||
if (currentVelocity.magnitude > 0.1f)
|
||||
{
|
||||
float maxTurnAngle = turnSpeed * 0.0167f; // 每帧最大转向角度(假设60FPS)
|
||||
currentVelocity = Vector3.RotateTowards(currentVelocity, targetDirection, maxTurnAngle * Mathf.Deg2Rad, moveSpeed * 0.0167f);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentVelocity = targetDirection * moveSpeed * 0.0167f;
|
||||
}
|
||||
|
||||
// 应用移动
|
||||
Vector3 newPos = base.Position.ToVector3() + currentVelocity;
|
||||
IntVec3 newCell = new IntVec3(Mathf.RoundToInt(newPos.x), 0, Mathf.RoundToInt(newPos.z));
|
||||
|
||||
if (newCell != base.Position && newCell.InBounds(base.Map))
|
||||
{
|
||||
base.Position = newCell;
|
||||
}
|
||||
|
||||
// 检查是否接近目标
|
||||
float distanceToTarget = Vector3.Distance(base.Position.ToVector3(), currentTarget.ToVector3());
|
||||
if (distanceToTarget < 1.5f)
|
||||
{
|
||||
// 到达目标附近,可以减速或保持位置
|
||||
currentVelocity *= 0.8f;
|
||||
}
|
||||
|
||||
Log.Message($"[GuidedEnergyLance] Moving to {currentTarget}, distance: {distanceToTarget:F1}");
|
||||
}
|
||||
|
||||
private void ApplyMinimalMovement()
|
||||
{
|
||||
// 无目标时的最小移动,防止完全静止
|
||||
if (currentVelocity.magnitude < 0.1f)
|
||||
{
|
||||
// 随机轻微移动
|
||||
currentVelocity = new Vector3(Rand.Range(-0.1f, 0.1f), 0f, Rand.Range(-0.1f, 0.1f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 缓慢减速
|
||||
currentVelocity *= 0.95f;
|
||||
}
|
||||
|
||||
Vector3 newPos = base.Position.ToVector3() + currentVelocity;
|
||||
IntVec3 newCell = new IntVec3(Mathf.RoundToInt(newPos.x), 0, Mathf.RoundToInt(newPos.z));
|
||||
|
||||
if (newCell != base.Position && newCell.InBounds(base.Map))
|
||||
{
|
||||
base.Position = newCell;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoBeamDamage()
|
||||
{
|
||||
if (extension == null) return;
|
||||
|
||||
// 在当前光束位置周围随机选择一个单元格
|
||||
IntVec3 targetCell = (from x in GenRadial.RadialCellsAround(base.Position, 2f, useCenter: true)
|
||||
where x.InBounds(base.Map)
|
||||
select x).RandomElementByWeight((IntVec3 x) => 1f - Mathf.Min(x.DistanceTo(base.Position) / 2f, 1f) + 0.05f);
|
||||
|
||||
// 尝试在该单元格点火(如果配置了点火)
|
||||
if (extension.igniteFires)
|
||||
{
|
||||
FireUtility.TryStartFireIn(targetCell, base.Map, Rand.Range(0.1f, extension.fireIgniteChance), instigator);
|
||||
}
|
||||
|
||||
// 对该单元格内的物体造成伤害
|
||||
tmpThings.Clear();
|
||||
tmpThings.AddRange(targetCell.GetThingList(base.Map));
|
||||
|
||||
for (int i = 0; i < tmpThings.Count; i++)
|
||||
{
|
||||
Thing thing = tmpThings[i];
|
||||
|
||||
// 检查是否对尸体造成伤害
|
||||
if (!extension.applyDamageToCorpses && thing is Corpse)
|
||||
continue;
|
||||
|
||||
// 计算伤害量
|
||||
int damageAmount = (thing is Corpse) ?
|
||||
extension.corpseDamageAmountRange.RandomInRange :
|
||||
extension.damageAmountRange.RandomInRange;
|
||||
|
||||
Pawn pawn = thing as Pawn;
|
||||
BattleLogEntry_DamageTaken battleLogEntry = null;
|
||||
|
||||
if (pawn != null)
|
||||
{
|
||||
battleLogEntry = new BattleLogEntry_DamageTaken(pawn, RulePackDefOf.DamageEvent_PowerBeam, instigator as Pawn);
|
||||
Find.BattleLog.Add(battleLogEntry);
|
||||
}
|
||||
|
||||
// 使用ModExtension中定义的伤害类型
|
||||
DamageInfo damageInfo = new DamageInfo(extension.damageDef, damageAmount, 0f, -1f, instigator, null, weaponDef);
|
||||
thing.TakeDamage(damageInfo).AssociateWithLog(battleLogEntry);
|
||||
}
|
||||
|
||||
tmpThings.Clear();
|
||||
}
|
||||
|
||||
// 外部调用:更新目标位置
|
||||
public void UpdateTarget(IntVec3 newTarget)
|
||||
{
|
||||
lastTargetUpdateTick = Find.TickManager.TicksGame;
|
||||
|
||||
if (newTarget.IsValid && newTarget.InBounds(base.Map))
|
||||
{
|
||||
currentTarget = newTarget;
|
||||
hasValidTarget = true;
|
||||
|
||||
Log.Message($"[GuidedEnergyLance] Target updated to {newTarget}");
|
||||
}
|
||||
else
|
||||
{
|
||||
hasValidTarget = false;
|
||||
currentTarget = IntVec3.Invalid;
|
||||
|
||||
Log.Message($"[GuidedEnergyLance] Target cleared");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_References.Look(ref controlBuilding, "controlBuilding");
|
||||
Scribe_Values.Look(ref currentTarget, "currentTarget", IntVec3.Invalid);
|
||||
Scribe_Values.Look(ref lastTargetUpdateTick, "lastTargetUpdateTick", -1);
|
||||
Scribe_Values.Look(ref maxNoUpdateTicks, "maxNoUpdateTicks", 60);
|
||||
Scribe_Values.Look(ref moveSpeed, "moveSpeed", 2.0f);
|
||||
Scribe_Values.Look(ref turnSpeed, "turnSpeed", 90f);
|
||||
Scribe_Values.Look(ref firesPerTick, "firesPerTick", 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompAbilityEffect_CallSkyfaller : CompAbilityEffect
|
||||
{
|
||||
public new CompProperties_AbilityCallSkyfaller Props => (CompProperties_AbilityCallSkyfaller)props;
|
||||
|
||||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||
{
|
||||
base.Apply(target, dest);
|
||||
|
||||
if (parent.pawn == null || parent.pawn.Map == null || !target.IsValid)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 创建延时召唤
|
||||
CallSkyfallerDelayed(target.Cell);
|
||||
|
||||
Log.Message($"[CallSkyfaller] Scheduled skyfaller at {target.Cell} with {Props.delayTicks} ticks delay");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[CallSkyfaller] Error calling skyfaller: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CallSkyfallerDelayed(IntVec3 targetCell)
|
||||
{
|
||||
// 使用延时动作来召唤skyfaller
|
||||
parent.pawn.Map.GetComponent<MapComponent_SkyfallerDelayed>()?
|
||||
.ScheduleSkyfaller(Props.skyfallerDef, targetCell, Props.delayTicks, parent.pawn);
|
||||
}
|
||||
|
||||
// 绘制预览效果
|
||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||
{
|
||||
base.DrawEffectPreview(target);
|
||||
|
||||
if (parent.pawn == null || parent.pawn.Map == null || !target.IsValid)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 绘制圆形预览区域
|
||||
DrawCircularPreview(target.Cell);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// 忽略预览绘制错误
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCircularPreview(IntVec3 center)
|
||||
{
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 获取圆形区域内的所有单元格
|
||||
var previewCells = GenRadial.RadialCellsAround(center, Props.previewRadius, true);
|
||||
|
||||
// 绘制预览区域
|
||||
foreach (var cell in previewCells)
|
||||
{
|
||||
if (cell.InBounds(map))
|
||||
{
|
||||
GenDraw.DrawFieldEdges(new List<IntVec3> { cell }, Props.previewColor, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制目标点高亮
|
||||
GenDraw.DrawTargetHighlight(center);
|
||||
}
|
||||
|
||||
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
|
||||
{
|
||||
return $"召唤空投舱: {Props.delayTicks}刻后到达";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompBuildingBombardment : ThingComp
|
||||
{
|
||||
public CompProperties_BuildingBombardment Props => (CompProperties_BuildingBombardment)props;
|
||||
|
||||
// 轰炸状态
|
||||
private BuildingBombardmentState currentState = BuildingBombardmentState.Idle;
|
||||
private int nextBurstTick = 0;
|
||||
private int currentBurstCount = 0;
|
||||
private int nextInnerBurstTick = 0;
|
||||
private List<LocalTargetInfo> currentTargets = new List<LocalTargetInfo>();
|
||||
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
|
||||
if (!respawningAfterLoad)
|
||||
{
|
||||
// 新生成时立即开始第一轮轰炸
|
||||
StartNextBurst();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartNextBurst()
|
||||
{
|
||||
currentState = BuildingBombardmentState.Targeting;
|
||||
currentBurstCount = 0;
|
||||
currentTargets.Clear();
|
||||
|
||||
// 选择目标
|
||||
SelectTargets();
|
||||
|
||||
if (currentTargets.Count > 0)
|
||||
{
|
||||
currentState = BuildingBombardmentState.Bursting;
|
||||
nextInnerBurstTick = Find.TickManager.TicksGame;
|
||||
Log.Message($"[BuildingBombardment] Starting burst with {currentTargets.Count} targets");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有找到目标,等待下一轮
|
||||
currentState = BuildingBombardmentState.Idle;
|
||||
nextBurstTick = Find.TickManager.TicksGame + Props.burstIntervalTicks;
|
||||
Log.Message($"[BuildingBombardment] No targets found, waiting for next burst");
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectTargets()
|
||||
{
|
||||
Map map = parent.Map;
|
||||
if (map == null) return;
|
||||
|
||||
// 获取范围内的所有pawn
|
||||
var potentialTargets = new List<Pawn>();
|
||||
|
||||
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (IsValidTarget(pawn) && IsInRange(pawn.Position))
|
||||
{
|
||||
potentialTargets.Add(pawn);
|
||||
}
|
||||
}
|
||||
|
||||
// 随机选择目标,最多burstCount个
|
||||
int targetCount = Mathf.Min(Props.burstCount, potentialTargets.Count);
|
||||
currentTargets = potentialTargets
|
||||
.InRandomOrder()
|
||||
.Take(targetCount)
|
||||
.Select(p => new LocalTargetInfo(p))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool IsValidTarget(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || pawn.Downed || pawn.Dead) return false;
|
||||
|
||||
// 检查目标类型
|
||||
if (Props.targetEnemies && pawn.HostileTo(parent.Faction))
|
||||
return true;
|
||||
|
||||
if (Props.targetNeutrals && !pawn.HostileTo(parent.Faction) && pawn.Faction != parent.Faction)
|
||||
return true;
|
||||
|
||||
if (Props.targetAnimals && pawn.RaceProps.Animal)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsInRange(IntVec3 position)
|
||||
{
|
||||
float distance = Vector3.Distance(parent.Position.ToVector3(), position.ToVector3());
|
||||
return distance <= Props.radius;
|
||||
}
|
||||
|
||||
private void UpdateBursting()
|
||||
{
|
||||
if (Find.TickManager.TicksGame < nextInnerBurstTick)
|
||||
return;
|
||||
|
||||
if (currentBurstCount >= currentTargets.Count)
|
||||
{
|
||||
// 当前组发射完毕
|
||||
currentState = BuildingBombardmentState.Idle;
|
||||
nextBurstTick = Find.TickManager.TicksGame + Props.burstIntervalTicks;
|
||||
Log.Message($"[BuildingBombardment] Burst completed, waiting for next burst");
|
||||
return;
|
||||
}
|
||||
|
||||
// 发射当前目标
|
||||
var target = currentTargets[currentBurstCount];
|
||||
LaunchBombardment(target);
|
||||
currentBurstCount++;
|
||||
|
||||
// 设置下一个组内发射时间
|
||||
if (currentBurstCount < currentTargets.Count)
|
||||
{
|
||||
nextInnerBurstTick = Find.TickManager.TicksGame + Props.innerBurstIntervalTicks;
|
||||
}
|
||||
|
||||
Log.Message($"[BuildingBombardment] Launched bombardment {currentBurstCount}/{currentTargets.Count}");
|
||||
}
|
||||
|
||||
private void LaunchBombardment(LocalTargetInfo target)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 应用随机偏移
|
||||
IntVec3 targetCell = ApplyRandomOffset(target.Cell);
|
||||
|
||||
if (Props.skyfallerDef != null)
|
||||
{
|
||||
// 使用 Skyfaller
|
||||
Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(Props.skyfallerDef);
|
||||
GenSpawn.Spawn(skyfaller, targetCell, parent.Map);
|
||||
}
|
||||
else if (Props.projectileDef != null)
|
||||
{
|
||||
// 使用抛射体作为备用
|
||||
LaunchProjectileAt(targetCell);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[BuildingBombardment] Error launching bombardment: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private IntVec3 ApplyRandomOffset(IntVec3 originalCell)
|
||||
{
|
||||
if (Props.randomOffset <= 0f)
|
||||
return originalCell;
|
||||
|
||||
// 在随机偏移范围内选择一个位置
|
||||
float offsetX = Rand.Range(-Props.randomOffset, Props.randomOffset);
|
||||
float offsetZ = Rand.Range(-Props.randomOffset, Props.randomOffset);
|
||||
|
||||
IntVec3 offsetCell = new IntVec3(
|
||||
Mathf.RoundToInt(originalCell.x + offsetX),
|
||||
originalCell.y,
|
||||
Mathf.RoundToInt(originalCell.z + offsetZ)
|
||||
);
|
||||
|
||||
// 确保位置有效
|
||||
if (offsetCell.InBounds(parent.Map))
|
||||
return offsetCell;
|
||||
else
|
||||
return originalCell;
|
||||
}
|
||||
|
||||
private void LaunchProjectileAt(IntVec3 targetCell)
|
||||
{
|
||||
// 从建筑位置发射抛射体
|
||||
Vector3 spawnPos = parent.Position.ToVector3Shifted();
|
||||
|
||||
Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, parent.Position, parent.Map);
|
||||
if (projectile != null)
|
||||
{
|
||||
projectile.Launch(
|
||||
parent,
|
||||
spawnPos,
|
||||
new LocalTargetInfo(targetCell),
|
||||
new LocalTargetInfo(targetCell),
|
||||
ProjectileHitFlags.All,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case BuildingBombardmentState.Idle:
|
||||
if (Find.TickManager.TicksGame >= nextBurstTick)
|
||||
{
|
||||
StartNextBurst();
|
||||
}
|
||||
break;
|
||||
|
||||
case BuildingBombardmentState.Bursting:
|
||||
UpdateBursting();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
Scribe_Values.Look(ref currentState, "currentState", BuildingBombardmentState.Idle);
|
||||
Scribe_Values.Look(ref nextBurstTick, "nextBurstTick", 0);
|
||||
Scribe_Values.Look(ref currentBurstCount, "currentBurstCount", 0);
|
||||
Scribe_Values.Look(ref nextInnerBurstTick, "nextInnerBurstTick", 0);
|
||||
Scribe_Collections.Look(ref currentTargets, "currentTargets", LookMode.LocalTargetInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BuildingBombardmentState
|
||||
{
|
||||
Idle,
|
||||
Targeting,
|
||||
Bursting
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_AbilityCallSkyfaller : CompProperties_AbilityEffect
|
||||
{
|
||||
// 基础配置
|
||||
public int delayTicks = 120; // 延时(刻)
|
||||
public ThingDef skyfallerDef; // 使用的 Skyfaller
|
||||
|
||||
// 预览配置
|
||||
public float previewRadius = 5f; // 预览半径
|
||||
public Color previewColor = new Color(1f, 0.5f, 0.1f, 0.3f); // 预览颜色
|
||||
|
||||
public CompProperties_AbilityCallSkyfaller()
|
||||
{
|
||||
this.compClass = typeof(CompAbilityEffect_CallSkyfaller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_BuildingBombardment : CompProperties
|
||||
{
|
||||
// 轰炸区域配置
|
||||
public float radius = 15f; // 作用半径
|
||||
|
||||
// 目标选择配置
|
||||
public bool targetEnemies = true; // 是否以敌人为目标
|
||||
public bool targetNeutrals = false; // 是否以中立单位为目标
|
||||
public bool targetAnimals = false; // 是否以动物为目标
|
||||
|
||||
// 召唤配置
|
||||
public int burstCount = 3; // 单组召唤数量
|
||||
public int innerBurstIntervalTicks = 10; // 同组召唤间隔
|
||||
public int burstIntervalTicks = 60; // 组间召唤间隔
|
||||
public float randomOffset = 2f; // 随机偏移量
|
||||
|
||||
// Skyfaller 配置
|
||||
public ThingDef skyfallerDef; // 使用的 Skyfaller
|
||||
public ThingDef projectileDef; // 备用的抛射体定义
|
||||
|
||||
public CompProperties_BuildingBombardment()
|
||||
{
|
||||
this.compClass = typeof(CompBuildingBombardment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class MapComponent_SkyfallerDelayed : MapComponent
|
||||
{
|
||||
private List<DelayedSkyfaller> scheduledSkyfallers = new List<DelayedSkyfaller>();
|
||||
|
||||
public MapComponent_SkyfallerDelayed(Map map) : base(map) { }
|
||||
|
||||
public void ScheduleSkyfaller(ThingDef skyfallerDef, IntVec3 targetCell, int delayTicks, Pawn caster = null)
|
||||
{
|
||||
scheduledSkyfallers.Add(new DelayedSkyfaller
|
||||
{
|
||||
skyfallerDef = skyfallerDef,
|
||||
targetCell = targetCell,
|
||||
spawnTick = Find.TickManager.TicksGame + delayTicks,
|
||||
caster = caster
|
||||
});
|
||||
}
|
||||
|
||||
public override void MapComponentTick()
|
||||
{
|
||||
base.MapComponentTick();
|
||||
|
||||
int currentTick = Find.TickManager.TicksGame;
|
||||
|
||||
// 检查并执行到期的召唤
|
||||
for (int i = scheduledSkyfallers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var skyfaller = scheduledSkyfallers[i];
|
||||
if (currentTick >= skyfaller.spawnTick)
|
||||
{
|
||||
SpawnSkyfaller(skyfaller);
|
||||
scheduledSkyfallers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnSkyfaller(DelayedSkyfaller delayedSkyfaller)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (delayedSkyfaller.skyfallerDef != null && delayedSkyfaller.targetCell.IsValid && delayedSkyfaller.targetCell.InBounds(map))
|
||||
{
|
||||
Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(delayedSkyfaller.skyfallerDef);
|
||||
GenSpawn.Spawn(skyfaller, delayedSkyfaller.targetCell, map);
|
||||
Log.Message($"[DelayedSkyfaller] Spawned skyfaller at {delayedSkyfaller.targetCell}");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[DelayedSkyfaller] Error spawning skyfaller: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Collections.Look(ref scheduledSkyfallers, "scheduledSkyfallers", LookMode.Deep);
|
||||
}
|
||||
}
|
||||
|
||||
public class DelayedSkyfaller : IExposable
|
||||
{
|
||||
public ThingDef skyfallerDef;
|
||||
public IntVec3 targetCell;
|
||||
public int spawnTick;
|
||||
public Pawn caster;
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
Scribe_Defs.Look(ref skyfallerDef, "skyfallerDef");
|
||||
Scribe_Values.Look(ref targetCell, "targetCell");
|
||||
Scribe_Values.Look(ref spawnTick, "spawnTick");
|
||||
Scribe_References.Look(ref caster, "caster");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,17 @@
|
||||
<Compile Include="Ability\CompAbilityEffect_ResearchPrereq.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityBombardment\CompAbilityEffect_Bombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityBombardment\CompProperties_AbilityBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCircularBombardment\CompAbilityEffect_CircularBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCircularBombardment\CompProperties_AbilityCircularBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\CompAbilityEffect_EnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\CompProperties_EnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLanceExtension.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySurveillanceBombardment\CompAbilityEffect_CallSkyfaller.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySurveillanceBombardment\CompBuildingBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySurveillanceBombardment\CompProperties_AbilityCallSkyfaller.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySurveillanceBombardment\CompProperties_BuildingBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySurveillanceBombardment\MapComponent_SkyfallerDelayed.cs" />
|
||||
<Compile Include="BuildingComp\Building_TurretGunHasSpeed.cs" />
|
||||
<Compile Include="BuildingComp\WULA_InitialFaction\CompProperties_InitialFaction.cs" />
|
||||
<Compile Include="BuildingComp\WULA_MechanoidRecycler\Building_MechanoidRecycler.cs" />
|
||||
|
||||
Reference in New Issue
Block a user