This commit is contained in:
2025-08-17 16:28:01 +08:00
parent fab1e07bb3
commit 3f4d91fece
4 changed files with 160 additions and 52 deletions

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- Penetrating Rifle (No Explosion) -->
<ThingDef ParentName="BaseHumanMakeableGun">
<defName>WULA_RW_Penetrating_Rifle</defName>
<label>SLr-15 "长钉" (穿透型)</label>
<description>一把经过实验性改造的“蓝锥”步枪,能够发射一种特殊的钢针,利用过载的能量使其在击中第一个目标后仍能继续飞行,对路径上的多个敌人造成伤害。</description>
<techLevel>Spacer</techLevel>
<graphicData>
<texPath>Wula/Weapon/WULA_RW_Fractal_RF</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<soundInteract>Interact_Rifle</soundInteract>
<weaponClasses>
<li>LongShots</li>
<li>RangedHeavy</li>
</weaponClasses>
<recipeMaker>
<recipeUsers Inherit="False">
<li>WULA_Cube_Productor_BIO</li>
<li>WULA_Cube_Productor_Energy</li>
</recipeUsers>
<researchPrerequisite>WULA_Synth_Weapon_Technology</researchPrerequisite>
<unfinishedThingDef>UnfinishedWeapon</unfinishedThingDef>
</recipeMaker>
<statBases>
<WorkToMake>1300</WorkToMake>
<Mass>3.5</Mass>
<AccuracyTouch>0.3</AccuracyTouch>
<AccuracyShort>0.8</AccuracyShort>
<AccuracyMedium>0.9</AccuracyMedium>
<AccuracyLong>0.8</AccuracyLong>
<RangedWeapon_Cooldown>0.8</RangedWeapon_Cooldown>
</statBases>
<costList Inherit="False">
<Steel>120</Steel>
<ComponentIndustrial>6</ComponentIndustrial>
</costList>
<verbs>
<li>
<verbClass>Verb_Shoot</verbClass>
<hasStandardCommand>true</hasStandardCommand>
<defaultProjectile>Bullet_WULA_RW_Penetrating_Rifle</defaultProjectile>
<warmupTime>2</warmupTime>
<range>38</range>
<burstShotCount>1</burstShotCount>
<soundCast>ChargeLance_Fire</soundCast>
<soundCastTail>GunTail_Medium</soundCastTail>
<muzzleFlashScale>9</muzzleFlashScale>
</li>
</verbs>
<weaponTags>
<li>Wula_Weapon_Init</li>
</weaponTags>
<thingSetMakerTags><li>RewardStandardQualitySuper</li></thingSetMakerTags>
</ThingDef>
<ThingDef ParentName="BaseBullet">
<defName>Bullet_WULA_RW_Penetrating_Rifle</defName>
<label>穿透钢针弹</label>
<thingClass>WulaFallenEmpire.Projectile_WulaLineAttack</thingClass> <!-- 使用我们的自定义穿透弹丸类 -->
<tickerType>Normal</tickerType>
<neverMultiSelect>True</neverMultiSelect>
<graphicData>
<texPath>Things/Projectile/ChargeLanceShot</texPath>
<graphicClass>Graphic_Single</graphicClass>
<shaderType>TransparentPostLight</shaderType>
<drawSize>1.4</drawSize>
</graphicData>
<projectile>
<damageDef>Bullet</damageDef>
<damageAmountBase>25</damageAmountBase>
<speed>130</speed>
<armorPenetrationBase>0.5</armorPenetrationBase>
<stoppingPower>5</stoppingPower>
</projectile>
</ThingDef>
</Defs>

View File

@@ -35,28 +35,31 @@ namespace WulaFallenEmpire
// Find all rituals that are of our custom base class type.
foreach (PsychicRitualDef_Wula ritualDef in DefDatabase<PsychicRitualDef_Wula>.AllDefs)
{
Command_Action command_Action = new Command_Action();
command_Action.defaultLabel = ritualDef.LabelCap.Resolve();
command_Action.defaultDesc = ritualDef.description;
command_Action.icon = ritualDef.uiIcon;
command_Action.action = delegate
if (ritualDef.Visible)
{
// Mimic vanilla initialization
TargetInfo target = new TargetInfo(this.parent);
PsychicRitualRoleAssignments assignments = ritualDef.BuildRoleAssignments(target);
PsychicRitualCandidatePool candidatePool = ritualDef.FindCandidatePool();
ritualDef.InitializeCast(this.parent.Map);
Find.WindowStack.Add(new Dialog_BeginPsychicRitual(ritualDef, candidatePool, assignments, this.parent.Map));
};
Command_Action command_Action = new Command_Action();
command_Action.defaultLabel = ritualDef.LabelCap.Resolve();
command_Action.defaultDesc = ritualDef.description;
command_Action.icon = ritualDef.uiIcon;
command_Action.action = delegate
{
// Mimic vanilla initialization
TargetInfo target = new TargetInfo(this.parent);
PsychicRitualRoleAssignments assignments = ritualDef.BuildRoleAssignments(target);
PsychicRitualCandidatePool candidatePool = ritualDef.FindCandidatePool();
ritualDef.InitializeCast(this.parent.Map);
Find.WindowStack.Add(new Dialog_BeginPsychicRitual(ritualDef, candidatePool, assignments, this.parent.Map));
};
// Corrected check for cooldown and other requirements
AcceptanceReport acceptanceReport = Find.PsychicRitualManager.CanInvoke(ritualDef, this.parent.Map);
if (!acceptanceReport.Accepted)
{
command_Action.Disable(acceptanceReport.Reason.CapitalizeFirst());
// Corrected check for cooldown and other requirements
AcceptanceReport acceptanceReport = Find.PsychicRitualManager.CanInvoke(ritualDef, this.parent.Map);
if (!acceptanceReport.Accepted)
{
command_Action.Disable(acceptanceReport.Reason.CapitalizeFirst());
}
yield return command_Action;
}
yield return command_Action;
}
}
}

View File

@@ -8,63 +8,88 @@ namespace WulaFallenEmpire
public class Projectile_WulaLineAttack : Projectile
{
private List<Thing> alreadyDamaged = new List<Thing>();
private Vector3 lastTickPosition;
public override void ExposeData()
{
base.ExposeData();
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();
}
protected override void Tick()
{
base.Tick();
Vector3 startPos = this.lastTickPosition;
base.Tick(); // 这会更新弹丸的位置并可能调用Impact()
if (this.Destroyed)
{
return;
}
Map map = this.Map;
IntVec3 currentPosition = this.Position;
// 使用HashSet进行更高效的查找
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(currentPosition));
Vector3 endPos = this.ExactPosition;
foreach (Thing thing in thingsInCell)
{
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(thing) && GenHostility.HostileTo(thing, this.launcher.Faction))
{
DamageInfo dinfo = new DamageInfo(
this.def.projectile.damageDef,
(float)this.DamageAmount,
this.ArmorPenetration,
this.ExactRotation.eulerAngles.y,
this.launcher,
null,
this.equipmentDef,
DamageInfo.SourceCategory.ThingOrUnknown,
this.intendedTarget.Thing);
pawn.TakeDamage(dinfo);
alreadyDamaged.Add(pawn);
}
}
// 调用路径伤害检测
DamageMissedPawns(startPos, endPos);
// 为下一帧更新位置
this.lastTickPosition = endPos;
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
// 如果最终命中的目标还没有在飞行路径上被伤害过,
// 就在这里将它标记为已伤害以避免在基类Impact中再次造成伤害。
// 在最终碰撞前,最后一次检查从上一帧到当前碰撞点的路径
DamageMissedPawns(this.lastTickPosition, this.ExactPosition);
// 如果最终目标还没被路径伤害击中,在这里造成一次伤害
if (hitThing != null && !alreadyDamaged.Contains(hitThing))
{
// 注意这里我们不直接造成伤害因为基类的Impact会处理。
// 我们只是标记它,以防万一。
// 但实际上由于基类Impact会造成伤害这条线可能不是必须的
// 除非我们想完全控制伤害的施加时机。为了安全起见,我们保留它。
DamageInfo dinfo = new DamageInfo(this.def.projectile.damageDef, (float)this.DamageAmount, this.ArmorPenetration, this.ExactRotation.eulerAngles.y, this.launcher, null, this.equipmentDef, DamageInfo.SourceCategory.ThingOrUnknown, this.intendedTarget.Thing);
hitThing.TakeDamage(dinfo);
}
// 调用基类的Impact方法来处理最终的命中效果
// 比如爆炸、声音、或对最终目标的直接伤害。
// 调用基类方法来处理XML中定义的爆炸等最终效果
base.Impact(hitThing, blockedByShield);
}
private void DamageMissedPawns(Vector3 startPos, Vector3 endPos)
{
if (startPos == endPos) return;
Map map = this.Map;
float distance = Vector3.Distance(startPos, endPos);
Vector3 direction = (endPos - startPos).normalized;
for (float i = 0; i < distance; i += 0.5f)
{
Vector3 checkPos = startPos + direction * i;
IntVec3 checkCell = new IntVec3(checkPos);
if (!checkCell.InBounds(map)) continue;
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkCell));
foreach (Thing thing in thingsInCell)
{
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn) && GenHostility.HostileTo(pawn, this.launcher.Faction))
{
var dinfo = new DamageInfo(this.def.projectile.damageDef, (float)this.DamageAmount, this.ArmorPenetration, this.ExactRotation.eulerAngles.y, this.launcher, null, this.equipmentDef, DamageInfo.SourceCategory.ThingOrUnknown, this.intendedTarget.Thing);
pawn.TakeDamage(dinfo);
alreadyDamaged.Add(pawn);
}
}
}
}
}
}