1
This commit is contained in:
Binary file not shown.
@@ -1,24 +1,7 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_flyoverdroppod\\compproperties_flyoverdroppod.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_flyoverdroppod\\compproperties_flyoverdroppod.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\compproperties_abilityspawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\compproperties_abilityspawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
}
|
||||
],
|
||||
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
|
||||
"Documents": [],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
@@ -26,63 +9,11 @@
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"SelectedChildIndex": -1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "CompProperties_FlyOverDropPod.CS",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
|
||||
"RelativeDocumentMoniker": "Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
|
||||
"RelativeToolTip": "Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAFoAAAAFAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-10-27T16:23:17.672Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "CompProperties_AbilitySpawnFlyOver.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompProperties_AbilitySpawnFlyOver.cs",
|
||||
"RelativeDocumentMoniker": "Thing\\CompProperties_AbilitySpawnFlyOver.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompProperties_AbilitySpawnFlyOver.cs",
|
||||
"RelativeToolTip": "Thing\\CompProperties_AbilitySpawnFlyOver.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAACkAAAAFAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-10-27T13:29:49.568Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "ThingclassFlyOver.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\ThingclassFlyOver.cs",
|
||||
"RelativeDocumentMoniker": "Thing\\ThingclassFlyOver.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\ThingclassFlyOver.cs",
|
||||
"RelativeToolTip": "Thing\\ThingclassFlyOver.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAB8AAAAPAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-10-27T13:28:54.42Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "CompAbilityEffect_SpawnFlyOver.cs",
|
||||
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompAbilityEffect_SpawnFlyOver.cs",
|
||||
"RelativeDocumentMoniker": "Thing\\CompAbilityEffect_SpawnFlyOver.cs",
|
||||
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompAbilityEffect_SpawnFlyOver.cs",
|
||||
"RelativeToolTip": "Thing\\CompAbilityEffect_SpawnFlyOver.cs",
|
||||
"ViewState": "AgIAAO0AAAAAAAAAAAAiwDABAAAJAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-10-27T13:06:24.762Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -138,6 +138,12 @@
|
||||
<Compile Include="EventSystem\EventVariableManager.cs" />
|
||||
<Compile Include="EventSystem\Letter_EventChoice.cs" />
|
||||
<Compile Include="EventSystem\QuestNode_Root_EventLetter.cs" />
|
||||
<Compile Include="Flyover\ARA_SendLetterAfterTicks\CompProperties_SendLetterAfterTicks.cs" />
|
||||
<Compile Include="Flyover\ARA_SendLetterAfterTicks\CompSendLetterAfterTicks.cs" />
|
||||
<Compile Include="Flyover\ARA_ShipArtillery\CompProperties_ShipArtillery.cs" />
|
||||
<Compile Include="Flyover\ARA_ShipArtillery\CompShipArtillery.cs" />
|
||||
<Compile Include="Flyover\ARA_SpawnFlyOver\CompAbilityEffect_SpawnFlyOver.cs" />
|
||||
<Compile Include="Flyover\ARA_SpawnFlyOver\CompProperties_AbilitySpawnFlyOver.cs" />
|
||||
<Compile Include="HediffGiver\HediffGiver_NonPlayerFaction.cs" />
|
||||
<Compile Include="Hediffs\ARA_DrawMoteInRange\HediffComp_DrawMoteInRange.cs" />
|
||||
<Compile Include="Hediffs\ARA_HediffTerrainSpawn\CompHediffTerrainSpawn.cs" />
|
||||
@@ -154,10 +160,8 @@
|
||||
<Compile Include="Storyteller\IncidentWorker_CustomRaid.cs" />
|
||||
<Compile Include="Storyteller\RaidWaveDef.cs" />
|
||||
<Compile Include="Storyteller\RaidWavePoolDef.cs" />
|
||||
<Compile Include="Thing\CompAbilityEffect_SpawnFlyOver.cs" />
|
||||
<Compile Include="Thing\CompProperties_AbilitySpawnFlyOver.cs" />
|
||||
<Compile Include="Thing\ThingclassFlyOver.cs" />
|
||||
<Compile Include="Thing_Comps\ARA_FlyOverDropPod\CompProperties_FlyOverDropPod.CS" />
|
||||
<Compile Include="Flyover\ThingclassFlyOver.cs" />
|
||||
<Compile Include="Flyover\ARA_FlyOverDropPod\CompProperties_FlyOverDropPod.cs" />
|
||||
<Compile Include="Verbs\Verb_ShootWithOffset.cs" />
|
||||
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompAbilityEffect_AbilityShowTemperatureRange.cs" />
|
||||
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompProperties_AbilityShowTemperatureRange.cs" />
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_SendLetterAfterTicks : CompProperties
|
||||
{
|
||||
public int ticksDelay = 600; // 默认10秒 (60 ticks/秒)
|
||||
public string letterLabel;
|
||||
public string letterText;
|
||||
public LetterDef letterDef = LetterDefOf.NeutralEvent;
|
||||
public bool onlySendOnce = true;
|
||||
public bool requireOnMap = true;
|
||||
public bool destroyAfterSending = false;
|
||||
|
||||
public CompProperties_SendLetterAfterTicks()
|
||||
{
|
||||
compClass = typeof(CompSendLetterAfterTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompSendLetterAfterTicks : ThingComp
|
||||
{
|
||||
public CompProperties_SendLetterAfterTicks Props => (CompProperties_SendLetterAfterTicks)props;
|
||||
|
||||
private int ticksPassed = 0;
|
||||
private bool letterSent = false;
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
// 如果已经发送过且只发送一次,则跳过
|
||||
if (letterSent && Props.onlySendOnce)
|
||||
return;
|
||||
|
||||
// 如果需要在地图上但父物体不在有效地图上,则跳过
|
||||
if (Props.requireOnMap && (parent.Map == null || !parent.Spawned))
|
||||
return;
|
||||
|
||||
ticksPassed++;
|
||||
|
||||
// 检查是否达到延迟时间
|
||||
if (ticksPassed >= Props.ticksDelay)
|
||||
{
|
||||
SendLetter();
|
||||
|
||||
if (Props.destroyAfterSending)
|
||||
{
|
||||
parent.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendLetter()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否有有效的信件内容
|
||||
if (Props.letterLabel.NullOrEmpty() && Props.letterText.NullOrEmpty())
|
||||
{
|
||||
Log.Warning($"CompSendLetterAfterTicks: No letter content defined for {parent.def.defName}");
|
||||
return;
|
||||
}
|
||||
|
||||
string label = Props.letterLabel ?? "DefaultLetterLabel".Translate();
|
||||
string text = Props.letterText ?? "DefaultLetterText".Translate();
|
||||
|
||||
// 创建信件
|
||||
Letter letter = LetterMaker.MakeLetter(
|
||||
label,
|
||||
text,
|
||||
Props.letterDef,
|
||||
lookTargets: new LookTargets(parent)
|
||||
);
|
||||
|
||||
// 发送信件
|
||||
Find.LetterStack.ReceiveLetter(letter);
|
||||
|
||||
letterSent = true;
|
||||
|
||||
Log.Message($"Letter sent from {parent.def.defName} after {ticksPassed} ticks");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"Error sending letter from {parent.def.defName}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref ticksPassed, "ticksPassed", 0);
|
||||
Scribe_Values.Look(ref letterSent, "letterSent", false);
|
||||
}
|
||||
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
if (!letterSent && Props.requireOnMap && parent.Spawned)
|
||||
{
|
||||
int ticksRemaining = Props.ticksDelay - ticksPassed;
|
||||
if (ticksRemaining > 0)
|
||||
{
|
||||
return $"LetterInspection_TimeRemaining".Translate(ticksRemaining.ToStringTicksToPeriod());
|
||||
}
|
||||
}
|
||||
return base.CompInspectStringExtra();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompProperties_ShipArtillery : CompProperties
|
||||
{
|
||||
// 攻击配置
|
||||
public int ticksBetweenAttacks = 600; // 攻击间隔(tick)
|
||||
public int attackDurationTicks = 1800; // 攻击持续时间(tick)
|
||||
public int warmupTicks = 120; // 预热时间(tick)
|
||||
public bool continuousAttack = false; // 是否持续攻击直到飞越结束
|
||||
|
||||
// 目标区域配置
|
||||
public float attackRadius = 15f; // 攻击半径
|
||||
public IntVec3 targetOffset = IntVec3.Zero; // 目标偏移
|
||||
public bool useRandomTargets = true; // 是否使用随机目标
|
||||
public bool avoidPlayerAssets = true; // 是否避开玩家资产
|
||||
public float playerAssetAvoidanceRadius = 5f; // 避开玩家资产的半径
|
||||
|
||||
// 新增:无视保护机制的概率
|
||||
public float ignoreProtectionChance = 0f; // 0-1之间的值,0表示从不无视,1表示总是无视
|
||||
|
||||
// Skyfaller 配置
|
||||
public ThingDef skyfallerDef; // 使用的 Skyfaller 定义
|
||||
public List<ThingDef> skyfallerDefs; // 多个 Skyfaller 定义(随机选择)
|
||||
public int shellsPerVolley = 1; // 每轮齐射的炮弹数量
|
||||
public float scatterRadius = 3f; // 散布半径
|
||||
public bool useDifferentShells = false; // 是否使用不同类型的炮弹
|
||||
|
||||
// 音效配置
|
||||
public SoundDef attackSound; // 攻击音效
|
||||
public SoundDef impactSound; // 撞击音效
|
||||
|
||||
// 视觉效果
|
||||
public EffecterDef warmupEffect; // 预热效果
|
||||
public EffecterDef attackEffect; // 攻击效果
|
||||
public FleckDef warmupFleck; // 预热粒子
|
||||
public FleckDef attackFleck; // 攻击粒子
|
||||
|
||||
// 避免击中飞越物体本身
|
||||
public bool avoidHittingFlyOver = true;
|
||||
|
||||
// 信件通知
|
||||
public bool sendAttackLetter = true; // 是否发送攻击信件
|
||||
public string customLetterLabel; // 自定义信件标题
|
||||
public string customLetterText; // 自定义信件内容
|
||||
public LetterDef letterDef = LetterDefOf.ThreatBig; // 信件类型
|
||||
|
||||
public CompProperties_ShipArtillery()
|
||||
{
|
||||
compClass = typeof(CompShipArtillery);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CompShipArtillery : ThingComp
|
||||
{
|
||||
public CompProperties_ShipArtillery Props => (CompProperties_ShipArtillery)props;
|
||||
|
||||
// 状态变量
|
||||
private int ticksUntilNextAttack = 0;
|
||||
private int attackTicksRemaining = 0;
|
||||
private int warmupTicksRemaining = 0;
|
||||
private bool isAttacking = false;
|
||||
private bool isWarmingUp = false;
|
||||
private IntVec3 currentTarget;
|
||||
private Effecter warmupEffecter;
|
||||
private Effecter attackEffecter;
|
||||
|
||||
// 目标跟踪
|
||||
private List<IntVec3> previousTargets = new List<IntVec3>();
|
||||
|
||||
public override void Initialize(CompProperties props)
|
||||
{
|
||||
base.Initialize(props);
|
||||
|
||||
ticksUntilNextAttack = Props.ticksBetweenAttacks;
|
||||
|
||||
Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius, Scatter: {Props.scatterRadius}");
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
if (parent is not FlyOver flyOver || !flyOver.Spawned || flyOver.Map == null)
|
||||
return;
|
||||
|
||||
// 更新预热状态
|
||||
if (isWarmingUp)
|
||||
{
|
||||
UpdateWarmup(flyOver);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新攻击状态
|
||||
if (isAttacking)
|
||||
{
|
||||
UpdateAttack(flyOver);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否开始攻击
|
||||
if (ticksUntilNextAttack <= 0)
|
||||
{
|
||||
StartAttack(flyOver);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextAttack--;
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAttack(FlyOver flyOver)
|
||||
{
|
||||
if (!CanAttack(flyOver))
|
||||
return;
|
||||
|
||||
// 选择目标区域
|
||||
currentTarget = SelectTarget(flyOver);
|
||||
|
||||
if (!currentTarget.IsValid || !currentTarget.InBounds(flyOver.Map))
|
||||
{
|
||||
Log.Warning("Ship Artillery: Invalid target selected, skipping attack");
|
||||
ticksUntilNextAttack = Props.ticksBetweenAttacks;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Message($"Ship Artillery starting attack on target area: {currentTarget} (attack radius: {Props.attackRadius})");
|
||||
|
||||
// 开始预热
|
||||
isWarmingUp = true;
|
||||
warmupTicksRemaining = Props.warmupTicks;
|
||||
|
||||
// 启动预热效果
|
||||
if (Props.warmupEffect != null)
|
||||
{
|
||||
warmupEffecter = Props.warmupEffect.Spawn();
|
||||
warmupEffecter.Trigger(new TargetInfo(currentTarget, flyOver.Map), new TargetInfo(currentTarget, flyOver.Map));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWarmup(FlyOver flyOver)
|
||||
{
|
||||
warmupTicksRemaining--;
|
||||
|
||||
// 维持预热效果
|
||||
if (warmupEffecter != null)
|
||||
{
|
||||
warmupEffecter.EffectTick(new TargetInfo(currentTarget, flyOver.Map), new TargetInfo(currentTarget, flyOver.Map));
|
||||
}
|
||||
|
||||
// 生成预热粒子
|
||||
if (Props.warmupFleck != null && Rand.MTBEventOccurs(0.1f, 1f, 1f))
|
||||
{
|
||||
FleckMaker.Static(currentTarget.ToVector3Shifted(), flyOver.Map, Props.warmupFleck);
|
||||
}
|
||||
|
||||
// 预热完成,开始攻击
|
||||
if (warmupTicksRemaining <= 0)
|
||||
{
|
||||
StartFiring(flyOver);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartFiring(FlyOver flyOver)
|
||||
{
|
||||
isWarmingUp = false;
|
||||
isAttacking = true;
|
||||
attackTicksRemaining = Props.attackDurationTicks;
|
||||
|
||||
// 清理预热效果
|
||||
warmupEffecter?.Cleanup();
|
||||
warmupEffecter = null;
|
||||
|
||||
// 启动攻击效果
|
||||
if (Props.attackEffect != null)
|
||||
{
|
||||
attackEffecter = Props.attackEffect.Spawn();
|
||||
}
|
||||
|
||||
Log.Message($"Ship Artillery started firing at area {currentTarget} (scatter radius: {Props.scatterRadius})");
|
||||
|
||||
// 发送攻击通知
|
||||
if (Props.sendAttackLetter)
|
||||
{
|
||||
SendAttackLetter(flyOver);
|
||||
}
|
||||
|
||||
// 立即执行第一轮齐射
|
||||
ExecuteVolley(flyOver);
|
||||
}
|
||||
|
||||
private void UpdateAttack(FlyOver flyOver)
|
||||
{
|
||||
attackTicksRemaining--;
|
||||
|
||||
// 维持攻击效果
|
||||
if (attackEffecter != null)
|
||||
{
|
||||
attackEffecter.EffectTick(new TargetInfo(currentTarget, flyOver.Map), new TargetInfo(currentTarget, flyOver.Map));
|
||||
}
|
||||
|
||||
// 在攻击期间定期发射炮弹
|
||||
if (attackTicksRemaining % 60 == 0) // 每秒发射一次
|
||||
{
|
||||
ExecuteVolley(flyOver);
|
||||
}
|
||||
|
||||
// 生成攻击粒子
|
||||
if (Props.attackFleck != null && Rand.MTBEventOccurs(0.2f, 1f, 1f))
|
||||
{
|
||||
Vector3 randomOffset = new Vector3(Rand.Range(-3f, 3f), 0f, Rand.Range(-3f, 3f));
|
||||
FleckMaker.Static((currentTarget.ToVector3Shifted() + randomOffset), flyOver.Map, Props.attackFleck);
|
||||
}
|
||||
|
||||
// 攻击结束
|
||||
if (attackTicksRemaining <= 0)
|
||||
{
|
||||
EndAttack(flyOver);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteVolley(FlyOver flyOver)
|
||||
{
|
||||
for (int i = 0; i < Props.shellsPerVolley; i++)
|
||||
{
|
||||
FireShell(flyOver);
|
||||
}
|
||||
}
|
||||
|
||||
private void FireShell(FlyOver flyOver)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 选择炮弹类型
|
||||
ThingDef shellDef = SelectShellDef();
|
||||
if (shellDef == null)
|
||||
{
|
||||
Log.Error("Ship Artillery: No valid shell def found");
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算散布位置 - 使用新的散射逻辑
|
||||
IntVec3 shellTarget = GetScatteredTarget(flyOver);
|
||||
|
||||
// 创建 Skyfaller
|
||||
Skyfaller shell = (Skyfaller)ThingMaker.MakeThing(shellDef);
|
||||
|
||||
// 生成炮弹
|
||||
GenSpawn.Spawn(shell, GetLaunchPosition(flyOver), flyOver.Map);
|
||||
|
||||
float distanceFromCenter = shellTarget.DistanceTo(currentTarget);
|
||||
Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1}, scatter radius: {Props.scatterRadius})");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"Error firing ship artillery shell: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private ThingDef SelectShellDef()
|
||||
{
|
||||
if (Props.skyfallerDefs != null && Props.skyfallerDefs.Count > 0)
|
||||
{
|
||||
if (Props.useDifferentShells)
|
||||
{
|
||||
return Props.skyfallerDefs.RandomElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Props.skyfallerDefs[0];
|
||||
}
|
||||
}
|
||||
|
||||
return Props.skyfallerDef;
|
||||
}
|
||||
|
||||
private IntVec3 GetLaunchPosition(FlyOver flyOver)
|
||||
{
|
||||
// 从飞越物体的位置发射
|
||||
IntVec3 launchPos = flyOver.Position;
|
||||
|
||||
// 确保发射位置在地图边界内
|
||||
if (!launchPos.InBounds(flyOver.Map))
|
||||
{
|
||||
launchPos = flyOver.Map.Center;
|
||||
}
|
||||
|
||||
return launchPos;
|
||||
}
|
||||
|
||||
// 新的散射逻辑 - 基于 FlyOverDropPods 的实现方式
|
||||
private IntVec3 GetScatteredTarget(FlyOver flyOver)
|
||||
{
|
||||
// 基础目标(攻击区域中心)
|
||||
IntVec3 baseTarget = currentTarget;
|
||||
|
||||
// 如果散射半径为0,直接返回基础目标
|
||||
if (Props.scatterRadius <= 0)
|
||||
return baseTarget;
|
||||
|
||||
// 使用类似 FlyOverDropPods 的散射逻辑
|
||||
for (int i = 0; i < 20; i++) // 尝试20次找到有效位置
|
||||
{
|
||||
// 在散射半径内随机选择位置
|
||||
IntVec3 scatteredPos = baseTarget;
|
||||
|
||||
// 使用圆形散射而不是方形
|
||||
float angle = Rand.Range(0f, 360f);
|
||||
float distance = Rand.Range(0f, Props.scatterRadius);
|
||||
|
||||
scatteredPos.x += Mathf.RoundToInt(Mathf.Cos(angle * Mathf.Deg2Rad) * distance);
|
||||
scatteredPos.z += Mathf.RoundToInt(Mathf.Sin(angle * Mathf.Deg2Rad) * distance);
|
||||
|
||||
// 检查是否无视保护机制
|
||||
bool ignoreProtectionThisShot = Rand.Value < Props.ignoreProtectionChance;
|
||||
|
||||
if (scatteredPos.InBounds(flyOver.Map) && IsValidTarget(scatteredPos, flyOver.Map, ignoreProtectionThisShot))
|
||||
{
|
||||
if (ignoreProtectionThisShot)
|
||||
{
|
||||
Log.Warning($"Ship Artillery: Protection ignored! Shell may hit player assets at {scatteredPos}");
|
||||
}
|
||||
return scatteredPos;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找不到有效位置,使用基础目标
|
||||
Log.Warning($"Ship Artillery: Could not find valid scattered target after 20 attempts, using base target");
|
||||
return baseTarget;
|
||||
}
|
||||
|
||||
private IntVec3 SelectTarget(FlyOver flyOver)
|
||||
{
|
||||
// 获取飞越物体当前位置作为基础中心
|
||||
IntVec3 flyOverPos = GetFlyOverPosition(flyOver);
|
||||
IntVec3 center = flyOverPos + Props.targetOffset;
|
||||
|
||||
Log.Message($"FlyOver position: {flyOverPos}, Center for targeting: {center}");
|
||||
|
||||
// 在攻击半径内选择随机目标
|
||||
return FindRandomTargetInRadius(center, flyOver.Map, Props.attackRadius);
|
||||
}
|
||||
|
||||
// 改进的飞越物体位置获取
|
||||
private IntVec3 GetFlyOverPosition(FlyOver flyOver)
|
||||
{
|
||||
// 优先使用 DrawPos,因为它反映实际视觉位置
|
||||
Vector3 drawPos = flyOver.DrawPos;
|
||||
IntVec3 result = new IntVec3(
|
||||
Mathf.RoundToInt(drawPos.x),
|
||||
0,
|
||||
Mathf.RoundToInt(drawPos.z)
|
||||
);
|
||||
|
||||
// 如果 DrawPos 无效,回退到 Position
|
||||
if (!result.InBounds(flyOver.Map))
|
||||
{
|
||||
result = flyOver.Position;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 新的目标查找逻辑 - 基于攻击半径
|
||||
private IntVec3 FindRandomTargetInRadius(IntVec3 center, Map map, float radius)
|
||||
{
|
||||
Log.Message($"Finding target around {center} with radius {radius}");
|
||||
|
||||
// 如果半径为0,直接返回中心
|
||||
if (radius <= 0)
|
||||
return center;
|
||||
|
||||
bool ignoreProtectionForThisTarget = Rand.Value < Props.ignoreProtectionChance;
|
||||
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
// 在圆形区域内随机选择
|
||||
float angle = Rand.Range(0f, 360f);
|
||||
float distance = Rand.Range(0f, radius);
|
||||
|
||||
IntVec3 potentialTarget = center;
|
||||
potentialTarget.x += Mathf.RoundToInt(Mathf.Cos(angle * Mathf.Deg2Rad) * distance);
|
||||
potentialTarget.z += Mathf.RoundToInt(Mathf.Sin(angle * Mathf.Deg2Rad) * distance);
|
||||
|
||||
if (potentialTarget.InBounds(map) && IsValidTarget(potentialTarget, map, ignoreProtectionForThisTarget))
|
||||
{
|
||||
// 避免重复攻击同一位置
|
||||
if (!previousTargets.Contains(potentialTarget) || previousTargets.Count > 10)
|
||||
{
|
||||
if (previousTargets.Count > 10)
|
||||
previousTargets.RemoveAt(0);
|
||||
|
||||
previousTargets.Add(potentialTarget);
|
||||
|
||||
float actualDistance = potentialTarget.DistanceTo(center);
|
||||
Log.Message($"Found valid target at {potentialTarget} (distance from center: {actualDistance:F1})");
|
||||
|
||||
if (ignoreProtectionForThisTarget)
|
||||
{
|
||||
Log.Warning($"Protection ignored for target selection! May target player assets.");
|
||||
}
|
||||
|
||||
return potentialTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:使用地图随机位置
|
||||
Log.Warning("Could not find valid target in radius, using fallback");
|
||||
CellRect mapRect = CellRect.WholeMap(map);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
IntVec3 fallbackTarget = mapRect.RandomCell;
|
||||
if (IsValidTarget(fallbackTarget, map, ignoreProtectionForThisTarget))
|
||||
{
|
||||
return fallbackTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// 最终回退:使用中心
|
||||
return center;
|
||||
}
|
||||
|
||||
// 检查是否靠近玩家资产
|
||||
private bool IsNearPlayerAssets(IntVec3 cell, Map map)
|
||||
{
|
||||
if (!Props.avoidPlayerAssets)
|
||||
return false;
|
||||
|
||||
foreach (IntVec3 checkCell in GenRadial.RadialCellsAround(cell, Props.playerAssetAvoidanceRadius, true))
|
||||
{
|
||||
if (!checkCell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查玩家建筑
|
||||
var building = checkCell.GetEdifice(map);
|
||||
if (building != null && building.Faction == Faction.OfPlayer)
|
||||
return true;
|
||||
|
||||
// 检查玩家殖民者
|
||||
var pawn = map.thingGrid.ThingAt<Pawn>(checkCell);
|
||||
if (pawn != null && pawn.Faction == Faction.OfPlayer && pawn.RaceProps.Humanlike)
|
||||
return true;
|
||||
|
||||
// 检查玩家动物
|
||||
var animal = map.thingGrid.ThingAt<Pawn>(checkCell);
|
||||
if (animal != null && animal.Faction == Faction.OfPlayer && animal.RaceProps.Animal)
|
||||
return true;
|
||||
|
||||
// 检查玩家物品
|
||||
var items = checkCell.GetThingList(map);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Faction == Faction.OfPlayer && item.def.category == ThingCategory.Item)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(IntVec3 target, Map map, bool ignoreProtection = false)
|
||||
{
|
||||
if (!target.InBounds(map))
|
||||
return false;
|
||||
|
||||
// 避开玩家资产(除非无视保护机制)
|
||||
if (Props.avoidPlayerAssets && !ignoreProtection && IsNearPlayerAssets(target, map))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 避免击中飞越物体本身
|
||||
if (Props.avoidHittingFlyOver)
|
||||
{
|
||||
float distanceToFlyOver = target.DistanceTo(parent.Position);
|
||||
if (distanceToFlyOver < 10f) // 增加安全距离
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CanAttack(FlyOver flyOver)
|
||||
{
|
||||
if (flyOver.Map == null)
|
||||
return false;
|
||||
|
||||
if (flyOver.hasCompleted)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EndAttack(FlyOver flyOver)
|
||||
{
|
||||
isAttacking = false;
|
||||
|
||||
// 清理效果
|
||||
attackEffecter?.Cleanup();
|
||||
attackEffecter = null;
|
||||
|
||||
// 重置计时器
|
||||
if (Props.continuousAttack && !flyOver.hasCompleted)
|
||||
{
|
||||
ticksUntilNextAttack = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ticksUntilNextAttack = Props.ticksBetweenAttacks;
|
||||
}
|
||||
|
||||
Log.Message($"Ship Artillery attack ended");
|
||||
}
|
||||
|
||||
private void SendAttackLetter(FlyOver flyOver)
|
||||
{
|
||||
try
|
||||
{
|
||||
string label = Props.customLetterLabel ?? "ShipArtilleryAttack".Translate();
|
||||
string text = Props.customLetterText ?? "ShipArtilleryAttackDesc".Translate();
|
||||
|
||||
Find.LetterStack.ReceiveLetter(
|
||||
label,
|
||||
text,
|
||||
Props.letterDef,
|
||||
new TargetInfo(currentTarget, flyOver.Map)
|
||||
);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"Error sending ship artillery letter: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref ticksUntilNextAttack, "ticksUntilNextAttack", 0);
|
||||
Scribe_Values.Look(ref attackTicksRemaining, "attackTicksRemaining", 0);
|
||||
Scribe_Values.Look(ref warmupTicksRemaining, "warmupTicksRemaining", 0);
|
||||
Scribe_Values.Look(ref isAttacking, "isAttacking", false);
|
||||
Scribe_Values.Look(ref isWarmingUp, "isWarmingUp", false);
|
||||
Scribe_Values.Look(ref currentTarget, "currentTarget");
|
||||
Scribe_Collections.Look(ref previousTargets, "previousTargets", LookMode.Value);
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
if (DebugSettings.ShowDevGizmos && parent is FlyOver)
|
||||
{
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = "Dev: Trigger Artillery Attack",
|
||||
action = () => StartAttack(parent as FlyOver)
|
||||
};
|
||||
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = "Dev: Fire Single Shell",
|
||||
action = () => FireShell(parent as FlyOver)
|
||||
};
|
||||
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"Dev: Status - Next: {ticksUntilNextAttack}, Attacking: {isAttacking}",
|
||||
action = () => {}
|
||||
};
|
||||
|
||||
yield return new Command_Action
|
||||
{
|
||||
defaultLabel = $"Dev: Debug Position Info",
|
||||
action = () =>
|
||||
{
|
||||
if (parent is FlyOver flyOver)
|
||||
{
|
||||
IntVec3 flyOverPos = GetFlyOverPosition(flyOver);
|
||||
Log.Message($"FlyOver - DrawPos: {flyOver.DrawPos}, Position: {flyOver.Position}, Calculated: {flyOverPos}");
|
||||
Log.Message($"Current Target: {currentTarget}, Distance: {flyOverPos.DistanceTo(currentTarget):F1}");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerAttack()
|
||||
{
|
||||
if (parent is FlyOver flyOver)
|
||||
{
|
||||
StartAttack(flyOver);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTarget(IntVec3 target)
|
||||
{
|
||||
currentTarget = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,7 +318,7 @@ namespace ArachnaeSwarm
|
||||
private void CreateStandardFlyOver(IntVec3 startPos, IntVec3 endPos)
|
||||
{
|
||||
// 确保有默认的飞越定义
|
||||
ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase<ThingDef>.GetNamedSilentFail("ARA_FlyOverShip");
|
||||
ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase<ThingDef>.GetNamedSilentFail("ARA_HiveShip");
|
||||
if (flyOverDef == null)
|
||||
{
|
||||
Log.Warning("No fly over def specified for standard fly over");
|
||||
@@ -1,76 +0,0 @@
|
||||
// 在其他文件中调用:
|
||||
// 1. 简单飞越
|
||||
public void CreateSimpleFlyOverExample()
|
||||
{
|
||||
FlyOver flyOver = FlyOverUtility.CreateFlyOver(
|
||||
flyOverDef: ThingDefOf.FlyOverShip,
|
||||
map: Find.CurrentMap,
|
||||
startEdge: Rot4.West, // 从西边开始
|
||||
endEdge: Rot4.East, // 到东边结束
|
||||
speed: 1.5f,
|
||||
altitude: 12f
|
||||
);
|
||||
}
|
||||
// 2. 货运飞越
|
||||
public void CreateCargoFlyOverExample()
|
||||
{
|
||||
var cargoItems = new List<(ThingDef, int)>
|
||||
{
|
||||
(ThingDefOf.Steel, 50),
|
||||
(ThingDefOf.Plasteel, 20),
|
||||
(ThingDefOf.Component, 5)
|
||||
};
|
||||
FlyOver cargo = FlyOverUtility.CreateCargoFlyOver(
|
||||
map: Find.CurrentMap,
|
||||
cargoItems: cargoItems,
|
||||
startEdge: Rot4.North,
|
||||
speed: 0.8f
|
||||
);
|
||||
}
|
||||
// 3. 侦察飞越
|
||||
public void CreateScoutFlyOverExample()
|
||||
{
|
||||
FlyOver scout = FlyOverUtility.CreateScoutFlyOver(
|
||||
map: Find.CurrentMap,
|
||||
startEdge: Rot4.South,
|
||||
endEdge: Rot4.North
|
||||
);
|
||||
}
|
||||
// 4. 轰炸飞越
|
||||
public void CreateBombingRunExample()
|
||||
{
|
||||
IntVec3 target = new IntVec3(50, 0, 50); // 目标坐标
|
||||
|
||||
FlyOver bomber = FlyOverUtility.CreateBombingFlyOver(
|
||||
map: Find.CurrentMap,
|
||||
targetPosition: target,
|
||||
bombDef: ThingDefOf.Bomb,
|
||||
startEdge: Rot4.West
|
||||
);
|
||||
}
|
||||
// 5. 使用参数对象
|
||||
public void CreateWithParamsExample()
|
||||
{
|
||||
var flyOverParams = new FlyOverParams
|
||||
{
|
||||
flyOverDef = ThingDefOf.FlyOverShip,
|
||||
map = Find.CurrentMap,
|
||||
startEdge = Rot4.East,
|
||||
endEdge = Rot4.West,
|
||||
speed = 2f,
|
||||
altitude = 15f,
|
||||
contents = new List<Thing> { ThingMaker.MakeThing(ThingDefOf.Gold) },
|
||||
spawnContents = true
|
||||
};
|
||||
// 可以轻松扩展FlyOverUtility来支持参数对象
|
||||
FlyOver flyOver = FlyOverUtility.CreateFlyOverWithContents(
|
||||
flyOverParams.flyOverDef,
|
||||
flyOverParams.map,
|
||||
flyOverParams.contents,
|
||||
flyOverParams.spawnContents,
|
||||
flyOverParams.startEdge,
|
||||
flyOverParams.endEdge,
|
||||
flyOverParams.speed,
|
||||
flyOverParams.altitude
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user