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