整理scoure
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom CompProperties for our ritual spot. It no longer needs a tag.
|
||||
/// </summary>
|
||||
public class CompProperties_WulaRitualSpot : CompProperties
|
||||
{
|
||||
public CompProperties_WulaRitualSpot()
|
||||
{
|
||||
this.compClass = typeof(CompWulaRitualSpot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The core component for the custom ritual spot. Generates its own gizmos
|
||||
/// by specifically looking for Defs that inherit from our custom PsychicRitualDef_Wula base class.
|
||||
/// </summary>
|
||||
public class CompWulaRitualSpot : ThingComp
|
||||
{
|
||||
public CompProperties_WulaRitualSpot Props => (CompProperties_WulaRitualSpot)this.props;
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (Gizmo gizmo in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
// Find all rituals that are of our custom base class type.
|
||||
foreach (PsychicRitualDef_Wula ritualDef in DefDatabase<PsychicRitualDef_Wula>.AllDefs)
|
||||
{
|
||||
if (ritualDef.Visible)
|
||||
{
|
||||
Command_Action command_Action = new Command_Action();
|
||||
command_Action.defaultLabel = ritualDef.LabelCap.Resolve();
|
||||
command_Action.defaultDesc = ritualDef.description;
|
||||
command_Action.icon = ritualDef.uiIcon;
|
||||
command_Action.action = delegate
|
||||
{
|
||||
// Mimic vanilla initialization
|
||||
TargetInfo target = new TargetInfo(this.parent);
|
||||
PsychicRitualRoleAssignments assignments = ritualDef.BuildRoleAssignments(target);
|
||||
PsychicRitualCandidatePool candidatePool = ritualDef.FindCandidatePool();
|
||||
ritualDef.InitializeCast(this.parent.Map);
|
||||
Find.WindowStack.Add(new Dialog_BeginPsychicRitual(ritualDef, candidatePool, assignments, this.parent.Map));
|
||||
};
|
||||
|
||||
// Corrected check for cooldown and other requirements
|
||||
AcceptanceReport acceptanceReport = Find.PsychicRitualManager.CanInvoke(ritualDef, this.parent.Map);
|
||||
if (!acceptanceReport.Accepted)
|
||||
{
|
||||
command_Action.Disable(acceptanceReport.Reason.CapitalizeFirst());
|
||||
}
|
||||
|
||||
yield return command_Action;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class PsychicRitualDef_AddHediff : PsychicRitualDef_Wula
|
||||
{
|
||||
public HediffDef hediff;
|
||||
|
||||
public override List<PsychicRitualToil> CreateToils(PsychicRitual psychicRitual, PsychicRitualGraph parent)
|
||||
{
|
||||
List<PsychicRitualToil> list = base.CreateToils(psychicRitual, parent);
|
||||
list.Add(new PsychicRitualToil_AddHediff(TargetRole, hediff));
|
||||
list.Add(new PsychicRitualToil_TargetCleanup(InvokerRole, TargetRole));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// This class serves as a custom base for all Wula rituals.
|
||||
/// It inherits from PsychicRitualDef_InvocationCircle to retain all vanilla functionality,
|
||||
/// but provides a unique type that our custom CompWulaRitualSpot can specifically look for,
|
||||
/// ensuring these rituals only appear on our custom ritual spot.
|
||||
/// </summary>
|
||||
public class PsychicRitualDef_Wula : PsychicRitualDef_WulaBase
|
||||
{
|
||||
// This class can be expanded with Wula-specific ritual properties if needed in the future.
|
||||
// For now, its existence is enough to separate our rituals from the vanilla ones.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class PsychicRitualDef_WulaBase : PsychicRitualDef
|
||||
{
|
||||
public enum InvalidTargetReasonEnum
|
||||
{
|
||||
None,
|
||||
AreaNotClear
|
||||
}
|
||||
|
||||
private class RitualQualityOffsetCount
|
||||
{
|
||||
public float offset;
|
||||
public int count;
|
||||
|
||||
public RitualQualityOffsetCount(int count, float offset)
|
||||
{
|
||||
this.count = count;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
public FloatRange hoursUntilHoraxEffect;
|
||||
public FloatRange hoursUntilOutcome;
|
||||
public float invocationCircleRadius = 3.9f;
|
||||
[MustTranslate]
|
||||
public string outcomeDescription;
|
||||
public float psychicSensitivityPowerFactor = 0.25f;
|
||||
protected PsychicRitualRoleDef invokerRole;
|
||||
protected PsychicRitualRoleDef chanterRole;
|
||||
protected PsychicRitualRoleDef targetRole;
|
||||
protected PsychicRitualRoleDef defenderRole;
|
||||
protected IngredientCount requiredOffering;
|
||||
protected string timeAndOfferingLabelCached;
|
||||
|
||||
public static readonly SimpleCurve PsychicSensitivityToPowerFactor = new SimpleCurve
|
||||
{
|
||||
new CurvePoint(0f, 0f),
|
||||
new CurvePoint(1f, 0.5f),
|
||||
new CurvePoint(2f, 0.9f),
|
||||
new CurvePoint(3f, 1f)
|
||||
};
|
||||
|
||||
protected const int DurationTicksWaitPostEffect = 120;
|
||||
private static Dictionary<PsychicRitualRoleDef, List<IntVec3>> tmpParticipants = new Dictionary<PsychicRitualRoleDef, List<IntVec3>>(8);
|
||||
private List<Pawn> tmpGatheringPawns = new List<Pawn>(8);
|
||||
|
||||
public virtual PsychicRitualRoleDef InvokerRole => invokerRole;
|
||||
public virtual PsychicRitualRoleDef ChanterRole => chanterRole;
|
||||
public virtual PsychicRitualRoleDef TargetRole => targetRole;
|
||||
public virtual PsychicRitualRoleDef DefenderRole => defenderRole;
|
||||
public virtual IngredientCount RequiredOffering => requiredOffering;
|
||||
public TaggedString CooldownLabel => "PsychicRitualCooldownLabel".Translate() + ": " + (cooldownHours * 2500).ToStringTicksToPeriod();
|
||||
|
||||
public override List<PsychicRitualRoleDef> Roles
|
||||
{
|
||||
get
|
||||
{
|
||||
List<PsychicRitualRoleDef> roles = base.Roles;
|
||||
if (InvokerRole != null) roles.Add(InvokerRole);
|
||||
if (TargetRole != null) roles.Add(TargetRole);
|
||||
if (ChanterRole != null) roles.Add(ChanterRole);
|
||||
if (DefenderRole != null) roles.Add(DefenderRole);
|
||||
return roles;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResolveReferences()
|
||||
{
|
||||
base.ResolveReferences();
|
||||
requiredOffering?.ResolveReferences();
|
||||
invokerRole = invokerRole ?? PsychicRitualRoleDefOf.Invoker;
|
||||
chanterRole = chanterRole ?? PsychicRitualRoleDefOf.Chanter;
|
||||
}
|
||||
|
||||
public override List<PsychicRitualToil> CreateToils(PsychicRitual psychicRitual, PsychicRitualGraph parent)
|
||||
{
|
||||
float randomInRange = hoursUntilOutcome.RandomInRange;
|
||||
IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> readOnlyDictionary = GenerateRolePositions(psychicRitual.assignments);
|
||||
return new List<PsychicRitualToil>
|
||||
{
|
||||
new PsychicRitualToil_GatherForInvocation_Wula(psychicRitual, this, readOnlyDictionary),
|
||||
new PsychicRitualToil_InvokeHorax(InvokerRole, readOnlyDictionary.TryGetValue(InvokerRole), TargetRole, readOnlyDictionary.TryGetValue(TargetRole), ChanterRole, readOnlyDictionary.TryGetValue(ChanterRole), DefenderRole, readOnlyDictionary.TryGetValue(DefenderRole), RequiredOffering)
|
||||
{
|
||||
hoursUntilHoraxEffect = hoursUntilHoraxEffect.RandomInRange,
|
||||
hoursUntilOutcome = randomInRange
|
||||
},
|
||||
new PsychicRitualToil_Wait(120)
|
||||
};
|
||||
}
|
||||
|
||||
public override bool IsValidTarget(TargetInfo target, out AnyEnum reason)
|
||||
{
|
||||
foreach (IntVec3 item in GenRadial.RadialCellsAround(target.Cell, invocationCircleRadius, useCenter: true))
|
||||
{
|
||||
if (!item.Standable(target.Map))
|
||||
{
|
||||
reason = AnyEnum.FromEnum(InvalidTargetReasonEnum.AreaNotClear);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
reason = AnyEnum.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override TaggedString InvalidTargetReason(AnyEnum reason)
|
||||
{
|
||||
InvalidTargetReasonEnum? invalidTargetReasonEnum = reason.As<InvalidTargetReasonEnum>();
|
||||
if (invalidTargetReasonEnum.HasValue)
|
||||
{
|
||||
InvalidTargetReasonEnum valueOrDefault = invalidTargetReasonEnum.GetValueOrDefault();
|
||||
return valueOrDefault switch
|
||||
{
|
||||
InvalidTargetReasonEnum.None => TaggedString.Empty,
|
||||
InvalidTargetReasonEnum.AreaNotClear => "PsychicRitualDef_InvocationCircle_AreaMustBeClear".Translate(),
|
||||
_ => throw new System.InvalidOperationException($"Unknown reason {valueOrDefault}"),
|
||||
};
|
||||
}
|
||||
return base.InvalidTargetReason(reason);
|
||||
}
|
||||
|
||||
public override TaggedString OutcomeDescription(FloatRange qualityRange, string qualityNumber, PsychicRitualRoleAssignments assignments)
|
||||
{
|
||||
return outcomeDescription.Formatted();
|
||||
}
|
||||
|
||||
public override IEnumerable<TaggedString> OutcomeWarnings(PsychicRitualRoleAssignments assignments)
|
||||
{
|
||||
foreach (Pawn item in assignments.AssignedPawns(TargetRole))
|
||||
{
|
||||
if (item.HomeFaction != null && item.HomeFaction != Faction.OfPlayer && item.HomeFaction.def.humanlikeFaction && !item.HomeFaction.def.PermanentlyHostileTo(FactionDefOf.PlayerColony) && !item.HomeFaction.temporary && !item.HomeFaction.Hidden)
|
||||
{
|
||||
yield return "PsychicRitualFactionWarning".Translate(item.Named("PAWN"), item.HomeFaction.Named("FACTION")).Colorize(ColoredText.WarningColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override TaggedString TimeAndOfferingLabel()
|
||||
{
|
||||
if (timeAndOfferingLabelCached != null)
|
||||
{
|
||||
return timeAndOfferingLabelCached;
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine(DurationLabel());
|
||||
stringBuilder.Append(CooldownLabel);
|
||||
if (!OfferingLabel().NullOrEmpty())
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.Append(OfferingLabel());
|
||||
}
|
||||
timeAndOfferingLabelCached = stringBuilder.ToString();
|
||||
return timeAndOfferingLabelCached;
|
||||
}
|
||||
|
||||
private TaggedString OfferingLabel()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (RequiredOffering != null)
|
||||
{
|
||||
stringBuilder.Append("PsychicRitualRequiredOffering".Translate().CapitalizeFirst());
|
||||
stringBuilder.Append(": ");
|
||||
stringBuilder.Append(RequiredOffering.SummaryFilterFirst);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public TaggedString DurationLabel()
|
||||
{
|
||||
string value = ((int)(hoursUntilOutcome.Average * 2500f)).ToStringTicksToPeriod();
|
||||
TaggedString taggedString = ((hoursUntilOutcome.min != hoursUntilOutcome.max) ? "ExpectedLordJobDuration".Translate().CapitalizeFirst() : "PsychicRitualExpectedDurationLabel".Translate().CapitalizeFirst());
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.Append(taggedString);
|
||||
stringBuilder.Append(": ");
|
||||
stringBuilder.Append(value);
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> GenerateRolePositions(PsychicRitualRoleAssignments assignments)
|
||||
{
|
||||
tmpParticipants.ClearAndPoolValueLists();
|
||||
foreach (PsychicRitualRoleDef role in Roles)
|
||||
{
|
||||
tmpParticipants[role] = SimplePool<List<IntVec3>>.Get();
|
||||
}
|
||||
int num = assignments.RoleAssignedCount(ChanterRole) + assignments.RoleAssignedCount(InvokerRole);
|
||||
int num2 = 0;
|
||||
foreach (Pawn item in assignments.AssignedPawns(InvokerRole))
|
||||
{
|
||||
_ = item;
|
||||
int num3 = 0;
|
||||
IntVec3 cell;
|
||||
do
|
||||
{
|
||||
cell = assignments.Target.Cell;
|
||||
cell += IntVec3.FromPolar(360f * (float)num2++ / (float)num, invocationCircleRadius);
|
||||
}
|
||||
while (!cell.Walkable(assignments.Target.Map) && num3++ <= 10);
|
||||
if (num3 >= 10)
|
||||
{
|
||||
cell = assignments.Target.Cell;
|
||||
}
|
||||
tmpParticipants[InvokerRole].Add(cell);
|
||||
}
|
||||
foreach (Pawn item2 in assignments.AssignedPawns(ChanterRole))
|
||||
{
|
||||
_ = item2;
|
||||
IntVec3 cell2 = assignments.Target.Cell;
|
||||
cell2 += IntVec3.FromPolar(360f * (float)num2++ / (float)num, invocationCircleRadius);
|
||||
tmpParticipants[ChanterRole].Add(cell2);
|
||||
}
|
||||
foreach (Pawn item3 in assignments.AssignedPawns(TargetRole))
|
||||
{
|
||||
_ = item3;
|
||||
tmpParticipants[TargetRole].Add(assignments.Target.Cell);
|
||||
}
|
||||
if (DefenderRole != null)
|
||||
{
|
||||
num2 = 0;
|
||||
int num4 = assignments.RoleAssignedCount(DefenderRole);
|
||||
bool playerRitual = assignments.AllAssignedPawns.Any((Pawn x) => x.Faction == Faction.OfPlayer);
|
||||
foreach (Pawn item4 in assignments.AssignedPawns(DefenderRole))
|
||||
{
|
||||
_ = item4;
|
||||
IntVec3 cell3 = assignments.Target.Cell;
|
||||
cell3 += IntVec3.FromPolar(360f * (float)num2++ / (float)num4, invocationCircleRadius + 5f);
|
||||
cell3 = GetBestStandableRolePosition(playerRitual, cell3, assignments.Target.Cell, assignments.Target.Map);
|
||||
tmpParticipants[DefenderRole].Add(cell3);
|
||||
}
|
||||
}
|
||||
return tmpParticipants;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> BlockingIssues(PsychicRitualRoleAssignments assignments, Map map)
|
||||
{
|
||||
using (new ProfilerBlock("PsychicRitualDef.BlockingIssues"))
|
||||
{
|
||||
tmpGatheringPawns.Clear();
|
||||
foreach (var (psychicRitualRoleDef2, collection) in assignments.RoleAssignments)
|
||||
{
|
||||
if (psychicRitualRoleDef2.CanHandleOfferings)
|
||||
{
|
||||
tmpGatheringPawns.AddRange(collection);
|
||||
}
|
||||
}
|
||||
tmpGatheringPawns.RemoveAll(map, (Map _map, Pawn _pawn) => _pawn.MapHeld != _map);
|
||||
if (TargetRole != null && InvokerRole != null)
|
||||
{
|
||||
Pawn pawn = assignments.FirstAssignedPawn(TargetRole);
|
||||
if (pawn != null)
|
||||
{
|
||||
Pawn pawn2 = assignments.FirstAssignedPawn(InvokerRole);
|
||||
if (pawn2 != null && pawn.IsPrisoner && !map.reachability.CanReach(assignments.Target.Cell, pawn.PositionHeld, PathEndMode.Touch, TraverseParms.For(pawn2)))
|
||||
{
|
||||
yield return "PsychicRitualTargetUnreachableByInvoker".Translate(pawn.Named("TARGET"), pawn2.Named("INVOKER"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (RequiredOffering != null && !PsychicRitualDef.OfferingReachable(map, tmpGatheringPawns, RequiredOffering, out var reachableCount))
|
||||
{
|
||||
yield return "PsychicRitualOfferingsInsufficient".Translate(RequiredOffering.SummaryFilterFirst, reachableCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void CalculateMaxPower(PsychicRitualRoleAssignments assignments, List<QualityFactor> powerFactorsOut, out float power)
|
||||
{
|
||||
power = 0f;
|
||||
foreach (Pawn item in assignments.AssignedPawns(InvokerRole))
|
||||
{
|
||||
float statValue = item.GetStatValue(StatDefOf.PsychicSensitivity);
|
||||
float num = PsychicSensitivityToPowerFactor.Evaluate(statValue);
|
||||
num *= psychicSensitivityPowerFactor;
|
||||
powerFactorsOut?.Add(new QualityFactor
|
||||
{
|
||||
label = "PsychicRitualDef_InvocationCircle_QualityFactor_PsychicSensitivity".Translate(item.Named("PAWN")),
|
||||
positive = (statValue >= 1f),
|
||||
count = statValue.ToStringPercent(),
|
||||
quality = num,
|
||||
toolTip = "PsychicRitualDef_InvocationCircle_QualityFactor_PsychicSensitivity_Tooltip".Translate(item.Named("PAWN"))
|
||||
});
|
||||
power += num;
|
||||
}
|
||||
base.CalculateMaxPower(assignments, powerFactorsOut, out var power2);
|
||||
power += power2;
|
||||
if (assignments.Target.Thing is Building building)
|
||||
{
|
||||
CalculateFacilityQualityOffset(powerFactorsOut, ref power, building);
|
||||
}
|
||||
power = Mathf.Clamp01(power);
|
||||
}
|
||||
|
||||
private static void CalculateFacilityQualityOffset(List<QualityFactor> powerFactorsOut, ref float power, Building building)
|
||||
{
|
||||
Dictionary<ThingDef, RitualQualityOffsetCount> dictionary = new Dictionary<ThingDef, RitualQualityOffsetCount>();
|
||||
List<Thing> linkedFacilitiesListForReading = building.GetComp<CompAffectedByFacilities>().LinkedFacilitiesListForReading;
|
||||
for (int i = 0; i < linkedFacilitiesListForReading.Count; i++)
|
||||
{
|
||||
Thing thing = linkedFacilitiesListForReading[i];
|
||||
CompFacility compFacility = thing.TryGetComp<CompFacility>();
|
||||
if (compFacility?.StatOffsets == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < compFacility.StatOffsets.Count; j++)
|
||||
{
|
||||
StatModifier statModifier = compFacility.StatOffsets[j];
|
||||
if (statModifier.stat == StatDefOf.PsychicRitualQuality)
|
||||
{
|
||||
if (dictionary.TryGetValue(thing.def, out var value))
|
||||
{
|
||||
value.count++;
|
||||
value.offset += statModifier.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(thing.def, new RitualQualityOffsetCount(1, statModifier.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (KeyValuePair<ThingDef, RitualQualityOffsetCount> item in dictionary)
|
||||
{
|
||||
powerFactorsOut?.Add(new QualityFactor
|
||||
{
|
||||
label = Find.ActiveLanguageWorker.Pluralize(item.Key.label).CapitalizeFirst(),
|
||||
positive = true,
|
||||
count = item.Value.count + " / " + item.Key.GetCompProperties<CompProperties_Facility>().maxSimultaneous,
|
||||
quality = item.Value.offset,
|
||||
toolTip = "PsychicRitualDef_InvocationCircle_QualityFactor_Increase_Tooltip".Translate().CapitalizeFirst().EndWithPeriod()
|
||||
});
|
||||
power += item.Value.offset;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<StatDrawEntry> SpecialDisplayStats(StatRequest req)
|
||||
{
|
||||
foreach (StatDrawEntry item in base.SpecialDisplayStats(req))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
if (requiredOffering != null)
|
||||
{
|
||||
yield return new StatDrawEntry(StatCategoryDefOf.PsychicRituals, "StatsReport_Offering".Translate(), requiredOffering.SummaryFilterFirst, "StatsReport_Offering_Desc".Translate(), 1000);
|
||||
}
|
||||
yield return new StatDrawEntry(StatCategoryDefOf.PsychicRituals, "StatsReport_RitualDuration".Translate(), Mathf.FloorToInt(hoursUntilOutcome.min * 2500f).ToStringTicksToPeriod(), "StatsReport_RitualDuration_Desc".Translate(), 500);
|
||||
yield return new StatDrawEntry(StatCategoryDefOf.PsychicRituals, "StatsReport_RitualCooldown".Translate(), (cooldownHours * 2500).ToStringTicksToPeriod(), "StatsReport_RitualCooldown_Desc".Translate(), 100);
|
||||
}
|
||||
|
||||
public override void CheckPsychicRitualCancelConditions(PsychicRitual psychicRitual)
|
||||
{
|
||||
base.CheckPsychicRitualCancelConditions(psychicRitual);
|
||||
if (!psychicRitual.canceled && invokerRole != null)
|
||||
{
|
||||
Pawn pawn = psychicRitual.assignments.FirstAssignedPawn(InvokerRole);
|
||||
if (pawn != null && pawn.DeadOrDowned)
|
||||
{
|
||||
psychicRitual.CancelPsychicRitual("PsychicRitualDef_InvocationCircle_InvokerLost".Translate(pawn.Named("PAWN")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class PsychicRitualToil_AddHediff : PsychicRitualToil
|
||||
{
|
||||
public PsychicRitualRoleDef targetRole;
|
||||
public HediffDef hediff;
|
||||
|
||||
private static List<Pawn> tmpTargetPawns = new List<Pawn>(4);
|
||||
|
||||
public PsychicRitualToil_AddHediff()
|
||||
{
|
||||
}
|
||||
|
||||
public PsychicRitualToil_AddHediff(PsychicRitualRoleDef targetRole, HediffDef hediff)
|
||||
{
|
||||
this.targetRole = targetRole;
|
||||
this.hediff = hediff;
|
||||
}
|
||||
|
||||
public override void Start(PsychicRitual psychicRitual, PsychicRitualGraph graph)
|
||||
{
|
||||
tmpTargetPawns.Clear();
|
||||
tmpTargetPawns.AddRange(psychicRitual.assignments.AssignedPawns(targetRole));
|
||||
foreach (Pawn tmpTargetPawn in tmpTargetPawns)
|
||||
{
|
||||
ApplyOutcome(psychicRitual, tmpTargetPawn);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyOutcome(PsychicRitual psychicRitual, Pawn pawn)
|
||||
{
|
||||
if (hediff != null)
|
||||
{
|
||||
pawn.health.AddHediff(hediff);
|
||||
}
|
||||
|
||||
if (PawnUtility.ShouldSendNotificationAbout(pawn))
|
||||
{
|
||||
Find.LetterStack.ReceiveLetter("PsychicRitualCompleteLabel".Translate(psychicRitual.def.label), ((PsychicRitualDef_AddHediff)psychicRitual.def).outcomeDescription.Formatted(pawn.Named("PAWN")), LetterDefOf.NeutralEvent, pawn);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Defs.Look(ref targetRole, "targetRole");
|
||||
Scribe_Defs.Look(ref hediff, "hediff");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class PsychicRitualToil_GatherForInvocation_Wula : PsychicRitualToil_Multiplex
|
||||
{
|
||||
protected PsychicRitualToil_Goto fallbackToil;
|
||||
protected PsychicRitualGraph invokerToil;
|
||||
protected PsychicRitualToil_Goto invokerFinalToil;
|
||||
private static List<Pawn> blockingPawns = new List<Pawn>(16);
|
||||
|
||||
protected PsychicRitualToil_GatherForInvocation_Wula() { }
|
||||
|
||||
protected PsychicRitualToil_GatherForInvocation_Wula(PsychicRitualDef_WulaBase def, PsychicRitualToil_Goto fallbackToil, PsychicRitualGraph invokerToil)
|
||||
: base(new Dictionary<PsychicRitualRoleDef, PsychicRitualToil> { { def.InvokerRole, invokerToil } }, fallbackToil)
|
||||
{
|
||||
this.fallbackToil = fallbackToil;
|
||||
this.invokerToil = invokerToil;
|
||||
invokerFinalToil = (PsychicRitualToil_Goto)invokerToil.GetToil(invokerToil.ToilCount - 1);
|
||||
}
|
||||
|
||||
public PsychicRitualToil_GatherForInvocation_Wula(PsychicRitual psychicRitual, PsychicRitualDef_WulaBase def, IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> rolePositions)
|
||||
: this(def, FallbackToil(psychicRitual, def, rolePositions), InvokerToil(def, rolePositions))
|
||||
{
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_References.Look(ref fallbackToil, "fallbackToil");
|
||||
Scribe_References.Look(ref invokerToil, "invokerToil");
|
||||
Scribe_References.Look(ref invokerFinalToil, "invokerFinalToil");
|
||||
}
|
||||
|
||||
public override string GetReport(PsychicRitual psychicRitual, PsychicRitualGraph parent)
|
||||
{
|
||||
blockingPawns.Clear();
|
||||
blockingPawns.AddRange(fallbackToil.BlockingPawns);
|
||||
if (invokerToil.CurrentToil == invokerFinalToil)
|
||||
{
|
||||
blockingPawns.AddRange(invokerFinalToil.BlockingPawns);
|
||||
}
|
||||
else
|
||||
{
|
||||
blockingPawns.AddRange(invokerFinalToil.ControlledPawns(psychicRitual));
|
||||
}
|
||||
string text = "PsychicRitualToil_GatherForInvocation_Report".Translate();
|
||||
string text2 = blockingPawns.Select((Pawn pawn) => pawn.LabelShortCap).ToCommaList();
|
||||
return text + ": " + text2;
|
||||
}
|
||||
|
||||
public static PsychicRitualToil_Goto FallbackToil(PsychicRitual psychicRitual, PsychicRitualDef_WulaBase def, IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> rolePositions)
|
||||
{
|
||||
return new PsychicRitualToil_Goto(rolePositions.Slice(rolePositions.Keys.Except(def.InvokerRole)));
|
||||
}
|
||||
|
||||
public static PsychicRitualGraph InvokerToil(PsychicRitualDef_WulaBase def, IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> rolePositions)
|
||||
{
|
||||
return new PsychicRitualGraph(InvokerGatherPhaseToils(def, rolePositions))
|
||||
{
|
||||
willAdvancePastLastToil = false
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<PsychicRitualToil> InvokerGatherPhaseToils(PsychicRitualDef_WulaBase def, IReadOnlyDictionary<PsychicRitualRoleDef, List<IntVec3>> rolePositions)
|
||||
{
|
||||
if (def.RequiredOffering != null)
|
||||
{
|
||||
yield return new PsychicRitualToil_GatherOfferings(def.InvokerRole, def.RequiredOffering);
|
||||
}
|
||||
if (def.TargetRole != null)
|
||||
{
|
||||
yield return new PsychicRitualToil_CarryAndGoto(def.InvokerRole, def.TargetRole, rolePositions);
|
||||
}
|
||||
yield return new PsychicRitualToil_Goto(rolePositions.Slice(def.InvokerRole));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI.Group;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
// 用于在XML中定义祭品
|
||||
public class OfferingItem
|
||||
{
|
||||
public ThingDef thingDef;
|
||||
public float power;
|
||||
}
|
||||
|
||||
public class QualityThreshold
|
||||
{
|
||||
public float threshold;
|
||||
public QualityCategory quality;
|
||||
}
|
||||
|
||||
public class PsychicRitual_TechOffering : PsychicRitualDef_Wula
|
||||
{
|
||||
// 从XML加载的额外祭品列表
|
||||
public List<OfferingItem> extraOfferings = new List<OfferingItem>();
|
||||
|
||||
// 从XML加载的奖励池
|
||||
public List<ThingDef> rewardWeaponPool = new List<ThingDef>();
|
||||
|
||||
// 从XML加载的品质阈值
|
||||
public List<QualityThreshold> qualityThresholds = new List<QualityThreshold>();
|
||||
|
||||
// 重写计算最大能量的方法
|
||||
public override void CalculateMaxPower(PsychicRitualRoleAssignments assignments, List<QualityFactor> powerFactorsOut, out float power)
|
||||
{
|
||||
// 首先调用基类方法
|
||||
base.CalculateMaxPower(assignments, powerFactorsOut, out power);
|
||||
|
||||
IntVec3 center = assignments.Target.Cell;
|
||||
Map map = assignments.Target.Map;
|
||||
float offeringRadius = 8f;
|
||||
var thingsInRadius = GenRadial.RadialDistinctThingsAround(center, map, offeringRadius, useCenter: true).ToList();
|
||||
|
||||
// 创建一个可变的必需品计数器
|
||||
var requiredCounts = new Dictionary<ThingDef, int>();
|
||||
if (this.requiredOffering != null)
|
||||
{
|
||||
foreach (ThingDef thingDef in this.requiredOffering.filter.AllowedThingDefs)
|
||||
{
|
||||
requiredCounts[thingDef] = (int)this.requiredOffering.GetBaseCount();
|
||||
}
|
||||
}
|
||||
|
||||
float extraPowerFromOfferings = 0f;
|
||||
int offeringItemsCount = 0;
|
||||
|
||||
if (!extraOfferings.NullOrEmpty())
|
||||
{
|
||||
var extraOfferingInfo = extraOfferings.ToDictionary(o => o.thingDef, o => o.power);
|
||||
|
||||
// 遍历仪式范围内的所有物品
|
||||
foreach (Thing thing in thingsInRadius)
|
||||
{
|
||||
// 检查这个物品是否可以作为额外祭品
|
||||
if (extraOfferingInfo.TryGetValue(thing.def, out float powerPerItem))
|
||||
{
|
||||
int countInStack = thing.stackCount;
|
||||
|
||||
// 检查这个物品是否是必需品,并扣除相应数量
|
||||
if (requiredCounts.TryGetValue(thing.def, out int requiredCount) && requiredCount > 0)
|
||||
{
|
||||
int numToFulfillRequirement = System.Math.Min(countInStack, requiredCount);
|
||||
requiredCounts[thing.def] -= numToFulfillRequirement;
|
||||
countInStack -= numToFulfillRequirement;
|
||||
}
|
||||
|
||||
// 任何剩余的物品都算作额外祭品
|
||||
if (countInStack > 0)
|
||||
{
|
||||
extraPowerFromOfferings += powerPerItem * countInStack;
|
||||
offeringItemsCount += countInStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加UI显示元素
|
||||
powerFactorsOut?.Add(new QualityFactor
|
||||
{
|
||||
label = "WULA_ExtraOfferings".Translate(),
|
||||
positive = offeringItemsCount > 0,
|
||||
quality = extraPowerFromOfferings,
|
||||
toolTip = "WULA_ExtraOfferings_Tooltip".Translate(),
|
||||
count = offeringItemsCount > 0 ? "✓" : "✗" // 使用对勾/叉号来清晰显示状态
|
||||
});
|
||||
}
|
||||
|
||||
power += extraPowerFromOfferings;
|
||||
power = UnityEngine.Mathf.Clamp01(power);
|
||||
}
|
||||
|
||||
// 重写创建仪式步骤的方法
|
||||
public override List<PsychicRitualToil> CreateToils(PsychicRitual psychicRitual, PsychicRitualGraph parent)
|
||||
{
|
||||
// 获取基类的仪式步骤,这其中已经包含了等待 hoursUntilOutcome 的逻辑
|
||||
List<PsychicRitualToil> toils = base.CreateToils(psychicRitual, parent);
|
||||
|
||||
// 在所有基类步骤之后,添加我们自定义的奖励步骤
|
||||
toils.Add(new PsychicRitualToil_TechOfferingOutcome(this));
|
||||
|
||||
return toils;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义的仪式步骤,用于处理奖励
|
||||
public class PsychicRitualToil_TechOfferingOutcome : PsychicRitualToil
|
||||
{
|
||||
private PsychicRitual_TechOffering ritualDef;
|
||||
|
||||
// 需要一个无参构造函数用于序列化
|
||||
public PsychicRitualToil_TechOfferingOutcome() { }
|
||||
|
||||
public PsychicRitualToil_TechOfferingOutcome(PsychicRitual_TechOffering def)
|
||||
{
|
||||
this.ritualDef = def;
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Defs.Look(ref ritualDef, "ritualDef");
|
||||
}
|
||||
|
||||
public override void Start(PsychicRitual psychicRitual, PsychicRitualGraph graph)
|
||||
{
|
||||
float power = psychicRitual.PowerPercent;
|
||||
|
||||
// 消耗祭品
|
||||
IntVec3 center = psychicRitual.assignments.Target.Cell;
|
||||
Map map = psychicRitual.assignments.Target.Map;
|
||||
float offeringRadius = 8f;
|
||||
|
||||
if (!ritualDef.extraOfferings.NullOrEmpty())
|
||||
{
|
||||
var offeringThings = new Dictionary<ThingDef, float>();
|
||||
foreach(var offering in ritualDef.extraOfferings)
|
||||
{
|
||||
offeringThings[offering.thingDef] = offering.power;
|
||||
}
|
||||
|
||||
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(center, map, offeringRadius, useCenter: true))
|
||||
{
|
||||
if (offeringThings.ContainsKey(thing.def))
|
||||
{
|
||||
thing.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从奖励池中随机选择一个武器
|
||||
if (ritualDef.rewardWeaponPool.NullOrEmpty())
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Reward weapon pool is empty for {ritualDef.defName}");
|
||||
return;
|
||||
}
|
||||
ThingDef weaponDef = ritualDef.rewardWeaponPool.RandomElement();
|
||||
if (weaponDef == null)
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Could not find weapon Def in reward pool for {ritualDef.defName}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据能量值决定物品品质
|
||||
QualityCategory quality = QualityCategory.Awful; // 默认最低品质
|
||||
if (!ritualDef.qualityThresholds.NullOrEmpty())
|
||||
{
|
||||
var sortedThresholds = ritualDef.qualityThresholds.OrderByDescending(t => t.threshold).ToList();
|
||||
foreach (var threshold in sortedThresholds)
|
||||
{
|
||||
if (power >= threshold.threshold)
|
||||
{
|
||||
quality = threshold.quality;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (power >= 1.0f) { quality = QualityCategory.Legendary; }
|
||||
else if (power >= 0.8f) { quality = QualityCategory.Masterwork; }
|
||||
else if (power >= 0.5f) { quality = QualityCategory.Excellent; }
|
||||
else if (power >= 0.2f) { quality = QualityCategory.Normal; }
|
||||
else { quality = QualityCategory.Poor; }
|
||||
}
|
||||
|
||||
// 创建物品并设置品质
|
||||
Thing reward = ThingMaker.MakeThing(weaponDef);
|
||||
if (reward.TryGetComp<CompQuality>() is CompQuality compQuality)
|
||||
{
|
||||
compQuality.SetQuality(quality, ArtGenerationContext.Colony);
|
||||
}
|
||||
|
||||
// 在仪式中心点生成奖励物品
|
||||
GenPlace.TryPlaceThing(reward, psychicRitual.assignments.Target.Cell, map, ThingPlaceMode.Near);
|
||||
|
||||
// 发送消息通知玩家
|
||||
Find.LetterStack.ReceiveLetter(
|
||||
"WULA_RitualReward_Label".Translate(),
|
||||
"WULA_RitualReward_Description".Translate(reward.Label, quality.GetLabel()),
|
||||
LetterDefOf.PositiveEvent,
|
||||
new LookTargets(psychicRitual.assignments.Target.Cell, map)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class RitualTagExtension : DefModExtension
|
||||
{
|
||||
public string ritualTag;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user