Files
ArachnaeSwarm/Source/ArachnaeSwarm/Verbs/Verb_ShootWithOffset.cs
ProjectKoi-Kalo\Kalo 134ce2214f feat(building): 增强防御塔功能并调整武器射程
更新了阿拉克涅虫族防御建筑的行为与配置:
- 将部分 turret 类型的 thingClass 替换为 Building_TurretGunHasSpeed,
  并添加 ModExt_HasSpeedTurret 扩展以支持旋转速度控制。
- 为多个炮塔添加射击偏移扩展 ModExtension_ShootWithOffset,
  并指定具体的坐标偏移量。
- 调整两种 needle gun 武器的射程从 22 提高至 28。
- 移除了已废弃的 ARA_Weapon_FireSpew.xml 配置文件。
- 在项目中引入新的构建类:Building_TurretGunHasSpeed 和 Verb_ShootWithOffset。
2025-10-03 13:53:44 +08:00

262 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 PlaceWorker_ShowTurretWithOffsetRadius : PlaceWorker
{
public override AcceptanceReport AllowsPlacing(BuildableDef checkingDef, IntVec3 loc, Rot4 rot, Map map, Thing thingToIgnore = null, Thing thing = null)
{
VerbProperties verbProperties = ((ThingDef)checkingDef).building.turretGunDef.Verbs.Find((VerbProperties v) => v.verbClass == typeof(Verb_ShootWithOffset));
if (verbProperties.range > 0f)
{
GenDraw.DrawRadiusRing(loc, verbProperties.range);
}
if (verbProperties.minRange > 0f)
{
GenDraw.DrawRadiusRing(loc, verbProperties.minRange);
}
return true;
}
}
public class ModExtension_ShootWithOffset : DefModExtension
{
public Vector2 GetOffsetFor(int index)
{
Vector2 result;
if (this.offsets.NullOrEmpty<Vector2>())
{
result = Vector2.zero;
}
else
{
int index2 = index % this.offsets.Count;
result = this.offsets[index2];
}
return result;
}
public List<Vector2> offsets = new List<Vector2>();
}
public class Verb_ShootWithOffset : Verb_Shoot
{
public int offset = 0;
protected override bool TryCastShot()
{
bool num = BaseTryCastShot();
if (num && CasterIsPawn)
{
CasterPawn.records.Increment(RecordDefOf.ShotsFired);
}
return num;
}
protected bool BaseTryCastShot()
{
if (currentTarget.HasThing && currentTarget.Thing.Map != caster.Map)
{
return false;
}
ThingDef projectile = Projectile;
if (projectile == null)
{
return false;
}
ShootLine resultingLine;
bool flag = TryFindShootLineFromTo(caster.Position, currentTarget, out resultingLine);
if (verbProps.stopBurstWithoutLos && !flag)
{
return false;
}
if (base.EquipmentSource != null)
{
base.EquipmentSource.GetComp<CompChangeableProjectile>()?.Notify_ProjectileLaunched();
base.EquipmentSource.GetComp<CompApparelVerbOwner_Charged>()?.UsedOnce();
}
lastShotTick = Find.TickManager.TicksGame;
Thing manningPawn = caster;
Thing equipmentSource = base.EquipmentSource;
CompMannable compMannable = caster.TryGetComp<CompMannable>();
if (compMannable?.ManningPawn != null)
{
manningPawn = compMannable.ManningPawn;
equipmentSource = caster;
}
Vector3 drawPos = caster.DrawPos;
drawPos = ApplyProjectileOffset(drawPos, equipmentSource);
Projectile projectile2 = (Projectile)GenSpawn.Spawn(projectile, resultingLine.Source, caster.Map);
if (equipmentSource.TryGetComp(out CompUniqueWeapon comp))
{
foreach (WeaponTraitDef item in comp.TraitsListForReading)
{
if (item.damageDefOverride != null)
{
projectile2.damageDefOverride = item.damageDefOverride;
}
if (!item.extraDamages.NullOrEmpty())
{
Projectile projectile3 = projectile2;
if (projectile3.extraDamages == null)
{
projectile3.extraDamages = new List<ExtraDamage>();
}
projectile2.extraDamages.AddRange(item.extraDamages);
}
}
}
if (verbProps.ForcedMissRadius > 0.5f)
{
float num = verbProps.ForcedMissRadius;
if (manningPawn is Pawn pawn)
{
num *= verbProps.GetForceMissFactorFor(equipmentSource, pawn);
}
float num2 = VerbUtility.CalculateAdjustedForcedMiss(num, currentTarget.Cell - caster.Position);
if (num2 > 0.5f)
{
IntVec3 forcedMissTarget = GetForcedMissTarget(num2);
if (forcedMissTarget != currentTarget.Cell)
{
ProjectileHitFlags projectileHitFlags = ProjectileHitFlags.NonTargetWorld;
if (Rand.Chance(0.5f))
{
projectileHitFlags = ProjectileHitFlags.All;
}
if (!canHitNonTargetPawnsNow)
{
projectileHitFlags &= ~ProjectileHitFlags.NonTargetPawns;
}
projectile2.Launch(manningPawn, drawPos, forcedMissTarget, currentTarget, projectileHitFlags, preventFriendlyFire, equipmentSource);
return true;
}
}
}
ShotReport shotReport = ShotReport.HitReportFor(caster, this, currentTarget);
Thing randomCoverToMissInto = shotReport.GetRandomCoverToMissInto();
ThingDef targetCoverDef = randomCoverToMissInto?.def;
if (verbProps.canGoWild && !Rand.Chance(shotReport.AimOnTargetChance_IgnoringPosture))
{
bool flyOverhead = projectile2?.def?.projectile != null && projectile2.def.projectile.flyOverhead;
resultingLine.ChangeDestToMissWild(shotReport.AimOnTargetChance_StandardTarget, flyOverhead, caster.Map);
ProjectileHitFlags projectileHitFlags2 = ProjectileHitFlags.NonTargetWorld;
if (Rand.Chance(0.5f) && canHitNonTargetPawnsNow)
{
projectileHitFlags2 |= ProjectileHitFlags.NonTargetPawns;
}
projectile2.Launch(manningPawn, drawPos, resultingLine.Dest, currentTarget, projectileHitFlags2, preventFriendlyFire, equipmentSource, targetCoverDef);
return true;
}
if (currentTarget.Thing != null && currentTarget.Thing.def.CanBenefitFromCover && !Rand.Chance(shotReport.PassCoverChance))
{
ProjectileHitFlags projectileHitFlags3 = ProjectileHitFlags.NonTargetWorld;
if (canHitNonTargetPawnsNow)
{
projectileHitFlags3 |= ProjectileHitFlags.NonTargetPawns;
}
projectile2.Launch(manningPawn, drawPos, randomCoverToMissInto, currentTarget, projectileHitFlags3, preventFriendlyFire, equipmentSource, targetCoverDef);
return true;
}
ProjectileHitFlags projectileHitFlags4 = ProjectileHitFlags.IntendedTarget;
if (canHitNonTargetPawnsNow)
{
projectileHitFlags4 |= ProjectileHitFlags.NonTargetPawns;
}
if (!currentTarget.HasThing || currentTarget.Thing.def.Fillage == FillCategory.Full)
{
projectileHitFlags4 |= ProjectileHitFlags.NonTargetWorld;
}
if (currentTarget.Thing != null)
{
projectile2.Launch(manningPawn, drawPos, currentTarget, currentTarget, projectileHitFlags4, preventFriendlyFire, equipmentSource, targetCoverDef);
}
else
{
projectile2.Launch(manningPawn, drawPos, resultingLine.Dest, currentTarget, projectileHitFlags4, preventFriendlyFire, equipmentSource, targetCoverDef);
}
return true;
}
private Vector3 ApplyProjectileOffset(Vector3 originalDrawPos, Thing equipmentSource)
{
if (equipmentSource != null)
{
// 获取投射物偏移的模组扩展
ModExtension_ShootWithOffset offsetExtension =
equipmentSource.def.GetModExtension<ModExtension_ShootWithOffset>();
if (offsetExtension != null && offsetExtension.offsets != null && offsetExtension.offsets.Count > 0)
{
// 获取当前连发射击的剩余次数
int burstShotsLeft = GetBurstShotsLeft();
// 计算从发射者到目标的角度
Vector3 targetPos = currentTarget.CenterVector3;
Vector3 casterPos = caster.DrawPos;
float rimworldAngle = targetPos.AngleToFlat(casterPos);
// 将RimWorld角度转换为适合偏移计算的角度
float correctedAngle = ConvertRimWorldAngleToOffsetAngle(rimworldAngle);
// 应用偏移并旋转到正确方向
Vector2 offset = offsetExtension.GetOffsetFor(burstShotsLeft);
Vector2 rotatedOffset = offset.RotatedBy(correctedAngle);
// 将2D偏移转换为3D并应用到绘制位置
originalDrawPos += new Vector3(rotatedOffset.x, 0f, rotatedOffset.y);
}
}
return originalDrawPos;
}
/// <summary>
/// 获取当前连发射击剩余次数
/// </summary>
/// <returns>连发射击剩余次数</returns>
private int GetBurstShotsLeft()
{
if (burstShotsLeft >= 0)
{
return (int)burstShotsLeft;
}
return 0;
}
/// <summary>
/// 将RimWorld角度转换为偏移计算用的角度
/// RimWorld使用顺时针角度系统需要转换为标准的数学角度系统
/// </summary>
/// <param name="rimworldAngle">RimWorld角度</param>
/// <returns>转换后的角度</returns>
private float ConvertRimWorldAngleToOffsetAngle(float rimworldAngle)
{
// RimWorld角度0°=东90°=北180°=西270°=南
// 转换为0°=东90°=南180°=西270°=北
return -rimworldAngle - 90f;
}
}
}