偷轮子炮塔

This commit is contained in:
2025-08-28 16:08:24 +08:00
parent 8202eb9c68
commit fcc30bad3e
4 changed files with 352 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!--
这是作为炮塔的武器定义 (ThingDef)
它本身是一个不可装备、不可见的物品,仅作为 HediffComp 的数据源。
HediffComp_TopTurret 会从这里读取武器的属性 (verb)、射程、冷却时间等,
以及炮塔的图形 (graphicData)。
-->
<ThingDef ParentName="BaseWeaponTurret">
<defName>Gun_WULA_MiniTurretGun_ForHediff</defName>
<label>tactical turret gun</label>
<description>A simple automatic gun made to be mounted on a turret.</description>
<graphicData>
<texPath>Things/Building/TacticalTurret/TacticalTurret_Top</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<statBases>
<AccuracyTouch>0.77</AccuracyTouch>
<AccuracyShort>0.70</AccuracyShort>
<AccuracyMedium>0.45</AccuracyMedium>
<AccuracyLong>0.24</AccuracyLong>
<RangedWeapon_Cooldown>4.8</RangedWeapon_Cooldown>
<DeteriorationRate>0</DeteriorationRate>
<Mass>5</Mass>
<Flammability>0</Flammability>
</statBases>
<verbs>
<li>
<verbClass>Verb_Shoot</verbClass>
<defaultProjectile>Bullet_TacticalTurret</defaultProjectile>
<warmupTime>0</warmupTime>
<range>19.9</range> <!-- Must be kept in sync with "explosion radius" of Grenade_TurretPack -->
<ticksBetweenBurstShots>8</ticksBetweenBurstShots>
<burstShotCount>2</burstShotCount>
<soundCast>GunShotA</soundCast>
<soundCastTail>GunTail_Light</soundCastTail>
<muzzleFlashScale>9</muzzleFlashScale>
<consumeFuelPerShot>1</consumeFuelPerShot>
</li>
</verbs>
</ThingDef>
<!--
这是 Hediff 定义本身
你可以通过任何方式将这个 Hediff 添加给一个 Pawn
例如通过植入物、基因、甚至是开发人员命令。
-->
<HediffDef ParentName="ImplantHediffBase">
<defName>WULA_ShoulderCannon</defName>
<label>shoulder cannon</label>
<description>A shoulder-mounted automated cannon that tracks and fires upon hostiles.</description>
<spawnThingOnRemoved>Steel</spawnThingOnRemoved>
<hediffClass>Hediff_Implant</hediffClass>
<defaultLabelColor>(0.6, 0.6, 0.6)</defaultLabelColor>
<isBad>false</isBad>
<!-- 这里是关键部分 -->
<comps>
<li Class="WulaFallenEmpire.HediffCompProperties_TopTurret">
<!--
指定上面定义的那个武器 ThingDef。
这个组件会根据这个 ThingDef 创建一个虚拟的枪械并进行射击。
-->
<turretDef>Gun_WULA_MiniTurretGun_ForHediff</turretDef>
<!-- (可选) 炮塔旋转的偏移角度0度通常指向右方 -->
<angleOffset>0</angleOffset>
<!-- (可选) 是否自动攻击,默认为 true -->
<autoAttack>true</autoAttack>
</li>
</comps>
<stages>
<li>
<!-- 你可以在这里添加其他效果,比如移动速度加成/减成等 -->
</li>
</stages>
</HediffDef>
</Defs>

View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;
using RimWorld;
namespace WulaFallenEmpire
{
public class HediffCompProperties_TopTurret : HediffCompProperties
{
public HediffCompProperties_TopTurret()
{
this.compClass = typeof(HediffComp_TopTurret);
}
public ThingDef turretDef;
public float angleOffset;
public bool autoAttack = true;
}
[StaticConstructorOnStartup]
public class HediffComp_TopTurret : HediffComp, IAttackTargetSearcher
{
public Thing Thing
{
get
{
return this.Pawn;
}
}
private HediffCompProperties_TopTurret Props
{
get
{
return (HediffCompProperties_TopTurret)this.props;
}
}
public Verb CurrentEffectiveVerb
{
get
{
return this.AttackVerb;
}
}
public LocalTargetInfo LastAttackedTarget
{
get
{
return this.lastAttackedTarget;
}
}
public int LastAttackTargetTick
{
get
{
return this.lastAttackTargetTick;
}
}
public CompEquippable GunCompEq
{
get
{
return this.gun.TryGetComp<CompEquippable>();
}
}
public Verb AttackVerb
{
get
{
return this.GunCompEq.PrimaryVerb;
}
}
private bool WarmingUp
{
get
{
return this.burstWarmupTicksLeft > 0;
}
}
private bool CanShoot
{
get
{
Pawn pawn;
if ((pawn = (this.Pawn)) != null)
{
if (!pawn.Spawned || pawn.Downed || pawn.Dead || !pawn.Awake())
{
return false;
}
if (pawn.stances.stunner.Stunned)
{
return false;
}
if (this.TurretDestroyed)
{
return false;
}
if (pawn.IsColonyMechPlayerControlled && !this.fireAtWill)
{
return false;
}
}
CompCanBeDormant compCanBeDormant = this.Pawn.TryGetComp<CompCanBeDormant>();
return compCanBeDormant == null || compCanBeDormant.Awake;
}
}
public bool TurretDestroyed
{
get
{
Pawn pawn;
return (pawn = (this.Pawn)) != null && this.AttackVerb.verbProps.linkedBodyPartsGroup != null && this.AttackVerb.verbProps.ensureLinkedBodyPartsGroupAlwaysUsable && PawnCapacityUtility.CalculateNaturalPartsAverageEfficiency(pawn.health.hediffSet, this.AttackVerb.verbProps.linkedBodyPartsGroup) <= 0f;
}
}
private Material TurretMat
{
get
{
if (this.turretMat == null)
{
this.turretMat = MaterialPool.MatFrom(this.Props.turretDef.graphicData.texPath);
}
return this.turretMat;
}
}
public bool AutoAttack
{
get
{
return this.Props.autoAttack;
}
}
public override void CompPostMake()
{
base.CompPostMake();
this.MakeGun();
}
private void MakeGun()
{
this.gun = ThingMaker.MakeThing(this.Props.turretDef, null);
this.UpdateGunVerbs();
}
private void UpdateGunVerbs()
{
List<Verb> allVerbs = this.gun.TryGetComp<CompEquippable>().AllVerbs;
for (int i = 0; i < allVerbs.Count; i++)
{
Verb verb = allVerbs[i];
verb.caster = this.Pawn;
verb.castCompleteCallback = delegate ()
{
this.burstCooldownTicksLeft = this.AttackVerb.verbProps.defaultCooldownTime.SecondsToTicks();
};
}
}
public override void CompPostTick(ref float severityAdjustment)
{
base.CompPostTick(ref severityAdjustment);
if (!this.CanShoot)
{
return;
}
if (this.currentTarget.IsValid)
{
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
}
this.AttackVerb.VerbTick();
if (this.AttackVerb.state != VerbState.Bursting)
{
if (this.WarmingUp)
{
this.burstWarmupTicksLeft--;
if (this.burstWarmupTicksLeft == 0)
{
this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
this.lastAttackTargetTick = Find.TickManager.TicksGame;
this.lastAttackedTarget = this.currentTarget;
return;
}
}
else
{
if (this.burstCooldownTicksLeft > 0)
{
this.burstCooldownTicksLeft--;
}
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
{
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
if (this.currentTarget.IsValid)
{
this.burstWarmupTicksLeft = 1;
return;
}
this.ResetCurrentTarget();
}
}
}
}
private void ResetCurrentTarget()
{
this.currentTarget = LocalTargetInfo.Invalid;
this.burstWarmupTicksLeft = 0;
}
public override void CompExposeData()
{
base.CompExposeData();
Scribe_Values.Look<int>(ref this.burstCooldownTicksLeft, "burstCooldownTicksLeft", 0, false);
Scribe_Values.Look<int>(ref this.burstWarmupTicksLeft, "burstWarmupTicksLeft", 0, false);
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
if (this.gun == null)
{
Log.Error("CompTurrentGun had null gun after loading. Recreating.");
this.MakeGun();
return;
}
this.UpdateGunVerbs();
}
}
private const int StartShootIntervalTicks = 10;
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
public Thing gun;
protected int burstCooldownTicksLeft;
protected int burstWarmupTicksLeft;
protected LocalTargetInfo currentTarget = LocalTargetInfo.Invalid;
private bool fireAtWill = true;
private LocalTargetInfo lastAttackedTarget = LocalTargetInfo.Invalid;
private int lastAttackTargetTick;
private float curRotation;
[Unsaved(false)]
public Material turretMat;
}
}

View File

@@ -192,6 +192,7 @@
<Compile Include="Patch_ForceTargetable.cs" />
<Compile Include="Verb\VerbProperties_Excalibur.cs" />
<Compile Include="Verb\Verb_Excalibur.cs" />
<Compile Include="HediffComp_TopTurret.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Thing_ExcaliburBeam.cs" />