Merge branch 'master' of https://git.ra3battle.cn/Kalospacer/ArachnaeSwarm
This commit is contained in:
@@ -0,0 +1,458 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
using Verse.AI;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
[StaticConstructorOnStartup]
|
||||||
|
public class Building_SacrificialFuelTank : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn
|
||||||
|
{
|
||||||
|
private CompRefuelable refuelableComp;
|
||||||
|
private Graphic cachedTopGraphic;
|
||||||
|
|
||||||
|
// IThingHolderWithDrawnPawn implementation
|
||||||
|
public float HeldPawnDrawPos_Y => DrawPos.y + 0.03658537f;
|
||||||
|
public float HeldPawnBodyAngle => base.Rotation.AsAngle;
|
||||||
|
public PawnPosture HeldPawnPosture => PawnPosture.LayingOnGroundFaceUp;
|
||||||
|
|
||||||
|
// 燃料转换配置
|
||||||
|
private const float DAMAGE_TO_FUEL_RATIO = 0.1f; // 每点伤害转换为0.1燃料
|
||||||
|
private const float DAMAGE_INTERVAL_TICKS = 250; // 每250ticks造成一次伤害
|
||||||
|
private const float DAMAGE_PER_INTERVAL = 5f; // 每次间隔造成5点伤害
|
||||||
|
|
||||||
|
private Graphic TopGraphic
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (cachedTopGraphic == null)
|
||||||
|
{
|
||||||
|
var modExtension = def.GetModExtension<DefModExtension_SacrificialFuelTank>();
|
||||||
|
if (modExtension != null && !modExtension.topGraphicPath.NullOrEmpty())
|
||||||
|
{
|
||||||
|
cachedTopGraphic = GraphicDatabase.Get(modExtension.graphicClass, modExtension.topGraphicPath, ShaderDatabase.Transparent, def.graphicData.drawSize, Color.white, Color.white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedTopGraphic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompRefuelable RefuelableComp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (refuelableComp == null)
|
||||||
|
{
|
||||||
|
refuelableComp = this.TryGetComp<CompRefuelable>();
|
||||||
|
}
|
||||||
|
return refuelableComp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector3 PawnDrawOffset => Vector3.zero;
|
||||||
|
|
||||||
|
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||||
|
{
|
||||||
|
base.SpawnSetup(map, respawningAfterLoad);
|
||||||
|
refuelableComp = this.TryGetComp<CompRefuelable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
|
||||||
|
{
|
||||||
|
if (mode != DestroyMode.WillReplace && selectedPawn != null && innerContainer.Contains(selectedPawn))
|
||||||
|
{
|
||||||
|
Notify_PawnRemoved();
|
||||||
|
}
|
||||||
|
base.DeSpawn(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 造成伤害并转换为燃料
|
||||||
|
private void ApplySacrificialDamage(Pawn pawn)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pawn.Dead || pawn.Downed) return;
|
||||||
|
|
||||||
|
// 选择随机的身体部位
|
||||||
|
BodyPartRecord targetPart = GetRandomVulnerablePart(pawn);
|
||||||
|
if (targetPart == null) return;
|
||||||
|
|
||||||
|
// 造成伤害
|
||||||
|
DamageInfo damage = new DamageInfo(
|
||||||
|
DamageDefOf.Cut, // 使用切割伤害
|
||||||
|
DAMAGE_PER_INTERVAL,
|
||||||
|
0.5f, // 中等护甲穿透
|
||||||
|
-1f,
|
||||||
|
instigator: null,
|
||||||
|
hitPart: targetPart
|
||||||
|
);
|
||||||
|
|
||||||
|
float damageDealt = pawn.TakeDamage(damage).TotalDamageDealt;
|
||||||
|
|
||||||
|
// 将伤害转换为燃料
|
||||||
|
if (damageDealt > 0 && RefuelableComp != null)
|
||||||
|
{
|
||||||
|
float fuelToAdd = damageDealt * DAMAGE_TO_FUEL_RATIO;
|
||||||
|
RefuelableComp.Refuel(fuelToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果pawn死亡,停止处理
|
||||||
|
if (pawn.Dead)
|
||||||
|
{
|
||||||
|
OnPawnDied();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error($"Error applying sacrificial damage to {pawn}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BodyPartRecord GetRandomVulnerablePart(Pawn pawn)
|
||||||
|
{
|
||||||
|
// 优先选择外部身体部位
|
||||||
|
var vulnerableParts = pawn.health.hediffSet.GetNotMissingParts()
|
||||||
|
.Where(part => part.depth == BodyPartDepth.Outside &&
|
||||||
|
!part.def.conceptual &&
|
||||||
|
part.def.hitPoints > 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return vulnerableParts.Count > 0 ? vulnerableParts.RandomElement() : pawn.RaceProps.body.corePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int damageTickCounter = 0;
|
||||||
|
|
||||||
|
protected override void Tick()
|
||||||
|
{
|
||||||
|
base.Tick();
|
||||||
|
|
||||||
|
if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn)))
|
||||||
|
{
|
||||||
|
OnStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base.Working && selectedPawn != null && !selectedPawn.Dead)
|
||||||
|
{
|
||||||
|
damageTickCounter++;
|
||||||
|
|
||||||
|
// 定期造成伤害
|
||||||
|
if (damageTickCounter >= DAMAGE_INTERVAL_TICKS)
|
||||||
|
{
|
||||||
|
ApplySacrificialDamage(selectedPawn);
|
||||||
|
damageTickCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查pawn是否死亡
|
||||||
|
if (selectedPawn.Dead)
|
||||||
|
{
|
||||||
|
OnPawnDied();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPawnDied()
|
||||||
|
{
|
||||||
|
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
|
||||||
|
{
|
||||||
|
// 弹出死亡的pawn
|
||||||
|
Notify_PawnRemoved();
|
||||||
|
innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
|
||||||
|
OnStop();
|
||||||
|
|
||||||
|
// 可以选择在这里添加一些效果,比如声音或信息
|
||||||
|
Messages.Message("SacrificialFuelTank_PawnDied".Translate(selectedPawn.LabelShort), this, MessageTypeDefOf.NeutralEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AcceptanceReport CanAcceptPawn(Pawn pawn)
|
||||||
|
{
|
||||||
|
if (base.Working)
|
||||||
|
{
|
||||||
|
return "Occupied".Translate();
|
||||||
|
}
|
||||||
|
if (selectedPawn != null && selectedPawn != pawn)
|
||||||
|
{
|
||||||
|
return "WaitingForPawn".Translate(selectedPawn.Named("PAWN"));
|
||||||
|
}
|
||||||
|
if (pawn.Dead || pawn.Downed)
|
||||||
|
{
|
||||||
|
return "PawnMustBeAlive".Translate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁止虫群成员
|
||||||
|
if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster) ||
|
||||||
|
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) ||
|
||||||
|
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker))
|
||||||
|
{
|
||||||
|
return "PawnIsHiveMember".Translate(pawn.Named("PAWN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许殖民者、囚犯和奴隶,但不能是任务寄宿者
|
||||||
|
bool isColonist = pawn.IsColonist;
|
||||||
|
bool isPrisoner = pawn.IsPrisonerOfColony;
|
||||||
|
bool isSlave = pawn.IsSlaveOfColony;
|
||||||
|
|
||||||
|
return (isColonist || isPrisoner || isSlave) && !pawn.IsQuestLodger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TryAcceptPawn(Pawn pawn)
|
||||||
|
{
|
||||||
|
if (!CanAcceptPawn(pawn).Accepted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedPawn = pawn;
|
||||||
|
bool deselected = pawn.DeSpawnOrDeselect();
|
||||||
|
if (innerContainer.TryAddOrTransfer(pawn))
|
||||||
|
{
|
||||||
|
startTick = Find.TickManager.TicksGame;
|
||||||
|
damageTickCounter = 0; // 重置伤害计数器
|
||||||
|
}
|
||||||
|
if (deselected)
|
||||||
|
{
|
||||||
|
Find.Selector.Select(pawn, playSound: false, forceDesignatorDeselect: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStop()
|
||||||
|
{
|
||||||
|
selectedPawn = null;
|
||||||
|
startTick = -1;
|
||||||
|
damageTickCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Notify_PawnRemoved()
|
||||||
|
{
|
||||||
|
// 可以在这里添加声音效果
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Gizmo> GetGizmos()
|
||||||
|
{
|
||||||
|
foreach (Gizmo gizmo in base.GetGizmos())
|
||||||
|
{
|
||||||
|
yield return gizmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base.Working)
|
||||||
|
{
|
||||||
|
// 插入人员的操作
|
||||||
|
var insertCommand = new Command_Action
|
||||||
|
{
|
||||||
|
defaultLabel = "InsertSacrificialPawn".Translate() + "...",
|
||||||
|
defaultDesc = "InsertSacrificialPawnDesc".Translate(),
|
||||||
|
icon = ContentFinder<Texture2D>.Get("UI/Commands/Sacrifice"),
|
||||||
|
action = () =>
|
||||||
|
{
|
||||||
|
List<FloatMenuOption> list = new List<FloatMenuOption>();
|
||||||
|
foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
|
||||||
|
{
|
||||||
|
if (CanAcceptPawn(p).Accepted)
|
||||||
|
{
|
||||||
|
list.Add(new FloatMenuOption(p.LabelCap, () => SelectPawn(p), p, Color.white));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!list.Any())
|
||||||
|
{
|
||||||
|
list.Add(new FloatMenuOption("NoViablePawns".Translate(), null));
|
||||||
|
}
|
||||||
|
Find.WindowStack.Add(new FloatMenu(list));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!base.AnyAcceptablePawns)
|
||||||
|
{
|
||||||
|
insertCommand.Disable("NoViablePawns".Translate());
|
||||||
|
}
|
||||||
|
yield return insertCommand;
|
||||||
|
|
||||||
|
// 指派搬运囚犯/奴隶的操作
|
||||||
|
var command_CarryPrisoner = new Command_Action
|
||||||
|
{
|
||||||
|
defaultLabel = "AssignCarryPrisoner".Translate() + "...",
|
||||||
|
defaultDesc = "AssignCarryPrisonerSacrificialDesc".Translate(),
|
||||||
|
icon = ContentFinder<Texture2D>.Get("UI/Commands/Attack"),
|
||||||
|
action = () =>
|
||||||
|
{
|
||||||
|
List<FloatMenuOption> list = new List<FloatMenuOption>();
|
||||||
|
foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
|
||||||
|
{
|
||||||
|
if (CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony))
|
||||||
|
{
|
||||||
|
list.Add(new FloatMenuOption(
|
||||||
|
p.LabelCap + " (" + (p.IsPrisonerOfColony ? "Prisoner" : "Slave") + ")",
|
||||||
|
() => AssignCarrierForPrisoner(p),
|
||||||
|
p,
|
||||||
|
Color.white
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!list.Any())
|
||||||
|
{
|
||||||
|
list.Add(new FloatMenuOption("NoPrisonersOrSlaves".Translate(), null));
|
||||||
|
}
|
||||||
|
Find.WindowStack.Add(new FloatMenu(list));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool hasPrisonersOrSlaves = base.Map.mapPawns.AllPawnsSpawned
|
||||||
|
.Any(p => CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony));
|
||||||
|
|
||||||
|
if (!hasPrisonersOrSlaves)
|
||||||
|
{
|
||||||
|
command_CarryPrisoner.Disable("NoPrisonersOrSlaves".Translate());
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return command_CarryPrisoner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetInspectString()
|
||||||
|
{
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.Append(base.GetInspectString());
|
||||||
|
|
||||||
|
if (base.Working && selectedPawn != null)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLineIfNotEmpty().Append("CasketContains".Translate().ToString() + ": " + selectedPawn.NameShortColored.Resolve());
|
||||||
|
|
||||||
|
// 显示燃料转换效率
|
||||||
|
if (RefuelableComp != null)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLineIfNotEmpty().Append("FuelConversionRate".Translate() + ": " + (DAMAGE_TO_FUEL_RATIO * 100).ToString("F1") + "%");
|
||||||
|
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity.ToString("F1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示pawn的健康状态
|
||||||
|
if (selectedPawn.health.summaryHealth.SummaryHealthPercent < 1.0f)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLineIfNotEmpty().Append("PawnHealth".Translate() + ": " + selectedPawn.health.summaryHealth.SummaryHealthPercent.ToStringPercent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (selectedPawn != null)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<FloatMenuOption> GetFloatMenuOptions(Pawn selPawn)
|
||||||
|
{
|
||||||
|
foreach (FloatMenuOption floatMenuOption in base.GetFloatMenuOptions(selPawn))
|
||||||
|
{
|
||||||
|
yield return floatMenuOption;
|
||||||
|
}
|
||||||
|
if (!selPawn.CanReach(this, PathEndMode.InteractionCell, Danger.Deadly))
|
||||||
|
{
|
||||||
|
yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + "NoPath".Translate().CapitalizeFirst(), null);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
AcceptanceReport acceptanceReport = CanAcceptPawn(selPawn);
|
||||||
|
if (acceptanceReport.Accepted)
|
||||||
|
{
|
||||||
|
yield return FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("EnterSacrificialTank".Translate(this), () => SelectPawn(selPawn)), selPawn, this);
|
||||||
|
}
|
||||||
|
else if (!acceptanceReport.Reason.NullOrEmpty())
|
||||||
|
{
|
||||||
|
yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + acceptanceReport.Reason.CapitalizeFirst(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DynamicDrawPhaseAt(DrawPhase phase, Vector3 drawLoc, bool flip = false)
|
||||||
|
{
|
||||||
|
if (base.Working && selectedPawn != null && innerContainer.Contains(selectedPawn))
|
||||||
|
{
|
||||||
|
selectedPawn.Drawer.renderer.DynamicDrawPhaseAt(phase, drawLoc + PawnDrawOffset, null, neverAimWeapon: true);
|
||||||
|
}
|
||||||
|
base.DynamicDrawPhaseAt(phase, drawLoc, flip);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
|
||||||
|
{
|
||||||
|
base.DrawAt(drawLoc, flip);
|
||||||
|
if (TopGraphic != null)
|
||||||
|
{
|
||||||
|
TopGraphic.Draw(DrawPos + Altitudes.AltIncVect * 2f, base.Rotation, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Job CreateCarryJobForPrisoner(Pawn prisoner, Pawn carrier)
|
||||||
|
{
|
||||||
|
if (prisoner == null || carrier == null)
|
||||||
|
return null;
|
||||||
|
if (!CanAcceptPawn(prisoner).Accepted)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
JobDef carryJobDef = DefDatabase<JobDef>.GetNamed("ARA_CarryPrisonerToSacrificialTank");
|
||||||
|
if (carryJobDef == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Job job = JobMaker.MakeJob(carryJobDef, prisoner, this);
|
||||||
|
job.count = 1;
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Pawn> GetAvailableCarriers()
|
||||||
|
{
|
||||||
|
foreach (Pawn pawn in base.Map.mapPawns.AllPawnsSpawned)
|
||||||
|
{
|
||||||
|
if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) ||
|
||||||
|
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker) ||
|
||||||
|
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster))
|
||||||
|
{
|
||||||
|
if (pawn.workSettings != null && pawn.workSettings.WorkIsActive(WorkTypeDefOf.Hauling) &&
|
||||||
|
!pawn.Downed && !pawn.IsPrisoner && pawn.CanReach(this, PathEndMode.InteractionCell, Danger.Some))
|
||||||
|
{
|
||||||
|
yield return pawn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssignCarrierForPrisoner(Pawn prisoner)
|
||||||
|
{
|
||||||
|
if (prisoner == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var availableCarriers = GetAvailableCarriers().ToList();
|
||||||
|
if (!availableCarriers.Any())
|
||||||
|
{
|
||||||
|
Messages.Message("NoAvailableHiveCarriers".Translate(), MessageTypeDefOf.RejectInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FloatMenuOption> options = new List<FloatMenuOption>();
|
||||||
|
foreach (Pawn carrier in availableCarriers)
|
||||||
|
{
|
||||||
|
options.Add(new FloatMenuOption(
|
||||||
|
carrier.LabelCap,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Job carryJob = CreateCarryJobForPrisoner(prisoner, carrier);
|
||||||
|
if (carryJob != null)
|
||||||
|
{
|
||||||
|
carrier.jobs.TryTakeOrderedJob(carryJob, JobTag.MiscWork);
|
||||||
|
Messages.Message("CarrierAssigned".Translate(carrier.LabelShort, prisoner.LabelShort), MessageTypeDefOf.NeutralEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
carrier,
|
||||||
|
Color.white
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.Any())
|
||||||
|
{
|
||||||
|
Find.WindowStack.Add(new FloatMenu(options));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Messages.Message("NoAvailableCarriers".Translate(), MessageTypeDefOf.RejectInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class DefModExtension_SacrificialFuelTank : DefModExtension
|
||||||
|
{
|
||||||
|
public string topGraphicPath;
|
||||||
|
public Type graphicClass = typeof(Graphic_Multi);
|
||||||
|
|
||||||
|
// 可配置的伤害和燃料转换参数
|
||||||
|
public float damageToFuelRatio = 0.1f;
|
||||||
|
public int damageIntervalTicks = 250;
|
||||||
|
public float damagePerInterval = 5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user