diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index af51cdd..8f71447 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/DamageDefs/ARA_Damage_Freeze.xml b/1.6/1.6/Defs/DamageDefs/ARA_Damage_Freeze.xml new file mode 100644 index 0000000..9befb28 --- /dev/null +++ b/1.6/1.6/Defs/DamageDefs/ARA_Damage_Freeze.xml @@ -0,0 +1,280 @@ + + + + + + ARA_Hediff_Frozen + + 这个身体部位正在被冷冻。移动会因此减慢。如果该部位被完全冷冻,它将在下一次受到冲击时碎裂。 + ArachnaeSwarm.Hediff_Frozen + true + +
  • + -6 +
  • +
  • + 0.3 +
  • +
  • + + + 0 + true + 已碎裂 + + +
  • + + true +
  • +
  • + + 0.2 + + 40 + + + +
  • +
  • + + 0.35 + + 80 + + +
  • + Moving + -0.1 +
  • +
  • + Manipulation + -0.2 +
  • + + +
  • + + 0.5 + + 120 + + +
  • + Moving + -0.3 +
  • +
  • + Manipulation + -0.4 +
  • + + +
  • + + 0.65 + + 160 + + +
  • + Moving + -0.5 +
  • +
  • + Manipulation + -0.6 +
  • + + +
  • + + 0.85 + + 200 + + +
  • + Moving + 0.2 +
  • +
  • + Manipulation + 0.2 +
  • + + + +
    + + + ARA_Hediff_FrostCoverd_after + + 此人被一片冰霜之云所覆盖。 + (1, 1, 0.8) + HediffWithComps + +
  • + -4 +
  • +
  • + ARA_Damage_Freeze + Explosion_Stun + 3 + true + 5 + 0.10 + 60~120 + ARA_FrostGasCloud + 1 + 1 +
  • +
  • + + + + + + + ARA_CryoShock + + 此人正处于低温休克状态。 + (1, 1, 0.8) + ArachnaeSwarm.HediffCurseFlame + +
  • + -6 +
  • +
    + +
  • + Stun + 1~2 + 80 +
  • +
    + + 0.02 + + +
  • + + true +
  • +
  • + + 0.2 + + 40 + + +
  • + Consciousness + -0.10 +
  • + + +
  • + + 0.35 + + 80 + + +
  • + Consciousness + -0.20 +
  • + + +
  • + + 0.5 + + 120 + + +
  • + Consciousness + -0.20 +
  • + + +
  • + + 0.65 + + 160 + + +
  • + Consciousness + -0.30 +
  • + + +
  • + + 0.85 + + 200 + + +
  • + Consciousness + -0.30 +
  • + + +
    +
    + + + + ARA_Damage_Freeze + + DamageWorker_AddInjury + true + {0}的身体在极寒中化为了冰雕,随后碎裂四散。 + ARA_Hediff_Frozen + ARA_Hediff_Frozen + false + Heat + 0 + 3 + 0 + 0 + 0 + + + + + ARA_Damage_Freeze_ex + + DamageWorker_AddInjury + true + {0}的身体在极寒中化为了冰雕,随后碎裂四散。 + ARA_Hediff_Frozen + ARA_Hediff_Frozen + false + Heat + 0 + 3 + 0 + 0 + 0 + +
  • + ARA_CryoShock + 0.01 +
  • +
  • + ARA_Hediff_FrostCoverd_after + 0.01 + true + true +
  • +
    +
    + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_PowerArmor_Defs.xml b/1.6/1.6/Defs/Thing_Misc/ARA_PowerArmor_Defs.xml index cbf1f62..777653d 100644 --- a/1.6/1.6/Defs/Thing_Misc/ARA_PowerArmor_Defs.xml +++ b/1.6/1.6/Defs/Thing_Misc/ARA_PowerArmor_Defs.xml @@ -66,6 +66,7 @@ ARA_Building_SpiderOne ARA_PowerArmor_NoFuel 0.5 + ARA_RW_Icez_Mortar diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Things_Gas.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Things_Gas.xml index 6cb81c8..433afbf 100644 --- a/1.6/1.6/Defs/Thing_Misc/ARA_Things_Gas.xml +++ b/1.6/1.6/Defs/Thing_Misc/ARA_Things_Gas.xml @@ -41,31 +41,86 @@ - - ARA_AcidGasCloud - - - Things/Gas/Puff - 2.6 - (0.56, 1, 0.03,0.5) - - - - 30 - 50 - - - 20 - - ArachnaeSwarm.OPToxicGas - -
  • - ARA_AcidCoverd - 0.1 - 240 -
  • -
    -
    + 20 + + ArachnaeSwarm.OPToxicGas + +
  • + ARA_AcidCoverd + 0.1 + 240 +
  • +
    + + + + + ARA_FrostGasCloud_Ex + + + Things/Gas/Puff + 2.6 + (0.52, 1, 0.95,0.5) + + + + 5 + 10 + + + 5 + + ArachnaeSwarm.OPToxicGas + +
  • + ARA_Hediff_FrostCoverd_after + 0.1 + 240 +
  • +
    +
    + + + ARA_FrostGasCloud + + + Things/Gas/Puff + 2.6 + (0.52, 1, 0.95,0.5) + + + + 5 + 10 + + + 5 + + ArachnaeSwarm.OPToxicGas + +
  • + ARA_CryoShock + 0.1 + 240 +
  • +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml index 7b60faa..16af3ca 100644 --- a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml +++ b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml @@ -1093,7 +1093,7 @@ true false 5 - 120 + 80 1 SpitterSpit diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Icez.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Icez.xml new file mode 100644 index 0000000..536373e --- /dev/null +++ b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Icez.xml @@ -0,0 +1,122 @@ + + + + ARA_RW_Icez_Mortar + + 阿拉克涅虫群督虫使用大型远程武装器官,可以发射极度冰冷的霜冻气团,被接触到的敌人会产生霜冻爆炸,一旦被冻结不会立即致死,但随之而来的任何外来伤害都会立刻摧毁脆弱的冰雕。 + Normal + Animal + + ARA_Cocoon_Weapon_2Stage + + + ArachnaeSwarm/Weapon/ARA_RW_Acid_Mortar + Graphic_Single + 1.5 + + 0.75 + SpitterSpawn + + + ARA_Technology_9VXI + UnfinishedWeapon + + + 2500 + 1300 + + 3.5 + 0.3 + 0.3 + 0.25 + 0.1 + 3.5 + 450 + 15 + + +
  • + Verb_Shoot + true + false + 1.5 + 3 + Bullet_ARA_RW_Icez_Mortar + true + false + 5 + 80 + 1 + SpitterSpit + + true + +
  • +
    + + 50 + + +
  • ARA_Armed_Organ
  • +
  • ARA_Armed_Organ_Ranged
  • +
  • ARA_Armed_Organ_T2
  • +
    + 0 + None + + +
  • + +
  • ARA_Cocoon_Weapon_2Stage
  • +
  • ARA_BioforgeIncubator_Thing
  • + + +
  • + +
  • ARA_Huge_Weapon
  • +
  • ARA_Weapon_Damage_Acid
  • + + + 2 + 2 + + +
    +
    + + Bullet_ARA_RW_Icez_Mortar + + + Graphic_Single_AgeSecs + Things/Projectile/FleshmassSpitterProjectileSheet + (.75, .75) + MoteGlow + + 0.8 + Projectile_Explosive + + True + 1 + ARA_Damage_Freeze_ex + 15 + 25 + 45 + 1 + 3.5 + true + ARA_FrostGasCloud_Ex + 1 + 1 + Explosion_Stun + false + + +
  • + Shell_AcidSpitStream +
  • +
  • + Shell_AcidSpitLaunched +
  • +
    +
    +
    \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_HediffDefOf.cs b/Source/ArachnaeSwarm/ARA_HediffDefOf.cs index 611ee5c..4cc24b8 100644 --- a/Source/ArachnaeSwarm/ARA_HediffDefOf.cs +++ b/Source/ArachnaeSwarm/ARA_HediffDefOf.cs @@ -1,19 +1,21 @@ using RimWorld; using Verse; -using Verse.Sound; namespace ArachnaeSwarm { [DefOf] public static class ARA_HediffDefOf { - public static HediffDef ARA_AcidCoverd; + public static HediffDef ARA_Hediff_Frozen; + public static HediffDef ARA_Hediff_FrostCoverd_after; + public static HediffDef ARA_CryoShock; public static HediffDef ARA_HiveMindMaster; public static HediffDef ARA_HiveMindDrone; - public static HediffDef ARA_HiveMindWorker; + public static HediffDef ARA_HiveMindWorker; // 如果存在这个Def + static ARA_HediffDefOf() { - DefOfHelper.EnsureInitializedInCtor(typeof(HediffDefOf)); + DefOfHelper.EnsureInitializedInCtor(typeof(ARA_HediffDefOf)); } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index c3a7155..c1f9dcb 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -192,6 +192,9 @@ + + + @@ -272,12 +275,12 @@ + - diff --git a/Source/ArachnaeSwarm/HarmonyPatch_Pawn_HealthTracker_PreApplyDamage.cs b/Source/ArachnaeSwarm/HarmonyPatch_Pawn_HealthTracker_PreApplyDamage.cs new file mode 100644 index 0000000..644bc80 --- /dev/null +++ b/Source/ArachnaeSwarm/HarmonyPatch_Pawn_HealthTracker_PreApplyDamage.cs @@ -0,0 +1,73 @@ +using HarmonyLib; +using RimWorld; +using Verse; +using System.Linq; +using UnityEngine; // For FleckMaker + +namespace ArachnaeSwarm +{ + // Harmony 入口点,将在 Mod 加载时自动初始化 + [StaticConstructorOnStartup] + public static class HarmonyPatches + { + static HarmonyPatches() + { + var harmony = new Harmony("ArachnaeSwarm.FreezeMod"); + harmony.PatchAll(); + } + } + + // 补丁目标:Pawn_HealthTracker 类的 PreApplyDamage 方法 + [HarmonyPatch(typeof(Pawn_HealthTracker), "PreApplyDamage")] + public static class HarmonyPatch_Pawn_HealthTracker_PreApplyDamage + { + // Postfix 补丁:在原方法执行后运行 + // __instance 是 Pawn_HealthTracker 的实例 + // dinfo 是 DamageInfo 的引用 + // absorbed 是 out bool 参数的引用 + public static void Postfix(Pawn_HealthTracker __instance, ref DamageInfo dinfo, ref bool absorbed) + { + // 如果伤害已经被吸收,或者没有击中任何部位,则不处理 + if (absorbed || dinfo.HitPart == null) + { + return; + } + + Pawn pawn = (Pawn)AccessTools.Field(typeof(Pawn_HealthTracker), "pawn").GetValue(__instance); + if (pawn == null) + { + return; + } + + // 复制一份 dinfo,避免 ref 参数在 lambda 中使用的问题 + DamageInfo currentDinfo = dinfo; + + // 获取被击中部位上的 Hediff_Frozen + Hediff_Frozen frozenHediff = pawn.health.hediffSet.hediffs.OfType() + .FirstOrDefault(h => h.Part == currentDinfo.HitPart); + + // 如果该部位存在 Hediff_Frozen 并且已经完全冷冻 + if (frozenHediff != null && frozenHediff.Severity >= currentDinfo.HitPart.def.GetMaxHealth(pawn)) + { + // 检查伤害是否是我们的冷冻伤害(防止循环触发) + if (currentDinfo.Def.hediff == ARA_HediffDefOf.ARA_Hediff_Frozen) + { + return; // 忽略我们自己的冷冻伤害 + } + + // 检查伤害是否是会造成生命值损伤的普通伤害 + if (currentDinfo.Def.harmsHealth && currentDinfo.Amount > 0.01f) + { + // 施加一个巨大的、足以摧毁该部位的伤害 + DamageInfo fatalDamage = new DamageInfo(DamageDefOf.Crush, 99999f, 999f, -1f, currentDinfo.Instigator, currentDinfo.HitPart); + pawn.TakeDamage(fatalDamage); + + // 播放一个破碎的特效 + FleckMaker.Static(pawn.Position, pawn.Map, FleckDefOf.ExplosionFlash, 2f); + + absorbed = true; // 标记原伤害已被处理,阻止其继续执行 + } + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HediffComp_GlobalFreeze.cs b/Source/ArachnaeSwarm/HediffComp_GlobalFreeze.cs new file mode 100644 index 0000000..6d8bd07 --- /dev/null +++ b/Source/ArachnaeSwarm/HediffComp_GlobalFreeze.cs @@ -0,0 +1,72 @@ +using RimWorld; +using Verse; +using System.Linq; + +namespace ArachnaeSwarm +{ + public class HediffComp_GlobalFreeze : HediffComp + { + private const int CheckInterval = 60; // 每60 ticks检查一次 (1秒) + private bool isStunnedByFreeze = false; // 用于追踪是否是由我们这个组件造成的眩晕 + + public HediffCompProperties_GlobalFreeze Props => (HediffCompProperties_GlobalFreeze)props; + + public override void CompPostTick(ref float severityAdjustment) + { + base.CompPostTick(ref severityAdjustment); + + // 每隔一段时间才执行一次,避免性能问题 + if (Pawn.IsHashIntervalTick(CheckInterval)) + { + // 计算全身所有Hediff_Frozen的总严重性 + float totalFreezeSeverity = Pawn.health.hediffSet.hediffs + .OfType() + .Sum(h => h.Severity); + + // 获取Pawn的总生命值作为参考 + float totalBodyHealth = Pawn.health.summaryHealth.SummaryHealthPercent * Pawn.MaxHitPoints; + + // 计算冷冻阈值 + float stunThreshold = totalBodyHealth * Props.stunThreshold; + + // 如果总冷冻值超过阈值 + if (totalFreezeSeverity >= stunThreshold) + { + // 如果Pawn当前没有被眩晕,则施加一个“永久”的眩晕 + // StunFor会取最大值,所以我们给一个足够长的时间(5秒),它会在下一轮检查时被刷新 + if (!Pawn.stances.stunner.Stunned) + { + Pawn.stances.stunner.StunFor(300, Pawn, false, true); + isStunnedByFreeze = true; + } + } + // 如果总冷冻值低于阈值,并且之前是由我们造成的眩晕 + else if (isStunnedByFreeze) + { + // 立即停止眩晕 + // 我们通过将stunTicksLeft设置为0来直接唤醒它 + Pawn.stances.stunner.StunFor(0, Pawn, false, false); + isStunnedByFreeze = false; + } + } + } + + // 保存和加载我们的状态,确保读档后逻辑正确 + public override void CompExposeData() + { + base.CompExposeData(); + Scribe_Values.Look(ref isStunnedByFreeze, "isStunnedByFreeze", false); + } + } + + public class HediffCompProperties_GlobalFreeze : HediffCompProperties + { + // XML中可配置的阈值,默认为全身健康的30% + public float stunThreshold = 0.3f; + + public HediffCompProperties_GlobalFreeze() + { + compClass = typeof(HediffComp_GlobalFreeze); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HediffComp_TimedExplosion.cs b/Source/ArachnaeSwarm/HediffComp_TimedExplosion.cs new file mode 100644 index 0000000..450399e --- /dev/null +++ b/Source/ArachnaeSwarm/HediffComp_TimedExplosion.cs @@ -0,0 +1,244 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Verse; +using Verse.Noise; + +namespace ArachnaeSwarm // 修正命名空间 +{ + public class HediffComp_TimedExplosion : HediffComp + { + // 倒计时相关字段 + public int ticksToDisappear; + public int disappearsAfterTicks; + public int seed; + + // 配置属性快捷访问 + public HediffCompProperties_TimedExplosion Props => + (HediffCompProperties_TimedExplosion)props; + + // 消失判定属性 + public override bool CompShouldRemove + { + get + { + if (ticksToDisappear > 0) return false; + if (Props.requiredMentalState != null) + { + return parent.pawn.MentalStateDef != Props.requiredMentalState; + } + return true; + } + } + + // 进度计算 + public float Progress => + 1f - (float)ticksToDisappear / Mathf.Max(1, disappearsAfterTicks); + + public int EffectiveTicksToDisappear => ticksToDisappear / TicksLostPerTick; + + public float NoisyProgress => AddNoiseToProgress(Progress, seed); + + public virtual int TicksLostPerTick => 1; + + public override string CompLabelInBracketsExtra + { + get + { + if (Props.showRemainingTime) + { + if (EffectiveTicksToDisappear < 2500) + { + return EffectiveTicksToDisappear.ToStringSecondsFromTicks("F0"); + } + return EffectiveTicksToDisappear.ToStringTicksToPeriod(allowSeconds: true, shortForm: true, canUseDecimals: true, allowYears: true, Props.canUseDecimalsShortForm); + } + return base.CompLabelInBracketsExtra; + } + } + + private static float AddNoiseToProgress(float progress, int seed) + { + float num = (float)Perlin.GetValue(progress, 0.0, 0.0, 9.0, seed); + float num2 = 0.25f * (1f - progress); + return Mathf.Clamp01(progress + num2 * num); + } + + // 初始化 + public override void CompPostMake() + { + base.CompPostMake(); + disappearsAfterTicks = Props.disappearsAfterTicks.RandomInRange; + seed = Rand.Int; + ticksToDisappear = disappearsAfterTicks; + } + + // 每帧更新 + public override void CompPostTick(ref float severityAdjustment) + { + ticksToDisappear--; + if (CompShouldRemove) + { + parent.pawn.health.RemoveHediff(parent); + } + } + + // 移除后处理 + public override void CompPostPostRemoved() + { + base.CompPostPostRemoved(); + + // 处理新鲜伤口状态 + if (!Props.leaveFreshWounds && parent.Part != null) + { + foreach (BodyPartRecord part in parent.Part.GetPartAndAllChildParts()) + { + Hediff_MissingPart hediff = parent.pawn.health.hediffSet.GetMissingPartFor(part) as Hediff_MissingPart; + if (hediff != null) + { + hediff.IsFresh = false; + } + } + } + + // 触发爆炸逻辑 + if (ShouldTriggerExplosion()) + { + TriggerExplosion(); + DestroyGearIfNeeded(); + } + + // 发送消息通知 + if (!Props.messageOnDisappear.NullOrEmpty() && PawnUtility.ShouldSendNotificationAbout(parent.pawn)) + { + Messages.Message( + Props.messageOnDisappear.Formatted(parent.pawn.Named("PAWN")), + parent.pawn, + MessageTypeDefOf.NeutralEvent + ); + } + + // 发送信件通知 + if (!Props.letterTextOnDisappear.NullOrEmpty() && + !Props.letterLabelOnDisappear.NullOrEmpty() && + PawnUtility.ShouldSendNotificationAbout(parent.pawn)) + { + Find.LetterStack.ReceiveLetter( + Props.letterLabelOnDisappear.Formatted(parent.pawn.Named("PAWN")), + Props.letterTextOnDisappear.Formatted(parent.pawn.Named("PAWN")), + LetterDefOf.NegativeEvent, + parent.pawn + ); + } + } + + // 爆炸条件检查 + private bool ShouldTriggerExplosion() + { + return parent.pawn.Spawned && + Props.explosionRadius > 0.01f && + Props.damageDef != null && + parent.pawn.Map != null; + } + + // 执行爆炸 + private void TriggerExplosion() + { + GenExplosion.DoExplosion( + center: parent.pawn.Position, + map: parent.pawn.Map, + radius: Props.explosionRadius, + damType: Props.damageDef, + instigator: parent.pawn, + damAmount: Props.damageAmount, + armorPenetration: Props.armorPenetration, + explosionSound: Props.soundDef, + weapon: null, + projectile: null, + intendedTarget: null, + postExplosionSpawnThingDef: Props.postExplosionSpawnThingDef, + postExplosionSpawnChance: Props.postExplosionSpawnChance, + postExplosionSpawnThingCount: Props.postExplosionSpawnThingCount, + postExplosionGasType: null, + applyDamageToExplosionCellsNeighbors: false, + chanceToStartFire: Props.chanceToStartFire, + damageFalloff: Props.damageFalloff, + direction: null, + ignoredThings: new List { parent.pawn } + ); + } + + // 装备销毁 + private void DestroyGearIfNeeded() + { + if (!Props.destroyGear) return; + + if (parent.pawn.equipment != null) + { + parent.pawn.equipment.DestroyAllEquipment(DestroyMode.Vanish); + } + if (parent.pawn.apparel != null) + { + parent.pawn.apparel.DestroyAll(DestroyMode.Vanish); + } + } + + // 数据持久化 + public override void CompExposeData() + { + Scribe_Values.Look(ref ticksToDisappear, "ticksToDisappear", 0); + Scribe_Values.Look(ref disappearsAfterTicks, "disappearsAfterTicks", 0); + Scribe_Values.Look(ref seed, "seed", 0); + } + + // 调试信息 + public override string CompDebugString() + { + return $"倒计时: {ticksToDisappear}\n爆炸半径: {Props.explosionRadius}"; + } + } + + public class HediffCompProperties_TimedExplosion : HediffCompProperties + { + [Header("消失设置")] + public IntRange disappearsAfterTicks = new IntRange(600, 1200); + public bool showRemainingTime = true; + public bool canUseDecimalsShortForm; + public MentalStateDef requiredMentalState; + public bool leaveFreshWounds = true; + + [Header("爆炸设置")] + public float explosionRadius = 3f; + public DamageDef damageDef; + public int damageAmount = 20; + public float armorPenetration; + public SoundDef soundDef; + public float chanceToStartFire; + public bool damageFalloff = true; + + [Header("后续效果")] + public bool destroyGear; + public GasType gasType; + public ThingDef postExplosionSpawnThingDef; + public float postExplosionSpawnChance; + public int postExplosionSpawnThingCount = 1; + + [Header("通知设置")] + [MustTranslate] + public string messageOnDisappear; + [MustTranslate] + public string letterLabelOnDisappear; + [MustTranslate] + public string letterTextOnDisappear; + public bool sendLetterOnDisappearIfDead = true; + + public HediffCompProperties_TimedExplosion() + { + compClass = typeof(HediffComp_TimedExplosion); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Hediff_Frozen.cs b/Source/ArachnaeSwarm/Hediff_Frozen.cs new file mode 100644 index 0000000..b9adecc --- /dev/null +++ b/Source/ArachnaeSwarm/Hediff_Frozen.cs @@ -0,0 +1,46 @@ +using RimWorld; +using Verse; +using UnityEngine; + +namespace ArachnaeSwarm +{ + public class Hediff_Frozen : Hediff_Injury + { + // 我们需要覆盖这个方法来改变在健康面板上显示的标签 + public override string LabelInBrackets + { + get + { + // 如果严重性达到了部位的满生命值,就显示“完全冷冻” + if (Severity >= Part.def.GetMaxHealth(pawn)) + { + return "Fully Frozen"; + } + return base.LabelInBrackets; + } + } + + // 当部位的疼痛感,可以根据严重性调整 + public override float PainOffset + { + get + { + // 冷冻不造成额外疼痛 + return 0f; + } + } + + // 当一个部位的伤害被治疗(在这里就是冷冻值减少)时调用 + public override void Heal(float amount) + { + // 减少严重性,但不让它低于0 + Severity -= amount; + if (Severity < 0) + { + Severity = 0; + } + } + + // 伤害拦截逻辑将通过 Harmony 补丁实现,不在 Hediff 内部处理 + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/PowerArmor/ARA_PowerArmor.cs b/Source/ArachnaeSwarm/PowerArmor/ARA_PowerArmor.cs index 646af0a..2b48001 100644 --- a/Source/ArachnaeSwarm/PowerArmor/ARA_PowerArmor.cs +++ b/Source/ArachnaeSwarm/PowerArmor/ARA_PowerArmor.cs @@ -19,6 +19,7 @@ namespace ArachnaeSwarm public float structurePointsMax = 500f; public HediffDef hediffOnEmptyFuel; public float fuelConsumptionRate = 0.5f; // Nutrition per day + public ThingDef powerArmorWeapon; } [StaticConstructorOnStartup] @@ -51,6 +52,12 @@ namespace ArachnaeSwarm public float StructurePointsPercent => StructurePoints / StructurePointsMax; public Building sourceBuilding; + private ThingWithComps originalWeapon; + + public void SetOriginalWeapon(ThingWithComps weapon) + { + originalWeapon = weapon; + } #endregion #region Ticker @@ -110,6 +117,7 @@ namespace ArachnaeSwarm base.ExposeData(); Scribe_Values.Look(ref structurePoints, "structurePoints", -1f); Scribe_References.Look(ref sourceBuilding, "sourceBuilding"); + Scribe_References.Look(ref originalWeapon, "originalWeapon"); } #endregion @@ -143,6 +151,25 @@ namespace ArachnaeSwarm { base.Notify_Unequipped(pawn); + // Handle weapon restoration + if (Ext?.powerArmorWeapon != null && pawn?.equipment != null) + { + ThingWithComps primaryWeapon = pawn.equipment.Primary; + if (primaryWeapon != null && primaryWeapon.def == Ext.powerArmorWeapon) + { + // Destroy the power armor's weapon instead of dropping it. + pawn.equipment.Remove(primaryWeapon); + primaryWeapon.Destroy(); + } + } + + if (originalWeapon != null && pawn?.equipment != null) + { + pawn.equipment.MakeRoomFor(originalWeapon); + pawn.equipment.AddEquipment(originalWeapon); + originalWeapon = null; + } + Building building = sourceBuilding; // If the source building reference is lost, create a new one as a fallback. @@ -171,6 +198,12 @@ namespace ArachnaeSwarm buildingFuelComp.ReceiveFuel(apparelFuelComp.Fuel); } + // Sync quality back to building + if (this.TryGetComp() is CompQuality apparelQuality && building.TryGetComp() is CompQuality buildingQuality) + { + buildingQuality.SetQuality(apparelQuality.Quality, ArtGenerationContext.Colony); + } + Log.Message($"[PA_Debug] Notify_Unequipped: Before spawning building (ID: {building.thingIDNumber}) - HitPoints: {building.HitPoints}, StackCount: {building.stackCount}"); // Ensure stackCount is at least 1 for buildings, as 0 stackCount causes errors during spawning diff --git a/Source/ArachnaeSwarm/PowerArmor/CompPowerArmorStation.cs b/Source/ArachnaeSwarm/PowerArmor/CompPowerArmorStation.cs index 7515614..9ed75c4 100644 --- a/Source/ArachnaeSwarm/PowerArmor/CompPowerArmorStation.cs +++ b/Source/ArachnaeSwarm/PowerArmor/CompPowerArmorStation.cs @@ -1,5 +1,8 @@ using RimWorld; using Verse; +using System.Collections.Generic; +using Verse.AI; // For PathEndMode and Danger +using UnityEngine; // For Texture2D namespace ArachnaeSwarm { @@ -16,5 +19,34 @@ namespace ArachnaeSwarm public class CompPowerArmorStation : ThingComp { public CompProperties_PowerArmorStation Props => (CompProperties_PowerArmorStation)props; + + public override IEnumerable CompFloatMenuOptions(Pawn selPawn) + { + foreach (FloatMenuOption option in base.CompFloatMenuOptions(selPawn)) + { + yield return option; + } + + // Check if there's an apparelDef defined + if (Props.apparelDef == null) + { + yield break; // No apparel to wear + } + + // Check if the pawn can interact with the building + if (!selPawn.CanReserveAndReach(parent, PathEndMode.InteractionCell, Danger.Deadly)) + { + yield return new FloatMenuOption("CannotEnterPowerArmor".Translate() + ": " + "CannotReach".Translate(), null); + } + else + { + void enterAction() + { + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_EnterPowerArmor"), parent); + selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc); + } + yield return new FloatMenuOption("EnterPowerArmor".Translate(parent.Label), enterAction); + } + } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/PowerArmor/Harmony_ThingWithComps_GetFloatMenuOptions.cs b/Source/ArachnaeSwarm/PowerArmor/Harmony_ThingWithComps_GetFloatMenuOptions.cs deleted file mode 100644 index 0ec7543..0000000 --- a/Source/ArachnaeSwarm/PowerArmor/Harmony_ThingWithComps_GetFloatMenuOptions.cs +++ /dev/null @@ -1,59 +0,0 @@ -using HarmonyLib; -using RimWorld; -using System.Collections.Generic; -using Verse; -using Verse.AI; - -namespace ArachnaeSwarm -{ - [HarmonyPatch(typeof(ThingWithComps), "GetFloatMenuOptions")] - public static class Harmony_ThingWithComps_GetFloatMenuOptions - { - [HarmonyPostfix] - public static IEnumerable Postfix(IEnumerable values, ThingWithComps __instance, Pawn selPawn) - { - // First, return all original options - foreach (var value in values) - { - yield return value; - } - - // --- DEBUG LOGGING --- - // Use a more specific check to avoid log spam - if (__instance.def.defName != null && __instance.def.defName.StartsWith("ARA_")) - { - Log.Message($"[PA_Debug] GetFloatMenuOptions Postfix triggered for: {__instance.def.defName}"); - } - - // Check if the thing is our power armor building - var comp = __instance.GetComp(); - - if (comp == null && __instance.def.defName != null && __instance.def.defName.StartsWith("ARA_")) - { - Log.Message($"[PA_Debug] CompPowerArmorStation is NULL for {__instance.def.defName}"); - } - - if (comp != null) - { - Log.Message($"[PA_Debug] CompPowerArmorStation FOUND for {__instance.def.defName}. Checking reachability."); - - // Check if the pawn can interact - if (!selPawn.CanReserveAndReach(__instance, PathEndMode.InteractionCell, Danger.Deadly)) - { - yield return new FloatMenuOption("CannotEnterPowerArmor".Translate() + ": " + "CannotReach".Translate(), null); - } - else - { - // Action to give the job - void enterAction() - { - Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_EnterPowerArmor"), __instance); - selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc); - } - - yield return new FloatMenuOption("EnterPowerArmor".Translate(__instance.Label), enterAction); - } - } - } - } -} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/PowerArmor/JobDriver_EnterPowerArmor.cs b/Source/ArachnaeSwarm/PowerArmor/JobDriver_EnterPowerArmor.cs index 29bc532..007247a 100644 --- a/Source/ArachnaeSwarm/PowerArmor/JobDriver_EnterPowerArmor.cs +++ b/Source/ArachnaeSwarm/PowerArmor/JobDriver_EnterPowerArmor.cs @@ -50,11 +50,38 @@ namespace ArachnaeSwarm apparelFuelComp.ReceiveFuel(buildingFuelComp.Fuel); } + // Sync quality + if (building.TryGetComp() is CompQuality buildingQuality && apparel.TryGetComp() is CompQuality apparelQuality) + { + apparelQuality.SetQuality(buildingQuality.Quality, ArtGenerationContext.Colony); + } + // Wear the apparel. The second argument 'false' is lockWhileWorn. // The third argument 'false' is playerForced, which is CRITICAL. // If playerForced is true, the game automatically locks the apparel. actor.apparel.Wear(apparel, false, false); - + + // Handle weapon switching + if (apparel.Ext.powerArmorWeapon != null) + { + if (actor.equipment.Primary != null) + { + apparel.SetOriginalWeapon(actor.equipment.Primary); + actor.equipment.TryDropEquipment(actor.equipment.Primary, out _, actor.Position, false); + } + + ThingWithComps weapon = (ThingWithComps)ThingMaker.MakeThing(apparel.Ext.powerArmorWeapon); + + // Sync weapon quality with armor quality + if (apparel.TryGetComp() is CompQuality existingApparelQuality && weapon.TryGetComp() is CompQuality weaponQuality) + { + weaponQuality.SetQuality(existingApparelQuality.Quality, ArtGenerationContext.Colony); + } + + actor.equipment.MakeRoomFor(weapon); + actor.equipment.AddEquipment(weapon); + } + // Despawn the building building.DeSpawn(); }