diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 103f7c27..e6153256 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml b/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml new file mode 100644 index 00000000..6cbc6ff9 --- /dev/null +++ b/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml @@ -0,0 +1,98 @@ + + + + Wula_Mech_Mobile_Factory_Produce + + 立刻生产10台CRm-51"兵蚁",快速组织一道近战阵线。 + Wula/UI/Abilities/Wula_Mech_Mobile_Factory_Produce + 5000 + 5 + true + true + 300 + true + false + + Verb_CastAbility + 24 + 0 + WarqueenWarUrchinsSpawned + false + false + + true + + + +
  • + Wula_Mech_Mobile_Factory_Produce_Proj + 10 +
  • +
    +
    + + Wula_Mech_Mobile_Factory_Produce_Proj + + Projectile_SpawnsPawnZeroAge + + Wula/Things/WULA_Mech_Flyer/WULA_Mech_Flyer_south + Graphic_Single + + + 41 + WULA_Mech_Flyer + true + Bullet + 1 + + + + + WULA_PsiCrusher + + 释放纯净的灵能能量,直接摧毁面前扇形区域内的所有目标。 + UI/Abilities/FireSpew + True + False + true + true + true + 600 + + Verb_CastAbility + 24 + 0 + WarqueenWarUrchinsSpawned + + true + true + + false + + Mote_HellsphereCannon_Aim + Mote_HellsphereCannon_Charge + 1.07 + 32 + Mote_HellsphereCannon_Target + + + true + false + true + true + true + + + +
  • + 12 + 5 + true + false + false + Fire_Spew + Fire_SpewShort +
  • +
    +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/PawnKinds/PawnKinds_Wula.xml b/1.6/1.6/Defs/PawnKinds/PawnKinds_Wula.xml index 53f61fd3..434798fd 100644 --- a/1.6/1.6/Defs/PawnKinds/PawnKinds_Wula.xml +++ b/1.6/1.6/Defs/PawnKinds/PawnKinds_Wula.xml @@ -231,52 +231,45 @@ 1 9999~9999 - - Wula_Mech_Mobile_Factory_Produce - - 立刻生产10台CRm-51"兵蚁",快速组织一道近战阵线。 - Wula/UI/Abilities/Wula_Mech_Mobile_Factory_Produce - 5000 - 5 - true - true - 300 - true - false - - Verb_CastAbility - 24 - 0 - WarqueenWarUrchinsSpawned - false - false - - true - - - -
  • - Wula_Mech_Mobile_Factory_Produce_Proj - 10 + + + Wula_Psi_Titan + + Wula_Psi_Titan + 1000 + false + PlayerColony + false + true + + Wula/Things/Wula_Mech_Mobile_Factory/Flying/Wula_Mech_Mobile_Factory_Flying_ + 1 + 1 + 2 + false + + +
  • + + Wula/Things/Wula_Psi_Titan/Bodies/Naked_Thin + Wula/Things/WULA_Cat/AllegianceOverlays/None + CutoutWithOverlay + Graphic_Multi + 9 + + (1.4, 1.8, 1.4) + +
  • -
    -
    - - Wula_Mech_Mobile_Factory_Produce_Proj - - Projectile_SpawnsPawnZeroAge - - Wula/Things/WULA_Mech_Flyer/WULA_Mech_Flyer_south - Graphic_Single - - - 41 - WULA_Mech_Flyer - true - Bullet - 1 - - + + 99999~99999 + + 0.7 + + +
  • Wula_Mech_Mobile_Factory_Produce
  • +
    + diff --git a/1.6/1.6/Defs/QuestScriptDefs/WULA_Scripts_Utility.xml b/1.6/1.6/Defs/QuestScriptDefs/WULA_Scripts_Utility.xml deleted file mode 100644 index d7377068..00000000 --- a/1.6/1.6/Defs/QuestScriptDefs/WULA_Scripts_Utility.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Util_Wula_GlobalResourceCheck - - - -
  • - $resourceDef - $requiredCount - $retryDelayTicks - $successSignal - $failSignal - $deductOnSuccess - $useInputStorage -
  • -
    -
    -
    -
    \ No newline at end of file diff --git a/1.6/1.6/Defs/ThingDefs_Races/Races_Wulaspecies.xml b/1.6/1.6/Defs/ThingDefs_Races/Races_Wulaspecies.xml index ad153e41..66f82379 100644 --- a/1.6/1.6/Defs/ThingDefs_Races/Races_Wulaspecies.xml +++ b/1.6/1.6/Defs/ThingDefs_Races/Races_Wulaspecies.xml @@ -896,6 +896,10 @@ 1~2 + + 80 + 1 + WULA_Bunker_Drop_Technology @@ -1441,6 +1445,69 @@ + + Wula_Psi_Titan + + 骇人之物 + + 1 + 1 + 35 + + 9999 + 0 + + + Mech_Warqueen + 30 + +
  • + MechanoidFullyFormed + 0 + Pawn_Mech_Warqueen_Wounded + Pawn_Mech_Warqueen_Death + Pawn_Mech_Warqueen_Call +
  • +
    + 25 + + 1 + +
    + +
  • + + +
  • Blunt
  • + + 360 + 8 + Torso + true + +
    + +
  • + 36000 + 8.2 + 8.4 + 0.01 + 4.0 + false +
  • + +
  • + + + Drafted + + + WULA_Hover_FlyNorth + WULA_Hover_FlyEast + WULA_Hover_FlySouth +
  • +
    +
    diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/Wula_Base_QuestScript.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/Wula_Base_QuestScript.xml index 7294f7de..538507d4 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/Wula_Base_QuestScript.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/Wula_Base_QuestScript.xml @@ -60,6 +60,6 @@
  • questName->什一税税收
  • -
  • questDescription->唯死亡和税收不可避免——按时上交什一税是乌拉帝国殖民地的光荣义务。\n\n乌拉帝国的什一税会从殖民地储存在舰队中的资产里面扣除,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将物资输送到位于轨道上的舰队。\n\n你可以快速地准备好税金,乌拉帝国对积极纳税的殖民地会给予更多关照——但是如果一直拖延,则会惹其不快,甚至有可能被定性为叛国!
  • +
  • questDescription->唯死亡和税收不可避免——按时上交什一税是乌拉帝国殖民地的光荣义务。\n\n乌拉帝国的什一税会从殖民地储存在舰队中的资产里面扣除,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将物资输送到位于轨道上的舰队。\n\n乌拉帝国对积极纳税的殖民地会给予更多关照——但是如果拖延,则每延期一天都会惹其不快,最后甚至有可能被定性为叛国!
  • \ No newline at end of file diff --git a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs new file mode 100644 index 00000000..be21297e --- /dev/null +++ b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs @@ -0,0 +1,338 @@ +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; + +namespace WulaFallenEmpire +{ + public class CompAbilityEffect_AreaDestruction : CompAbilityEffect + { + private readonly List tmpCells = new List(); + private readonly List tmpThings = new List(); + + private new CompProperties_AbilityAreaDestruction Props => (CompProperties_AbilityAreaDestruction)props; + + private Pawn Pawn => parent.pawn; + + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + base.Apply(target, dest); + + Map map = parent.pawn.MapHeld; + if (map == null) return; + + // 获取扇形区域内的所有单元格 + List affectedCells = AffectedCells(target); + + // 记录所有被影响的目标 + List affectedTargets = new List(); + + foreach (IntVec3 cell in affectedCells) + { + if (!cell.InBounds(map)) continue; + + // 处理该单元格中的所有事物 + tmpThings.Clear(); + tmpThings.AddRange(cell.GetThingList(map)); + + foreach (Thing thing in tmpThings) + { + if (thing == null || thing.Destroyed) continue; + + // 检查是否应该影响这个目标 + if (!ShouldAffectThing(thing)) continue; + + // 添加到受影响目标列表 + if (!affectedTargets.Contains(thing)) + { + affectedTargets.Add(thing); + } + + // 根据事物类型进行处理 + if (thing is Building building) + { + DestroyBuilding(building); + } + else if (thing is Pawn targetPawn) + { + DestroyAllBodyParts(targetPawn); + } + } + } + + // 为每个受影响的目标播放命中效果器 + foreach (Thing affectedThing in affectedTargets) + { + PlayHitEffecter(affectedThing, map); + } + + // 播放主要效果 + if (Props.effecterDef != null) + { + Props.effecterDef.Spawn(target.Cell, map).Cleanup(); + } + } + + private void PlayHitEffecter(Thing target, Map map) + { + try + { + if (Props.hitEffecter == null) return; + if (target == null || target.Destroyed) return; + + // 创建效果器 + Effecter effecter = Props.hitEffecter.Spawn(); + + // 计算效果器方向(从目标指向施法者) + TargetInfo targetInfo = new TargetInfo(target.Position, map, false); + TargetInfo casterInfo = new TargetInfo(Pawn.Position, map, false); + + // 触发效果器,方向朝向施法者 + effecter.Trigger(targetInfo, casterInfo); + + // 如果效果器需要持续维护,添加到维护列表 + if (Props.hitEffecter.maintainTicks > 0) + { + map.effecterMaintainer.AddEffecterToMaintain(effecter, target, Props.hitEffecter.maintainTicks); + } + else + { + // 否则在适当时间后清理 + LongEventHandler.ExecuteWhenFinished(delegate + { + effecter.Cleanup(); + }); + } + + Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}"); + } + catch (System.Exception ex) + { + Log.Warning($"[AreaDestruction] Error playing hit effecter on {target?.Label}: {ex.Message}"); + } + } + + private bool ShouldAffectThing(Thing thing) + { + // 检查是否影响施法者自己 + if (thing == Pawn && !Props.affectCaster) + return false; + + // 检查是否影响友方单位 + if (thing is Pawn targetPawn && targetPawn.Faction != null) + { + if (!Props.affectAllies && targetPawn.Faction == Pawn.Faction) + return false; + + // 不攻击囚犯(除非设置影响友方) + if (targetPawn.IsPrisoner && targetPawn.HostFaction == Pawn.Faction && !Props.affectAllies) + return false; + } + + return true; + } + + private void DestroyBuilding(Building building) + { + try + { + if (building.Destroyed || !building.Spawned) return; + + // 记录建筑信息用于日志 + string buildingInfo = $"{building.Label} at {building.Position}"; + + // 直接销毁建筑 + building.Destroy(DestroyMode.Vanish); + + Log.Message($"[AreaDestruction] Destroyed building: {buildingInfo}"); + } + catch (System.Exception ex) + { + Log.Warning($"[AreaDestruction] Error destroying building {building?.Label}: {ex.Message}"); + } + } + + private void DestroyAllBodyParts(Pawn targetPawn) + { + try + { + if (targetPawn.Destroyed || !targetPawn.Spawned || targetPawn.Dead) return; + + // 记录pawn信息 + string pawnInfo = $"{targetPawn.Label} at {targetPawn.Position}"; + + // 获取所有身体部位(不包括核心部位如躯干、头部) + var bodyPartRecords = targetPawn.def.race.body.AllParts; + + int partsDestroyed = 0; + foreach (var bodyPartRecord in bodyPartRecords) + { + // 跳过核心部位以避免立即死亡(可选,根据需求调整) + if (IsCoreBodyPart(bodyPartRecord)) continue; + + // 检查该部位是否已经缺失 + if (!targetPawn.health.hediffSet.PartIsMissing(bodyPartRecord)) + { + // 添加缺失部位hediff + targetPawn.health.AddHediff(HediffDefOf.MissingBodyPart, bodyPartRecord); + partsDestroyed++; + } + } + + // 如果摧毁了任何部位,检查是否应该杀死pawn + if (partsDestroyed > 0) + { + // 检查pawn是否还"活着"(没有核心部位缺失时可能还能存活) + CheckPawnViability(targetPawn); + + Log.Message($"[AreaDestruction] Destroyed {partsDestroyed} body parts on {pawnInfo}"); + } + } + catch (System.Exception ex) + { + Log.Warning($"[AreaDestruction] Error destroying body parts on {targetPawn?.Label}: {ex.Message}"); + } + } + + private bool IsCoreBodyPart(BodyPartRecord bodyPart) + { + // 定义核心部位,这些部位缺失会导致立即死亡 + return bodyPart.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 大脑 + bodyPart.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 心脏 + bodyPart.def == BodyPartDefOf.Torso; // 躯干 + } + + private void CheckPawnViability(Pawn pawn) + { + // 检查pawn是否还能存活 + if (pawn.Dead) return; + + // 如果失去了所有肢体,pawn可能会倒下但不会立即死亡 + bool hasAnyLimbs = false; + var allParts = pawn.def.race.body.AllParts; + + foreach (var part in allParts) + { + if ((part.def.tags.Contains(BodyPartTagDefOf.MovingLimbCore) || + part.def.tags.Contains(BodyPartTagDefOf.ManipulationLimbCore)) && + !pawn.health.hediffSet.PartIsMissing(part)) + { + hasAnyLimbs = true; + break; + } + } + + // 如果没有肢体了,让pawn倒下 + if (!hasAnyLimbs && pawn.health.capacities.CapableOf(PawnCapacityDefOf.Moving)) + { + pawn.health.forceDowned = true; + pawn.health.CheckForStateChange(null, null); + } + } + + public override IEnumerable GetPreCastActions() + { + if (Props.effecterDef != null) + { + yield return new PreCastAction + { + action = delegate(LocalTargetInfo a, LocalTargetInfo b) + { + parent.AddEffecterToMaintain(Props.effecterDef.Spawn(parent.pawn.Position, a.Cell, parent.pawn.Map), + Pawn.Position, a.Cell, 17, Pawn.MapHeld); + }, + ticksAwayFromCast = 17 + }; + } + } + + public override void DrawEffectPreview(LocalTargetInfo target) + { + GenDraw.DrawFieldEdges(AffectedCells(target), Color.red); + } + + public override bool AICanTargetNow(LocalTargetInfo target) + { + if (Pawn.Faction != null && !Props.affectAllies) + { + foreach (IntVec3 cell in AffectedCells(target)) + { + List thingList = cell.GetThingList(Pawn.Map); + for (int i = 0; i < thingList.Count; i++) + { + if (thingList[i].Faction == Pawn.Faction) + { + return false; + } + } + } + } + return true; + } + + private List AffectedCells(LocalTargetInfo target) + { + tmpCells.Clear(); + Vector3 casterPos = Pawn.Position.ToVector3Shifted().Yto0(); + IntVec3 targetCell = target.Cell.ClampInsideMap(Pawn.Map); + + if (Pawn.Position == targetCell) + { + return tmpCells; + } + + float distance = (targetCell - Pawn.Position).LengthHorizontal; + float xRatio = (float)(targetCell.x - Pawn.Position.x) / distance; + float zRatio = (float)(targetCell.z - Pawn.Position.z) / distance; + + // 计算扇形末端位置 + targetCell.x = Mathf.RoundToInt((float)Pawn.Position.x + xRatio * Props.range); + targetCell.z = Mathf.RoundToInt((float)Pawn.Position.z + zRatio * Props.range); + + float targetAngle = Vector3.SignedAngle(targetCell.ToVector3Shifted().Yto0() - casterPos, Vector3.right, Vector3.up); + float halfWidth = Props.lineWidthEnd / 2f; + float coneLength = Mathf.Sqrt(Mathf.Pow((targetCell - Pawn.Position).LengthHorizontal, 2f) + Mathf.Pow(halfWidth, 2f)); + float coneAngle = 57.29578f * Mathf.Asin(halfWidth / coneLength); + + // 遍历范围内的所有单元格 + int radialCellCount = GenRadial.NumCellsInRadius(Props.range); + for (int i = 0; i < radialCellCount; i++) + { + IntVec3 cell = Pawn.Position + GenRadial.RadialPattern[i]; + if (CanUseCell(cell) && + Mathf.Abs(Mathf.DeltaAngle(Vector3.SignedAngle(cell.ToVector3Shifted().Yto0() - casterPos, Vector3.right, Vector3.up), targetAngle)) <= coneAngle) + { + tmpCells.Add(cell); + } + } + + // 添加从施法者到目标直线上的单元格 + List lineCells = GenSight.BresenhamCellsBetween(Pawn.Position, targetCell); + for (int j = 0; j < lineCells.Count; j++) + { + IntVec3 lineCell = lineCells[j]; + if (!tmpCells.Contains(lineCell) && CanUseCell(lineCell)) + { + tmpCells.Add(lineCell); + } + } + + return tmpCells; + + bool CanUseCell(IntVec3 c) + { + if (!c.InBounds(Pawn.Map)) + return false; + if (c == Pawn.Position && !Props.affectCaster) + return false; + if (!Props.canHitFilledCells && c.Filled(Pawn.Map)) + return false; + if (!c.InHorDistOf(Pawn.Position, Props.range)) + return false; + + ShootLine resultingLine; + return parent.verb.TryFindShootLineFromTo(Pawn.Position, c, out resultingLine); + } + } + } +} diff --git a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs new file mode 100644 index 00000000..f30deb0e --- /dev/null +++ b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs @@ -0,0 +1,27 @@ +using RimWorld; +using Verse; + +namespace WulaFallenEmpire +{ + public class CompProperties_AbilityAreaDestruction : CompProperties_AbilityEffect + { + public float range; + public float lineWidthEnd; + public EffecterDef effecterDef; + public bool canHitFilledCells; + + // 新增:命中效果器 + public EffecterDef hitEffecter; + + // 新增:是否影响友方单位 + public bool affectAllies = false; + + // 新增:是否影响施法者自己 + public bool affectCaster = false; + + public CompProperties_AbilityAreaDestruction() + { + compClass = typeof(CompAbilityEffect_AreaDestruction); + } + } +} diff --git a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs index bad0bcd3..8830ca7f 100644 --- a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs +++ b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs @@ -82,7 +82,7 @@ namespace WulaFallenEmpire.HarmonyPatches return result; } - // 清理反弹计数 - 修复:使用 Thing 的 Destroy 方法 + // 清理反弹计数 [HarmonyPatch(typeof(Thing), "Destroy")] public static class Thing_Destroy_Patch { diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs index 3ed7b89d..3bbabbea 100644 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs @@ -87,6 +87,200 @@ namespace WulaFallenEmpire } } + + public override void CompPostTick(ref float severityAdjustment) + { + base.CompPostTick(ref severityAdjustment); + if (!TurretEnabled) + { + ResetCurrentTarget(); + return; + } + if (!this.CanShoot) + { + return; + } + // 优先处理手动目标 + if (HasManualTarget && CanAttackManualTarget) + { + LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + this.currentTarget = manualTarget; + this.curRotation = (manualTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + else if (this.currentTarget.IsValid) + { + this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + this.AttackVerb.VerbTick(); + if (this.AttackVerb.state != VerbState.Bursting) + { + if (this.WarmingUp) + { + this.burstWarmupTicksLeft--; + if (this.burstWarmupTicksLeft == 0) + { + bool attackSuccess = this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true); + if (attackSuccess) + { + this.lastAttackTargetTick = Find.TickManager.TicksGame; + this.lastAttackedTarget = this.currentTarget; + } + else + { + // 如果手动攻击失败且目标无效,清除手动目标 + if (HasManualTarget && !CanAttackManualTarget) + { + VolleyTargetManager.ClearVolleyTarget(Pawn); + } + } + return; + } + } + else + { + if (this.burstCooldownTicksLeft > 0) + { + this.burstCooldownTicksLeft--; + } + if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10)) + { + // 如果手动目标无效,清除它 + if (HasManualTarget && !CanAttackManualTarget) + { + VolleyTargetManager.ClearVolleyTarget(Pawn); + } + // 只有在没有有效的手动目标时才寻找新目标 + if (!HasManualTarget || !CanAttackManualTarget) + { + this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); + if (this.currentTarget.IsValid) + { + this.burstWarmupTicksLeft = 1; + return; + } + } + this.ResetCurrentTarget(); + } + } + } + } + // 检查是否有手动目标 + private bool HasManualTarget + { + get + { + LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + return manualTarget.IsValid; + } + } + // 检查是否可以攻击手动目标 + private bool CanAttackManualTarget + { + get + { + LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (!manualTarget.IsValid) + return false; + // 检查目标是否在射程内 + float distance = Pawn.Position.DistanceTo(manualTarget.Cell); + if (distance > AttackVerb.verbProps.range) + return false; + // 检查是否可以命中目标 + if (!AttackVerb.CanHitTarget(manualTarget)) + return false; + // 检查目标是否还活着(如果是生物) + if (manualTarget.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed)) + return false; + // 检查目标是否被摧毁(如果是建筑) + if (manualTarget.Thing != null && manualTarget.Thing.Destroyed) + return false; + return true; + } + } + // 简化的Gizmos - 只有设置目标和清除目标按钮 + public override IEnumerable CompGetGizmos() + { + // 只有 pawn 被选中且是玩家派系时才显示按钮 + if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn)) + { + // 原有开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleTurret".Translate(), + defaultDesc = "CommandToggleTurretDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ToggleTurret"), + isActive = () => TurretEnabled, + toggleAction = () => TurretEnabled = !TurretEnabled, + hotKey = KeyBindingDefOf.Misc1 + }; + // 设置目标按钮 + yield return new Command_Action + { + defaultLabel = "CommandSetTarget".Translate(), + defaultDesc = "CommandSetTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/SetTarget"), + action = () => + { + Find.Targeter.BeginTargeting( + CreateTargetingParameters(), + delegate (LocalTargetInfo target) + { + VolleyTargetManager.SetVolleyTarget(Pawn, target); + }, + Pawn, // caster 参数 + null, // actionWhenFinished + null, // mouseAttachment + true // requiresCastedSelected + ); + }, + hotKey = KeyBindingDefOf.Misc2 + }; + // 清除目标按钮(只在有手动目标时显示) + LocalTargetInfo currentTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (currentTarget.IsValid) + { + yield return new Command_Action + { + defaultLabel = "CommandClearTarget".Translate(), + defaultDesc = "CommandClearTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ClearTarget"), + action = () => VolleyTargetManager.ClearVolleyTarget(Pawn), + hotKey = KeyBindingDefOf.Misc3 + }; + } + } + } + // 创建目标参数 + private TargetingParameters CreateTargetingParameters() + { + return TargetingParameters.ForThing(); + } + // 在提示中显示目标状态 + public override string CompTipStringExtra + { + get + { + string baseString = base.CompTipStringExtra; + string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; + string targetStatus = "Manual Target: "; + LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (manualTarget.IsValid) + { + targetStatus += $"{manualTarget.Thing?.LabelCap ?? manualTarget.Cell.ToString()}"; + if (!CanAttackManualTarget) + { + targetStatus += " (Unreachable)"; + } + } + else + { + targetStatus += "None"; + } + string result = turretStatus + "\n" + targetStatus; + return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result; + } + } + // 新增:炮塔启用状态 public bool TurretEnabled { @@ -190,60 +384,6 @@ namespace WulaFallenEmpire }; } } - - public override void CompPostTick(ref float severityAdjustment) - { - base.CompPostTick(ref severityAdjustment); - - // 新增:只在启用状态下执行攻击逻辑 - if (!TurretEnabled) - { - ResetCurrentTarget(); - return; - } - - if (!this.CanShoot) - { - return; - } - if (this.currentTarget.IsValid) - { - this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; - } - this.AttackVerb.VerbTick(); - if (this.AttackVerb.state != VerbState.Bursting) - { - if (this.WarmingUp) - { - this.burstWarmupTicksLeft--; - if (this.burstWarmupTicksLeft == 0) - { - this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true); - this.lastAttackTargetTick = Find.TickManager.TicksGame; - this.lastAttackedTarget = this.currentTarget; - return; - } - } - else - { - if (this.burstCooldownTicksLeft > 0) - { - this.burstCooldownTicksLeft--; - } - if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10)) - { - this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); - if (this.currentTarget.IsValid) - { - this.burstWarmupTicksLeft = 1; - return; - } - this.ResetCurrentTarget(); - } - } - } - } - private void ResetCurrentTarget() { this.currentTarget = LocalTargetInfo.Invalid; @@ -273,35 +413,6 @@ namespace WulaFallenEmpire } } - // 新增:实现 Gizmo 接口 - public override IEnumerable CompGetGizmos() - { - // 只有 pawn 被选中且是玩家派系时才显示按钮 - if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn)) - { - yield return new Command_Toggle - { - defaultLabel = "CommandToggleTurret".Translate(), - defaultDesc = "CommandToggleTurretDesc".Translate(), - icon = ContentFinder.Get("UI/Gizmos/ToggleTurret"), - isActive = () => TurretEnabled, - toggleAction = () => TurretEnabled = !TurretEnabled, - hotKey = KeyBindingDefOf.Misc1 - }; - } - } - - // 新增:在绘制时显示状态 - public override string CompTipStringExtra - { - get - { - string baseString = base.CompTipStringExtra; - string status = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; - return string.IsNullOrEmpty(baseString) ? status : baseString + "\n" + status; - } - } - private const int StartShootIntervalTicks = 10; private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret"); diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs index de05cc74..61adfccc 100644 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs @@ -15,6 +15,8 @@ namespace WulaFallenEmpire volleyTargets[pawn] = target; volleyEnabled[pawn] = target.IsValid; + + Log.Message($"Set volley target for {pawn.Label}: {target.Thing?.Label ?? target.Cell.ToString()}"); } public static void ClearVolleyTarget(Pawn pawn) @@ -23,6 +25,8 @@ namespace WulaFallenEmpire volleyTargets.Remove(pawn); volleyEnabled.Remove(pawn); + + Log.Message($"Cleared volley target for {pawn.Label}"); } public static LocalTargetInfo GetVolleyTarget(Pawn pawn) @@ -48,11 +52,36 @@ namespace WulaFallenEmpire bool current = IsVolleyEnabled(pawn); volleyEnabled[pawn] = !current; + Log.Message($"Toggled volley for {pawn.Label}: {!current}"); + // 如果禁用齐射,清除目标 if (!volleyEnabled[pawn]) { ClearVolleyTarget(pawn); } } + + // 新增:检查齐射目标是否仍然有效 + public static bool IsVolleyTargetValid(Pawn pawn) + { + if (!IsVolleyEnabled(pawn)) + return false; + + LocalTargetInfo target = GetVolleyTarget(pawn); + if (!target.IsValid) + return false; + + // 检查目标是否还活着/存在 + if (target.Thing != null) + { + if (target.Thing.Destroyed) + return false; + + if (target.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed)) + return false; + } + + return true; + } } } diff --git a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs index 6b243f44..eef15cdc 100644 --- a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs +++ b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs @@ -2,6 +2,7 @@ using HarmonyLib; using RimWorld; using System; using System.Collections.Generic; +using System.Reflection; using System.Text; using UnityEngine; using Verse; @@ -11,11 +12,12 @@ namespace WulaFallenEmpire { public class CompProperties_ApparelInterceptor : CompProperties { - public float radius = 3f; // 仅用于视觉效果 + public float radius = 3f; public int startupDelay = 0; public int rechargeDelay = 3200; public int hitPoints = 100; public int maxBounces = 3; + public float bounceRange = 15f; // 反弹射程 public bool interceptGroundProjectiles = false; public bool interceptNonHostileProjectiles = false; @@ -78,6 +80,267 @@ namespace WulaFallenEmpire public CompProperties_ApparelInterceptor Props => (CompProperties_ApparelInterceptor)props; public Pawn PawnOwner => (parent as Apparel)?.Wearer; + + private void BounceProjectileNew(Projectile originalProjectile) + { + try + { + if (originalProjectile == null || originalProjectile.Destroyed) + return; + // 计算反弹方向 - 朝穿戴者前方发射 + Vector3 bounceDirection = CalculateForwardBounceDirection(); + + // 计算新目标位置 + Vector3 newDestination = PawnOwner.Position.ToVector3Shifted() + bounceDirection * Props.bounceRange; + IntVec3 targetCell = newDestination.ToIntVec3(); + // 创建新的抛射体 + Projectile newProjectile = (Projectile)ThingMaker.MakeThing(originalProjectile.def, null); + + // 使用 Traverse 复制字段 + CopyProjectileFieldsUsingTraverse(newProjectile, originalProjectile); + // 生成新抛射体 + GenSpawn.Spawn(newProjectile, PawnOwner.Position, PawnOwner.Map); + + // 使用 Traverse 调用 Launch 方法 + LaunchProjectileUsingTraverse(newProjectile, targetCell, originalProjectile); + // 销毁原抛射体 + originalProjectile.Destroy(DestroyMode.Vanish); + // 播放反弹效果 + PlayBounceEffect(originalProjectile); + Log.Message($"[Interceptor] Projectile bounced forward to {targetCell}"); + } + catch (Exception ex) + { + Log.Error($"Error in BounceProjectileNew: {ex}"); + } + } + // 使用 Traverse 复制字段 + private void CopyProjectileFieldsUsingTraverse(Projectile newProjectile, Projectile originalProjectile) + { + try + { + Traverse newTraverse = Traverse.Create(newProjectile); + Traverse originalTraverse = Traverse.Create(originalProjectile); + // 复制所有重要字段 + newTraverse.Field("launcher").SetValue(originalTraverse.Field("launcher").GetValue()); + newTraverse.Field("equipment").SetValue(originalTraverse.Field("equipment").GetValue()); + newTraverse.Field("equipmentDef").SetValue(originalTraverse.Field("equipmentDef").GetValue()); + newTraverse.Field("damageDefOverride").SetValue(originalTraverse.Field("damageDefOverride").GetValue()); + newTraverse.Field("targetCoverDef").SetValue(originalTraverse.Field("targetCoverDef").GetValue()); + + // 复制额外伤害列表 + List originalExtraDamages = originalTraverse.Field("extraDamages").GetValue>(); + if (originalExtraDamages != null) + { + newTraverse.Field("extraDamages").SetValue(new List(originalExtraDamages)); + } + } + catch (Exception ex) + { + Log.Warning($"Error copying projectile fields with Traverse: {ex}"); + } + } + // 使用 Traverse 调用 Launch 方法 + private void LaunchProjectileUsingTraverse(Projectile projectile, IntVec3 targetCell, Projectile originalProjectile) + { + try + { + Traverse projectileTraverse = Traverse.Create(projectile); + Traverse originalTraverse = Traverse.Create(originalProjectile); + // 获取 Launch 方法 + var launchMethod = projectileTraverse.Method("Launch", new object[] + { + PawnOwner, // 发射者 + PawnOwner.Position.ToVector3Shifted(), // 发射位置 + new LocalTargetInfo(targetCell), // 目标位置 + new LocalTargetInfo(targetCell), // 预期目标 + originalProjectile.HitFlags, // 命中标志 + originalTraverse.Field("preventFriendlyFire").GetValue(), // 防止友军伤害 + originalTraverse.Field("equipment").GetValue(), // 装备 + originalTraverse.Field("targetCoverDef").GetValue() // 目标覆盖定义 + }); + if (launchMethod.MethodExists()) + { + launchMethod.GetValue(); + } + else + { + Log.Error("Launch method not found using Traverse"); + } + } + catch (Exception ex) + { + Log.Error($"Error launching projectile with Traverse: {ex}"); + } + } + // 使用反射设置抛射体字段 + private void SetProjectileFields(Projectile newProjectile, Projectile originalProjectile) + { + try + { + // 获取 Projectile 类型 + Type projectileType = typeof(Projectile); + + // 设置发射者 + FieldInfo launcherField = projectileType.GetField("launcher", BindingFlags.Instance | BindingFlags.NonPublic); + launcherField?.SetValue(newProjectile, originalProjectile.Launcher); + + // 设置装备 + FieldInfo equipmentField = projectileType.GetField("equipment", BindingFlags.Instance | BindingFlags.NonPublic); + equipmentField?.SetValue(newProjectile, GetEquipment(originalProjectile)); + + // 设置装备定义 + FieldInfo equipmentDefField = projectileType.GetField("equipmentDef", BindingFlags.Instance | BindingFlags.NonPublic); + equipmentDefField?.SetValue(newProjectile, GetEquipmentDef(originalProjectile)); + + // 设置伤害定义覆盖 + FieldInfo damageDefOverrideField = projectileType.GetField("damageDefOverride", BindingFlags.Instance | BindingFlags.NonPublic); + damageDefOverrideField?.SetValue(newProjectile, GetDamageDefOverride(originalProjectile)); + + // 设置额外伤害 + FieldInfo extraDamagesField = projectileType.GetField("extraDamages", BindingFlags.Instance | BindingFlags.NonPublic); + if (extraDamagesField != null) + { + List originalExtraDamages = (List)extraDamagesField.GetValue(originalProjectile); + if (originalExtraDamages != null) + { + List newExtraDamages = new List(originalExtraDamages); + extraDamagesField.SetValue(newProjectile, newExtraDamages); + } + } + + // 设置目标覆盖定义 + FieldInfo targetCoverDefField = projectileType.GetField("targetCoverDef", BindingFlags.Instance | BindingFlags.NonPublic); + targetCoverDefField?.SetValue(newProjectile, GetTargetCoverDef(originalProjectile)); + } + catch (Exception ex) + { + Log.Warning($"Error setting projectile fields: {ex}"); + } + } + // 使用反射调用 Launch 方法 + private void LaunchProjectile(Projectile projectile, IntVec3 targetCell, Projectile originalProjectile) + { + try + { + Type projectileType = typeof(Projectile); + + // 获取 Launch 方法 + MethodInfo launchMethod = projectileType.GetMethod("Launch", new Type[] + { + typeof(Thing), + typeof(Vector3), + typeof(LocalTargetInfo), + typeof(LocalTargetInfo), + typeof(ProjectileHitFlags), + typeof(bool), + typeof(Thing), + typeof(ThingDef) + }); + if (launchMethod != null) + { + // 获取原抛射体的命中标志 + ProjectileHitFlags hitFlags = GetHitFlags(originalProjectile); + + // 获取防止友军伤害设置 + bool preventFriendlyFire = GetPreventFriendlyFire(originalProjectile); + + // 调用 Launch 方法 + launchMethod.Invoke(projectile, new object[] + { + PawnOwner, // 发射者改为护盾穿戴者 + PawnOwner.Position.ToVector3Shifted(), // 发射位置 + new LocalTargetInfo(targetCell), // 目标位置 + new LocalTargetInfo(targetCell), // 预期目标 + hitFlags, + preventFriendlyFire, + GetEquipment(originalProjectile), // 装备 + GetTargetCoverDef(originalProjectile) // 目标覆盖定义 + }); + } + else + { + Log.Error("Could not find Launch method on Projectile"); + } + } + catch (Exception ex) + { + Log.Error($"Error launching projectile: {ex}"); + } + } + // 使用反射获取私有字段值 + private Thing GetEquipment(Projectile projectile) + { + try + { + FieldInfo equipmentField = typeof(Projectile).GetField("equipment", BindingFlags.Instance | BindingFlags.NonPublic); + return (Thing)equipmentField?.GetValue(projectile); + } + catch + { + return null; + } + } + private ThingDef GetEquipmentDef(Projectile projectile) + { + try + { + FieldInfo equipmentDefField = typeof(Projectile).GetField("equipmentDef", BindingFlags.Instance | BindingFlags.NonPublic); + return (ThingDef)equipmentDefField?.GetValue(projectile); + } + catch + { + return null; + } + } + private DamageDef GetDamageDefOverride(Projectile projectile) + { + try + { + FieldInfo damageDefOverrideField = typeof(Projectile).GetField("damageDefOverride", BindingFlags.Instance | BindingFlags.NonPublic); + return (DamageDef)damageDefOverrideField?.GetValue(projectile); + } + catch + { + return null; + } + } + private ThingDef GetTargetCoverDef(Projectile projectile) + { + try + { + FieldInfo targetCoverDefField = typeof(Projectile).GetField("targetCoverDef", BindingFlags.Instance | BindingFlags.NonPublic); + return (ThingDef)targetCoverDefField?.GetValue(projectile); + } + catch + { + return null; + } + } + private ProjectileHitFlags GetHitFlags(Projectile projectile) + { + try + { + return projectile.HitFlags; + } + catch + { + return ProjectileHitFlags.All; + } + } + private bool GetPreventFriendlyFire(Projectile projectile) + { + try + { + FieldInfo preventFriendlyFireField = typeof(Projectile).GetField("preventFriendlyFire", BindingFlags.Instance | BindingFlags.NonPublic); + return preventFriendlyFireField != null && (bool)preventFriendlyFireField.GetValue(projectile); + } + catch + { + return false; + } + } + // 主要拦截方法 public bool TryInterceptProjectile(Projectile projectile, Thing hitThing) { @@ -156,8 +419,8 @@ namespace WulaFallenEmpire } } - // 反弹抛射体 - BounceProjectile(projectile); + // 反弹抛射体 - 新方法:销毁原抛射体并创建新的 + BounceProjectileNew(projectile); } catch (Exception ex) { @@ -165,61 +428,17 @@ namespace WulaFallenEmpire } } - private void BounceProjectile(Projectile projectile) + private Vector3 CalculateForwardBounceDirection() { - try - { - if (projectile == null || projectile.Destroyed) - return; - - // 计算反弹方向 - Vector3 bounceDirection = CalculateBounceDirection(projectile); - - // 使用 Traverse 修改抛射体方向 - var traverse = Traverse.Create(projectile); - - // 修改目的地 - 随机弹射 - Vector3 newDestination = projectile.ExactPosition + bounceDirection * 30f; - traverse.Field("destination").SetValue(newDestination); - - // 重置起点为当前位置 - traverse.Field("origin").SetValue(projectile.ExactPosition); - - // 重新计算飞行时间 - float distance = (newDestination - projectile.ExactPosition).MagnitudeHorizontal(); - int newTicks = Mathf.CeilToInt(distance / projectile.def.projectile.SpeedTilesPerTick); - traverse.Field("ticksToImpact").SetValue(newTicks); - - // 播放反弹效果 - PlayBounceEffect(projectile); - - Log.Message($"[Interceptor] Projectile bounced towards {bounceDirection}"); - } - catch (Exception ex) - { - Log.Error($"Error in BounceProjectile: {ex}"); - } - } - - private Vector3 CalculateBounceDirection(Projectile projectile) - { - // 如果有发射者,尝试弹向发射者 - if (projectile.Launcher != null && projectile.Launcher.Spawned) - { - try - { - Vector3 toLauncher = (projectile.Launcher.Position.ToVector3() - projectile.ExactPosition).normalized; - return toLauncher; - } - catch - { - // 如果计算失败,使用随机方向 - } - } - - // 随机弹射方向 - float angle = Rand.Range(0f, 360f); - return Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward; + // 获取穿戴者的朝向 + float pawnRotation = PawnOwner.Rotation.AsAngle; + + // 添加一些随机偏移,使反弹更自然 + float randomOffset = Rand.Range(-30f, 30f); + float finalAngle = pawnRotation + randomOffset; + + // 转换为方向向量 + return Quaternion.AngleAxis(finalAngle, Vector3.up) * Vector3.forward; } private void PlayBounceEffect(Projectile projectile)