This commit is contained in:
Tourswen
2025-10-19 22:29:15 +08:00
parent 8ce28545e8
commit 85b99c4079
18 changed files with 525 additions and 460 deletions

View File

@@ -3,44 +3,8 @@
"WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\powerarmor\\ara_powerarmor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\ara_powerarmor.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\\powerarmor\\jobdriver_enterpowerarmor.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\jobdriver_enterpowerarmor.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\\powerarmor\\comppowerarmorstation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\comppowerarmorstation.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\\powerarmor\\gizmo_structurepanel.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\gizmo_structurepanel.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\\abilities\\ara_huggingface\\compabilityeffect_possess.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\ara_huggingface\\compabilityeffect_possess.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\\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\\building_comps\\ara_building_refuelingvat\\building_refuelingvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_building_refuelingvat\\building_refuelingvat.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\\building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.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\\building_comps\\compnutritiontofuelconverter.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\compnutritiontofuelconverter.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\\building_comps\\ara_compinteractiveproducer\\compresearchproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compresearchproducer.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\\verbs\\verb_shootarc.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:verbs\\verb_shootarc.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -50,134 +14,24 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 4,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "CompPowerArmorStation.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\CompPowerArmorStation.cs",
"RelativeDocumentMoniker": "PowerArmor\\CompPowerArmorStation.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\CompPowerArmorStation.cs",
"RelativeToolTip": "PowerArmor\\CompPowerArmorStation.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAAmAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T16:40:43.953Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "JobDriver_EnterPowerArmor.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\JobDriver_EnterPowerArmor.cs",
"RelativeDocumentMoniker": "PowerArmor\\JobDriver_EnterPowerArmor.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\JobDriver_EnterPowerArmor.cs",
"RelativeToolTip": "PowerArmor\\JobDriver_EnterPowerArmor.cs",
"ViewState": "AgIAAGUAAAAAAAAAAAAAAIwAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T16:33:18.657Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "Gizmo_StructurePanel.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\Gizmo_StructurePanel.cs",
"RelativeDocumentMoniker": "PowerArmor\\Gizmo_StructurePanel.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\Gizmo_StructurePanel.cs",
"RelativeToolTip": "PowerArmor\\Gizmo_StructurePanel.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T16:32:53.277Z"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "ARA_PowerArmor.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\ARA_PowerArmor.cs",
"RelativeDocumentMoniker": "PowerArmor\\ARA_PowerArmor.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\ARA_PowerArmor.cs",
"RelativeToolTip": "PowerArmor\\ARA_PowerArmor.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAA==",
"Title": "Verb_ShootArc.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verbs\\Verb_ShootArc.cs",
"RelativeDocumentMoniker": "Verbs\\Verb_ShootArc.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verbs\\Verb_ShootArc.cs",
"RelativeToolTip": "Verbs\\Verb_ShootArc.cs",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T16:30:55.497Z",
"WhenOpened": "2025-10-19T13:10:19.942Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "CompAbilityEffect_Possess.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs",
"RelativeToolTip": "Abilities\\ARA_HuggingFace\\CompAbilityEffect_Possess.cs",
"ViewState": "AgIAACwAAAAAAAAAAAAkwDsAAAAtAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T13:31:46.288Z"
},
{
"$type": "Document",
"DocumentIndex": 5,
"Title": "Building_NutrientVat.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
"RelativeToolTip": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
"ViewState": "AgIAAAgAAAAAAAAAAAAQwBAAAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T11:56:31.022Z"
},
{
"$type": "Document",
"DocumentIndex": 6,
"Title": "Building_RefuelingVat.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
"RelativeToolTip": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
"ViewState": "AgIAAGcAAAAAAAAAAAAAAI4AAAAgAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-18T11:56:12.333Z"
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "CompNutritionToFuelConverter.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompNutritionToFuelConverter.cs",
"RelativeDocumentMoniker": "Building_Comps\\CompNutritionToFuelConverter.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\CompNutritionToFuelConverter.cs",
"RelativeToolTip": "Building_Comps\\CompNutritionToFuelConverter.cs",
"ViewState": "AgIAAGEAAAAAAAAAAAAQwHYAAAAJAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-17T11:32:00.484Z"
},
{
"$type": "Document",
"DocumentIndex": 9,
"Title": "CompResearchProducer.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"ViewState": "AgIAADoBAAAAAAAAAAAkwFIBAAAmAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-17T09:09:18.518Z"
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "CompInteractiveProducer.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"ViewState": "AgIAAAwAAAAAAAAAAAAgwGYAAABWAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-17T09:00:51.526Z"
}
]
}

View File

@@ -5,197 +5,181 @@ using Verse;
namespace ArachnaeSwarm
{
// Final, robust extension class for configuring path-based penetration.
public class Wula_PathPierce_Extension : DefModExtension
// 在 Wula_PathPierce_Extension 类中添加粒子特效相关的属性
public class Wula_PathPierce_Extension : DefModExtension
{
// 原有的穿透属性
public int maxHits = 3;
public float damageFalloff = 0.25f;
public bool preventFriendlyFire = false;
public FleckDef tailFleckDef;
public int fleckDelayTicks = 10;
// 新增的击中特效属性(来自 Projectile_BulletWithEffect_Extension
public EffecterDef impactEffecter; // 击中时的特效
}
public class Projectile_WulaLineAttack : Bullet
{
private int hitCounter = 0;
private List<Thing> alreadyDamaged = new List<Thing>();
private Vector3 lastTickPosition;
private int Fleck_MakeFleckTick;
public int Fleck_MakeFleckTickMax = 1;
public IntRange Fleck_MakeFleckNum = new IntRange(1, 1);
public FloatRange Fleck_Angle = new FloatRange(-180f, 180f);
public FloatRange Fleck_Scale = new FloatRange(1f, 1f);
public FloatRange Fleck_Speed = new FloatRange(0f, 0f);
public FloatRange Fleck_Rotation = new FloatRange(-180f, 180f);
private Wula_PathPierce_Extension Props => def.GetModExtension<Wula_PathPierce_Extension>();
public override void ExposeData()
{
// Set to a positive number for limited hits, or -1 for infinite penetration.
public int maxHits = 3;
// The percentage of damage lost per hit. 0.25 means 25% damage loss per hit.
public float damageFalloff = 0.25f;
// If true, this projectile will never cause friendly fire, regardless of game settings.
public bool preventFriendlyFire = false;
public FleckDef tailFleckDef; // 用于配置拖尾特效的 FleckDef
public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间tick
base.ExposeData();
Scribe_Values.Look(ref hitCounter, "hitCounter", 0);
Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference);
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition");
if (alreadyDamaged == null)
{
alreadyDamaged = new List<Thing>();
}
}
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
{
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
this.lastTickPosition = origin;
this.alreadyDamaged.Clear();
this.hitCounter = 0;
this.preventFriendlyFire = preventFriendlyFire || (Props?.preventFriendlyFire ?? false);
}
protected override void Tick()
{
Vector3 startPos = this.lastTickPosition;
base.Tick();
if (this.Destroyed) return;
this.Fleck_MakeFleckTick++;
if (this.Fleck_MakeFleckTick >= Props.fleckDelayTicks)
{
if (this.Fleck_MakeFleckTick >= (Props.fleckDelayTicks + this.Fleck_MakeFleckTickMax))
{
this.Fleck_MakeFleckTick = Props.fleckDelayTicks;
}
Map map = base.Map;
int randomInRange = this.Fleck_MakeFleckNum.RandomInRange;
Vector3 currentPosition = this.ExactPosition;
for (int i = 0; i < randomInRange; i++)
{
float currentBulletAngle = ExactRotation.eulerAngles.y;
float fleckRotationAngle = currentBulletAngle;
float velocityAngle = this.Fleck_Angle.RandomInRange + currentBulletAngle;
float randomInRange2 = this.Fleck_Scale.RandomInRange;
float randomInRange3 = this.Fleck_Speed.RandomInRange;
if (Props?.tailFleckDef != null)
{
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, Props.tailFleckDef, randomInRange2);
dataStatic.rotation = fleckRotationAngle;
dataStatic.rotationRate = this.Fleck_Rotation.RandomInRange;
dataStatic.velocityAngle = velocityAngle;
dataStatic.velocitySpeed = randomInRange3;
map.flecks.CreateFleck(dataStatic);
}
}
}
if (this.Destroyed) return;
Vector3 endPos = this.ExactPosition;
CheckPathForDamage(startPos, endPos);
this.lastTickPosition = endPos;
}
public class Projectile_WulaLineAttack : Bullet
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
private int hitCounter = 0;
private List<Thing> alreadyDamaged = new List<Thing>();
private Vector3 lastTickPosition;
private int Fleck_MakeFleckTick; // 拖尾特效的计时器
public int Fleck_MakeFleckTickMax = 1; // 拖尾特效的生成频率
public IntRange Fleck_MakeFleckNum = new IntRange(1, 1); // 每次生成的粒子数量
public FloatRange Fleck_Angle = new FloatRange(-180f, 180f); // 粒子角度
public FloatRange Fleck_Scale = new FloatRange(1f, 1f); // 粒子大小
public FloatRange Fleck_Speed = new FloatRange(0f, 0f); // 粒子速度
public FloatRange Fleck_Rotation = new FloatRange(-180f, 180f); // 粒子旋转
// 原有的穿透检测
CheckPathForDamage(lastTickPosition, this.ExactPosition);
private Wula_PathPierce_Extension Props => def.GetModExtension<Wula_PathPierce_Extension>();
if (hitThing != null && alreadyDamaged.Contains(hitThing))
{
base.Impact(null, blockedByShield);
}
else
{
base.Impact(hitThing, blockedByShield);
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref hitCounter, "hitCounter", 0);
Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference);
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition");
if (alreadyDamaged == null)
{
alreadyDamaged = new List<Thing>();
}
}
// 新增:触发击中特效(来自 Projectile_BulletWithEffect 的功能)
if (Props?.impactEffecter != null)
{
// 创建一个新的 Effecter 实例并触发
Effecter effecter = Props.impactEffecter.Spawn();
effecter.Trigger(new TargetInfo(this.ExactPosition.ToIntVec3(), this.launcher.Map, false), this.launcher);
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
{
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
this.lastTickPosition = origin;
this.alreadyDamaged.Clear();
this.hitCounter = 0;
// Friendly fire is prevented if EITHER the game setting is true OR the XML extension is true.
this.preventFriendlyFire = preventFriendlyFire || (Props?.preventFriendlyFire ?? false);
}
protected override void Tick()
{
Vector3 startPos = this.lastTickPosition;
base.Tick();
if (this.Destroyed) return;
this.Fleck_MakeFleckTick++;
// 只有当达到延迟时间后才开始生成Fleck
if (this.Fleck_MakeFleckTick >= Props.fleckDelayTicks)
{
if (this.Fleck_MakeFleckTick >= (Props.fleckDelayTicks + this.Fleck_MakeFleckTickMax))
{
this.Fleck_MakeFleckTick = Props.fleckDelayTicks; // 重置计时器,从延迟时间开始循环
}
Map map = base.Map;
int randomInRange = this.Fleck_MakeFleckNum.RandomInRange;
Vector3 currentPosition = this.ExactPosition; // Current position of the bullet
Vector3 previousPosition = this.lastTickPosition; // Previous position of the bullet
for (int i = 0; i < randomInRange; i++)
{
float currentBulletAngle = ExactRotation.eulerAngles.y; // 使用子弹当前的水平旋转角度
float fleckRotationAngle = currentBulletAngle; // Fleck 的旋转角度与子弹方向一致
float velocityAngle = this.Fleck_Angle.RandomInRange + currentBulletAngle; // Fleck 的速度角度基于子弹方向加上随机偏移
float randomInRange2 = this.Fleck_Scale.RandomInRange;
float randomInRange3 = this.Fleck_Speed.RandomInRange;
if (Props?.tailFleckDef != null)
{
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, Props.tailFleckDef, randomInRange2);
dataStatic.rotation = fleckRotationAngle;
dataStatic.rotationRate = this.Fleck_Rotation.RandomInRange;
dataStatic.velocityAngle = velocityAngle;
dataStatic.velocitySpeed = randomInRange3;
map.flecks.CreateFleck(dataStatic);
}
}
}
if (this.Destroyed) return;
Vector3 endPos = this.ExactPosition;
CheckPathForDamage(startPos, endPos);
this.lastTickPosition = endPos;
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
CheckPathForDamage(lastTickPosition, this.ExactPosition);
if (hitThing != null && alreadyDamaged.Contains(hitThing))
{
base.Impact(null, blockedByShield);
}
else
{
base.Impact(hitThing, blockedByShield);
}
}
private void CheckPathForDamage(Vector3 startPos, Vector3 endPos)
{
if (startPos == endPos) return;
int maxHits = Props?.maxHits ?? 1;
bool infinitePenetration = maxHits < 0;
if (!infinitePenetration && hitCounter >= maxHits) return;
Map map = this.Map;
float distance = Vector3.Distance(startPos, endPos);
Vector3 direction = (endPos - startPos).normalized;
for (float i = 0; i < distance; i += 0.8f)
{
if (!infinitePenetration && hitCounter >= maxHits) break;
Vector3 checkPos = startPos + direction * i;
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkPos.ToIntVec3()));
foreach (Thing thing in thingsInCell)
{
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn))
{
bool shouldDamage = false;
// Case 1: Always damage the intended target if it's a pawn. This allows hunting.
if (this.intendedTarget.Thing == pawn)
{
shouldDamage = true;
}
// Case 2: Always damage hostile pawns in the path.
else if (pawn.HostileTo(this.launcher))
{
shouldDamage = true;
}
// Case 3: Damage non-hostiles (friendlies, neutrals) if the shot itself isn't marked to prevent friendly fire.
else if (!this.preventFriendlyFire)
{
shouldDamage = true;
}
if (shouldDamage)
{
ApplyPathDamage(pawn);
if (!infinitePenetration && hitCounter >= maxHits) break;
}
}
}
}
}
private void ApplyPathDamage(Pawn pawn)
{
Wula_PathPierce_Extension props = Props;
float falloff = props?.damageFalloff ?? 0.25f;
// Damage falloff now applies universally, even for infinite penetration.
float damageMultiplier = Mathf.Pow(1f - falloff, hitCounter);
int damageAmount = (int)(this.DamageAmount * damageMultiplier);
if (damageAmount <= 0) return;
var dinfo = new DamageInfo(
this.def.projectile.damageDef,
damageAmount,
this.ArmorPenetration * damageMultiplier,
this.ExactRotation.eulerAngles.y,
this.launcher,
null,
this.equipmentDef,
DamageInfo.SourceCategory.ThingOrUnknown,
this.intendedTarget.Thing);
pawn.TakeDamage(dinfo);
alreadyDamaged.Add(pawn);
hitCounter++;
}
// 可选:在一段时间后清理 Effecter
// 这里我们使用一个临时的 Effecter所以不需要手动清理
}
}
private void CheckPathForDamage(Vector3 startPos, Vector3 endPos)
{
if (startPos == endPos) return;
int maxHits = Props?.maxHits ?? 1;
bool infinitePenetration = maxHits < 0;
if (!infinitePenetration && hitCounter >= maxHits) return;
Map map = this.Map;
float distance = Vector3.Distance(startPos, endPos);
Vector3 direction = (endPos - startPos).normalized;
for (float i = 0; i < distance; i += 0.8f)
{
if (!infinitePenetration && hitCounter >= maxHits) break;
Vector3 checkPos = startPos + direction * i;
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkPos.ToIntVec3()));
foreach (Thing thing in thingsInCell)
{
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn))
{
bool shouldDamage = false;
if (this.intendedTarget.Thing == pawn)
{
shouldDamage = true;
}
else if (pawn.HostileTo(this.launcher))
{
shouldDamage = true;
}
else if (!this.preventFriendlyFire)
{
shouldDamage = true;
}
if (shouldDamage)
{
ApplyPathDamage(pawn);
if (!infinitePenetration && hitCounter >= maxHits) break;
}
}
}
}
}
private void ApplyPathDamage(Pawn pawn)
{
Wula_PathPierce_Extension props = Props;
float falloff = props?.damageFalloff ?? 0.25f;
float damageMultiplier = Mathf.Pow(1f - falloff, hitCounter);
int damageAmount = (int)(this.DamageAmount * damageMultiplier);
if (damageAmount <= 0) return;
var dinfo = new DamageInfo(
this.def.projectile.damageDef,
damageAmount,
this.ArmorPenetration * damageMultiplier,
this.ExactRotation.eulerAngles.y,
this.launcher,
null,
this.equipmentDef,
DamageInfo.SourceCategory.ThingOrUnknown,
this.intendedTarget.Thing);
pawn.TakeDamage(dinfo);
alreadyDamaged.Add(pawn);
hitCounter++;
}
}
}