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