This commit is contained in:
2025-11-21 11:50:58 +08:00
parent 4a44ad5005
commit 635d587a9b
16 changed files with 936 additions and 130 deletions

View File

@@ -21,9 +21,6 @@ namespace WulaFallenEmpire
Map map = parent.pawn.MapHeld;
if (map == null) return;
// 播放发射特效(在施法者位置)- 在释放瞬间播放
//PlayCastEffecter(target, map);
// 获取扇形区域内的所有单元格
List<IntVec3> affectedCells = AffectedCells(target);
@@ -56,38 +53,15 @@ namespace WulaFallenEmpire
// 为每个受影响的目标播放命中效果器并处理效果
foreach (Thing affectedThing in affectedTargets)
{
PlayHitEffecter(affectedThing, map);
// 只对建筑和Pawn播放命中特效
if (affectedThing is Building || affectedThing is Pawn)
{
PlayHitEffecter(affectedThing, map);
}
ProcessTarget(affectedThing);
}
}
private void PlayCastEffecter(LocalTargetInfo target, Map map)
{
try
{
if (Props.castEffecter == null) return;
// 在释放瞬间创建效果器,确保正确的方向
Effecter effecter = Props.castEffecter.Spawn(Pawn.Position, target.Cell, map);
if (Props.castEffecterMaintainTicks > 0)
{
// 使用与参考代码相同的方法来维持效果器
parent.AddEffecterToMaintain(effecter, Pawn.Position, target.Cell, Props.castEffecterMaintainTicks, map);
}
else
{
effecter.Cleanup();
}
Log.Message($"[AreaDestruction] Played cast effecter from {Pawn.Position} to {target.Cell}");
}
catch (System.Exception ex)
{
Log.Warning($"[AreaDestruction] Error playing cast effecter: {ex.Message}");
}
}
private void PlayHitEffecter(Thing target, Map map)
{
try
@@ -99,7 +73,6 @@ namespace WulaFallenEmpire
Vector3 directionFromCaster = (target.Position.ToVector3Shifted() - Pawn.Position.ToVector3Shifted()).normalized;
// 计算反向位置:目标位置 + 反向向量 * 距离
// 这样特效会从目标位置向施法者的反方向播放
IntVec3 reversePosition = target.Position + new IntVec3(
Mathf.RoundToInt(-directionFromCaster.x * 2f),
0,
@@ -110,7 +83,6 @@ namespace WulaFallenEmpire
reversePosition = reversePosition.ClampInsideMap(map);
// 使用两个位置参数来设置效果器方向
// 从目标位置到反向位置,这样特效会向施法者反方向播放
Effecter effecter = Props.hitEffecter.Spawn(target.Position, reversePosition, map);
if (Props.hitEffecterMaintainTicks > 0)
@@ -123,7 +95,8 @@ namespace WulaFallenEmpire
effecter.Cleanup();
}
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position} with reverse direction to {reversePosition}");
// 可选:记录日志用于调试
// Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}");
}
catch (System.Exception ex)
{
@@ -156,10 +129,15 @@ namespace WulaFallenEmpire
{
DestroyAllBodyParts(targetPawn);
}
// 其他类型的物体(如物品、植物等)不进行处理
}
private bool ShouldAffectThing(Thing thing)
{
// 只影响建筑和Pawn
if (!(thing is Building) && !(thing is Pawn))
return false;
// 检查是否影响施法者自己
if (thing == Pawn && !Props.affectCaster)
return false;
@@ -190,7 +168,8 @@ namespace WulaFallenEmpire
// 直接销毁建筑
building.Destroy(DestroyMode.Vanish);
Log.Message($"[AreaDestruction] Destroyed building: {buildingInfo}");
// 可选:记录日志用于调试
// Log.Message($"[AreaDestruction] Destroyed building: {buildingInfo}");
}
catch (System.Exception ex)
{
@@ -213,7 +192,7 @@ namespace WulaFallenEmpire
int partsDestroyed = 0;
foreach (var bodyPartRecord in bodyPartRecords)
{
// 跳过核心部位以避免立即死亡(可选,根据需求调整)
// 跳过核心部位以避免立即死亡
if (IsCoreBodyPart(bodyPartRecord)) continue;
// 检查该部位是否已经缺失
@@ -231,7 +210,8 @@ namespace WulaFallenEmpire
// 检查pawn是否还"活着"(没有核心部位缺失时可能还能存活)
CheckPawnViability(targetPawn);
Log.Message($"[AreaDestruction] Destroyed {partsDestroyed} body parts on {pawnInfo}");
// 可选:记录日志用于调试
// Log.Message($"[AreaDestruction] Destroyed {partsDestroyed} body parts on {pawnInfo}");
}
}
catch (System.Exception ex)
@@ -290,7 +270,7 @@ namespace WulaFallenEmpire
List<Thing> thingList = cell.GetThingList(Pawn.Map);
for (int i = 0; i < thingList.Count; i++)
{
if (thingList[i].Faction == Pawn.Faction)
if (thingList[i] is Pawn pawn && pawn.Faction == Pawn.Faction)
{
return false;
}

View File

@@ -0,0 +1,108 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompAbilityEffect_SpawnAligned : CompAbilityEffect_Spawn
{
public new CompProperties_AbilitySpawnAligned Props => (CompProperties_AbilitySpawnAligned)props;
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
// 获取刚刚生成的物品
Thing spawnedThing = target.Cell.GetFirstThing(parent.pawn.Map, Props.thingDef);
if (spawnedThing != null && Props.alignFaction)
{
AlignThingFaction(spawnedThing);
}
}
/// <summary>
/// 将生成的物品与施法者阵营对齐
/// </summary>
private void AlignThingFaction(Thing spawnedThing)
{
Faction casterFaction = parent.pawn.Faction;
// 处理生物
if (spawnedThing is Pawn spawnedPawn)
{
AlignPawnFaction(spawnedPawn, casterFaction);
}
// 处理建筑
else if (spawnedThing is Building building)
{
AlignBuildingFaction(building, casterFaction);
}
// 处理其他有阵营的物品
else
{
AlignThingWithCompsFaction(spawnedThing, casterFaction);
}
}
/// <summary>
/// 对齐生物阵营
/// </summary>
private void AlignPawnFaction(Pawn pawn, Faction casterFaction)
{
// 设置生物阵营
if (pawn.Faction != casterFaction)
{
pawn.SetFaction(casterFaction);
}
// 如果是野生动物,尝试驯服
if (pawn.Faction == null && pawn.RaceProps.Animal && casterFaction == Faction.OfPlayer)
{
pawn.SetFaction(casterFaction);
}
}
/// <summary>
/// 对齐建筑阵营
/// </summary>
private void AlignThingWithCompsFaction(Thing thing, Faction casterFaction)
{
if (thing.Faction != casterFaction)
{
thing.SetFaction(casterFaction);
}
}
/// <summary>
/// 对齐建筑阵营
/// </summary>
private void AlignBuildingFaction(Building building, Faction casterFaction)
{
if (building.Faction != casterFaction)
{
building.SetFaction(casterFaction);
}
}
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
{
// 先调用基类的验证
if (!base.Valid(target, throwMessages))
{
return false;
}
// 额外的阵营检查
if (Props.alignFaction && parent.pawn.Faction == null)
{
if (throwMessages)
{
Messages.Message("CannotSpawnAlignedWithoutFaction".Translate(),
parent.pawn, MessageTypeDefOf.RejectInput, false);
}
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_AbilitySpawnAligned : CompProperties_AbilitySpawn
{
// 是否将生成的物品与施法者阵营对齐
public bool alignFaction = true;
public CompProperties_AbilitySpawnAligned()
{
compClass = typeof(CompAbilityEffect_SpawnAligned);
}
}
}

View File

@@ -0,0 +1,229 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class CompAbilityEffect_TeleportSelf : CompAbilityEffect
{
public static string SkipUsedSignalTag = "CompAbilityEffect.SkipUsed";
public new CompProperties_AbilityTeleportSelf Props => (CompProperties_AbilityTeleportSelf)props;
public override IEnumerable<PreCastAction> GetPreCastActions()
{
yield return new PreCastAction
{
action = delegate(LocalTargetInfo target, LocalTargetInfo dest)
{
Pawn caster = parent.pawn;
Map map = caster.Map;
// 使用自定义或默认的入口特效
if (Props.customEntryFleck != null)
{
// 自定义入口粒子效果
FleckMaker.Static(caster.Position, map, Props.customEntryFleck);
}
else
{
// 默认入口粒子效果
FleckMaker.Static(caster.Position, map, FleckDefOf.PsycastSkipFlashEntry);
}
// 使用自定义或默认的出口特效
if (Props.customExitFleck != null)
{
// 自定义出口粒子效果
FleckMaker.Static(target.Cell, map, Props.customExitFleck);
// 如果需要更大的效果,可以创建多个粒子
if (Props.effectScale > 1.5f)
{
for (int i = 0; i < Mathf.FloorToInt(Props.effectScale); i++)
{
Vector3 offset = new Vector3(Rand.Range(-0.5f, 0.5f), 0f, Rand.Range(-0.5f, 0.5f));
FleckMaker.Static(target.Cell.ToVector3Shifted() + offset, map, Props.customExitFleck);
}
}
}
else
{
// 默认出口粒子效果
FleckMaker.Static(target.Cell, map, FleckDefOf.PsycastSkipInnerExit);
FleckMaker.Static(target.Cell, map, FleckDefOf.PsycastSkipOuterRingExit);
}
// 播放传送音效
SoundDefOf.Psycast_Skip_Entry.PlayOneShot(new TargetInfo(caster.Position, map));
SoundDefOf.Psycast_Skip_Exit.PlayOneShot(new TargetInfo(target.Cell, map));
},
ticksAwayFromCast = 5
};
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
if (!target.IsValid)
{
return;
}
Pawn caster = parent.pawn;
Map map = caster.Map;
// 使用自定义或默认的入口效果器
EffecterDef entryEffecter = Props.customEntryEffecter ?? EffecterDefOf.Skip_Entry;
Effecter entryEffect = entryEffecter.Spawn(caster, map);
// 应用效果缩放
if (Props.effectScale != 1.0f && entryEffect is Effecter effect)
{
// 这里可以添加效果缩放的逻辑
// 注意Effecter类可能没有直接的缩放属性需要根据具体实现调整
}
parent.AddEffecterToMaintain(entryEffect, caster.Position, 60);
// 使用自定义或默认的出口效果器
EffecterDef exitEffecter = Props.customExitEffecter ?? EffecterDefOf.Skip_Exit;
Effecter exitEffect = exitEffecter.Spawn(target.Cell, map);
parent.AddEffecterToMaintain(exitEffect, target.Cell, 60);
// 唤醒可能休眠的组件
caster.TryGetComp<CompCanBeDormant>()?.WakeUp();
// 执行传送
caster.Position = target.Cell;
caster.Notify_Teleported();
// 如果是玩家阵营,解除战争迷雾
if ((caster.Faction == Faction.OfPlayer || caster.IsPlayerControlled) && caster.Position.Fogged(map))
{
FloodFillerFog.FloodUnfog(caster.Position, map);
}
// 传送后眩晕
caster.stances.stunner.StunFor(Props.stunTicks.RandomInRange, caster, addBattleLog: false, showMote: false);
// 发送传送信号
SendSkipUsedSignal(caster.Position, caster);
// 播放到达时的喧嚣效果
if (Props.destClamorType != null)
{
// 根据效果缩放调整喧嚣半径
float adjustedRadius = Props.destClamorRadius * Props.effectScale;
GenClamor.DoClamor(caster, target.Cell, adjustedRadius, Props.destClamorType);
}
}
public override bool Valid(LocalTargetInfo target, bool showMessages = true)
{
// 检查目的地是否有效
if (!CanTeleportTo(target.Cell, parent.pawn.Map))
{
if (showMessages)
{
Messages.Message("CannotTeleportToLocation".Translate(),
new LookTargets(target.Cell, parent.pawn.Map),
MessageTypeDefOf.RejectInput);
}
return false;
}
return base.Valid(target, showMessages);
}
/// <summary>
/// 检查是否可以命中目标
/// </summary>
public bool CanHitTarget(LocalTargetInfo target)
{
// 检查是否在范围内
if (Props.range > 0f && target.Cell.DistanceTo(parent.pawn.Position) > Props.range)
{
return false;
}
// 检查视线(如果需要)
if (Props.requireLineOfSight && !GenSight.LineOfSight(parent.pawn.Position, target.Cell, parent.pawn.Map))
{
return false;
}
// 检查是否可以传送到该位置
return CanTeleportTo(target.Cell, parent.pawn.Map);
}
/// <summary>
/// 检查是否可以传送到指定位置
/// </summary>
private bool CanTeleportTo(IntVec3 cell, Map map)
{
if (!cell.InBounds(map))
return false;
// 检查战争迷雾
if (!Props.canTeleportToFogged && cell.Fogged(map))
return false;
// 检查屋顶
if (!Props.canTeleportToRoofed && map.roofGrid.Roofed(cell))
return false;
// 检查是否可站立
if (!cell.Standable(map))
return false;
// 检查是否有障碍物
Building edifice = cell.GetEdifice(map);
if (edifice != null && edifice.def.surfaceType != SurfaceType.Item &&
edifice.def.surfaceType != SurfaceType.Eat && !(edifice is Building_Door { Open: not false }))
{
return false;
}
// 检查是否有物品阻挡
List<Thing> thingList = cell.GetThingList(map);
for (int i = 0; i < thingList.Count; i++)
{
if (thingList[i].def.category == ThingCategory.Item)
{
return false;
}
}
return true;
}
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
{
if (!CanHitTarget(target))
{
return "CannotTeleportToLocation".Translate();
}
return base.ExtraLabelMouseAttachment(target);
}
public override void DrawEffectPreview(LocalTargetInfo target)
{
// 绘制传送目的地的预览
GenDraw.DrawTargetHighlight(target);
// 绘制传送范围
if (Props.range > 0)
{
GenDraw.DrawRadiusRing(parent.pawn.Position, Props.range);
}
}
public static void SendSkipUsedSignal(LocalTargetInfo target, Thing initiator)
{
Find.SignalManager.SendSignal(new Signal(SkipUsedSignalTag, target.Named("POSITION"), initiator.Named("SUBJECT")));
}
}
}

View File

@@ -0,0 +1,33 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_AbilityTeleportSelf : CompProperties_AbilityEffect
{
public float range = 12f;
public IntRange stunTicks = new IntRange(30, 60);
public float maxBodySize = 2f;
// 到达时的喧嚣效果
public ClamorDef destClamorType;
public float destClamorRadius = 2f;
// 传送限制
public bool requireLineOfSight = true;
public bool canTeleportToFogged = true;
public bool canTeleportToRoofed = true;
// 自定义效果器 - 为大型生物设计
public EffecterDef customEntryEffecter;
public EffecterDef customExitEffecter;
public FleckDef customEntryFleck;
public FleckDef customExitFleck;
public float effectScale = 1.0f; // 效果缩放比例
public CompProperties_AbilityTeleportSelf()
{
compClass = typeof(CompAbilityEffect_TeleportSelf);
}
}
}

View File

@@ -0,0 +1,206 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class CompAreaDamage : ThingComp
{
private int ticksUntilNextDamage;
public CompProperties_AreaDamage Props => (CompProperties_AreaDamage)props;
public override void Initialize(CompProperties props)
{
base.Initialize(props);
ticksUntilNextDamage = Props.damageIntervalTicks;
}
public override void CompTick()
{
base.CompTick();
if (!parent.Spawned)
return;
ticksUntilNextDamage--;
if (ticksUntilNextDamage <= 0)
{
DoAreaDamage();
ticksUntilNextDamage = Props.damageIntervalTicks;
}
}
private void DoAreaDamage()
{
Map map = parent.Map;
if (map == null)
return;
// 获取范围内的所有物体
List<Thing> thingsInRange = new List<Thing>();
foreach (IntVec3 cell in GenRadial.RadialCellsAround(parent.Position, Props.radius, true))
{
if (!cell.InBounds(map))
continue;
List<Thing> thingList = cell.GetThingList(map);
foreach (Thing thing in thingList)
{
if (IsValidTarget(thing) && !thingsInRange.Contains(thing))
{
thingsInRange.Add(thing);
}
}
}
// 对每个有效目标造成伤害
foreach (Thing target in thingsInRange)
{
ApplyDamageToTarget(target);
}
}
private bool IsValidTarget(Thing thing)
{
// 检查是否有生命值
if (thing.def.useHitPoints == false || thing.HitPoints <= 0)
return false;
// 检查物体类型过滤
if (thing is Building && !Props.affectBuildings)
return false;
if (thing is Pawn && !Props.affectPawns)
return false;
if (thing is Plant && !Props.affectPlants)
return false;
// 检查阵营关系(如果是生物)
if (thing is Pawn pawn)
{
Faction targetFaction = pawn.Faction;
Faction parentFaction = parent.Faction;
if (targetFaction == null)
{
if (!Props.affectNeutral)
return false;
}
else if (targetFaction == parentFaction)
{
if (!Props.affectFriendly)
return false;
}
else if (targetFaction.HostileTo(parentFaction))
{
if (!Props.affectHostile)
return false;
}
else
{
if (!Props.affectNeutral)
return false;
}
}
return true;
}
private void ApplyDamageToTarget(Thing target)
{
if (Props.damageDef == null)
return;
// 计算最终伤害量(应用缩放)
int finalDamageAmount = CalculateFinalDamage(target);
// 创建伤害信息
DamageInfo damageInfo = new DamageInfo(
Props.damageDef,
finalDamageAmount,
armorPenetration: 0f,
instigator: parent,
hitPart: null,
weapon: null,
category: DamageInfo.SourceCategory.ThingOrUnknown
);
// 应用伤害
target.TakeDamage(damageInfo);
}
/// <summary>
/// 计算最终伤害量,应用心灵敏感度缩放和保底伤害
/// </summary>
private int CalculateFinalDamage(Thing target)
{
float damageFactor = 1.0f;
// 使用固定缩放值
if (Props.useFixedScaling)
{
damageFactor = Props.fixedDamageFactor;
}
// 使用心灵敏感度缩放
else if (Props.scaleWithPsychicSensitivity && target is Pawn pawn)
{
damageFactor = CalculatePsychicSensitivityFactor(pawn);
}
// 确保伤害倍率在最小和最大范围内
damageFactor = Mathf.Clamp(damageFactor, Props.minDamageFactor, Props.maxDamageFactor);
// 计算最终伤害
int finalDamage = Mathf.RoundToInt(Props.damageAmount * damageFactor);
// 确保至少造成1点伤害
return Mathf.Max(1, finalDamage);
}
/// <summary>
/// 根据目标的心灵敏感度计算伤害倍率
/// </summary>
private float CalculatePsychicSensitivityFactor(Pawn targetPawn)
{
// 获取心灵敏感度如果目标没有心灵敏感度使用默认值0.5
float psychicSensitivity = 0.5f;
if (targetPawn.health != null && targetPawn.health.capacities != null)
{
psychicSensitivity = targetPawn.GetStatValue(StatDefOf.PsychicSensitivity);
}
// 返回心灵敏感度作为伤害倍率
// 例如敏感度0.5 = 0.5倍伤害敏感度1.5 = 1.5倍伤害
return psychicSensitivity;
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ticksUntilNextDamage, "ticksUntilNextDamage", Props.damageIntervalTicks);
}
/// <summary>
/// 调试方法:显示伤害计算信息
/// </summary>
public string GetDamageDebugInfo(Thing target)
{
if (target is Pawn pawn)
{
float psychicSensitivity = pawn.GetStatValue(StatDefOf.PsychicSensitivity);
float damageFactor = CalculatePsychicSensitivityFactor(pawn);
int finalDamage = CalculateFinalDamage(target);
return $"目标: {pawn.Label}\n" +
$"心灵敏感度: {psychicSensitivity:F2}\n" +
$"伤害倍率: {damageFactor:F2}\n" +
$"基础伤害: {Props.damageAmount}\n" +
$"最终伤害: {finalDamage}";
}
return $"目标: {target.Label}\n基础伤害: {Props.damageAmount}";
}
}
}

View File

@@ -0,0 +1,33 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_AreaDamage : CompProperties
{
public float radius = 5f; // A: 伤害半径
public int damageIntervalTicks = 60; // B: 伤害间隔(帧数)
public DamageDef damageDef; // C: 伤害类型
public int damageAmount = 10; // 基础伤害量
// 伤害缩放设置
public bool scaleWithPsychicSensitivity = false; // 是否随心灵敏感度缩放
public float minDamageFactor = 0.5f; // 最低伤害倍率0.0-1.0
public float maxDamageFactor = 2.0f; // 最高伤害倍率
public bool useFixedScaling = false; // 是否使用固定缩放值
public float fixedDamageFactor = 1.0f; // 固定伤害倍率
// 目标过滤
public bool affectFriendly = false; // 是否影响友方
public bool affectNeutral = true; // 是否影响中立
public bool affectHostile = true; // 是否影响敌方
public bool affectBuildings = true; // 是否影响建筑
public bool affectPawns = true; // 是否影响生物
public bool affectPlants = false; // 是否影响植物
public CompProperties_AreaDamage()
{
compClass = typeof(CompAreaDamage);
}
}
}

View File

@@ -88,6 +88,10 @@
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLance.cs" />
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLanceExtension.cs" />
<Compile Include="Ability\WULA_AbilityCallSkyfaller\CompAbilityEffect_CallSkyfaller.cs" />
<Compile Include="Ability\WULA_AbilitySpawnAligned\CompAbilityEffect_SpawnAligned.cs" />
<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="BuildingComp\Building_ExtraGraphics.cs" />
<Compile Include="BuildingComp\Building_MapObserver.cs" />
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompBuildingBombardment.cs" />
@@ -224,6 +228,8 @@
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.cs" />
<Compile Include="ThingComp\Comp_WeaponRenderDynamic.cs" />
<Compile Include="ThingComp\WULA_AreaDamage\CompAreaDamage.cs" />
<Compile Include="ThingComp\WULA_AreaDamage\CompProperties_AreaDamage.cs" />
<Compile Include="ThingComp\WULA_AreaShield\AreaShieldManager.cs" />
<Compile Include="ThingComp\WULA_AreaShield\CompProperties_AreaShield.cs" />
<Compile Include="ThingComp\WULA_AreaShield\Gizmo_AreaShieldStatus.cs" />