XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
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;
    }
}