1
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompAbilityEffect_StunKnockback : CompAbilityEffect
|
||||
{
|
||||
public new CompProperties_StunKnockback Props => (CompProperties_StunKnockback)props;
|
||||
|
||||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||
{
|
||||
base.Apply(target, dest);
|
||||
|
||||
if (target.HasThing && target.Thing is Pawn targetPawn)
|
||||
{
|
||||
// 第一步:造成伤害和眩晕
|
||||
bool targetDied = ApplyDamageAndStun(targetPawn);
|
||||
|
||||
// 第二步:如果目标仍然存活,执行击退
|
||||
if (!targetDied && targetPawn != null && !targetPawn.Dead && !targetPawn.Downed)
|
||||
{
|
||||
PerformKnockback(targetPawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用伤害和眩晕效果,返回目标是否死亡
|
||||
/// </summary>
|
||||
private bool ApplyDamageAndStun(Pawn targetPawn)
|
||||
{
|
||||
// 记录目标的初始状态(是否存活)
|
||||
bool wasAliveBeforeDamage = !targetPawn.Dead;
|
||||
|
||||
// 创建伤害信息
|
||||
DamageInfo damageInfo = new DamageInfo(
|
||||
Props.damageDef,
|
||||
Props.damageAmount,
|
||||
Props.armorPenetration,
|
||||
-1f,
|
||||
parent.pawn,
|
||||
null
|
||||
);
|
||||
|
||||
// 应用伤害
|
||||
targetPawn.TakeDamage(damageInfo);
|
||||
|
||||
// 检查目标是否死亡
|
||||
bool targetDied = targetPawn.Dead || targetPawn.Destroyed || targetPawn == null;
|
||||
|
||||
if (targetDied)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 使用施法者的地图而不是目标的地图
|
||||
Map map = parent.pawn.Map;
|
||||
|
||||
// 播放冲击效果
|
||||
if (Props.impactEffecter != null && map != null)
|
||||
{
|
||||
Effecter effect = Props.impactEffecter.Spawn();
|
||||
effect.Trigger(new TargetInfo(targetPawn.Position, map), new TargetInfo(targetPawn.Position, map));
|
||||
effect.Cleanup();
|
||||
}
|
||||
|
||||
// 播放冲击音效
|
||||
if (Props.impactSound != null && map != null)
|
||||
{
|
||||
Props.impactSound.PlayOneShot(new TargetInfo(targetPawn.Position, map));
|
||||
}
|
||||
|
||||
// 应用眩晕 - 只在目标存活时应用
|
||||
if (Props.stunTicks > 0 && !targetPawn.Dead)
|
||||
{
|
||||
targetPawn.stances.stunner.StunFor(Props.stunTicks, parent.pawn);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行击退
|
||||
/// </summary>
|
||||
private void PerformKnockback(Pawn targetPawn)
|
||||
{
|
||||
// 再次检查目标是否有效
|
||||
if (targetPawn == null || targetPawn.Destroyed || targetPawn.Dead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算击退方向
|
||||
IntVec3 knockbackDirection = CalculateKnockbackDirection(targetPawn.Position);
|
||||
|
||||
// 寻找最远的可站立击退位置
|
||||
IntVec3 knockbackDestination = FindFarthestStandablePosition(targetPawn, knockbackDirection);
|
||||
|
||||
// 如果找到了有效位置,执行击退飞行
|
||||
if (knockbackDestination.IsValid && knockbackDestination != targetPawn.Position)
|
||||
{
|
||||
CreateKnockbackFlyer(targetPawn, knockbackDestination);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算击退方向(施法者到目标的连线延长线)
|
||||
/// </summary>
|
||||
private IntVec3 CalculateKnockbackDirection(IntVec3 targetPosition)
|
||||
{
|
||||
// 从施法者指向目标的方向
|
||||
IntVec3 direction = targetPosition - parent.pawn.Position;
|
||||
|
||||
// 标准化方向(保持整数坐标)
|
||||
if (direction.x != 0 || direction.z != 0)
|
||||
{
|
||||
// 找到主要方向分量
|
||||
if (Mathf.Abs(direction.x) > Mathf.Abs(direction.z))
|
||||
{
|
||||
return new IntVec3(Mathf.Sign(direction.x) > 0 ? 1 : -1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new IntVec3(0, 0, Mathf.Sign(direction.z) > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果施法者和目标在同一位置,使用随机方向
|
||||
return new IntVec3(Rand.Value > 0.5f ? 1 : -1, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找最远的可站立击退位置(不检查路径通行性,只检查目标格子是否可站立)
|
||||
/// </summary>
|
||||
private IntVec3 FindFarthestStandablePosition(Pawn targetPawn, IntVec3 direction)
|
||||
{
|
||||
Map map = targetPawn.Map;
|
||||
IntVec3 currentPos = targetPawn.Position;
|
||||
IntVec3 farthestValidPos = currentPos;
|
||||
|
||||
// 从最大距离开始向回找,找到第一个可站立的格子
|
||||
for (int distance = Props.maxKnockbackDistance; distance >= 1; distance--)
|
||||
{
|
||||
IntVec3 testPos = currentPos + (direction * distance);
|
||||
|
||||
if (!testPos.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查格子是否可站立且没有其他Pawn
|
||||
if (IsCellStandableAndEmpty(testPos, map, targetPawn))
|
||||
{
|
||||
farthestValidPos = testPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return farthestValidPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查格子是否可站立且没有其他Pawn
|
||||
/// </summary>
|
||||
private bool IsCellStandableAndEmpty(IntVec3 cell, Map map, Pawn targetPawn)
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
return false;
|
||||
|
||||
// 检查是否可站立
|
||||
if (!cell.Standable(map))
|
||||
return false;
|
||||
|
||||
// 检查是否有建筑阻挡(如果配置不允许击退到墙上)
|
||||
if (!Props.canKnockbackIntoWalls)
|
||||
{
|
||||
Building edifice = cell.GetEdifice(map);
|
||||
if (edifice != null && !(edifice is Building_Door))
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查视线(如果需要)
|
||||
if (Props.requireLineOfSight && !GenSight.LineOfSight(targetPawn.Position, cell, map))
|
||||
return false;
|
||||
|
||||
// 检查是否有其他pawn
|
||||
List<Thing> thingList = cell.GetThingList(map);
|
||||
foreach (Thing thing in thingList)
|
||||
{
|
||||
if (thing is Pawn otherPawn && otherPawn != targetPawn)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建击退飞行器
|
||||
/// </summary>
|
||||
private void CreateKnockbackFlyer(Pawn targetPawn, IntVec3 destination)
|
||||
{
|
||||
Map map = targetPawn.Map;
|
||||
|
||||
// 使用自定义飞行器或默认飞行器
|
||||
ThingDef flyerDef = Props.knockbackFlyerDef ?? ThingDefOf.PawnFlyer;
|
||||
|
||||
// 创建飞行器(参考JumpUtility.DoJump)
|
||||
PawnFlyer flyer = PawnFlyer.MakeFlyer(
|
||||
flyerDef,
|
||||
targetPawn,
|
||||
destination,
|
||||
Props.flightEffecterDef,
|
||||
Props.landingSound,
|
||||
false, // 不携带物品
|
||||
null, // 不覆盖起始位置
|
||||
null, // 传递Ability对象而不是CompAbilityEffect
|
||||
new LocalTargetInfo(destination)
|
||||
);
|
||||
|
||||
if (flyer != null)
|
||||
{
|
||||
// 生成飞行器
|
||||
GenSpawn.Spawn(flyer, destination, map);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
|
||||
{
|
||||
// 首先调用基类验证
|
||||
if (!base.Valid(target, throwMessages))
|
||||
return false;
|
||||
|
||||
// 检查目标是否为Pawn
|
||||
if (!target.HasThing || !(target.Thing is Pawn))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查目标是否存活
|
||||
Pawn targetPawn = target.Thing as Pawn;
|
||||
if (targetPawn.Dead || targetPawn.Downed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在同一地图
|
||||
if (targetPawn.Map != parent.pawn.Map)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_StunKnockback : CompProperties_AbilityEffect
|
||||
{
|
||||
// 伤害设置
|
||||
public DamageDef damageDef = DamageDefOf.Blunt;
|
||||
public float damageAmount = 15f;
|
||||
public float armorPenetration = 0f;
|
||||
|
||||
// 眩晕设置
|
||||
public int stunTicks = 180; // 3秒眩晕
|
||||
|
||||
// 击退设置
|
||||
public int maxKnockbackDistance = 5; // 最大击退距离
|
||||
public bool requireLineOfSight = true; // 击退路径是否需要视线
|
||||
public bool canKnockbackIntoWalls = false; // 是否可以击退到墙上
|
||||
|
||||
// 飞行效果设置
|
||||
public ThingDef knockbackFlyerDef;
|
||||
public EffecterDef flightEffecterDef;
|
||||
public SoundDef landingSound;
|
||||
|
||||
// 伤害和击退的视觉效果
|
||||
public EffecterDef impactEffecter;
|
||||
public SoundDef impactSound;
|
||||
|
||||
public CompProperties_StunKnockback()
|
||||
{
|
||||
compClass = typeof(CompAbilityEffect_StunKnockback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace WulaFallenEmpire
|
||||
// 通过 ModExtension 配置的图形数据
|
||||
private ExtraGraphicsExtension modExtension;
|
||||
|
||||
// 图形缓存
|
||||
// 图形缓存 - 现在包含Shader信息
|
||||
private Dictionary<string, Graphic> graphicsCache = new Dictionary<string, Graphic>();
|
||||
|
||||
// 动画状态 - 每个图层的独立浮动
|
||||
@@ -41,16 +41,16 @@ namespace WulaFallenEmpire
|
||||
// 重写 Graphic 属性返回 null,完全自定义渲染
|
||||
public override Graphic Graphic => null;
|
||||
|
||||
// 获取缓存的图形
|
||||
private Graphic GetCachedGraphic(string texturePath, Vector2 scale, Color color)
|
||||
// 获取缓存的图形 - 修改后支持自定义Shader
|
||||
private Graphic GetCachedGraphic(string texturePath, Vector2 scale, Color color, Shader shader)
|
||||
{
|
||||
string cacheKey = $"{texturePath}_{scale.x}_{scale.y}_{color}";
|
||||
string cacheKey = $"{texturePath}_{scale.x}_{scale.y}_{color}_{shader?.name ?? "null"}";
|
||||
|
||||
if (!graphicsCache.TryGetValue(cacheKey, out Graphic graphic))
|
||||
{
|
||||
graphic = GraphicDatabase.Get<Graphic_Single>(
|
||||
texturePath,
|
||||
ShaderDatabase.TransparentPostLight,
|
||||
shader ?? ShaderDatabase.TransparentPostLight, // 使用传入的Shader,如果为null则使用默认
|
||||
scale,
|
||||
color);
|
||||
graphicsCache[cacheKey] = graphic;
|
||||
@@ -58,6 +58,66 @@ namespace WulaFallenEmpire
|
||||
|
||||
return graphic;
|
||||
}
|
||||
|
||||
// 根据Shader名称获取Shader - 修正版本
|
||||
private Shader GetShaderByName(string shaderName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shaderName))
|
||||
return ShaderDatabase.TransparentPostLight;
|
||||
|
||||
// 使用switch语句匹配实际可用的Shader
|
||||
switch (shaderName.ToLower())
|
||||
{
|
||||
case "transparent":
|
||||
return ShaderDatabase.Transparent;
|
||||
case "transparentpostlight":
|
||||
return ShaderDatabase.TransparentPostLight;
|
||||
case "transparentplant":
|
||||
return ShaderDatabase.TransparentPlant;
|
||||
case "cutout":
|
||||
return ShaderDatabase.Cutout;
|
||||
case "cutoutcomplex":
|
||||
return ShaderDatabase.CutoutComplex;
|
||||
case "cutoutflying":
|
||||
return ShaderDatabase.CutoutFlying;
|
||||
case "cutoutflying01":
|
||||
return ShaderDatabase.CutoutFlying01;
|
||||
case "terrainfade":
|
||||
return ShaderDatabase.TerrainFade;
|
||||
case "terrainfaderough":
|
||||
return ShaderDatabase.TerrainFadeRough;
|
||||
case "mote":
|
||||
return ShaderDatabase.Mote;
|
||||
case "moteglow":
|
||||
return ShaderDatabase.MoteGlow;
|
||||
case "motepulse":
|
||||
return ShaderDatabase.MotePulse;
|
||||
case "moteglowpulse":
|
||||
return ShaderDatabase.MoteGlowPulse;
|
||||
case "motewater":
|
||||
return ShaderDatabase.MoteWater;
|
||||
case "moteglowdistorted":
|
||||
return ShaderDatabase.MoteGlowDistorted;
|
||||
case "solidcolor":
|
||||
return ShaderDatabase.SolidColor;
|
||||
case "vertexcolor":
|
||||
return ShaderDatabase.VertexColor;
|
||||
case "invisible":
|
||||
return ShaderDatabase.Invisible;
|
||||
case "silhouette":
|
||||
return ShaderDatabase.Silhouette;
|
||||
case "worldterrain":
|
||||
return ShaderDatabase.WorldTerrain;
|
||||
case "worldocean":
|
||||
return ShaderDatabase.WorldOcean;
|
||||
case "metaoverlay":
|
||||
return ShaderDatabase.MetaOverlay;
|
||||
default:
|
||||
Log.Warning($"Building_ExtraGraphics: Shader '{shaderName}' not found, using TransparentPostLight as fallback");
|
||||
return ShaderDatabase.TransparentPostLight;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
|
||||
{
|
||||
// 不调用基类的 DrawAt,完全自定义渲染
|
||||
@@ -101,8 +161,11 @@ namespace WulaFallenEmpire
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取图形
|
||||
Graphic graphic = GetCachedGraphic(layer.texturePath, layer.scale, layer.color);
|
||||
// 获取Shader
|
||||
Shader shader = GetShaderByName(layer.shaderName);
|
||||
|
||||
// 获取图形(现在传入Shader)
|
||||
Graphic graphic = GetCachedGraphic(layer.texturePath, layer.scale, layer.color, shader);
|
||||
|
||||
// 计算图层浮动偏移
|
||||
float hoverOffset = 0f;
|
||||
@@ -187,7 +250,7 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
// 单个图形层的配置数据
|
||||
// 单个图形层的配置数据 - 添加shaderName字段
|
||||
public class GraphicLayerData
|
||||
{
|
||||
// 基础配置
|
||||
@@ -195,6 +258,7 @@ namespace WulaFallenEmpire
|
||||
public Vector2 scale = Vector2.one; // 缩放比例
|
||||
public Color color = Color.white; // 颜色
|
||||
public int drawOrder = 0; // 绘制顺序(数字小的先绘制)
|
||||
public string shaderName = "TransparentPostLight"; // Shader名称(新增)
|
||||
|
||||
// 位置配置 - 使用环世界坐标系
|
||||
// X: 左右偏移, Y: 图层深度, Z: 上下偏移
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
<Compile Include="Ability\WULA_AbilitySpawnAligned\CompProperties_AbilitySpawnAligned.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityTeleportSelf\CompAbilityEffect_TeleportSelf.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityTeleportSelf\CompProperties_AbilityTeleportSelf.cs" />
|
||||
<Compile Include="Ability\WULA_StunKnockback\CompAbilityEffect_StunKnockback.cs" />
|
||||
<Compile Include="Ability\WULA_StunKnockback\CompProperties_StunKnockback.cs" />
|
||||
<Compile Include="BuildingComp\Building_ExtraGraphics.cs" />
|
||||
<Compile Include="BuildingComp\Building_MapObserver.cs" />
|
||||
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompBuildingBombardment.cs" />
|
||||
|
||||
Reference in New Issue
Block a user