using ExampleMod.Common.Systems;
using ExampleMod.Content.BossBars;
using ExampleMod.Content.Items;
using ExampleMod.Content.Items.Armor.Vanity;
using ExampleMod.Content.Items.Consumables;
using ExampleMod.Content.Pets.MinionBossPet;
using ExampleMod.Content.Projectiles;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Terraria.GameContent.Bestiary;
using Terraria.GameContent.ItemDropRules;
using Terraria.Graphics.CameraModifiers;
using Terraria.ModLoader;
namespace ExampleMod.Content.NPCs.MinionBoss
public class MinionBossBody:ModNPC
public static int secondStageHeadSlot = -1;
set = >NPC.ai[0] = value ? 1f : 0f;
public Vector2 FirstStageDestination
get = >new Vector2 (NPC.ai[1], NPC.ai[2]);
public int MinionMaxHealthTotal
set = >NPC.ai[3] = value;
public int MinionHealthTotal
public Vector2 LastFirstStageDestination
public bool SpawnedMinions
get = >NPC.localAI[0] == 1f;
set = >NPC.localAI[0] = value ? 1f : 0f;
private const int FirstStageTimerMax = 90;
public ref float FirstStageTimer = >ref NPC.localAI[1];
public ref float SecondStageTimer_SpawnEyes = >ref NPC.localAI[3];
public static int MinionType ()
return ModContent.NPCType < MinionBossMinion > ();
public static int MinionCount ()
public override void Load ()
string texture = BossHeadTexture + "_SecondStage";
secondStageHeadSlot = Mod.AddBossHeadTexture (texture, -1);
public override void BossHeadSlot (ref int index)
int slot = secondStageHeadSlot;
if (SecondStage && slot != -1)
public override void SetStaticDefaults ()
Main.npcFrameCount[Type] = 6;
NPCID.Sets.MPAllowedEnemies[Type] = true;
NPCID.Sets.BossBestiaryPriority.Add (Type);
NPCID.Sets.SpecificDebuffImmunity[Type][BuffID.Poisoned] = true;
NPCID.Sets.SpecificDebuffImmunity[Type][BuffID.Confused] = true;
NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers =
new NPCID.Sets.NPCBestiaryDrawModifiers ()
CustomTexturePath = "ExampleMod/Assets/Textures/Bestiary/MinionBoss_Preview", PortraitScale = 0.6f,
PortraitPositionYOverride = 0f,};
NPCID.Sets.NPCBestiaryDrawOffset.Add (Type, drawModifiers);
public override void SetDefaults ()
NPC.HitSound = SoundID.NPCHit1;
NPC.DeathSound = SoundID.NPCDeath1;
NPC.knockBackResist = 0f;
NPC.noTileCollide = true;
NPC.value = Item.buyPrice (gold:5);
NPC.SpawnWithHigherTime (30);
NPC.BossBar = ModContent.GetInstance < MinionBossBossBar > ();
Music = MusicLoader.GetMusicSlot (Mod, "Assets/Music/Ropocalypse2");
public override void SetBestiary (BestiaryDatabase database,
BestiaryEntry bestiaryEntry)
bestiaryEntry.Info.AddRange (new List < IBestiaryInfoElement >
new MoonLordPortraitBackgroundProviderBestiaryInfoElement (),
FlavorTextBestiaryInfoElement
("Example Minion Boss that spawns minions on spawn, summoned with a spawn item. Showcases boss minion handling, multiplayer considerations, and custom boss bar.")});
public override void ModifyNPCLoot (NPCLoot npcLoot)
npcLoot.Add (ItemDropRule.
BossBag (ModContent.ItemType < MinionBossBag > ()));
npcLoot.Add (ItemDropRule.
Common (ModContent.ItemType <
Items.Placeable.Furniture.MinionBossTrophy > (),
npcLoot.Add (ItemDropRule.
MasterModeCommonDrop (ModContent.ItemType <
Items.Placeable.Furniture.
npcLoot.Add (ItemDropRule.
MasterModeDropOnAllPlayers (ModContent.ItemType <
MinionBossPetItem > (), 4));
LeadingConditionRule notExpertRule =
new LeadingConditionRule (new Conditions.NotExpert ());
notExpertRule.OnSuccess (ItemDropRule.
Common (ModContent.ItemType < MinionBossMask >
int itemType = ModContent.ItemType < ExampleItem > ();
var parameters = new DropOneByOne.Parameters (){
MinimumStackPerChunkBase = 1,
MaximumStackPerChunkBase = 1,
MinimumItemDropsCount = 12,
MaximumItemDropsCount = 15,
notExpertRule.OnSuccess (new DropOneByOne (itemType, parameters));
npcLoot.Add (notExpertRule);
public override void OnKill ()
NPC.SetEventFlagCleared (ref DownedBossSystem.downedMinionBoss, -1);
public override void BossLoot (ref string name, ref int potionType)
public override bool CanHitPlayer (Player target, ref int cooldownSlot)
cooldownSlot = ImmunityCooldownID.Bosses;
public override void FindFrame (int frameHeight)
finalFrame = Main.npcFrameCount[NPC.type] - 1;
if (NPC.frame.Y < startFrame * frameHeight)
NPC.frame.Y = startFrame * frameHeight;
NPC.frameCounter += 0.5f;
NPC.frameCounter += NPC.velocity.Length () / 10f;
if (NPC.frameCounter > frameSpeed)
NPC.frame.Y += frameHeight;
if (NPC.frame.Y > finalFrame * frameHeight)
NPC.frame.Y = startFrame * frameHeight;
public override void HitEffect (NPC.HitInfo hit)
if (Main.netMode == NetmodeID.Server)
Mod.Find < ModGore > ("MinionBossBody_Back").Type;
Mod.Find < ModGore > ("MinionBossBody_Front").Type;
var entitySource = NPC.GetSource_Death ();
for (int i = 0; i < 2; i++)
Gore.NewGore (entitySource, NPC.position,
new Vector2 (Main.rand.Next (-6, 7),
Gore.NewGore (entitySource, NPC.position,
new Vector2 (Main.rand.Next (-6, 7),
SoundEngine.PlaySound (SoundID.Roar, NPC.Center);
PunchCameraModifier modifier =
new PunchCameraModifier (NPC.Center,
(Main.rand.NextFloat () *
2f)).ToRotationVector2 (), 20f, 6f, 20,
Main.instance.CameraModifiers.Add (modifier);
public override void AI ()
if (NPC.target < 0 || NPC.target == 255 || Main.player[NPC.target].dead
|| !Main.player[NPC.target].active)
Player player = Main.player[NPC.target];
NPC.EncourageDespawn (10);
NPC.dontTakeDamage = !SecondStage;
private void SpawnMinions ()
if (Main.netMode == NetmodeID.MultiplayerClient)
int count = MinionCount ();
var entitySource = NPC.GetSource_FromAI ();
MinionMaxHealthTotal = 0;
for (int i = 0; i < count; i++)
NPC.NewNPCDirect (entitySource, (int) NPC.Center.X,
ModContent.NPCType < MinionBossMinion > (),
if (minionNPC.whoAmI == Main.maxNPCs)
MinionBossMinion minion = (MinionBossMinion) minionNPC.ModNPC;
minion.ParentIndex = NPC.whoAmI;
minion.PositionOffset = i / (float) count;
MinionMaxHealthTotal += minionNPC.lifeMax;
if (Main.netMode == NetmodeID.Server)
NetMessage.SendData (MessageID.SyncNPC, number:minionNPC.whoAmI);
if (Main.netMode == NetmodeID.Server)
NetMessage.SendData (MessageID.SyncNPC, number:NPC.whoAmI);
private void CheckSecondStage ()
for (int i = 0; i < Main.maxNPCs; i++)
NPC otherNPC = Main.npc[i];
if (otherNPC.active && otherNPC.type == MinionType ()
&& otherNPC.ModNPC is MinionBossMinion minion)
if (minion.ParentIndex == NPC.whoAmI)
MinionHealthTotal += otherNPC.life;
if (MinionHealthTotal <= 0
&& Main.netMode != NetmodeID.MultiplayerClient)
private void DoFirstStage (Player player)
if (FirstStageTimer > FirstStageTimerMax)
if (FirstStageTimer == 0)
Vector2 fromPlayer = NPC.Center - player.Center;
if (Main.netMode != NetmodeID.MultiplayerClient)
float angle = fromPlayer.ToRotation ();
float twelfth = MathHelper.Pi / 6;
MathHelper.Pi + Main.rand.NextFloat (-twelfth, twelfth);
if (angle > MathHelper.TwoPi)
angle -= MathHelper.TwoPi;
angle += MathHelper.TwoPi;
Vector2 relativeDestination =
angle.ToRotationVector2 () * distance;
FirstStageDestination = player.Center + relativeDestination;
Vector2 toDestination = FirstStageDestination - NPC.Center;
Vector2 toDestinationNormalized =
toDestination.SafeNormalize (Vector2.UnitY);
float speed = Math.Min (distance, toDestination.Length ());
NPC.velocity = toDestinationNormalized * speed / 30;
if (FirstStageDestination != LastFirstStageDestination)
if (Main.netMode != NetmodeID.Server)
NPC.position += NPC.netOffset;
Dust.QuickDustLine (NPC.Center +
toDestinationNormalized * NPC.width,
toDestination.Length () / 20f,
NPC.position -= NPC.netOffset;
LastFirstStageDestination = FirstStageDestination;
MinionHealthTotal / (float) MinionMaxHealthTotal;
NPC.alpha = (int) (remainingShields * 255);
NPC.rotation = NPC.velocity.ToRotation () - MathHelper.PiOver2;
private void DoSecondStage (Player player)
if (NPC.life < NPC.lifeMax * 0.5f)
ApplySecondStageBuffImmunities ();
Vector2 toPlayer = player.Center - NPC.Center;
player.Top + new Vector2 (NPC.direction * offsetX, -NPC.height);
Vector2 toAbovePlayer = abovePlayer - NPC.Center;
Vector2 toAbovePlayerNormalized =
toAbovePlayer.SafeNormalize (Vector2.UnitY);
float changeDirOffset = offsetX * 0.7f;
&& NPC.Center.X - changeDirOffset < abovePlayer.X
&& NPC.Center.X + changeDirOffset > abovePlayer.X)
if (NPC.Top.Y > player.Bottom.Y)
Vector2 moveTo = toAbovePlayerNormalized * speed;
NPC.velocity = (NPC.velocity * (inertia - 1) + moveTo) / inertia;
DoSecondStage_SpawnEyes (player);
NPC.damage = NPC.defDamage;
NPC.rotation = toPlayer.ToRotation () - MathHelper.PiOver2;
private void DoSecondStage_SpawnEyes (Player player)
Utils.Clamp ((float) NPC.life / NPC.lifeMax, 0.33f, 1f) * 90;
SecondStageTimer_SpawnEyes++;
if (SecondStageTimer_SpawnEyes > timerMax)
SecondStageTimer_SpawnEyes = 0;
if (NPC.HasValidTarget && SecondStageTimer_SpawnEyes == 0
&& Main.netMode != NetmodeID.MultiplayerClient)
Utils.Clamp (player.velocity.X * 16, -100, 100);
player.Bottom + new Vector2 (kitingOffsetX +
Main.rand.Next (-100, 100),
Main.rand.Next (50, 100));
int type = ModContent.ProjectileType < MinionBossEye > ();
int damage = NPC.damage / 2;
var entitySource = NPC.GetSource_FromAI ();
Projectile.NewProjectile (entitySource, position, -Vector2.UnitY,
type, damage, 0f, Main.myPlayer);
private void ApplySecondStageBuffImmunities ()
if (NPC.buffImmune[BuffID.OnFire])
NPC.BecomeImmuneTo (BuffID.OnFire);
if (Main.netMode != NetmodeID.MultiplayerClient)
NPC.ClearImmuneToBuffs (out bool anyBuffsCleared);
for (int loops = 0; loops < 2; loops++)
for (int i = 0; i < 50; i++)
Vector2 speed = Main.rand.NextVector2CircularEdge (1f, 1f);
Dust d = Dust.NewDustPerfect (NPC.Center, DustID.BlueCrystalShard, speed * 10 * (loops + 1), Scale:1.5f);
if (npc.type == NPCID.Boss1 && Main.expertMode)