using System; using Terraria; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Common; /// /// 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. /// 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().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; } /// /// 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. /// 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; } }