508 lines
20 KiB
C#
508 lines
20 KiB
C#
// File: Building_Ootheca.cs
|
||
using RimWorld;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
using Verse;
|
||
using Verse.AI;
|
||
|
||
namespace ArachnaeSwarm
|
||
{
|
||
public class Building_Ootheca : Building, IFluxController, ILarvaActivatable
|
||
{
|
||
// === 通量系统字段 ===
|
||
private float neutronFlux = 0.5f;
|
||
private FluxMode fluxMode = FluxMode.Balance;
|
||
|
||
// === 孵化核心字段 ===
|
||
public bool isIncubating = false;
|
||
public float incubationProgress = 0f;
|
||
public float incubationDuration = 0f;
|
||
public PawnKindDef incubatingPawnKind = null;
|
||
|
||
// 幼虫交互相关
|
||
public Pawn assignedLarva = null;
|
||
public int larvaOperateTicksRemaining = 0;
|
||
|
||
// 乘数系统
|
||
private float speedMultiplier = 1.0f;
|
||
private float qualityMultiplier = 1.0f;
|
||
private float qualityProgress = 0f;
|
||
private float qualityTotal = 0f;
|
||
|
||
// 时间控制
|
||
protected int lastMultiplierUpdateTick = -1;
|
||
protected const int MultiplierUpdateInterval = 250;
|
||
|
||
// === 属性 ===
|
||
public IFluxController FluxController => this;
|
||
public float NeutronFlux => neutronFlux;
|
||
public float RawFlux => neutronFlux;
|
||
public FluxMode FluxMode => fluxMode;
|
||
public FluxMode CurrentFluxMode => fluxMode;
|
||
public float FluxEfficiency => IFluxControllerExtensions.GetEfficiency(neutronFlux);
|
||
public bool IsAutoMode => fluxMode != FluxMode.Manual;
|
||
public bool IsIncubating => isIncubating;
|
||
public bool IsDormant => neutronFlux < 0.05f;
|
||
|
||
public CompRefuelableNutrition FuelComp => GetComp<CompRefuelableNutrition>();
|
||
public Comp_SwarmMaintenance MaintenanceComp => GetComp<Comp_SwarmMaintenance>();
|
||
public CompIncubatorData IncubatorData => GetComp<CompIncubatorData>();
|
||
|
||
public void SetNeutronFlux(float value) => neutronFlux = Mathf.Clamp01(value);
|
||
public void CycleFluxMode() => fluxMode = (FluxMode)(((int)fluxMode + 1) % 4);
|
||
public void SetFluxMode(FluxMode mode) => fluxMode = mode;
|
||
|
||
public string GetModeName() => fluxMode switch {
|
||
FluxMode.Manual => "手动",
|
||
FluxMode.Quality => "品质",
|
||
FluxMode.Balance => "平衡",
|
||
FluxMode.Speed => "速度",
|
||
_ => "?"
|
||
};
|
||
|
||
public string GetModeShort() => fluxMode switch {
|
||
FluxMode.Manual => "M",
|
||
FluxMode.Quality => "Q",
|
||
FluxMode.Balance => "B",
|
||
FluxMode.Speed => "S",
|
||
_ => "?"
|
||
};
|
||
|
||
public float SpeedMultiplier
|
||
{
|
||
get
|
||
{
|
||
if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval)
|
||
{
|
||
UpdateSpeedMultiplier();
|
||
}
|
||
return speedMultiplier;
|
||
}
|
||
}
|
||
|
||
public float QualityMultiplier => qualityMultiplier;
|
||
public float QualityProgress => qualityProgress;
|
||
public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
|
||
public float AdjustedProgressPercent => incubationDuration > 0 ? incubationProgress / incubationDuration : 0f;
|
||
|
||
// === 描述方法 ===
|
||
public string GetSpeedFactorsDescription()
|
||
{
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("速度因子");
|
||
builder.AppendLine();
|
||
builder.AppendLine("总速度倍率: " + SpeedMultiplier.ToStringPercent());
|
||
return builder.ToString().TrimEndNewlines();
|
||
}
|
||
|
||
public string GetQualityFactorsDescription()
|
||
{
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("质量因子");
|
||
builder.AppendLine();
|
||
builder.AppendLine("总质量倍率: " + QualityMultiplier.ToStringPercent());
|
||
return builder.ToString().TrimEndNewlines();
|
||
}
|
||
|
||
private string BuildCallLarvaDescription(IncubationConfig config)
|
||
{
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("ARA_OothecaIncubator.CallLarvaTitle".Translate());
|
||
builder.AppendLine();
|
||
builder.AppendLine("ARA_OothecaIncubator.LarvaWillCome".Translate());
|
||
builder.AppendLine(config.pawnKind.LabelCap);
|
||
return builder.ToString().TrimEndNewlines();
|
||
}
|
||
|
||
// === 幼虫交互 ===
|
||
public void CallLarva()
|
||
{
|
||
if (isIncubating || assignedLarva != null) return;
|
||
var larva = FindLarva();
|
||
if (larva == null)
|
||
{
|
||
Messages.Message("ARA_OothecaIncubator.NoLarvaeFound".Translate(), MessageTypeDefOf.RejectInput);
|
||
return;
|
||
}
|
||
|
||
var job = JobMaker.MakeJob(ARA_JobDefOf.ARA_OperateIncubator, this);
|
||
larva.jobs.TryTakeOrderedJob(job, JobTag.MiscWork);
|
||
assignedLarva = larva;
|
||
Messages.Message("ARA_OothecaIncubator.LarvaCalled".Translate(), MessageTypeDefOf.PositiveEvent);
|
||
}
|
||
|
||
private Pawn FindLarva()
|
||
{
|
||
float searchRadius = 30f;
|
||
Map map = Map;
|
||
if (map == null) return null;
|
||
|
||
foreach (var pawn in map.mapPawns.SpawnedPawnsInFaction(Faction))
|
||
{
|
||
if (pawn.def.defName == "ArachnaeBase_Race_Larva" && !pawn.Downed && !pawn.Dead)
|
||
{
|
||
if (Position.DistanceTo(pawn.Position) <= searchRadius)
|
||
return pawn;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public void NotifyLarvaArrived(Pawn larva)
|
||
{
|
||
larvaOperateTicksRemaining = 180;
|
||
assignedLarva = larva;
|
||
}
|
||
|
||
public void NotifyLarvaOperationComplete(Pawn larva)
|
||
{
|
||
var config = IncubatorData?.SelectedConfig;
|
||
if (config == null) return;
|
||
|
||
incubatingPawnKind = config.pawnKind;
|
||
incubationDuration = config.daysRequired * 60000f;
|
||
incubationProgress = 0f;
|
||
qualityTotal = incubationDuration;
|
||
qualityProgress = 0f;
|
||
isIncubating = true;
|
||
assignedLarva = null;
|
||
larvaOperateTicksRemaining = 0;
|
||
|
||
Messages.Message("ARA_Msg_IncubationStarted".Translate(incubatingPawnKind.LabelCap), MessageTypeDefOf.PositiveEvent);
|
||
}
|
||
|
||
public void CancelIncubation()
|
||
{
|
||
isIncubating = false;
|
||
incubationProgress = 0f;
|
||
incubationDuration = 0f;
|
||
incubatingPawnKind = null;
|
||
Messages.Message("ARA_Msg_IncubationCancelled".Translate(), MessageTypeDefOf.NeutralEvent);
|
||
}
|
||
|
||
private void CompleteIncubation()
|
||
{
|
||
if (incubatingPawnKind == null) return;
|
||
|
||
float finalQuality = QualityPercent;
|
||
var pawn = PawnGenerator.GeneratePawn(incubatingPawnKind, Faction);
|
||
ApplyQualityEffects(pawn, finalQuality);
|
||
|
||
GenSpawn.Spawn(pawn, Position, Map);
|
||
|
||
isIncubating = false;
|
||
incubatingPawnKind = null;
|
||
Messages.Message("ARA_Msg_IncubationComplete".Translate(pawn.LabelCap), MessageTypeDefOf.PositiveEvent);
|
||
Destroy();
|
||
}
|
||
|
||
// === Tick方法 ===
|
||
protected override void Tick()
|
||
{
|
||
base.Tick();
|
||
|
||
// 清理无效的幼虫引用
|
||
if (assignedLarva != null)
|
||
{
|
||
if (assignedLarva.Dead || assignedLarva.Destroyed ||
|
||
assignedLarva.CurJobDef == null ||
|
||
assignedLarva.CurJobDef.defName != "ARA_OperateIncubator" ||
|
||
assignedLarva.CurJob?.targetA.Thing != this)
|
||
{
|
||
assignedLarva = null;
|
||
}
|
||
}
|
||
|
||
if (larvaOperateTicksRemaining > 0)
|
||
{
|
||
larvaOperateTicksRemaining--;
|
||
}
|
||
|
||
if (isIncubating)
|
||
{
|
||
if (lastMultiplierUpdateTick < 0 || Find.TickManager.TicksGame - lastMultiplierUpdateTick >= MultiplierUpdateInterval)
|
||
{
|
||
UpdateSpeedMultiplier();
|
||
UpdateQualityMultiplier();
|
||
}
|
||
|
||
if (IsAutoMode && Find.TickManager.TicksGame % 250 == 0)
|
||
{
|
||
CalculateAutoFlux();
|
||
}
|
||
|
||
if (FuelComp != null && neutronFlux > 0.01f)
|
||
{
|
||
float fuelPerTick = (50f * FluxEfficiency) / 60000f;
|
||
FuelComp.ConsumeFuel(fuelPerTick);
|
||
}
|
||
|
||
if (IsDormant)
|
||
{
|
||
float qualityDecay = (qualityTotal * 0.1f) / 60000f;
|
||
qualityProgress = Mathf.Max(0f, qualityProgress - qualityDecay);
|
||
|
||
if (qualityProgress <= 0 && qualityTotal > 0)
|
||
{
|
||
Messages.Message("ARA_Msg_IncubatorBrokenQualityZero".Translate(), this, MessageTypeDefOf.NegativeEvent);
|
||
Destroy(DestroyMode.KillFinalize);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 进度增长
|
||
float fluxSpeed = SpeedMultiplier * FluxEfficiency * 5f;
|
||
incubationProgress += fluxSpeed;
|
||
|
||
// 品质增长(新公式:50%通量时与进度同步)
|
||
float qualityGain = IncubatorUtils.CalculateQualityGainNew(fluxSpeed, neutronFlux);
|
||
qualityProgress = Mathf.Min(qualityProgress + qualityGain, qualityTotal);
|
||
}
|
||
|
||
if (incubationProgress >= incubationDuration)
|
||
{
|
||
CompleteIncubation();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CalculateAutoFlux()
|
||
{
|
||
if (fluxMode == FluxMode.Manual) return;
|
||
|
||
float targetFlux = 0.5f;
|
||
float incubationPercent = AdjustedProgressPercent;
|
||
float gQualityPercent = QualityPercent;
|
||
float gap = gQualityPercent - incubationPercent;
|
||
|
||
switch (fluxMode)
|
||
{
|
||
case FluxMode.Quality:
|
||
if (gQualityPercent >= 0.98f) targetFlux = 1.0f;
|
||
else if (gap > 0.2f) targetFlux = 0.6f;
|
||
else if (gap > 0.1f) targetFlux = 0.45f;
|
||
else if (gap > 0f) targetFlux = 0.35f;
|
||
else targetFlux = 0.2f;
|
||
break;
|
||
|
||
case FluxMode.Speed:
|
||
if (gQualityPercent >= 0.95f) targetFlux = 1.0f;
|
||
else if (gQualityPercent < 0.3f && incubationPercent > 0.5f) targetFlux = 0.7f;
|
||
else if (gQualityPercent < 0.2f && incubationPercent > 0.7f) targetFlux = 0.5f;
|
||
else targetFlux = 1.0f;
|
||
break;
|
||
|
||
case FluxMode.Balance:
|
||
default:
|
||
if (gQualityPercent >= 0.95f) targetFlux = 1.0f;
|
||
else if (gap > 0.15f) targetFlux = 0.8f;
|
||
else if (gap > 0.05f) targetFlux = 0.6f;
|
||
else if (gap > -0.05f) targetFlux = 0.5f;
|
||
else if (gap > -0.15f) targetFlux = 0.35f;
|
||
else targetFlux = 0.2f;
|
||
break;
|
||
}
|
||
|
||
if (FuelComp != null && FuelComp.Fuel < 2f) targetFlux = Mathf.Min(targetFlux, 0.2f);
|
||
|
||
float adjustSpeed = 0.03f;
|
||
if (neutronFlux < targetFlux) neutronFlux = Mathf.Min(neutronFlux + adjustSpeed, targetFlux);
|
||
else if (neutronFlux > targetFlux) neutronFlux = Mathf.Max(neutronFlux - adjustSpeed, targetFlux);
|
||
|
||
neutronFlux = Mathf.Clamp(neutronFlux, 0.1f, 1.0f);
|
||
}
|
||
|
||
// === 质量效果与奖励 ===
|
||
private void ApplyQualityEffects(Pawn pawn, float qualityPercent)
|
||
{
|
||
ApplyHediffRewards(pawn, qualityPercent);
|
||
}
|
||
|
||
private void ApplyHediffRewards(Pawn pawn, float qualityPercent)
|
||
{
|
||
var config = IncubatorData?.SelectedConfig;
|
||
if (config == null) return;
|
||
|
||
List<HediffDef> rewardHediffs = config.GetRewardHediffs(qualityPercent);
|
||
if (rewardHediffs == null || rewardHediffs.Count == 0) return;
|
||
|
||
int appliedCount = 0;
|
||
foreach (var hediffDef in rewardHediffs)
|
||
{
|
||
if (hediffDef == null) continue;
|
||
if (!pawn.health.hediffSet.HasHediff(hediffDef))
|
||
{
|
||
pawn.health.AddHediff(HediffMaker.MakeHediff(hediffDef, pawn));
|
||
appliedCount++;
|
||
}
|
||
}
|
||
|
||
if (appliedCount > 0)
|
||
{
|
||
string message = config.GetRewardMessage(qualityPercent);
|
||
if (string.IsNullOrEmpty(message))
|
||
message = "ARA_QualityReward_Default".Translate(pawn.LabelShortCap, appliedCount);
|
||
Messages.Message(message, pawn, MessageTypeDefOf.PositiveEvent);
|
||
}
|
||
}
|
||
|
||
// === 辅助与UI ===
|
||
private void UpdateSpeedMultiplier()
|
||
{
|
||
speedMultiplier = 1.0f;
|
||
lastMultiplierUpdateTick = Find.TickManager.TicksGame;
|
||
}
|
||
|
||
private void UpdateQualityMultiplier()
|
||
{
|
||
qualityMultiplier = 1.0f;
|
||
}
|
||
|
||
public float GetRemainingTicks()
|
||
{
|
||
if (!isIncubating) return 0f;
|
||
return Mathf.Max(0f, (incubationDuration - incubationProgress) / (SpeedMultiplier * FluxEfficiency * 5f));
|
||
}
|
||
|
||
public float GetRemainingDays() => GetRemainingTicks() / 60000f;
|
||
public float GetRemainingHours() => (GetRemainingTicks() % 60000f) / 2500f;
|
||
|
||
public override string GetInspectString()
|
||
{
|
||
StringBuilder sb = new StringBuilder();
|
||
if (isIncubating && incubatingPawnKind != null)
|
||
{
|
||
sb.AppendLine("ARA_Status_Incubating".Translate(incubatingPawnKind.LabelCap));
|
||
sb.AppendLine("ARA_Status_Progress".Translate(AdjustedProgressPercent.ToStringPercent()));
|
||
sb.AppendLine("ARA_Status_RemainingTime".Translate(GetRemainingDays().ToString("F1")));
|
||
sb.Append("ARA_Status_SpeedAndQuality".Translate(SpeedMultiplier.ToStringPercent(), QualityMultiplier.ToStringPercent()));
|
||
}
|
||
else if (assignedLarva != null)
|
||
{
|
||
sb.Append(larvaOperateTicksRemaining > 0 ? "ARA_Status_LarvaActivating".Translate() : "ARA_Status_LarvaOnTheWay".Translate());
|
||
}
|
||
else
|
||
{
|
||
var config = IncubatorData?.SelectedConfig;
|
||
if (config != null)
|
||
{
|
||
sb.AppendLine("ARA_Status_Target".Translate(config.pawnKind.LabelCap));
|
||
sb.Append("ARA_Status_Speed".Translate(SpeedMultiplier.ToStringPercent()));
|
||
}
|
||
}
|
||
|
||
var baseStr = base.GetInspectString();
|
||
if (!string.IsNullOrEmpty(baseStr))
|
||
{
|
||
if (sb.Length > 0) sb.AppendLine();
|
||
sb.Append(baseStr);
|
||
}
|
||
return sb.ToString().TrimEndNewlines();
|
||
}
|
||
|
||
public override IEnumerable<Gizmo> GetGizmos()
|
||
{
|
||
foreach (var gizmo in base.GetGizmos())
|
||
{
|
||
if (gizmo is Command_Action cmd && cmd.defaultLabel != null)
|
||
{
|
||
string label = cmd.defaultLabel.ToString();
|
||
if (label.Contains("拆除") || label.Contains("Deconstruct") || label.Contains("半径") || label.Contains("Radius"))
|
||
continue;
|
||
}
|
||
|
||
// 强制将基础组件(如 Refuelable)甚至默认排序为 -100 的东西移到后面
|
||
if (gizmo.Order >= -100f && gizmo.Order <= 0f)
|
||
{
|
||
gizmo.Order = -90f;
|
||
}
|
||
|
||
yield return gizmo;
|
||
}
|
||
|
||
if (Faction == Faction.OfPlayer)
|
||
{
|
||
yield return new Gizmo_IncubationProgress(this);
|
||
yield return new Gizmo_NeutronFlux(this);
|
||
|
||
var config = IncubatorData?.SelectedConfig;
|
||
|
||
// 添加订单按钮(未孵化且未选目标,或已选目标但支持切换)
|
||
if (!isIncubating && assignedLarva == null)
|
||
{
|
||
yield return new Command_Action
|
||
{
|
||
defaultLabel = "ARA_Gizmo_AddOrder".Translate(config != null ? 1 : 0, 1),
|
||
defaultDesc = "ARA_Gizmo_AddOrderDesc_Pawn".Translate(),
|
||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_NodeSwarmIcon", false),
|
||
action = () => IncubatorData?.ShowFloatMenu(),
|
||
Order = 100f
|
||
};
|
||
}
|
||
|
||
// 呼叫幼虫按钮逻辑
|
||
if (!isIncubating && config != null && config.IsResearchComplete)
|
||
{
|
||
if (assignedLarva == null)
|
||
{
|
||
// 无幼虫,可以呼叫
|
||
yield return new Command_Action
|
||
{
|
||
defaultLabel = "ARA_Gizmo_CallLarva".Translate(),
|
||
defaultDesc = BuildCallLarvaDescription(config),
|
||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
|
||
action = CallLarva,
|
||
Order = 100f
|
||
};
|
||
}
|
||
else
|
||
{
|
||
// 幼虫在路上或工作中
|
||
string statusText = larvaOperateTicksRemaining > 0
|
||
? "ARA_Gizmo_LarvaActivating".Translate()
|
||
: "ARA_Gizmo_LarvaOnTheWay".Translate();
|
||
yield return new Command_Action
|
||
{
|
||
defaultLabel = statusText,
|
||
defaultDesc = "ARA_Gizmo_LarvaWorkingDesc".Translate(0),
|
||
icon = ContentFinder<Texture2D>.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
|
||
Disabled = true,
|
||
Order = 100f
|
||
};
|
||
}
|
||
}
|
||
|
||
if (isIncubating)
|
||
{
|
||
yield return new Command_Action
|
||
{
|
||
defaultLabel = "ARA_OothecaIncubator.CancelIncubation".Translate(),
|
||
icon = ContentFinder<Texture2D>.Get("UI/Commands/Cancel", false),
|
||
action = CancelIncubation,
|
||
Order = 100f
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void ExposeData()
|
||
{
|
||
base.ExposeData();
|
||
Scribe_Values.Look(ref isIncubating, "isIncubating", false);
|
||
Scribe_Values.Look(ref incubationProgress, "incubationProgress", 0f);
|
||
Scribe_Values.Look(ref incubationDuration, "incubationDuration", 0f);
|
||
Scribe_Defs.Look(ref incubatingPawnKind, "incubatingPawnKind");
|
||
Scribe_References.Look(ref assignedLarva, "assignedLarva");
|
||
Scribe_Values.Look(ref larvaOperateTicksRemaining, "larvaOperateTicksRemaining", 0);
|
||
Scribe_Values.Look(ref speedMultiplier, "speedMultiplier", 1.0f);
|
||
Scribe_Values.Look(ref qualityMultiplier, "qualityMultiplier", 1.0f);
|
||
Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
|
||
Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
|
||
Scribe_Values.Look(ref neutronFlux, "neutronFlux", 0.5f);
|
||
Scribe_Values.Look(ref fluxMode, "fluxMode", FluxMode.Balance);
|
||
}
|
||
}
|
||
}
|