using System;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Common;
/// <summary>
/// Tracks encounter-wide participation for bosses whose hittable parts are spread
/// over multiple NPC instances. A participant is recorded after any positive-damage
/// item or owned-projectile hit, irrespective of the weapon used.
/// </summary>
internal sealed class ReaperBossParticipationSystem : ModSystem
{
private const int FamilyCount = (int)ReaperBossFamily.MoonLord + 1;
private static readonly bool[][] ParticipantsByFamily = CreateParticipantStorage();
private static readonly bool[] MechanicalEncounterParticipants = new bool[Main.maxPlayers];
private static int mechanicalFamiliesSeenMask;
private static int mechanicalFamiliesDefeatedMask;
private static bool mechanicalCompositeEncounter;
private static bool mechanicalRewardPending;
public override void OnWorldLoad()
{
ClearAll();
}
public override void OnWorldUnload()
{
ClearAll();
}
public override void PostUpdateWorld()
{
if (Main.netMode != NetmodeID.MultiplayerClient)
UpdateMechanicalEncounter();
for (int familyIndex = 1; familyIndex < FamilyCount; familyIndex++)
{
ReaperBossFamily family = (ReaperBossFamily)familyIndex;
if (!IsFamilyActive(family, ignoredNpcIndex: -1))
Array.Clear(ParticipantsByFamily[familyIndex]);
}
}
internal static void RecordHit(NPC npc, int playerIndex)
{
if (playerIndex < 0 || playerIndex >= Main.maxPlayers)
return;
ReaperBossFamily family = GetFamily(npc.type);
if (family != ReaperBossFamily.None)
{
ParticipantsByFamily[(int)family][playerIndex] = true;
if (IsMechanicalFamily(family))
MechanicalEncounterParticipants[playerIndex] = true;
}
}
internal static void ClearPlayer(int playerIndex)
{
if (playerIndex < 0 || playerIndex >= Main.maxPlayers)
return;
for (int familyIndex = 1; familyIndex < FamilyCount; familyIndex++)
ParticipantsByFamily[familyIndex][playerIndex] = false;
MechanicalEncounterParticipants[playerIndex] = false;
foreach (NPC npc in Main.ActiveNPCs)
npc.GetGlobalNPC<ReaperBossRewardGlobalNPC>().ClearParticipant(playerIndex);
}
internal static bool TryTakeParticipants(NPC defeatedNpc, out bool[] participants)
{
participants = [];
ReaperBossFamily family = GetFamily(defeatedNpc.type);
if (family == ReaperBossFamily.None || !IsCompletionNpc(family, defeatedNpc))
return false;
participants = (bool[])ParticipantsByFamily[(int)family].Clone();
Array.Clear(ParticipantsByFamily[(int)family]);
return true;
}
/// <summary>
/// Mechanical families that overlap in the world are one encounter. This is
/// the vanilla Mechdusa contract and also covers modded summons which compose
/// the three vanilla mechanical bosses without sharing a realLife root.
/// </summary>
internal static bool IsCompositeMechanicalEncounter()
{
ObserveActiveMechanicalFamilies();
return mechanicalCompositeEncounter;
}
internal static void RegisterMechanicalCompletion(ReaperBossFamily family, bool[] familyParticipants)
{
if (!IsMechanicalFamily(family))
return;
for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
MechanicalEncounterParticipants[playerIndex] |= familyParticipants[playerIndex];
int familyBit = GetFamilyBit(family);
mechanicalFamiliesSeenMask |= familyBit;
mechanicalFamiliesDefeatedMask |= familyBit;
mechanicalRewardPending = true;
}
internal static ReaperBossFamily GetFamily(int npcType)
{
return npcType switch
{
NPCID.SkeletronHead or NPCID.SkeletronHand => ReaperBossFamily.Skeletron,
NPCID.EaterofWorldsHead or NPCID.EaterofWorldsBody or NPCID.EaterofWorldsTail => ReaperBossFamily.EaterOfWorlds,
NPCID.BrainofCthulhu or NPCID.Creeper => ReaperBossFamily.BrainOfCthulhu,
NPCID.WallofFlesh or NPCID.WallofFleshEye => ReaperBossFamily.WallOfFlesh,
NPCID.Retinazer or NPCID.Spazmatism => ReaperBossFamily.Twins,
NPCID.TheDestroyer or NPCID.TheDestroyerBody or NPCID.TheDestroyerTail => ReaperBossFamily.Destroyer,
NPCID.SkeletronPrime or NPCID.PrimeCannon or NPCID.PrimeLaser or NPCID.PrimeSaw or NPCID.PrimeVice => ReaperBossFamily.SkeletronPrime,
NPCID.Plantera or NPCID.PlanterasHook or NPCID.PlanterasTentacle => ReaperBossFamily.Plantera,
NPCID.Golem or NPCID.GolemHead or NPCID.GolemHeadFree or NPCID.GolemFistLeft or NPCID.GolemFistRight => ReaperBossFamily.Golem,
NPCID.MoonLordCore or NPCID.MoonLordHead or NPCID.MoonLordHand or NPCID.MoonLordFreeEye => ReaperBossFamily.MoonLord,
_ => ReaperBossFamily.None
};
}
private static bool IsCompletionNpc(ReaperBossFamily family, NPC defeatedNpc)
{
return family switch
{
ReaperBossFamily.EaterOfWorlds => !IsFamilyActive(family, defeatedNpc.whoAmI),
ReaperBossFamily.Twins => !IsFamilyActive(family, defeatedNpc.whoAmI),
ReaperBossFamily.Skeletron => defeatedNpc.type == NPCID.SkeletronHead,
ReaperBossFamily.BrainOfCthulhu => defeatedNpc.type == NPCID.BrainofCthulhu,
ReaperBossFamily.WallOfFlesh => defeatedNpc.type == NPCID.WallofFlesh,
ReaperBossFamily.Destroyer => defeatedNpc.type == NPCID.TheDestroyer,
ReaperBossFamily.SkeletronPrime => defeatedNpc.type == NPCID.SkeletronPrime,
ReaperBossFamily.Plantera => defeatedNpc.type == NPCID.Plantera,
ReaperBossFamily.Golem => defeatedNpc.type == NPCID.Golem,
ReaperBossFamily.MoonLord => defeatedNpc.type == NPCID.MoonLordCore,
_ => false
};
}
private static bool IsFamilyActive(ReaperBossFamily family, int ignoredNpcIndex)
{
foreach (NPC npc in Main.ActiveNPCs)
{
if (npc.whoAmI != ignoredNpcIndex && GetFamily(npc.type) == family)
return true;
}
return false;
}
private static void UpdateMechanicalEncounter()
{
int activeFamilies = GetActiveMechanicalFamilyMask();
if (activeFamilies != 0)
{
mechanicalFamiliesSeenMask |= activeFamilies;
if (HasMultipleBits(mechanicalFamiliesSeenMask))
mechanicalCompositeEncounter = true;
return;
}
// A normal single-family mechanical boss is rewarded immediately by its
// GlobalNPC. Only a composite encounter is settled here, after every
// participating family and lingering segment has left the world.
if (mechanicalCompositeEncounter && mechanicalRewardPending)
{
ReaperBossRewardGlobalNPC.AwardMechanicalEncounter(
MechanicalEncounterParticipants,
MechanicalEncounterCompletesTrio());
}
ClearMechanicalEncounter();
}
private static void ObserveActiveMechanicalFamilies()
{
int activeFamilies = GetActiveMechanicalFamilyMask();
mechanicalFamiliesSeenMask |= activeFamilies;
if (HasMultipleBits(mechanicalFamiliesSeenMask))
mechanicalCompositeEncounter = true;
}
private static int GetActiveMechanicalFamilyMask()
{
int mask = 0;
foreach (NPC npc in Main.ActiveNPCs)
{
ReaperBossFamily family = GetFamily(npc.type);
if (IsMechanicalFamily(family))
mask |= GetFamilyBit(family);
}
return mask;
}
private static bool MechanicalEncounterCompletesTrio()
{
int availableFamilies = mechanicalFamiliesDefeatedMask;
if (NPC.downedMechBoss1)
availableFamilies |= GetFamilyBit(ReaperBossFamily.Destroyer);
if (NPC.downedMechBoss2)
availableFamilies |= GetFamilyBit(ReaperBossFamily.Twins);
if (NPC.downedMechBoss3)
availableFamilies |= GetFamilyBit(ReaperBossFamily.SkeletronPrime);
int trioMask = GetFamilyBit(ReaperBossFamily.Destroyer)
| GetFamilyBit(ReaperBossFamily.Twins)
| GetFamilyBit(ReaperBossFamily.SkeletronPrime);
return (availableFamilies & trioMask) == trioMask;
}
private static bool IsMechanicalFamily(ReaperBossFamily family)
{
return family is ReaperBossFamily.Twins
or ReaperBossFamily.Destroyer
or ReaperBossFamily.SkeletronPrime;
}
private static int GetFamilyBit(ReaperBossFamily family)
{
return 1 << (int)family;
}
private static bool HasMultipleBits(int value)
{
return value != 0 && (value & (value - 1)) != 0;
}
private static bool[][] CreateParticipantStorage()
{
bool[][] storage = new bool[FamilyCount][];
for (int index = 0; index < storage.Length; index++)
storage[index] = new bool[Main.maxPlayers];
return storage;
}
private static void ClearAll()
{
for (int index = 0; index < ParticipantsByFamily.Length; index++)
Array.Clear(ParticipantsByFamily[index]);
ClearMechanicalEncounter();
}
private static void ClearMechanicalEncounter()
{
Array.Clear(MechanicalEncounterParticipants);
mechanicalFamiliesSeenMask = 0;
mechanicalFamiliesDefeatedMask = 0;
mechanicalCompositeEncounter = false;
mechanicalRewardPending = false;
}
}
using System;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Common;
/// <summary>
/// Tracks encounter-wide participation for bosses whose hittable parts are spread
/// over multiple NPC instances. A participant is recorded after any positive-damage
/// item or owned-projectile hit, irrespective of the weapon used.
/// </summary>
internal sealed class ReaperBossParticipationSystem : ModSystem
{
private const int FamilyCount = (int)ReaperBossFamily.MoonLord + 1;
private static readonly bool[][] ParticipantsByFamily = CreateParticipantStorage();
private static readonly bool[] MechanicalEncounterParticipants = new bool[Main.maxPlayers];
private static int mechanicalFamiliesSeenMask;
private static int mechanicalFamiliesDefeatedMask;
private static bool mechanicalCompositeEncounter;
private static bool mechanicalRewardPending;
public override void OnWorldLoad()
{
ClearAll();
}
public override void OnWorldUnload()
{
ClearAll();
}
public override void PostUpdateWorld()
{
if (Main.netMode != NetmodeID.MultiplayerClient)
UpdateMechanicalEncounter();
for (int familyIndex = 1; familyIndex < FamilyCount; familyIndex++)
{
ReaperBossFamily family = (ReaperBossFamily)familyIndex;
if (!IsFamilyActive(family, ignoredNpcIndex: -1))
Array.Clear(ParticipantsByFamily[familyIndex]);
}
}
internal static void RecordHit(NPC npc, int playerIndex)
{
if (playerIndex < 0 || playerIndex >= Main.maxPlayers)
return;
ReaperBossFamily family = GetFamily(npc.type);
if (family != ReaperBossFamily.None)
{
ParticipantsByFamily[(int)family][playerIndex] = true;
if (IsMechanicalFamily(family))
MechanicalEncounterParticipants[playerIndex] = true;
}
}
internal static void ClearPlayer(int playerIndex)
{
if (playerIndex < 0 || playerIndex >= Main.maxPlayers)
return;
for (int familyIndex = 1; familyIndex < FamilyCount; familyIndex++)
ParticipantsByFamily[familyIndex][playerIndex] = false;
MechanicalEncounterParticipants[playerIndex] = false;
foreach (NPC npc in Main.ActiveNPCs)
npc.GetGlobalNPC<ReaperBossRewardGlobalNPC>().ClearParticipant(playerIndex);
}
internal static bool TryTakeParticipants(NPC defeatedNpc, out bool[] participants)
{
participants = [];
ReaperBossFamily family = GetFamily(defeatedNpc.type);
if (family == ReaperBossFamily.None || !IsCompletionNpc(family, defeatedNpc))
return false;
participants = (bool[])ParticipantsByFamily[(int)family].Clone();
Array.Clear(ParticipantsByFamily[(int)family]);
return true;
}
/// <summary>
/// Mechanical families that overlap in the world are one encounter. This is
/// the vanilla Mechdusa contract and also covers modded summons which compose
/// the three vanilla mechanical bosses without sharing a realLife root.
/// </summary>
internal static bool IsCompositeMechanicalEncounter()
{
ObserveActiveMechanicalFamilies();
return mechanicalCompositeEncounter;
}
internal static void RegisterMechanicalCompletion(ReaperBossFamily family, bool[] familyParticipants)
{
if (!IsMechanicalFamily(family))
return;
for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
MechanicalEncounterParticipants[playerIndex] |= familyParticipants[playerIndex];
int familyBit = GetFamilyBit(family);
mechanicalFamiliesSeenMask |= familyBit;
mechanicalFamiliesDefeatedMask |= familyBit;
mechanicalRewardPending = true;
}
internal static ReaperBossFamily GetFamily(int npcType)
{
return npcType switch
{
NPCID.SkeletronHead or NPCID.SkeletronHand => ReaperBossFamily.Skeletron,
NPCID.EaterofWorldsHead or NPCID.EaterofWorldsBody or NPCID.EaterofWorldsTail => ReaperBossFamily.EaterOfWorlds,
NPCID.BrainofCthulhu or NPCID.Creeper => ReaperBossFamily.BrainOfCthulhu,
NPCID.WallofFlesh or NPCID.WallofFleshEye => ReaperBossFamily.WallOfFlesh,
NPCID.Retinazer or NPCID.Spazmatism => ReaperBossFamily.Twins,
NPCID.TheDestroyer or NPCID.TheDestroyerBody or NPCID.TheDestroyerTail => ReaperBossFamily.Destroyer,
NPCID.SkeletronPrime or NPCID.PrimeCannon or NPCID.PrimeLaser or NPCID.PrimeSaw or NPCID.PrimeVice => ReaperBossFamily.SkeletronPrime,
NPCID.Plantera or NPCID.PlanterasHook or NPCID.PlanterasTentacle => ReaperBossFamily.Plantera,
NPCID.Golem or NPCID.GolemHead or NPCID.GolemHeadFree or NPCID.GolemFistLeft or NPCID.GolemFistRight => ReaperBossFamily.Golem,
NPCID.MoonLordCore or NPCID.MoonLordHead or NPCID.MoonLordHand or NPCID.MoonLordFreeEye => ReaperBossFamily.MoonLord,
_ => ReaperBossFamily.None
};
}
private static bool IsCompletionNpc(ReaperBossFamily family, NPC defeatedNpc)
{
return family switch
{
ReaperBossFamily.EaterOfWorlds => !IsFamilyActive(family, defeatedNpc.whoAmI),
ReaperBossFamily.Twins => !IsFamilyActive(family, defeatedNpc.whoAmI),
ReaperBossFamily.Skeletron => defeatedNpc.type == NPCID.SkeletronHead,
ReaperBossFamily.BrainOfCthulhu => defeatedNpc.type == NPCID.BrainofCthulhu,
ReaperBossFamily.WallOfFlesh => defeatedNpc.type == NPCID.WallofFlesh,
ReaperBossFamily.Destroyer => defeatedNpc.type == NPCID.TheDestroyer,
ReaperBossFamily.SkeletronPrime => defeatedNpc.type == NPCID.SkeletronPrime,
ReaperBossFamily.Plantera => defeatedNpc.type == NPCID.Plantera,
ReaperBossFamily.Golem => defeatedNpc.type == NPCID.Golem,
ReaperBossFamily.MoonLord => defeatedNpc.type == NPCID.MoonLordCore,
_ => false
};
}
private static bool IsFamilyActive(ReaperBossFamily family, int ignoredNpcIndex)
{
foreach (NPC npc in Main.ActiveNPCs)
{
if (npc.whoAmI != ignoredNpcIndex && GetFamily(npc.type) == family)
return true;
}
return false;
}
private static void UpdateMechanicalEncounter()
{
int activeFamilies = GetActiveMechanicalFamilyMask();
if (activeFamilies != 0)
{
mechanicalFamiliesSeenMask |= activeFamilies;
if (HasMultipleBits(mechanicalFamiliesSeenMask))
mechanicalCompositeEncounter = true;
return;
}
// A normal single-family mechanical boss is rewarded immediately by its
// GlobalNPC. Only a composite encounter is settled here, after every
// participating family and lingering segment has left the world.
if (mechanicalCompositeEncounter && mechanicalRewardPending)
{
ReaperBossRewardGlobalNPC.AwardMechanicalEncounter(
MechanicalEncounterParticipants,
MechanicalEncounterCompletesTrio());
}
ClearMechanicalEncounter();
}
private static void ObserveActiveMechanicalFamilies()
{
int activeFamilies = GetActiveMechanicalFamilyMask();
mechanicalFamiliesSeenMask |= activeFamilies;
if (HasMultipleBits(mechanicalFamiliesSeenMask))
mechanicalCompositeEncounter = true;
}
private static int GetActiveMechanicalFamilyMask()
{
int mask = 0;
foreach (NPC npc in Main.ActiveNPCs)
{
ReaperBossFamily family = GetFamily(npc.type);
if (IsMechanicalFamily(family))
mask |= GetFamilyBit(family);
}
return mask;
}
private static bool MechanicalEncounterCompletesTrio()
{
int availableFamilies = mechanicalFamiliesDefeatedMask;
if (NPC.downedMechBoss1)
availableFamilies |= GetFamilyBit(ReaperBossFamily.Destroyer);
if (NPC.downedMechBoss2)
availableFamilies |= GetFamilyBit(ReaperBossFamily.Twins);
if (NPC.downedMechBoss3)
availableFamilies |= GetFamilyBit(ReaperBossFamily.SkeletronPrime);
int trioMask = GetFamilyBit(ReaperBossFamily.Destroyer)
| GetFamilyBit(ReaperBossFamily.Twins)
| GetFamilyBit(ReaperBossFamily.SkeletronPrime);
return (availableFamilies & trioMask) == trioMask;
}
private static bool IsMechanicalFamily(ReaperBossFamily family)
{
return family is ReaperBossFamily.Twins
or ReaperBossFamily.Destroyer
or ReaperBossFamily.SkeletronPrime;
}
private static int GetFamilyBit(ReaperBossFamily family)
{
return 1 << (int)family;
}
private static bool HasMultipleBits(int value)
{
return value != 0 && (value & (value - 1)) != 0;
}
private static bool[][] CreateParticipantStorage()
{
bool[][] storage = new bool[FamilyCount][];
for (int index = 0; index < storage.Length; index++)
storage[index] = new bool[Main.maxPlayers];
return storage;
}
private static void ClearAll()
{
for (int index = 0; index < ParticipantsByFamily.Length; index++)
Array.Clear(ParticipantsByFamily[index]);
ClearMechanicalEncounter();
}
private static void ClearMechanicalEncounter()
{
Array.Clear(MechanicalEncounterParticipants);
mechanicalFamiliesSeenMask = 0;
mechanicalFamiliesDefeatedMask = 0;
mechanicalCompositeEncounter = false;
mechanicalRewardPending = false;
}
}