Files
WulaFallenEmpireRW/Source/WulaFallenEmpire/Flyover/WULA_FlyOverDropPod/CompProperties_FlyOverDropPod.cs
ProjectKoi-Kalo\Kalo 98a0400c78 WulaFallenEmpireSettings.cs - 添加了 public bool enableDebugLogs = false; 字段和保存配置
 WulaLog.cs - 修改了DebugEnabled属性,仅检查enableDebugLogs设置(不检查DevMode)
 WulaFallenEmpireMod.cs - 在DoSettingsWindowContents中添加了UI复选框,显示"Enable Debug Logs"选项
 替换了所有848个Log.Message/Error/Warning调用为WulaLog.Debug()
2025-12-15 13:05:50 +08:00

744 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using UnityEngine;
using Verse.AI.Group;
namespace WulaFallenEmpire
{
// 扩展的空投仓配置属性 - 移除 dropPodDef
public class CompProperties_FlyOverDropPods : CompProperties
{
public IntVec3 dropOffset = IntVec3.Zero; // 投掷位置偏移
// 投掷时机配置
public float dropProgress = 0.5f; // 投掷进度 (0-1)
public bool useCyclicDrops = false; // 是否使用循环投掷
public float cyclicDropIntervalHours = 24f; // 循环投掷间隔(小时)
public bool waitForExternalSignal = false; // 是否等待外部信号
public string externalSignalTag; // 外部信号标签
public int dropCount = 1; // 投掷数量
public float scatterRadius = 3f; // 散布半径
public bool useTradeDropSpot; // 是否使用贸易空投点
public bool allowFogged; // 是否允许雾区
public bool dropAllInSamePod; // 是否在同一空投仓中
public bool leaveSlag; // 是否留下残骸
// 内容物配置
public List<ThingDefCountClass> thingDefs = new List<ThingDefCountClass>();
public bool dropAllContents = false; // 是否投掷所有内容物
// Pawn 生成配置
public List<PawnKindDefCountClass> pawnKinds = new List<PawnKindDefCountClass>();
public FactionDef pawnFactionDef; // Pawn 派系定义
public bool generatePawnsOnDrop = true; // 是否在投掷时生成 Pawn
// 乘客配置
public bool joinPlayer;
public bool makePrisoners;
// LordJob 配置 - 简化版本
public bool assignAssaultLordJob = false; // 是否分配袭击殖民地的 LordJob
public bool canKidnap = true; // 是否可以绑架
public bool canTimeoutOrFlee = true; // 是否可以超时或逃跑
public bool useSappers = false; // 是否使用工兵
public bool useAvoidGridSmart = false; // 是否使用智能回避网格
public bool canSteal = true; // 是否可以偷窃
public bool useBreachers = false; // 是否使用破墙者
public bool canPickUpOpportunisticWeapons = false; // 是否可以捡起机会性武器
// 信件通知
public bool sendStandardLetter = true;
public string customLetterText;
public string customLetterLabel;
public LetterDef customLetterDef;
// 派系
public Faction faction;
public CompProperties_FlyOverDropPods()
{
this.compClass = typeof(CompFlyOverDropPods);
}
}
// PawnKind 数量类
public class PawnKindDefCountClass : IExposable
{
public PawnKindDef pawnKindDef;
public int count;
public PawnKindDefCountClass() { }
public PawnKindDefCountClass(PawnKindDef pawnKindDef, int count)
{
this.pawnKindDef = pawnKindDef;
this.count = count;
}
public void ExposeData()
{
Scribe_Defs.Look(ref pawnKindDef, "pawnKindDef");
Scribe_Values.Look(ref count, "count", 1);
}
public override string ToString()
{
return $"{count}x {pawnKindDef?.label ?? "null"}";
}
}
// 空投仓投掷 Comp - 使用原版空投仓
public class CompFlyOverDropPods : ThingComp
{
public CompProperties_FlyOverDropPods Props => (CompProperties_FlyOverDropPods)props;
// 状态变量
private bool hasDropped = false;
private int ticksUntilNextDrop = 0;
private bool waitingForSignal = false;
private List<Thing> items = new List<Thing>();
private List<Pawn> pawns = new List<Pawn>();
public override void Initialize(CompProperties props)
{
base.Initialize(props);
// 预生成内容物
if (Props.thingDefs != null)
{
foreach (ThingDefCountClass thingDefCount in Props.thingDefs)
{
Thing thing = ThingMaker.MakeThing(thingDefCount.thingDef);
thing.stackCount = thingDefCount.count;
items.Add(thing);
}
}
// 如果不在投掷时生成 Pawn则预生成 Pawn
if (!Props.generatePawnsOnDrop && Props.pawnKinds != null)
{
GeneratePawnsFromKinds();
}
// 初始化循环投掷计时器
if (Props.useCyclicDrops)
{
ticksUntilNextDrop = (int)(Props.cyclicDropIntervalHours * 2500f); // 1小时 = 2500 ticks
WulaLog.Debug($"Cyclic drops initialized: {Props.cyclicDropIntervalHours} hours interval");
}
// 初始化信号等待状态
if (Props.waitForExternalSignal)
{
waitingForSignal = true;
WulaLog.Debug($"Waiting for external signal: {Props.externalSignalTag}");
}
}
// 从 PawnKind 定义生成 Pawn
private void GeneratePawnsFromKinds()
{
if (Props.pawnKinds == null) return;
foreach (PawnKindDefCountClass pawnKindCount in Props.pawnKinds)
{
for (int i = 0; i < pawnKindCount.count; i++)
{
Pawn pawn = GeneratePawn(pawnKindCount.pawnKindDef);
if (pawn != null)
{
pawns.Add(pawn);
WulaLog.Debug($"Generated pawn: {pawn.Label} ({pawnKindCount.pawnKindDef.defName})");
}
}
}
}
// 生成单个 Pawn
private Pawn GeneratePawn(PawnKindDef pawnKindDef)
{
if (pawnKindDef == null)
{
WulaLog.Debug("Attempted to generate pawn with null PawnKindDef");
return null;
}
try
{
// 确定派系
Faction faction = DetermineFactionForPawn();
// 生成 Pawn
PawnGenerationRequest request = new PawnGenerationRequest(
pawnKindDef,
faction,
PawnGenerationContext.NonPlayer,
-1,
forceGenerateNewPawn: true,
allowDead: false,
allowDowned: false,
canGeneratePawnRelations: true,
mustBeCapableOfViolence: false,
colonistRelationChanceFactor: 1f,
forceAddFreeWarmLayerIfNeeded: false,
allowGay: true,
allowFood: true,
allowAddictions: true,
inhabitant: false,
certainlyBeenInCryptosleep: false,
forceRedressWorldPawnIfFormerColonist: false,
worldPawnFactionDoesntMatter: false,
biocodeWeaponChance: 0f,
biocodeApparelChance: 0f,
extraPawnForExtraRelationChance: null,
relationWithExtraPawnChanceFactor: 1f,
validatorPreGear: null,
validatorPostGear: null,
forcedTraits: null,
prohibitedTraits: null,
minChanceToRedressWorldPawn: 0f,
fixedBiologicalAge: null,
fixedChronologicalAge: null,
fixedGender: null
);
Pawn pawn = PawnGenerator.GeneratePawn(request);
// 设置 Pawn 的基本状态
if (pawn.mindState != null)
{
pawn.mindState.SetupLastHumanMeatTick();
}
WulaLog.Debug($"Successfully generated pawn: {pawn.LabelCap} from {pawnKindDef.defName}");
return pawn;
}
catch (System.Exception ex)
{
WulaLog.Debug($"Failed to generate pawn from {pawnKindDef.defName}: {ex}");
return null;
}
}
// 确定 Pawn 的派系
private Faction DetermineFactionForPawn()
{
// 优先使用指定的派系定义
if (Props.pawnFactionDef != null)
{
Faction faction = Find.FactionManager.FirstFactionOfDef(Props.pawnFactionDef);
if (faction != null) return faction;
}
// 使用 Comp 的派系
if (Props.faction != null) return Props.faction;
// 使用默认中立派系
return Faction.OfAncients;
}
public override void CompTick()
{
base.CompTick();
if (parent is FlyOver flyOver)
{
// 检查不同的投掷模式
if (!hasDropped && !waitingForSignal)
{
if (Props.useCyclicDrops)
{
CheckCyclicDrop(flyOver);
}
else
{
CheckProgressDrop(flyOver);
}
}
}
}
// 检查进度投掷
private void CheckProgressDrop(FlyOver flyOver)
{
if (flyOver.currentProgress >= Props.dropProgress && !hasDropped)
{
DropPods(flyOver);
hasDropped = true;
}
}
// 检查循环投掷
private void CheckCyclicDrop(FlyOver flyOver)
{
ticksUntilNextDrop--;
if (ticksUntilNextDrop <= 0)
{
DropPods(flyOver);
// 重置计时器
ticksUntilNextDrop = (int)(Props.cyclicDropIntervalHours * 2500f);
WulaLog.Debug($"Cyclic drop completed, next drop in {Props.cyclicDropIntervalHours} hours");
}
}
// 外部信号触发投掷
public void TriggerDropFromSignal()
{
if (parent is FlyOver flyOver && waitingForSignal)
{
WulaLog.Debug($"External signal received, triggering drop pods");
DropPods(flyOver);
waitingForSignal = false;
}
}
// 接收信号的方法
public override void ReceiveCompSignal(string signal)
{
base.ReceiveCompSignal(signal);
if (Props.waitForExternalSignal && signal == Props.externalSignalTag)
{
TriggerDropFromSignal();
}
}
private void DropPods(FlyOver flyOver)
{
Map map = flyOver.Map;
if (map == null)
{
WulaLog.Debug("FlyOver DropPods: Map is null");
return;
}
IntVec3 dropCenter = GetDropCenter(flyOver);
WulaLog.Debug($"DropPods triggered at progress {flyOver.currentProgress}, center: {dropCenter}");
// 如果在投掷时生成 Pawn现在生成
if (Props.generatePawnsOnDrop && Props.pawnKinds != null)
{
GeneratePawnsFromKinds();
}
// 准备要投掷的物品列表
List<Thing> thingsToDrop = new List<Thing>();
// 添加预生成的内容物(确保不在容器中)
foreach (Thing item in items)
{
if (item.holdingOwner != null)
{
item.holdingOwner.Remove(item);
}
thingsToDrop.Add(item);
}
// 添加生成的 Pawn确保不在容器中
foreach (Pawn pawn in pawns)
{
if (pawn.holdingOwner != null)
{
pawn.holdingOwner.Remove(pawn);
}
thingsToDrop.Add(pawn);
}
if (!thingsToDrop.Any())
{
WulaLog.Debug("No items to drop from FlyOver drop pods");
return;
}
// 移除已销毁的物品
thingsToDrop.RemoveAll(x => x.Destroyed);
// 设置乘客派系和行为
SetupPawnsForDrop();
// 执行投掷
if (Props.dropCount > 1)
{
DropMultiplePods(thingsToDrop, dropCenter, map);
}
else
{
DropSinglePod(thingsToDrop, dropCenter, map);
}
// 发送信件通知
if (Props.sendStandardLetter)
{
SendDropLetter(thingsToDrop, dropCenter, map);
}
WulaLog.Debug($"Drop pods completed: {thingsToDrop.Count} items dropped, including {pawns.Count} pawns");
// 清空已投掷的物品列表,避免重复投掷
items.Clear();
pawns.Clear();
}
// 设置 Pawn 的派系和行为
private void SetupPawnsForDrop()
{
foreach (Pawn pawn in pawns)
{
if (Props.joinPlayer)
{
if (pawn.Faction != Faction.OfPlayer)
{
pawn.SetFaction(Faction.OfPlayer);
}
}
else if (Props.makePrisoners)
{
if (pawn.RaceProps.Humanlike && !pawn.IsPrisonerOfColony)
{
pawn.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Prisoner);
HealthUtility.TryAnesthetize(pawn);
}
}
// 设置初始状态
pawn.needs.SetInitialLevels();
pawn.mindState?.SetupLastHumanMeatTick();
}
// 分配 LordJob如果启用
if (Props.assignAssaultLordJob && pawns.Count > 0)
{
AssignAssaultLordJob();
}
}
// 分配袭击殖民地的 LordJob
private void AssignAssaultLordJob()
{
// 按派系分组 Pawn
var pawnsByFaction = new Dictionary<Faction, List<Pawn>>();
foreach (Pawn pawn in pawns)
{
// 跳过玩家派系和囚犯
if (pawn.Faction == Faction.OfPlayer || pawn.IsPrisonerOfColony)
continue;
// 跳过无效派系
if (pawn.Faction == null)
continue;
if (!pawnsByFaction.ContainsKey(pawn.Faction))
{
pawnsByFaction[pawn.Faction] = new List<Pawn>();
}
pawnsByFaction[pawn.Faction].Add(pawn);
}
// 为每个派系创建 LordJob
foreach (var factionGroup in pawnsByFaction)
{
Faction faction = factionGroup.Key;
List<Pawn> factionPawns = factionGroup.Value;
if (factionPawns.Count == 0)
continue;
// 创建 LordJob_AssaultColony
LordJob_AssaultColony lordJob = new LordJob_AssaultColony(
faction,
Props.canKidnap,
Props.canTimeoutOrFlee,
Props.useSappers,
Props.useAvoidGridSmart,
Props.canSteal,
Props.useBreachers,
Props.canPickUpOpportunisticWeapons
);
// 创建 Lord
Lord lord = LordMaker.MakeNewLord(faction, lordJob, Find.CurrentMap, factionPawns);
WulaLog.Debug($"Assigned assault lord job to {factionPawns.Count} pawns of faction {faction.Name}");
}
}
private void DropSinglePod(List<Thing> thingsToDrop, IntVec3 dropCenter, Map map)
{
// 使用原版空投仓系统
if (Props.dropAllInSamePod)
{
// 所有物品在一个空投仓中
DropPodUtility.DropThingGroupsNear(
dropCenter,
map,
new List<List<Thing>> { thingsToDrop },
openDelay: 110,
instaDrop: false,
leaveSlag: Props.leaveSlag,
canRoofPunch: !Props.useTradeDropSpot,
forbid: false,
allowFogged: Props.allowFogged,
canTransfer: false,
faction: Props.faction
);
}
else
{
// 每个物品单独空投仓
DropPodUtility.DropThingsNear(
dropCenter,
map,
thingsToDrop,
openDelay: 110,
canInstaDropDuringInit: false,
leaveSlag: Props.leaveSlag,
canRoofPunch: !Props.useTradeDropSpot,
forbid: false,
allowFogged: Props.allowFogged,
faction: Props.faction
);
}
}
private void DropMultiplePods(List<Thing> thingsToDrop, IntVec3 dropCenter, Map map)
{
List<List<Thing>> podGroups = new List<List<Thing>>();
// 首先,确保所有物品都不在任何容器中
foreach (Thing thing in thingsToDrop)
{
if (thing.holdingOwner != null)
{
thing.holdingOwner.Remove(thing);
}
}
if (Props.dropAllInSamePod)
{
// 所有物品在一个空投仓中,但生成多个相同的空投仓
for (int i = 0; i < Props.dropCount; i++)
{
List<Thing> podItems = CreatePodItemsCopy(thingsToDrop);
podGroups.Add(podItems);
}
}
else
{
// 将原始物品分配到多个空投仓中
List<Thing> remainingItems = new List<Thing>(thingsToDrop);
for (int i = 0; i < Props.dropCount; i++)
{
List<Thing> podItems = new List<Thing>();
int itemsPerPod = Mathf.CeilToInt((float)remainingItems.Count / (Props.dropCount - i));
for (int j = 0; j < itemsPerPod && remainingItems.Count > 0; j++)
{
podItems.Add(remainingItems[0]);
remainingItems.RemoveAt(0);
}
podGroups.Add(podItems);
}
}
// 投掷多个空投仓组
foreach (List<Thing> podGroup in podGroups)
{
if (podGroup.Count == 0) continue;
IntVec3 scatterPos = GetScatteredDropPos(dropCenter, map);
DropPodUtility.DropThingGroupsNear(
scatterPos,
map,
new List<List<Thing>> { podGroup },
openDelay: 110,
instaDrop: false,
leaveSlag: Props.leaveSlag,
canRoofPunch: !Props.useTradeDropSpot,
forbid: false,
allowFogged: Props.allowFogged,
canTransfer: false,
faction: Props.faction
);
}
}
// 创建物品的深拷贝
private List<Thing> CreatePodItemsCopy(List<Thing> originalItems)
{
List<Thing> copies = new List<Thing>();
foreach (Thing original in originalItems)
{
if (original is Pawn originalPawn)
{
// 对于 Pawn重新生成
Pawn newPawn = GeneratePawn(originalPawn.kindDef);
if (newPawn != null)
{
copies.Add(newPawn);
}
}
else
{
// 对于物品,创建副本
Thing copy = ThingMaker.MakeThing(original.def, original.Stuff);
copy.stackCount = original.stackCount;
// 复制其他重要属性
if (original.def.useHitPoints)
{
copy.HitPoints = original.HitPoints;
}
copies.Add(copy);
}
}
return copies;
}
private IntVec3 GetDropCenter(FlyOver flyOver)
{
// 计算投掷中心位置(基于当前飞行位置 + 偏移)
Vector3 currentPos = Vector3.Lerp(
flyOver.startPosition.ToVector3(),
flyOver.endPosition.ToVector3(),
flyOver.currentProgress
);
IntVec3 dropCenter = currentPos.ToIntVec3() + Props.dropOffset;
// 如果使用贸易空投点,找到贸易空投点
if (Props.useTradeDropSpot)
{
dropCenter = DropCellFinder.TradeDropSpot(flyOver.Map);
}
return dropCenter;
}
private IntVec3 GetScatteredDropPos(IntVec3 center, Map map)
{
if (Props.scatterRadius <= 0)
return center;
// 在散布半径内找到有效位置
for (int i = 0; i < 10; i++)
{
IntVec3 scatterPos = center + new IntVec3(
Rand.RangeInclusive(-(int)Props.scatterRadius, (int)Props.scatterRadius),
0,
Rand.RangeInclusive(-(int)Props.scatterRadius, (int)Props.scatterRadius)
);
if (scatterPos.InBounds(map) &&
scatterPos.Standable(map) &&
!scatterPos.Roofed(map) &&
(Props.allowFogged || !scatterPos.Fogged(map)))
{
return scatterPos;
}
}
// 如果找不到有效位置,返回随机空投点
return DropCellFinder.RandomDropSpot(map);
}
private void SendDropLetter(List<Thing> thingsToDrop, IntVec3 dropCenter, Map map)
{
TaggedString text = null;
TaggedString label = null;
// 生成信件内容(模仿原版逻辑)
if (Props.joinPlayer && pawns.Count == 1 && pawns[0].RaceProps.Humanlike)
{
text = "LetterRefugeeJoins".Translate(pawns[0].Named("PAWN"));
label = "LetterLabelRefugeeJoins".Translate(pawns[0].Named("PAWN"));
PawnRelationUtility.TryAppendRelationsWithColonistsInfo(ref text, ref label, pawns[0]);
}
else
{
text = "LetterQuestDropPodsArrived".Translate(GenLabel.ThingsLabel(thingsToDrop));
label = "LetterLabelQuestDropPodsArrived".Translate();
PawnRelationUtility.Notify_PawnsSeenByPlayer_Letter(
pawns,
ref label,
ref text,
"LetterRelatedPawnsNeutralGroup".Translate(Faction.OfPlayer.def.pawnsPlural),
informEvenIfSeenBefore: true
);
}
// 应用自定义文本
label = (Props.customLetterLabel.NullOrEmpty() ? label : Props.customLetterLabel.Formatted(label.Named("BASELABEL")));
text = (Props.customLetterText.NullOrEmpty() ? text : Props.customLetterText.Formatted(text.Named("BASETEXT")));
// 发送信件
Find.LetterStack.ReceiveLetter(
label,
text,
Props.customLetterDef ?? LetterDefOf.PositiveEvent,
new TargetInfo(dropCenter, map)
);
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref hasDropped, "hasDropped", false);
Scribe_Values.Look(ref ticksUntilNextDrop, "ticksUntilNextDrop", 0);
Scribe_Values.Look(ref waitingForSignal, "waitingForSignal", false);
Scribe_Collections.Look(ref items, "items", LookMode.Deep);
Scribe_Collections.Look(ref pawns, "pawns", LookMode.Reference);
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
if (DebugSettings.ShowDevGizmos && parent is FlyOver)
{
yield return new Command_Action
{
defaultLabel = "Dev: Trigger Drop Pods",
action = () => DropPods(parent as FlyOver)
};
yield return new Command_Action
{
defaultLabel = "Dev: Generate Pawns Now",
action = () =>
{
GeneratePawnsFromKinds();
Messages.Message($"Generated {pawns.Count} pawns", MessageTypeDefOf.NeutralEvent);
}
};
if (Props.waitForExternalSignal)
{
yield return new Command_Action
{
defaultLabel = "Dev: Send External Signal",
action = () => TriggerDropFromSignal()
};
}
}
}
// 公共方法:供其他 Comps 调用以触发投掷
public void TriggerDropPods()
{
if (parent is FlyOver flyOver)
{
DropPods(flyOver);
}
}
}
}