暂存
This commit is contained in:
Binary file not shown.
60
1.6/1.6/Defs/AbilityDefs/ARA_GuardianPsyField_Abilities.xml
Normal file
60
1.6/1.6/Defs/AbilityDefs/ARA_GuardianPsyField_Abilities.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<AbilityDef>
|
||||
<defName>ARA_GuardianPsyField_On</defName>
|
||||
<label>守护者灵能场</label>
|
||||
<description>投射一个强大的灵能防御力场。</description>
|
||||
<iconPath>ArachnaeSwarm/UI/Abilities/ARA_RaceBaseSwarmProduceOn</iconPath>
|
||||
<cooldownTicksRange>601</cooldownTicksRange>
|
||||
<hostile>false</hostile>
|
||||
<showOnCharacterCard>true</showOnCharacterCard>
|
||||
<aiCanUse>false</aiCanUse>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<warmupTime>0</warmupTime>
|
||||
<drawAimPie>false</drawAimPie>
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
<targetable>false</targetable>
|
||||
<targetParams>
|
||||
<canTargetSelf>true</canTargetSelf>
|
||||
</targetParams>
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="CompProperties_AbilityGiveHediff">
|
||||
<compClass>ArachnaeSwarm.CompAbilityEffect_GiveSwitchHediff</compClass>
|
||||
<hediffDef>ARA_GuardianPsyField</hediffDef>
|
||||
<applyToSelf>true</applyToSelf>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
|
||||
<AbilityDef>
|
||||
<defName>ARA_GuardianPsyField_Off</defName>
|
||||
<label>守护者灵能场</label>
|
||||
<description>关闭灵能防御力场。</description>
|
||||
<iconPath>ArachnaeSwarm/UI/Abilities/ARA_RaceBaseSwarmProduceOff</iconPath>
|
||||
<cooldownTicksRange>601</cooldownTicksRange>
|
||||
<hostile>false</hostile>
|
||||
<showOnCharacterCard>true</showOnCharacterCard>
|
||||
<aiCanUse>false</aiCanUse>
|
||||
<verbProperties>
|
||||
<verbClass>Verb_CastAbility</verbClass>
|
||||
<warmupTime>0</warmupTime>
|
||||
<drawAimPie>false</drawAimPie>
|
||||
<requireLineOfSight>false</requireLineOfSight>
|
||||
<targetable>false</targetable>
|
||||
<targetParams>
|
||||
<canTargetSelf>true</canTargetSelf>
|
||||
</targetParams>
|
||||
</verbProperties>
|
||||
<comps>
|
||||
<li Class="CompProperties_AbilityRemoveHediff">
|
||||
<compClass>ArachnaeSwarm.CompAbilityEffect_RemoveSwitchHediff</compClass>
|
||||
<hediffDef>ARA_GuardianPsyField</hediffDef>
|
||||
<applyToSelf>true</applyToSelf>
|
||||
</li>
|
||||
</comps>
|
||||
</AbilityDef>
|
||||
|
||||
</Defs>
|
||||
36
1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml
Normal file
36
1.6/1.6/Defs/HediffDefs/ARA_GuardianPsyField_Hediff.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<HediffDef>
|
||||
<defName>ARA_GuardianPsyField</defName>
|
||||
<label>守护者灵能场</label>
|
||||
<description>一个强大的灵能防御力场。它会自动拦截进入其作用范围的敌对飞行物,并能将附近友军受到的伤害转移到施法者身上。每次成功的守护都会消耗施法者的精神力。</description>
|
||||
<hediffClass>ArachnaeSwarm.Hediff_DynamicInterceptor</hediffClass>
|
||||
<isBad>false</isBad>
|
||||
<defaultLabelColor>(0.6, 0.2, 0.9)</defaultLabelColor>
|
||||
<scenarioCanAdd>false</scenarioCanAdd>
|
||||
<comps>
|
||||
<li Class="ArachnaeSwarm.HediffCompProperties_DynamicInterceptor">
|
||||
<guardianProps Class="ArachnaeSwarm.CompProperties_GuardianPsyField">
|
||||
<!-- Basic functionality -->
|
||||
<radius>5.9</radius>
|
||||
<hitPoints>150</hitPoints>
|
||||
<rechargeDelay>3200</rechargeDelay>
|
||||
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks>
|
||||
|
||||
<!-- Interception types -->
|
||||
<interceptGroundProjectiles>true</interceptGroundProjectiles>
|
||||
<interceptAirProjectiles>true</interceptAirProjectiles>
|
||||
<interceptNonHostileProjectiles>true</interceptNonHostileProjectiles>
|
||||
|
||||
<!-- Visuals and Sound -->
|
||||
<color>(0.3, 0.8, 0.8)</color>
|
||||
<interceptEffecter>Interceptor_BlockedProjectile</interceptEffecter>
|
||||
<breakEffecter>Shield_Break</breakEffecter>
|
||||
<reactivateEffecter>BulletShieldGenerator_Reactivate</reactivateEffecter>
|
||||
</guardianProps>
|
||||
</li>
|
||||
</comps>
|
||||
</HediffDef>
|
||||
|
||||
</Defs>
|
||||
@@ -291,6 +291,8 @@
|
||||
<abilities>
|
||||
<li>ARA_PsychicBrainburn</li>
|
||||
<li>ARA_NeuroSwarm_jump</li>
|
||||
<li>ARA_GuardianPsyField_On</li>
|
||||
<li>ARA_GuardianPsyField_Off</li>
|
||||
<!-- <li>ARA_Ability_Morph</li> -->
|
||||
</abilities>
|
||||
<apparelTags>
|
||||
|
||||
@@ -165,6 +165,9 @@
|
||||
<Compile Include="Hediffs\ProphecyGearEffect.cs" />
|
||||
<Compile Include="Hediffs\WULA_HediffDamgeShield\DRMDamageShield.cs" />
|
||||
<Compile Include="Hediffs\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
|
||||
<Compile Include="Hediff_DynamicInterceptor.cs" />
|
||||
<Compile Include="ThingComp_GuardianPsyField.cs" />
|
||||
<Compile Include="Harmony_ProjectileInterceptor.cs" />
|
||||
<Compile Include="MainHarmony.cs" />
|
||||
<Compile Include="Thing_Comps\CompAndPatch_GiveHediffOnShot.cs" />
|
||||
<Compile Include="Pawn_Comps\ARA_AutoMechCarrier\CompAutoMechCarrier.cs" />
|
||||
|
||||
41
Source/ArachnaeSwarm/Harmony_ProjectileInterceptor.cs
Normal file
41
Source/ArachnaeSwarm/Harmony_ProjectileInterceptor.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
|
||||
public static class Projectile_CheckForFreeInterceptBetween_Patch
|
||||
{
|
||||
// This patch will find our custom ThingComp on pawns and call its intercept method.
|
||||
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
|
||||
{
|
||||
if (__instance.Map == null || __instance.Destroyed)
|
||||
{
|
||||
return true; // Let original method run if something is wrong
|
||||
}
|
||||
|
||||
// Iterate through all pawns on the map
|
||||
foreach (Pawn pawn in __instance.Map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
// Our comp is directly on the pawn, not on apparel
|
||||
if (pawn.TryGetComp<ThingComp_GuardianPsyField>(out var interceptor))
|
||||
{
|
||||
// Call our custom intercept method
|
||||
if (interceptor.TryIntercept(__instance))
|
||||
{
|
||||
// If interception is successful, destroy the projectile
|
||||
__instance.Destroy(DestroyMode.Vanish);
|
||||
|
||||
// Prevent the original game method from running, as we've handled it
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no interception happened, let the original method run
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs
Normal file
75
Source/ArachnaeSwarm/Hediff_DynamicInterceptor.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using System.Linq;
|
||||
using System; // For Activator
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Hediff_DynamicInterceptor : HediffWithComps
|
||||
{
|
||||
// This Hediff's job is to add and remove our custom ThingComp_GuardianPsyField.
|
||||
|
||||
public CompProperties_GuardianPsyField GuardianProps
|
||||
{
|
||||
get
|
||||
{
|
||||
var compProps = def.comps?.FirstOrDefault(c => c is HediffCompProperties_DynamicInterceptor);
|
||||
if (compProps is HediffCompProperties_DynamicInterceptor customProps)
|
||||
{
|
||||
// Note: The XML now needs to contain CompProperties_GuardianPsyField
|
||||
return customProps.guardianProps;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostAdd(DamageInfo? dinfo)
|
||||
{
|
||||
base.PostAdd(dinfo);
|
||||
var props = GuardianProps;
|
||||
if (pawn != null && props != null)
|
||||
{
|
||||
if (pawn.GetComp<ThingComp_GuardianPsyField>() == null)
|
||||
{
|
||||
Log.Message($"[DynamicInterceptor] Adding ThingComp_GuardianPsyField to {pawn.LabelShort}.");
|
||||
var newComp = (ThingComp_GuardianPsyField)Activator.CreateInstance(typeof(ThingComp_GuardianPsyField));
|
||||
newComp.parent = pawn;
|
||||
newComp.Initialize(props);
|
||||
pawn.AllComps.Add(newComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostRemoved()
|
||||
{
|
||||
base.PostRemoved();
|
||||
if (pawn != null)
|
||||
{
|
||||
var comp = pawn.GetComp<ThingComp_GuardianPsyField>();
|
||||
if (comp != null)
|
||||
{
|
||||
Log.Message($"[DynamicInterceptor] Removing ThingComp_GuardianPsyField from {pawn.LabelShort}.");
|
||||
pawn.AllComps.Remove(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This comp will hold the properties for our custom interceptor
|
||||
public class HediffCompProperties_DynamicInterceptor : HediffCompProperties
|
||||
{
|
||||
// This will now point to our custom properties class
|
||||
public CompProperties_GuardianPsyField guardianProps;
|
||||
|
||||
public HediffCompProperties_DynamicInterceptor()
|
||||
{
|
||||
this.compClass = typeof(HediffComp_DynamicInterceptor);
|
||||
}
|
||||
}
|
||||
|
||||
// A simple HediffComp to go with the properties
|
||||
public class HediffComp_DynamicInterceptor : HediffComp
|
||||
{
|
||||
public HediffCompProperties_DynamicInterceptor Props => (HediffCompProperties_DynamicInterceptor)props;
|
||||
}
|
||||
}
|
||||
209
Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs
Normal file
209
Source/ArachnaeSwarm/ThingComp_GuardianPsyField.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
// 1. Expanded CompProperties to match the user's example
|
||||
public class CompProperties_GuardianPsyField : CompProperties
|
||||
{
|
||||
public float radius = 5.9f;
|
||||
public int hitPoints = 100;
|
||||
public int rechargeDelay = 3200; // Ticks after breaking
|
||||
public int rechargeHitPointsIntervalTicks = 60; // Ticks to restore 1 HP
|
||||
|
||||
public bool interceptGroundProjectiles = true;
|
||||
public bool interceptAirProjectiles = true;
|
||||
public bool interceptNonHostileProjectiles = false;
|
||||
|
||||
public EffecterDef interceptEffecter;
|
||||
public EffecterDef breakEffecter;
|
||||
public EffecterDef reactivateEffecter;
|
||||
|
||||
public Color color = Color.cyan;
|
||||
|
||||
public CompProperties_GuardianPsyField()
|
||||
{
|
||||
compClass = typeof(ThingComp_GuardianPsyField);
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
public class ThingComp_GuardianPsyField : ThingComp
|
||||
{
|
||||
// --- State Variables ---
|
||||
private int lastInterceptTicks = -999999;
|
||||
private int ticksToReset = 0; // Cooldown timer
|
||||
public int currentHitPoints;
|
||||
|
||||
// --- Properties ---
|
||||
public CompProperties_GuardianPsyField Props => (CompProperties_GuardianPsyField)props;
|
||||
private Pawn PawnOwner => parent as Pawn;
|
||||
public bool IsOnCooldown => ticksToReset > 0;
|
||||
public int HitPointsMax => Props.hitPoints;
|
||||
|
||||
// --- Visuals ---
|
||||
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
|
||||
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || IsOnCooldown || currentHitPoints <= 0)
|
||||
return false;
|
||||
|
||||
var hediff = PawnOwner.health.hediffSet.GetFirstHediffOfDef(HediffDef.Named("ARA_GuardianPsyField"));
|
||||
return hediff != null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostPostMake()
|
||||
{
|
||||
base.PostPostMake();
|
||||
currentHitPoints = HitPointsMax;
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Values.Look(ref lastInterceptTicks, "lastInterceptTicks", -999999);
|
||||
Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0);
|
||||
Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", 0);
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
if (PawnOwner == null) return;
|
||||
|
||||
if (IsOnCooldown)
|
||||
{
|
||||
ticksToReset--;
|
||||
if (ticksToReset <= 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
else if (currentHitPoints < HitPointsMax && this.parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks))
|
||||
{
|
||||
currentHitPoints++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryIntercept(Projectile projectile)
|
||||
{
|
||||
if (!Active) return false;
|
||||
|
||||
bool isHostile = projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner.Faction);
|
||||
if (!isHostile && !Props.interceptNonHostileProjectiles) return false;
|
||||
|
||||
if (Vector3.Distance(projectile.ExactPosition, PawnOwner.TrueCenter()) > Props.radius) return false;
|
||||
|
||||
// --- Interception Success ---
|
||||
lastInterceptTicks = Find.TickManager.TicksGame;
|
||||
|
||||
Props.interceptEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
|
||||
|
||||
// Consume Hitpoints
|
||||
currentHitPoints -= (int)projectile.DamageAmount;
|
||||
if (currentHitPoints <= 0)
|
||||
{
|
||||
Break();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Break()
|
||||
{
|
||||
Props.breakEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
|
||||
ticksToReset = Props.rechargeDelay;
|
||||
currentHitPoints = 0;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
if (PawnOwner.Spawned)
|
||||
{
|
||||
Props.reactivateEffecter?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
|
||||
}
|
||||
currentHitPoints = HitPointsMax;
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
base.PostDraw();
|
||||
if (!Active || PawnOwner.Map == null) return;
|
||||
|
||||
Vector3 drawPos = PawnOwner.Drawer.DrawPos;
|
||||
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
|
||||
|
||||
float alpha = GetCurrentAlpha();
|
||||
if (alpha > 0f)
|
||||
{
|
||||
Color color = Props.color;
|
||||
color.a *= alpha;
|
||||
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
|
||||
Matrix4x4 matrix = default;
|
||||
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(Props.radius * 2f, 1f, Props.radius * 2f));
|
||||
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetCurrentAlpha()
|
||||
{
|
||||
float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)parent.thingIDNumber + Time.realtimeSinceStartup * 1.5f) + 1f) / 2f);
|
||||
float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
|
||||
return Mathf.Max(idleAlpha, interceptAlpha);
|
||||
}
|
||||
|
||||
// --- GIZMO ---
|
||||
public override System.Collections.Generic.IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
if (PawnOwner != null && Find.Selector.SingleSelectedThing == PawnOwner)
|
||||
{
|
||||
yield return new Gizmo_GuardianShieldStatus { shield = this };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gizmo class copied from the user's example and adapted
|
||||
[StaticConstructorOnStartup]
|
||||
public class Gizmo_GuardianShieldStatus : Gizmo
|
||||
{
|
||||
public ThingComp_GuardianPsyField shield;
|
||||
private static readonly Texture2D FullShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.3f, 0.8f, 0.8f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
|
||||
private static readonly Texture2D EmptyShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.2f, 0.2f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
|
||||
|
||||
public override float GetWidth(float maxWidth) => 140f;
|
||||
|
||||
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||
{
|
||||
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
|
||||
Rect rect2 = rect.ContractedBy(6f);
|
||||
Widgets.DrawWindowBackground(rect);
|
||||
|
||||
Rect labelRect = rect2;
|
||||
labelRect.height = rect.height / 2f;
|
||||
Text.Font = GameFont.Tiny;
|
||||
Widgets.Label(labelRect, "Guardian Field");
|
||||
|
||||
Rect barRect = rect2;
|
||||
barRect.yMin = rect2.y + rect2.height / 2f;
|
||||
float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax;
|
||||
Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false);
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
|
||||
TaggedString statusText = shield.IsOnCooldown ? "Cooldown" : new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax);
|
||||
Widgets.Label(barRect, statusText);
|
||||
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
return new GizmoResult(GizmoState.Clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user