using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI.Group;
namespace Implants.Biotech
{
#region Command Range
[HarmonyPatchCategory("Biotech")]
class BiotechMechanitorPatches
{
[HarmonyPatch(typeof(Pawn_MechanitorTracker))]
[HarmonyPatch(nameof(Pawn_MechanitorTracker.CanCommandTo))]
class
Pawn_MechanitorTracker_CanCommandTo_Patch //increases the mechanitor's range by MechRemoteControlDistanceOffset
{
///
/// pushed always commandable check to front to reduct compute.
/// TODO perhaps need a custom patch order to make sure this logic always works.
///
/// target mech to command
/// should be mechanitor, but not always the mechanitor for example like Dead man switch ctrl mechs
/// Commandable result from original code.
[HarmonyPostfix]
public static void CanCommandToPostfix(LocalTargetInfo target, Pawn_MechanitorTracker __instance,
ref bool __result)
{
if (__result)
return;
float SignalBoosterRange =
__instance.Pawn?.GetStatValue(StatDef.Named("MechRemoteControlDistanceOffset")) ?? 0f;
__result = target.Cell.InBounds(__instance.Pawn.MapHeld) &&
(float)__instance.Pawn.Position.DistanceToSquared(target.Cell) <
(24.9f + SignalBoosterRange) *
(24.9f +
SignalBoosterRange); //last line should mean that if something else makes it true, then it is(?)
}
}
[HarmonyPatch(typeof(Pawn_MechanitorTracker))]
[HarmonyPatch(nameof(Pawn_MechanitorTracker.DrawCommandRadius))]
class
Pawn_MechanitorTracker_DrawCommandRadius_Patch //increases the displayed mechanitor range by MechRemoteControlDistanceOffset
{
///
/// Skip original draw radius using a prefix.
/// TODO may need specify fixed patch order to make sure this works.
///
/// return false to skip original draw.
[HarmonyPrefix]
static bool DrawCommandRadiusPrefix()
{
return false;
}
///
/// Postfix the draw radius by apply our extended radius on top.
///
///
[HarmonyPostfix]
public static void DrawCommandRadiusPostfix(Pawn_MechanitorTracker __instance)
{
if (__instance.Pawn.Spawned && __instance.AnySelectedDraftedMechs)
{
//GenDraw.DrawRadiusRing(___pawn.Position, 24.9f + (3f*___pawn.health?.hediffSet?.GetFirstHediffOfDef(HediffDef.Named("SignalBoosterImplant"))?.Severity ?? 0f), Color.white, (IntVec3 c) => __instance.CanCommandTo(c));
if (!ModsConfig.IsActive("swwu.MechanitorCommandRange") &&
!ModsConfig.IsActive(
"Neronix17.TweaksGalore")) //for tweaks galore, it'd be better to try to find the setting specifically, with an inverted result and a null check true
{
IntVec3 position = __instance.Pawn.Position;
float radius = 24.9f +
(__instance.Pawn?.GetStatValue(
StatDef.Named("MechRemoteControlDistanceOffset")) ?? 0f);
//Make our command circle yellow. And make sure this postfix implemented draw follows vanilla impl.
GenDraw.DrawRadiusRing(position, radius, Color.yellow,
(IntVec3 c) => __instance.CanCommandTo((LocalTargetInfo)c));
}
}
}
}
}
#endregion
#region Resurrect
public class CompProperties_MechanitorResurrectMech : CompProperties_AbilityEffect
{
public CompProperties_MechanitorResurrectMech()
{
this.compClass = typeof(CompAbilityEffect_MechanitorResurrectMech);
}
public int maxCorpseAgeTicks = int.MaxValue;
public EffecterDef appliedEffecterDef;
public EffecterDef resolveEffecterDef;
//public EffecterDef centerEffecterDef;
}
public class CompAbilityEffect_MechanitorResurrectMech : CompAbilityEffect
{
public new CompProperties_MechanitorResurrectMech Props
{
get { return (CompProperties_MechanitorResurrectMech)this.props; }
}
public override bool CanApplyOn(LocalTargetInfo target, LocalTargetInfo dest)
{
Corpse corpse;
bool bandwidthCheck = (target.Thing as Corpse).InnerPawn.GetStatValue(StatDef.Named("BandwidthCost")) <=
this.parent.pawn.mechanitor.TotalBandwidth -
this.parent.pawn.mechanitor.UsedBandwidth;
bool canApplyOnCheck = (base.CanApplyOn(target, dest) && target.HasThing &&
(corpse = target.Thing as Corpse) != null && this.CanResurrect(corpse) &&
bandwidthCheck);
//Log.Message("CanApplyOn check: " + canApplyOnCheck);
if ((target.Thing as Corpse).InnerPawn.Faction != this.parent.pawn.Faction)
{
Messages.Message("Can only resurrect allied mechs", (target.Thing as Pawn),
MessageTypeDefOf.NegativeEvent);
}
else if ((target.Thing as Corpse).timeOfDeath <=
Find.TickManager.TicksGame - this.Props.maxCorpseAgeTicks)
{
Messages.Message("Target has been dead too long", (target.Thing as Pawn),
MessageTypeDefOf.NegativeEvent);
}
else if (!bandwidthCheck)
{
Messages.Message("Insufficient bandwidth", (target.Thing as Pawn), MessageTypeDefOf.NegativeEvent);
}
return canApplyOnCheck;
}
private bool CanResurrect(Corpse corpse)
{
//return corpse.InnerPawn.RaceProps.IsMechanoid && corpse.InnerPawn.RaceProps.mechWeightClass < MechWeightClass.UltraHeavy && corpse.InnerPawn.Faction == this.parent.pawn.Faction && (corpse.InnerPawn.kindDef.abilities == null || !corpse.InnerPawn.kindDef.abilities.Contains(AbilityDefOf.ResurrectionMech)) && corpse.timeOfDeath >= Find.TickManager.TicksGame - this.Props.maxCorpseAgeTicks;
return corpse.InnerPawn.RaceProps.IsMechanoid && corpse.InnerPawn.Faction == this.parent.pawn.Faction &&
(corpse.InnerPawn.kindDef.abilities == null ||
!corpse.InnerPawn.kindDef.abilities.Contains(AbilityDefOf.ResurrectionMech)) &&
corpse.timeOfDeath >= Find.TickManager.TicksGame - this.Props.maxCorpseAgeTicks;
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
//Log.Message("Apply called");
base.Apply(target, dest);
Corpse corpse = (Corpse)target.Thing;
if (!this.CanResurrect(corpse))
{
return;
}
Pawn innerPawn = corpse.InnerPawn;
ResurrectionUtility.TryResurrect(innerPawn, null);
if (this.Props.appliedEffecterDef != null)
{
Effecter effecter = this.Props.appliedEffecterDef.SpawnAttached(innerPawn, innerPawn.MapHeld, 1f);
effecter.Trigger(innerPawn, innerPawn, -1);
effecter.Cleanup();
this.parent.pawn.relations.AddDirectRelation(PawnRelationDefOf.Overseer,
innerPawn); //if resurrection successful, immediately takes control of resurrected mech.
}
innerPawn.stances.stagger.StaggerFor(60, 0.17f);
}
public override bool GizmoDisabled(out string reason)
{
reason = null;
return false;
}
public override IEnumerable CustomWarmupMotes(LocalTargetInfo target)
{
foreach (LocalTargetInfo localTargetInfo in this.parent.GetAffectedTargets(target))
{
Thing thing = localTargetInfo.Thing;
yield return MoteMaker.MakeAttachedOverlay(thing, ThingDefOf.Mote_MechResurrectWarmupOnTarget,
Vector3.zero, 1f, -1f);
}
yield break;
}
public override void PostApplied(List targets, Map map)
{
//Log.Message("PostApplied called");
Vector3 vector = Vector3.zero;
foreach (LocalTargetInfo localTargetInfo in targets)
{
vector += localTargetInfo.Cell.ToVector3Shifted();
}
vector /= (float)targets.Count();
IntVec3 intVec = vector.ToIntVec3();
this.Props.resolveEffecterDef.Spawn(intVec, map, 1f).EffectTick(new TargetInfo(intVec, map, false),
new TargetInfo(intVec, map, false));
}
}
#endregion
#region Remote Dominate
public class CompProperties_MechanitorDominateMech : CompProperties_AbilityEffect
{
public CompProperties_MechanitorDominateMech()
{
this.compClass = typeof(CompAbilityEffect_MechanitorDominateMech);
}
}
public class CompAbilityEffect_MechanitorDominateMech : CompAbilityEffect
{
public new CompProperties_MechanitorDominateMech Props
{
get { return (CompProperties_MechanitorDominateMech)this.props; }
}
public override bool CanApplyOn(LocalTargetInfo target, LocalTargetInfo dest)
{
Pawn pawn;
bool bandwidthCheck = (target.Thing as Pawn).GetStatValue(StatDef.Named("BandwidthCost")) <=
this.parent.pawn.mechanitor.TotalBandwidth -
this.parent.pawn.mechanitor.UsedBandwidth;
bool notTempMech = target.Thing.TryGetComp() == null;
bool canApplyOnCheck = (base.CanApplyOn(target, dest) && target.HasThing &&
(pawn = target.Thing as Pawn) != null && bandwidthCheck &&
this.CanDominate(pawn) && notTempMech);
if (!notTempMech)
{
Messages.Message("Cannot target temporary mech", (target.Thing as Pawn),
MessageTypeDefOf.NegativeEvent);
}
else if ((target.Thing as Pawn).RaceProps.mechWeightClass >= MechWeightClass.UltraHeavy)
{
Messages.Message("Cannot target superheavy mech", (target.Thing as Pawn),
MessageTypeDefOf.NegativeEvent);
}
else if (!bandwidthCheck)
{
Messages.Message("Insufficient bandwidth", (target.Thing as Pawn), MessageTypeDefOf.NegativeEvent);
}
return canApplyOnCheck;
}
private bool CanDominate(Pawn pawn)
{
return pawn.RaceProps.IsMechanoid && pawn.RaceProps.mechWeightClass < MechWeightClass.UltraHeavy &&
(pawn.kindDef.abilities == null ||
!pawn.kindDef.abilities.Contains(AbilityDefOf.ResurrectionMech));
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
//Log.Message("Apply called");
base.Apply(target, dest);
Pawn pawn = (Pawn)target;
if (!this.CanDominate(pawn))
{
return;
}
pawn.SetFaction(this.parent.pawn.Faction); //convert pawn
this.parent.pawn.relations.AddDirectRelation(PawnRelationDefOf.Overseer, pawn);
pawn.stances.stagger.StaggerFor(60, 0.17f);
}
public override bool GizmoDisabled(out string reason)
{
reason = null;
return false;
}
//public override IEnumerable CustomWarmupMotes(LocalTargetInfo target)
//{
// foreach (LocalTargetInfo localTargetInfo in this.parent.GetAffectedTargets(target))
// {
// Thing thing = localTargetInfo.Thing;
// yield return MoteMaker.MakeAttachedOverlay(thing, ThingDefOf.Mote_MechResurrectWarmupOnTarget, Vector3.zero, 1f, -1f);
// }
// IEnumerator enumerator = null;
// yield break;
// yield break;
//}
public override void PostApplied(List targets, Map map)
{
//Log.Message("PostApplied called");
//Vector3 vector = Vector3.zero;
//foreach (LocalTargetInfo localTargetInfo in targets)
//{
// vector += localTargetInfo.Cell.ToVector3Shifted();
//}
//vector /= (float)targets.Count();
//IntVec3 intVec = vector.ToIntVec3();
//this.Props.resolveEffecterDef.Spawn(intVec, map, 1f).EffectTick(new TargetInfo(intVec, map, false), new TargetInfo(intVec, map, false));
}
}
#endregion
#region Mech carrier
public class CompProperties_MechanitorMechCarrier : CompProperties_AbilityEffect
{
public CompProperties_MechanitorMechCarrier()
{
this.compClass = typeof(CompAbilityEffect_MechanitorMechCarrier);
}
public PawnKindDef spawnPawnKind;
public int cooldownTicks = 900;
public int maxPawnsToSpawn = 2;
public EffecterDef spawnEffecter;
public EffecterDef spawnedMechEffecter;
public bool attachSpawnedEffecter;
public bool attachSpawnedMechEffecter;
}
public class CompAbilityEffect_MechanitorMechCarrier : CompAbilityEffect
{
public new CompProperties_MechanitorMechCarrier Props
{
get { return (CompProperties_MechanitorMechCarrier)this.props; }
}
public override void CompTick()
{
base.CompTick();
//if (Find.Selector.IsSelected(parent.pawn) && (int)Find.TickManager.CurTimeSpeed != 0 && Find.TickManager.TicksGame % (int)Find.TickManager.CurTimeSpeed == 0)//if the mechanitor is selected, and once erry 60/1 irl seconds
//{
// for (int i = 0; i < spawnedPawns.Count; i++)
// {
// if (!spawnedPawns[i].Dead)
// {
// GenDraw.DrawLineBetween(this.parent.pawn.TrueCenter(), spawnedPawns[i].TrueCenter());
// }
// }
//}
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
TrySpawnPawns();
}
public int maxspawn()
{
//Log.Message("RemainingCharges: " + parent.RemainingCharges+1);
int max = parent.RemainingCharges +
1; //requires +1 to account for the charge that's automatically used upon ability activation, prior to this.
if (max > 2)
{
return 2;
}
else
{
return max;
}
}
public void TrySpawnPawns()
{
int maxCanSpawn = maxspawn();
//Log.Message("max spawns: "+ maxCanSpawn);
if (maxCanSpawn <= 0)
{
return;
}
PawnGenerationRequest pawnGenerationRequest = new PawnGenerationRequest(this.Props.spawnPawnKind,
this.parent.pawn.Faction, PawnGenerationContext.NonPlayer, -1, true, false, false, true, false, 1f,
false, true, false, true, true, false, false, false, false, 0f, 0f, null, 1f, null, null, null,
null, null, null, null, null, null, null, null, null, false, false, false, false, null, null, null,
null, null, 0f, DevelopmentalStage.Newborn, null, null, null, false, false, false, -1, 0, false);
Pawn pawn;
Lord lord = (((pawn = this.parent.pawn as Pawn) != null) ? pawn.GetLord() : null);
for (int i = 0; i < maxCanSpawn; i++)
{
Pawn pawn2 = PawnGenerator.GeneratePawn(pawnGenerationRequest);
GenSpawn.Spawn(pawn2, this.parent.pawn.Position, this.parent.pawn.Map, WipeMode.Vanish);
this.spawnedPawns.Add(pawn2);
if (lord != null)
{
lord.AddPawn(pawn2);
}
if (this.Props.spawnedMechEffecter != null)
{
Effecter effecter = new Effecter(this.Props.spawnedMechEffecter);
effecter.Trigger(
this.Props.attachSpawnedMechEffecter
? pawn2
: new TargetInfo(pawn2.Position, pawn2.Map, false), TargetInfo.Invalid, -1);
effecter.Cleanup();
}
}
this.cooldownTicksRemaining = this.Props.cooldownTicks;
if (this.Props.spawnEffecter != null)
{
Effecter effecter2 = new Effecter(this.Props.spawnEffecter);
effecter2.Trigger(
this.Props.attachSpawnedEffecter
? this.parent.pawn
: new TargetInfo(this.parent.pawn.Position, this.parent.pawn.Map, false),
TargetInfo.Invalid, -1);
effecter2.Cleanup();
}
if (maxCanSpawn == 2) //subtract an extra charge, as we're summoning 2
{
parent.RemainingCharges--;
}
}
public List GetSpawnedPawns()
{
return spawnedPawns;
}
private int cooldownTicksRemaining;
private List spawnedPawns = new List();
public SoundDef soundReload;
}
public class HediffCompProperties_KillSpawnedPawns : HediffCompProperties
{
public AbilityDef abilityDef;
public HediffCompProperties_KillSpawnedPawns()
{
compClass = typeof(HediffComp_KillSpawnedPawns);
}
}
public class HediffComp_KillSpawnedPawns : HediffComp
{
public HediffCompProperties_KillSpawnedPawns Props => (HediffCompProperties_KillSpawnedPawns)props;
public override void Notify_PawnKilled()
{
foreach (Pawn i in base.Pawn.abilities.GetAbility(Props.abilityDef)
.CompOfType().GetSpawnedPawns())
{
if (!i.Dead)
{
i.Kill(null, null);
}
}
}
}
#endregion
#region Call a cluster
public class CompProperties_AbilityMechCluster : CompProperties_AbilityEffect
{
public CompProperties_AbilityMechCluster()
{
this.compClass = typeof(CompAbilityEffect_MechCluster);
}
public float displayRadius;
}
public class CompAbilityEffect_MechCluster : CompAbilityEffect
{
public new CompProperties_AbilityMechCluster Props
{
get { return (CompProperties_AbilityMechCluster)this.props; }
}
public bool ShouldHaveInspectString
{
get { return ModsConfig.BiotechActive && this.parent.pawn.RaceProps.IsMechanoid; }
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
if (Faction.OfMechanoids == null)
{
Messages.Message("MessageNoFactionForVerbMechCluster".Translate(), this.parent.pawn,
MessageTypeDefOf.RejectInput, null, false);
}
else
{
MechClusterUtility.SpawnCluster(target.Cell, this.parent.pawn.MapHeld,
MechClusterGenerator.GenerateClusterSketch(2500f, this.parent.pawn.MapHeld, true, true), true,
false, null);
}
}
public override void PostApplied(List targets, Map map)
{
base.PostApplied(targets, map);
if (this.parent.def.defName ==
"MechhiveSatelliteUplink") //add field for cooldownFactorStat. Change this if statement to if it's not null. change the contents of getstatvalue on the next line to that field.
{
this.parent.StartCooldown(Mathf.RoundToInt(this.parent.def.cooldownTicksRange.RandomInRange *
this.parent.pawn?.GetStatValue(StatDef.Named("MechhiveSatelliteUplinkCooldownFactor")) ?? 1f));
}
}
public override void DrawEffectPreview(LocalTargetInfo target)
{
GenDraw.DrawRadiusRing(target.Cell, this.Props.displayRadius);
}
public override string CompInspectStringExtra()
{
if (!this.ShouldHaveInspectString)
{
return null;
}
if (this.parent.CanCast)
{
return "AbilityMechSmokepopCharged".Translate();
}
return "AbilityMechSmokepopRecharging".Translate(
this.parent.CooldownTicksRemaining.ToStringTicksToPeriod(true, false, true, true, false));
}
}
#endregion
}