diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 53a448c..3a8885d 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml b/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml index 29e392a..34183df 100644 --- a/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml +++ b/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml @@ -62,7 +62,7 @@
  • - ARA_FlyOverShip + ARA_HiveShip Standard 0.01 20 diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml index a2952b9..65e180b 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml @@ -635,6 +635,7 @@
  • ARA_Surgery_Install_Slide_Patagium
  • ARA_Surgery_Install_Acidling_Pouch
  • ARA_Surgery_Install_Tumor_Pouch
  • +
  • ARA_Surgery_Install_Internal_Circulation_Lung
  • diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml index 8a36bdb..1917351 100644 --- a/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml +++ b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml @@ -1,8 +1,8 @@ - ARA_FlyOverShip - + ARA_HiveShip + ArachnaeSwarm.FlyOver Normal RealtimeOnly @@ -11,7 +11,7 @@ ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow Graphic_Single TransparentPostLight - (75,170) + (100,250) (195,195,195,45) @@ -23,32 +23,42 @@
  • ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow - 0.8 false +
  • false false false - Projectile + LightingOverlay +
  • + 60 + 虫巢母舰 + 苍穹之上传来嘶鸣,无数的虫群掠过殖民地,一只庞然大物投下的阴影遮天蔽日——\n\n虫巢母舰,阿拉克涅虫群中最大的节点生物,也是虫群永恒远征的支柱。现在,正有这样的一只骇人之物盘踞在殖民地的上空。它会使用一切手段摧毁你的防御,并投放无穷无尽的虫海淹没你的殖民者,直到其离开殖民地上方轨道。准备好迎接冲击! + ThreatBig + true + true + false +
  • true 1 10 - 15 + 25 false false false false - true - 虫群空调 - 虫巢舰正在空投一支虫族部队! + false + NegativeEvent + 虫群空投袭击 + 虫巢母舰正在空投一支虫族部队!这支部队会被空投到虫巢母舰附近,在地面战场上为虫巢母舰提供掩护。 @@ -92,6 +102,44 @@ false false
  • +
  • + 600 + 600 + 60 + 60 + 3 + 60 + 0.1 + + ARA_HiveShip_Fire_Incoming + + Skyfaller_Crashing + Explosion_Bomb + + true + true + 10 + + true + 战舰炮击警告 + 一艘敌方战舰正在对殖民地进行炮击!立即寻找掩护! + ThreatBig +
  • + + + ARA_HiveShip_Fire_Incoming + + (2, 2) + + Decelerate + Things/Skyfaller/SkyfallerShadowDropPod + (2.5, 2.5) + 10 + ARA_AcidBurn + 0.5 + 1 + +
    \ No newline at end of file diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index ba9bcf9..fa1964a 100644 Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json index e684f41..990db67 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -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": "" } ] } diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index b73cb11..4be990d 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -138,6 +138,12 @@ + + + + + + @@ -154,10 +160,8 @@ - - - - + + diff --git a/Source/ArachnaeSwarm/Thing_Comps/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.CS b/Source/ArachnaeSwarm/Flyover/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.cs similarity index 100% rename from Source/ArachnaeSwarm/Thing_Comps/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.CS rename to Source/ArachnaeSwarm/Flyover/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.cs diff --git a/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompProperties_SendLetterAfterTicks.cs b/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompProperties_SendLetterAfterTicks.cs new file mode 100644 index 0000000..ed120c8 --- /dev/null +++ b/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompProperties_SendLetterAfterTicks.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); + } + } +} diff --git a/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompSendLetterAfterTicks.cs b/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompSendLetterAfterTicks.cs new file mode 100644 index 0000000..d184b66 --- /dev/null +++ b/Source/ArachnaeSwarm/Flyover/ARA_SendLetterAfterTicks/CompSendLetterAfterTicks.cs @@ -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(); + } + } +} diff --git a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs new file mode 100644 index 0000000..8d02f30 --- /dev/null +++ b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs @@ -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 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); + } + } +} diff --git a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs new file mode 100644 index 0000000..ef3354e --- /dev/null +++ b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs @@ -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 previousTargets = new List(); + + 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(checkCell); + if (pawn != null && pawn.Faction == Faction.OfPlayer && pawn.RaceProps.Humanlike) + return true; + + // 检查玩家动物 + var animal = map.thingGrid.ThingAt(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 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; + } + } +} diff --git a/Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs similarity index 99% rename from Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs rename to Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs index eb58f2b..0dc2af5 100644 --- a/Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs +++ b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs @@ -318,7 +318,7 @@ namespace ArachnaeSwarm private void CreateStandardFlyOver(IntVec3 startPos, IntVec3 endPos) { // 确保有默认的飞越定义 - ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase.GetNamedSilentFail("ARA_FlyOverShip"); + ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase.GetNamedSilentFail("ARA_HiveShip"); if (flyOverDef == null) { Log.Warning("No fly over def specified for standard fly over"); diff --git a/Source/ArachnaeSwarm/Thing/CompProperties_AbilitySpawnFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs similarity index 100% rename from Source/ArachnaeSwarm/Thing/CompProperties_AbilitySpawnFlyOver.cs rename to Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs diff --git a/Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs similarity index 100% rename from Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs rename to Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs diff --git a/Source/ArachnaeSwarm/Thing/示范.md b/Source/ArachnaeSwarm/Thing/示范.md deleted file mode 100644 index 4b0acc2..0000000 --- a/Source/ArachnaeSwarm/Thing/示范.md +++ /dev/null @@ -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 { 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 - ); -} \ No newline at end of file