diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 12c871b..bcfc585 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/ARA_Abilities.xml b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml index e51fad1..b8b4b72 100644 --- a/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml +++ b/1.6/1.6/Defs/AbilityDefs/ARA_Abilities.xml @@ -762,7 +762,7 @@ ARA_Flyer_TrackingCharge ArachnaeSwarm.PawnFlyer_TrackingCharge - 0.5 + 1.5 0 @@ -1579,7 +1579,7 @@ - ARA_Praetorian_jump + ARA_Praetorian_Jump 以强力的肌腱向目标地点跳跃。 ArachnaeSwarm/UI/Abilities/ARA_Fighter_Invisibility_jump @@ -1703,6 +1703,118 @@ + + ARA_Praetorian_TailSweep + + 阿拉克涅督虫甩动尾巴猛抽面前的敌人,对扇形范围内所有的敌对目标造成伤害,如果对方在攻击中幸存,就会被击飞并眩晕一段时间。 + ArachnaeSwarm/UI/Abilities/ARA_Praetorian_TailSweep + true + true + true + 1800 + + Verb_CastAbility + 6 + 0.6 + Pawn_Melee_BigBash_HitPawn + + true + + + +
  • + WarTrumpet + 20 +
  • +
  • + 饮食 + true + Food + 0.3 + 营养值不足,需要进食 +
  • +
  • + + 6 + 100 + 12 + + + Blunt + 25 + 1 + + + 120 + + + 3 + false + false + + true + true + 5 + false + + + ARA_Melee_Attack_Hit + Pawn_Melee_BigBash_HitPawn + + + PawnFlyer + + PawnFlyer_Land + + + false + true + false + true + + + false + false +
  • +
    +
    + + ARA_Praetorian_Long_Jump + + 以强力的肌腱向目标地点跳跃。 + ArachnaeSwarm/UI/Abilities/ARA_Fighter_Invisibility_jump + 1000 + 3 + true + false + false + false + + Verb_CastAbilityJump + false + false + + 26 + false + Longjump_Jump + Longjump_Land + + true + false + false + + + CastJump + +
  • + 饮食 + true + Food + 0.1 + 营养值不足,需要进食 +
  • +
    +
    diff --git a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml index 12a4d5f..be6b085 100644 --- a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml +++ b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml @@ -1334,12 +1334,14 @@
  • ARA_Skyraider_jump
  • -
  • ARA_Skyraider_Hivelord
  • +
  • ARA_Skyraider_Ferry
  • ARA_Skyraider_Empthrower
  • +
  • ARA_Skyraider_Hivelord
  • +
  • ARA_Skyraider_Ferry
  • ARA_Skyraider_Hivelord
  • ARA_Skyraider_Empthrower
  • @@ -1347,10 +1349,10 @@ - ARA_Skyraider_Hivelord - - 使空天种发生内驱性进化,以降低机动力和失去高空机动能力为代价,使其获得孵化大量天巢种辅虫的能力——这种辅虫体型很小,会以让人烦扰的近战紧紧黏住敌人。\n\n该进化方向提供8只阿拉克涅天巢种辅虫。 - ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Hivelord + ARA_Skyraider_Ferry + + 使空天种发生内驱性进化,以失去高空机动能力为代价,使其获得更高的移动速度,且在远行队时大幅增加远行队移动速度。\n\n该进化方向不提供阿拉克涅辅虫。 + ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Ferry 1800 false true @@ -1374,20 +1376,20 @@
  • CompAbilityEffect_GiveHediff - ARA_Skyraider_Hivelord + ARA_Skyraider_Ferry True true 1
  • - ARA_Technology_6LOD + ARA_Technology_1FRY
  • - ARA_Skyraider_Hivelord - - 这只阿拉克涅空天种已经获得拔耀,可以投掷天巢种辅虫,这些灵敏的辅虫会后散开四处狩猎目标。 + ARA_Skyraider_Ferry + + 这只阿拉克涅空天种已经获得拔耀,虽然无法再孵化辅虫,但是速度极快,且可以以常态化飞行以增加远行队速度。 HediffWithComps (0.6, 0.4, 0.8) false @@ -1397,7 +1399,7 @@
  • PawnRenderNode_AttachmentHead PawnRenderNodeWorker_FlipWhenCrawling - ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Hivelord_Tail + ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail Body false false @@ -1425,49 +1427,15 @@
  • 0.01 + + 5 + - 2 + 10
  • - -
  • - -
  • - ArachnaeBase_Race_Skyhive - 4 - 200 -
  • - - -
  • - ARA_Skyraider_Hivelord_Turret - 0 - true -
  • -
  • - ARA_Mote_Hivelord_Turret_Range - true -
  • -
    - - ARA_Mote_Hivelord_Turret_Range - MoteAttached - LightingOverlay - true - - 9999999 - true - - - Graphic_Mote - Things/Mote/CombatCommandMask - MoteGlow - (32, 17, 0, 255) - 66 - - ARA_Skyraider_Empthrower @@ -1581,6 +1549,128 @@ 51 + + ARA_Skyraider_Hivelord + + 使空天种发生内驱性进化,以降低机动力和失去高空机动能力为代价,使其获得孵化大量天巢种辅虫的能力——这种辅虫体型很小,会以让人烦扰的近战紧紧黏住敌人。\n\n该进化方向提供8只阿拉克涅天巢种辅虫。 + ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Hivelord + 1800 + false + true + true + false + false + true + false + CastAbilityOnThing + + Verb_CastAbility + 1 + 12 + AcidSpray_Resolve + false + false + + True + + + +
  • + CompAbilityEffect_GiveHediff + ARA_Skyraider_Hivelord + True + true + 1 +
  • +
  • + ARA_Technology_6LOD_T +
  • +
    +
    + + ARA_Skyraider_Hivelord + + 这只阿拉克涅空天种已经获得拔耀,可以投掷天巢种辅虫,这些灵敏的辅虫会后散开四处狩猎目标。 + HediffWithComps + (0.6, 0.4, 0.8) + false + false + 1.0 + +
  • + PawnRenderNode_AttachmentHead + PawnRenderNodeWorker_FlipWhenCrawling + ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Hivelord_Tail + Body + false + false + Fresh, Rotting + + + (0, 0, -0.05) + 120 + + + (0, 0, -0.05) + + + (0, 0, -0.05) + + + (0, 0, -0.05) + + + -40 + + +
  • +
    + +
  • + 0.01 + + 2 + +
  • +
    + +
  • + +
  • + ArachnaeBase_Race_Skyhive + 4 + 200 +
  • + + +
  • + ARA_Skyraider_Hivelord_Turret + 0 + true +
  • +
  • + ARA_Mote_Hivelord_Turret_Range + true +
  • +
    +
    + + ARA_Mote_Hivelord_Turret_Range + MoteAttached + LightingOverlay + true + + 9999999 + true + + + Graphic_Mote + Things/Mote/CombatCommandMask + MoteGlow + (32, 17, 0, 255) + 66 + + @@ -1615,15 +1705,17 @@
  • -
  • ARA_Praetorian_jump
  • +
  • ARA_Praetorian_Jump
  • ARA_Praetorian_Commander_Ability_On
  • ARA_Praetorian_Commander_Ability_Off
  • ARA_Praetorian_Navigator
  • +
  • ARA_Praetorian_Legion
  • ARA_Praetorian_Navigator
  • +
  • ARA_Praetorian_Legion
  • @@ -1692,6 +1784,87 @@
    + + ARA_Praetorian_Legion + + 使禁卫种发生内驱性进化,以牺牲指挥能力为代价,使其换取更强大的近战、远程、防御和通过甩尾、冲撞、跳跃等体术对抗敌人的能力。\n\n该进化方向不提供阿拉克涅辅虫。 + ArachnaeSwarm/UI/Abilities/ARA_Praetorian_Legion + 1800 + false + true + true + false + false + true + false + CastAbilityOnThing + + Verb_CastAbility + 1 + 12 + AcidSpray_Resolve + false + false + + True + + + +
  • + CompAbilityEffect_GiveHediff + ARA_Praetorian_Legion + True + true + 1 +
  • +
  • + +
  • + Melee + Major + 600000 +
  • +
  • + Shooting + Major + 600000 +
  • + + +
  • + ARA_Technology_3LGN_T +
  • +
    +
    + + ARA_Praetorian_Legion + + 这只阿拉克涅禁卫种已经获得拔耀,战斗能力在虫群节点个体中已经登峰造极。 + HediffWithComps + (0.6, 0.4, 0.8) + false + false + 1.0 + +
  • + 0 + + 0.75 + 0.75 + 1 + +
  • +
    + +
  • + +
  • ARA_Praetorian_Long_Jump
  • +
  • ARA_Praetorian_TailSweep
  • +
  • ARA_Ability_TrackingCharge
  • + + +
    +
    diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml index dc3f2a5..df9f3e3 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_Hostile_Hive_PawnKinds.xml @@ -14,7 +14,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -49,7 +49,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -84,7 +84,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -119,7 +119,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -150,7 +150,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -191,7 +191,7 @@
  • ARA_Armed_Organ_Melee
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -226,7 +226,7 @@
  • ARA_Armed_Organ_Huge
  • - +
  • ARA_Inner
  • ARA_Clothes
  • @@ -257,7 +257,7 @@
  • ARA_Armed_Organ_Base
  • - +
  • ARA_Inner
  • ARA_Clothes
  • diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index b593314..e83ae67 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -211,6 +211,10 @@ false + +
  • ARA_Init_Clothes
  • +
    + 1000~2000
  • Violent
  • @@ -238,9 +242,6 @@
  • ARA_AcidSprayBurst
  • - - - 0 ArachnaeNode_Race_ShieldHead @@ -256,9 +257,6 @@ - - - 0 ArachnaeNode_Race_WeaponSmith @@ -274,9 +272,6 @@ - - - 0 ArachnaeNode_Race_Facehugger @@ -295,9 +290,6 @@
  • ARA_Ability_Possess
  • - - - 0
    ArachnaeNode_Race_Fighter @@ -319,9 +311,6 @@ ARA_RaceBaseSwarmProduceSwitchHediff - - - 0 ArachnaeNode_Race_Smokepop @@ -406,9 +395,6 @@
  • ARA_GuardianPsyField_Off
  • ARA_Ability_Morph
  • - - - 0
    ArachnaeNode_Race_Praetorian @@ -453,9 +439,6 @@ - - - 0 diff --git a/1.6/1.6/Defs/RecipeDefs/ARA_Recipes.xml b/1.6/1.6/Defs/RecipeDefs/ARA_Recipes.xml index 7f88436..ce7c2c8 100644 --- a/1.6/1.6/Defs/RecipeDefs/ARA_Recipes.xml +++ b/1.6/1.6/Defs/RecipeDefs/ARA_Recipes.xml @@ -102,88 +102,6 @@ 30 - - ARA_Surgery_Install_Shell_Thorn - - 为阿拉克涅虫族的甲壳植入两排棘刺腔管,它们是拥有半自主意识的器官,会对附近的敌军自动发射棘刺。此外,该变异也会加厚甲壳以获得更强的防御力。 - - ARA_Shell_Thorn_Hediff - ARA_Shell_Thorn_Turret - - 正在实施定向变异 - -
  • - - -
  • ARA_Activated_Bacterium
  • - - - 15 - -
  • - - -
  • ARA_Gene_Essence
  • - - - 20 - -
    - -
  • ARA_Chitin_Shell
  • -
    - - -
  • ARA_Activated_Bacterium
  • -
    -
    - ARA_Shell_Thorn_Hediff - ARA_Technology_7EVO -
    - - ARA_Shell_Thorn_Hediff - - Hediff_Implant - 阿拉克涅虫族在甲壳上植入了两排棘刺腔管,只要不处于近战状态下,它们就会对靠近的敌人自动发射棘刺攻击对方。 - - ARA_Surgery_Install_Shell_Thorn - ARA_Shell_Thorn_Turret - - - true - - -
  • - - 0.2 - 0.2 - -
  • -
    - -
  • - ARA_Shell_Thorn_Turret - 0 - true -
  • -
    -
    - - ARA_Shell_Thorn - - 在阿拉克涅虫族甲壳上植入两排棘刺腔管,只要不处于近战状态下,它们就会对靠近的敌人自动发射棘刺攻击对方。该手术不需要制作部件,可以直接在阿拉克涅督虫身上实施。 - - ARA_Surgery_Install_Shell_Thorn - ARA_Shell_Thorn_Turret - - - ARA_Technology_7EVO - - - 15 - 20 - - ARA_Surgery_Install_Reactive_Shell diff --git a/1.6/1.6/Defs/RecipeDefs/ARA_Titan_Recipes.xml b/1.6/1.6/Defs/RecipeDefs/ARA_Titan_Recipes.xml new file mode 100644 index 0000000..ab264e4 --- /dev/null +++ b/1.6/1.6/Defs/RecipeDefs/ARA_Titan_Recipes.xml @@ -0,0 +1,85 @@ + + + + ARA_Surgery_Install_Shell_Thorn + + 为阿拉克涅虫族的甲壳植入两排棘刺腔管,它们是拥有半自主意识的器官,会对附近的敌军自动发射棘刺。此外,该变异也会加厚甲壳以获得更强的防御力。 + + ARA_Shell_Thorn_Hediff + ARA_Shell_Thorn_Turret + + 正在实施定向变异 + +
  • + + +
  • ARA_Activated_Bacterium
  • + + + 15 + +
  • + + +
  • ARA_Gene_Essence
  • + + + 20 + +
    + +
  • ARA_Chitin_Shell
  • +
    + + +
  • ARA_Activated_Bacterium
  • +
    +
    + ARA_Shell_Thorn_Hediff + ARA_Technology_7EVO_T +
    + + ARA_Shell_Thorn_Hediff + + Hediff_Implant + 阿拉克涅虫族在甲壳上植入了两排棘刺腔管,只要不处于近战状态下,它们就会对靠近的敌人自动发射棘刺攻击对方。 + + ARA_Surgery_Install_Shell_Thorn + ARA_Shell_Thorn_Turret + + + true + + +
  • + + 0.2 + 0.2 + +
  • +
    + +
  • + ARA_Shell_Thorn_Turret + 0 + true +
  • +
    +
    + + ARA_Shell_Thorn + + 在阿拉克涅虫族甲壳上植入两排棘刺腔管,只要不处于近战状态下,它们就会对靠近的敌人自动发射棘刺攻击对方。该手术不需要制作部件,可以直接在阿拉克涅督虫身上实施。 + + ARA_Surgery_Install_Shell_Thorn + ARA_Shell_Thorn_Turret + + + ARA_Technology_7EVO + + + 15 + 20 + + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml index 7cd00b8..0baba82 100644 --- a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml +++ b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml @@ -80,7 +80,7 @@ ARA_Technology_7VXI - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 300 2.00 0.90 @@ -94,7 +94,7 @@ ARA_Technology_8VXI - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 1250 5.50 0.90 @@ -108,7 +108,7 @@ ARA_Technology_9VXI - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 2000 10.00 1.50 @@ -122,7 +122,7 @@ ARA_Technology_10VXI - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 3000 11.00 1.50 @@ -146,7 +146,7 @@ ARA_Technology_1THD - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的灵能闪电系武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的灵能闪电系武器。 2500 7.50 2.70 @@ -299,7 +299,7 @@ ARA_Technology_5PAV - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器,并允许部分带毒针的虫族进行毒针喷射。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器,并允许部分带毒针的虫族进行毒针喷射。 200 1.00 0.30 @@ -313,7 +313,7 @@ ARA_Technology_6PAV - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 1000 5.50 0.30 @@ -327,7 +327,7 @@ ARA_Technology_7PAV - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 1800 10.00 0.30 @@ -338,17 +338,6 @@
  • ARA_Technology_2WMT
  • - - ARA_Technology_7XPAV - - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 - 2000 - 11.00 - 0.30 - -
  • ARA_Technology_7PAV
  • -
    -
    ARA_Technology_8PAV @@ -358,14 +347,14 @@ 0.30
  • ARA_Technology_1NPT
  • -
  • ARA_Technology_7XPAV
  • +
  • ARA_Technology_7PAV
  • ARA_Technology_2MEL - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 600 5.50 2.70 @@ -376,7 +365,7 @@ ARA_Technology_3MEL - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 1200 10.00 0.90 @@ -532,7 +521,7 @@ ARA_Technology_6SPV - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许工艺种孵化新的武器。 + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 800 5.50 1.50 @@ -646,17 +635,14 @@ - ARA_Technology_6LOD - - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许空天种进行定向进化,以牺牲高速和高空机动的能力换取向敌人投射大量天巢种的能力,这种飞行辅虫速度很快,并且在近战中很难缠。\n\n阿拉克涅虫群所有需要蓝图的科技,都需要使用其触须分支特有的研究方式完成研究。 - 3500 - 10.00 - 5.30 - -
  • ARA_Technology_2KYC
  • -
    + ARA_Technology_1FRY + + <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许空天种进行定向进化,以牺牲高空机动和辅虫孵化的能力换取在远行队中的大量移动速度增幅。\n\n阿拉克涅虫群所有需要蓝图的科技,都需要使用其触须分支特有的研究方式完成研究。 + 1000 + 6.50 + 3.80 -
  • ARA_Technology_2WMT
  • +
  • ARA_Technology_2KYC
  • @@ -774,20 +760,6 @@
  • ARA_Technology_4CLO
  • - - ARA_Technology_7EVO - - <color=#887E78><i>阿拉克涅虫群-主巢触须\n主巢触须的进化路径是包含于每一支虫群中的通用进化路径,它们奠定了虫群在生物学上的优越性。</i></color>\n\n允许实行新的阿拉克涅进化手术,允许阿拉克涅虫族植入会自动攻击附近敌人的棘刺腔管。 - 1500 - 7.50 - 4.80 - -
  • ARA_Technology_5ESS
  • -
    - -
  • ARA_Technology_1VTE
  • -
    -
    ARA_Technology_8EVO diff --git a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects_Titan.xml b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects_Titan.xml index e3248a3..bf1e630 100644 --- a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects_Titan.xml +++ b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects_Titan.xml @@ -1,6 +1,5 @@ - ARA_Titan_Base_Technology @@ -12,7 +11,7 @@ ARA_Technology_8SLA - <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许女皇种孵化新的兽虫——暴屠种。\n\n阿拉克涅虫群 泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许女皇种孵化新的兽虫——暴屠种。\n\n阿拉克涅虫群泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 3000 17.00 5.80 @@ -27,10 +26,10 @@ ARA_Technology_7ACD_T - <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许蜜罐种进行定向进化,抛弃孵化辅虫的能力,换取溶解囚犯和俘虏以快速换取虫蜜的溶脂强酸。\n\n阿拉克涅虫群 泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许蜜罐种进行定向进化,抛弃孵化辅虫的能力,换取溶解囚犯和俘虏以快速换取虫蜜的溶脂强酸。\n\n阿拉克涅虫群泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 500 17.00 - 0.30 + 1.50
  • ARA_Technology_7VXI
  • @@ -38,4 +37,75 @@
  • ARA_Titan_Base_Technology
  • + + ARA_Technology_3LGN_T + + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许禁卫种进行定向进化,抛弃孵化辅虫的能力,换取更强大的近战、远程、防御能力和各种体术。\n\n阿拉克涅虫群泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 + 3000 + 17.00 + 3.90 + +
  • ARA_Technology_7KYC
  • +
    + +
  • ARA_Titan_Base_Technology
  • +
    +
    + + ARA_Technology_6LOD_T + + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许空天种进行定向进化,以牺牲高速和高空机动的能力换取向敌人投射大量天巢种的能力,这种飞行辅虫速度很快,并且在近战中很难缠。\n\n阿拉克涅虫群泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 + 3500 + 17.00 + 4.90 + +
  • ARA_Technology_2KYC
  • +
  • ARA_Technology_2WMT
  • +
    + +
  • ARA_Titan_Base_Technology
  • +
    +
    + + ARA_Technology_7EVO_T + + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许实行新的阿拉克涅进化手术,允许阿拉克涅虫族植入会自动攻击附近敌人的棘刺腔管。\n\n阿拉克涅虫群泰坦触须所有需要蓝图的科技,其研究只能通过基因试验卵进行。 + 1500 + 17.00 + 4.40 + +
  • ARA_Technology_5KYC
  • +
    + +
  • ARA_Titan_Base_Technology
  • +
    +
    + + ARA_Technology_1STG_T + + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 + 500 + 17.00 + 0.90 + +
  • ARA_Technology_6PAV
  • +
    + +
  • ARA_Titan_Base_Technology
  • +
    +
    + + ARA_Technology_3XPV_T + + <color=#915A30><i>阿拉克涅虫群-泰坦触须\n泰坦触须是阿拉克涅虫群的主力军团,包含阿拉克涅虫群中最坚韧、最具有适应力的族群,承担在战场上维持战线的任务。这个分支下的虫群拥有均衡的攻防能力,擅长以硬碰硬的模式消灭对手。</i></color>\n\n允许阿拉克涅茧孵化新的武器。 + 2000 + 17.00 + 0.30 + +
  • ARA_Technology_7PAV
  • +
    + +
  • ARA_Titan_Base_Technology
  • +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml index b1cb50e..f5953c9 100644 --- a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml +++ b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml @@ -227,8 +227,8 @@ -
  • Wula_Apparel
  • -
  • Wula_Clothes
  • +
  • ARA_Apparel
  • +
  • ARA_Clothes
  • None
  • @@ -277,6 +277,9 @@ ArachnaeSwarm/Apparel/ARA_Maid_Uniform + +
  • ARA_Init_Clothes
  • +
  • Torso
  • Shoulders
  • @@ -324,6 +327,9 @@ ArachnaeSwarm/Apparel/ARA_Maid_Dress + +
  • ARA_Init_Clothes
  • +
  • Torso
  • Shoulders
  • @@ -371,6 +377,9 @@ ArachnaeSwarm/Apparel/ARA_Daily_Wear + +
  • ARA_Init_Clothes
  • +
  • Torso
  • Shoulders
  • 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 354c788..8b8742d 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 @@ -467,7 +467,7 @@ SpitterSpawn - ARA_Technology_6PAV + ARA_Technology_1STG_T UnfinishedWeapon @@ -705,7 +705,7 @@ SpitterSpawn - ARA_Technology_7XPAV + ARA_Technology_3XPV_T UnfinishedWeapon diff --git a/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_eath.png b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_eath.png new file mode 100644 index 0000000..a429904 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_eath.png differ diff --git a/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_north.png b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_north.png new file mode 100644 index 0000000..90646ca Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_north.png differ diff --git a/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_south.png b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_south.png new file mode 100644 index 0000000..8b35112 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Ferry_Tail_south.png differ diff --git a/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_Legion.png b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_Legion.png new file mode 100644 index 0000000..cd71a61 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_Legion.png differ diff --git a/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_TailSweep.png b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_TailSweep.png new file mode 100644 index 0000000..9683e9f Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Praetorian_TailSweep.png differ diff --git a/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Ferry.png b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Ferry.png new file mode 100644 index 0000000..de590c8 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/UI/Abilities/ARA_Skyraider_Ferry.png differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index 4043db4..345e5be 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 f9a668d..b1ff94b 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -2,6 +2,18 @@ "Version": 1, "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\ara_fanshapedstunknockback\\compproperties_abilityfanshapedstunknockback.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_fanshapedstunknockback\\compproperties_abilityfanshapedstunknockback.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\ara_fanshapedstunknockback\\compabilityeffect_fanshapedstunknockback.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_fanshapedstunknockback\\compabilityeffect_fanshapedstunknockback.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\abilities\\ara_ejectorgans\\compabilityeffect_ejectorgans.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_ejectorgans\\compabilityeffect_ejectorgans.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, { "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\pawn_comps\\ara_comphediffgiver\\compproperties_hediffgiver.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:pawn_comps\\ara_comphediffgiver\\compproperties_hediffgiver.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -18,15 +30,54 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 2, + "SelectedChildIndex": 1, "Children": [ { "$type": "Bookmark", "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "CompProperties_AbilityFanShapedStunKnockback.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_FanShapedStunKnockback\\CompProperties_AbilityFanShapedStunKnockback.cs", + "RelativeDocumentMoniker": "Abilities\\ARA_FanShapedStunKnockback\\CompProperties_AbilityFanShapedStunKnockback.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_FanShapedStunKnockback\\CompProperties_AbilityFanShapedStunKnockback.cs", + "RelativeToolTip": "Abilities\\ARA_FanShapedStunKnockback\\CompProperties_AbilityFanShapedStunKnockback.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAUAAAA9AAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-15T06:29:46.581Z", + "EditorCaption": "" + }, { "$type": "Document", "DocumentIndex": 1, + "Title": "CompAbilityEffect_FanShapedStunKnockback.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_FanShapedStunKnockback\\CompAbilityEffect_FanShapedStunKnockback.cs", + "RelativeDocumentMoniker": "Abilities\\ARA_FanShapedStunKnockback\\CompAbilityEffect_FanShapedStunKnockback.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_FanShapedStunKnockback\\CompAbilityEffect_FanShapedStunKnockback.cs", + "RelativeToolTip": "Abilities\\ARA_FanShapedStunKnockback\\CompAbilityEffect_FanShapedStunKnockback.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-15T06:29:34.172Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "CompAbilityEffect_EjectOrgans.cs", + "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_EjectOrgans\\CompAbilityEffect_EjectOrgans.cs", + "RelativeDocumentMoniker": "Abilities\\ARA_EjectOrgans\\CompAbilityEffect_EjectOrgans.cs", + "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_EjectOrgans\\CompAbilityEffect_EjectOrgans.cs", + "RelativeToolTip": "Abilities\\ARA_EjectOrgans\\CompAbilityEffect_EjectOrgans.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAQAAAAXAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-02-15T06:29:29.494Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, "Title": "CompHediffGiver.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_CompHediffGiver\\CompHediffGiver.cs", @@ -38,7 +89,7 @@ }, { "$type": "Document", - "DocumentIndex": 0, + "DocumentIndex": 3, "Title": "CompProperties_HediffGiver.cs", "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_CompHediffGiver\\CompProperties_HediffGiver.cs", "RelativeDocumentMoniker": "Pawn_Comps\\ARA_CompHediffGiver\\CompProperties_HediffGiver.cs", diff --git a/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompAbilityEffect_FanShapedStunKnockback.cs b/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompAbilityEffect_FanShapedStunKnockback.cs new file mode 100644 index 0000000..cb0bfc4 --- /dev/null +++ b/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompAbilityEffect_FanShapedStunKnockback.cs @@ -0,0 +1,722 @@ +using RimWorld; +using System.Collections.Generic; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class CompAbilityEffect_FanShapedStunKnockback : CompAbilityEffect + { + private readonly List tmpCells = new List(); + private Effecter effecter; + + public new CompProperties_AbilityFanShapedStunKnockback Props => (CompProperties_AbilityFanShapedStunKnockback)props; + + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) + { + base.Apply(target, dest); + + Pawn caster = parent.pawn; + if (caster == null || caster.Map == null || !target.IsValid) + return; + + // 1. 获取扇形区域内的所有单元格 + List affectedCells = GetFanShapedCells(caster, target.Cell); + + // 2. 收集区域内的所有目标 + var affectedTargets = CollectAffectedTargets(caster, affectedCells); + + // 3. 对每个目标应用效果 + foreach (Thing targetThing in affectedTargets) + { + if (targetThing != null && !targetThing.Destroyed && targetThing.Spawned) + { + if (targetThing is Pawn pawn) + { + ApplyEffectToPawn(caster, pawn, target); + } + else if (Props.affectNonPawnThings) + { + ApplyEffectToNonPawnThing(caster, targetThing, target); + } + } + } + + // 4. 播放整体攻击效果(参考Verb_MeleeAttack_Cleave) + PlayMainAttackEffect(caster, target); + } + + /// + /// 播放主要攻击效果 + /// + private void PlayMainAttackEffect(Pawn caster, LocalTargetInfo target) + { + if (caster == null || caster.Map == null || !target.IsValid) + return; + + // 播放主要攻击效果 + if (Props.impactEffecter != null) + { + effecter = Props.impactEffecter.Spawn(); + // 关键修复:第一个参数是施法者位置,第二个参数是目标位置 + effecter.Trigger(new TargetInfo(caster.Position, caster.Map), target.ToTargetInfo(caster.Map)); + effecter.Cleanup(); + effecter = null; + } + + // 播放攻击音效 + if (Props.impactSound != null) + { + Props.impactSound.PlayOneShot(new TargetInfo(target.Cell, caster.Map)); + } + } + + /// + /// 收集扇形区域内的所有目标(包括Pawn和非Pawn物体) + /// + private List CollectAffectedTargets(Pawn caster, List cells) + { + List targets = new List(); + HashSet addedThings = new HashSet(); + + if (caster == null || caster.Map == null || cells == null) + return targets; + + foreach (IntVec3 cell in cells) + { + if (!cell.InBounds(caster.Map)) + continue; + + List things = cell.GetThingList(caster.Map); + foreach (Thing thing in things) + { + if (thing == null || addedThings.Contains(thing)) + continue; + + // 检查是否为施法者 + if (!Props.affectCaster && thing == caster) + continue; + + // 检查是否需要视线 + if (Props.requireLineOfSightToTarget && !GenSight.LineOfSight(caster.Position, cell, caster.Map)) + continue; + + // Pawn的处理 + if (thing is Pawn pawn) + { + // 检查是否为敌人 + if (Props.onlyAffectEnemies && !pawn.HostileTo(caster)) + continue; + + targets.Add(pawn); + addedThings.Add(pawn); + } + // 非Pawn物体的处理 + else if (Props.affectNonPawnThings && thing is ThingWithComps thingWithComps) + { + // 检查是否为敌人(如果物体有派系) + if (Props.onlyAffectEnemies) + { + bool isEnemy = IsThingEnemy(caster, thingWithComps); + if (!isEnemy) + continue; + } + + // 检查是否可以被伤害 + if (Props.canDamageNonPawnThings && !CanBeDamaged(thingWithComps)) + continue; + + targets.Add(thingWithComps); + addedThings.Add(thingWithComps); + } + } + } + + return targets; + } + + /// + /// 检查物体是否为敌人 + /// + private bool IsThingEnemy(Pawn caster, Thing thing) + { + // 如果物体有派系,检查是否敌对 + if (thing.Faction != null) + { + return caster.HostileTo(thing); + } + + // 如果物体是建筑且没有派系,根据设置决定 + // 默认情况下,视为中立(只有当onlyAffectEnemies为false时才影响) + return false; + } + + /// + /// 检查物体是否可以被伤害 + /// + private bool CanBeDamaged(Thing thing) + { + // 检查是否有生命值组件 + if (thing.def.useHitPoints) + { + // 检查是否被摧毁或已死亡 + if (thing.Destroyed || thing.HitPoints <= 0) + return false; + + // 检查是否可以承受伤害 + if (thing.def.destroyable) + return true; + } + + return false; + } + + /// + /// 对非Pawn物体应用效果 + /// + private void ApplyEffectToNonPawnThing(Pawn caster, Thing targetThing, LocalTargetInfo targetInfo) + { + if (targetThing == null || caster == null) + return; + + // 1. 造成伤害 + ApplyDamageToNonPawn(caster, targetThing); + + // 注意:非Pawn物体不进行击退,也不眩晕 + } + + /// + /// 对非Pawn物体造成伤害 + /// + private void ApplyDamageToNonPawn(Pawn caster, Thing targetThing) + { + if (!Props.canDamageNonPawnThings) + return; + + // 获取调整后的伤害值 + float adjustedDamage = GetAdjustedDamage(caster); + + // 应用非Pawn物体的伤害倍率 + float finalDamage = adjustedDamage * Props.nonPawnDamageMultiplier; + + // 检查目标是否可以被伤害 + if (targetThing.def.useHitPoints && targetThing.HitPoints > 0) + { + // 创建伤害信息 + DamageInfo damageInfo = new DamageInfo( + Props.damageDef, + finalDamage, + Props.armorPenetration, + -1f, + caster, + null + ); + + // 应用伤害 + targetThing.TakeDamage(damageInfo); + + // 播放个体命中效果 + if (Props.applySpecialEffectsToNonPawn && Props.impactEffecter != null && caster.Map != null) + { + Effecter effect = Props.impactEffecter.Spawn(); + // 关键修复:第一个参数是施法者,第二个参数是目标 + effect.Trigger(new TargetInfo(caster.Position, caster.Map), new TargetInfo(targetThing.Position, caster.Map)); + effect.Cleanup(); + } + + // 播放个体命中音效 + if (Props.applySpecialEffectsToNonPawn && Props.impactSound != null && caster.Map != null) + { + Props.impactSound.PlayOneShot(new TargetInfo(targetThing.Position, caster.Map)); + } + } + } + + /// + /// 获取伤害系数 + /// + private float GetDamageMultiplier(Pawn caster) + { + if (caster == null) return 1f; + + if (Props.multiplyDamageByMeleeFactor) + { + if (Props.damageMultiplierStat != null) + { + return caster.GetStatValue(Props.damageMultiplierStat); + } + return caster.GetStatValue(StatDefOf.MeleeDamageFactor); + } + + return 1f; + } + /// + /// 获取眩晕时间系数 + /// + private float GetStunMultiplier(Pawn caster) + { + if (caster == null) return 1f; + + if (Props.multiplyStunTimeByMeleeFactor) + { + if (Props.stunMultiplierStat != null) + { + return caster.GetStatValue(Props.stunMultiplierStat); + } + return caster.GetStatValue(StatDefOf.MeleeDamageFactor); + } + + return 1f; + } + /// + /// 获取调整后的伤害值 + /// + private float GetAdjustedDamage(Pawn caster) + { + float baseDamage = Props.damageAmount; + float multiplier = GetDamageMultiplier(caster); + return baseDamage * multiplier; + } + /// + /// 获取调整后的眩晕时间 + /// + private int GetAdjustedStunTicks(Pawn caster) + { + int baseStunTicks = Props.stunTicks; + float multiplier = GetStunMultiplier(caster); + return Mathf.RoundToInt(baseStunTicks * multiplier); + } + /// + /// 显示伤害和眩晕加成信息(用于预览) + /// + public string GetAdjustedDamageAndStunInfo(Pawn caster) + { + if (caster == null) return string.Empty; + + float damageMultiplier = GetDamageMultiplier(caster); + float stunMultiplier = GetStunMultiplier(caster); + + // 如果都不需要乘以系数,则不显示信息 + if (damageMultiplier == 1f && stunMultiplier == 1f) + { + return string.Empty; + } + + var sb = new System.Text.StringBuilder(); + sb.AppendLine("LBD_AdjustedEffects".Translate()); + + if (damageMultiplier != 1f) + { + float adjustedDamage = Props.damageAmount * damageMultiplier; + sb.AppendLine("LBD_AdjustedDamage".Translate(Props.damageAmount, adjustedDamage)); + } + + if (stunMultiplier != 1f) + { + int adjustedStunTicks = Mathf.RoundToInt(Props.stunTicks * stunMultiplier); + float baseStunSeconds = Props.stunTicks / 60f; + float adjustedStunSeconds = adjustedStunTicks / 60f; + sb.AppendLine("LBD_AdjustedStun".Translate(baseStunSeconds, adjustedStunSeconds)); + } + + return sb.ToString(); + } + + /// + /// 获取扇形区域内的所有单元格 + /// + private List GetFanShapedCells(Pawn caster, IntVec3 targetCell) + { + tmpCells.Clear(); + + if (caster == null || caster.Map == null) + return tmpCells; + + IntVec3 casterPos = caster.Position; + IntVec3 clampedTarget = targetCell.ClampInsideMap(caster.Map); + + // 如果施法者和目标在同一位置,则没有扇形 + if (casterPos == clampedTarget) + return tmpCells; + + Vector3 casterVector = casterPos.ToVector3Shifted().Yto0(); + + // 计算方向向量和角度 + float horizontalLength = (clampedTarget - casterPos).LengthHorizontal; + float dirX = (clampedTarget.x - casterPos.x) / horizontalLength; + float dirZ = (clampedTarget.z - casterPos.z) / horizontalLength; + + // 调整目标点到扇形半径 + clampedTarget.x = Mathf.RoundToInt(casterPos.x + dirX * Props.range); + clampedTarget.z = Mathf.RoundToInt(casterPos.z + dirZ * Props.range); + + // 计算扇形的中心角 + float targetAngle = Vector3.SignedAngle( + clampedTarget.ToVector3Shifted().Yto0() - casterVector, + Vector3.right, + Vector3.up); + + // 计算扇形的半角(从中心线到边缘) + float halfWidth = Props.lineWidthEnd / 2f; + float coneEdgeDistance = Mathf.Sqrt( + Mathf.Pow((clampedTarget - casterPos).LengthHorizontal, 2f) + + Mathf.Pow(halfWidth, 2f)); + float halfAngle = Mathf.Rad2Deg * Mathf.Asin(halfWidth / coneEdgeDistance); + + // 限制最大角度不超过设定值 + halfAngle = Mathf.Min(halfAngle, Props.coneSizeDegrees / 2f); + + // 遍历半径内的所有单元格,检查是否在扇形内 + int radialCellCount = GenRadial.NumCellsInRadius(Props.range); + for (int i = 0; i < radialCellCount; i++) + { + IntVec3 cell = casterPos + GenRadial.RadialPattern[i]; + + // 检查单元格是否有效 + if (!CanUseCell(caster, cell)) + continue; + + // 计算单元格相对于施法者的角度 + float cellAngle = Vector3.SignedAngle( + cell.ToVector3Shifted().Yto0() - casterVector, + Vector3.right, + Vector3.up); + + // 检查角度差是否在扇形范围内 + if (Mathf.Abs(Mathf.DeltaAngle(cellAngle, targetAngle)) <= halfAngle) + { + tmpCells.Add(cell); + } + } + + // 添加从施法者到目标点的直线上的单元格 + List lineCells = GenSight.BresenhamCellsBetween(casterPos, clampedTarget); + for (int i = 0; i < lineCells.Count; i++) + { + IntVec3 cell = lineCells[i]; + if (!tmpCells.Contains(cell) && CanUseCell(caster, cell)) + { + tmpCells.Add(cell); + } + } + + return tmpCells; + } + + /// + /// 对单个Pawn应用效果 + /// + private void ApplyEffectToPawn(Pawn caster, Pawn target, LocalTargetInfo targetInfo) + { + // 1. 造成伤害 + bool targetDied = ApplyDamageAndStun(caster, target); + + // 2. 如果目标存活,执行击退 + if (!targetDied && target != null && !target.Dead && !target.Downed) + { + PerformKnockback(caster, target); + } + } + + /// + /// 应用伤害和眩晕效果,返回目标是否死亡 + /// + private bool ApplyDamageAndStun(Pawn caster, Pawn target) + { + // 获取调整后的伤害和眩晕时间 + float adjustedDamage = GetAdjustedDamage(caster); + int adjustedStunTicks = GetAdjustedStunTicks(caster); + + // 创建伤害信息 + DamageInfo damageInfo = new DamageInfo( + Props.damageDef, + adjustedDamage, + Props.armorPenetration, + -1f, + caster, + null + ); + // 应用伤害 + target.TakeDamage(damageInfo); + + // 检查目标是否死亡 + bool targetDied = target.Dead || target.Destroyed; + + if (targetDied) + { + return true; + } + + // 播放个体命中效果(可选,因为已经有主要攻击效果) + if (Props.applySpecialEffectsToNonPawn && Props.impactEffecter != null && caster.Map != null) + { + Effecter effect = Props.impactEffecter.Spawn(); + // 关键修复:第一个参数是施法者,第二个参数是目标 + effect.Trigger(new TargetInfo(caster.Position, caster.Map), new TargetInfo(target.Position, caster.Map)); + effect.Cleanup(); + } + + // 应用眩晕 - 只在目标存活时应用 + if (adjustedStunTicks > 0 && !target.Dead) + { + target.stances?.stunner?.StunFor(adjustedStunTicks, caster); + } + return false; + } + + /// + /// 执行击退 + /// + private void PerformKnockback(Pawn caster, Pawn target) + { + if (target == null || target.Destroyed || target.Dead || caster.Map == null) + return; + + // 计算击退方向(从施法者指向目标) + IntVec3 knockbackDirection = CalculateKnockbackDirection(caster, target.Position); + + // 寻找最远的可站立击退位置 + IntVec3 knockbackDestination = FindFarthestStandablePosition(caster, target, knockbackDirection); + + // 如果找到了有效位置,执行击退飞行 + if (knockbackDestination.IsValid && knockbackDestination != target.Position) + { + CreateKnockbackFlyer(caster, target, knockbackDestination); + } + } + + /// + /// 计算击退方向 + /// + private IntVec3 CalculateKnockbackDirection(Pawn caster, IntVec3 targetPosition) + { + IntVec3 direction = targetPosition - caster.Position; + + // 标准化方向(保持整数坐标) + if (direction.x != 0 || direction.z != 0) + { + if (Mathf.Abs(direction.x) > Mathf.Abs(direction.z)) + { + return new IntVec3(Mathf.Sign(direction.x) > 0 ? 1 : -1, 0, 0); + } + else + { + return new IntVec3(0, 0, Mathf.Sign(direction.z) > 0 ? 1 : -1); + } + } + + // 如果施法者和目标在同一位置,使用随机方向 + return new IntVec3(Rand.Value > 0.5f ? 1 : -1, 0, 0); + } + + /// + /// 寻找最远的可站立击退位置 + /// + private IntVec3 FindFarthestStandablePosition(Pawn caster, Pawn target, IntVec3 direction) + { + Map map = caster.Map; + IntVec3 currentPos = target.Position; + IntVec3 farthestValidPos = currentPos; + + // 从最大距离开始向回找,找到第一个可站立的格子 + for (int distance = Props.maxKnockbackDistance; distance >= 1; distance--) + { + IntVec3 testPos = currentPos + (direction * distance); + + if (!testPos.InBounds(map)) + continue; + + if (IsCellStandableAndEmpty(caster, target, testPos, map)) + { + farthestValidPos = testPos; + break; + } + } + + return farthestValidPos; + } + + /// + /// 检查格子是否可站立且没有其他Pawn + /// + private bool IsCellStandableAndEmpty(Pawn caster, Pawn target, IntVec3 cell, Map map) + { + if (!cell.InBounds(map)) + return false; + + // 检查是否可站立 + if (!cell.Standable(map)) + return false; + + // 检查是否有建筑阻挡 + if (!Props.canKnockbackIntoWalls) + { + Building edifice = cell.GetEdifice(map); + if (edifice != null && !(edifice is Building_Door)) + return false; + } + + // 检查视线 + if (Props.requireLineOfSight && !GenSight.LineOfSight(target.Position, cell, map)) + return false; + + // 检查是否有其他pawn + List thingList = cell.GetThingList(map); + foreach (Thing thing in thingList) + { + if (thing is Pawn otherPawn && otherPawn != target) + return false; + } + + return true; + } + + /// + /// 创建击退飞行器 + /// + private void CreateKnockbackFlyer(Pawn caster, Pawn target, IntVec3 destination) + { + Map map = caster.Map; + + // 使用自定义飞行器或默认飞行器 + ThingDef flyerDef = Props.knockbackFlyerDef ?? ThingDefOf.PawnFlyer; + + // 创建飞行器 + PawnFlyer flyer = PawnFlyer.MakeFlyer( + flyerDef, + target, + destination, + Props.flightEffecterDef, + Props.landingSound, + false, // 不携带物品 + null, // 不覆盖起始位置 + parent, // 传递Ability对象 + new LocalTargetInfo(destination) + ); + + if (flyer != null) + { + GenSpawn.Spawn(flyer, destination, map); + } + } + + /// + /// 检查单元格是否可用于效果 + /// + private bool CanUseCell(Pawn caster, IntVec3 cell) + { + if (caster == null || caster.Map == null) + return false; + + if (!cell.InBounds(caster.Map)) + return false; + + if (!Props.affectCaster && cell == caster.Position) + return false; + + if (!Props.canHitFilledCells && cell.Filled(caster.Map)) + return false; + + if (!cell.InHorDistOf(caster.Position, Props.range)) + return false; + + return true; + } + + /// + /// 绘制效果预览 + /// + public override void DrawEffectPreview(LocalTargetInfo target) + { + base.DrawEffectPreview(target); + + Pawn caster = parent.pawn; + if (caster == null || caster.Map == null || !target.IsValid) + return; + + // 绘制扇形区域 + List cells = GetFanShapedCells(caster, target.Cell); + GenDraw.DrawFieldEdges(cells, Color.red); + + // 绘制扇形边线 + DrawConeBoundaries(caster, target.Cell); + } + + /// + /// 绘制扇形边界线 + /// + private void DrawConeBoundaries(Pawn caster, IntVec3 targetCell) + { + if (caster == null || caster.Map == null) + return; + + IntVec3 casterPos = caster.Position; + Vector3 casterVector = casterPos.ToVector3Shifted().Yto0(); + + // 计算中心线 + float horizontalLength = (targetCell - casterPos).LengthHorizontal; + float dirX = (targetCell.x - casterPos.x) / horizontalLength; + float dirZ = (targetCell.z - casterPos.z) / horizontalLength; + + IntVec3 clampedTarget = targetCell; + clampedTarget.x = Mathf.RoundToInt(casterPos.x + dirX * Props.range); + clampedTarget.z = Mathf.RoundToInt(casterPos.z + dirZ * Props.range); + + float targetAngle = Vector3.SignedAngle( + clampedTarget.ToVector3Shifted().Yto0() - casterVector, + Vector3.right, + Vector3.up); + + float halfWidth = Props.lineWidthEnd / 2f; + float coneEdgeDistance = Mathf.Sqrt( + Mathf.Pow((clampedTarget - casterPos).LengthHorizontal, 2f) + + Mathf.Pow(halfWidth, 2f)); + float halfAngle = Mathf.Rad2Deg * Mathf.Asin(halfWidth / coneEdgeDistance); + halfAngle = Mathf.Min(halfAngle, Props.coneSizeDegrees / 2f); + + // 绘制两条边界线 + float leftAngle = targetAngle - halfAngle; + float rightAngle = targetAngle + halfAngle; + + Vector3 leftDir = Quaternion.Euler(0, leftAngle, 0) * Vector3.right; + Vector3 rightDir = Quaternion.Euler(0, rightAngle, 0) * Vector3.right; + + IntVec3 leftEnd = casterPos + new IntVec3( + Mathf.RoundToInt(leftDir.x * Props.range), + 0, + Mathf.RoundToInt(leftDir.z * Props.range) + ).ClampInsideMap(caster.Map); + + IntVec3 rightEnd = casterPos + new IntVec3( + Mathf.RoundToInt(rightDir.x * Props.range), + 0, + Mathf.RoundToInt(rightDir.z * Props.range) + ).ClampInsideMap(caster.Map); + + GenDraw.DrawLineBetween(casterPos.ToVector3Shifted(), leftEnd.ToVector3Shifted(), SimpleColor.White); + GenDraw.DrawLineBetween(casterPos.ToVector3Shifted(), rightEnd.ToVector3Shifted(), SimpleColor.White); + } + + public override bool Valid(LocalTargetInfo target, bool throwMessages = false) + { + if (!base.Valid(target, throwMessages)) + return false; + + Pawn caster = parent.pawn; + if (caster == null || caster.Map == null) + return false; + + // 检查目标是否在范围内 + float distance = caster.Position.DistanceTo(target.Cell); + if (distance > Props.range) + { + if (throwMessages) + Messages.Message("AbilityTargetOutOfRange".Translate(), caster, MessageTypeDefOf.RejectInput); + return false; + } + + return true; + } + } +} diff --git a/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompProperties_AbilityFanShapedStunKnockback.cs b/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompProperties_AbilityFanShapedStunKnockback.cs new file mode 100644 index 0000000..a22d18d --- /dev/null +++ b/Source/ArachnaeSwarm/Abilities/ARA_FanShapedStunKnockback/CompProperties_AbilityFanShapedStunKnockback.cs @@ -0,0 +1,58 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + public class CompProperties_AbilityFanShapedStunKnockback : CompProperties_AbilityEffect + { + // 扇形参数 + public float range = 5f; // 扇形半径 + public float coneSizeDegrees = 45f; // 扇形角度(总角度) + public float lineWidthEnd = 3f; // 扇形末端宽度 + + // 伤害参数 + public DamageDef damageDef = DamageDefOf.Blunt; + public float damageAmount = 15f; + public float armorPenetration = 0f; + + // 眩晕参数 + public int stunTicks = 180; // 3秒眩晕 (60 ticks = 1秒) + + // 击退参数 + public int maxKnockbackDistance = 3; // 最大击退距离 + public bool canKnockbackIntoWalls = false; // 是否可以击退到墙上 + public bool requireLineOfSight = true; // 击退路径是否需要视线 + + // 新增:对非Pawn物体的处理参数 + public bool affectNonPawnThings = true; // 是否影响非Pawn物体 + public bool canDamageNonPawnThings = true; // 是否可以对非Pawn物体造成伤害 + public float nonPawnDamageMultiplier = 1.0f; // 非Pawn物体的伤害倍率 + public bool applySpecialEffectsToNonPawn = false; // 是否对非Pawn物体应用特殊效果 + + // 视觉和音效效果 + public EffecterDef impactEffecter; // 命中效果 + public SoundDef impactSound; // 命中音效 + + // 飞行效果设置 + public ThingDef knockbackFlyerDef; // 击退飞行器定义 + public EffecterDef flightEffecterDef; // 飞行效果 + public SoundDef landingSound; // 落地音效 + + // 过滤设置 + public bool affectCaster = false; // 是否影响施法者 + public bool canHitFilledCells = true; // 是否可以击中已填充的单元格 + public bool onlyAffectEnemies = true; // 只影响敌人 + public bool requireLineOfSightToTarget = true; // 是否需要视线到目标 + + // 近战伤害系数加成 + public bool multiplyDamageByMeleeFactor = false; // 伤害是否乘以近战伤害系数 + public bool multiplyStunTimeByMeleeFactor = false; // 眩晕时间是否乘以近战伤害系数 + public StatDef damageMultiplierStat = null; // 自定义伤害系数Stat(如果为空则使用MeleeDamageFactor) + public StatDef stunMultiplierStat = null; // 自定义眩晕时间系数Stat(如果为空则使用MeleeDamageFactor) + + public CompProperties_AbilityFanShapedStunKnockback() + { + compClass = typeof(CompAbilityEffect_FanShapedStunKnockback); + } + } +} diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index a525b19..7137873 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -34,6 +34,8 @@ 4 + + diff --git a/Source/ArachnaeSwarm/Buildings/Building_ResearchBlueprintReader/ResearchBlueprintReaderManager.cs b/Source/ArachnaeSwarm/Buildings/Building_ResearchBlueprintReader/ResearchBlueprintReaderManager.cs index 977e4c9..a00140a 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_ResearchBlueprintReader/ResearchBlueprintReaderManager.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_ResearchBlueprintReader/ResearchBlueprintReaderManager.cs @@ -1,4 +1,3 @@ -// File: Managers/ResearchBlueprintReaderManager.cs using RimWorld; using System; using System.Collections.Generic; @@ -23,9 +22,18 @@ namespace ArachnaeSwarm private int cleanupTimer; private const int CleanupInterval = 2500; + // === 新增:反向校验相关字段 === + private int reverseCheckTimer; + private const int ReverseCheckInterval = 10000; // 每10秒检查一次(6000ticks = 10秒) + + // === 新增:建筑健康状态记录 === + private Dictionary buildingHealthRecords; + // === 新增:用于序列化的临时字段 === private List serializedProjects; private List> serializedBuildings; + private List serializedHealthRecordsKeys; + private List serializedHealthRecordsValues; // === 新增:科技丢失检查计时器 === private int lostResearchCheckTimer; @@ -36,7 +44,9 @@ namespace ArachnaeSwarm instance = this; allReaders = new List(); researchBuildings = new Dictionary>(); + buildingHealthRecords = new Dictionary(); lostResearchCheckTimer = 0; + reverseCheckTimer = 0; } public static ResearchBlueprintReaderManager Instance => instance; @@ -69,19 +79,41 @@ namespace ArachnaeSwarm Scribe_Collections.Look(ref serializedProjects, "serializedProjects", LookMode.Def); Scribe_Collections.Look(ref serializedBuildings, "serializedBuildings", LookMode.Reference); + + // === 新增:序列化建筑健康记录 === + serializedHealthRecordsKeys = new List(); + serializedHealthRecordsValues = new List(); + + foreach (var kvp in buildingHealthRecords) + { + if (kvp.Key != null && !kvp.Key.Destroyed) + { + serializedHealthRecordsKeys.Add(kvp.Key); + serializedHealthRecordsValues.Add(kvp.Value); + } + } + + Scribe_Collections.Look(ref serializedHealthRecordsKeys, "healthRecordsKeys", LookMode.Reference); + Scribe_Collections.Look(ref serializedHealthRecordsValues, "healthRecordsValues", LookMode.Deep); } else if (Scribe.mode == LoadSaveMode.LoadingVars) { // 加载时:清空现有数据 allReaders = new List(); researchBuildings = new Dictionary>(); + buildingHealthRecords = new Dictionary(); serializedProjects = null; serializedBuildings = null; + serializedHealthRecordsKeys = null; + serializedHealthRecordsValues = null; Scribe_Collections.Look(ref serializedProjects, "serializedProjects", LookMode.Def); Scribe_Collections.Look(ref serializedBuildings, "serializedBuildings", LookMode.Reference); + Scribe_Collections.Look(ref serializedHealthRecordsKeys, "healthRecordsKeys", LookMode.Reference); + Scribe_Collections.Look(ref serializedHealthRecordsValues, "healthRecordsValues", LookMode.Deep); + // 重建 researchBuildings 字典 if (serializedProjects != null && serializedBuildings != null && serializedProjects.Count == serializedBuildings.Count) { @@ -102,7 +134,7 @@ namespace ArachnaeSwarm // 添加到所有建筑列表 foreach (var building in buildings) { - if (!allReaders.Contains(building)) + if (building != null && !allReaders.Contains(building)) { allReaders.Add(building); } @@ -112,22 +144,41 @@ namespace ArachnaeSwarm } } - ArachnaeLog.Debug($"[ResearchManager] Loaded {allReaders.Count} buildings, {researchBuildings.Count} research projects"); + // 重建 buildingHealthRecords 字典 + if (serializedHealthRecordsKeys != null && serializedHealthRecordsValues != null && + serializedHealthRecordsKeys.Count == serializedHealthRecordsValues.Count) + { + for (int i = 0; i < serializedHealthRecordsKeys.Count; i++) + { + var building = serializedHealthRecordsKeys[i]; + var record = serializedHealthRecordsValues[i]; + + if (building != null && record != null) + { + buildingHealthRecords[building] = record; + } + } + } + + ArachnaeLog.Debug($"[ResearchManager] Loaded {allReaders.Count} buildings, {researchBuildings.Count} research projects, {buildingHealthRecords.Count} health records"); } else if (Scribe.mode == LoadSaveMode.PostLoadInit) { // 后加载初始化:清理所有无效数据 CleanupInvalidData(); + ValidateAllBuildings(); } // 保存和加载计时器 Scribe_Values.Look(ref lostResearchCheckTimer, "lostResearchCheckTimer", 0); + Scribe_Values.Look(ref reverseCheckTimer, "reverseCheckTimer", 0); } public override void GameComponentTick() { base.GameComponentTick(); + // 正向清理计时器 cleanupTimer++; if (cleanupTimer >= CleanupInterval) { @@ -135,7 +186,15 @@ namespace ArachnaeSwarm cleanupTimer = 0; } - // === 新增:定期检查是否有科技因建筑损失而丢失 === + // 反向校验计时器 + reverseCheckTimer++; + if (reverseCheckTimer >= ReverseCheckInterval) + { + PerformReverseValidation(); + reverseCheckTimer = 0; + } + + // 科技丢失检查计时器 lostResearchCheckTimer++; if (lostResearchCheckTimer >= LostResearchCheckInterval) { @@ -145,50 +204,238 @@ namespace ArachnaeSwarm } /// - /// === 新增:检查因建筑损失而丢失的科技 === + /// === 新增:反向校验 - 管理器主动检查建筑状态 === /// - private void CheckForLostResearch() + private void PerformReverseValidation() { - if (researchBuildings == null || researchBuildings.Count == 0) + if (allReaders == null || allReaders.Count == 0) return; - ArachnaeLog.Debug($"[ResearchManager] Checking for lost research projects..."); + ArachnaeLog.Debug($"[ResearchManager] Performing reverse validation on {allReaders.Count} buildings"); - // 获取需要检查的项目列表(复制以避免修改时遍历) - var projectsToCheck = new List(researchBuildings.Keys); + int invalidCount = 0; + int missingCount = 0; - foreach (var project in projectsToCheck) + // 检查所有已注册的建筑 + foreach (var building in allReaders.ToList()) // 使用副本避免修改时错误 { - if (project == null) - continue; - - if (!researchBuildings.ContainsKey(project)) - continue; - - var buildings = researchBuildings[project]; - if (buildings == null) + if (building == null) { - researchBuildings.Remove(project); + invalidCount++; + RemoveDeadBuilding(building); continue; } - // 清理无效建筑 - buildings.RemoveAll(b => - b == null || b.Destroyed || !b.Spawned || b.Map == null); + // 检查建筑是否仍然存在 + bool isValid = ValidateBuildingExistence(building); - // 如果已经没有建筑了 - if (buildings.Count == 0) + if (!isValid) + { + missingCount++; + + // 更新建筑健康记录 + UpdateBuildingHealthRecord(building, false); + + // 如果建筑连续多次检查失败,则移除 + if (ShouldRemoveBuilding(building)) + { + RemoveDeadBuilding(building); + } + } + else + { + // 建筑有效,更新健康记录 + UpdateBuildingHealthRecord(building, true); + } + } + + if (invalidCount > 0 || missingCount > 0) + { + ArachnaeLog.Debug($"[ResearchManager] Reverse validation found {invalidCount} invalid and {missingCount} missing buildings"); + } + } + + /// + /// === 新增:验证建筑是否存在 === + /// + private bool ValidateBuildingExistence(Building_ResearchBlueprintReader building) + { + if (building == null) + return false; + + try + { + // 方法1:检查建筑是否被标记为已摧毁 + if (building.Destroyed) + { + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} is marked as destroyed"); + return false; + } + + // 方法2:检查建筑是否在地图上 + if (!building.Spawned) + { + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} is not spawned"); + return false; + } + + // 方法3:检查建筑是否有效 + if (building.Map == null) + { + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} has no map reference"); + return false; + } + + // 方法4:检查建筑是否在地图建筑列表中 + if (building.Map != null && !building.Map.listerBuildings.allBuildingsColonist.Contains(building)) + { + // 建筑可能被取消、拆除或移动 + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} not found in map building list"); + return false; + } + + return true; + } + catch (Exception ex) + { + ArachnaeLog.Debug($"[ResearchManager] Error validating building {building.ThingID}: {ex.Message}"); + return false; + } + } + + /// + /// === 新增:更新建筑健康记录 === + /// + private void UpdateBuildingHealthRecord(Building_ResearchBlueprintReader building, bool isHealthy) + { + if (building == null) + return; + + if (!buildingHealthRecords.ContainsKey(building)) + { + buildingHealthRecords[building] = new BuildingHealthRecord(); + } + + var record = buildingHealthRecords[building]; + record.LastCheckTick = Find.TickManager.TicksGame; + record.IsHealthy = isHealthy; + + if (isHealthy) + { + record.ConsecutiveFailures = 0; + record.LastHealthyTick = Find.TickManager.TicksGame; + } + else + { + record.ConsecutiveFailures++; + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} health check failed {record.ConsecutiveFailures} times"); + } + } + + /// + /// === 新增:判断是否应该移除建筑 === + /// + private bool ShouldRemoveBuilding(Building_ResearchBlueprintReader building) + { + if (building == null || !buildingHealthRecords.ContainsKey(building)) + return true; + + var record = buildingHealthRecords[building]; + + // 如果连续失败超过3次,或者超过30秒没有健康状态 + if (record.ConsecutiveFailures >= 3) + { + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} has {record.ConsecutiveFailures} consecutive failures, removing"); + return true; + } + + if (record.LastHealthyTick > 0 && + Find.TickManager.TicksGame - record.LastHealthyTick > 1800) // 30秒 + { + ArachnaeLog.Debug($"[ResearchManager] Building {building.ThingID} has been unhealthy for 30+ seconds, removing"); + return true; + } + + return false; + } + + /// + /// === 新增:移除死亡建筑 === + /// + private void RemoveDeadBuilding(Building_ResearchBlueprintReader building) + { + if (building == null) + return; + + ArachnaeLog.Debug($"[ResearchManager] Removing dead building: {building.ThingID}"); + + // 从所有列表中移除 + allReaders.Remove(building); + buildingHealthRecords.Remove(building); + + // 从研究项目中移除 + var project = building.StoredResearch; + if (project != null && researchBuildings.ContainsKey(project)) + { + researchBuildings[project].Remove(building); + + // 如果项目没有建筑了,检查是否丢失 + if (researchBuildings[project].Count == 0) { researchBuildings.Remove(project); - - // 调用Patch创建的移除方法来丢失科技 OnResearchLostDueToBuildingLoss(project); } } } /// - /// === 新增:科技因建筑损失而丢失的处理 === + /// === 新增:加载后验证所有建筑 === + /// + private void ValidateAllBuildings() + { + ArachnaeLog.Debug("[ResearchManager] Validating all buildings after load"); + + // 验证所有已注册的建筑 + foreach (var building in allReaders.ToList()) + { + if (!ValidateBuildingExistence(building)) + { + ArachnaeLog.Debug($"[ResearchManager] Invalid building detected after load: {building.ThingID}"); + RemoveDeadBuilding(building); + } + else + { + UpdateBuildingHealthRecord(building, true); + } + } + + // 清理无效的建筑健康记录 + var invalidRecords = buildingHealthRecords.Keys.Where(b => b == null || b.Destroyed).ToList(); + foreach (var building in invalidRecords) + { + buildingHealthRecords.Remove(building); + } + + ArachnaeLog.Debug($"[ResearchManager] Validation complete: {allReaders.Count} valid buildings, {buildingHealthRecords.Count} health records"); + } + + /// + /// 检查因建筑损失而丢失的科技 + /// + private void CheckForLostResearch() + { + if (researchBuildings == null || researchBuildings.Count == 0) + return; + + // 只记录调试信息,不重复执行 + if (Find.TickManager.TicksGame % 60000 == 0) // 每分钟记录一次 + { + ArachnaeLog.Debug($"[ResearchManager] Research status: {researchBuildings.Count} active projects"); + } + } + + /// + /// 科技因建筑损失而丢失的处理 /// private void OnResearchLostDueToBuildingLoss(ResearchProjectDef project) { @@ -233,7 +480,7 @@ namespace ArachnaeSwarm if (allReaders != null) { removedCount += allReaders.RemoveAll(b => - b == null || b.Destroyed || !b.Spawned || b.Map == null); + b == null || b.Destroyed); if (removedCount > 0) { @@ -260,7 +507,7 @@ namespace ArachnaeSwarm // 清理无效建筑 kvp.Value.RemoveAll(b => - b == null || b.Destroyed || !b.Spawned || b.Map == null); + b == null || b.Destroyed); if (kvp.Value.Count == 0) { @@ -289,6 +536,20 @@ namespace ArachnaeSwarm { researchBuildings = new Dictionary>(); } + + // 清理建筑健康记录 + if (buildingHealthRecords != null) + { + var keysToRemove = buildingHealthRecords.Keys.Where(k => k == null || k.Destroyed).ToList(); + foreach (var key in keysToRemove) + { + buildingHealthRecords.Remove(key); + } + } + else + { + buildingHealthRecords = new Dictionary(); + } } /// @@ -296,7 +557,7 @@ namespace ArachnaeSwarm /// public void RegisterReader(Building_ResearchBlueprintReader reader) { - if (reader == null || reader.Destroyed || !reader.Spawned) + if (reader == null || reader.Destroyed) return; // 防止重复注册 @@ -308,6 +569,19 @@ namespace ArachnaeSwarm if (!allReaders.Contains(reader)) { allReaders.Add(reader); + + // 初始化健康记录 + if (!buildingHealthRecords.ContainsKey(reader)) + { + buildingHealthRecords[reader] = new BuildingHealthRecord + { + LastHealthyTick = Find.TickManager.TicksGame, + LastCheckTick = Find.TickManager.TicksGame, + IsHealthy = true, + ConsecutiveFailures = 0 + }; + } + ArachnaeLog.Debug($"[ResearchManager] Registered reader: {reader.ThingID} at position {reader.Position}"); } } @@ -370,10 +644,34 @@ namespace ArachnaeSwarm { allReaders.Remove(building); } + + // 从健康记录中移除 + if (buildingHealthRecords != null) + { + buildingHealthRecords.Remove(building); + } } /// - /// === 新增:获取指定科技的建筑数量 === + /// === 新增:建筑健康心跳 - 建筑主动报告 === + /// + public void ReportBuildingHealth(Building_ResearchBlueprintReader building) + { + if (building == null) + return; + + if (buildingHealthRecords.ContainsKey(building)) + { + var record = buildingHealthRecords[building]; + record.LastHealthyTick = Find.TickManager.TicksGame; + record.LastCheckTick = Find.TickManager.TicksGame; + record.IsHealthy = true; + record.ConsecutiveFailures = 0; + } + } + + /// + /// 获取指定科技的建筑数量 /// public int GetBuildingCountForResearch(ResearchProjectDef project) { @@ -384,37 +682,12 @@ namespace ArachnaeSwarm } /// - /// === 新增:手动触发科技丢失检查(用于调试) === + /// === 新增:手动触发反向校验(用于调试) === /// - public void DebugTriggerLostResearchCheck() + public void DebugTriggerReverseValidation() { - ArachnaeLog.Debug("[ResearchManager] Manual trigger of lost research check"); - CheckForLostResearch(); - } - - /// - /// === 新增:强制移除某个科技(用于调试) === - /// - public void DebugForceRemoveResearch(ResearchProjectDef project) - { - if (project == null) - return; - - ArachnaeLog.Debug($"[ResearchManager] Debug force remove research: {project.defName}"); - - // 从字典中移除 - if (researchBuildings.ContainsKey(project)) - { - researchBuildings.Remove(project); - } - - // 使用ResearchRemover移除科技 - ResearchRemover.RemoveResearchProject(project, removeDependencies: false); - - Messages.Message( - $"Debug: Research '{project.LabelCap}' has been forcibly removed.", - MessageTypeDefOf.NeutralEvent - ); + ArachnaeLog.Debug("[ResearchManager] Manual trigger of reverse validation"); + PerformReverseValidation(); } /// @@ -431,6 +704,7 @@ namespace ArachnaeSwarm ArachnaeLog.Debug("=== Research Manager Status ==="); ArachnaeLog.Debug($"Total buildings: {Instance.allReaders?.Count ?? 0}"); ArachnaeLog.Debug($"Active research projects: {Instance.researchBuildings?.Count ?? 0}"); + ArachnaeLog.Debug($"Building health records: {Instance.buildingHealthRecords?.Count ?? 0}"); if (Instance.researchBuildings != null) { @@ -442,6 +716,39 @@ namespace ArachnaeSwarm ArachnaeLog.Debug($" - {kvp.Key.defName}: {activeBuildings} active buildings"); } } + + // 显示建筑健康状态 + if (Instance.buildingHealthRecords != null && Instance.buildingHealthRecords.Count > 0) + { + ArachnaeLog.Debug("=== Building Health Status ==="); + foreach (var kvp in Instance.buildingHealthRecords) + { + if (kvp.Key == null) continue; + + string status = kvp.Value.IsHealthy ? "Healthy" : "Unhealthy"; + string timeSinceHealthy = (Find.TickManager.TicksGame - kvp.Value.LastHealthyTick).ToStringTicksToPeriod(); + ArachnaeLog.Debug($" - {kvp.Key.ThingID}: {status}, Failures: {kvp.Value.ConsecutiveFailures}, Last healthy: {timeSinceHealthy} ago"); + } + } + } + } + + /// + /// === 新增:建筑健康记录类 === + /// + public class BuildingHealthRecord : IExposable + { + public int LastCheckTick = 0; + public int LastHealthyTick = 0; + public bool IsHealthy = false; + public int ConsecutiveFailures = 0; + + public void ExposeData() + { + Scribe_Values.Look(ref LastCheckTick, "LastCheckTick", 0); + Scribe_Values.Look(ref LastHealthyTick, "LastHealthyTick", 0); + Scribe_Values.Look(ref IsHealthy, "IsHealthy", false); + Scribe_Values.Look(ref ConsecutiveFailures, "ConsecutiveFailures", 0); } } } diff --git a/非公开资源/Content/Textures/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Skyraider_Tail_south.sai2 b/非公开资源/Content/Textures/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Skyraider_Tail_south.sai2 index 988100c..04a5cd7 100644 Binary files a/非公开资源/Content/Textures/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Skyraider_Tail_south.sai2 and b/非公开资源/Content/Textures/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Skyraider_Tail_south.sai2 differ diff --git a/非公开资源/Content/Textures/UI/Abilities/ARA_Praetorian_TailSweep.sai2 b/非公开资源/Content/Textures/UI/Abilities/ARA_Praetorian_TailSweep.sai2 new file mode 100644 index 0000000..cda7e90 Binary files /dev/null and b/非公开资源/Content/Textures/UI/Abilities/ARA_Praetorian_TailSweep.sai2 differ diff --git a/非公开资源/Content/Textures/UI/Abilities/ARA_Queen_Upgrade_1_Stage.sai2 b/非公开资源/Content/Textures/UI/Abilities/ARA_Queen_Upgrade_1_Stage.sai2 index 5f2e3e0..6c179c3 100644 Binary files a/非公开资源/Content/Textures/UI/Abilities/ARA_Queen_Upgrade_1_Stage.sai2 and b/非公开资源/Content/Textures/UI/Abilities/ARA_Queen_Upgrade_1_Stage.sai2 differ