using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Terraria;
using Terraria.Audio;
using Terraria.DataStructures;
using Terraria.GameContent;
using Terraria.GameContent.Bestiary;
using Terraria.GameContent.ItemDropRules;
using Terraria.GameContent.Personalities;
using Terraria.GameContent.UI;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;
using Terraria.Utilities;
namespace ZephyrusNPCs.Content.NPCs
{
// [AutoloadHead] and NPC.townNPC are extremely important and absolutely both necessary for any Town NPC to work at all.
[AutoloadHead]
public class Gunseller : ModNPC
{
public const string ShopName = "GunShop";
private static int ShimmerHeadIndex;
private static Profiles.StackedNPCProfile NPCProfile;
public override void Load() {
// Adds our Shimmer Head to the NPCHeadLoader.
ShimmerHeadIndex = Mod.AddNPCHeadTexture(Type, Texture + "_Shimmer_Head");
}
public override void SetStaticDefaults() {
Main.npcFrameCount[Type] = 25; // The total amount of frames the NPC has
NPCID.Sets.ExtraFramesCount[Type] = 9; // Generally for Town NPCs, but this is how the NPC does extra things such as sitting in a chair and talking to other NPCs. This is the remaining frames after the walking frames.
NPCID.Sets.AttackFrameCount[Type] = 4; // The amount of frames in the attacking animation.
NPCID.Sets.DangerDetectRange[Type] = 500; // The amount of pixels away from the center of the NPC that it tries to attack enemies.
NPCID.Sets.AttackType[Type] = 1; // The type of attack the Town NPC performs. 0 = throwing, 1 = shooting, 2 = magic, 3 = melee
NPCID.Sets.AttackTime[Type] = 45; // The amount of time it takes for the NPC's attack animation to be over once it starts.
NPCID.Sets.AttackAverageChance[Type] = 30; // The denominator for the chance for a Town NPC to attack. Lower numbers make the Town NPC appear more aggressive.
NPCID.Sets.HatOffsetY[Type] = 4; // For when a party is active, the party hat spawns at a Y offset.
NPCID.Sets.ShimmerTownTransform[NPC.type] = true; // This set says that the Town NPC has a Shimmered form. Otherwise, the Town NPC will become transparent when touching Shimmer like other enemies.
NPCID.Sets.ShimmerTownTransform[Type] = true; // Allows for this NPC to have a different texture after touching the Shimmer liquid.
// Influences how the NPC looks in the Bestiary
NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers() {
Velocity = 1f, // Draws the NPC in the bestiary as if its walking +1 tiles in the x direction
Direction = 1 // -1 is left and 1 is right. NPCs are drawn facing the left by default but ExamplePerson will be drawn facing the right
// Rotation = MathHelper.ToRadians(180) // You can also change the rotation of an NPC. Rotation is measured in radians
// If you want to see an example of manually modifying these when the NPC is drawn, see PreDraw
};
NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers);
// Set Example Person's biome and neighbor preferences with the NPCHappiness hook. You can add happiness text and remarks with localization (See an example in ExampleMod/Localization/en-US.lang).
// NOTE: The following code uses chaining - a style that works due to the fact that the SetXAffection methods return the same NPCHappiness instance they're called on.
NPC.Happiness
.SetBiomeAffection<OceanBiome>(AffectionLevel.Like) // Example Person prefers the forest.
.SetBiomeAffection<HallowBiome>(AffectionLevel.Dislike) // Example Person dislikes the snow.
.SetBiomeAffection<JungleBiome>(AffectionLevel.Love) // Example Person likes the Example Surface Biome
.SetNPCAffection(NPCID.Stylist, AffectionLevel.Love) // Loves living near the dryad.
.SetNPCAffection(NPCID.ArmsDealer, AffectionLevel.Like) // Likes living near the guide.
.SetNPCAffection(NPCID.Nurse, AffectionLevel.Dislike) // Dislikes living near the merchant.
.SetNPCAffection(NPCID.GoblinTinkerer, AffectionLevel.Hate) // Hates living near the demolitionist.
; // < Mind the semicolon!
// This creates a "profile" for ExamplePerson, which allows for different textures during a party and/or while the NPC is shimmered.
NPCProfile = new Profiles.StackedNPCProfile(
new Profiles.DefaultNPCProfile(Texture, NPCHeadLoader.GetHeadSlot(HeadTexture), Texture + "_Party"),
new Profiles.DefaultNPCProfile(Texture + "_Shimmer", ShimmerHeadIndex, Texture + "_Shimmer_Party")
);
}
public override void SetDefaults() {
NPC.townNPC = true; // Sets NPC to be a Town NPC
NPC.friendly = true; // NPC Will not attack player
NPC.width = 18;
NPC.height = 40;
NPC.aiStyle = 7;
NPC.damage = 10;
NPC.defense = 25;
NPC.lifeMax = 250;
NPC.HitSound = SoundID.NPCHit1;
NPC.DeathSound = SoundID.NPCDeath1;
NPC.knockBackResist = 0.5f;
AnimationType = NPCID.ArmsDealer;
}
public override void SetBestiary(BestiaryDatabase database, BestiaryEntry bestiaryEntry) {
// We can use AddRange instead of calling Add multiple times in order to add multiple items at once
bestiaryEntry.Info.AddRange(new IBestiaryInfoElement[] {
// Sets the preferred biomes of this town NPC listed in the bestiary.
// With Town NPCs, you usually set this to what biome it likes the most in regards to NPC happiness.
BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Biomes.Surface,
// You can add multiple elements if you really wanted to
// You can also use localization keys (see Localization/en-US.lang)
new FlavorTextBestiaryInfoElement("Mods.ZephyrusNPCs.Bestiary.Gunseller")
});
}
// The PreDraw hook is useful for drawing things before our sprite is drawn or running code before the sprite is drawn
// Returning false will allow you to manually draw your NPC
public override bool PreDraw(SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) {
// This code slowly rotates the NPC in the bestiary
// (simply checking NPC.IsABestiaryIconDummy and incrementing NPC.Rotation won't work here as it gets overridden by drawModifiers.Rotation each tick)
if (NPCID.Sets.NPCBestiaryDrawOffset.TryGetValue(Type, out NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers)) {
drawModifiers.Rotation += 0.001f;
// Replace the existing NPCBestiaryDrawModifiers with our new one with an adjusted rotation
NPCID.Sets.NPCBestiaryDrawOffset.Remove(Type);
NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers);
}
return true;
}
public override void HitEffect(NPC.HitInfo hit) {
int num = NPC.life > 0 ? 1 : 5;
// Create gore when the NPC is killed.
if (Main.netMode != NetmodeID.Server && NPC.life <= 0) {
// Retrieve the gore types. This NPC has shimmer and party variants for head, arm, and leg gore. (12 total gores)
string variant = "";
if (NPC.IsShimmerVariant) variant += "_Shimmer";
if (NPC.altTexture == 1) variant += "_Party";
int hatGore = NPC.GetPartyHatGore();
int headGore = Mod.Find<ModGore>($"{Name}_Gore{variant}_Head").Type;
int armGore = Mod.Find<ModGore>($"{Name}_Gore{variant}_Arm").Type;
int legGore = Mod.Find<ModGore>($"{Name}_Gore{variant}_Leg").Type;
// Spawn the gores. The positions of the arms and legs are lowered for a more natural look.
if (hatGore > 0) {
Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, hatGore);
}
Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, headGore, 1f);
Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, 20), NPC.velocity, armGore);
Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, 20), NPC.velocity, armGore);
Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, 34), NPC.velocity, legGore);
Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, 34), NPC.velocity, legGore);
}
}
public override bool CanTownNPCSpawn(int numTownNPCs) { // Requirements for the town NPC to spawn.
if (NPC.downedBoss2) {
// If Example Person has spawned in this world before, we don't require the user satisfying the ExampleItem/ExampleBlock inventory conditions for a respawn.
return true;
}
return false;
}
public override ITownNPCProfile TownNPCProfile() {
return NPCProfile;
}
public override List<string> SetNPCNameList() {
return new List<string>() {
"Zayne",
"Zippy",
"Zeph",
"Zak",
"Emmett",
"Sean",
"Carl",
"Ceasar",
"Jason"
};
}
public override void FindFrame(int frameHeight) {
/*npc.frame.Width = 40;
if (((int)Main.time / 10) % 2 == 0)
{
npc.frame.X = 40;
}
else
{
npc.frame.X = 0;
}*/
}
public override string GetChat() {
WeightedRandom<string> chat = new WeightedRandom<string>();
int partyGirl = NPC.FindFirstNPC(NPCID.PartyGirl);
if (partyGirl >= 0 && Main.rand.NextBool(4)) {
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerPartyGirlDialogue", Main.npc[partyGirl].GivenName));
}
// These are things that the NPC has a chance of telling you when you talk to it.
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerStandardDialogue1"));
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerStandardDialogue2"));
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerStandardDialogue3"));
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerStandardDialogue4"));
chat.Add(Language.GetTextValue("Mods.ZephyrusNPCs.GunsellerRareDialogue"), 0.1);
string chosenChat = chat; // chat is implicitly cast to a string. This is where the random choice is made.
return chosenChat;
}
public override void SetChatButtons(ref string button, ref string button2)
{
button = Language.GetTextValue("LegacyInterface.28");
}
public override void OnChatButtonClicked(bool firstButton, ref string shopName)
{
if (firstButton)
{
shopName = "GunShop";
}
}
// Not completely finished, but below is what the NPC will sell
public override void AddShops() {
var npcShop = new NPCShop(Type, ShopName)
.Add(new Item(ItemID.Revolver) { shopCustomPrice = 60000 })
.Add(new Item(ItemID.Boomstick) { shopCustomPrice = 30000 })
.Add(new Item(ItemID.Handgun) { shopCustomPrice = 50000 }, Condition.DownedSkeletron)
.Add(new Item(ItemID.Shotgun) { shopCustomPrice = 100000 }, new Condition("", () => Main.hardMode))
.Add(new Item(ItemID.Uzi) { shopCustomPrice = 150000 }, new Condition("", () => Main.hardMode))
.Add(new Item(ItemID.TacticalShotgun) { shopCustomPrice = 500000 }, new Condition("", () => NPC.downedPlantBoss))
.Add(new Item(ItemID.SniperRifle) { shopCustomPrice = 1000000 }, new Condition("", () => NPC.downedPlantBoss));
npcShop.Register(); // Name of this shop tab
}
public override void ModifyActiveShop(string shopName, Item[] items) {
foreach (Item item in items) {
// Skip 'air' items and null items.
if (item == null || item.type == ItemID.None) {
continue;
}
// If NPC is shimmered then reduce all prices by 50%.
if (NPC.IsShimmerVariant) {
int value = item.shopCustomPrice ?? item.value;
item.shopCustomPrice = value / 2;
}
}
}
// Make this Town NPC teleport to the King and/or Queen statue when triggered. Return toKingStatue for only King Statues. Return !toKingStatue for only Queen Statues. Return true for both.
public override bool CanGoToStatue(bool toKingStatue) => true;
// Make something happen when the npc teleports to a statue. Since this method only runs server side, any visual effects like dusts or gores have to be synced across all clients manually.
public override void OnGoToStatue(bool toKingStatue) {
StatueTeleport();
}
// Create a square of pixels around the NPC on teleport.
public void StatueTeleport() {
for (int i = 0; i < 30; i++) {
Vector2 position = Main.rand.NextVector2Square(-20, 21);
if (Math.Abs(position.X) > Math.Abs(position.Y)) {
position.X = Math.Sign(position.X) * 20;
}
else {
position.Y = Math.Sign(position.Y) * 20;
}
}
}
public override void TownNPCAttackStrength(ref int damage, ref float knockback) {
if (!Main.hardMode)
{
damage = 26;
}
if (Main.hardMode)
{
damage = 30;
}
knockback = 4f;
}
public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) {
cooldown = 10;
randExtraCooldown = 5;
}
public override void TownNPCAttackProj(ref int projType, ref int attackDelay) {
attackDelay = 14;
if (!Main.hardMode)
{
projType = 14;
}
if (Main.hardMode)
{
attackDelay = 9;
projType = 242;
}
}
public override void DrawTownAttackGun(ref Texture2D item, ref Rectangle itemFrame, ref float scale, ref int horizontalHoldoutOffset)/* tModPorter Note: closeness is now horizontalHoldoutOffset, use 'horizontalHoldoutOffset = Main.DrawPlayerItemPos(1f, itemtype) - originalClosenessValue' to adjust to the change. See docs for how to use hook with an item type. */ //Allows you to customize how this town NPC's weapon is drawn when this NPC is shooting (this NPC must have an attack type of 1). Scale is a multiplier for the item's drawing size, item is the ID of the item to be drawn, and closeness is how close the item should be drawn to the NPC.
{
scale = 1f;
horizontalHoldoutOffset = 20;
if (!Main.hardMode)
{
item = TextureAssets.Item[ItemID.Handgun].Value;
}
if (Main.hardMode)
{
item = TextureAssets.Item[ItemID.Uzi].Value;
}
}
public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) {
multiplier = 12f;
}
}
}