diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 64b692c..320b27c 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/BodyAndPartDefs/ARA_Bodyparts.xml b/1.6/1.6/Defs/BodyAndPartDefs/ARA_Bodyparts.xml index 88d0d7e..15daa1b 100644 --- a/1.6/1.6/Defs/BodyAndPartDefs/ARA_Bodyparts.xml +++ b/1.6/1.6/Defs/BodyAndPartDefs/ARA_Bodyparts.xml @@ -3815,4 +3815,386 @@ + + ArachnaePraetorian_Body + + + Torso + Middle + Outside + +
  • Torso
  • +
    + +
  • + Ribcage + 0.036 + Inside + +
  • Torso
  • + + +
  • + Sternum + 0.015 + Inside + +
  • Torso
  • + + +
  • + Pelvis + 0.025 + Bottom + Inside + +
  • Torso
  • + + +
  • + Spine + 0.025 + Inside + +
  • Torso
  • + + +
  • + Stomach + 0.025 + Inside + +
  • Torso
  • + + +
  • + Heart + 0.020 + Inside + +
  • Torso
  • + + +
  • + Lung + 左肺 + 0.025 + Inside + +
  • Torso
  • + + +
  • + Lung + 右肺 + 0.025 + Inside + +
  • Torso
  • + + +
  • + Kidney + 左肾 + 0.017 + Inside + +
  • Torso
  • + + +
  • + Kidney + 右肾 + 0.017 + Inside + +
  • Torso
  • + + +
  • + Liver + 0.025 + Inside + +
  • Torso
  • + + +
  • + Neck + 0.07 + Top + +
  • Neck
  • + + +
  • + Head + 0.80 + +
  • UpperHead
  • +
  • FullHead
  • +
  • HeadAttackTool
  • + + +
  • + Skull + 0.18 + Inside + +
  • UpperHead
  • +
  • Eyes
  • +
  • FullHead
  • + + +
  • + Brain + 0.8 + +
  • UpperHead
  • +
  • Eyes
  • +
  • FullHead
  • + + +
    + +
  • + Eye + left eye + 0.07 + +
  • FullHead
  • +
  • Eyes
  • + + LeftEye + true + +
  • South
  • +
  • West
  • +
    + +
  • + Eye + right eye + 0.07 + +
  • FullHead
  • +
  • Eyes
  • + + RightEye + +
  • South
  • +
  • East
  • +
    + +
  • + Ear + 左耳 + 0.07 + true + +
  • UpperHead
  • +
  • FullHead
  • + + +
  • + Ear + 右耳 + 0.07 + +
  • UpperHead
  • +
  • FullHead
  • + + +
  • + Nose + 0.10 + +
  • FullHead
  • + + +
  • + Jaw + 0.15 + +
  • Teeth
  • +
  • FullHead
  • +
  • Mouth
  • + + +
  • + Tongue + 0.001 + Inside + +
  • FullHead
  • + + +
    + +
    + +
    + +
  • + Shoulder + 左肩 + 0.12 + LeftShoulder + +
  • Shoulders
  • + + +
  • + Arm + 左辅肢 + 0.77 + +
  • Arms
  • + + +
  • + Hand + 左手 + 0.14 + Bottom + +
  • Hands
  • + + +
    + +
    + +
  • + Shoulder + 右肩 + 0.12 + RightShoulder + +
  • Shoulders
  • + + +
  • + Arm + 右辅肢 + 0.77 + +
  • Arms
  • + + +
  • + Hand + 右手 + 0.14 + Bottom + +
  • Hands
  • + + +
    + +
    + +
  • + Waist + 0 + Bottom + +
  • Waist
  • + + +
  • + Leg + 左腿 + 0.14 + Bottom + +
  • Legs
  • + + LeftLeg + true + +
  • + Foot + 左足 + 0.1 + true + +
  • Feet
  • + + +
    + +
  • + Leg + 右腿 + 0.14 + Bottom + +
  • Legs
  • + + RightLeg + +
  • + Foot + 右足 + 0.1 + +
  • Feet
  • + + +
    + + +
  • + ARA_Tail + Bottom + 0.05 + Outside + +
  • Torso
  • + + + +
  • + ARA_Chitin_Shell + 尾部护甲 + 0.01 + Outside + +
  • Torso
  • + + + +
  • + ARA_Pouch + 0.001 + Inside + +
  • ARA_Pouchs
  • + + +
    + +
  • + ARA_Chitin_Shell + 左胯护甲 + 0.05 + Outside + +
  • Torso
  • + + +
  • + ARA_Chitin_Shell + 右胯护甲 + 0.05 + Outside + +
  • Torso
  • + + +
    +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/EventDefs/ARA_Guide_Event.xml b/1.6/1.6/Defs/EventDefs/ARA_Guide_Event.xml new file mode 100644 index 0000000..4c5e20f --- /dev/null +++ b/1.6/1.6/Defs/EventDefs/ARA_Guide_Event.xml @@ -0,0 +1,196 @@ + + + + ARA_Guide_Pheromone + + 来源未知的信息素,使用后可以短暂链接蜂巢意志,获取运营虫巢的引导。 + ThingWithComps + Item + MapMeshOnly + Animal + false + 14 + true + Item + Never + true + Middle + +
  • Items
  • +
    + + 0 + 0.01 + 0 + 15000 + + + ArachnaeSwarm/Item/ARA_Guide_Pheromone + Graphic_Single + + + UnfinishedHealthItemProsthetic + false + GeneralLaborSpeed + Crafting + Smith + Recipe_Machining + 650 + + None + +
  • +
  • + UseNeurotrainer + 获取教程 + true +
  • +
  • + ARA_QuestEvent_1 +
  • +
    +
    + + ARA_QuestEvent_1 + + 信息素中包含了一些预先写下的信息。 + + +
  • + + +
  • questName->信息素残留(新手引导)
  • + + + +
  • + + +
  • questDescription->信息素中包含了一些预先写下的信息。
  • + + + +
  • + 信息素残留(新手引导) + 信息素残留(新手引导) + 信息素中包含了一些预先写下的信息。 + +
  • + + +
  • + +
  • + ARA_Event_Guide_1 +
  • + + + + +
  • + +
  • + + +
    +
    +
    + + ARA_Event_Guide_1 + + ArachnaeSwarm/Events/Portraits/ARA_Fighter_1 + 蜂巢意志 + +
  • 信息素的残留还很浓厚,你可以在有限的范围内问出自己的问题。]]>
  • +
    + +
  • +
  • +
    + +
  • + + +
  • + +
  • + ARA_Event_Guide_10 +
  • +
  • + +
  • + + +
  • + + +
  • + +
  • + ARA_Event_Guide_100 +
  • +
  • + +
  • + + +
  • + + +
  • + +
  • + +
  • + + +
    +
    + + ARA_Event_Guide_100 + + ArachnaeSwarm/Events/Portraits/ARA_Fighter_1 + 蜂巢意志 + +
  • 虚影正在等待你的进一步问题。。]]>
  • +
    + +
  • + + +
  • + +
  • + ARA_Event_Guide_10 +
  • +
  • + +
  • + + +
  • + + +
  • + +
  • + ARA_Event_Guide_100 +
  • +
  • + +
  • + + +
  • + + +
  • + +
  • + +
  • + + +
    +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml index 10a92ab..0d9c6c6 100644 --- a/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml +++ b/1.6/1.6/Defs/EvolutionDefs/ARA_Evolution.xml @@ -833,7 +833,7 @@ ARA_Smokepop_Production_Bacterium - 这只阿拉克涅迷雾种已经获得拔耀,前部甲壳脱落且无法再喷射信息素,转而换取了生产活化钜菌的能力。一只育菌种每天产出3份活化钜菌。 + 这只阿拉克涅迷雾种已经获得拔耀,前部甲壳脱落且无法再喷射信息素,转而换取了生产活化钜菌的能力。一只育菌种每天产出5份活化钜菌。 ARA_Activated_Bacterium diff --git a/1.6/1.6/Defs/MiscSettingDefs/EventUIConfig.xml b/1.6/1.6/Defs/MiscSettingDefs/EventUIConfig.xml new file mode 100644 index 0000000..b77c771 --- /dev/null +++ b/1.6/1.6/Defs/MiscSettingDefs/EventUIConfig.xml @@ -0,0 +1,37 @@ + + + + + ARA_EventUIConfig + + + Small + false + false + true + + + + (500, 800) + (650, 130) + (650, 350) + 750 + + + 0 + 0 + + (750, 600) + + + (200, 50) + (600, 200) + (600, 200) + 600 + 20 + 20 + 20 + + + + diff --git a/1.6/1.6/Defs/MiscSettingDefs/LetterDefs/EventLetter.xml b/1.6/1.6/Defs/MiscSettingDefs/LetterDefs/EventLetter.xml new file mode 100644 index 0000000..bc59ea7 --- /dev/null +++ b/1.6/1.6/Defs/MiscSettingDefs/LetterDefs/EventLetter.xml @@ -0,0 +1,9 @@ + + + + ARA_EventChoiceLetter + ArachnaeSwarm.Letter_EventChoice + LetterArrive_Good + (120, 150, 255) + + \ 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 18fde07..f11f836 100644 --- a/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml +++ b/1.6/1.6/Defs/ResearchProjectDefs/ARA_ResearchProjects.xml @@ -138,7 +138,7 @@ ARA_Technology_2KYC 允许女皇种孵化新的虫族——空天种,敏捷而致命的精锐虫族,拥有以飞行姿态穿梭于战场的能力。 - 1200 + 1600 5.50 4.30 ARA_ResearchBench @@ -173,7 +173,7 @@ ARA_Technology_6KYC - 允许女皇种孵化新的虫族——织域种,一种寿命长且拥有强大灵能的特殊,不仅能协助虫群的科研工作,也能承担战场指挥官的责任。 + 允许女皇种孵化新的虫族——织域种,一种寿命长且拥有强大灵能的特殊虫族,不仅能协助虫群的科研工作,也是一个强大的施法者。 800 7.50 2.70 @@ -182,6 +182,18 @@
  • ARA_Technology_1WMT
  • + + ARA_Technology_7KYC + + 允许女皇种孵化新的虫族——禁卫种,一种寿命较其他虫族更长的精锐虫族,拥有优秀的远程作战能力和社交能力,同时也可以作为指挥官指挥虫群。 + 2800 + 10.00 + 3.80 + ARA_ResearchBench + +
  • ARA_Technology_2WMT
  • +
    +
    ARA_Technology_4DIL @@ -393,8 +405,8 @@ ARA_Technology_6GUT - - 允许虫族建造一种利用活体钜菌和生物质产出阿拉克涅虫蜜的建筑。 + + 允许虫利用活体钜菌的催化能力,使虫群可以建造一系列产出建筑。 1000 6.50 0.90 @@ -567,7 +579,7 @@ ARA_Technology_6LOD 允许空天种进行定向进化,以牺牲高速和高空机动的能力换取向敌人投射大量天巢种的能力。 - 2500 + 3500 10.00 5.30 ARA_ResearchBench diff --git a/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml b/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml index ef8c3ff..3689271 100644 --- a/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml +++ b/1.6/1.6/Defs/Scenarios/ARA_Scenarios.xml @@ -64,6 +64,11 @@ ARA_InteractiveEggSac_Start 4 +
  • ScatterThingsNearPlayerStart diff --git a/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml b/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml index 58bf575..b08d1e2 100644 --- a/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml +++ b/1.6/1.6/Defs/StoryTellers/ARA_Storytellers.xml @@ -1,11 +1,11 @@  - ARA_Lyne - - 蛉是一只阿拉克涅织域种督虫,作为讲述者时行为和「经典」卡桑德拉相近,但是会加强大型袭击的难度。此外,选择蛉作为讲述者时,将会开启阿拉克涅虫巢的新手引导,以帮助新巢穴适应这个世界(引导暂未完成,敬请期待)。 - ArachnaeSwarm/Storyteller/ARA_Lyne - ArachnaeSwarm/Storyteller/ARA_Lyne_TINY + ARA_Chrony + + 克洛妮是一只阿拉克涅战士种督虫,虽然身材娇小但是侵略性极强,会在周期内发起更强大的大型袭击。此外,她会安排更少的访客和路过流浪者,并且不会在殖民地即将沦陷时派发黑衣人。 + ArachnaeSwarm/Storyteller/ARA_Chrony + ArachnaeSwarm/Storyteller/ARA_Chrony_TINY 20 @@ -86,7 +86,7 @@
  • VisitorGroup - 3 + 6 4 5 @@ -95,7 +95,7 @@
  • TravelerGroup - 1 + 3 6 1 @@ -170,8 +170,8 @@
  • World
  • - 15 - 15 + 10 + 10
  • @@ -180,11 +180,6 @@ 8 1
  • - -
  • - StrangerInBlackJoin - 180 -
  • \ No newline at end of file diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml index 6d2f017..1b33a21 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml @@ -625,6 +625,7 @@
  • ARA_Surgery_Install_Plasteel
  • +
  • ARA_Surgery_Install_Carapace_Shell
  • ARA_Surgery_Install_Huge_Stomach
  • ARA_Surgery_Install_Cycle_Suppression
  • ARA_Surgery_Install_Shell_Thorn
  • @@ -1173,7 +1174,7 @@ ArachnaeFighter_Body - 0.8 + 0.7 2 @@ -1885,7 +1886,7 @@ - ArachnaeFighter_Body + ArachnaePraetorian_Body 2 5 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 38b4083..e0a7410 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 @@ -19,6 +19,9 @@ CutoutComplex 1 + 0 + None + 1 Animal @@ -65,9 +68,6 @@ UnfinishedWeapon - -
  • RewardStandardQualitySuper
  • -
  • @@ -134,9 +134,9 @@ ARA_Technology_2MEL UnfinishedWeapon - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -212,9 +212,9 @@ ARA_Technology_3MEL UnfinishedWeapon - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -316,9 +316,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T1
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -437,9 +437,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -522,9 +522,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -621,9 +621,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -734,9 +734,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -834,9 +834,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T1
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -947,9 +947,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -1094,9 +1094,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T2
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -1210,9 +1210,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T3
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -1342,9 +1342,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T3
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -1476,9 +1476,9 @@
  • ARA_Armed_Organ_Ranged
  • ARA_Armed_Organ_T1
  • - -
  • RewardStandardQualitySuper
  • -
    + 0 + None +
  • @@ -1605,9 +1605,9 @@ UnfinishedWeapon - -
  • RewardStandardQualitySuper
  • -
    + 0 + None + diff --git a/1.6/1.6/Defs/Thing_building/ARA_Building.xml b/1.6/1.6/Defs/Thing_building/ARA_Building.xml index 19be91b..0a161d1 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_Building.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_Building.xml @@ -588,6 +588,9 @@ (0,0,-0.1) + +
  • ARA_Base_Technology
  • +
    0.8 false 0 @@ -630,6 +633,9 @@ (0,0,-0.1) + +
  • ARA_Base_Technology
  • +
    Building 100 @@ -700,6 +706,9 @@ 10 + +
  • ARA_Base_Technology
  • +
    0 true @@ -743,12 +752,18 @@ PassThroughOnly 100 false + +
  • ARA_Base_Technology
  • +
    -5 0 8 2500 + + 50 + (2,2)
  • ITab_Storage
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml index 070d693..eb1bd5b 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml @@ -219,7 +219,6 @@ (1.2,1.2) -
  • ARA_Technology_2KYC
  • ARA_Technology_4KYC
  • @@ -287,6 +286,7 @@
  • ARA_Technology_5KYC
  • ARA_Technology_6KYC
  • +
  • ARA_Technology_2KYC
  • @@ -307,7 +307,7 @@
  • ArachnaeNode_Race_NeuroSwarm - 880000 + 840000 ARA_Technology_6KYC
  • @@ -414,6 +414,9 @@ (0.9, 0.9 ,0.5) (1.4,1.4) + +
  • ARA_Technology_7KYC
  • +
  • @@ -433,7 +436,7 @@
  • ArachnaeNode_Race_NeuroSwarm - 1440000 + 840000 ARA_Technology_6KYC
  • @@ -444,7 +447,7 @@
  • ArachnaeNode_Race_Praetorian 520000 - ARA_Technology_2KYC + ARA_Technology_7KYC
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml index 8105e2e..17a1880 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml @@ -811,7 +811,8 @@
  • -
  • + +
  • Steels 钢铁 钢铁 @@ -821,46 +822,22 @@ 50 - 0 + 50 true true true
  • -
  • - 甲壳素 - 甲壳素 - - 100.0 - 0 - true - 0 - false - false - - - -
  • ARA_Carapace
  • - - - - false - true - true - -
  • - 200 - 1 - 2000 - 1 -
  • - - -
  • - - CompRefuelable - - 0.99 + +
  • + 60000~60000 + +
  • + ARA_Carapace + 25 +
  • + + true
  • diff --git a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml index 1e3f9b9..f1fb35d 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml @@ -359,7 +359,8 @@ -20 - 50 + 70 + 400
  • @@ -512,7 +513,8 @@ -20 - 50 + 100 + 300
  • @@ -602,7 +604,7 @@ ArachnaeSwarm/Building/ARA_CatastropheMissileSilo_Base Graphic_Single (5,5) - (0,0,0.5) + (0,0,0) (2, 2, 2.5) (0,0,-0.15) @@ -683,13 +685,14 @@ CatastropheMissile_Weapon 15.0 7.0 - (0,1.2) + (0,0)
  • Artillery
  • - 50 + 150 + 500 ARA_Buildings 12 diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_Carapace_Productor.png b/Content/Textures/ArachnaeSwarm/Building/ARA_Carapace_Productor.png index c25d8f6..23f8637 100644 Binary files a/Content/Textures/ArachnaeSwarm/Building/ARA_Carapace_Productor.png and b/Content/Textures/ArachnaeSwarm/Building/ARA_Carapace_Productor.png differ diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_Shelf.png b/Content/Textures/ArachnaeSwarm/Building/ARA_Shelf.png index c0c772f..304011a 100644 Binary files a/Content/Textures/ArachnaeSwarm/Building/ARA_Shelf.png and b/Content/Textures/ArachnaeSwarm/Building/ARA_Shelf.png differ diff --git a/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_1.png b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_1.png new file mode 100644 index 0000000..0454b8e Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_1.png differ diff --git a/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_2.png b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_2.png new file mode 100644 index 0000000..b91abc4 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_2.png differ diff --git a/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_3.png b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_3.png new file mode 100644 index 0000000..257ac7a Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Events/Portraits/ARA_Fighter_3.png differ diff --git a/Content/Textures/ArachnaeSwarm/Item/ARA_Guide_Pheromone.png b/Content/Textures/ArachnaeSwarm/Item/ARA_Guide_Pheromone.png new file mode 100644 index 0000000..e59506b Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Item/ARA_Guide_Pheromone.png differ diff --git a/Content/Textures/ArachnaeSwarm/Storyteller.rar b/Content/Textures/ArachnaeSwarm/Storyteller.rar deleted file mode 100644 index 79b4d6e..0000000 Binary files a/Content/Textures/ArachnaeSwarm/Storyteller.rar and /dev/null differ diff --git a/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony.png b/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony.png new file mode 100644 index 0000000..41d1f60 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony.png differ diff --git a/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony_TINY.png b/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony_TINY.png new file mode 100644 index 0000000..62859e0 Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Storyteller/ARA_Chrony_TINY.png differ diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo index 0dc2f96..92e540d 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 c99f45a..8a5d5cf 100644 --- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json +++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json @@ -1,29 +1,30 @@ { "Version": 1, - "WorkspaceRootPath": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", + "WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\", "Documents": [ { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|c:\\steam\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\jobs\\jobdriver_supercarry\\floatmenuoptionprovider_supercarry.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:jobs\\jobdriver_supercarry\\floatmenuoptionprovider_supercarry.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\eventsystem\\questnode_root_eventletter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\questnode_root_eventletter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|c:\\steam\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_thingcomp_guardianpsyfield\\harmony_projectileinterceptor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_thingcomp_guardianpsyfield\\harmony_projectileinterceptor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\eventsystem\\dialog_newlayoutdisplay.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\dialog_newlayoutdisplay.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|c:\\steam\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_thingcomp_guardianpsyfield\\thingcomp_guardianpsyfield.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_thingcomp_guardianpsyfield\\thingcomp_guardianpsyfield.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\eventsystem\\dialog_customdisplay.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\dialog_customdisplay.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_nutrientvat\\building_nutrientvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_nutrientvat\\building_nutrientvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\eventsystem\\effect.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\effect.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_nutrientnetwork\\compnutrientprovider.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_nutrientnetwork\\compnutrientprovider.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\eventsystem\\compopencustomui.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\compopencustomui.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, { - "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientNetwork\\CompLineDrawer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\eventsystem\\eventuiconfigdef.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:eventsystem\\eventuiconfigdef.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" } ], "DocumentGroupContainers": [ @@ -42,75 +43,80 @@ { "$type": "Document", "DocumentIndex": 0, - "Title": "FloatMenuOptionProvider_SuperCarry.cs", - "DocumentMoniker": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SuperCarry\\FloatMenuOptionProvider_SuperCarry.cs", - "RelativeDocumentMoniker": "Jobs\\JobDriver_SuperCarry\\FloatMenuOptionProvider_SuperCarry.cs", - "ToolTip": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Jobs\\JobDriver_SuperCarry\\FloatMenuOptionProvider_SuperCarry.cs", - "RelativeToolTip": "Jobs\\JobDriver_SuperCarry\\FloatMenuOptionProvider_SuperCarry.cs", - "ViewState": "AQIAAAAAAAAAAAAAAADwvwAAAAAAAAAA", + "Title": "QuestNode_Root_EventLetter.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\QuestNode_Root_EventLetter.cs", + "RelativeDocumentMoniker": "EventSystem\\QuestNode_Root_EventLetter.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\QuestNode_Root_EventLetter.cs", + "RelativeToolTip": "EventSystem\\QuestNode_Root_EventLetter.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAABoAAAA1AAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-04T07:00:14.106Z", + "WhenOpened": "2025-10-07T09:56:01.195Z", "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 1, - "Title": "Harmony_ProjectileInterceptor.cs", - "DocumentMoniker": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_ThingComp_GuardianPsyField\\Harmony_ProjectileInterceptor.cs", - "RelativeDocumentMoniker": "Thing_Comps\\ARA_ThingComp_GuardianPsyField\\Harmony_ProjectileInterceptor.cs", - "ToolTip": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_ThingComp_GuardianPsyField\\Harmony_ProjectileInterceptor.cs", - "RelativeToolTip": "Thing_Comps\\ARA_ThingComp_GuardianPsyField\\Harmony_ProjectileInterceptor.cs", - "ViewState": "AQIAAAAAAAAAAAAAAADwvwAAAAAAAAAA", + "Title": "Dialog_NewLayoutDisplay.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Dialog_NewLayoutDisplay.cs", + "RelativeDocumentMoniker": "EventSystem\\Dialog_NewLayoutDisplay.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Dialog_NewLayoutDisplay.cs", + "RelativeToolTip": "EventSystem\\Dialog_NewLayoutDisplay.cs", + "ViewState": "AgIAAAsAAAAAAAAAAAAuwDYAAAARAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-04T07:00:14.029Z", + "WhenOpened": "2025-10-07T09:39:47.15Z", "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 2, - "Title": "ThingComp_GuardianPsyField.cs", - "DocumentMoniker": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_ThingComp_GuardianPsyField\\ThingComp_GuardianPsyField.cs", - "RelativeDocumentMoniker": "Thing_Comps\\ARA_ThingComp_GuardianPsyField\\ThingComp_GuardianPsyField.cs", - "ToolTip": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_ThingComp_GuardianPsyField\\ThingComp_GuardianPsyField.cs", - "RelativeToolTip": "Thing_Comps\\ARA_ThingComp_GuardianPsyField\\ThingComp_GuardianPsyField.cs", - "ViewState": "AQIAAAAAAAAAAAAAAADwvwAAAAAAAAAA", + "Title": "Dialog_CustomDisplay.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Dialog_CustomDisplay.cs", + "RelativeDocumentMoniker": "EventSystem\\Dialog_CustomDisplay.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Dialog_CustomDisplay.cs", + "RelativeToolTip": "EventSystem\\Dialog_CustomDisplay.cs", + "ViewState": "AgIAAAsAAAAAAAAAAAAuwBcAAABWAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-02T16:24:06.176Z", + "WhenOpened": "2025-10-07T09:39:43.149Z", "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 3, - "Title": "Building_NutrientVat.cs", - "DocumentMoniker": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs", - "RelativeDocumentMoniker": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs", - "ToolTip": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs", - "RelativeToolTip": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs", - "ViewState": "AgIAAF8AAAAAAAAAAAAswJkBAAANAAAAAAAAAA==", + "Title": "Effect.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Effect.cs", + "RelativeDocumentMoniker": "EventSystem\\Effect.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\Effect.cs", + "RelativeToolTip": "EventSystem\\Effect.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwwAAAA7AAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-02T15:30:05.897Z" - }, - { - "$type": "Document", - "DocumentIndex": 4, - "Title": "CompNutrientProvider.cs", - "DocumentMoniker": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientNetwork\\CompNutrientProvider.cs", - "RelativeDocumentMoniker": "Building_Comps\\ARA_NutrientNetwork\\CompNutrientProvider.cs", - "ToolTip": "C:\\Steam\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientNetwork\\CompNutrientProvider.cs", - "RelativeToolTip": "Building_Comps\\ARA_NutrientNetwork\\CompNutrientProvider.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABkAAAAZAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-02T15:29:28.358Z" + "WhenOpened": "2025-10-07T09:35:57.661Z", + "EditorCaption": "" }, { "$type": "Document", "DocumentIndex": 5, - "Title": "CompLineDrawer.cs", - "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientNetwork\\CompLineDrawer.cs", - "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientNetwork\\CompLineDrawer.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABkAAAAAAAAAAAAAAA==", + "Title": "EventUIConfigDef.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\EventUIConfigDef.cs", + "RelativeDocumentMoniker": "EventSystem\\EventUIConfigDef.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\EventUIConfigDef.cs", + "RelativeToolTip": "EventSystem\\EventUIConfigDef.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAABYAAAAqAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2025-10-02T15:11:03.083Z" + "WhenOpened": "2025-10-07T08:37:06.577Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, + "Title": "CompOpenCustomUI.cs", + "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\CompOpenCustomUI.cs", + "RelativeDocumentMoniker": "EventSystem\\CompOpenCustomUI.cs", + "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\EventSystem\\CompOpenCustomUI.cs", + "RelativeToolTip": "EventSystem\\CompOpenCustomUI.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvxQAAAAtAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-10-07T08:36:51.993Z", + "EditorCaption": "" } ] } diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 87a3515..30f7c49 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -98,6 +98,19 @@ + + + + + + + + + + + + + diff --git a/Source/ArachnaeSwarm/EventSystem/CompOpenCustomUI.cs b/Source/ArachnaeSwarm/EventSystem/CompOpenCustomUI.cs new file mode 100644 index 0000000..e61cdc6 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/CompOpenCustomUI.cs @@ -0,0 +1,61 @@ +using System; // Required for Activator +using RimWorld; +using Verse; +using System.Collections.Generic; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class CompProperties_OpenCustomUI : CompProperties + { + public string uiDefName; + public string label; // The text to display in the float menu + public string failReason; // Optional: Custom text to show if the pawn can't reach the building + + public CompProperties_OpenCustomUI() + { + this.compClass = typeof(CompOpenCustomUI); + } + } + + public class CompOpenCustomUI : ThingComp + { + public CompProperties_OpenCustomUI Props => (CompProperties_OpenCustomUI)this.props; + + public override IEnumerable CompFloatMenuOptions(Pawn selPawn) + { + // Check if the pawn can interact with the building + if (!selPawn.CanReserveAndReach(this.parent, PathEndMode.InteractionCell, Danger.Deadly)) + { + string reason = Props.failReason ?? "CannotUseNoPath".Translate(); + yield return new FloatMenuOption(reason, null); + yield break; + } + + // Check for power if the building has a power component + CompPowerTrader powerComp = this.parent.GetComp(); + if (powerComp != null && !powerComp.PowerOn) + { + yield return new FloatMenuOption("CannotUseNoPower".Translate(), null); + yield break; + } + + string label = Props.label ?? "Open Custom UI"; // Use default label if not provided + + FloatMenuOption option = new FloatMenuOption(label, delegate() + { + EventDef uiDef = DefDatabase.GetNamed(Props.uiDefName, false); + if (uiDef != null) + { + Find.WindowStack.Add((Window)Activator.CreateInstance(uiDef.windowType, uiDef)); + } + else + { + Log.Error($"[CompOpenCustomUI] Could not find EventDef named '{Props.uiDefName}'."); + } + }); + + yield return option; + } + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/Condition.cs b/Source/ArachnaeSwarm/EventSystem/Condition.cs new file mode 100644 index 0000000..3f72bc0 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Condition.cs @@ -0,0 +1,240 @@ +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public abstract class Condition + { + public abstract bool IsMet(out string reason); + } + + public class Condition_VariableEquals : Condition + { + public string name; + public string value; + public string valueVariableName; + + public override bool IsMet(out string reason) + { + var eventVarManager = Find.World.GetComponent(); + if (!eventVarManager.HasVariable(name)) + { + reason = $"Variable '{name}' not found."; + return false; + } + + object variable = eventVarManager.GetVariable(name); + string compareValueStr = value; + + if (!string.IsNullOrEmpty(valueVariableName)) + { + compareValueStr = eventVarManager.GetVariable(valueVariableName)?.ToString(); + if (compareValueStr == null) + { + reason = $"Comparison variable '{valueVariableName}' not set."; + return false; + } + } + + bool met = false; + try + { + if (variable is int) + { + met = (int)variable == int.Parse(compareValueStr); + } + else if (variable is float) + { + met = (float)variable == float.Parse(compareValueStr); + } + else if (variable is bool) + { + met = (bool)variable == bool.Parse(compareValueStr); + } + else + { + met = variable?.ToString() == compareValueStr; + } + } + catch (System.Exception e) + { + Log.Warning($"[EventSystem] Condition_VariableEquals: Could not compare '{variable}' and '{compareValueStr}'. Error: {e.Message}"); + reason = "Type mismatch or parsing error during comparison."; + return false; + } + + if (!met) + { + reason = $"Requires {name} = {compareValueStr} (Current: {variable})"; + } + else + { + reason = ""; + } + return met; + } + } + + public abstract class Condition_CompareVariable : Condition + { + public string name; + public float value; + public string valueVariableName; + + protected abstract bool Compare(float var1, float var2); + protected abstract string GetOperatorString(); + + public override bool IsMet(out string reason) + { + var eventVarManager = Find.World.GetComponent(); + if (!eventVarManager.HasVariable(name)) + { + Log.Message($"[EventSystem] {GetType().Name}: Variable '{name}' not found, defaulting to 0f."); + eventVarManager.SetVariable(name, 0f); + } + + float variable = eventVarManager.GetVariable(name); + + float compareValue = value; + if (!string.IsNullOrEmpty(valueVariableName)) + { + compareValue = eventVarManager.GetVariable(valueVariableName, float.NaN); + if (float.IsNaN(compareValue)) + { + reason = $"Comparison variable '{valueVariableName}' not set or not a number."; + Log.Warning($"[EventSystem] {GetType().Name} check for '{name}' failed: {reason}"); + return false; + } + } + + bool met = Compare(variable, compareValue); + Log.Message($"[EventSystem] {GetType().Name} check: Name='{name}', CurrentValue='{variable}', CompareValue='{compareValue}', Met={met}"); + if (!met) + { + reason = $"Requires {name} {GetOperatorString()} {compareValue} (Current: {variable})"; + } + else + { + reason = ""; + } + return met; + } + } + + public class Condition_VariableGreaterThan : Condition_CompareVariable + { + protected override bool Compare(float var1, float var2) => var1 > var2; + protected override string GetOperatorString() => ">"; + } + + public class Condition_VariableLessThan : Condition_CompareVariable + { + protected override bool Compare(float var1, float var2) => var1 < var2; + protected override string GetOperatorString() => "<"; + } + + public class Condition_VariableGreaterThanOrEqual : Condition_CompareVariable + { + protected override bool Compare(float var1, float var2) => var1 >= var2; + protected override string GetOperatorString() => ">="; + } + + public class Condition_VariableLessThanOrEqual : Condition_CompareVariable + { + protected override bool Compare(float var1, float var2) => var1 <= var2; + protected override string GetOperatorString() => "<="; + } + + public class Condition_VariableNotEqual : Condition + { + public string name; + public string value; + public string valueVariableName; + + public override bool IsMet(out string reason) + { + var eventVarManager = Find.World.GetComponent(); + if (!eventVarManager.HasVariable(name)) + { + reason = $"Variable '{name}' not found."; + return false; + } + + object variable = eventVarManager.GetVariable(name); + string compareValueStr = value; + + if (!string.IsNullOrEmpty(valueVariableName)) + { + compareValueStr = eventVarManager.GetVariable(valueVariableName)?.ToString(); + if (compareValueStr == null) + { + reason = $"Comparison variable '{valueVariableName}' not set."; + return false; + } + } + + bool met = false; + try + { + if (variable is int) + { + met = (int)variable != int.Parse(compareValueStr); + } + else if (variable is float) + { + met = (float)variable != float.Parse(compareValueStr); + } + else if (variable is bool) + { + met = (bool)variable != bool.Parse(compareValueStr); + } + else + { + met = variable?.ToString() != compareValueStr; + } + } + catch (System.Exception e) + { + Log.Warning($"[EventSystem] Condition_VariableNotEqual: Could not compare '{variable}' and '{compareValueStr}'. Error: {e.Message}"); + reason = "Type mismatch or parsing error during comparison."; + return false; + } + + Log.Message($"[EventSystem] Condition_VariableNotEqual check: Name='{name}', Type='{variable?.GetType().Name ?? "null"}', CurrentValue='{variable}', CompareValue='{compareValueStr}', Met={met}"); + if (!met) + { + reason = $"Requires {name} != {compareValueStr} (Current: {variable})"; + } + else + { + reason = ""; + } + return met; + } + } + + public class Condition_FactionExists : Condition + { + public FactionDef factionDef; + + public override bool IsMet(out string reason) + { + if (factionDef == null) + { + reason = "FactionDef not specified in Condition_FactionExists."; + return false; + } + + bool exists = Find.FactionManager.FirstFactionOfDef(factionDef) != null; + if (!exists) + { + reason = $"Faction '{factionDef.label}' does not exist in the world."; + } + else + { + reason = ""; + } + return exists; + } + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/DebugActions.cs b/Source/ArachnaeSwarm/EventSystem/DebugActions.cs new file mode 100644 index 0000000..f3d597b --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/DebugActions.cs @@ -0,0 +1,65 @@ +using System; // Required for Activator +using System.Collections.Generic; +using LudeonTK; +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public static class WulaDebugActions + { + [DebugAction("Wula Fallen Empire", "Open Custom UI...", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.Playing)] + private static void OpenCustomUI() + { + List list = new List(); + foreach (EventDef localDef in DefDatabase.AllDefs) + { + EventDef currentDef = localDef; + list.Add(new DebugMenuOption(currentDef.defName, DebugMenuOptionMode.Action, delegate + { + if (currentDef.hiddenWindow) + { + if (!currentDef.dismissEffects.NullOrEmpty()) + { + foreach (var conditionalEffect in currentDef.dismissEffects) + { + string reason; + bool conditionsMet = true; + if (!conditionalEffect.conditions.NullOrEmpty()) + { + foreach (var condition in conditionalEffect.conditions) + { + if (!condition.IsMet(out reason)) + { + conditionsMet = false; + break; + } + } + } + + if (conditionsMet) + { + conditionalEffect.Execute(null); + } + } + } + } + else + { + Find.WindowStack.Add((Window)Activator.CreateInstance(currentDef.windowType, currentDef)); + } + })); + } + Find.WindowStack.Add(new Dialog_DebugOptionListLister(list)); + } + } + + public static class WulaDebugActionsVariables + { + [DebugAction("Wula Fallen Empire", "Manage Event Variables", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.PlayingOnMap)] + private static void ManageEventVariables() + { + Find.WindowStack.Add(new Dialog_ManageEventVariables()); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/EventSystem/DelayedActionManager.cs b/Source/ArachnaeSwarm/EventSystem/DelayedActionManager.cs new file mode 100644 index 0000000..a07863d --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/DelayedActionManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace ArachnaeSwarm +{ + public class DelayedActionManager : WorldComponent + { + // Nested class must be public to be accessible for serialization + public class DelayedAction : IExposable + { + public int TicksRemaining; + public string eventDefName; + + // Parameterless constructor for Scribe + public DelayedAction() { } + + public DelayedAction(string eventDefName, int ticks) + { + this.eventDefName = eventDefName; + this.TicksRemaining = ticks; + } + + public void ExposeData() + { + Scribe_Values.Look(ref TicksRemaining, "ticksRemaining", 0); + Scribe_Values.Look(ref eventDefName, "eventDefName"); + } + } + + private List actions = new List(); + + public DelayedActionManager(World world) : base(world) + { + } + + public void AddAction(string eventDefName, int delayTicks) + { + if (string.IsNullOrEmpty(eventDefName) || delayTicks <= 0) + { + return; + } + actions.Add(new DelayedAction(eventDefName, delayTicks)); + } + + public override void WorldComponentTick() + { + base.WorldComponentTick(); + for (int i = actions.Count - 1; i >= 0; i--) + { + DelayedAction delayedAction = actions[i]; + delayedAction.TicksRemaining--; + if (delayedAction.TicksRemaining <= 0) + { + try + { + ExecuteAction(delayedAction.eventDefName); + } + catch (Exception ex) + { + Log.Error($"[WulaFallenEmpire] Error executing delayed action for event '{delayedAction.eventDefName}': {ex}"); + } + actions.RemoveAt(i); + } + } + } + + private void ExecuteAction(string defName) + { + EventDef nextDef = DefDatabase.GetNamed(defName, false); + if (nextDef != null) + { + // This logic is simplified from Effect_OpenCustomUI.OpenUI + // It assumes delayed actions always open a new dialog. + Find.WindowStack.Add((Window)Activator.CreateInstance(nextDef.windowType, nextDef)); + } + else + { + Log.Error($"[WulaFallenEmpire] DelayedActionManager could not find EventDef named '{defName}'"); + } + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Collections.Look(ref actions, "delayedActions", LookMode.Deep); + if (actions == null) + { + actions = new List(); + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/EventSystem/Dialog_CustomDisplay.cs b/Source/ArachnaeSwarm/EventSystem/Dialog_CustomDisplay.cs new file mode 100644 index 0000000..e9045d5 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Dialog_CustomDisplay.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class Dialog_CustomDisplay : Window + { + private EventDef def; + private Texture2D portrait; + private Texture2D background; + private string selectedDescription; + + private static EventUIConfigDef config; + public static EventUIConfigDef Config + { + get + { + if (config == null) + { + config = DefDatabase.GetNamed("ARA_EventUIConfig"); + } + return config; + } + } + + public override Vector2 InitialSize + { + get + { + if (def.windowSize != Vector2.zero) + { + return def.windowSize; + } + return Config.defaultWindowSize; + } + } + + public Dialog_CustomDisplay(EventDef def) + { + this.def = def; + this.forcePause = true; + this.absorbInputAroundWindow = true; + this.doCloseX = true; + + var eventVarManager = Find.World.GetComponent(); + if (!def.descriptions.NullOrEmpty()) + { + if (def.descriptionMode == DescriptionSelectionMode.Random) + { + selectedDescription = def.descriptions.RandomElement(); + } + else + { + string indexVarName = $"_seq_desc_index_{def.defName}"; + int currentIndex = eventVarManager.GetVariable(indexVarName, 0); + + selectedDescription = def.descriptions[currentIndex]; + + int nextIndex = (currentIndex + 1) % def.descriptions.Count; + eventVarManager.SetVariable(indexVarName, nextIndex); + } + } + else + { + selectedDescription = "Error: No descriptions found in def."; + } + } + + public override void PreOpen() + { + base.PreOpen(); + if (!def.portraitPath.NullOrEmpty()) + { + portrait = ContentFinder.Get(def.portraitPath); + } + + string bgPath = !def.backgroundImagePath.NullOrEmpty() ? def.backgroundImagePath : Config.defaultBackgroundImagePath; + if (!bgPath.NullOrEmpty()) + { + background = ContentFinder.Get(bgPath); + } + + HandleAction(def.immediateEffects); + + if (!def.conditionalDescriptions.NullOrEmpty()) + { + foreach (var condDesc in def.conditionalDescriptions) + { + string reason; + if (AreConditionsMet(condDesc.conditions, out reason)) + { + selectedDescription += "\n\n" + condDesc.text; + } + } + } + + selectedDescription = FormatDescription(selectedDescription); + } + + public override void DoWindowContents(Rect inRect) + { + if (background != null) + { + GUI.DrawTexture(inRect, background, ScaleMode.ScaleToFit); + } + + if (Config.showDefName) + { + Text.Font = GameFont.Tiny; + GUI.color = Color.gray; + Widgets.Label(new Rect(5, 5, inRect.width - 10, 20f), def.defName); + GUI.color = Color.white; + } + + if (Config.showLabel) + { + Text.Font = Config.labelFont; + Widgets.Label(new Rect(5, 20f, inRect.width - 10, 30f), def.label); + Text.Font = GameFont.Small; + } + + float virtualWidth = Config.lihuiSize.x + Config.textSize.x; + float virtualHeight = Config.lihuiSize.y; + + float scaleX = inRect.width / virtualWidth; + float scaleY = inRect.height / virtualHeight; + float scale = Mathf.Min(scaleX, scaleY) * 0.95f; + + float scaledLihuiWidth = Config.lihuiSize.x * scale; + float scaledLihuiHeight = Config.lihuiSize.y * scale; + float scaledNameWidth = Config.nameSize.x * scale; + float scaledNameHeight = Config.nameSize.y * scale; + float scaledTextWidth = Config.textSize.x * scale; + float scaledTextHeight = Config.textSize.y * scale; + float scaledOptionsWidth = Config.optionsWidth * scale; + + float totalContentWidth = scaledLihuiWidth + scaledTextWidth; + float totalContentHeight = scaledLihuiHeight; + float startX = (inRect.width - totalContentWidth) / 2; + float startY = (inRect.height - totalContentHeight) / 2; + + Rect lihuiRect = new Rect(startX, startY, scaledLihuiWidth, scaledLihuiHeight); + if (portrait != null) + { + GUI.DrawTexture(lihuiRect, portrait, ScaleMode.ScaleToFit); + } + if (Config.drawBorders) + { + Widgets.DrawBox(lihuiRect); + } + + Rect nameRect = new Rect(lihuiRect.xMax, lihuiRect.y, scaledNameWidth, scaledNameHeight); + if (Config.drawBorders) + { + Widgets.DrawBox(nameRect); + } + Text.Anchor = TextAnchor.MiddleCenter; + Text.Font = GameFont.Medium; + Widgets.Label(nameRect, def.characterName); + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.UpperLeft; + + Rect textRect = new Rect(nameRect.x, nameRect.yMax + Config.textNameOffset * scale, scaledTextWidth, scaledTextHeight); + if (Config.drawBorders) + { + Widgets.DrawBox(textRect); + } + Rect textInnerRect = textRect.ContractedBy(10f * scale); + Widgets.Label(textInnerRect, selectedDescription); + + Rect optionRect = new Rect(nameRect.x, textRect.yMax + Config.optionsTextOffset * scale, scaledOptionsWidth, lihuiRect.height - nameRect.height - textRect.height - (Config.textNameOffset + Config.optionsTextOffset) * scale); + + Listing_Standard listing = new Listing_Standard(); + listing.Begin(optionRect.ContractedBy(10f * scale)); + if (def.options != null) + { + foreach (var option in def.options) + { + string reason; + bool conditionsMet = AreConditionsMet(option.conditions, out reason); + + if (conditionsMet) + { + if (listing.ButtonText(option.label)) + { + HandleAction(option.optionEffects); + } + } + else + { + if (option.hideWhenDisabled) + { + continue; + } + Rect rect = listing.GetRect(30f); + Widgets.ButtonText(rect, option.label, false, true, false); + TooltipHandler.TipRegion(rect, GetDisabledReason(option, reason)); + } + } + } + listing.End(); + } + + private void HandleAction(List conditionalEffects) + { + if (conditionalEffects.NullOrEmpty()) + { + return; + } + + foreach (var ce in conditionalEffects) + { + if (AreConditionsMet(ce.conditions, out _)) + { + ce.Execute(this); + } + } + } + + private bool AreConditionsMet(List conditions, out string reason) + { + reason = ""; + if (conditions.NullOrEmpty()) + { + return true; + } + + foreach (var condition in conditions) + { + if (!condition.IsMet(out string singleReason)) + { + reason = singleReason; + return false; + } + } + return true; + } + + private string GetDisabledReason(EventOption option, string reason) + { + if (!option.disabledReason.NullOrEmpty()) + { + return option.disabledReason; + } + return reason; + } + + public override void PostClose() + { + base.PostClose(); + HandleAction(def.dismissEffects); + } + + private string FormatDescription(string description) + { + var eventVarManager = Find.World.GetComponent(); + // Use regex to find all placeholders like {variableName} + return Regex.Replace(description, @"\{(.+?)\}", match => + { + string varName = match.Groups[1].Value; + if (eventVarManager.HasVariable(varName)) + { + // Important: GetVariable to get any type + return eventVarManager.GetVariable(varName)?.ToString() ?? ""; + } + return match.Value; // Keep placeholder if variable not found + }); + } + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/Dialog_ManageEventVariables.cs b/Source/ArachnaeSwarm/EventSystem/Dialog_ManageEventVariables.cs new file mode 100644 index 0000000..3998c3d --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Dialog_ManageEventVariables.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class Dialog_ManageEventVariables : Window + { + private Vector2 scrollPosition; + private Dictionary editBuffers = new Dictionary(); + private EventVariableManager manager; + + public override Vector2 InitialSize => new Vector2(800f, 600f); + + public Dialog_ManageEventVariables() + { + forcePause = true; + doCloseX = true; + doCloseButton = true; + closeOnClickedOutside = true; + absorbInputAroundWindow = true; + manager = Find.World.GetComponent(); + RefreshBuffers(); + } + + private void RefreshBuffers() + { + editBuffers.Clear(); + foreach (var kvp in manager.GetAllVariables()) + { + editBuffers[kvp.Key] = kvp.Value?.ToString() ?? ""; + } + } + + public override void DoWindowContents(Rect inRect) + { + Listing_Standard listing = new Listing_Standard(); + listing.Begin(inRect); + + if (listing.ButtonText("Refresh")) + { + RefreshBuffers(); + } + if (listing.ButtonText("Clear All Variables")) + { + manager.ClearAll(); + RefreshBuffers(); + } + + listing.GapLine(); + + Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, manager.GetAllVariables().Count * 32f); + Widgets.BeginScrollView(listing.GetRect(inRect.height - 100f), ref scrollPosition, viewRect); + + Listing_Standard varListing = new Listing_Standard(); + varListing.Begin(viewRect); + + var allVars = manager.GetAllVariables().OrderBy(kvp => kvp.Key).ToList(); + + foreach (var kvp in allVars) + { + Rect rowRect = varListing.GetRect(30f); + string key = kvp.Key; + object value = kvp.Value; + string typeName = value?.GetType().Name ?? "null"; + + Widgets.Label(rowRect.LeftPart(0.4f).Rounded(), $"{key} ({typeName})"); + + string buffer = editBuffers[key]; + string newValue = Widgets.TextField(rowRect.RightPart(0.6f).LeftPart(0.8f).Rounded(), buffer); + editBuffers[key] = newValue; + + if (Widgets.ButtonText(rowRect.RightPart(0.1f).Rounded(), "Set")) + { + // Attempt to parse and set the variable + if (value is int) + { + if (int.TryParse(newValue, out int intVal)) manager.SetVariable(key, intVal); + } + else if (value is float) + { + if (float.TryParse(newValue, out float floatVal)) manager.SetVariable(key, floatVal); + } + else + { + manager.SetVariable(key, newValue); + } + } + } + + varListing.End(); + Widgets.EndScrollView(); + listing.End(); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/EventSystem/Dialog_NewLayoutDisplay.cs b/Source/ArachnaeSwarm/EventSystem/Dialog_NewLayoutDisplay.cs new file mode 100644 index 0000000..b0e9644 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Dialog_NewLayoutDisplay.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class Dialog_NewLayoutDisplay : Window + { + private EventDef def; + private Texture2D portrait; + private Texture2D background; + private string selectedDescription; + + private static EventUIConfigDef config; + public static EventUIConfigDef Config + { + get + { + if (config == null) + { + config = DefDatabase.GetNamed("ARA_EventUIConfig"); + } + return config; + } + } + + public override Vector2 InitialSize + { + get + { + if (def.windowSize != Vector2.zero) + { + return def.windowSize; + } + return Config.defaultWindowSize; + } + } + + public Dialog_NewLayoutDisplay(EventDef def) + { + this.def = def; + this.forcePause = true; + this.absorbInputAroundWindow = true; + this.doCloseX = true; + + var eventVarManager = Find.World.GetComponent(); + if (!def.descriptions.NullOrEmpty()) + { + if (def.descriptionMode == DescriptionSelectionMode.Random) + { + selectedDescription = def.descriptions.RandomElement(); + } + else + { + string indexVarName = $"_seq_desc_index_{def.defName}"; + int currentIndex = eventVarManager.GetVariable(indexVarName, 0); + + selectedDescription = def.descriptions[currentIndex]; + + int nextIndex = (currentIndex + 1) % def.descriptions.Count; + eventVarManager.SetVariable(indexVarName, nextIndex); + } + } + else + { + selectedDescription = "Error: No descriptions found in def."; + } + } + + public override void PreOpen() + { + base.PreOpen(); + if (!def.portraitPath.NullOrEmpty()) + { + portrait = ContentFinder.Get(def.portraitPath); + } + + string bgPath = !def.backgroundImagePath.NullOrEmpty() ? def.backgroundImagePath : Config.defaultBackgroundImagePath; + if (!bgPath.NullOrEmpty()) + { + background = ContentFinder.Get(bgPath); + } + + HandleAction(def.immediateEffects); + + if (!def.conditionalDescriptions.NullOrEmpty()) + { + foreach (var condDesc in def.conditionalDescriptions) + { + string reason; + if (AreConditionsMet(condDesc.conditions, out reason)) + { + selectedDescription += "\n\n" + condDesc.text; + } + } + } + + selectedDescription = FormatDescription(selectedDescription); + } + + public override void DoWindowContents(Rect inRect) + { + if (background != null) + { + GUI.DrawTexture(inRect, background, ScaleMode.ScaleToFit); + } + + if (Config.showDefName) + { + Text.Font = GameFont.Tiny; + GUI.color = Color.gray; + Widgets.Label(new Rect(5, 5, inRect.width - 10, 20f), def.defName); + GUI.color = Color.white; + } + + if (Config.showLabel) + { + Text.Font = Config.labelFont; + Widgets.Label(new Rect(5, 20f, inRect.width - 10, 30f), def.label); + Text.Font = GameFont.Small; + } + + // 假设一个统一的边距 + float padding = Config.newLayoutPadding; + + // 名称区域 + float nameHeight = Config.newLayoutNameSize.y; + float nameWidth = Config.newLayoutNameSize.x; + Rect nameRect = new Rect(inRect.x + (inRect.width - nameWidth) / 2f, inRect.y + padding, nameWidth, nameHeight); + if (Config.drawBorders) + { + Widgets.DrawBox(nameRect); + } + Text.Anchor = TextAnchor.MiddleCenter; + Text.Font = GameFont.Medium; + Widgets.Label(nameRect, def.characterName); + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.UpperLeft; + + // 立绘区域 + float lihuiWidth = Config.newLayoutLihuiSize.x; + float lihuiHeight = Config.newLayoutLihuiSize.y; + Rect lihuiRect = new Rect(inRect.x + (inRect.width - lihuiWidth) / 2f, nameRect.yMax + padding, lihuiWidth, lihuiHeight); + if (portrait != null) + { + GUI.DrawTexture(lihuiRect, portrait, ScaleMode.ScaleToFit); + } + if (Config.drawBorders) + { + Widgets.DrawBox(lihuiRect); + } + + // 选项区域 (预先计算高度) + float optionButtonHeight = 30f; // 每个按钮的高度 + float optionSpacing = 5f; // 按钮之间的间距 + float calculatedOptionHeight = 0f; + if (def.options != null && def.options.Any()) + { + calculatedOptionHeight = def.options.Count * optionButtonHeight + (def.options.Count - 1) * optionSpacing; + } + calculatedOptionHeight = Mathf.Max(calculatedOptionHeight, 100f); // 最小高度 + + float optionsWidth = Config.newLayoutOptionsWidth; + Rect optionRect = new Rect(inRect.x + (inRect.width - optionsWidth) / 2f, inRect.yMax - padding - calculatedOptionHeight, optionsWidth, calculatedOptionHeight); + + // 描述区域 + float textWidth = Config.newLayoutTextSize.x; + Rect textRect = new Rect(inRect.x + (inRect.width - textWidth) / 2f, lihuiRect.yMax + padding, textWidth, optionRect.y - (lihuiRect.yMax + padding) - padding); + if (Config.drawBorders) + { + Widgets.DrawBox(textRect); + } + Rect textInnerRect = textRect.ContractedBy(padding); + Widgets.Label(textInnerRect, selectedDescription); + + // 选项列表的绘制 + Listing_Standard listing = new Listing_Standard(); + listing.Begin(optionRect); // 使用完整的 optionRect + if (def.options != null) + { + foreach (var option in def.options) + { + string reason; + bool conditionsMet = AreConditionsMet(option.conditions, out reason); + + if (conditionsMet) + { + if (listing.ButtonText(option.label)) + { + HandleAction(option.optionEffects); + } + } + else + { + if (option.hideWhenDisabled) + { + continue; + } + Rect rect = listing.GetRect(30f); + Widgets.ButtonText(rect, option.label, false, true, false); + TooltipHandler.TipRegion(rect, GetDisabledReason(option, reason)); + } + } + } + listing.End(); + } + + private void HandleAction(List conditionalEffects) + { + if (conditionalEffects.NullOrEmpty()) + { + return; + } + + foreach (var ce in conditionalEffects) + { + if (AreConditionsMet(ce.conditions, out _)) + { + ce.Execute(this); + } + } + } + + private bool AreConditionsMet(List conditions, out string reason) + { + reason = ""; + if (conditions.NullOrEmpty()) + { + return true; + } + + foreach (var condition in conditions) + { + if (!condition.IsMet(out string singleReason)) + { + reason = singleReason; + return false; + } + } + return true; + } + + private string GetDisabledReason(EventOption option, string reason) + { + if (!option.disabledReason.NullOrEmpty()) + { + return option.disabledReason; + } + return reason; + } + + public override void PostClose() + { + base.PostClose(); + HandleAction(def.dismissEffects); + } + + private string FormatDescription(string description) + { + var eventVarManager = Find.World.GetComponent(); + // Use regex to find all placeholders like {variableName} + return Regex.Replace(description, @"\{(.+?)\}", match => + { + string varName = match.Groups[1].Value; + if (eventVarManager.HasVariable(varName)) + { + // Important: GetVariable to get any type + return eventVarManager.GetVariable(varName)?.ToString() ?? ""; + } + return match.Value; // Keep placeholder if variable not found + }); + } + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/Effect.cs b/Source/ArachnaeSwarm/EventSystem/Effect.cs new file mode 100644 index 0000000..6194a75 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Effect.cs @@ -0,0 +1,655 @@ +using System; // Required for Activator +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public abstract class Effect + { + public float weight = 1.0f; + public abstract void Execute(Window dialog = null); + } + + public class Effect_OpenCustomUI : Effect + { + public string defName; + public int delayTicks = 0; + + public override void Execute(Window dialog = null) + { + if (delayTicks > 0) + { + var actionManager = Find.World.GetComponent(); + if (actionManager != null) + { + actionManager.AddAction(defName, delayTicks); + } + else + { + Log.Error("[WulaFallenEmpire] DelayedActionManager not found. Cannot schedule delayed UI opening."); + } + } + else + { + OpenUI(); + } + } + + private void OpenUI() + { + EventDef nextDef = DefDatabase.GetNamed(defName); + if (nextDef != null) + { + if (nextDef.hiddenWindow) + { + if (!nextDef.dismissEffects.NullOrEmpty()) + { + foreach (var conditionalEffect in nextDef.dismissEffects) + { + string reason; + if (AreConditionsMet(conditionalEffect.conditions, out reason)) + { + conditionalEffect.Execute(null); + } + } + } + } + else + { + Find.WindowStack.Add((Window)Activator.CreateInstance(nextDef.windowType, nextDef)); + } + } + else + { + Log.Error($"[WulaFallenEmpire] Effect_OpenCustomUI could not find EventDef named '{defName}'"); + } + } + + private bool AreConditionsMet(List conditions, out string reason) + { + reason = ""; + if (conditions.NullOrEmpty()) + { + return true; + } + + foreach (var condition in conditions) + { + if (!condition.IsMet(out string singleReason)) + { + reason = singleReason; + return false; + } + } + return true; + } + } + + public class Effect_CloseDialog : Effect + { + public override void Execute(Window dialog = null) + { + dialog?.Close(); + } + } + + public class Effect_ShowMessage : Effect + { + public string message; + public MessageTypeDef messageTypeDef; + + public override void Execute(Window dialog = null) + { + if (messageTypeDef == null) + { + messageTypeDef = MessageTypeDefOf.PositiveEvent; + } + Messages.Message(message, messageTypeDef); + } + } + + public class Effect_FireIncident : Effect + { + public IncidentDef incident; + + public override void Execute(Window dialog = null) + { + if (incident == null) + { + Log.Error("[WulaFallenEmpire] Effect_FireIncident has a null incident Def."); + return; + } + + IncidentParms parms = new IncidentParms + { + target = Find.CurrentMap, + forced = true + }; + + if (!incident.Worker.TryExecute(parms)) + { + Log.Error($"[WulaFallenEmpire] Could not fire incident {incident.defName}"); + } + } + } + + public class Effect_ChangeFactionRelation : Effect + { + public FactionDef faction; + public int goodwillChange; + + public override void Execute(Window dialog = null) + { + if (faction == null) + { + Log.Error("[WulaFallenEmpire] Effect_ChangeFactionRelation has a null faction Def."); + return; + } + + Faction targetFaction = Find.FactionManager.FirstFactionOfDef(faction); + if (targetFaction == null) + { + Log.Warning($"[WulaFallenEmpire] Could not find an active faction for FactionDef '{faction.defName}'."); + return; + } + + Faction.OfPlayer.TryAffectGoodwillWith(targetFaction, goodwillChange, canSendMessage: true, canSendHostilityLetter: true, reason: null, lookTarget: null); + } + } + + public class Effect_SetVariable : Effect + { + public string name; + public string value; + public string type; // Int, Float, String, Bool + public bool forceSet = false; + + public override void Execute(Window dialog = null) + { + var eventVarManager = Find.World.GetComponent(); + if (!forceSet && eventVarManager.HasVariable(name)) + { + return; + } + + object realValue = value; + if (!string.IsNullOrEmpty(type)) + { + if (type.Equals("int", System.StringComparison.OrdinalIgnoreCase) && int.TryParse(value, out int intVal)) + { + realValue = intVal; + } + else if (type.Equals("float", System.StringComparison.OrdinalIgnoreCase) && float.TryParse(value, out float floatVal)) + { + realValue = floatVal; + } + else if (type.Equals("bool", System.StringComparison.OrdinalIgnoreCase) && bool.TryParse(value, out bool boolVal)) + { + realValue = boolVal; + } + } + eventVarManager.SetVariable(name, realValue); + } + } + + public class Effect_ChangeFactionRelation_FromVariable : Effect + { + public FactionDef faction; + public string goodwillVariableName; + + public override void Execute(Window dialog = null) + { + if (faction == null) + { + Log.Error("[WulaFallenEmpire] Effect_ChangeFactionRelation_FromVariable has a null faction Def."); + return; + } + + Faction targetFaction = Find.FactionManager.FirstFactionOfDef(faction); + if (targetFaction == null) + { + Log.Warning($"[WulaFallenEmpire] Could not find an active faction for FactionDef '{faction.defName}'."); + return; + } + + int goodwillChange = Find.World.GetComponent().GetVariable(goodwillVariableName); + Faction.OfPlayer.TryAffectGoodwillWith(targetFaction, goodwillChange, canSendMessage: true, canSendHostilityLetter: true, reason: null, lookTarget: null); + } + } + + public class Effect_SpawnPawnAndStore : Effect + { + public PawnKindDef kindDef; + public int count = 1; + public string storeAs; + + public override void Execute(Window dialog = null) + { + if (kindDef == null) + { + Log.Error("[WulaFallenEmpire] Effect_SpawnPawnAndStore has a null kindDef."); + return; + } + if (storeAs.NullOrEmpty()) + { + Log.Error("[WulaFallenEmpire] Effect_SpawnPawnAndStore needs a 'storeAs' variable name."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + List spawnedPawns = new List(); + for (int i = 0; i < count; i++) + { + Pawn newPawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer); + IntVec3 loc = CellFinder.RandomSpawnCellForPawnNear(Find.CurrentMap.mapPawns.FreeColonists.First().Position, Find.CurrentMap, 10); + GenSpawn.Spawn(newPawn, loc, Find.CurrentMap); + spawnedPawns.Add(newPawn); + } + + if (count == 1) + { + eventVarManager.SetVariable(storeAs, spawnedPawns.First()); + } + else + { + eventVarManager.SetVariable(storeAs, spawnedPawns); + } + } + } + + public class Effect_GiveThing : Effect + { + public ThingDef thingDef; + public int count = 1; + + public override void Execute(Window dialog = null) + { + if (thingDef == null) + { + Log.Error("[WulaFallenEmpire] Effect_GiveThing has a null thingDef."); + return; + } + + Map currentMap = Find.CurrentMap; + if (currentMap == null) + { + Log.Error("[WulaFallenEmpire] Effect_GiveThing cannot execute without a current map."); + return; + } + + Thing thing = ThingMaker.MakeThing(thingDef); + thing.stackCount = count; + + IntVec3 dropCenter = DropCellFinder.TradeDropSpot(currentMap); + DropPodUtility.DropThingsNear(dropCenter, currentMap, new List { thing }, 110, false, false, false, false); + + Messages.Message("LetterLabelCargoPodCrash".Translate(), new TargetInfo(dropCenter, currentMap), MessageTypeDefOf.PositiveEvent); + } + } + + public class Effect_SpawnPawn : Effect + { + public PawnKindDef kindDef; + public int count = 1; + public bool joinPlayerFaction = true; + public string letterLabel; + public string letterText; + public LetterDef letterDef; + + public override void Execute(Window dialog = null) + { + if (kindDef == null) + { + Log.Error("[WulaFallenEmpire] Effect_SpawnPawn has a null kindDef."); + return; + } + + Map map = Find.CurrentMap; + if (map == null) + { + Log.Error("[WulaFallenEmpire] Effect_SpawnPawn cannot execute without a current map."); + return; + } + + for (int i = 0; i < count; i++) + { + Faction faction = joinPlayerFaction ? Faction.OfPlayer : null; + PawnGenerationRequest request = new PawnGenerationRequest( + kindDef, faction, PawnGenerationContext.NonPlayer, -1, true, false, false, false, + true, 20f, false, true, false, true, true, false, false, false, false, 0f, 0f, null, 1f, + null, null, null, null, null, null, null, null, null, null, null, null, false + ); + Pawn pawn = PawnGenerator.GeneratePawn(request); + + if (!CellFinder.TryFindRandomEdgeCellWith((IntVec3 c) => map.reachability.CanReachColony(c) && !c.Fogged(map), map, CellFinder.EdgeRoadChance_Neutral, out IntVec3 cell)) + { + cell = DropCellFinder.RandomDropSpot(map); + } + + GenSpawn.Spawn(pawn, cell, map, WipeMode.Vanish); + + if (!string.IsNullOrEmpty(letterLabel) && !string.IsNullOrEmpty(letterText)) + { + TaggedString finalLabel = letterLabel.Formatted(pawn.Named("PAWN")).AdjustedFor(pawn); + TaggedString finalText = letterText.Formatted(pawn.Named("PAWN")).AdjustedFor(pawn); + PawnRelationUtility.TryAppendRelationsWithColonistsInfo(ref finalText, ref finalLabel, pawn); + Find.LetterStack.ReceiveLetter(finalLabel, finalText, letterDef ?? LetterDefOf.PositiveEvent, pawn); + } + } + } + } + + public enum VariableOperation + { + Add, + Subtract, + Multiply, + Divide + } + + public class Effect_ModifyVariable : Effect + { + public string name; + public string value; + public string valueVariableName; + public VariableOperation operation; + + public override void Execute(Window dialog = null) + { + if (string.IsNullOrEmpty(name)) + { + Log.Error("[WulaFallenEmpire] Effect_ModifyVariable has a null or empty name."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + + // Determine the value to modify by + string valueStr = value; + if (!string.IsNullOrEmpty(valueVariableName)) + { + valueStr = eventVarManager.GetVariable(valueVariableName)?.ToString(); + if (valueStr == null) + { + Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable: valueVariableName '{valueVariableName}' not found."); + return; + } + } + + // Get the target variable, or initialize it + object variable = eventVarManager.GetVariable(name); + if (variable == null) + { + Log.Message($"[EventSystem] Effect_ModifyVariable: Variable '{name}' not found, initializing to 0."); + variable = 0; + } + + object originalValue = variable; + object newValue = null; + + // Perform operation based on type + try + { + if (variable is int || (variable is float && !valueStr.Contains("."))) // Allow int ops + { + int currentVal = System.Convert.ToInt32(variable); + int modVal = int.Parse(valueStr); + newValue = (int)Modify((float)currentVal, (float)modVal, operation); + } + else // Default to float operation + { + float currentVal = System.Convert.ToSingle(variable); + float modVal = float.Parse(valueStr); + newValue = Modify(currentVal, modVal, operation); + } + + Log.Message($"[EventSystem] Modifying variable '{name}'. Operation: {operation}. Value: {valueStr}. From: {originalValue} To: {newValue}"); + eventVarManager.SetVariable(name, newValue); + } + catch (System.Exception e) + { + Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable: Could not parse or operate on value '{valueStr}' for variable '{name}'. Error: {e.Message}"); + } + } + + private float Modify(float current, float modifier, VariableOperation op) + { + switch (op) + { + case VariableOperation.Add: return current + modifier; + case VariableOperation.Subtract: return current - modifier; + case VariableOperation.Multiply: return current * modifier; + case VariableOperation.Divide: + if (modifier != 0) return current / modifier; + Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable tried to divide by zero."); + return current; + default: return current; + } + } + } + + public class Effect_ClearVariable : Effect + { + public string name; + + public override void Execute(Window dialog = null) + { + if (string.IsNullOrEmpty(name)) + { + Log.Error("[WulaFallenEmpire] Effect_ClearVariable has a null or empty name."); + return; + } + Find.World.GetComponent().ClearVariable(name); + } + } + + public class Effect_AddQuest : Effect + { + public QuestScriptDef quest; + + public override void Execute(Window dialog = null) + { + if (quest == null) + { + Log.Error("[WulaFallenEmpire] Effect_AddQuest has a null quest Def."); + return; + } + + Quest newQuest = Quest.MakeRaw(); + newQuest.root = quest; + newQuest.id = Find.UniqueIDsManager.GetNextQuestID(); + Find.QuestManager.Add(newQuest); + } + } + + public class Effect_FinishResearch : Effect + { + public ResearchProjectDef research; + + public override void Execute(Window dialog = null) + { + if (research == null) + { + Log.Error("[WulaFallenEmpire] Effect_FinishResearch has a null research Def."); + return; + } + + Find.ResearchManager.FinishProject(research); + } + } + public class Effect_TriggerRaid : Effect + { + public float points; + public FactionDef faction; + public RaidStrategyDef raidStrategy; + public PawnsArrivalModeDef raidArrivalMode; + public PawnGroupKindDef groupKind; + public List pawnGroupMakers; + public string letterLabel; + public string letterText; + + public override void Execute(Window dialog = null) + { + Map map = Find.CurrentMap; + if (map == null) + { + Log.Error("[WulaFallenEmpire] Effect_TriggerRaid cannot execute without a current map."); + return; + } + + Faction factionInst = Find.FactionManager.FirstFactionOfDef(this.faction); + if (factionInst == null) + { + Log.Error($"[WulaFallenEmpire] Effect_TriggerRaid could not find an active faction for FactionDef '{this.faction?.defName}'."); + return; + } + + IncidentParms parms = new IncidentParms + { + target = map, + points = this.points, + faction = factionInst, + raidStrategy = this.raidStrategy, + raidArrivalMode = this.raidArrivalMode, + pawnGroupMakerSeed = Rand.Int, + forced = true + }; + + if (!RCellFinder.TryFindRandomPawnEntryCell(out parms.spawnCenter, map, CellFinder.EdgeRoadChance_Hostile)) + { + Log.Error("[WulaFallenEmpire] Effect_TriggerRaid could not find a valid spawn center."); + return; + } + + PawnGroupMakerParms groupMakerParms = new PawnGroupMakerParms + { + groupKind = this.groupKind ?? PawnGroupKindDefOf.Combat, + tile = map.Tile, + points = this.points, + faction = factionInst, + raidStrategy = this.raidStrategy, + seed = parms.pawnGroupMakerSeed + }; + + List pawns; + if (!pawnGroupMakers.NullOrEmpty()) + { + var groupMaker = pawnGroupMakers.RandomElementByWeight(x => x.commonality); + pawns = groupMaker.GeneratePawns(groupMakerParms).ToList(); + } + else + { + pawns = PawnGroupMakerUtility.GeneratePawns(groupMakerParms).ToList(); + } + + if (pawns.Any()) + { + raidArrivalMode.Worker.Arrive(pawns, parms); + // Assign Lord and LordJob to make the pawns actually perform the raid. + raidStrategy.Worker.MakeLords(parms, pawns); + + if (!string.IsNullOrEmpty(letterLabel) && !string.IsNullOrEmpty(letterText)) + { + Find.LetterStack.ReceiveLetter(letterLabel, letterText, LetterDefOf.ThreatBig, pawns[0]); + } + } + } + } + + public class Effect_CheckFactionGoodwill : Effect + { + public FactionDef factionDef; + public string variableName; + + public override void Execute(Window dialog = null) + { + if (factionDef == null || string.IsNullOrEmpty(variableName)) + { + Log.Error("[WulaFallenEmpire] Effect_CheckFactionGoodwill is not configured correctly."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + Faction faction = Find.FactionManager.FirstFactionOfDef(factionDef); + + if (faction != null) + { + int goodwill = faction.GoodwillWith(Faction.OfPlayer); + Log.Message($"[EventSystem] Storing goodwill for faction '{faction.Name}' ({goodwill}) into variable '{variableName}'."); + eventVarManager.SetVariable(variableName, goodwill); + } + else + { + Log.Warning($"[EventSystem] Effect_CheckFactionGoodwill: Faction '{factionDef.defName}' not found. Storing 0 in variable '{variableName}'."); + eventVarManager.SetVariable(variableName, 0); + } + } + } + + public class Effect_StoreRealPlayTime : Effect + { + public string variableName; + + public override void Execute(Window dialog = null) + { + if (string.IsNullOrEmpty(variableName)) + { + Log.Error("[WulaFallenEmpire] Effect_StoreRealPlayTime is not configured correctly (missing variableName)."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + float realPlayTime = Find.GameInfo.RealPlayTimeInteracting; + Log.Message($"[EventSystem] Storing real play time ({realPlayTime}s) into variable '{variableName}'."); + eventVarManager.SetVariable(variableName, realPlayTime); + } + } + + public class Effect_StoreDaysPassed : Effect + { + public string variableName; + + public override void Execute(Window dialog = null) + { + if (string.IsNullOrEmpty(variableName)) + { + Log.Error("[WulaFallenEmpire] Effect_StoreDaysPassed is not configured correctly (missing variableName)."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + int daysPassed = GenDate.DaysPassed; + Log.Message($"[EventSystem] Storing days passed ({daysPassed}) into variable '{variableName}'."); + eventVarManager.SetVariable(variableName, daysPassed); + } + } + + public class Effect_StoreColonyWealth : Effect + { + public string variableName; + + public override void Execute(Window dialog = null) + { + if (string.IsNullOrEmpty(variableName)) + { + Log.Error("[WulaFallenEmpire] Effect_StoreColonyWealth is not configured correctly (missing variableName)."); + return; + } + + Map currentMap = Find.CurrentMap; + if (currentMap == null) + { + Log.Error("[WulaFallenEmpire] Effect_StoreColonyWealth cannot execute without a current map."); + return; + } + + var eventVarManager = Find.World.GetComponent(); + float wealth = currentMap.wealthWatcher.WealthTotal; + Log.Message($"[EventSystem] Storing colony wealth ({wealth}) into variable '{variableName}'."); + eventVarManager.SetVariable(variableName, wealth); + } + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/EventDef.cs b/Source/ArachnaeSwarm/EventSystem/EventDef.cs new file mode 100644 index 0000000..9adaac9 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/EventDef.cs @@ -0,0 +1,148 @@ +using System; // Add this line +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public enum DescriptionSelectionMode + { + Random, + Sequential + } + + public class EventDef : Def + { + public string portraitPath; + public string characterName; + + // New system: list of descriptions + public List descriptions; + public DescriptionSelectionMode descriptionMode = DescriptionSelectionMode.Random; + public bool hiddenWindow = false; + + // Backwards compatibility: old single description field + public new string description = null; + + public Vector2 windowSize = Vector2.zero; + + public Type windowType = typeof(Dialog_CustomDisplay); // 默认窗口类型 + public List options; + public string backgroundImagePath; + public List immediateEffects; + public List dismissEffects; + public List conditionalDescriptions; + + public override void PostLoad() + { + base.PostLoad(); +#pragma warning disable 0618 + // If the old description field is used, move its value to the new list for processing. + if (!description.NullOrEmpty()) + { + if (descriptions.NullOrEmpty()) + { + descriptions = new List(); + } + descriptions.Insert(0, description); + description = null; // Clear the old field to prevent confusion + } +#pragma warning restore 0618 + // If hiddenWindow is true, merge immediateEffects into dismissEffects at load time. + if (hiddenWindow && !immediateEffects.NullOrEmpty()) + { + if (dismissEffects.NullOrEmpty()) + { + dismissEffects = new List(); + } + dismissEffects.AddRange(immediateEffects); + immediateEffects = null; // Clear to prevent double execution + } + } + } + + public class EventOption + { + public string label; + public List optionEffects; + public List conditions; + public string disabledReason; + public bool hideWhenDisabled = false; + } + + public class LoopEffects + { + public int count = 1; + public string countVariableName; + public List effects; + } + + public class ConditionalEffects + { + public List conditions; + public List effects; + public List randomlistEffects; + public List loopEffects; + + public void Execute(Window dialog) + { + // Execute all standard effects + if (!effects.NullOrEmpty()) + { + foreach (var effect in effects) + { + effect.Execute(dialog); + } + } + + // Execute one random effect from the random list + if (!randomlistEffects.NullOrEmpty()) + { + float totalWeight = randomlistEffects.Sum(e => e.weight); + float randomPoint = Rand.Value * totalWeight; + + foreach (var effect in randomlistEffects) + { + if (randomPoint < effect.weight) + { + effect.Execute(dialog); + break; + } + randomPoint -= effect.weight; + } + } + + // Execute looped effects + if (!loopEffects.NullOrEmpty()) + { + var eventVarManager = Find.World.GetComponent(); + foreach (var loop in loopEffects) + { + int loopCount = loop.count; + if (!loop.countVariableName.NullOrEmpty() && eventVarManager.HasVariable(loop.countVariableName)) + { + loopCount = eventVarManager.GetVariable(loop.countVariableName); + } + + for (int i = 0; i < loopCount; i++) + { + if (!loop.effects.NullOrEmpty()) + { + foreach (var effect in loop.effects) + { + effect.Execute(dialog); + } + } + } + } + } + } + } + + public class ConditionalDescription + { + public List conditions; + public string text; + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/EventUIConfigDef.cs b/Source/ArachnaeSwarm/EventSystem/EventUIConfigDef.cs new file mode 100644 index 0000000..6c4dd91 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/EventUIConfigDef.cs @@ -0,0 +1,34 @@ +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class EventUIConfigDef : Def + { + // General Style + public GameFont labelFont = GameFont.Small; + public bool drawBorders = true; + public bool showDefName = true; + public bool showLabel = true; + public string defaultBackgroundImagePath; + public Vector2 defaultWindowSize = new Vector2(750f, 500f); + + // Virtual Layout Dimensions + public Vector2 lihuiSize = new Vector2(500f, 800f); + public Vector2 nameSize = new Vector2(260f, 130f); + public Vector2 textSize = new Vector2(650f, 500f); + public float optionsWidth = 610f; + + // Virtual Layout Offsets + public float textNameOffset = 20f; + public float optionsTextOffset = 20f; + // New Layout Dimensions + public Vector2 newLayoutNameSize = new Vector2(200f, 50f); + public Vector2 newLayoutLihuiSize = new Vector2(300f, 400f); + public Vector2 newLayoutTextSize = new Vector2(600f, 200f); + public float newLayoutOptionsWidth = 600f; + public float newLayoutPadding = 20f; + public float newLayoutTextNameOffset = 20f; + public float newLayoutOptionsTextOffset = 20f; + } +} diff --git a/Source/ArachnaeSwarm/EventSystem/EventVariableManager.cs b/Source/ArachnaeSwarm/EventSystem/EventVariableManager.cs new file mode 100644 index 0000000..1e8f705 --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/EventVariableManager.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using Verse; +using RimWorld; +using RimWorld.Planet; + +namespace ArachnaeSwarm +{ + public class EventVariableManager : WorldComponent + { + private Dictionary intVars = new Dictionary(); + private Dictionary floatVars = new Dictionary(); + private Dictionary stringVars = new Dictionary(); + private Dictionary pawnVars = new Dictionary(); + private Dictionary> pawnListVars = new Dictionary>(); + + // 用于Scribe的辅助列表 + private List pawnVarKeys; + private List pawnVarValues; + private List pawnListVarKeys; + private List> pawnListVarValues; + + // Required for WorldComponent + public EventVariableManager(World world) : base(world) + { + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Collections.Look(ref intVars, "intVars", LookMode.Value, LookMode.Value); + Scribe_Collections.Look(ref floatVars, "floatVars", LookMode.Value, LookMode.Value); + Scribe_Collections.Look(ref stringVars, "stringVars", LookMode.Value, LookMode.Value); + Scribe_Collections.Look(ref pawnVars, "pawnVars", LookMode.Value, LookMode.Reference, ref pawnVarKeys, ref pawnVarValues); + Scribe_Collections.Look(ref pawnListVars, "pawnListVars", LookMode.Value, LookMode.Reference, ref pawnListVarKeys, ref pawnListVarValues); + + // Ensure dictionaries are not null after loading + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + intVars ??= new Dictionary(); + floatVars ??= new Dictionary(); + stringVars ??= new Dictionary(); + pawnVars ??= new Dictionary(); + pawnListVars ??= new Dictionary>(); + } + } + + public void SetVariable(string name, object value) + { + if (string.IsNullOrEmpty(name)) return; + + // Log the variable change + Log.Message($"[EventSystem] Setting variable '{name}' to value '{value}' of type {value?.GetType().Name ?? "null"}."); + + // Clear any existing variable with the same name to prevent type confusion + ClearVariable(name); + + if (value is int intValue) + { + intVars[name] = intValue; + } + else if (value is float floatValue) + { + floatVars[name] = floatValue; + } + else if (value is string stringValue) + { + stringVars[name] = stringValue; + } + else if (value is Pawn pawnValue) + { + pawnVars[name] = pawnValue; + } + else if (value is List pawnListValue) + { + pawnListVars[name] = pawnListValue; + } + else if (value != null) + { + stringVars[name] = value.ToString(); + Log.Warning($"[WulaFallenEmpire] EventVariableManager: Variable '{name}' of type {value.GetType()} was converted to string for storage. This may lead to unexpected behavior."); + } + } + + public T GetVariable(string name, T defaultValue = default) + { + if (string.IsNullOrEmpty(name)) return defaultValue; + + object value = null; + if (pawnListVars.TryGetValue(name, out var pawnListVal)) + { + value = pawnListVal; + } + else if (pawnVars.TryGetValue(name, out var pawnVal)) + { + value = pawnVal; + } + else if (floatVars.TryGetValue(name, out var floatVal)) + { + value = floatVal; + } + else if (intVars.TryGetValue(name, out var intVal)) + { + value = intVal; + } + else if (stringVars.TryGetValue(name, out var stringVal)) + { + value = stringVal; + } + + if (value != null) + { + if (value is T typedValue) + { + return typedValue; + } + try + { + // Handle cases where T is object but the stored value is, e.g., an int + if (typeof(T) == typeof(object)) + { + return (T)value; + } + return (T)System.Convert.ChangeType(value, typeof(T)); + } + catch (System.Exception e) + { + Log.Warning($"[WulaFallenEmpire] EventVariableManager: Variable '{name}' of type {value.GetType()} could not be converted to {typeof(T)}. Error: {e.Message}"); + return defaultValue; + } + } + + return defaultValue; + } + + public bool HasVariable(string name) + { + return intVars.ContainsKey(name) || + floatVars.ContainsKey(name) || + stringVars.ContainsKey(name) || + pawnVars.ContainsKey(name) || + pawnListVars.ContainsKey(name); + } + + public void ClearVariable(string name) + { + if (HasVariable(name)) + { + Log.Message($"[EventSystem] Clearing variable '{name}'."); + } + intVars.Remove(name); + floatVars.Remove(name); + stringVars.Remove(name); + pawnVars.Remove(name); + pawnListVars.Remove(name); + } + + public void ClearAll() + { + intVars.Clear(); + floatVars.Clear(); + stringVars.Clear(); + pawnVars.Clear(); + pawnListVars.Clear(); + } + + public Dictionary GetAllVariables() + { + var allVars = new Dictionary(); + foreach (var kvp in intVars) allVars[kvp.Key] = kvp.Value; + foreach (var kvp in floatVars) allVars[kvp.Key] = kvp.Value; + foreach (var kvp in stringVars) allVars[kvp.Key] = kvp.Value; + foreach (var kvp in pawnVars) allVars[kvp.Key] = kvp.Value; + foreach (var kvp in pawnListVars) allVars[kvp.Key] = kvp.Value; + return allVars; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/EventSystem/Letter_EventChoice.cs b/Source/ArachnaeSwarm/EventSystem/Letter_EventChoice.cs new file mode 100644 index 0000000..cdc99ca --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/Letter_EventChoice.cs @@ -0,0 +1,94 @@ +using RimWorld; +using RimWorld.QuestGen; +using System; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + public class Letter_EventChoice : ChoiceLetter + { + // These fields are now inherited from the base Letter class + // public string letterLabel; + // public string letterTitle; + // public string letterText; + public List options; + public new Quest quest; + + public override IEnumerable Choices + { + get + { + if (options.NullOrEmpty()) + { + yield break; + } + + foreach (var optionDef in options) + { + var currentOption = optionDef; + Action choiceAction = delegate + { + if (!currentOption.optionEffects.NullOrEmpty()) + { + foreach (var conditionalEffect in currentOption.optionEffects) + { + string reason; + if (AreConditionsMet(conditionalEffect.conditions, out reason)) + { + conditionalEffect.Execute(null); + } + } + } + if (quest != null && !quest.hidden && !quest.Historical) + { + quest.End(QuestEndOutcome.Success); + } + Find.LetterStack.RemoveLetter(this); + }; + + var diaOption = new DiaOption(currentOption.label) + { + action = choiceAction, + resolveTree = true + }; + yield return diaOption; + } + } + } + + public override bool CanDismissWithRightClick => false; + + private bool AreConditionsMet(List conditions, out string reason) + { + reason = ""; + if (conditions.NullOrEmpty()) + { + return true; + } + + foreach (var condition in conditions) + { + if (!condition.IsMet(out string singleReason)) + { + reason = singleReason; + return false; + } + } + return true; + } + + public override void ExposeData() + { + base.ExposeData(); + // Scribe_Values.Look(ref letterLabel, "letterLabel"); // Now uses base.label + // Scribe_Values.Look(ref letterTitle, "letterTitle"); // Now uses base.title + // Scribe_Values.Look(ref letterText, "letterText"); // Now uses base.text + Scribe_Collections.Look(ref options, "options", LookMode.Deep); + if (Scribe.mode != LoadSaveMode.Saving || quest != null) + { + Scribe_References.Look(ref quest, "quest"); + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/EventSystem/QuestNode_Root_EventLetter.cs b/Source/ArachnaeSwarm/EventSystem/QuestNode_Root_EventLetter.cs new file mode 100644 index 0000000..31b318a --- /dev/null +++ b/Source/ArachnaeSwarm/EventSystem/QuestNode_Root_EventLetter.cs @@ -0,0 +1,49 @@ +using RimWorld; +using RimWorld.QuestGen; +using System; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + public class QuestNode_Root_EventLetter : QuestNode + { + // Fields to be set from the QuestScriptDef XML + public SlateRef letterLabel; + public SlateRef letterTitle; + public SlateRef letterText; + public List