This commit is contained in:
2025-11-25 13:57:57 +08:00
40 changed files with 1973 additions and 711 deletions

View File

@@ -3,12 +3,12 @@ using RimWorld;
namespace WulaFallenEmpire
{
public abstract class Condition
public abstract class ConditionBase
{
public abstract bool IsMet(out string reason);
}
public class Condition_VariableEquals : Condition
public class Condition_VariableEquals : ConditionBase
{
public string name;
public string value;
@@ -75,7 +75,7 @@ namespace WulaFallenEmpire
}
}
public abstract class Condition_CompareVariable : Condition
public abstract class Condition_CompareVariable : ConditionBase
{
public string name;
public float value;
@@ -145,7 +145,7 @@ namespace WulaFallenEmpire
protected override string GetOperatorString() => "<=";
}
public class Condition_VariableNotEqual : Condition
public class Condition_VariableNotEqual : ConditionBase
{
public string name;
public string value;
@@ -213,7 +213,7 @@ namespace WulaFallenEmpire
}
}
public class Condition_FactionExists : Condition
public class Condition_FactionExists : ConditionBase
{
public FactionDef factionDef;

View File

@@ -0,0 +1,83 @@
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
public abstract class Condition
{
public abstract bool IsMet(out string reason);
}
public class Condition_FlagExists : ConditionBase
{
public string flagName;
public override bool IsMet(out string reason)
{
if (string.IsNullOrEmpty(flagName))
{
reason = "Flag name is not specified.";
return false;
}
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
bool flagExists = eventVarManager.HasFlag(flagName);
if (!flagExists)
{
reason = $"Flag '{flagName}' does not exist or has expired.";
}
else
{
int remainingTicks = eventVarManager.GetFlagRemainingTicks(flagName);
if (remainingTicks < 0)
{
reason = $"Flag '{flagName}' exists (permanent).";
}
else
{
reason = $"Flag '{flagName}' exists (expires in {remainingTicks} ticks).";
}
}
Log.Message($"[EventSystem] Condition_FlagExists check: Flag='{flagName}', Exists={flagExists}, Reason='{reason}'");
return flagExists;
}
}
public class Condition_FlagNotExists : ConditionBase
{
public string flagName;
public override bool IsMet(out string reason)
{
if (string.IsNullOrEmpty(flagName))
{
reason = "Flag name is not specified.";
return false;
}
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
bool flagExists = eventVarManager.HasFlag(flagName);
if (flagExists)
{
int remainingTicks = eventVarManager.GetFlagRemainingTicks(flagName);
if (remainingTicks < 0)
{
reason = $"Flag '{flagName}' exists (permanent).";
}
else
{
reason = $"Flag '{flagName}' exists (expires in {remainingTicks} ticks).";
}
return false;
}
else
{
reason = $"Flag '{flagName}' does not exist.";
return true;
}
}
}
}

View File

@@ -572,7 +572,7 @@ namespace WulaFallenEmpire
}
}
private bool AreConditionsMet(List<Condition> conditions, out string reason)
private bool AreConditionsMet(List<ConditionBase> conditions, out string reason)
{
reason = "";
if (conditions.NullOrEmpty())

View File

@@ -68,7 +68,7 @@ namespace WulaFallenEmpire
}
}
private bool AreConditionsMet(List<Condition> conditions, out string reason)
private bool AreConditionsMet(List<ConditionBase> conditions, out string reason)
{
reason = "";
if (conditions.NullOrEmpty())

View File

@@ -0,0 +1,27 @@
// 在 EffectBase.cs 中添加以下类
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
public class Effect_SetTimedFlag : EffectBase
{
public string flagName;
public int durationTicks; // 持续时间tick负数表示永久
public override void Execute(Window dialog = null)
{
if (string.IsNullOrEmpty(flagName))
{
Log.Error("[WulaFallenEmpire] Effect_SetTimedFlag has a null or empty flagName.");
return;
}
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
eventVarManager.SetTimedFlag(flagName, durationTicks);
string durationInfo = durationTicks < 0 ? "permanent" : $"{durationTicks} ticks";
Log.Message($"[EventSystem] Set timed flag '{flagName}' with duration: {durationInfo}");
}
}
}

View File

@@ -71,7 +71,7 @@ namespace WulaFallenEmpire
{
[MustTranslate]
public string label;
public List<Condition> conditions;
public List<ConditionBase> conditions;
[MustTranslate]
public string disabledReason;
public bool hideWhenDisabled = true;
@@ -102,7 +102,7 @@ namespace WulaFallenEmpire
public class ConditionalEffects
{
public List<Condition> conditions;
public List<ConditionBase> conditions;
public List<EffectBase> effects;
public List<EffectBase> randomlistEffects;
public List<LoopEffects> loopEffects;
@@ -164,7 +164,7 @@ namespace WulaFallenEmpire
public class ConditionalDescription
{
public List<Condition> conditions;
public List<ConditionBase> conditions;
[MustTranslate]
public string text;
}

View File

@@ -12,12 +12,17 @@ namespace WulaFallenEmpire
private Dictionary<string, string> stringVars = new Dictionary<string, string>();
private Dictionary<string, Pawn> pawnVars = new Dictionary<string, Pawn>();
private Dictionary<string, List<Pawn>> pawnListVars = new Dictionary<string, List<Pawn>>();
// 新增有时限的flag字典
private Dictionary<string, int> timedFlags = new Dictionary<string, int>();
// 用于Scribe的辅助列表
private List<string> pawnVarKeys;
private List<Pawn> pawnVarValues;
private List<string> pawnListVarKeys;
private List<List<Pawn>> pawnListVarValues;
private List<string> timedFlagKeys;
private List<int> timedFlagValues;
// Required for WorldComponent
public EventVariableManager(World world) : base(world)
@@ -32,6 +37,7 @@ namespace WulaFallenEmpire
Scribe_Collections.Look(ref stringVars, "stringVars", LookMode.Value, LookMode.Value);
Scribe_Collections.Look(ref pawnVars, "pawnVars", LookMode.Value, LookMode.Reference, ref pawnVarKeys, ref pawnVarValues);
Scribe_Collections.Look(ref pawnListVars, "pawnListVars", LookMode.Value, LookMode.Reference, ref pawnListVarKeys, ref pawnListVarValues);
Scribe_Collections.Look(ref timedFlags, "timedFlags", LookMode.Value, LookMode.Value, ref timedFlagKeys, ref timedFlagValues);
// Ensure dictionaries are not null after loading
if (Scribe.mode == LoadSaveMode.PostLoadInit)
@@ -41,6 +47,43 @@ namespace WulaFallenEmpire
stringVars ??= new Dictionary<string, string>();
pawnVars ??= new Dictionary<string, Pawn>();
pawnListVars ??= new Dictionary<string, List<Pawn>>();
timedFlags ??= new Dictionary<string, int>();
}
}
public override void WorldComponentTick()
{
base.WorldComponentTick();
// 每60 tick检查一次过期flag
if (Find.TickManager.TicksGame % 60 == 0)
{
CheckExpiredFlags();
}
}
/// <summary>
/// 检查并清理过期的flag
/// </summary>
private void CheckExpiredFlags()
{
List<string> flagsToRemove = new List<string>();
int currentTick = Find.TickManager.TicksGame;
foreach (var kvp in timedFlags)
{
// 如果flag的过期时间不为负数且小于当前tick则标记为需要移除
if (kvp.Value >= 0 && currentTick >= kvp.Value)
{
flagsToRemove.Add(kvp.Key);
Log.Message($"[EventSystem] Flag '{kvp.Key}' expired and will be removed.");
}
}
// 移除过期的flag
foreach (string flagName in flagsToRemove)
{
timedFlags.Remove(flagName);
}
}
@@ -81,6 +124,80 @@ namespace WulaFallenEmpire
}
}
/// <summary>
/// 设置有时限的flag
/// </summary>
/// <param name="flagName">flag名称</param>
/// <param name="durationTicks">持续时间tick负数表示永久</param>
public void SetTimedFlag(string flagName, int durationTicks)
{
if (string.IsNullOrEmpty(flagName)) return;
int expiryTick;
if (durationTicks < 0)
{
// 负数表示永久flag
expiryTick = -1;
Log.Message($"[EventSystem] Setting permanent flag '{flagName}'.");
}
else
{
// 正数表示有时间限制的flag
expiryTick = Find.TickManager.TicksGame + durationTicks;
Log.Message($"[EventSystem] Setting timed flag '{flagName}' with duration {durationTicks} ticks (expires at tick {expiryTick}).");
}
timedFlags[flagName] = expiryTick;
}
/// <summary>
/// 检查flag是否存在且未过期
/// </summary>
public bool HasFlag(string flagName)
{
if (string.IsNullOrEmpty(flagName)) return false;
if (timedFlags.TryGetValue(flagName, out int expiryTick))
{
if (expiryTick < 0)
{
// 永久flag
return true;
}
else
{
// 检查是否过期
bool isActive = Find.TickManager.TicksGame < expiryTick;
if (!isActive)
{
// 如果过期了,移除它
timedFlags.Remove(flagName);
Log.Message($"[EventSystem] Flag '{flagName}' has expired and was removed.");
}
return isActive;
}
}
return false;
}
/// <summary>
/// 获取flag的剩余时间tick
/// </summary>
public int GetFlagRemainingTicks(string flagName)
{
if (string.IsNullOrEmpty(flagName) || !timedFlags.TryGetValue(flagName, out int expiryTick))
return 0;
if (expiryTick < 0)
{
// 永久flag
return -1;
}
int remaining = expiryTick - Find.TickManager.TicksGame;
return remaining > 0 ? remaining : 0;
}
public T GetVariable<T>(string name, T defaultValue = default)
{
if (string.IsNullOrEmpty(name)) return defaultValue;
@@ -138,7 +255,8 @@ namespace WulaFallenEmpire
floatVars.ContainsKey(name) ||
stringVars.ContainsKey(name) ||
pawnVars.ContainsKey(name) ||
pawnListVars.ContainsKey(name);
pawnListVars.ContainsKey(name) ||
timedFlags.ContainsKey(name);
}
public void ClearVariable(string name)
@@ -152,6 +270,7 @@ namespace WulaFallenEmpire
stringVars.Remove(name);
pawnVars.Remove(name);
pawnListVars.Remove(name);
timedFlags.Remove(name);
}
public void ClearAll()
@@ -161,6 +280,7 @@ namespace WulaFallenEmpire
stringVars.Clear();
pawnVars.Clear();
pawnListVars.Clear();
timedFlags.Clear();
}
public Dictionary<string, object> GetAllVariables()
@@ -171,7 +291,8 @@ namespace WulaFallenEmpire
foreach (var kvp in stringVars) allVars[kvp.Key] = kvp.Value;
foreach (var kvp in pawnVars) allVars[kvp.Key] = kvp.Value;
foreach (var kvp in pawnListVars) allVars[kvp.Key] = kvp.Value;
foreach (var kvp in timedFlags) allVars[kvp.Key] = $"Flag (expires: {kvp.Value})";
return allVars;
}
}
}
}

View File

@@ -24,20 +24,27 @@ namespace WulaFallenEmpire
// 目标跟踪
private List<IntVec3> previousTargets = new List<IntVec3>();
// 新增:微追踪目标列表
private List<LocalTargetInfo> microTrackingTargets = new List<LocalTargetInfo>();
private List<float> microTrackingWeights = new List<float>(); // 新增:权重列表
// 优化:缓存目标列表,避免每帧重新计算
private List<LocalTargetInfo> cachedTargets = new List<LocalTargetInfo>();
private List<float> cachedTargetWeights = new List<float>();
private int lastTargetUpdateTick = -9999;
private const int TARGET_UPDATE_INTERVAL = 60; // 每60 ticks更新一次目标列表
// 新增:目标类型权重配置
private const float PAWN_WEIGHT = 5.0f; // Pawn权重5倍
private const float OWNED_BUILDING_WEIGHT = 1.0f; // 有主建筑权重1倍
private const float UNOWNED_BUILDING_WEIGHT = 0.01f; // 无主建筑权重0.01倍
private const float OTHER_WEIGHT = 1.0f; // 其他目标权重1倍
// 优化:一轮炮击的目标缓存
private IntVec3 currentVolleyCenter;
private List<IntVec3> currentVolleyTargets = new List<IntVec3>();
private int currentVolleyIndex = 0;
// 目标类型权重配置
private const float PAWN_WEIGHT = 5.0f;
private const float OWNED_BUILDING_WEIGHT = 1.0f;
private const float UNOWNED_BUILDING_WEIGHT = 0.01f;
private const float WALL_WEIGHT = 0.001f; // 墙的权重极低
private const float OTHER_WEIGHT = 1.0f;
public override void Initialize(CompProperties props)
{
base.Initialize(props);
ticksUntilNextAttack = Props.ticksBetweenAttacks;
Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius");
@@ -51,10 +58,14 @@ namespace WulaFallenEmpire
if (parent is not FlyOver flyOver || !flyOver.Spawned || flyOver.Map == null)
return;
// 更新微追踪目标列表(如果需要)
// 优化:减少目标更新频率
if (Props.useMicroTracking && Props.useFactionDiscrimination)
{
UpdateMicroTrackingTargets(flyOver);
if (Find.TickManager.TicksGame - lastTargetUpdateTick > TARGET_UPDATE_INTERVAL)
{
UpdateTargetCache(flyOver);
lastTargetUpdateTick = Find.TickManager.TicksGame;
}
}
// 更新预热状态
@@ -82,103 +93,99 @@ namespace WulaFallenEmpire
}
}
// 新增:更新微追踪目标列表
private void UpdateMicroTrackingTargets(FlyOver flyOver)
// 优化:缓存目标列表
private void UpdateTargetCache(FlyOver flyOver)
{
microTrackingTargets.Clear();
microTrackingWeights.Clear();
cachedTargets.Clear();
cachedTargetWeights.Clear();
Faction targetFaction = GetTargetFaction(flyOver);
if (targetFaction == null) return;
// 获取飞越物体当前位置
IntVec3 center = GetFlyOverPosition(flyOver);
// 搜索范围内的所有潜在目标
foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, Props.attackRadius, true))
// 优化:使用更高效的目标搜索
var potentialTargets = GenRadial.RadialDistinctThingsAround(center, flyOver.Map, Props.attackRadius, true)
.Where(thing => IsValidMicroTrackingTarget(thing, targetFaction))
.Distinct(); // 避免重复
foreach (Thing thing in potentialTargets)
{
if (!cell.InBounds(flyOver.Map)) continue;
// 检查建筑
Building building = cell.GetEdifice(flyOver.Map);
if (building != null && IsValidMicroTrackingTarget(building, targetFaction))
{
microTrackingTargets.Add(new LocalTargetInfo(building));
float weight = GetTargetWeight(building);
microTrackingWeights.Add(weight);
}
// 检查生物
List<Thing> thingList = cell.GetThingList(flyOver.Map);
foreach (Thing thing in thingList)
{
if (thing is Pawn pawn && IsValidMicroTrackingTarget(pawn, targetFaction))
{
microTrackingTargets.Add(new LocalTargetInfo(pawn));
float weight = GetTargetWeight(pawn);
microTrackingWeights.Add(weight);
}
}
cachedTargets.Add(new LocalTargetInfo(thing));
cachedTargetWeights.Add(GetTargetWeight(thing));
}
// 移除重复目标(基于位置)
for (int i = microTrackingTargets.Count - 1; i >= 0; i--)
if (DebugSettings.godMode && cachedTargets.Count > 0)
{
for (int j = 0; j < i; j++)
{
if (microTrackingTargets[i].Cell == microTrackingTargets[j].Cell)
{
microTrackingTargets.RemoveAt(i);
microTrackingWeights.RemoveAt(i);
break;
}
}
}
if (DebugSettings.godMode)
{
Log.Message($"MicroTracking: Found {microTrackingTargets.Count} targets for faction {targetFaction.def.defName}");
// 输出目标统计信息
var targetStats = GetTargetStatistics();
Log.Message($"Target Statistics - Pawns: {targetStats.pawnCount}, Owned Buildings: {targetStats.ownedBuildingCount}, Unowned Buildings: {targetStats.unownedBuildingCount}, Others: {targetStats.otherCount}");
Log.Message($"Target Cache Updated: Found {cachedTargets.Count} targets");
var stats = GetTargetStatistics();
Log.Message($"Target Statistics - Pawns: {stats.pawnCount}, Owned Buildings: {stats.ownedBuildingCount}, Unowned Buildings: {stats.unownedBuildingCount}, Walls: {stats.wallCount}, Others: {stats.otherCount}");
}
}
// 新增:获取目标权重
// 优化:改进的目标有效性检查
private bool IsValidMicroTrackingTarget(Thing thing, Faction targetFaction)
{
if (thing == null || thing.Destroyed) return false;
// 修复1无主建筑总是被排除
if (thing is Building building && building.Faction == null)
return false;
// 修复2isWall的建筑总是不考虑
if (thing.def?.building?.isWall == true)
return false;
// 检查派系关系
if (thing.Faction != null)
{
if (thing.Faction == targetFaction) return false;
if (thing.Faction.RelationKindWith(targetFaction) == FactionRelationKind.Ally) return false;
}
// 检查保护范围
if (Props.avoidPlayerAssets && IsNearPlayerAssets(thing.Position, thing.Map))
return false;
// 避免击中飞越物体本身
if (Props.avoidHittingFlyOver && thing.Position.DistanceTo(parent.Position) < 10f)
return false;
return true;
}
// 优化:获取目标权重
private float GetTargetWeight(Thing thing)
{
if (thing is Pawn)
{
return PAWN_WEIGHT;
}
else if (thing is Building building)
{
// 修复2墙的权重极低
if (building.def?.building?.isWall == true)
return WALL_WEIGHT;
if (building.Faction == null)
{
return UNOWNED_BUILDING_WEIGHT;
}
else
{
return OWNED_BUILDING_WEIGHT;
}
}
else
{
return OTHER_WEIGHT;
}
}
// 新增:获取目标统计信息
private (int pawnCount, int ownedBuildingCount, int unownedBuildingCount, int otherCount) GetTargetStatistics()
private (int pawnCount, int ownedBuildingCount, int unownedBuildingCount, int wallCount, int otherCount) GetTargetStatistics()
{
int pawnCount = 0;
int ownedBuildingCount = 0;
int unownedBuildingCount = 0;
int wallCount = 0;
int otherCount = 0;
for (int i = 0; i < microTrackingTargets.Count; i++)
foreach (var target in cachedTargets)
{
Thing thing = microTrackingTargets[i].Thing;
Thing thing = target.Thing;
if (thing == null) continue;
if (thing is Pawn)
@@ -187,7 +194,11 @@ namespace WulaFallenEmpire
}
else if (thing is Building building)
{
if (building.Faction == null)
if (building.def?.building?.isWall == true)
{
wallCount++;
}
else if (building.Faction == null)
{
unownedBuildingCount++;
}
@@ -202,50 +213,20 @@ namespace WulaFallenEmpire
}
}
return (pawnCount, ownedBuildingCount, unownedBuildingCount, otherCount);
return (pawnCount, ownedBuildingCount, unownedBuildingCount, wallCount, otherCount);
}
// 新增:检查是否为有效的微追踪目标
private bool IsValidMicroTrackingTarget(Thing thing, Faction targetFaction)
{
if (thing == null || thing.Destroyed) return false;
// 检查派系关系:目标派系的友军不应该被攻击
if (thing.Faction != null)
{
if (thing.Faction == targetFaction) return false;
if (thing.Faction.RelationKindWith(targetFaction) == FactionRelationKind.Ally) return false;
}
// 检查是否在保护范围内
if (Props.avoidPlayerAssets && IsNearPlayerAssets(thing.Position, thing.Map))
{
return false;
}
// 避免击中飞越物体本身
if (Props.avoidHittingFlyOver && thing.Position.DistanceTo(parent.Position) < 10f)
{
return false;
}
return true;
}
// 新增:获取目标派系
private Faction GetTargetFaction(FlyOver flyOver)
{
if (!Props.useFactionDiscrimination)
return null;
// 如果指定了目标派系,使用指定的派系
if (Props.targetFaction != null)
{
Faction faction = Find.FactionManager.FirstFactionOfDef(Props.targetFaction);
if (faction != null) return faction;
}
// 否则使用玩家当前派系
return Faction.OfPlayer;
}
@@ -266,11 +247,15 @@ namespace WulaFallenEmpire
Log.Message($"Ship Artillery starting attack on target area: {currentTarget} (attack radius: {Props.attackRadius})");
// 开始预热
// 修复3在一轮炮击中只进行一次目标选择
currentVolleyCenter = currentTarget;
currentVolleyTargets.Clear();
currentVolleyIndex = 0;
// 预热阶段
isWarmingUp = true;
warmupTicksRemaining = Props.warmupTicks;
// 启动预热效果
if (Props.warmupEffect != null)
{
warmupEffecter = Props.warmupEffect.Spawn();
@@ -282,19 +267,16 @@ namespace WulaFallenEmpire
{
warmupTicksRemaining--;
// 维持预热效果
if (warmupEffecter != null)
{
warmupEffecter.EffectTick(new TargetInfo(currentTarget, flyOver.Map), new TargetInfo(currentTarget, flyOver.Map));
}
// 生成预热粒子
if (Props.warmupFleck != null && Rand.MTBEventOccurs(0.1f, 1f, 1f))
{
FleckMaker.Static(currentTarget.ToVector3Shifted(), flyOver.Map, Props.warmupFleck);
}
// 预热完成,开始攻击
if (warmupTicksRemaining <= 0)
{
StartFiring(flyOver);
@@ -307,11 +289,9 @@ namespace WulaFallenEmpire
isAttacking = true;
attackTicksRemaining = Props.attackDurationTicks;
// 清理预热效果
warmupEffecter?.Cleanup();
warmupEffecter = null;
// 启动攻击效果
if (Props.attackEffect != null)
{
attackEffecter = Props.attackEffect.Spawn();
@@ -333,26 +313,23 @@ namespace WulaFallenEmpire
{
attackTicksRemaining--;
// 维持攻击效果
if (attackEffecter != null)
{
attackEffecter.EffectTick(new TargetInfo(currentTarget, flyOver.Map), new TargetInfo(currentTarget, flyOver.Map));
}
// 在攻击期间定期发射炮弹
if (attackTicksRemaining % 60 == 0) // 每秒发射一次
if (attackTicksRemaining % 60 == 0)
{
ExecuteVolley(flyOver);
}
// 生成攻击粒子
if (Props.attackFleck != null && Rand.MTBEventOccurs(0.2f, 1f, 1f))
{
Vector3 randomOffset = new Vector3(Rand.Range(-3f, 3f), 0f, Rand.Range(-3f, 3f));
FleckMaker.Static((currentTarget.ToVector3Shifted() + randomOffset), flyOver.Map, Props.attackFleck);
}
// 攻击结束
if (attackTicksRemaining <= 0)
{
EndAttack(flyOver);
@@ -361,17 +338,66 @@ namespace WulaFallenEmpire
private void ExecuteVolley(FlyOver flyOver)
{
// 修复3为这一轮炮击生成所有目标
if (currentVolleyTargets.Count == 0)
{
GenerateVolleyTargets(flyOver);
}
for (int i = 0; i < Props.shellsPerVolley; i++)
{
FireShell(flyOver);
if (currentVolleyIndex < currentVolleyTargets.Count)
{
FireShell(flyOver, currentVolleyTargets[currentVolleyIndex]);
currentVolleyIndex++;
}
else
{
// 如果目标用完了,重新生成(对于持续攻击)
GenerateVolleyTargets(flyOver);
if (currentVolleyTargets.Count > 0)
{
FireShell(flyOver, currentVolleyTargets[0]);
currentVolleyIndex = 1;
}
}
}
}
private void FireShell(FlyOver flyOver)
// 修复3生成一轮炮击的所有目标
private void GenerateVolleyTargets(FlyOver flyOver)
{
currentVolleyTargets.Clear();
currentVolleyIndex = 0;
for (int i = 0; i < Props.shellsPerVolley * 3; i++) // 生成足够的目标
{
IntVec3 target;
if (Props.useMicroTracking && Props.useFactionDiscrimination && cachedTargets.Count > 0)
{
target = SelectTargetFromCache(flyOver);
}
else
{
target = SelectRandomTargetInRadius(currentVolleyCenter, flyOver.Map, Props.attackRadius);
}
if (target.IsValid && target.InBounds(flyOver.Map))
{
currentVolleyTargets.Add(target);
}
}
if (DebugSettings.godMode)
{
Log.Message($"Generated {currentVolleyTargets.Count} targets for volley around {currentVolleyCenter}");
}
}
private void FireShell(FlyOver flyOver, IntVec3 shellTarget)
{
try
{
// 选择炮弹类型
ThingDef shellDef = SelectShellDef();
if (shellDef == null)
{
@@ -379,24 +405,14 @@ namespace WulaFallenEmpire
return;
}
// 选择目标
IntVec3 shellTarget;
if (Props.useMicroTracking && Props.useFactionDiscrimination && microTrackingTargets.Count > 0)
{
shellTarget = SelectMicroTrackingTarget(flyOver);
}
else
{
shellTarget = SelectRandomTarget(flyOver);
}
// 关键修复:使用 SkyfallerMaker 创建并立即生成 Skyfaller
SkyfallerMaker.SpawnSkyfaller(shellDef, shellTarget, flyOver.Map);
float distanceFromCenter = shellTarget.DistanceTo(currentTarget);
Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1})");
float distanceFromCenter = shellTarget.DistanceTo(currentVolleyCenter);
if (DebugSettings.godMode)
{
Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1})");
}
// 播放音效
if (Props.attackSound != null)
{
Props.attackSound.PlayOneShot(new TargetInfo(shellTarget, flyOver.Map));
@@ -408,16 +424,15 @@ namespace WulaFallenEmpire
}
}
// 修改:微追踪目标选择 - 现在使用权重系统
private IntVec3 SelectMicroTrackingTarget(FlyOver flyOver)
// 优化:从缓存中选择目标
private IntVec3 SelectTargetFromCache(FlyOver flyOver)
{
if (microTrackingTargets.Count == 0)
if (cachedTargets.Count == 0)
{
Log.Warning("MicroTracking: No targets available, falling back to random target");
return SelectRandomTarget(flyOver);
return SelectRandomTargetInRadius(currentVolleyCenter, flyOver.Map, Props.attackRadius);
}
// 使用权重系统选择目标
LocalTargetInfo selectedTarget = SelectTargetByWeight();
IntVec3 targetCell = selectedTarget.Cell;
@@ -429,7 +444,6 @@ namespace WulaFallenEmpire
offsetTarget.x += Mathf.RoundToInt(Mathf.Cos(angle * Mathf.Deg2Rad) * offsetDistance);
offsetTarget.z += Mathf.RoundToInt(Mathf.Sin(angle * Mathf.Deg2Rad) * offsetDistance);
// 确保目标在地图内
if (!offsetTarget.InBounds(flyOver.Map))
{
offsetTarget = targetCell;
@@ -448,37 +462,34 @@ namespace WulaFallenEmpire
return offsetTarget;
}
// 新增:基于权重的目标选择
// 基于权重的目标选择
private LocalTargetInfo SelectTargetByWeight()
{
if (microTrackingTargets.Count == 0)
if (cachedTargets.Count == 0)
return LocalTargetInfo.Invalid;
if (microTrackingTargets.Count == 1)
return microTrackingTargets[0];
if (cachedTargets.Count == 1)
return cachedTargets[0];
// 计算总权重
float totalWeight = 0f;
foreach (float weight in microTrackingWeights)
foreach (float weight in cachedTargetWeights)
{
totalWeight += weight;
}
// 随机选择
float randomValue = Rand.Range(0f, totalWeight);
float currentSum = 0f;
for (int i = 0; i < microTrackingTargets.Count; i++)
for (int i = 0; i < cachedTargets.Count; i++)
{
currentSum += microTrackingWeights[i];
currentSum += cachedTargetWeights[i];
if (randomValue <= currentSum)
{
return microTrackingTargets[i];
return cachedTargets[i];
}
}
// 回退到最后一个目标
return microTrackingTargets[microTrackingTargets.Count - 1];
return cachedTargets[cachedTargets.Count - 1];
}
private ThingDef SelectShellDef()
@@ -512,26 +523,18 @@ namespace WulaFallenEmpire
return launchPos;
}
// 简化的目标选择 - 每次直接随机选择目标
private IntVec3 SelectRandomTarget(FlyOver flyOver)
private IntVec3 SelectTarget(FlyOver flyOver)
{
IntVec3 center = GetFlyOverPosition(flyOver) + Props.targetOffset;
return FindRandomTargetInRadius(center, flyOver.Map, Props.attackRadius);
}
private IntVec3 SelectTarget(FlyOver flyOver)
// 简化的目标选择 - 每次直接随机选择目标
private IntVec3 SelectRandomTargetInRadius(IntVec3 center, Map map, float radius)
{
// 获取飞越物体当前位置作为基础中心
IntVec3 flyOverPos = GetFlyOverPosition(flyOver);
IntVec3 center = flyOverPos + Props.targetOffset;
Log.Message($"FlyOver position: {flyOverPos}, Center for targeting: {center}");
// 在攻击半径内选择随机目标
return FindRandomTargetInRadius(center, flyOver.Map, Props.attackRadius);
return FindRandomTargetInRadius(center, map, radius);
}
// 改进的飞越物体位置获取
private IntVec3 GetFlyOverPosition(FlyOver flyOver)
{
// 优先使用 DrawPos因为它反映实际视觉位置
@@ -554,15 +557,13 @@ namespace WulaFallenEmpire
// 目标查找逻辑 - 基于攻击半径
private IntVec3 FindRandomTargetInRadius(IntVec3 center, Map map, float radius)
{
Log.Message($"Finding target around {center} with radius {radius}");
// 如果半径为0直接返回中心
if (radius <= 0)
return center;
bool ignoreProtectionForThisTarget = Rand.Value < Props.ignoreProtectionChance;
for (int i = 0; i < 30; i++)
// 优化:减少尝试次数
for (int i = 0; i < 15; i++)
{
// 在圆形区域内随机选择
float angle = Rand.Range(0f, 360f);
@@ -582,12 +583,15 @@ namespace WulaFallenEmpire
previousTargets.Add(potentialTarget);
float actualDistance = potentialTarget.DistanceTo(center);
Log.Message($"Found valid target at {potentialTarget} (distance from center: {actualDistance:F1})");
if (ignoreProtectionForThisTarget)
if (DebugSettings.godMode)
{
Log.Warning($"Protection ignored for target selection! May target player assets.");
float actualDistance = potentialTarget.DistanceTo(center);
Log.Message($"Found valid target at {potentialTarget} (distance from center: {actualDistance:F1})");
if (ignoreProtectionForThisTarget)
{
Log.Warning($"Protection ignored for target selection! May target player assets.");
}
}
return potentialTarget;
@@ -598,7 +602,7 @@ namespace WulaFallenEmpire
// 回退:使用地图随机位置
Log.Warning("Could not find valid target in radius, using fallback");
CellRect mapRect = CellRect.WholeMap(map);
for (int i = 0; i < 10; i++)
for (int i = 0; i < 5; i++)
{
IntVec3 fallbackTarget = mapRect.RandomCell;
if (IsValidTarget(fallbackTarget, map, ignoreProtectionForThisTarget))
@@ -731,6 +735,10 @@ namespace WulaFallenEmpire
attackEffecter?.Cleanup();
attackEffecter = null;
// 清理缓存
currentVolleyTargets.Clear();
currentVolleyIndex = 0;
// 重置计时器
if (Props.continuousAttack && !flyOver.hasCompleted)
{
@@ -773,9 +781,13 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref isAttacking, "isAttacking", false);
Scribe_Values.Look(ref isWarmingUp, "isWarmingUp", false);
Scribe_Values.Look(ref currentTarget, "currentTarget");
Scribe_Values.Look(ref currentVolleyCenter, "currentVolleyCenter");
Scribe_Values.Look(ref currentVolleyIndex, "currentVolleyIndex");
Scribe_Values.Look(ref lastTargetUpdateTick, "lastTargetUpdateTick", -9999);
Scribe_Collections.Look(ref previousTargets, "previousTargets", LookMode.Value);
Scribe_Collections.Look(ref microTrackingTargets, "microTrackingTargets", LookMode.LocalTargetInfo);
Scribe_Collections.Look(ref microTrackingWeights, "microTrackingWeights", LookMode.Value);
Scribe_Collections.Look(ref cachedTargets, "cachedTargets", LookMode.LocalTargetInfo);
Scribe_Collections.Look(ref cachedTargetWeights, "cachedTargetWeights", LookMode.Value);
Scribe_Collections.Look(ref currentVolleyTargets, "currentVolleyTargets", LookMode.Value);
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
@@ -791,7 +803,14 @@ namespace WulaFallenEmpire
yield return new Command_Action
{
defaultLabel = "Dev: Fire Single Shell",
action = () => FireShell(parent as FlyOver)
action = () =>
{
if (parent is FlyOver flyOver)
{
IntVec3 target = SelectRandomTargetInRadius(GetFlyOverPosition(flyOver), flyOver.Map, Props.attackRadius);
FireShell(flyOver, target);
}
}
};
yield return new Command_Action
@@ -814,11 +833,14 @@ namespace WulaFallenEmpire
// 显示派系甄别信息
Faction targetFaction = GetTargetFaction(flyOver);
Log.Message($"Faction Discrimination: {Props.useFactionDiscrimination}, Target Faction: {targetFaction?.def.defName ?? "None"}");
Log.Message($"Micro Tracking: {Props.useMicroTracking}, Targets Found: {microTrackingTargets.Count}");
Log.Message($"Micro Tracking: {Props.useMicroTracking}, Targets Found: {cachedTargets.Count}");
// 显示目标统计
var stats = GetTargetStatistics();
Log.Message($"Target Stats - Pawns: {stats.pawnCount}, Owned Buildings: {stats.ownedBuildingCount}, Unowned Buildings: {stats.unownedBuildingCount}, Others: {stats.otherCount}");
Log.Message($"Target Stats - Pawns: {stats.pawnCount}, Owned Buildings: {stats.ownedBuildingCount}, Unowned Buildings: {stats.unownedBuildingCount}, Walls: {stats.wallCount}, Others: {stats.otherCount}");
// 显示炮击信息
Log.Message($"Volley - Center: {currentVolleyCenter}, Targets: {currentVolleyTargets.Count}, Index: {currentVolleyIndex}");
}
}
};
@@ -828,26 +850,56 @@ namespace WulaFallenEmpire
{
yield return new Command_Action
{
defaultLabel = $"Dev: Show Micro Targets ({microTrackingTargets.Count})",
defaultLabel = $"Dev: Show Cached Targets ({cachedTargets.Count})",
action = () =>
{
if (parent is FlyOver flyOver)
{
for (int i = 0; i < microTrackingTargets.Count; i++)
for (int i = 0; i < cachedTargets.Count; i++)
{
var target = microTrackingTargets[i];
float weight = microTrackingWeights[i];
var target = cachedTargets[i];
float weight = cachedTargetWeights[i];
Thing thing = target.Thing;
string type = thing is Pawn ? "Pawn" :
thing is Building building ?
(building.Faction == null ? "Unowned Building" : "Owned Building") : "Other";
Log.Message($"Micro Target: {thing?.Label ?? "Unknown"} ({type}) at {target.Cell}, Weight: {weight:F2}");
Log.Message($"Cached Target: {thing?.Label ?? "Unknown"} ({type}) at {target.Cell}, Weight: {weight:F2}");
}
}
}
};
}
// 显示当前炮击目标
if (currentVolleyTargets.Count > 0)
{
yield return new Command_Action
{
defaultLabel = $"Dev: Show Volley Targets ({currentVolleyTargets.Count})",
action = () =>
{
for (int i = 0; i < currentVolleyTargets.Count; i++)
{
Log.Message($"Volley Target {i}: {currentVolleyTargets[i]} ({(i == currentVolleyIndex ? "NEXT" : "queued")})");
}
}
};
}
// 强制更新目标缓存
yield return new Command_Action
{
defaultLabel = "Dev: Force Update Target Cache",
action = () =>
{
if (parent is FlyOver flyOver)
{
UpdateTargetCache(flyOver);
Log.Message($"Force updated target cache: {cachedTargets.Count} targets found");
}
}
};
}
}
@@ -863,5 +915,21 @@ namespace WulaFallenEmpire
{
currentTarget = target;
}
// 新增:获取当前状态信息
public string GetStatusString()
{
if (parent is not FlyOver flyOver)
return "Invalid parent";
string status = isWarmingUp ? $"Warming up ({warmupTicksRemaining} ticks)" :
isAttacking ? $"Attacking ({attackTicksRemaining} ticks)" :
$"Next attack in {ticksUntilNextAttack} ticks";
string targetInfo = currentTarget.IsValid ? $"Target: {currentTarget}" : "No target";
string volleyInfo = currentVolleyTargets.Count > 0 ? $", Volley: {currentVolleyIndex}/{currentVolleyTargets.Count}" : "";
return $"{status}, {targetInfo}{volleyInfo}";
}
}
}

View File

@@ -207,6 +207,112 @@ namespace WulaFallenEmpire
{
yield return new DroneGizmo(this);
}
// 更换武器按钮(仅当有装备武器时显示)
if (MechPawn.equipment?.Primary != null)
{
yield return CreateWeaponSwitchGizmo();
}
}
/// <summary>
/// 创建更换武器的Gizmo
/// </summary>
private Gizmo CreateWeaponSwitchGizmo()
{
Command_Action switchWeaponCommand = new Command_Action
{
defaultLabel = "WULA_SwitchWeapon".Translate(),
defaultDesc = "WULA_SwitchWeapon_Desc".Translate(),
icon = ContentFinder<Texture2D>.Get("Wula/UI/Abilities/WULA_WeaponSwitchAbility", false) ?? BaseContent.BadTex,
action = SwitchWeapon
};
return switchWeaponCommand;
}
/// <summary>
/// 更换武器逻辑
/// </summary>
private void SwitchWeapon()
{
if (MechPawn == null || MechPawn.Destroyed || !MechPawn.Spawned)
return;
try
{
// 1. 扔掉当前武器
ThingWithComps currentWeapon = MechPawn.equipment?.Primary;
if (currentWeapon != null)
{
// 将武器扔在地上
MechPawn.equipment.TryDropEquipment(currentWeapon, out ThingWithComps droppedWeapon, MechPawn.Position, true);
if (Prefs.DevMode)
{
Log.Message($"[CompAutonomousMech] {MechPawn.LabelCap} dropped weapon: {currentWeapon.LabelCap}");
}
}
// 2. 从PawnKind允许的武器中生成新武器
ThingDef newWeaponDef = GetRandomWeaponFromPawnKind();
if (newWeaponDef != null)
{
// 生成新武器
Thing newWeapon = ThingMaker.MakeThing(newWeaponDef);
if (newWeapon is ThingWithComps newWeaponWithComps)
{
// 使用 AddEquipment 方法装备新武器
MechPawn.equipment.AddEquipment(newWeaponWithComps);
Messages.Message("WULA_WeaponSwitched".Translate(MechPawn.LabelCap, newWeaponDef.LabelCap),
MechPawn, MessageTypeDefOf.PositiveEvent);
if (Prefs.DevMode)
{
Log.Message($"[CompAutonomousMech] {MechPawn.LabelCap} equipped new weapon: {newWeaponDef.LabelCap}");
}
}
}
else
{
Messages.Message("WULA_NoWeaponAvailable".Translate(MechPawn.LabelCap),
MechPawn, MessageTypeDefOf.NegativeEvent);
}
}
catch (System.Exception ex)
{
Log.Error($"[CompAutonomousMech] Error switching weapon for {MechPawn?.LabelCap}: {ex}");
}
}
/// <summary>
/// 从PawnKind允许的武器中随机获取一个武器定义
/// </summary>
private ThingDef GetRandomWeaponFromPawnKind()
{
if (MechPawn.kindDef?.weaponTags == null || MechPawn.kindDef.weaponTags.Count == 0)
return null;
// 收集所有匹配的武器
List<ThingDef> availableWeapons = new List<ThingDef>();
foreach (string weaponTag in MechPawn.kindDef.weaponTags)
{
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
{
if (thingDef.IsWeapon && thingDef.weaponTags != null && thingDef.weaponTags.Contains(weaponTag))
{
availableWeapons.Add(thingDef);
}
}
}
if (availableWeapons.Count == 0)
return null;
// 随机选择一个武器
return availableWeapons.RandomElement();
}
public void SetWorkMode(DroneWorkModeDef mode)

View File

@@ -16,11 +16,13 @@ namespace WulaFallenEmpire
{
if (map == null)
yield break;
if (Find.TickManager.TicksGame - lastUpdateTick > UPDATE_INTERVAL_TICKS)
{
UpdateShieldCache();
lastUpdateTick = Find.TickManager.TicksGame;
}
if (activeShieldsByMap.TryGetValue(map, out var shields))
{
foreach (var shield in shields)
@@ -30,36 +32,57 @@ namespace WulaFallenEmpire
}
}
}
private static void UpdateShieldCache()
{
activeShieldsByMap.Clear();
foreach (var map in Find.Maps)
{
if (map == null) continue;
var shieldSet = new HashSet<ThingComp_AreaShield>();
// 搜索装备上的护盾
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
{
if (pawn?.apparel == null || pawn.Destroyed)
continue;
foreach (var apparel in pawn.apparel.WornApparel)
{
if (apparel == null || apparel.Destroyed)
continue;
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
// 修改:只有立定且激活的护盾才加入缓存
if (shield != null && shield.Active && !shield.IsWearerMoving)
if (shield != null && shield.Active && !shield.IsHolderMoving)
{
shieldSet.Add(shield);
}
}
}
// 搜索固定物品上的护盾(新增)
foreach (var thing in map.listerThings.AllThings)
{
if (thing == null || thing.Destroyed || thing is Apparel)
continue;
var shield = thing.TryGetComp<ThingComp_AreaShield>();
if (shield != null && shield.Active)
{
shieldSet.Add(shield);
}
}
activeShieldsByMap[map] = shieldSet;
}
}
public static void NotifyShieldStateChanged(ThingComp_AreaShield shield)
{
if (shield?.Wearer?.Map != null)
if (shield?.Holder?.Map != null)
{
lastUpdateTick = 0;
}

View File

@@ -10,7 +10,8 @@ namespace WulaFallenEmpire
public ThingComp_AreaShield shield;
private static readonly Texture2D FullShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.8f, 0.85f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
private static readonly Texture2D EmptyShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.2f, 0.24f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
// 新增:移动状态的颜色
// 新增:移动状态的颜色(仅对装备有效)
private static readonly Texture2D MovingShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.5f, 0.5f, 0.5f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
public override float GetWidth(float maxWidth) => 140f;
@@ -39,11 +40,11 @@ namespace WulaFallenEmpire
barTex = EmptyShieldBarTex;
statusText = "ShieldOnCooldown".Translate();
}
else if (shield.IsWearerMoving)
else if (shield.IsEquipment && shield.IsHolderMoving)
{
// 移动时显示灰色状态条和"移动中"文本
// 移动时显示灰色状态条和"移动中"文本(仅对装备有效)
barTex = MovingShieldBarTex;
statusText = "ShieldOfflineByMoving".Translate(); // 你可以根据需要修改这个文本
statusText = "ShieldOfflineByMoving".Translate();
}
else
{

View File

@@ -21,42 +21,205 @@ namespace WulaFallenEmpire
private bool drawInterceptCone;
public CompProperties_AreaShield Props => (CompProperties_AreaShield)props;
// 回退机制:支持装备和普通物品
public Pawn Wearer => (parent as Apparel)?.Wearer;
public bool IsEquipment => parent is Apparel;
public bool IsStandalone => !IsEquipment;
// 获取护盾持有者(回退机制)
public Thing Holder => IsEquipment ? (Thing)Wearer : parent;
public bool IsOnCooldown => ticksToReset > 0;
public int HitPointsMax => Props.baseHitPoints;
private bool initialized = false;
private StunHandler stunner;
// 新增:移动状态检测
public bool IsWearerMoving
// 材质定义
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
private const float TextureActualRingSizeFactor = 1.1601562f;
private static readonly Color InactiveColor = new Color(0.2f, 0.2f, 0.2f);
// 护盾绘制方法 - 参考原版实现
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
if (!IsEquipment) return; // 只有装备使用这个方法
DrawShield();
}
public override void PostDraw()
{
base.PostDraw();
if (IsEquipment) return; // 装备使用 CompDrawWornExtras
DrawShield();
}
/// <summary>
/// 统一的护盾绘制方法 - 参考原版实现
/// </summary>
private void DrawShield()
{
if (!Active || Holder?.Map == null || Holder.Destroyed)
return;
Vector3 drawPos = GetHolderDrawPos();
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
float currentAlpha = GetCurrentAlpha();
if (currentAlpha > 0f)
{
// 参考原版:未激活但被选中时使用灰色
Color color = (!Active && Find.Selector.IsSelected(parent)) ? InactiveColor : Props.color;
color.a *= currentAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default;
float scale = Props.radius * 2f * TextureActualRingSizeFactor;
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(scale, 1f, scale));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
}
// 添加拦截锥形效果
float coneAlpha = GetCurrentConeAlpha();
if (coneAlpha > 0f)
{
Color color = Props.color;
color.a *= coneAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default;
float scale = Props.radius * 2f;
matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(scale, 1f, scale));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
}
}
/// <summary>
/// 获取当前透明度 - 参考原版的多状态叠加
/// </summary>
private float GetCurrentAlpha()
{
// 多个透明度来源叠加,取最大值
return Mathf.Max(
Mathf.Max(
Mathf.Max(
GetCurrentAlpha_Idle(),
GetCurrentAlpha_Selected()
),
GetCurrentAlpha_RecentlyIntercepted()
),
0.1f // 最小透明度
);
}
/// <summary>
/// 空闲状态透明度
/// </summary>
private float GetCurrentAlpha_Idle()
{
if (!Active) return 0f;
// 固定物品:始终显示空闲状态
if (IsStandalone)
{
// 脉冲效果
return Mathf.Lerp(0.3f, 0.6f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
}
// 装备:只在特定条件下显示
else if (IsEquipment)
{
// 装备护盾只在以下情况显示空闲状态:
if (Holder is Pawn pawn)
{
if (pawn.Drafted || pawn.InAggroMentalState ||
(pawn.Faction != null && pawn.Faction.HostileTo(Faction.OfPlayer) && !pawn.IsPrisoner))
{
return Mathf.Lerp(0.3f, 0.6f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
}
}
}
return 0f;
}
/// <summary>
/// 被选中状态透明度 - 参考原版实现
/// </summary>
private float GetCurrentAlpha_Selected()
{
// 如果被选中,显示更高的透明度
if (Find.Selector.IsSelected(parent) && Active)
{
return Mathf.Lerp(0.4f, 0.8f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 96804938) + Time.realtimeSinceStartup * 2.5f) + 1f) / 2f);
}
return 0f;
}
/// <summary>
/// 最近拦截状态透明度
/// </summary>
private float GetCurrentAlpha_RecentlyIntercepted()
{
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.3f;
}
/// <summary>
/// 拦截锥形透明度
/// </summary>
private float GetCurrentConeAlpha()
{
if (!drawInterceptCone) return 0f;
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.82f;
}
/// <summary>
/// 获取持有者绘制位置(回退机制)
/// </summary>
private Vector3 GetHolderDrawPos()
{
if (Holder is Pawn pawn)
return pawn.Drawer?.DrawPos ?? pawn.Position.ToVector3Shifted();
else
return Holder.DrawPos;
}
// 移动状态检测(仅对装备有效)
public bool IsHolderMoving
{
get
{
if (IsStandalone) return false; // 固定物品不会移动
if (Wearer == null || !Wearer.Spawned) return false;
return Wearer.pather.Moving;
}
}
// 修改Active属性只有在立定时才激活
// 修改Active属性装备只有在立定时才激活,固定物品始终激活
public bool Active
{
get
{
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown)
if (Holder == null || !Holder.Spawned || Holder.Destroyed)
return false;
// 新增:只有在立定时才激活
if (IsWearerMoving)
if (Holder is Pawn pawn && (pawn.Dead || pawn.Downed))
return false;
if (IsOnCooldown)
return false;
// 装备:只有在立定时才激活
if (IsEquipment && IsHolderMoving)
return false;
return true;
}
}
// 材质定义
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
public override void PostPostMake()
{
base.PostPostMake();
@@ -84,7 +247,7 @@ namespace WulaFallenEmpire
wasActiveLastCheck = isActive;
}
if (Wearer == null) return;
if (Holder == null) return;
if (IsOnCooldown)
{
@@ -125,14 +288,15 @@ namespace WulaFallenEmpire
public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos)
{
// 增强安全检查
if (!Active || projectile == null || projectile.Destroyed || Wearer == null || Wearer.Map == null)
if (!Active || projectile == null || projectile.Destroyed || Holder == null || Holder.Map == null)
return false;
if (currentHitPoints <= 0)
return false;
try
{
if (!GenGeo.IntersectLineCircleOutline(Wearer.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
if (!GenGeo.IntersectLineCircleOutline(Holder.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
{
return false;
}
@@ -140,27 +304,27 @@ namespace WulaFallenEmpire
return false;
if (!projectile.def.projectile.flyOverhead && !Props.interceptGroundProjectiles)
return false;
if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Wearer.Faction) && !Props.interceptNonHostileProjectiles)
if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Holder.Faction) && !Props.interceptNonHostileProjectiles)
return false;
lastInterceptTicks = Find.TickManager.TicksGame;
// 记录拦截角度用于视觉效果
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(Wearer.TrueCenter());
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(GetHolderCenter());
drawInterceptCone = true;
// 尝试反射
if (Props.canReflect && TryReflectProjectile(projectile, lastExactPos, newExactPos))
{
// 反射成功,播放反射特效
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Holder.Map).Cleanup();
ApplyCosts(Props.reflectCost);
return false; // 不销毁原抛射体,让它继续飞行(我们会在反射中销毁它)
}
else
{
// 普通拦截,播放拦截特效
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Holder.Map).Cleanup();
ApplyCosts();
return true; // 销毁抛射体
}
@@ -172,6 +336,17 @@ namespace WulaFallenEmpire
}
}
/// <summary>
/// 获取持有者中心位置(回退机制)
/// </summary>
private Vector3 GetHolderCenter()
{
if (Holder is Pawn pawn)
return pawn.TrueCenter();
else
return Holder.DrawPos;
}
/// <summary>
/// 尝试反射抛射体 - 现在会创建新的抛射体
/// </summary>
@@ -183,13 +358,14 @@ namespace WulaFallenEmpire
// 检查反射概率
if (Rand.Value > Props.reflectChance)
return false;
try
{
// 计算入射方向
Vector3 incomingDirection = (newExactPos - lastExactPos).normalized;
// 计算法线方向(从护盾中心到碰撞点)
Vector3 normal = (newExactPos - Wearer.DrawPos).normalized;
Vector3 normal = (newExactPos - GetHolderCenter()).normalized;
// 计算反射方向(镜面反射)
Vector3 reflectDirection = Vector3.Reflect(incomingDirection, normal);
@@ -216,28 +392,34 @@ namespace WulaFallenEmpire
{
try
{
if (originalProjectile == null || originalProjectile.Destroyed || Wearer == null || Wearer.Map == null)
if (originalProjectile == null || originalProjectile.Destroyed || Holder == null || Holder.Map == null)
return false;
// 计算新的发射位置(护盾位置附近)
Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint);
// 确保位置在地图内
IntVec3 spawnCell = spawnPosition.ToIntVec3();
if (!spawnCell.InBounds(Wearer.Map))
if (!spawnCell.InBounds(Holder.Map))
{
spawnCell = Wearer.Position;
spawnCell = Holder.Position;
}
// 计算新的目标位置
Vector3 targetPosition = spawnCell.ToVector3Shifted() + reflectDirection * 30f;
IntVec3 targetCell = targetPosition.ToIntVec3();
// 创建新的抛射体
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Wearer.Map);
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Holder.Map);
if (newProjectile == null)
{
Log.Warning("Failed to spawn reflected projectile");
return false;
}
// 设置发射者为装备穿戴者
Thing launcher = Wearer;
// 设置发射者为护盾持有者
Thing launcher = Holder;
// 发射新抛射体
newProjectile.Launch(
launcher,
@@ -247,11 +429,14 @@ namespace WulaFallenEmpire
ProjectileHitFlags.All,
false
);
// 复制重要的属性
CopyProjectileProperties(originalProjectile, newProjectile);
// 使用延迟销毁而不是立即销毁
ReflectedProjectileManager.MarkForDelayedDestroy(originalProjectile);
Log.Message($"反射抛射体: 由 {Wearer?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
Log.Message($"反射抛射体: 由 {Holder?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
return true;
}
catch (System.Exception ex)
@@ -266,15 +451,15 @@ namespace WulaFallenEmpire
/// </summary>
private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint)
{
if (Wearer == null)
if (Holder == null)
return collisionPoint;
// 计算从护盾中心到碰撞点的方向
Vector3 directionFromCenter = (collisionPoint - Wearer.DrawPos).normalized;
Vector3 directionFromCenter = (collisionPoint - GetHolderCenter()).normalized;
// 在护盾边界上生成(稍微向内一点避免立即再次碰撞)
float spawnDistance = Props.radius * 0.9f;
Vector3 spawnPosition = Wearer.DrawPos + directionFromCenter * spawnDistance;
Vector3 spawnPosition = GetHolderCenter() + directionFromCenter * spawnDistance;
return spawnPosition;
}
@@ -310,26 +495,26 @@ namespace WulaFallenEmpire
public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
{
absorbed = false;
if (!Active || Wearer == null) return;
if (!Active || Holder == null) return;
if (dinfo.Def.isRanged) return;
if (dinfo.Instigator != null)
{
float distance = Wearer.Position.DistanceTo(dinfo.Instigator.Position);
float distance = Holder.Position.DistanceTo(dinfo.Instigator.Position);
if (distance > Props.radius) return;
}
if (currentHitPoints <= 0) return;
Props.absorbEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.absorbEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
ApplyCosts();
absorbed = true;
}
private void Break()
{
Props.breakEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.breakEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
ticksToReset = Props.rechargeDelay;
currentHitPoints = 0;
AreaShieldManager.NotifyShieldStateChanged(this);
@@ -337,85 +522,50 @@ namespace WulaFallenEmpire
private void Reset()
{
if (Wearer != null && Wearer.Spawned)
if (Holder != null && Holder.Spawned)
{
Props.reactivateEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.reactivateEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
}
currentHitPoints = HitPointsMax;
AreaShieldManager.NotifyShieldStateChanged(this);
}
// 护盾绘制方法 - 只有在立定时才绘制
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
// 修改:移动时不绘制护盾
if (!Active || Wearer?.Map == null || !ShouldDisplay || IsWearerMoving)
return;
Vector3 drawPos = Wearer.Drawer?.DrawPos ?? Wearer.Position.ToVector3Shifted();
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;
float scale = Props.radius * 2f * 1.1601562f;
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(scale, 1f, scale));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
}
// 添加拦截锥形效果
float coneAlpha = GetCurrentConeAlpha();
if (coneAlpha > 0f)
{
Color color = Props.color;
color.a *= coneAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default;
float scale = Props.radius * 2f;
matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(scale, 1f, scale));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
}
}
// 显示条件
// 显示条件 - 修改为固定物品始终显示,装备有条件显示
protected bool ShouldDisplay
{
get
{
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || !Active)
if (Holder == null || !Holder.Spawned || Holder.Destroyed || !Active)
return false;
if (Wearer.Drafted || Wearer.InAggroMentalState ||
(Wearer.Faction != null && Wearer.Faction.HostileTo(Faction.OfPlayer) && !Wearer.IsPrisoner))
// 对于装备:只在特定条件下显示
if (IsEquipment && Holder is Pawn pawn)
{
if (pawn.Dead || pawn.Downed)
return false;
// 装备护盾只在以下情况显示:
// 1. 穿戴者被选中
// 2. 穿戴者处于战斗状态(征召状态或敌对)
// 3. 穿戴者处于攻击性精神状态
if (Find.Selector.IsSelected(pawn))
return true;
if (pawn.Drafted || pawn.InAggroMentalState)
return true;
if (pawn.Faction != null && pawn.Faction.HostileTo(Faction.OfPlayer) && !pawn.IsPrisoner)
return true;
}
// 对于固定物品:始终显示(只要护盾激活)
else if (IsStandalone)
{
return true;
if (Find.Selector.IsSelected(Wearer))
return true;
}
return false;
}
}
private float GetCurrentAlpha()
{
float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
return Mathf.Max(idleAlpha, interceptAlpha);
}
private float GetCurrentConeAlpha()
{
if (!drawInterceptCone) return 0f;
return Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f) * 0.82f;
}
private void EnsureInitialized()
{
if (initialized) return;
@@ -433,7 +583,18 @@ namespace WulaFallenEmpire
{
EnsureInitialized();
if (Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
if (IsEquipment && Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
EnsureInitialized();
// 固定物品也显示护盾状态
if (IsStandalone && Find.Selector.SingleSelectedThing == parent)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}

View File

@@ -0,0 +1,187 @@
using RimWorld;
using Verse;
using Verse.Sound;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public class CompPlaySoundOnSpawn : ThingComp
{
private CompProperties_PlaySoundOnSpawn Props => (CompProperties_PlaySoundOnSpawn)props;
private bool soundPlayed = false;
private int delayTicksRemaining = 0;
public override void Initialize(CompProperties props)
{
base.Initialize(props);
// 计算延迟的 ticks
if (Props.delaySeconds > 0)
{
delayTicksRemaining = (int)(Props.delaySeconds * 60f);
}
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
// 如果是重新加载存档,不播放声音
if (respawningAfterLoad)
return;
// 检查播放条件
if (!ShouldPlaySound())
return;
// 如果有延迟,设置延迟计数器
if (Props.delaySeconds > 0)
{
delayTicksRemaining = (int)(Props.delaySeconds * 60f);
}
else
{
// 立即播放声音
PlaySound();
}
}
public override void CompTick()
{
base.CompTick();
// 处理延迟播放
if (delayTicksRemaining > 0 && !soundPlayed)
{
delayTicksRemaining--;
if (delayTicksRemaining <= 0)
{
PlaySound();
}
}
}
private bool ShouldPlaySound()
{
if (soundPlayed)
return false;
if (Props.sound == null)
return false;
// 检查派系条件
if (parent.Faction != null)
{
if (Props.onlyIfPlayerFaction && parent.Faction != Faction.OfPlayer)
return false;
if (Props.onlyIfHostileFaction && parent.Faction.RelationKindWith(Faction.OfPlayer) != FactionRelationKind.Hostile)
return false;
if (Props.onlyIfNeutralFaction && parent.Faction.RelationKindWith(Faction.OfPlayer) != FactionRelationKind.Neutral)
return false;
}
else
{
// 如果没有派系,检查是否要求特定派系
if (Props.onlyIfPlayerFaction || Props.onlyIfHostileFaction || Props.onlyIfNeutralFaction)
return false;
}
return true;
}
private void PlaySound()
{
if (soundPlayed || Props.sound == null)
return;
try
{
SoundInfo soundInfo;
if (Props.playOnCamera)
{
// 在摄像机位置播放
soundInfo = SoundInfo.OnCamera();
}
else if (Props.playAtThingPosition)
{
// 在物体位置播放
soundInfo = SoundInfo.InMap(new TargetInfo(parent.Position, parent.Map));
}
else
{
// 默认在物体位置播放
soundInfo = SoundInfo.InMap(new TargetInfo(parent.Position, parent.Map));
}
// 应用音量和音调设置
if (Props.volume != 1f)
soundInfo.volumeFactor = Props.volume;
if (Props.pitch != 1f)
soundInfo.pitchFactor = Props.pitch;
// 播放声音
Props.sound.PlayOneShot(soundInfo);
soundPlayed = true;
// 调试日志
if (Prefs.DevMode)
{
Log.Message($"Played spawn sound: {Props.sound.defName} for {parent.Label} at {parent.Position}");
}
}
catch (System.Exception ex)
{
Log.Error($"Error playing spawn sound for {parent.Label}: {ex}");
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref soundPlayed, "soundPlayed", false);
Scribe_Values.Look(ref delayTicksRemaining, "delayTicksRemaining", 0);
}
// 调试工具
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
if (DebugSettings.ShowDevGizmos)
{
yield return new Command_Action
{
defaultLabel = "Dev: Test Spawn Sound",
defaultDesc = $"Play sound: {Props.sound?.defName ?? "None"}",
action = () =>
{
if (Props.sound != null)
{
PlaySound();
}
else
{
Log.Warning("No sound defined for CompPlaySoundOnSpawn");
}
}
};
yield return new Command_Action
{
defaultLabel = $"Dev: Sound Status - Played: {soundPlayed}, Delay: {delayTicksRemaining}",
action = () => {}
};
}
}
// 重置状态(用于重新播放)
public void Reset()
{
soundPlayed = false;
delayTicksRemaining = Props.delaySeconds > 0 ? (int)(Props.delaySeconds * 60f) : 0;
}
}
}

View File

@@ -0,0 +1,31 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_PlaySoundOnSpawn : CompProperties
{
public SoundDef sound;
// 可选:延迟播放声音(秒)
public float delaySeconds = 0f;
// 可选:只在特定条件下播放
public bool onlyIfPlayerFaction = false;
public bool onlyIfHostileFaction = false;
public bool onlyIfNeutralFaction = false;
// 可选:音量控制
public float volume = 1f;
public float pitch = 1f;
// 可选:播放位置
public bool playOnCamera = false; // 在摄像机位置播放
public bool playAtThingPosition = true; // 在物体位置播放
public CompProperties_PlaySoundOnSpawn()
{
compClass = typeof(CompPlaySoundOnSpawn);
}
}
}

View File

@@ -117,13 +117,15 @@
<Compile Include="BuildingComp\WULA_StorageTurret\CompProperties_StorageTurret.cs" />
<Compile Include="BuildingComp\WULA_StorageTurret\CompStorageTurret.cs" />
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
<Compile Include="EventSystem\Condition.cs" />
<Compile Include="EventSystem\Condition\ConditionBase.cs" />
<Compile Include="EventSystem\Condition\Condition_FlagExists.cs" />
<Compile Include="EventSystem\DebugActions.cs" />
<Compile Include="EventSystem\DelayedActionManager.cs" />
<Compile Include="EventSystem\Dialog_CustomDisplay.cs" />
<Compile Include="EventSystem\Dialog_ManageEventVariables.cs" />
<Compile Include="EventSystem\Effect\EffectBase.cs" />
<Compile Include="EventSystem\Effect\Effect_CallSkyfaller.cs" />
<Compile Include="EventSystem\Effect\Effect_SetTimedFlag.cs" />
<Compile Include="EventSystem\EventDef.cs" />
<Compile Include="EventSystem\EventUIButtonConfigDef.cs" />
<Compile Include="EventSystem\EventUIConfigDef.cs" />
@@ -266,6 +268,8 @@
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />
<Compile Include="ThingComp\WULA_PlaySoundOnSpawn\CompPlaySoundOnSpawn.cs" />
<Compile Include="ThingComp\WULA_PlaySoundOnSpawn\CompProperties_PlaySoundOnSpawn.cs" />
<Compile Include="ThingComp\WULA_PsychicRitual\CompWulaRitualSpot.cs" />
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualDef_AddHediff.cs" />
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualDef_Wula.cs" />