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>
/// Awards one progression seal for every world-unlocked stage per completed boss
/// encounter to each online player who dealt damage. Dead participants remain
/// eligible because Player.active, not Player.dead, is the eligibility condition.
/// </summary>
internal sealed class ReaperBossRewardGlobalNPC : GlobalNPC
{
    private readonly bool[] participants = new bool[Main.maxPlayers];

    public override bool InstancePerEntity => true;

    public override void SetDefaults(NPC npc)
    {
        Array.Clear(participants);
    }

    public override void OnHitByItem(NPC npc, Player player, Item item, NPC.HitInfo hit, int damageDone)
    {
        if (Main.netMode != NetmodeID.MultiplayerClient && damageDone > 0)
            RecordHit(npc, player.whoAmI);
    }

    public override void OnHitByProjectile(NPC npc, Projectile projectile, NPC.HitInfo hit, int damageDone)
    {
        if (Main.netMode != NetmodeID.MultiplayerClient && damageDone > 0)
            RecordHit(npc, projectile.owner);
    }

    public override void OnKill(NPC npc)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient || npc.friendly || npc.lifeMax <= 5)
            return;

        ReaperBossFamily family = ReaperBossParticipationSystem.GetFamily(npc.type);
        bool belongsToGenericBoss = npc.boss
            || npc.realLife >= 0
                && npc.realLife < Main.maxNPCs
                && Main.npc[npc.realLife].active
                && Main.npc[npc.realLife].boss;
        if (family == ReaperBossFamily.None && !belongsToGenericBoss)
            return;

        // Vanilla records player interaction before resolving CheckDead/OnKill. OR
        // that authoritative record into our encounter ledger so a lethal first hit
        // and client-authoritative melee hits cannot miss the reward window.
        CaptureVanillaParticipants(npc);

        bool[] encounterParticipants;
        if (family != ReaperBossFamily.None)
        {
            if (!ReaperBossParticipationSystem.TryTakeParticipants(npc, out encounterParticipants))
                return;
        }
        else
        {
            if (!npc.boss)
                return;
            encounterParticipants = (bool[])participants.Clone();
        }

        if (IsMechanicalBoss(npc.type, family)
            && ReaperBossParticipationSystem.IsCompositeMechanicalEncounter())
        {
            // Defer cross-family encounters until every mechanical component is
            // gone. The shared ledger unions participation and produces one reward
            // bundle per player for the entire encounter.
            ReaperBossParticipationSystem.RegisterMechanicalCompletion(family, encounterParticipants);
            return;
        }

        ReaperStage maximumRewardStage = GetHighestUnlockedRewardStage(
            npc.type,
            family,
            mechanicalEncounterCompletedTrio: false);
        if (maximumRewardStage == ReaperStage.Locked)
            return;

        for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
        {
            Player player = Main.player[playerIndex];
            if (!encounterParticipants[playerIndex] || !player.active)
                continue;

            GrantUnlockedStageRewards(player, maximumRewardStage);
        }
    }

    internal static void AwardMechanicalEncounter(bool[] encounterParticipants, bool completedTrio)
    {
        ReaperStage maximumRewardStage = GetHighestUnlockedRewardStage(
            NPCID.None,
            ReaperBossFamily.None,
            completedTrio);
        if (maximumRewardStage == ReaperStage.Locked)
            return;

        for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
        {
            Player player = Main.player[playerIndex];
            if (!encounterParticipants[playerIndex] || !player.active)
                continue;

            GrantUnlockedStageRewards(player, maximumRewardStage);
        }
    }

    internal void ClearParticipant(int playerIndex)
    {
        if (playerIndex >= 0 && playerIndex < participants.Length)
            participants[playerIndex] = false;
    }

    private void RecordHit(NPC npc, int playerIndex)
    {
        if (playerIndex < 0 || playerIndex >= Main.maxPlayers || !Main.player[playerIndex].active)
            return;

        participants[playerIndex] = true;
        ReaperBossParticipationSystem.RecordHit(npc, playerIndex);

        // Generic segmented modded bosses commonly point every segment at realLife.
        // Preserve their participants on the root even when they are not in a known
        // vanilla family.
        if (npc.realLife >= 0 && npc.realLife < Main.maxNPCs && npc.realLife != npc.whoAmI)
            Main.npc[npc.realLife].GetGlobalNPC<ReaperBossRewardGlobalNPC>().participants[playerIndex] = true;
    }

    private void CaptureVanillaParticipants(NPC npc)
    {
        int count = Math.Min(Main.maxPlayers, npc.playerInteraction.Length);
        for (int playerIndex = 0; playerIndex < count; playerIndex++)
        {
            if (npc.playerInteraction[playerIndex])
                RecordHit(npc, playerIndex);
        }
    }

    private static void GrantUnlockedStageRewards(Player player, ReaperStage maximumStage)
    {
        MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
        for (ReaperStage stage = ReaperStage.StageI; stage <= maximumStage; stage++)
            modPlayer.TryGrantSoulSeal(stage);
    }

    private static ReaperStage GetHighestUnlockedRewardStage(
        int defeatedNpcType,
        ReaperBossFamily defeatedFamily,
        bool mechanicalEncounterCompletedTrio)
    {
        if (AllMechanicalBossesDefeated()
            || mechanicalEncounterCompletedTrio
            || IsMechanicalBoss(defeatedNpcType, defeatedFamily)
                && WouldCompleteMechanicalTrio(defeatedNpcType, defeatedFamily))
        {
            return ReaperStage.StageIII;
        }

        if (Main.hardMode || defeatedNpcType == NPCID.WallofFlesh)
            return ReaperStage.StageII;

        if (NPC.downedBoss3 || defeatedNpcType == NPCID.SkeletronHead)
            return ReaperStage.StageI;

        return ReaperStage.Locked;
    }

    private static bool WouldCompleteMechanicalTrio(int npcType, ReaperBossFamily family)
    {
        bool destroyer = NPC.downedMechBoss1 || family == ReaperBossFamily.Destroyer || npcType == NPCID.TheDestroyer;
        bool twins = NPC.downedMechBoss2 || family == ReaperBossFamily.Twins || npcType is NPCID.Retinazer or NPCID.Spazmatism;
        bool prime = NPC.downedMechBoss3 || family == ReaperBossFamily.SkeletronPrime || npcType == NPCID.SkeletronPrime;
        return destroyer && twins && prime;
    }

    private static bool AllMechanicalBossesDefeated()
    {
        return NPC.downedMechBoss1 && NPC.downedMechBoss2 && NPC.downedMechBoss3;
    }

    private static bool IsMechanicalBoss(int npcType, ReaperBossFamily family)
    {
        return family is ReaperBossFamily.Twins or ReaperBossFamily.Destroyer or ReaperBossFamily.SkeletronPrime
            || npcType is NPCID.Retinazer or NPCID.Spazmatism or NPCID.TheDestroyer or NPCID.SkeletronPrime;
    }

}