This commit is contained in:
2025-09-01 14:01:35 +08:00
parent c93b2e5c29
commit 32d4f20d33
11 changed files with 552 additions and 6 deletions

Binary file not shown.

View File

@@ -22,16 +22,46 @@
</targetParams>
</verbProperties>
<comps>
<li Class="CompProperties_AbilitySprayLiquid">
<projectileDef>ARA_Proj_StrongSludgeSpray</projectileDef>
<numCellsToHit>18</numCellsToHit>
<sprayEffecter>AcidSpray_Directional</sprayEffecter>
</li>
<li Class="CompProperties_AbilityLaunchProjectile">
<projectileDef>ARA_Proj_EggSac</projectileDef>
</li>
</comps>
</AbilityDef>
<AbilityDef>
<defName>ARA_AcidSprayBurst</defName>
<label>酸液连射</label>
<description>快速连续喷射多次腐蚀性酸液,覆盖一片区域。</description>
<iconPath>UI/Abilities/AcidSpray</iconPath>
<cooldownTicksRange>5000</cooldownTicksRange> <!-- 2 hours -->
<aiCanUse>true</aiCanUse>
<displayOrder>300</displayOrder>
<displayGizmoWhileUndrafted>true</displayGizmoWhileUndrafted>
<disableGizmoWhileUndrafted>false</disableGizmoWhileUndrafted>
<warmupStartSound>AcidSpray_Warmup</warmupStartSound>
<verbProperties>
<verbClass>Verb_CastAbility</verbClass>
<range>14.9</range>
<warmupTime>0.25</warmupTime>
<soundCast>AcidSpray_Resolve</soundCast>
<targetParams>
<canTargetLocations>true</canTargetLocations>
</targetParams>
</verbProperties>
<comps>
<li Class="ArachnaeSwarm.CompProperties_AbilitySprayLiquidMulti">
<!-- CompProperties_AbilitySprayLiquid 的属性 -->
<projectileDef>ARA_Proj_StrongSludgeSpray</projectileDef>
<numCellsToHit>18</numCellsToHit>
<sprayEffecter>AcidSpray_Directional</sprayEffecter>
<!-- CompProperties_AbilitySprayLiquidMulti 新增的属性 -->
<shotCount>32</shotCount> <!-- 总共发射5次 -->
<ticksBetweenShots>12</ticksBetweenShots> <!-- 每次发射间隔12 Ticks (0.2秒) -->
</li>
</comps>
</AbilityDef>
<ThingDef>
<defName>ARA_Proj_StrongSludgeSpray</defName>
<label>虫族酸液</label>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<DamageDef ParentName="Flame">
<defName>ARA_AcidBurn</defName>
<label>酸蚀</label>
<additionalHediffs>
<li>
<hediff>ARA_AcidCoverd</hediff>
<severityPerDamageDealt>0.01</severityPerDamageDealt>
</li>
</additionalHediffs>
<workerClass>DamageWorker_AddInjury</workerClass>
<armorCategory>Sharp</armorCategory>
<hediff>AcidBurn</hediff>
<scaleDamageToBuildingsBasedOnFlammability>false</scaleDamageToBuildingsBasedOnFlammability>
</DamageDef>
</Defs>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<HediffDef>
<defName>ARA_AcidCoverd</defName>
<label>虫族酸液</label>
<description>你的身上沾上了虫族强酸,可能会痛一会。</description>
<defaultLabelColor>(1, 1, 0.8)</defaultLabelColor>
<hediffClass>ArachnaeSwarm.HediffCurseFlame</hediffClass>
<comps>
<li Class="HediffCompProperties_SeverityPerDay">
<severityPerDay>-4</severityPerDay>
</li>
<li Class="HediffCompProperties_Disappears">
<disappearsAfterTicks>1800</disappearsAfterTicks>
</li>
<li Class="HediffCompProperties_DisappearsOnDeath"/>
</comps>
<modExtensions>
<li Class="ArachnaeSwarm.CurseFlameModExt">
<damageDefName>ARA_AcidBurn</damageDefName>
<damageRange>1~5</damageRange>
<damageIntervalTicks>40</damageIntervalTicks>
</li>
</modExtensions>
<stages>
<li>
<label>minor</label>
<becomeVisible>true</becomeVisible>
</li>
<li>
<label>minor</label>
<minSeverity>0.2</minSeverity>
<statFactors>
<IncomingDamageFactor>1.25</IncomingDamageFactor>
</statFactors>
<statOffsets>
<ArmorRating_Sharp>-0.15</ArmorRating_Sharp>
<ArmorRating_Blunt>-0.1</ArmorRating_Blunt>
</statOffsets>
</li>
<li>
<label>moderate</label>
<minSeverity>0.35</minSeverity>
<statFactors>
<IncomingDamageFactor>1.75</IncomingDamageFactor>
</statFactors>
<statOffsets>
<ArmorRating_Sharp>-0.35</ArmorRating_Sharp>
<ArmorRating_Blunt>-0.3</ArmorRating_Blunt>
</statOffsets>
</li>
<li>
<label>serious</label>
<minSeverity>0.5</minSeverity>
<statFactors>
<IncomingDamageFactor>2.35</IncomingDamageFactor>
</statFactors>
<statOffsets>
<ArmorRating_Sharp>-0.65</ArmorRating_Sharp>
<ArmorRating_Blunt>-0.6</ArmorRating_Blunt>
</statOffsets>
</li>
<li>
<label>extreme</label>
<minSeverity>0.65</minSeverity>
<statFactors>
<IncomingDamageFactor>2.85</IncomingDamageFactor>
</statFactors>
<statOffsets>
<ArmorRating_Sharp>-0.8</ArmorRating_Sharp>
<ArmorRating_Blunt>-0.75</ArmorRating_Blunt>
</statOffsets>
</li>
<li>
<label>completely</label>
<minSeverity>0.85</minSeverity>
<statFactors>
<IncomingDamageFactor>3.25</IncomingDamageFactor>
</statFactors>
<statOffsets>
<ArmorRating_Sharp>-2</ArmorRating_Sharp>
<ArmorRating_Blunt>-2</ArmorRating_Blunt>
</statOffsets>
</li>
</stages>
</HediffDef>
</Defs>

View File

@@ -16,6 +16,7 @@
<canBeScattered>false</canBeScattered>
<abilities>
<li>ARA_EggSpew</li>
<li>ARA_AcidSprayBurst</li>
</abilities>
<xenotypeSet>
<xenotypeChances>

View File

@@ -7,9 +7,11 @@
<description>一个黏滑的囊状物,可以通过交互来孵化特定的昆虫。</description>
<thingClass>Building</thingClass>
<category>Building</category>
<size>(1,1)</size>
<graphicData>
<texPath>Things/Building/EggSac</texPath>
<graphicClass>Graphic_Random</graphicClass>
<drawSize>(1.5,1.5)</drawSize>
</graphicData>
<altitudeLayer>Building</altitudeLayer>
<passability>PassThroughOnly</passability>
@@ -18,7 +20,7 @@
<tickerType>Normal</tickerType>
<terrainAffordanceNeeded>Light</terrainAffordanceNeeded>
<statBases>
<MaxHitPoints>20</MaxHitPoints>
<MaxHitPoints>200</MaxHitPoints>
<Flammability>0.4</Flammability>
<Beauty>-6</Beauty>
</statBases>

13
Source/ARA.code-workspace Normal file
View File

@@ -0,0 +1,13 @@
{
"folders": [
{
"name": "ArachnaeSwarm",
"path": ".."
},
{
"name": "Data",
"path": "../../../Data"
}
],
"settings": {}
}

View File

@@ -71,6 +71,9 @@
<Compile Include="CompProperties_SpawnPawnFromList.cs" />
<Compile Include="CompSpawnPawnFromList.cs" />
<Compile Include="JobDriver_Incubate.cs" />
<Compile Include="CompProperties_AbilitySprayLiquidMulti.cs" />
<Compile Include="CompAbilityEffect_SprayLiquidMulti.cs" />
<Compile Include="Hediffs\Hediff_CurseFlame.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class CompAbilityEffect_SprayLiquidMulti : CompAbilityEffect
{
private enum State
{
Ready,
Shooting
}
private State state = State.Ready;
private int ticksUntilNextShot;
private int shotsLeft;
private List<IntVec3> currentAffectedCells;
private new CompProperties_AbilitySprayLiquidMulti Props => (CompProperties_AbilitySprayLiquidMulti)props;
private Pawn Pawn => parent.pawn;
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
if (state == State.Ready)
{
currentAffectedCells = AffectedCells(target);
if (currentAffectedCells.Count > 0)
{
Fire(currentAffectedCells);
shotsLeft = Props.shotCount - 1;
if (shotsLeft > 0)
{
state = State.Shooting;
ticksUntilNextShot = Props.ticksBetweenShots;
}
}
}
}
public override void CompTick()
{
base.CompTick();
if (state == State.Shooting)
{
if (ticksUntilNextShot > 0)
{
ticksUntilNextShot--;
}
else
{
if (shotsLeft > 0)
{
Fire(currentAffectedCells);
shotsLeft--;
ticksUntilNextShot = Props.ticksBetweenShots;
}
if (shotsLeft <= 0)
{
state = State.Ready;
currentAffectedCells = null;
}
}
}
}
private void Fire(List<IntVec3> cells)
{
if (Pawn == null || !Pawn.Spawned || Props.projectileDef == null || cells == null)
{
state = State.Ready;
return;
}
if (Props.sprayEffecter != null)
{
Effecter eff = Props.sprayEffecter.Spawn(Pawn.Position, Pawn.Map);
eff.Cleanup();
}
foreach (var cell in cells)
{
if (Pawn.Position.IsValid && cell.IsValid)
{
Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, Pawn.Position, Pawn.Map);
projectile.Launch(Pawn, Pawn.DrawPos, cell, cell, ProjectileHitFlags.IntendedTarget);
}
}
}
public override void DrawEffectPreview(LocalTargetInfo target)
{
GenDraw.DrawFieldEdges(AffectedCells(target));
}
public override bool AICanTargetNow(LocalTargetInfo target)
{
if (Pawn.Faction != null)
{
foreach (IntVec3 item in AffectedCells(target))
{
List<Thing> thingList = item.GetThingList(Pawn.Map);
for (int i = 0; i < thingList.Count; i++)
{
if (thingList[i].Faction == Pawn.Faction)
{
return false;
}
}
}
}
return true;
}
private List<IntVec3> AffectedCells(LocalTargetInfo target)
{
List<Pair<IntVec3, float>> tmpCellDots = new List<Pair<IntVec3, float>>();
List<IntVec3> tmpCells = new List<IntVec3>();
tmpCellDots.Clear();
tmpCells.Clear();
tmpCellDots.Add(new Pair<IntVec3, float>(target.Cell, 999f));
if (Props.numCellsToHit > 1)
{
Vector3 vector = Pawn.Position.ToVector3Shifted().Yto0();
Vector3 vector2 = target.Cell.ToVector3Shifted().Yto0();
IntVec3[] array;
int num;
if (Props.numCellsToHit < 10)
{
array = GenAdj.AdjacentCells;
num = 8;
}
else
{
array = GenRadial.RadialPattern;
num = Props.numCellsToHit + 5;
}
for (int i = 0; i < num; i++)
{
IntVec3 first = target.Cell + array[i];
Vector3 vector3 = first.ToVector3Shifted().Yto0();
float second = Vector3.Dot((vector3 - vector).normalized, (vector3 - vector2).normalized);
tmpCellDots.Add(new Pair<IntVec3, float>(first, second));
}
tmpCellDots.SortByDescending(x => x.Second);
}
Map map = Pawn.Map;
int num2 = Mathf.Min(tmpCellDots.Count, Props.numCellsToHit);
for (int j = 0; j < num2; j++)
{
IntVec3 first2 = tmpCellDots[j].First;
if (!first2.InBounds(map)) continue;
if (first2.Filled(map))
{
Building_Door door = first2.GetDoor(map);
if (door == null || !door.Open) continue;
}
if (parent.verb.TryFindShootLineFromTo(Pawn.Position, first2, out var _, ignoreRange: true))
{
tmpCells.Add(first2);
}
}
return tmpCells;
}
}
}

View File

@@ -0,0 +1,17 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_AbilitySprayLiquidMulti : CompProperties_AbilitySprayLiquid
{
public int shotCount = 1;
public int ticksBetweenShots = 10;
public CompProperties_AbilitySprayLiquidMulti()
{
compClass = typeof(CompAbilityEffect_SprayLiquidMulti);
}
}
}

View File

@@ -0,0 +1,197 @@
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class CurseFlameModExt : DefModExtension
{
// XML 配置字段
public string damageDefName = "AcidBurn"; // 默认 AcidBurn
public FloatRange damageRange = new FloatRange(1f, 3f);
public int damageIntervalTicks = 40;
// 延迟加载的 DamageDef
private DamageDef _resolvedDamageDef;
public DamageDef ResolvedDamageDef
{
get
{
if (_resolvedDamageDef == null)
{
// 从 DefDatabase 解析 DamageDef
_resolvedDamageDef = DefDatabase<DamageDef>.GetNamedSilentFail(damageDefName);
// 回退逻辑:如果配置的 DefName 无效,使用 AcidBurn
if (_resolvedDamageDef == null)
{
Log.Error($"[DragonianMix] 未找到 DamageDef: {damageDefName}, 已回退到 AcidBurn");
_resolvedDamageDef = DamageDefOf.AcidBurn;
}
}
return _resolvedDamageDef;
}
}
}
public class HediffCurseFlame : HediffWithComps
{
public override void Tick()
{
base.Tick();
// 1. 获取 ModExtension 配置
var modExt = def.GetModExtension<CurseFlameModExt>();
if (modExt == null || pawn == null) return;
// 2. 解析 DamageDef此时 DefOf 已初始化)
DamageDef damageDef = modExt.ResolvedDamageDef;
if (damageDef == null) return;
// 3. 触发伤害逻辑
if (pawn.Spawned && !pawn.Dead && pawn.IsHashIntervalTick(modExt.damageIntervalTicks))
{
var hittableParts = HittablePartsViolence(pawn.health.hediffSet).ToList();
if (hittableParts.Any())
{
DamageInfo damage = new DamageInfo(
def: damageDef,
amount: modExt.damageRange.RandomInRange,
armorPenetration: 999f,
hitPart: hittableParts.RandomElement(),
instigator: null
);
pawn.TakeDamage(damage);
}
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_References.Look(ref parent, "parent", false);
}
private static IEnumerable<BodyPartRecord> HittablePartsViolence(HediffSet bodyModel)
{
return bodyModel.GetNotMissingParts()
.Where(part =>
part.coverageAbs > 0 && // 关键过滤:只选可被击中的部位
(part.depth == BodyPartDepth.Outside ||
(part.depth == BodyPartDepth.Inside && part.def.IsSolid(part, bodyModel.hediffs)))
);
}
public Pawn parent;
}
public class HediffComp_ContinuousDamage : HediffComp
{
// Token: 0x1700000E RID: 14
// (get) Token: 0x0600004F RID: 79 RVA: 0x00003885 File Offset: 0x00001A85
public HediffCompProperties_ContinuousDamage Props
{
get
{
return (HediffCompProperties_ContinuousDamage)this.props;
}
}
// Token: 0x1700000F RID: 15
// (get) Token: 0x06000050 RID: 80 RVA: 0x00003892 File Offset: 0x00001A92
public override bool CompShouldRemove
{
get
{
return this.partMissing || base.CompShouldRemove;
}
}
// Token: 0x06000051 RID: 81 RVA: 0x000038A4 File Offset: 0x00001AA4
public override void CompPostMake()
{
base.CompPostMake();
this.ticksToDamage = this.Props.ticksPerDamage;
}
// Token: 0x06000052 RID: 82 RVA: 0x000038C0 File Offset: 0x00001AC0
public override void CompPostTick(ref float severityAdjustment)
{
if (this.parent.IsTended() || this.parent.IsPermanent() || this.Props.endTicks <= (float)this.ticksNullify)
{
return;
}
this.ticksToDamage--;
this.ticksNullify++;
if (this.ticksToDamage <= 0)
{
this.ticksToDamage = this.Props.ticksPerDamage;
DamageDef damageDef = this.Props.damageDef;
DamageInfo dinfo = new DamageInfo(damageDef, this.Props.damageAmount, 0f, 0f, null, this.parent.Part, null, DamageInfo.SourceCategory.ThingOrUnknown, base.Pawn, true, true, QualityCategory.Normal, true);
dinfo.SetIgnoreArmor(true);
base.Pawn.TakeDamage(dinfo);
if (base.Pawn.health.hediffSet.PartIsMissing(this.parent.Part))
{
this.partMissing = true;
}
}
}
// Token: 0x06000053 RID: 83 RVA: 0x000039B4 File Offset: 0x00001BB4
public override void CompPostMerged(Hediff other)
{
base.CompPostMerged(other);
HediffComp_ContinuousDamage hediffComp_ContinuousDamage = other.TryGetComp<HediffComp_ContinuousDamage>();
if (hediffComp_ContinuousDamage != null)
{
this.ticksNullify = Mathf.Min(hediffComp_ContinuousDamage.ticksNullify, this.ticksNullify);
}
}
// Token: 0x06000054 RID: 84 RVA: 0x000039E9 File Offset: 0x00001BE9
public override void CompExposeData()
{
Scribe_Values.Look<int>(ref this.ticksNullify, "ticksNullify", 0, false);
Scribe_Values.Look<int>(ref this.ticksToDamage, "ticksToDamage", 0, false);
Scribe_Values.Look<bool>(ref this.partMissing, "HCPD_partMissing", false, false);
}
// Token: 0x06000055 RID: 85 RVA: 0x00003A21 File Offset: 0x00001C21
public override string CompDebugString()
{
return "ticksToDamage: " + this.ticksToDamage.ToString();
}
// Token: 0x0400002C RID: 44
public int ticksNullify;
// Token: 0x0400002D RID: 45
public int ticksToDamage;
// Token: 0x0400002E RID: 46
public bool partMissing;
}
public class HediffCompProperties_ContinuousDamage : HediffCompProperties
{
// Token: 0x0600004E RID: 78 RVA: 0x0000384F File Offset: 0x00001A4F
public HediffCompProperties_ContinuousDamage()
{
this.compClass = typeof(HediffComp_ContinuousDamage);
}
// Token: 0x04000028 RID: 40
public DamageDef damageDef;
// Token: 0x04000029 RID: 41
public int ticksPerDamage = 100;
// Token: 0x0400002A RID: 42
public float endTicks = -1f;
// Token: 0x0400002B RID: 43
public float damageAmount = 1f;
}
}