Skip to content

Instantly share code, notes, and snippets.

@Rijam
Last active May 14, 2024 17:52
Show Gist options
  • Save Rijam/c67eb86eb3aef9db7ac474ad5ef7dfba to your computer and use it in GitHub Desktop.
Save Rijam/c67eb86eb3aef9db7ac474ad5ef7dfba to your computer and use it in GitHub Desktop.
Ultimate Town NPC Guide

This guide has been moved!

View the guide at https://github.com/Rijam/TownNPCGuide/wiki now

Ultimate Town NPC Guide

Need help creating a Town NPC for your mod? Here is a comprehensive guide on how to do just that!

This guide focuses on "traditional" Town NPCs. The Traveling Merchant, Skeleton Merchant, Old Man, and Town Pets/Slimes are a little different.

This guide is designed for 1.4.4

TODO:

  • Double check spelling and wording.
  • Check any TODO's listed in the code and such.
  • Perhaps add more collapsible spoilers? This page is super long lol.

Table of Contents

What are Town NPCs?

Town NPCs are the non-player characters that will generally live in homes, can be spoken to, reduce enemy spawn rates, offer a service to the player, and can attack enemies.

Most players will refer to these characters as simply "NPCs", but that is ambiguous in the context of the Terraria code and Terraria modding. In the context of modding, many things are NPCs such as enemies, bosses, critters, and even some 'projectiles'. This guide will refer to the type of NPCs described above as Town NPCs. Other names include "Townies" and "Villagers". "Friendly NPCs" is not adequate enough because critters are also friendly.

Spriting

Your Town NPC needs a sprite in order to be seen! Unlike player sprites, Town NPC sprites are a little more flexible. Still, there are some things you should follow.

This guide may say sprite or texture interchangeably. While there technically a difference, this guide does not differentiate between the two terms.

Design

The design of your Town NPC can be almost whatever you want.

If you want your design to be similar to vanilla Terraria's Town NPC designs, here are some things to keep in mind: Male Town NPCs have the same design as the male player, but female Town NPCs are much thinner, even thinner than the female player. Here are examples of a male and female design.

Male: TutorialTownNPC_MaleSingle Female: TutorialTownNPC_FemaleSingle

The legs, arms, hands, and chest of the female design are skinnier than the male design. This does differ, though. For example, the Zoologist's arms are one pixel longer and the Nurse and the Princess have wider arms. Both the male and female designs are the same height, though.

Head Icon

Every Town NPC requires* a head icon. The head icon is shown on the map and is needed for the housing menu/banners. If you fail to have a working head icon, players will not be able to move which house your Town NPC lives in and the head icon will be invisible.

The head icon is usually just a cut out of the Town NPC's head.

TutorialTownNPC_Head

*If you are making a Skeleton Merchant like Town NPC, it technically doesn't count as a Town NPC. See Example Bone Merchant for more information.

Frames

Town NPCs follow specific framing to display correctly. In this guide, we will be counting the frame numbers starting from 0.

  • Frame 0: Idle
    • The normal standing pose of the Town NPC
  • Frame 1: Jumping/Falling
    • The frame that is used when the Town NPC jumps or is falling
  • Frame 2-15: Walking frames
    • There are 14 (total) walking frames. This is the same number as the player's walking frames.
    • If you extracted the vanilla assets, you may have noticed some of the female Town NPCs only have 12 walking frames. You can make your sprite only have 12 frames (or other amounts) if you set up the code correctly, but it will cause the party hat to bounce weirdly when walking. 14 walking frames is the amount for Terraria's players, too.
  • Frame 16, 17: Raising arm
    • These two frames are used when the Town NPC is talking or playing Rock Paper Scissors.
  • Frame 18: Sitting
    • This frame is used when the Town NPC sits in a chair, typically at night time.
    • *Advanced note: The sprite is drawn on the screen 2 real pixels higher than where the NPC normally is drawn. Do not worry about this when creating the sprite, but it is useful to know for drawing glow masks and other effects. See Advanced - Drawing
  • Frame 19: Talking
    • This frame is used when the Town NPC is talking. Depending on the design of your Town NPC, it may not make sense to have their mouth visible.
  • Frame 20: Blinking/Eyes Closed
    • Occasionally the Town NPC will blink. This frame is used for when the eyes are closed.
  • Frame 21+: Attacking frames
    • Depending on the style that your Town NPC will attack, the number of frames varies.
    • Throwing, Shooting, and Melee attacks all have 4 attacking frames.
      • For shooting attacks, if the Town NPC is aiming straight ahead, it will instead use the second arm raised frame.
    • Magic attacks only have 2 frames.
    • The Guide and Traveling Merchant have 5 frames for their shooting frames.

Here are some examples to visualize the framing of Town NPCs. The magenta lines in the second image are the "padding" between frames. See the Spriting Guide for more information on padding. For the attacking frames, TH stands for throwing, SH for shooting, MA for magic, and ME for melee.

TownNPCTemplate_FrameNumbers TownNPCTemplate_ColoredLabeled TutorialTownNPC_Body

Your Town NPC size does not need to follow the template above. Most vanilla Town NPCs are the same size as the template above. Town NPCs like the Wizard who have a tall hat have taller frames. You can also make your frames wider. You just need to meake sure your frames are all a consistent width and height.

One thing to keep in mind: the width and the height of the frame will be the bounding box (not to be confused with the hitbox/hurtbox, that is set with NPC.width and NPC.height) of the Town NPC. The bounding box is used for displaying the NPC's name when hovering over them and interacting with them.

For this guide, we will be using this sprite which has 25 total frames (0-24) **Click to Expand**

TutorialTownNPC

For a female sprite, check the Profile section.

Extract Vanilla Sprites

Use a tool such as TConvert to extract the vanilla assets. Most of the sprites will be in the Images/TownNPCs folder. The remaining sprites will be named NPC_#. Use this page to find the NPC ID for the Town NPC you are looking for.

Player Renderer Mod

The Player Renderer mod created by Auxves will render your current player into a Town NPC sprite sheet. It works with modded items and modded hairstyles. The mod does a pretty good job at generating a sprite sheet for you, but there are a few things you should keep in mind/need to do manually still.

  • The size of the frames generated by this mod are different from the template provided above and from the player's frames for equipment. This isn't a necessarily a bad thing, the frames can be any size, but it means they will not line up with the template or equipment items if you want to edit the sprite sheet.
  • The mod renders what you see. That means things like the Shroomite or Vortex stealth will make the render almost completely invisible.
  • Extra tall hats, such as the Wizard Hat, will overlap frames.
  • Wide accessories, like wings, might get cut off.
  • The mod renders everything in the player draw layers. That includes some extra things like the Solar Flare shield.
  • The mod creates a head icon automatically, but you'll need to crop it still.
  • The raise hand frames, sitting frame, talking frame, and attacking frames will need manual touch ups.

This mod can provide a great starting point for designing your Town NPC. Most notably the walking frames, which are the most annoying parts of the sprite sheet to create.

Create Your Town NPC's Class

The first thing we need to do to get our Town NPC in game is to create a class for it inheriting ModNPC. We use the [AutoloadHead] attribute to automatically load the head texture and assign it to our Town NPC. The head texture must be named the class name _Head (TutorialTownNPC_Head in this example). You will have to change the namespace and class name to match your mod.

using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace TownNPCGuide.Content.NPCs.TownNPCs
{
	[AutoloadHead]
	public class TutorialTownNPC : ModNPC
	{
		// overrides go here
	}
}

Set Defaults

Just like Items and Projectiles, the SetStaticDefaults() and SetDefaults() hooks are imporant for NPCs. Here the basic properties that are recommended for a Town NPC:

public override void SetStaticDefaults() {
	Main.npcFrameCount[Type] = 25; // The total amount of frames the NPC has. You may need to change this based on how many frames your sprite sheet has.

	NPCID.Sets.ExtraFramesCount[Type] = 9; // These are the frames for raising their arm, sitting, talking, blinking, and attack. This is the remaining number of frames after the walking frames.
	NPCID.Sets.AttackFrameCount[Type] = 4; // The amount of frames in the attacking animation.
	NPCID.Sets.DangerDetectRange[Type] = 700; // The amount of pixels away from the center of the NPC that it tries to attack enemies. There are 16 pixels in 1 tile so a range of 700 is almost 44 tiles.
	NPCID.Sets.AttackType[Type] = 0; // The type of attack the Town NPC performs. 0 = throwing, 1 = shooting, 2 = magic, 3 = melee
	NPCID.Sets.AttackTime[Type] = 90; // The amount of time it takes for the NPC's attack animation to be over once it starts. Measured in ticks. There are 60 ticks per second, so an amount of 90 will take 1.5 seconds.
	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. Adjust this number to change where on your NPC's head the party hat sits.
}

public override void SetDefaults() {
	NPC.townNPC = true; // Sets NPC to be a Town NPC
	NPC.friendly = true; // The NPC will not attack player
	NPC.width = 18; // The width of the hitbox (hurtbox)
	NPC.height = 40; // The height of the hitbox (hurtbox)
	NPC.aiStyle = NPCAIStyleID.Passive; // Copies the AI of passive NPCs. This is AI Style 7.
	NPC.damage = 10; // This is the amount of damage the NPC will deal as contact damage. This is NOT the damage dealt by the Town NPC's attack.
	NPC.defense = 15; // All vanilla Town NPCs have a base defense of 15. This will increases as more bosses are defeated.
	NPC.lifeMax = 250; // All vanilla Town NPCs have 250 HP.
	NPC.HitSound = SoundID.NPCHit1; // The sound that is played when the NPC takes damage.
	NPC.DeathSound = SoundID.NPCDeath1; // The sound that is played with the NPC dies.
	NPC.knockBackResist = 0.5f; // All vanilla Town NPCs have 50% knockback resistance. Think of this more as knockback susceptibility. 1f = 100% knockback taken, 0f = 0% knockback taken.

	AnimationType = NPCID.Guide; // Sets the animation style to follow the animation of your chosen vanilla Town NPC.
}

There are many more things that you can set in SetStaticDefaults() and SetDefaults(), those will be covered in the others sections of this guide.

Setting the NPC Name in Localization File

Not to be confused with the random names that a Town NPC can have, see the Names section for that.

The "profession" of the Town NPC will be autoloaded based on the class name. If you wish to change it (without changing the class name) change the DisplayName in your localization file. Check out the autoloading and localization guides if you are unfamiliar with how those work.

Spawn Condition

We've set up the basics minimum for our Town NPC, but it won't ever spawn in by itself. We'll need to override the CanTownNPCSpawn() hook to tell the game when it can spawn.

public override bool CanTownNPCSpawn(int numTownNPCs) {
	return true;
}

The above code says that our Town NPC can always arrive as soon as there is a house available. We can add if statements to change the conditions.

public override bool CanTownNPCSpawn(int numTownNPCs) {
	if (NPC.downedBoss1) {
		return true;
	}
	return false;
}

Now the above code says that our Town NPC can only arrive once the Eye of Cthulhu has been defeated (NPC.downedBoss1 corresponds to the Eye of Cthulhu). We don't need an else statement in this case because if the Eye of Cthulhu has not been defeated, the code will return false meaning it cannot spawn.

public override bool CanTownNPCSpawn(int numTownNPCs) {
	if (NPC.AnyNPCs(NPCID.ArmsDealer) && numTownNPCs >= 5 && Main.hardMode) {
		return true;
	}
	return false;
}

This code says that our Town NPC can only arrive if the Arms Dealer is present, there are at least 5 Town NPCs, and the world is in Hardmode.

public override bool CanTownNPCSpawn(int numTownNPCs) {
	// Search through all of the players
	for (int k = 0; k < Main.maxPlayers; k++) {
		Player player = Main.player[k];
		// If the player is not active (disconnected/doesn't exist), continue to the next player.
		if (!player.active) {
			continue;
		}

		// Player has at least 100 maximum mana, return true.
		// statManaMax2 includes the "temporary" mana. This includes accessories/potions/etc. that give extra max mana.
		if (player.statManaMax2 >= 100) {
			return true;
		}
	}
	return false;
}

This final code example says that our Town NPC can arrive if at least one player who is playing has at least 100 maximum mana.

Useful conditions can be found in the Basic NPC Spawning Guide, the NPC Class Documentation, and Player Class Documentation.

Specific Housing Requirements

If you want your Town NPC to only be able to live in specific requirements, like how the Truffle needs to live in a surface mushroom biome, we can override the CheckConditions() hook. If you want your Town NPC to be able to live in any normal house, simply don't include this hook.

public override bool CheckConditions(int left, int right, int top, int bottom) {
	// This code is very similar to how the Truffle checks if it is a surface mushroom biome. In this example, we are checking it is a surface Hallow biome instead.
	
	// If the bottom bound is below the surface height, return false.
	// The code might like the opposite of what you'd expect. That is because positive Y values go down.
	// So if the bottom is greater than the surface height, that means it is deeper in the world.
	if (bottom > Main.worldSurface) {
		return false;
	}

	// We call this function to get the bounds of the "biome". We are going to count how many Hallow blocks are in the area and see if it is enough to count as a Hallow biome.
	WorldGen.Housing_GetTestedRoomBounds(out var startX, out var endX, out var startY, out var endY);
	int score = 0;
	for (int i = startX + 1; i < endX; i++) {
		for (int j = startY + 2; j < endY + 2; j++) {
			Tile tile = Main.tile[i, j];
			// If the tile we are searching is Hallowed Grass, Pearlstone, Pearlsand, Pearlsandstone, Hardened Pearlsand, or Pink Ice, increase the score.
			if (tile.HasTile && (tile.TileType == TileID.HallowedGrass || tile.TileType == TileID.Pearlstone || tile.TileType == TileID.Pearlsand || tile.TileType == TileID.HallowSandstone || tile.TileType == TileID.HallowHardenedSand || tile.TileType == TileID.HallowedIce)) {
				score++;
			}
		}
	}

	// If the score matches the threshold for the biome, return true. In this case, 125 tiles are needed to count as a Hallow biome.
	if (score >= SceneMetrics.HallowTileThreshold) {
		return true;
	}

	return false;
}

In this example, we are checking if the house is in a surface Hallow biome. We make sure the height is correct, then count the number of Hallow tiles in the area to see if it counts as a Hallow biome. Why is this so complex? Why can't we just know what biome the house is in? Generally, only players know which biome they are in. NPCs, even bosses, don't know what biome they are in and base it off of where the player is. More on this in the Shop section.

Rescuable Town NPC

See Intermediate - Rescuable Town NPC

Names

All vanilla Town NPCs except Santa and the Old Man have random names that they can spawn with. Adding random names to our Town NPC is very easy. We need to override the SetNPCNameList() hook.

public override List<string> SetNPCNameList() {
	return new List<string>() {
		"blushiemagic",
		"Chicken Bones",
		"jopojelly",
		"Jofairden",
		"Mirsario",
		"Solxan"
	};
}

You can make this list as long or as short as you like.

If you do not want a random name like Santa or the Old Man, simply don't include this hook. The "profession" name will be shown instead.

Chat

We want our Town NPC to say things when we talk to them! For this, we override the GetChat() hook.

public override string GetChat() {
	return "Hello world!";
}

Now our Town NPC will say "Hello world!" when we speak to them! But, we want them to say other things, too. The best way to do this is to use a weighted random string.

public override string GetChat() {
	WeightedRandom<string> chat = new();

	chat.Add("Hi there!");
	chat.Add("Hello! This message is slightly more common than the other message!", 1.5);

	return chat;
}

Now, the game will randomly pick one of those two message to display with the second message being 1.5 times more common.

An even better way to type the chat messages is to use localization. This way we can add support for other languages without needing to update our Town NPC's code.

public override string GetChat() {
	WeightedRandom<string> chat = new();

	// Here we have 4 standard messages that the Town NPC can say
	chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue1"));
	chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue2"));
	chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue3"));
	chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue4"));
	
	// If the local player has a Terra Toilet in their inventory.
	// We can use Main.LocalPlayer here because GetChat() runs client side. So, the only player who can see the chat is the one chatting with the Town NPC.
	if (Main.LocalPlayer.HasItem(ItemID.TerraToilet)) {
		// This message is 2 times as common.
		chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.PlayerHasTerraToilet"), 2);
	}
	// If the local player is located in the desert.
	// As mentioned briefly before, only players know which biome they are in. Since the player is standing right next to the Town NPC to chat, checking the player is not a big deal.
	if (Main.LocalPlayer.ZoneDesert) {
		// This message is 2 times as rare.
		chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.DesertDialogue"), 0.5);
	}
	// We check that an Angler is present in the world. NPC.FindFirstNPC() returns the index of the NPC in Main.npc[]
	int angler = NPC.FindFirstNPC(NPCID.Angler);
	if (angler >= 0) { // If the Angler is not present, NPC.FindFirstNPC() will return -1.
		// We've set up our localization key value pair in such a way that {0} will be replaced with the name of the Angler.
		// You can use Main.npc[angler].FullName instead if you want it to say "Adam the Angler" instead of just "Adam".
		chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.AnglerDialogue", Main.npc[angler].GivenName));
	}
	// If Moon Lord has been defeated in this world.
	if (NPC.downedMoonlord) {
		// We've set this message up so that {0} will be replaced the name of the player and {1} will be replaced with the name of the world.
		chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.DownedMoonLord", Main.LocalPlayer.name, Main.worldName));
	}

	return chat;
}

And in our localization file we have:

Mods: {
	TownNPCGuide: {
		NPCs: {
			TutorialTownNPC: {
				# Other keys have been omitted here for this example.
			
			Dialogue: {
					StandardDialogue1: Hello!
					StandardDialogue2: Reading up on those tutorials I see!
					StandardDialogue3: All this coding is confusing...
					StandardDialogue4: Have you met my buddy Example Person?
					PlayerHasTerraToilet: Ah, a throne fit for a king!
					DesertDialogue: Do you have extra water? It sure is hot in the desert.
					# {0} and {1} are place holders which will be replaced by other text.
					AnglerDialogue: It seems like everyone has a different opinion about {0}.
					DownedMoonLord: Congrats {0}! You've defeated Moon Lord in {1}!
					ExamplePerson: "{0} is my good friend. If you have any questions about modding, they can sure help you!"
					OnTeleportToStatue: Woah!
					SecondButtonChat: Yo!
					OnRescue: Hey! Thanks for saving me!
				}
			}
		}
	}
}

Look at the localization guide for more information about localization.

Buttons

We've made it so our Town NPC has dialogue, but let's give them some buttons for us to click. For this, we override the SetChatButtons() hook.

public override void SetChatButtons(ref string button, ref string button2) {
	button = Language.GetTextValue("LegacyInterface.28"); // This will automatically be translated to say "Shop".
}

There, we go! Our Town NPC now has a "Shop" button. But, if you try to click on it, you'll notice that nothing happens. To give functionally to our buttons, we need to override the OnChatButtonClicked() hook.

public override void OnChatButtonClicked(bool firstButton, ref string shop) {
	if (firstButton) {
		// We set `shop` to a string that corresponds to our shop name.
		// See the shop section for more info.
		shop = ShopName;
	}
}

Now when we click the Shop button, the shop actually opens.

You can also set a second button here. Some Town NPCs like the Painter, Dye Trader, Tavernkeep, and Party Girl have a second chat button. You can use if statements to change what any of the buttons are, too. The Dryad's Status button changes to Purify when holding a Joja Cola, for example. Example Person has some more examples on setting the chat buttons.

public override void SetChatButtons(ref string button, ref string button2) {
	button = Language.GetTextValue("LegacyInterface.28"); // This will automatically be translated to say "Shop".
	if (Main.dayTime) { // The second button will only appear during the day time.
		button2 = Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.UI.SecondButton");
	}
}

public override void OnChatButtonClicked(bool firstButton, ref string shop) {
	if (firstButton) {
		// We set `shop` to a string that corresponds to our shop name.
		// See the shop section for more info.
		shop = ShopName;
	}
	if (!firstButton) {
		// Do something else here.
	}
}

Shop

If you have set shop = to a string in OnChatButtonClicked(), the shop will open up. We can make it any string, but we should define it at the top of our Town NPC so we can use it in multiple places without worrying about naming our shop incorrectly.

public const string ShopName = "Shop";

We need to set shop to our variable we defined in OnChatButtonClicked().

Now, let's add some items to the shop. To do this, we override the AddShops() hook.

public override void AddShops() {
	// First, create our new shop.
	// The "Type" parameter assigns the shop to our Town NPC.
	// The "ShopName" parameter is a string that refers to which shop to add. We defined this variable at the beginning of our ModNPC.
	// Most Town NPCs only have one shop, but adding multiple shops is easy with this method.
	var npcShop = new NPCShop(Type, ShopName);

	// Here we set the first slot to an Iron Pickaxe.
	// By default, the prices set to the value of the item which is 5 times the sell price.
	npcShop.Add(ItemID.IronPickaxe);
	// Add an item to the next slot.
	npcShop.Add(ItemID.Starfury);
	// Here is how you would set a modded item from your mod.
	npcShop.Add(ModContent.ItemType<TutorialItem>());

	// If want to change the price of an item, you can set the shopCustomPrice value.
	// To do this, we need to create a new Item object with a constructor setting the shopCustomPrice variable.
	// 1 = 1 copper coin, 100 = 1 silver coin, 10000 = 1 gold coin, and 1000000 = 1 platinum coin.
	// In this example, our Stone Blocks will cost 10 copper.
	npcShop.Add(new Item(ItemID.StoneBlock) { shopCustomPrice = 10 });
	// An easier way to set the price is to use Item.buyPrice()
	npcShop.Add(new Item(ItemID.DirtBlock) { shopCustomPrice = Item.buyPrice(silver: 5) });

	// We can use the Condition class to change the availability of items.
	// We put the condition after our item separated by a comma.
	// Here, our item will be available after any Mechanical Boss has been defeated.
	npcShop.Add(ItemID.HallowedBar, Condition.DownedMechBossAny);
	// Here our item will be available during Full Moons and New Moons.
	npcShop.Add(ItemID.MoonCharm, Condition.MoonPhases04);
	// Here our item will be available in the Hallow biome AND while below the surface.
	// Adding multiple conditions will require that ALL conditions are met for the item to appear.
	npcShop.Add(ItemID.CrystalShard, Condition.InHallow, Condition.InBelowSurface);

	// We can create our own conditions if the default ones don't work for us.
	// For this example, we are saying the Boomstick is available if in the Jungle OR Queen Bee has been defeated.
	// Conditions take two arguments: a string and a Func<bool>

	// The string is the availability written in plain language.
	// In our localization file, we have "When in the Jungle or after Queen Bee has been defeated"

	// The Func<bool> is a fancy form of a boolean expression.
	// We can write any expression as long as it will be evaluate as a boolean expression.
	// We can use existing Conditions with IsMet() to get their boolean values.
	// This is convenient because those Conditions are already written and we know they are correct.
	// Alternately, we could write "() => Main.LocalPlayer.ZoneJungle || NPC.downedQueenBee" if we don't want to use the existing Conditions.
	npcShop.Add(ItemID.Boomstick, new Condition(Language.GetTextValue("Mods.TownNPCGuide.Conditions.JungleOrDownedQueenBee"), () => Condition.InJungle.IsMet() || Condition.DownedQueenBee.IsMet()));

	// If we are to use a custom condition that we wrote several times, it might be wise to create our own class.
	// That way we can just define the Condition once instead of every time we need it.
	// This condition is defined in a different class that we created called CustomCondtions.
	npcShop.Add(ItemID.BirdieRattle, CustomConditions.TravelingMerchantPresentOrHardmode);

	// You may be temped to use Main.rand to set the availability of your item, this may not do what you want it do to.
	// In this example, there is a 50% chance of our item being in the shop.
	// However, this will chance will run every time the shop is opened. So, players can just close and reopen the shop to roll the chance again.
	npcShop.Add(ItemID.BoneTorch, new Condition("Not recommended", () => Main.rand.NextBool(2)));

	// Finally, we register or shop. If you forget this, nothing will shop up in your shop!
	npcShop.Register();
}

Setting the availability of an item requires a Condition. This is a record created by tModLoader and has a bunch of useful conditions already pre-defined. See the Condition guide for more info and for a list of the pre-defined Conditions: Conditions

Just like GetChat(), the shop runs client side. That means we can get away with using Main.LocalPlayer instead of having to check for every player on a server. We can check many stats about the player, world, or other NPCs for the availability of the shop items.

As mentioned elsewhere, to get what biome the Town NPC is in, we check the player's current biome. This is because only players know what biome they are in. Main.LocalPlayer.ZoneX bools state which biomes the player is currently in. Checking the player instead of properly checking the Town NPC is ok in this situation because the player needs to be standing next to the Town NPC to talk/buy items anyway.

Other useful information that can be use for Conditions can be found in the Basic NPC Spawning Guide, the NPC Class Documentation, and Player Class Documentation. Remember that they need to be made into a Condition record for them to be used in the shops. Refer back to the Conditions guide for more information.

AddShops() runs only once during mod load time. To change the shop 'in real time', use ModifyActiveShop(). See Advanced - Shop for more information.

Click here for Pre-1.4.4 Shop code **Click to Expand**
public override void SetupShop(Chest shop, ref int nextSlot) {
	// nextSlot is the slot for where our item will be.
	// Here we set the first slot to an Iron Pickaxe.
	// By default, the prices set to the value of the item which is 5 times the sell price.
	shop.item[nextSlot].SetDefaults(ItemID.IronPickaxe);
	nextSlot++; // We increment nextSlot so it goes to slot 1 (slot 0 the the first slot).
	shop.item[nextSlot].SetDefaults(ItemID.Starfury);
	nextSlot++;
	// Here is how you would set a modded item from your mod.
	shop.item[nextSlot].SetDefaults(ModContent.ItemType<TutorialItem>());
	nextSlot++;

	// If want to change the price of an item, you can set the shopCustomPrice value.
	// Items with no value can be purchased infinitely for free. So, it's good to have a custom price for them.
	shop.item[nextSlot].SetDefaults(ItemID.StoneBlock);
	// 1 = 1 copper coin, 100 = 1 silver coin, 10000 = 1 gold coin, and 1000000 = 1 platinum coin.
	// In this example, our Stone Blocks will cost 10 copper coins.
	shop.item[nextSlot].shopCustomPrice = 10;
	nextSlot++;

	shop.item[nextSlot].SetDefaults(ItemID.DirtBlock);
	// An easier way to set the price is to use Item.buyPrice()
	shop.item[nextSlot].shopCustomPrice = Item.buyPrice(silver: 5);
	nextSlot++;

	// We can also compact our statements by setting the item and incrementing nextSlot in one step.
	shop.item[nextSlot++].SetDefaults(ItemID.Boomstick);
	shop.item[nextSlot++].SetDefaults(ItemID.BirdieRattle);

	// We can use if statements to change the availability of items.
	// Here, our item will be available after any Mechanical Boss has been defeated.
	if (NPC.downedMechBossAny) {
		shop.item[nextSlot++].SetDefaults(ItemID.HallowedBar);
	}
	// Here our item will be available in the Hallow biome.
	// We check the player instead of the NPC because only players know which biome they are in.
	// The shop runs client side, so we can use Main.LocalPlayer
	if (Main.LocalPlayer.ZoneHallow) {
		shop.item[nextSlot++].SetDefaults(ItemID.CrystalShard);
	}
	// Here our item will be available during Full Moons and New Moons.
	if (Main.moonPhase == 0 || Main.moonPhase == 4) {
		shop.item[nextSlot++].SetDefaults(ItemID.MoonCharm);
	}

	// You may be temped to use Main.rand to set the availability of your item, this may not do what you want it do to.
	// In this example, there is a 50% chance of our item being in the shop.
	// However, this will run every time the shop is opened. So, players can just close and reopen the shop to roll the change again.
	if (Main.rand.NextBool(2)) { // Not recommended
		shop.item[nextSlot++].SetDefaults(ItemID.BoneTorch);
	}
}

Attacking

In vanilla, all Town NPCs except the Old Man can attack enemies to defend themselves. There are many hooks that we can use to add different types of attacks for our Town NPC.

There are two hooks that are used for all attacks: TownNPCAttackStrength() and TownNPCAttackCooldown().

public override void TownNPCAttackStrength(ref int damage, ref float knockback) {
	// The amount of base damage the attack will do.
	// This is NOT the same as NPC.damage (that is for contact damage).
	// Remember, the damage will increase as more bosses are defeated.
	damage = 20;
	// The amount of knockback the attack will deal.
	// This value does not scale like damage does.
	knockback = 4f;
}

public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) {
	// How long, in ticks, the Town NPC must wait before they can attack again.
	// The actual length will be: cooldown <= length < (cooldown + randExtraCooldown)
	cooldown = 30;
	randExtraCooldown = 30;
}

Remember that as the player defeats more bosses, the damage that the Town NPC deals will be boosted. This will not boost the knockback, though. See the vanilla wiki for more information.

In SetStaticDefaults(), we added out Town NPC to some sets that also affect how it attacks.

NPCID.Sets.DangerDetectRange[Type] = 700; // The amount of pixels away from the center of the NPC that it tries to attack enemies.
NPCID.Sets.AttackType[Type] = 0; // The type of attack the Town NPC performs. 0 = throwing, 1 = shooting, 2 = magic, 3 = melee
NPCID.Sets.AttackTime[Type] = 90; // The amount of time it takes for the NPC's attack animation to be over once it starts. Measured in ticks.
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.DangerDetectRange[Type]: is measured in pixels and there are 16 pixels in a tile. So, 700 means about 44 tiles.
  • NPCID.Sets.AttackType[Type]: is the type of attack the Town NPC preforms.
  • NPCID.Sets.AttackTime[Type]: is the length of time that the attack lasts once it starts measured in ticks. There are 60 ticks per second, so 90 is 1.5 seconds.
  • NPCID.Sets.AttackAverageChance[Type]: When a Town NPC decides that it can attack, it will roll a random chance of 1/x to see if it will actually attack. Settings this to lower numbers will make the Town NPC appear more aggressive because it will be more likely to attack.

Throwing

Throwing attacks are pretty simple. The Town NPC spawns a projectile aimed at the enemy. To add this to our Town NPC, first set NPCID.Sets.AttackType[Type] = 0 in SetStaticDefaults(). Next, we override TownNPCAttackProj() and TownNPCAttackProjSpeed().

public override void TownNPCAttackProj(ref int projType, ref int attackDelay) {
	// Throwing
	projType = ProjectileID.Shuriken; // Set the type of projectile the Town NPC will attack with.
	attackDelay = 10; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.
}

public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) {
	// Throwing
	multiplier = 12f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
	gravityCorrection = 2f; // This will affect how high the Town NPC will aim to correct for gravity.
	randomOffset = 1f; // This will affect the speed of the projectile (which also affects how accurate it will be).
}

If you want to use a projectile that you've created in your mod, use ModContent.ProjectileType<YourProjectileClass>().

Shooting

Shooting is similar throwing, but the Town NPC is shown holding a gun. First set NPCID.Sets.AttackType[Type] = 1 in SetStaticDefaults(). Next, we override TownNPCAttackProj(), TownNPCAttackProjSpeed(), and DrawTownAttackGun().

public override void TownNPCAttackProj(ref int projType, ref int attackDelay) {
	// Shooting
	projType = ProjectileID.Bullet; // Set the type of projectile the Town NPC will attack with.
	attackDelay = 1; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.

	// If the world is Hardmode, change the projectile to something else.
	if (Main.hardMode) {
		projType = ProjectileID.CursedBullet;
	}
}

public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) {
	// Shooting
	multiplier = 16f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
	randomOffset = 0.1f; // This will affect the speed of the projectile (which also affects how accurate it will be).
}

public override void DrawTownAttackGun(ref Texture2D item, ref Rectangle itemFrame, ref float scale, ref int horizontalHoldoutOffset) {
	// Only used for shooting attacks.
	
	// Here is an example on how we would change which weapon is displayed. Omit this part if only want one weapon.
	// In Pre-Hardmode, display the first gun.
	if (!Main.hardMode) {
		// This hook takes a Texture2D instead of an int for the item. That means the weapon our Town NPC uses doesn't need to be an existing item.
		// But, that also means we need to load the texture ourselves. Luckily, GetItemDrawFrame() can do the work for us.
		// The first parameter is what you set as the item.
		// Then, there are two "out" parameters. We can use those out parameters.
		Main.GetItemDrawFrame(ItemID.FlintlockPistol, out Texture2D itemTexture, out Rectangle itemRectangle);

		// Set the item texture to the item texture.
		item = itemTexture;

		// This is the source rectangle for the texture that will be drawn.
		// In this case, it is just the entire bounds of the texture because it has only one frame.
		// You could change this if your texture has multiple frames to be animated.
		itemFrame = itemRectangle;

		scale = 1f; // How large the item is drawn.
		horizontalHoldoutOffset = 12; // How close it is drawn to the Town NPC. Adjust this if the item isn't in the Town NPC's hand.

		return; // Return early so the Hardmode code doesn't run.
	}

	// If the world is in Hardmode, change the item to something else.
	Main.GetItemDrawFrame(ItemID.VenusMagnum, out Texture2D itemTexture2, out Rectangle itemRectangle2);
	item = itemTexture2;
	itemFrame = itemRectangle2;
	scale = 0.75f;
	horizontalHoldoutOffset = 15;
}

In this example, we are changing what item the Town NPC holds and what projectile they shoot in Hardmode.

Additionally, we can add:

public override void TownNPCAttackShoot(ref bool inBetweenShots) {
	// Only used for shooting attacks.
	// If this is true, it means that the Town NPC has already created a projectile and will continue to create projectiles as part of the same attack.
	// This is like how the Steampunker shoots a three round burst with her Clockwork Assault Rifle. TODO: actually verify how this works.
	inBetweenShots = false;
}

If you want to use an item and projectile that you've created in your mod, use ModContent.ItemType<YourItemClass>() ModContent.ProjectileType<YourProjectileClass>().

Magic

Magic is like throwing, but with extra fancy effects. An "aura" will appear in front of the Town NPC and the Town NPC will emit light. First set NPCID.Sets.AttackType[Type] = 2 in SetStaticDefaults(). Next, we override TownNPCAttackProj(), and TownNPCAttackProjSpeed().

public override void TownNPCAttackProj(ref int projType, ref int attackDelay) {
	// For throwing, shooting, and magic attacks.
	projType = ProjectileID.MagicMissile; // Set the type of projectile the Town NPC will attack with.
	attackDelay = 1; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.
}

public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) {
	multiplier = 16f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
	randomOffset = 5f; // This will affect the speed of the projectile (which also affects how accurate it will be).
}

We can customize the color of the aura with this set in SetStaticDefaults():

// In SetStaticDefaults()
// Magic attacks create an aura around the Town NPC. It is white by default, but we can set it to a color here.
NPCID.Sets.MagicAuraColor[Type] = Color.Yellow; 

In this example, we've set it to a yellow color. You can use the colors built into XNA to set it as predefined colors. If you want a specific color, you can instead write new Color(255, 255, 255) for setting the Red, Green, and Blue of the color. The range is from 0 (0% color) to 255 (100% color). For example new Color(152, 255, 152) would be a mint green.

We can also change how much light is created from the attack with the TownNPCAttackMagic() hook:

public override void TownNPCAttackMagic(ref float auraLightMultiplier) {
	auraLightMultiplier = 1f; // How strong the light is from the magic attack. 1f is the default.
}

If you do not include this hook, it will just be the standard brightness. A value less than 1f will make the light dimmer, and a value greater than 1f will make the light brighter.

Note: Any vanilla projectile that relies on its owner will not work correctly when a Town NPC uses it. Some examples:

  • Spears, Yoyos, Flails, Shortswords, and the Zenith will spawn from the player in single player instead of the Town NPC.
  • Boomerangs will work, but will try to return to the player instead. For a working one, check how the MechanicWrench works.
  • Minions will be assigned to the player without giving them the buff which makes it much more difficult to remove.
  • The Nimbus Rod and Crimson Rod projectiles will never stop at their destination.
  • The chain from the Harpoon will spawn from the player. The projectile itself will still work, though.
  • The Arkhalis and Terragrim projectiles will not work. For something similar check how ZoologistStrikeGreen and ZoologistStrikeRed work.

You can use Hot Reload to quickly test which projectiles work and which ones don't.

Melee

Melee attacks are quite different from the rest of the attack types. The Town NPC will swipe in front of itself. First set NPCID.Sets.AttackType[Type] = 3 in SetStaticDefaults(). Next we override TownNPCAttackSwing() and DrawTownAttackSwing().

public override void TownNPCAttackSwing(ref int itemWidth, ref int itemHeight) {
	// This is the hitbox of the melee swing. It recommended to set this to the resolution of the sprite you want to use.
	// Below, we've set the Exotic Scimitar as the weapon which has a resolution of 40x48.
	itemWidth = 40;
	itemHeight = 48;
}

public override void DrawTownAttackSwing(ref Texture2D item, ref Rectangle itemFrame, ref int itemSize, ref float scale, ref Vector2 offset) {
	// This hook takes a Texture2D instead of an int for the item. That means the weapon our Town NPC uses doesn't need to be an existing item.
	// But, that also means we need to load the texture ourselves. Luckily, GetItemDrawFrame() can do the work for us.
	// The first parameter is what you set as the item.
	// Then, there are two "out" parameters. We can use those out parameters.
	Main.GetItemDrawFrame(ItemID.DyeTradersScimitar, out Texture2D itemTexture, out Rectangle itemRectangle);
	
	// Set the item texture to the item texture.
	item = itemTexture;

	// This is the source rectangle for the texture that will be drawn.
	// In this case, it is just the entire bounds of the texture because it has only one frame.
	// You could change this if your texture has multiple frames to be animated.
	itemFrame = itemRectangle;

	// Set the size of the item to the size of one of the dimensions. This will always be a square, but it doesn't matter that much.
	// itemSize is only used to determine how far into the swing animation it should be.
	itemSize = itemRectangle.Width;

	// The scale affects how far the arc of the swing is from the Town NPC.
	// This is not how large the item will be drawn on the screen.
	// A scale of 0 will draw the swing directly on the Town NPC.
	// We set it to 0.15f so it the arc is slightly in front of the Town NPC.
	scale = 0.15f;

	// offset will change the position of the item.
	// Change this to match with the location of the Town NPC's hand.
	// Remember, positive Y values go down.
	offset = new Vector2(0, 12f);
}

In this example, we've made it so our Town NPC attacks with the Exotic Scimitar just like the Dye Trader. Since this hook takes a Texture2D instead of an int for the item, we can make our Town NPC swing with any texture that we want.

Texture2D itemTexture = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/SwordWithNoItem").Value;
item = itemTexture;
itemFrame = itemTexture.Bounds;
itemSize= itemTexture.Width;

Here we change the item texture to a sprite that we have in the specified path and set the frame and size accordingly.

You might've noticed that your Town NPC tries to attack enemies from much further away than they can actually hit them and that they hardly attack at all. We should change some of the Sets in SetStaticDefaults() and the values in TownNPCAttackCooldown() to better match how melee works.

// In SetStaticDefaults()
NPCID.Sets.DangerDetectRange[Type] = 60; // The amount of pixels away from the center of the NPC that it tries to attack enemies.
NPCID.Sets.AttackTime[Type] = 15; // The amount of time it takes for the NPC's attack animation to be over once it starts. Measured in ticks.
NPCID.Sets.AttackAverageChance[Type] = 1; // The denominator for the chance for a Town NPC to attack. Lower numbers make the Town NPC appear more aggressive.

public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) {
	// How long, in ticks, the Town NPC must wait before they can attack again.
	// The actual length will be: cooldown <= length < (cooldown + randExtraCooldown)
	cooldown = 12;
	randExtraCooldown = 6;
}

Here we've changed 3 sets to better match the melee attack. We set the DangerDetectRange to 60 (3.75 tiles) so that they only try to attack enemies when they are really close. We set the AttackTime to 15 so that the swing is short. Lastly, we set AttackAverageChance to 1 so the Town NPC always attacks if it is able to. We've changed the cooldown and randExtraCooldown in TownNPCAttackCooldown() so that the Town NPC doesn't have to wait an unnecessary amount of time before attacking.

Bestiary

If you've taken a look at your Town NPC in the bestiary, you will notice that it just stands still, has no background, and has no description. We can easily add those with the following:

In SetStaticDefaults(), add:

// Influences how the NPC looks in the Bestiary
NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new(0) {
	Velocity = 1f, // Draws the NPC in the bestiary as if it's walking +1 tiles in the x direction
	Direction = -1 // Faces left
};

NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers);

We also need to override the SetBestiary() hook:

public override void SetBestiary(BestiaryDatabase database, BestiaryEntry bestiaryEntry) {
	bestiaryEntry.Info.AddRange(new IBestiaryInfoElement[] {
		// The first line is for the background. Auto complete is recommended to see the available options.
		// Generally, this is set to the background of the biome that the Town NPC most loves/likes, but it is not automatic.
		BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Biomes.TheHallow,
		// Examples for how to modify the background
		// BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Visuals.Blizzard,
		// BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Times.NightTime,

		// This line is for the description of the entry. We are accessing a localization key here.
		new FlavorTextBestiaryInfoElement("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Bestiary")
	});
}

Happiness

Right now, our Town NPC Happiness is only affected by crowding/solidarity and has messed up text when we ask them about it. Let's add some Happiness to our Town NPC!

Happiness Basic Concepts

If you are unfamiliar with how the Happiness system works, here are some basics:

  • Town NPCs can love, like, dislike, and hate living in biomes and living by other Town NPCs.
  • Town NPCs automatically hate living in the Corruption, Crimson, or Dungeon.
  • Living in favorable locations and with favorable people will decrease the cost of their shop items and increase the sell value of items.
  • If the Town NPC is very happy, they will automatically sell the Pylon for the biome they are in. (Actually it's based on which biome the player is in, as mentioned before.)
  • In vanilla, all Town NPCs like the Princess. The Princess also loves all other Town NPCs.
  • In vanilla, all Town NPCs only like or dislike biomes. We can set loved and hated biomes, though.

Check out the vanilla wiki for more info on Happiness.

SetStaticDefaults()

In SetStaticDefaults(), we want to add NPC.Happiness and set the affections for the biomes and other Town NPCs.

// In SetStaticDefaults(),
// We use the NPC.Happiness hook to set our happiness. Here we can set our Town NPC's opinions on biomes and other Town NPCs.
// We can use the chaining syntax similar to recipes to reduce the verbosity of the code.
// Notice how we don't have any semicolons ; at the end of each line and instead a single semicolon at the very end.
NPC.Happiness
	// All of the vanilla Town NPCs only LIKE and DISLIKE one biome, but we can set LOVE and HATE as well as many as we want.
	.SetBiomeAffection<HallowBiome>(AffectionLevel.Love) // Our Town NPC will love the Hallow.
	.SetBiomeAffection<ForestBiome>(AffectionLevel.Like) // Our Town NPC will like the Forest.
	.SetBiomeAffection<DesertBiome>(AffectionLevel.Dislike) // Our Town NPC will dislike the Desert.
	.SetBiomeAffection<UndergroundBiome>(AffectionLevel.Dislike) // Our Town NPC will also dislike the Underground/Caverns/Underworld.

	.SetNPCAffection(NPCID.Guide, AffectionLevel.Love) // Our Town NPC loves living near the Guide.
	.SetNPCAffection(NPCID.PartyGirl, AffectionLevel.Like) // Our Town NPC likes living near the Party Girl.
	.SetNPCAffection(NPCID.BestiaryGirl, AffectionLevel.Like) // Our Town NPC also like living near the Zoologist. Use auto complete to find the NPCIDs.
	.SetNPCAffection(NPCID.DD2Bartender, AffectionLevel.Dislike) // Our Town NPC dislike living near the Tavernkeep. Use auto complete to find the NPCIDs.
	.SetNPCAffection(NPCID.GoblinTinkerer, AffectionLevel.Hate) // Our Town NPC hates living near the Goblin Tinkerer.
	// Use ModContent.NPCType<YourOtherTownNPC>() to set the affection towards the other Town NPCs in your mod.
	// All Town NPCs are automatically set to like the Princess.
; // < Mind the semicolon!

Localization

Our localization file contains the Happiness dialogue. {BiomeName} and {NPCName} are placeholders that will be replaced with the appropriate words. Something to note: {BiomeName} will be replaced with "the Biome".

Mods: {
	TownNPCGuide: {
		NPCs: {
			TutorialTownNPC: {
				# Other keys have been omitted here for this example.
				
				TownNPCMood: {
					# Content will display when there is nothing else to display.
					Content: I'm neutral.
					# NoHome will display when the Town NPC is homeless.
					NoHome: I would a like a home for myself.
					# LoveSpace will display when they get the Solitude Bonus. Check the vanilla wiki for on details on that.
					LoveSpace: Great solitude.
					# FarFromHome will display when the Town NPC is far from their house. TOTO: find the exact distance.
					FarFromHome: I seem to be far away from my home!
					# DislikeCrowded will display when there are 4-6 other Town NPCs within 25 tiles.
					DislikeCrowded: It's getting a little crowded in here.
					# HateCrowded will display when there are 7+ other Town NPCs wihtin 25 tiles.
					HateCrowded: There's no privacy at all!
					# LoveBiome, LikeBiome, DislikeBiome, HateBiome will display when in any of the loved, liked, disliked, and hated biomes.
					# The Corruption, Crimson, and Dungeon are automatically set as hated biomes.
					LoveBiome: I love {BiomeName}! It's so beautiful!
					LikeBiome: The {BiomeName} is a good place to live.
					DislikeBiome: I don't really enjoy living in {BiomeName}.
					HateBiome: I hate {BiomeName}!
					# LoveNPC, LikeNPC, DislikeNPC, HateNPC will display when in any of the loved, liked, disliked, and hated Town NPCs are nearby.
					# LikeNPC_Princess will display for when the Princess is nearby. This ONLY works for the Princess. It will not work for any other Town NPC.
					LoveNPC: "{NPCName} teaches people. I love that about them."
					LikeNPC: "{NPCName} is fun to be around."
					LikeNPC_Princess: "{NPCName} is so kind! They like everyone!"
					DislikeNPC: I have to tolerate {NPCName}.
					HateNPC: I hate {NPCName}!
					# This will set the Princess' dialogue for your Town NPC. This ONLY works for the Princess. It will not work for any other Town NPC.
					Princess_LovesNPC: I love {NPCName}'s work!
				}
			}
		}
	}
}

Limitations with the Happiness system

As customizable the Happiness system may seem, there are many limitations and things that can't be done (without detouring or IL editing).

Biomes Affection

Only the biomes listed defined in Terraria.GameContent.Personalities can be set in SetBiomeAffection.

  • DesertBiome
  • ForestBiome
  • HallowBiome
  • JungleBiome
  • MushroomBiome
  • OceanBiome
  • SnowBiome
  • UndergoundBiome
    • This includes the Underground layer, Cavern layer, and Underworld.
  • CorruptionBiome
    • The housing check will still prevent players from moving Town NPCs to the Corruption Biome.
  • CrimsonBiome
    • The housing check will still prevent players from moving Town NPCs to the Crimson Biome.
  • DungeonBiome
    • Even though Town NPCs can live in the Dungeon, they also automatically hate it. For example, if you set your Town NPC to love the Dungeon (or Corruption/Crimson), they will both hate and love the Dungeon at the same time. For this reason it is not recommended to set the affection of the Corruption, Crimson, or Dungeon.

To get other biomes, you will need to create it yourself. See Example Surface Biome.

NPC Affection

Adding affection for NPCs that are not considered Town NPCs will have no effect. This includes the Town Pets and Town Slimes.

Happiness Dialogue

The dialogue that appears when clicking the Happiness button is very limited. Everything is general, which means you can't give different biomes or Town NPCs of the same affection level different quotes.

Example: Your Town NPC likes both the Guide and Merchant and you want your Town NPC to say something different about each of them. That is not possible. When near the Guide or Merchant, the dialogue for both of them will the same LikeNPC dialogue.

This can be seen in vanilla. The Golfer likes both the Painter and Zoologist. You might expect him to say something different for the Zoologst considering they are siblings, but the Golfer says "It's always fun to hang out with {NPCName}. Good people." for both the Painter and Zoologist.

The only exception to this is the Princess. If your Town NPC is around the Princess, it will say the LikeNPC_Princess quote instead of the general LikeNPC quote. You might try to add other keys like "LikeNPC_Guide" in an attempt to give your Town NPC different dialogue for different Town NPCs, but this will not work.

For vanilla Town NPCs

We've made our Town NPC have opinions of vanilla Town NPCs, but how to we make vanilla Town NPCs have opinions of our Town NPC? To do this, we need to create a new class that inherits GlobalNPC. GlobalNPC allows us to change things about all NPCs, including vanilla NPCs.

using Terraria;
using Terraria.GameContent.Personalities;
using Terraria.ID;
using Terraria.ModLoader;

namespace TownNPCGuide.Content.NPCs.TownNPCs
{
	public class HappinessGlobalNPC : GlobalNPC
	{
		public override void SetStaticDefaults() {
			
			int tutorialTownNPC = ModContent.NPCType<TutorialTownNPC>(); // Get our Town NPC's type.

			var guideHappiness = NPCHappiness.Get(NPCID.Guide); // Get the Guide's happiness.
			var goblinTinkererHappiness = NPCHappiness.Get(NPCID.GoblinTinkerer); // Get the Goblin Tinkerer's happiness.

			guideHappiness.SetNPCAffection(tutorialTownNPC, AffectionLevel.Like); // Make the Guide like our Town NPC.
			goblinTinkererHappiness.SetNPCAffection(tutorialTownNPC, AffectionLevel.Dislike); // Make the Goblin Tinkerer dislike our Town NPC.
		}
	}
}

As mentioned above, we can't add new happiness dialog for specifically our Town NPC. The only exception to this is the Princess. We can change what she says about our Town NPC. In your localization file, add:

This is supposed to be not necessary anymore with the introduction of Princess_LovesNPC, but it doesn't seem to work for me.

# This will set the Princess' dialogue for your Town NPC.
# You will need to change the name of your mod and name of your Town NPC for this to work.
# TownNPCMood_Princess.LoveNPC_YourModHere/YourTownNPCHere
# This key must be outside of the Mods.YourMod scope.
TownNPCMood_Princess.LoveNPC_TownNPCGuide/TutorialTownNPC: I love {NPCName}'s work!

You'll need to change the key to match your mod and your Town NPC. This key must also go outside of the Mods. scope, otherwise it will not work.

Profile

In vanilla, Town NPCs with hats have a hatless version of their textures for parties (they wear the party hats instead). All vanilla Town NPCs also have alternate textures for when they are shimmered. We can add alternate textures to our Town NPC with a Profile.

For this guide, we will be using these sprites **Click to Expand**

In vanilla, the party textures just remove the hat that the Town NPC normally wears. Since our original sprites didn't have a hat, I added sunglasses instead.

TutorialTownNPC_Party TutorialTownNPC_Party TutorialTownNPC_Shimmer TutorialTownNPC_Shimmer TutorialTownNPC_Shimmer_Party TutorialTownNPC_Shimmer_Party TutorialTownNPC_Shimmer_Head TutorialTownNPC_Shimmer_Head

There are several parts to this. First, we want to add some properties and the Load() hook to our Town NPC.

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");
}

Next, in SetStaticDefaults() we want to add:

NPCID.Sets.ShimmerTownTransform[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.

// This creates a "profile" for our Town NPC, 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")
);

This will add our party, shimmer, shimmer party, and shimmer head icon textures. If you do not have extra party textures, remove the , Texture + "_Party" and , Texture + "_Shimmer_Party" parts.

Finally, we need to register this profile to our Town NPC by overring the TownNPCProfile() hook:

public override ITownNPCProfile TownNPCProfile() {
	return NPCProfile;
}

For a more customizable profile, see Advanced - ITownNPCProfile.

Gore

When our Town NPC dies, we want gore pieces to explode from their location. Create some sprites, and then create a Gores folder inside where your namespace is. For example: If your Town NPC's class' namespace is TownNPCGuide.Content.NPCs.TownNPCs, then create the new Gores folder in TownNPCGuide/Content/NPCs/TownNPCs.

For this guide, we will be using these sprites **Click to Expand**

TutorialTownNPC_Gore_Arm TutorialTownNPC_Gore_Arm TutorialTownNPC_Gore_Head TutorialTownNPC_Gore_Head TutorialTownNPC_Gore_Leg TutorialTownNPC_Gore_Leg TutorialTownNPC_Gore_Party_Head TutorialTownNPC_Gore_Party_Head TutorialTownNPC_Gore_Shimmer_Arm TutorialTownNPC_Gore_Shimmer_Arm TutorialTownNPC_Gore_Shimmer_Head TutorialTownNPC_Gore_Shimmer_Head TutorialTownNPC_Gore_Shimmer_Leg TutorialTownNPC_Gore_Shimmer_Leg TutorialTownNPC_Gore_Shimmer_Party_Head TutorialTownNPC_Gore_Shimmer_Party_Head

To spawn the gore, we first need to override the HitEffects() hook. Calling Gore.NewGore() with Mod.Find<ModGore>("GoreNameHere") will spawn gore.

public override void HitEffect(NPC.HitInfo hitInfo) {
	// Create gore when the NPC is killed.
	// HitEffect() is called every time the NPC takes damage.
	// We need to check that the gore is not spawned on a server and that the NPC is actually dead.
	if (Main.netMode != NetmodeID.Server && NPC.life <= 0) {
		// Spawn a piece of gore in the Gores folder with the name "TutorialTownNPC_Gore_Head".
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Head").Type);
		// Spawn two pieces named "TutorialTownNPC_Gore_Arm".
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Arm").Type);
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Arm").Type);
		// Spawn two pieces named "TutorialTownNPC_Gore_Leg".
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Leg").Type);
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Leg").Type);
	}
}

This will spawn the gore for our Town NPC's head, arms, and legs. We have different sprites for during parties and when the Town NPC is shimmered, though. We could call Gore.NewGore() and Mod.Find<ModGore>("GoreNameHere") for every single piece of gore, but that gets very verbose. There is a easier way to do this.

public override void HitEffect(NPC.HitInfo hitInfo) {
	// Create gore when the NPC is killed.
	// HitEffect() is called every time the NPC takes damage.
	// We need to check that the gore is not spawned on a server and that the NPC is actually dead.
	if (Main.netMode != NetmodeID.Server && NPC.life <= 0) {
		// Retrieve the gore types. This NPC has shimmer and party variants for head; shimmer variants for the arm and leg gore. (8 total gores)
		// The way this is set up is that in the Gores folder, our gore sprites are named "TutorialTownNPC_Gore_[Shimmer]_[Party]_<BodyPart>".
		// For example, the normal head gore is called "TutorialTownNPC_Gore_Head".
		// The shimmered party head gore is called "TutorialTownNPC_Gore_Shimmer_Party_Head".
		// Your naming system does not need to match this, but it is convenient because this the following code will work for all of your Town NPCs.

		string shimmer = ""; // Create an empty string.
		string party = ""; // Create an empty string.
		if (NPC.IsShimmerVariant) {
			shimmer += "_Shimmer"; // If the Town NPC is shimmered, add "_Shimmer" to the file path.
		}
		if (NPC.altTexture == 1) {
			party += "_Party";  // If the Town NPC has a different texture for parties, add "_Party" to the file path.
		}
		int hatGore = NPC.GetPartyHatGore(); // Get the party hat gore for the party hat that the Town NPC is currently wearing.
		int headGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}{party}_Head").Type; // Find the correct gore.
		int armGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}_Arm").Type; // {Name} will be replaced with the class name of the Town NPC.
		int legGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}_Leg").Type; // {shimmer} and {party} will add the extra bits of the string if it exists.

		// 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); // Spawn the party hat gore if there is one.
		}
		Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, headGore, 1f);
		// Remember, positive Y values go down.
		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);
	}
}

The above code will take care of all of the cases we have for our specific Town NPC. As long as our gore sprites are named correctly, it will choose the correct one. This code will also spawn the party hat gore in the correct color if there is party active. You may need to modify this depending on how many/which gore pieces you have for your Town NPC.

Misc

There are a few other things we can do with our Town NPC.

Teleport to King or Queen Statue

In vanilla, all Town NPCs can be teleported to a King or Queen Statue. We can easily add this for our Town NPC by overriding the CanGoToStatue() hook.

public override bool CanGoToStatue(bool toKingStatue) {
	// Can teleport to a King Statue
	return toKingStatue;
	// Return `!toKingStatue` for Queen Statues
	// Return `true` for both
}

Returning toKingStatue will allow our Town NPC to go to King Statues. If you want your Town NPC to go to Queen Statues instead, return !toKingStatue. If you want your Town NPC to go to both statues, return true.

If we wanted to, we can also add some special effects when our Town NPC is teleported by overriding OnGoToStatue().

public override void OnGoToStatue(bool toKingStatue) {
	// Display "Woah!" in chat. The message comes from our localization file.
	ChatHelper.BroadcastChatMessage(NetworkText.FromKey("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.OnTeleportToStatue"), Color.White);
}

In this example, "Woah!" will be displayed in chat when our Town NPC teleports. We need to use ChatHelper.BroadcastChatMessage() instead of Main.NewText() because OnGoToStatue() runs server side. For creating visual effects, look at Example Person.

Party Hat

By default, our Town NPC can wear party hats during parties. The UsesPartyHat() hook controls whether our Town NPC will wear a hat or not.

public override bool UsesPartyHat() {
	return true;
}

We could set it to false if we don't want our Town NPC to wear a party hat like the Zoologist or Tax Collector. We could also use if statements to change when our Town NPC can wear a hat. You don't need to include this hook if you want your Town NPC to wear a party hat during all parties.

If the Party Hat isn't sitting at the correct height, we can adjust it with a set, NPCID.Sets.HatOffsetY[Type] in SetStaticDefaults(). The measurement is in pixels and remember that negative Y values are up.

NPCID.Sets.HatOffsetY[Type] = 4; // Move the party hat down 4 pixels

If the Party Hat isn't sitting at the correct height between frames, we can also adjust that with a set, NPCID.Sets.NPCFramingGroup[Type] in SetStaticDefaults(). The number we assign to it is which group we want the hat to be animated as. What the framing groups do is move the party hat up or down based on the frames.

  • Group 0: By default, all Town NPCs are in Group 0. This group matches up with the standard walking animations (the same animations the player uses).
  • Group 1: Used by Nurse, Dryad, Party Girl, Steampunker, Mechanic, Stylist, Zoologist. These Town NPCs have only 12 walking frames.
  • Group 2: Used by Angler.
  • Group 3: Used by Truffle.
  • Group 4: Used by Town Cat.
  • Group 5: Used by Town Dog.
  • Group 6: Used by Town Bunny.
  • Group 7: Used by all Town Slimes.
Frame Group 0 Group 1 Group 2 Group 3 Group 4 Group 5 Group 6 Group 7
0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 -2
2 0 0 0 -2 0 0 -2 0
3 -2 -2 -2 0 0 0 -4 -2
4 -2 -2 -2 0 0 0 -4 -4
5 -2 -2 -2 0 0 0 -2 -6
6 0 0 0 0 0 0 0 -4
7 0 0 0 -2 0 0 -2 -2
8 0 0 -2 -2 0 -2 0 0
9 0 -2 -2 -2 0 -2 0 0
10 -2 -2 -2 0 2 -2 2 2
11 -2 -2 -2 0 2 0 4 2
12 -2 0 0 0 4 0 6 4
13 0 0 0 0 6 -2 4 2
14 0 0 0 -2 4 -2 2
15 0 0 0 -2 2 0 0
16 0 0 0 0 2 0 -2
17 0 0 0 0 -2 4 -4
18 0 0 0 0 -4 6 -6
19 0 0 0 0 -6 6 -6
20 0 0 0 0 -4 6 -6
21 0 0 0 0 -2 6 -6
22 0 0 0 -4 4 -6
23 0 0 0 -4 4 -6
24 0 0 0 -6 4 -6
25 0 -6 4 -4
26 -6 4 -2
27 -4 4

The measurement is in pixels and remember that negative Y values are up.

Here is a visual representation of all of the groups **Click to Expand**
The width and height of the frames here are the "standard" width and height. They may or may not line up correctly if you've increase the height of your frames. Note about Group 6: The Town Bunny has a `HatOffsetY` of 4, so it is moved down in game than what is shown here.

FramingGroup0 FramingGroup1 FramingGroup2 FramingGroup3 FramingGroup4 FramingGroup5 FramingGroup6 FramingGroup7

Drop an item on death

If you want your Town NPC to drop an item on death, the standard ModifyNPCLoot() hook will work. Here is an example where an item only drops if the Town NPC has a certain name:

public override void ModifyNPCLoot(NPCLoot npcLoot) {
	// Our Town NPC will drop an item only if it is named a specific name.
	// See the Basic NPC Drops and Loot guide for more information about NPC loot.
	LeadingConditionRule specificName = new LeadingConditionRule(new Conditions.NamedNPC("blushiemagic"));
	specificName.OnSuccess(ItemDropRule.Common(ModContent.ItemType<TutorialItem>()));
	npcLoot.Add(specificName);
}

Census Support

Census - Town NPC Checklist is a popular mod that will tell the player how to get a Town NPC. Adding support for our Town NPC is extremely simple. All we have to do is add a line in our Localization file:

Mods: {
	TownNPCGuide: {
		NPCs: {
			TutorialTownNPC: {
				# Other keys have been omitted here for this example.
				
				# Here is where we write our Town NPC's spawn condition.
				Census.SpawnCondition: Rescue on the surface. Lives in the Hallow.
			}
		}
	}
}

That's it. As of 1.4.4, a Mod Call to Census is no longer required so adding support for Census is super easy.

In fact, you probably don't even need to create this key yourself. Enabling Census will automatically create a key for your Town NPC in your mod. You can then edit the value to say how to get the Town NPC.

Other

Setting Main.npcChatText to a string will replace whatever chat dialogue with the new string. In the Chat Buttons section earlier, we could change the chat dialogue when the player clicks the second button.

public override void OnChatButtonClicked(bool firstButton, ref bool shop) {
	if (firstButton) {
		shop = true;
	}
	if (!firstButton) {
		Main.npcChatText = Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.SecondButtonChat");
	}
}

The CanChat() determines whether the Town NPC can be talked to. If NPC.townNPC is true, then this will be set to true by default. You could override this and return false if you do not want your Town NPC to be talked to.

NPC.homeless in SetDefaults() is a property that will make our Town NPC homeless by default. ModNPC.TownNPCStayingHomeless will make it so the Town NPC will not appear in the housing menu and will always be homeless.

NPCID.Sets.AllowDoorInteraction[Type] in SetStaticDefaults determines whether our Town NPC can open doors. By default they can, but we can set this to false to stop them from opening doors. This only works for Town NPCs with an aiStyle of 7 (NPCAIStyleID.Passive).

NPC.IsShimmerVariant is a getter that will tell you if the Town NPC is in its Shimmer variant. NPC.townNpcVariationIndex is also used to determine if the NPC is in its Shimmer varient. An index of 1 will always be "Shimmered", even if that variable is set manually and not by dunking the Town NPC in Shimmer.

Traveling Merchant and Skeleton Merchant

See Example Mod's ExampleTravelingMerchant and ExampleBoneMerchant

Town Pets

Creating new Town Pets is technically possible, but it is much more tricky because it isn't natively supported in tModLoader. See Advanced - Town Pets for how to implement a Town Pet.

Intermediate - Mod Emote

Town NPCs will talk to each other and the player by displaying an emote above them. All Town NPCs in vanilla have an emote of themselves. Let's make one for our Town NPC.

Emote Sprite

We need to create a sprite sheet for our emote. Each frame of the emote is going to be 34x28 (real) pixels and we'll want at two frames for our emote. Below is the sprite sheet that we will be using for our Tutorial Town NPC Emote:

TutorialTownNPCEmote

Here is a template to help better visualize how the emote will fit on the emote bubbles. You'll want to make sure your emote doesn't overlap or extend past the outside of the bubble's outline.

QuoteTemplate

Emote Code

We are going to create a new class for our Emote that inherits ModEmoteBubble. For this tutorial, our class will be placed in TownNPCGuide/Content/EmoteBubbles. This code example creates a base class that can support many Town NPC emotes. The actual Town NPC emote inherits the base class and just needs to change which row it is on on the texture.

// This abstract class is used for town NPC emotes quick setup.
public abstract class ModTownEmote : ModEmoteBubble
{
	// Redirecting texture path.
	// Change this string to the path of your texture.
	public override string Texture => "TownNPCGuide/Content/EmoteBubbles/TutorialTownNPCEmote";

	public override void SetStaticDefaults() {
		// Add NPC emotes to "Town" category.
		AddToCategory(EmoteID.Category.Town);
	}

	/// <summary>
	/// Which row of the sprite sheet is this NPC emote in?
	/// This is used to help get the correct frame rectangle for different emotes.
	/// </summary>
	public virtual int Row => 0;

	// You should decide the frame rectangle yourself by these two methods.
	public override Rectangle? GetFrame() {
		return new Rectangle(EmoteBubble.frame * 34, 28 * Row, 34, 28);
	}

	// Do note that you should never use EmoteBubble instance as the GetFrame() method above
	// in "Emote Menu Methods" (methods with -InEmoteMenu suffix).
	// Because in that case the value of EmoteBubble is always null.
	public override Rectangle? GetFrameInEmoteMenu(int frame, int frameCounter) {
		return new Rectangle(frame * 34, 28 * Row, 34, 28);
	}
}

// This is a showcase of using the same texture for different emotes.
// Command names of these classes are defined using .hjson files in the Localization/ folder.

// This is the actual emote for our Town NPC. It inherits our base class and just needs to change which row it is on on the texture.
public class TutorialTownNPCEmote : ModTownEmote
{
	public override int Row => 0;
}

// With this method, we can create several emotes for different Town NPCs with only using a single texture.
/*
public class DifferentTownNPCEmote : ModTownEmote 
{
	public override int Row => 1;
}

public class OtherTownNPCEmote : ModTownEmote 
{
	public override int Row => 2;
}
*/

Next, we need to give our emote a command name by adding it to our localization file.

Mods: {
	TownNPCGuide: {
		# All other key value pairs have been omitted for this example.
	
		Emotes: {
			# The best practice would be to prefix the command with the name of your mod.
			# That way two mods can't add the same command for different emotes.
			# Example:
			#    Your mod is called "Awesome Farming Mod" and your Town NPC is called "Farmer".
			#    You could make the command "afmfarmer"
			# This command is verbose just because the name of this tutorial mod and tutorial town NPC names are long.
			TutorialTownNPCEmote.Command: tnpcgtutorialtownnpc
		}
	}
}

Lastly, we need to add another NPCID.Sets to our Town NPC to register the emote to our Town NPC. That way other Town NPCs can use this emote when talking about our Town NPC.

// In SetStaticDefaults
NPCID.Sets.FaceEmote[Type] = ModContent.EmoteBubbleType<TutorialTownNPCEmote>();

This is not the only way to set up an emote. Check Example Mod for more ModEmote examples.

Intermediate - Preferred Emotes

There is a list of all of the emotes that they will use and the situations they will use them in on the Terraria wiki. Specific Town NPCs are more likely to use certain emotes more than others. For example, the Merchant is more likely to use the Gold and Bug Net emotes. How do we add that to our own Town NPC?

We will need to override the PickEmote() hook. The hook provides us with several useful parameters that we can use.

  • closestPlayer: Self explanitory. It is the player who is the closest to the NPC who is trying to choose an emote.
  • emoteList: A large list that contains all of the emotes that the NPC might choose.
  • otherAnchor: This is the thing that the NPC is trying to emote at, if one exists.
public override int? PickEmote(Player closestPlayer, List<int> emoteList, WorldUIAnchor otherAnchor) {
	// Add some more emotes to the list.
	emoteList.Add(EmoteID.CritterBird);
	emoteList.Add(EmoteID.PartyPresent);

	// We can use if statements to change when an emote will be added.
	if (!Main.dayTime) {
		emoteList.Add(EmoteID.EmoteSleep);
	}

	// If the nearest player is in the snow biome, display a blizzard emote.
	// The NPC doesn't know what biome it is in -- it is all based on the player's biome.
	// The player is probably standing pretty close to the Town NPC so it doesn't matter that much.
	if (closestPlayer.ZoneSnow) {
		emoteList.Add(EmoteID.WeatherSnowstorm);
	}

	// Here this emote will only be added if our Town NPC is talking to another specific Town NPC.
	//   The `otherAnchor?.entity is NPC entityNPC` checks that the other thing that our Town NPC is
	//   trying to talk to actually exists and is an NPC. We assign a variable to that and then check
	//   if the NPC type is the Town NPC we want.
	if (otherAnchor?.entity is NPC entityNPC && entityNPC.type == NPCID.Guide) {
		emoteList.Add(EmoteID.EmotionLove);
	}

	// Here this emote will only be added if our Town NPC is talking to a player.
	// You might try to do something like `otherAnchor?.entity is Player` but that will never be true.
	// If the Town NPC is speaking to the player, the otherAnchor is never set, so we'll never know if it is for the player.
	// However, we can look at the NPC AI to find out that ai[0] == 7 and ai[0] == 19 is when the Town NPC will speak to the player.
	if (NPC.ai[0] == 7f || NPC.ai[0] == 19f) {
		// For this example, we clear the list so that it becomes empty.
		// We then add our ModEmote to the list so that is the only thing that our Town NPC can say. 
		emoteList.Clear();
		emoteList.Add(ModContent.EmoteBubbleType<TutorialTownNPCEmote>());
	}

	// Make sure you return base.PickEmote here. Otherwise you'll override all other NPCs and all other emotes.
	return base.PickEmote(closestPlayer, emoteList, otherAnchor);
}

Check Example Mod for more examples of PickEmote() such as adding emotes for bosses and biomes.

Intermediate - Rescuable Town NPC

What if we want our Town NPC to need to be rescued? To do that, we make a second NPC that inherits ModNPC and will transform into our real Town NPC when talked to.

BoundTutorialTownNPC

Our rescuable Town NPC class **Click to Expand**
using Microsoft.Xna.Framework;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;

namespace TownNPCGuide.Content.NPCs.TownNPCs
{
	// This is how to make a rescuable Town NPC.
	public class BoundTutorialTownNPC : ModNPC
	{
		public override void SetStaticDefaults() {
			Main.npcFrameCount[Type] = 1;

			// Hide this NPC from the bestiary.
			NPCID.Sets.NPCBestiaryDrawModifiers bestiaryData = new(0) {
				Hide = true 
			};
			NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, bestiaryData);
		}

		public override void SetDefaults() {
			// Notice NPC.townNPC is not set.
			NPC.friendly = true;
			NPC.width = 28;
			NPC.height = 32;
			NPC.aiStyle = 0; // aiStyle of 0 is used. The NPC will not move.
			NPC.damage = 10;
			NPC.defense = 15;
			NPC.lifeMax = 250;
			NPC.HitSound = SoundID.NPCHit1;
			NPC.DeathSound = SoundID.NPCDeath1;
			NPC.knockBackResist = 0.5f;
			NPC.rarity = 1; // To make our NPC will show up on the Lifeform Analyzer.
		}

		public override bool CanChat() {
			// Make it so our NPC can be talked to.
			return true;
		}

		public override void AI() {
			// Using aiStyle 0 will make it so the NPC will always turn to face the player.
			// If you don't want that, you can set the spriteDirection to -1 (left) or 1 (right) so they always appear to face one way.
			// NPC.spriteDirection = 1;

			// This is where we check to see if a player has clicked on our NPC.
			// First, don't run this code if it is a multiplayer client.
			if (Main.netMode != NetmodeID.MultiplayerClient) {
				// Loop through every player on the server.
				for (int i = 0; i < Main.maxPlayers; i++) {
					// If the player is active (on the server) and are talking to this NPC...
					if (Main.player[i].active && Main.player[i].talkNPC == NPC.whoAmI) {
						NPC.Transform(ModContent.NPCType<TutorialTownNPC>()); // Transform to our real Town NPC.																  
						Main.BestiaryTracker.Chats.RegisterChatStartWith(NPC); // Unlock the Town NPC in the Bestiary.																  
						Main.player[i].SetTalkNPC(NPC.whoAmI);  // Change who the player is talking to to the new Town NPC. 
						TownNPCGuideWorld.rescuedTutorialTownNPC = true; // Set our rescue bool to true.

						// We need to sync these changes in multiplayer.
						if (Main.netMode == NetmodeID.Server) {
							NetMessage.SendData(MessageID.SyncTalkNPC, -1, -1, null, i);
							NetMessage.SendData(MessageID.WorldData);
						}
					}
				}
			}
		}

		public override string GetChat() {
			// Make the Town NPC say something unique when first rescued.
			return Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.OnRescue");
		}

		public override float SpawnChance(NPCSpawnInfo spawnInfo)
		{
			// In this example, the bound NPC can spawn at the surface on grass, dirt, or hallowed grass.
			// We also make sure not spawn the bound NPC if it has already spawned or if the NPC has already been rescued.
			if (spawnInfo.Player.ZoneOverworldHeight && !TownNPCGuideWorld.rescuedTutorialTownNPC && !NPC.AnyNPCs(ModContent.NPCType<BoundTutorialTownNPC>()) && !NPC.AnyNPCs(ModContent.NPCType<TutorialTownNPC>())) {
				if (spawnInfo.SpawnTileType == TileID.Grass || spawnInfo.SpawnTileType == TileID.Dirt || spawnInfo.SpawnTileType == TileID.HallowedGrass) {
					return 0.75f;
				}
			}
			return 0f;
		}

		public override void HitEffect(NPC.HitInfo hit) {
			// Create gore when the NPC is killed.
			// HitEffect() is called every time the NPC takes damage.
			// We need to check that the gore is not spawned on a server and that the NPC is actually dead.
			if (Main.netMode != NetmodeID.Server && NPC.life <= 0) {
				// Spawn a piece of gore in the Gores folder with the name "TutorialTownNPC_Gore_Head".
				Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Head").Type);
				// Spawn two pieces named "TutorialTownNPC_Gore_Arm".
				Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, NPC.height / 2f), NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Arm").Type);
				Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, NPC.height / 2f), NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Arm").Type);
				// Spawn two pieces named "TutorialTownNPC_Gore_Leg".
				Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, NPC.height), NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Leg").Type);
				Gore.NewGore(NPC.GetSource_Death(), NPC.position + new Vector2(0, NPC.height), NPC.velocity, Mod.Find<ModGore>("TutorialTownNPC_Gore_Leg").Type);
			}
		}
	}
}

See the basic NPC spawning guide for more information about spawning the NPC in the world.

We also need to create and save a bool to the world saying that our Town NPC has been rescued or not. We create another class this time inheriting ModSystem. For more information, see DownedBossSystem.

Our ModSystem class **Click to Expand**
// Here is where we save that our Town NPC has been rescued to the world.
public class TownNPCGuideWorld : ModSystem {
	public static bool rescuedTutorialTownNPC = false;

	public override void SaveWorldData(TagCompound tag) {
		if (rescuedTutorialTownNPC) {
			tag["rescuedTutorialTownNPC"] = true;
		}
	}

	public override void LoadWorldData(TagCompound tag) {
		rescuedTutorialTownNPC = tag.ContainsKey("rescuedTutorialTownNPC");
	}

	public override void NetSend(BinaryWriter writer) {
		BitsByte flags = new BitsByte();
		flags[0] = rescuedTutorialTownNPC;
		writer.Write(flags);
	}

	public override void NetReceive(BinaryReader reader) {
		BitsByte flags = reader.ReadByte();
		rescuedTutorialTownNPC = flags[0];
	}
}

Finally, we need to change CanTownNPCSpawn() in our Town NPC to check if it has been rescued or not.

public override bool CanTownNPCSpawn(int numTownNPCs) {
	// If our Town NPC hasn't been rescued, don't arrive.
	if (!TownNPCGuideWorld.rescuedTutorialTownNPC) {
		return false;
	}
	return true;
}

Intermediate - Custom Currencies

Using coins to buy items is what most people will want for their Town NPCs, but what if we want to use a different currency like the Tavernkeep? Here are three examples of creating a custom currency starting from the simplest to most complex.

Vanilla Item as Currency

Here is a simple example of a custom currency using a vanilla item as the currency.

First, we need to make a class for our currency that inherits CustomCurrencySingleCoin. It is highly recommended you use CustomCurrencySingleCoin because it has extra implementation that is missing from CustomCurrencySystem and matches the Defender's Medals.

Our vanilla item currency class **Click to Expand**
public class VanillaItemAsCurrency : CustomCurrencySingleCoin
{
	// The item we use for this currency is set in the mod class.

	public static VanillaItemAsCurrency VanillaItemAsCurrencySystem; // Set in the mod class
	public static int VanillaItemAsCurrencyID; // Set in the mod class

	public VanillaItemAsCurrency(int coinItemID, long currencyCap, string CurrencyTextKey) : base(coinItemID, currencyCap)
	{
		this.CurrencyTextKey = CurrencyTextKey; // The name of the currency as a localization key.
		CurrencyTextColor = Color.Salmon; // The color that the price line will be.

		// How large the icon for the currency will draw in the "Savings" list.
		// The Savings list will show up if you have this currency in your Piggy Bank and purchase an item that costs this currency.
		// Defender Medals are set to 0.8f
		CurrencyDrawScale = 1f;
	}

	// The name of the currency is set to all lowercase by default.
	// See TutorialCurrceny on how to change that.
}

We have two static fields which we will set in our mod's Load hook. Then we have a constructor that takes the item ID of the currency, the currency cap (not that relevant), and the localization string for our currency's name. We can also set the color of the currency line and the scale of the currency item when in the Savings list.

The Savings list is to the right of the shop window (under where it says "Shop") and will show up if you hover over or purchase an item and you have that currency in your Piggy Bank/Safe/Defender's Forge/Void Bag Vault. Defender Medals have their scale set to 0.8f, but 1f looks good as well for most items.

Of course, change the names of the class, constructor, and static fields based on the name of your currency.

Now need to load our currency in our Mod class.

Loading the currency in the Mod class **Click to Expand**
public override void Load()
{
	// Load a vanilla item as custom currency.
	// We set the localization path to the name of the currency.
	VanillaItemAsCurrency.VanillaItemAsCurrencySystem = new VanillaItemAsCurrency(ItemID.Vertebrae, 9999L, "Mods.TownNPCGuide.Currency.VanillaItemAsCurrency");
	VanillaItemAsCurrency.VanillaItemAsCurrencyID = CustomCurrencyManager.RegisterCurrency(VanillaItemAsCurrency.VanillaItemAsCurrencySystem);
}

(Note: this doesn't technically need to go in your Mod class, it can be any Load() hook. You could put this in your Town NPC's load hook for example.)

The item that is used as the currency is set here, the currency cap (don't worry too much about it), and the localization path for the currency's name. Then we register the currency to an ID which we can use later in our shops.

Of course, change the names based on what you set in your class.

That's it! Our currency works with the vanilla item that we set. See the later section on how to use the currency in a shop.

Custom Item as Currency

What if we want to make our own item? It is basically the same as using a vanilla item, but we use our mod item instead. This example has some extra details.

Currency Item

You can create any item you want to use as your currency. In this example we'll make an item that closely matches the vanilla coins. That is, a non animated sprite while in the inventory, an animated sprite when thrown in the world, dusts when thrown in the world, giving coin luck when thrown into shimmer, and hiding the value of them item.

This example will use these sprites for the items:

TutorialCurrencyCoin TutorialCurrencyCoin_Animated

The first one is the texture that will autoloaded by tModLoader.

Tutorial Currency Coin item **Click to Expand**
public class TutorialCurrencyCoin : ModItem
{
	public Asset<Texture2D> TutorialCurrencyCoinAnimated; // Where our animated texture is stored.

	public override void SetStaticDefaults()
	{
		// If you want your item to always be animated you can set these two.
		// Main.RegisterItemAnimation(Type, new DrawAnimationVertical(6, 8));
		// ItemID.Sets.AnimatesAsSoul[Type] = true;

		ItemID.Sets.CoinLuckValue[Type] = 1000; // How much coin luck is created when throwing this item into Shimmer.
		Item.ResearchUnlockCount = 100; // How many items we need to research before it is unlocked for duplication.
	}
	public override void SetDefaults()
	{
		Item.width = 18;
		Item.height = 18;
		Item.value = 0;
		Item.rare = ItemRarityID.Blue;
		Item.maxStack = Item.CommonMaxStack;
	}

	public override void ModifyTooltips(List<TooltipLine> tooltips)
	{
		// Hide the "No Value" line when looking in shops.
		TooltipLine tooltipLine = tooltips.FirstOrDefault(x => x.Name == "Price" && x.Mod == "Terraria");
		tooltipLine?.Hide();
	}

	// Used for the animation when thrown in the world.
	private int frame = 0; // Current frame.
	private int frameCounter = 0; // Counter for the frame rate.
	private readonly int frameRate = 6; // 6 ticks per frame; matches vanilla coins.
	private readonly int frameCount = 8; // 8 total frames in our animation.
	
	// Our item is a still frame, but we want it to be animated when we throw it on the ground.
	public override bool PreDrawInWorld(SpriteBatch spriteBatch, Color lightColor, Color alphaColor, ref float rotation, ref float scale, int whoAmI)
	{
		// Increase the frame every frameRate ticks and then set it back to 0 if it goes above the frameCount.
		if (frameCounter++ >= frameRate)
		{
			frameCounter = 0;
			frame = ++frame % frameCount;
		}

		// Get the animated texture. Save the result so we only have to load it once.
		TutorialCurrencyCoinAnimated ??= Mod.Assets.Request<Texture2D>("Content/Items/" + Name + "_Animated");

		// Get the frame of the animation.
		Rectangle sourceRectangle = TutorialCurrencyCoinAnimated.Frame(1, 8, frameY: frame);

		// Get the position of the item.
		Vector2 position = Item.position - Main.screenPosition;

		// Draw the item in the world.
		spriteBatch.Draw(TutorialCurrencyCoinAnimated.Value, position, sourceRectangle, lightColor, rotation, default, scale, SpriteEffects.None, 0f);

		return false; // Return false so the original sprite doesn't draw as well.
	}

	public override void PostDrawInWorld(SpriteBatch spriteBatch, Color lightColor, Color alphaColor, float rotation, float scale, int whoAmI)
	{
		// Spawn some dusts on the coin when thrown in the world.
		if (!Main.gamePaused && Main.instance.IsActive && lightColor.R > 60 && Main.rand.Next(500) - (Math.Abs(Item.velocity.X) + Math.Abs(Item.velocity.Y)) * 10f < (lightColor.R / 50f))
		{
			int sparkleDust = Dust.NewDust(Item.position, Item.width, Item.height, DustID.PlatinumCoin, 0f, 0f, 0, Color.White, 1f);
			Main.dust[sparkleDust].velocity *= 0f;
		}
	}
}

Currency Class

Here is the class for the Tutorial Currency.

Tutorial Currency class **Click to Expand**
public class TutorialCurrency : CustomCurrencySingleCoin
{
	public static TutorialCurrency TutorialCurrencySystem; // Set in the mod class
	public static int TutorialCurrencyID; // Set in the mod class

	public TutorialCurrency(int coinItemID, long currencyCap, string CurrencyTextKey) : base(coinItemID, currencyCap)
	{
		this.CurrencyTextKey = CurrencyTextKey; // The name of the currency as a localization key.
		CurrencyTextColor = Color.LightBlue; // The color that the price line will be.

		// How large the icon for the currency will draw in the "Savings" list.
		// The Savings list will show up if you have this currency in your Piggy Bank and purchase an item that costs this currency.
		// Defender Medals are set to 0.8f
		CurrencyDrawScale = 1f; 
	}
	
	// The name of the currency is set to all lowercase by default.
	// So it'll show up as "Buy Price: 1 tutorial currency coin" for example.
	// Here is how the line that says the buy price is set.
	// This example is the same as vanilla but without the .ToLower() to leave it the original case.
	public override void GetPriceText(string[] lines, ref int currentLine, long price)
	{
		Color lineColor = CurrencyTextColor * (Main.mouseTextColor / 255f);
		lines[currentLine++] = $"[c/{lineColor.R:X2}{lineColor.G:X2}{lineColor.B:X2}:{Lang.tip[50].Value} {price} {Language.GetTextValue(CurrencyTextKey)}]";
	}

	// There are several other overrides that you can use for currencies as well, but you are unlikely to need them.
	// Accepts(Item item)
	//		Can decide whether or not you can use that currency to buy things.
	//		By default you can if the item is currency.
	// CombineStacks(out bool overFlowing, params long[] coinCounts)
	//		Used for combining stacks of coins into higher denominations.
	//		Doesn't actually do the stacking, just returns a number of what the stack is.
	// CountCurrency(out bool overFlowing, Item[] inv, params int[] ignoreSlots)
	//		Counts how much of the currency you have only in your inventory.
	// DrawSavingsMoney(SpriteBatch sb, string text, float shopx, float shopy, long totalCoins, bool horizontal = false)
	//		When purchasing and item that costs this currency and the currency is in your Piggy Bank, a "Savings" section will aprear to the right of the shop window.
	//		This show how many of this currency you have in your Piggy Bank.
	//		You can easily change the scale of the sprite by changing CurrencyDrawScale in the constructor.
	// GetItemExpectedPrice(Item item, out long calcForSelling, out long calcForBuying)
	//		Calls item.GetStoreValue(); and returns that value for both out parameters.
	// TryPurchasing(long price, List<Item[]> inv, List<Point> slotCoins, List<Point> slotsEmpty, List<Point> slotEmptyBank, List<Point> slotEmptyBank2, List<Point> slotEmptyBank3, List<Point> slotEmptyBank4)
	//		A complex method that tries to buy the shop item if you have the currency in your inventory, Piggy Bank, Safe, Defender's Forge, or Void Bag/Vault.
}

In this example, we are changing how the price line with the currency is drawn. If the name of our currency is "Tutorial Currency Coin", by default it will draw as "Buy Price: 1 tutorial currency coin". The name of the currency will be set to lowercase. The example above is just how the line is drawn normally, but without the function to make the name lowercase. You can customize this to say something different, too.

Loading our currency is the same as the vanilla item currency example above, but we use our mod item instead.

Loading the currency in the Mod class **Click to Expand**
public override void Load()
{
	// Load a vanilla item as custom currency.
	// We set the localization path to the name of the currency.
	TutorialCurrency.TutorialCurrencySystem = new TutorialCurrency(ModContent.ItemType<TutorialCurrencyCoin>(), 9999L, "Mods.TownNPCGuide.Currency.TutorialCurrency");
	TutorialCurrency.TutorialCurrencyID = CustomCurrencyManager.RegisterCurrency(TutorialCurrency.TutorialCurrencySystem);
}

Virtual Currency

This final example is much more complex. Can we make a currency that has no item aka a virtual currency? Yes!

This is much trickier because the currency system is designed expecting that the currency has an actual item.

First, we need to a place to store the virtual currency balance. ModPlayer is good if you want the currency to be per player. ModSystem can be used if you want the currency to be per world. Here is an extremely simple ModPlayer example.

Virtual Currency ModPlayer **Click to Expand**
// A simple ModPlayer that has our balance and saves our balance.
// Use ModSystem for a per world currency instead of per player.
public class VirtualCurrencyPlayer : ModPlayer
{
	public long VirtualCurrencyBalance = 0; // Our balance for the virtual currency.

	public override void SaveData(TagCompound tag)
	{
		tag["VirtualCurrencyBalance"] = VirtualCurrencyBalance;
	}

	public override void LoadData(TagCompound tag)
	{
		VirtualCurrencyBalance = tag.GetAsLong("VirtualCurrencyBalance");
	}
}

Use a long instead of an int because it has a high maximum range. If you want fractional numbers, use a double or decimal instead of a float for better precision and range.

Now for our currency's class. We need to implement several other methods because the original methods assume we have an item for our currency.

Virtual Currency class **Click to Expand**
public class VirtualCurrency : CustomCurrencySingleCoin
{
	public static VirtualCurrency VirtualCurrencySystem; // Set in the mod class
	public static int VirtualCurrencyID; // Set in the mod class

	public VirtualCurrency(int coinItemID, long currencyCap, string CurrencyTextKey) : base(coinItemID, currencyCap)
	{
		this.CurrencyTextKey = CurrencyTextKey; // The name of the currency as a localization key.
		CurrencyTextColor = Color.LimeGreen; // The color that the price line will be.

		// How large the icon for the currency will draw in the "Savings" list.
		// The Savings list will show up if you have this currency in your Piggy Bank and purchase an item that costs this currency.
		// Defender Medals are set to 0.8f
		CurrencyDrawScale = 1f;
	}

	// The name of the currency is set to all lowercase by default.
	// So it'll show up as "Buy Price: 1 virtual currency" for example.
	// Here is how the line that says the buy price is set.
	// This example is the same as vanilla but without the .ToLower() to leave it the original case.
	public override void GetPriceText(string[] lines, ref int currentLine, long price)
	{
		Color lineColor = CurrencyTextColor * (Main.mouseTextColor / 255f);
		lines[currentLine++] = $"[c/{lineColor.R:X2}{lineColor.G:X2}{lineColor.B:X2}:{Lang.tip[50].Value} {price} {Language.GetTextValue(CurrencyTextKey)}]";
	}

	// Important: Needed so you can buy items without having the currency item.
	public override bool Accepts(Item item)
	{
		return true;
	}

	public override bool TryPurchasing(long price, List<Item[]> inv, List<Point> slotCoins, List<Point> slotsEmpty, List<Point> slotEmptyBank, List<Point> slotEmptyBank2, List<Point> slotEmptyBank3, List<Point> slotEmptyBank4)
	{
		// Purchasing logic.
		VirtualCurrencyPlayer modplayer = Main.LocalPlayer.GetModPlayer<VirtualCurrencyPlayer>();
		if (modplayer.VirtualCurrencyBalance >= price)
		{
			modplayer.VirtualCurrencyBalance -= price;
			return true;
		}
		return false;
	}

	// This shows up on the right side of the shop window.
	// Normally, this only shows up if you have the currency items in your Piggy Bank, Safe, Defender's Forge, or Void Bag/Vault, but this currency has no item.
	public override void DrawSavingsMoney(SpriteBatch sb, string text, float shopx, float shopy, long totalCoins, bool horizontal = false)
	{
		// How to get a texture from the currency item. Not needed for this example since this currency has no item.
		// int item = _valuePerUnit.Keys.ElementAt(0);
		// Main.instance.LoadItem(item);
		// Texture2D drawTexture = TextureAssets.Item[item].Value;
		
		totalCoins--; // We increased the actual count in CombineStacks by 1. Correct that here.

		// Here we are loading a custom texture to show in the Savings section.
		TownNPCGuide modInstance = ModContent.GetInstance<TownNPCGuide>();
		modInstance.VirtualCurrencyCoin ??= modInstance.Assets.Request<Texture2D>("Content/Currencies/VirtualCurrencyCoin");

		// Draw the texture and amount.
		int shift = ((totalCoins > 99) ? (-6) : 0);
		sb.Draw(modInstance.VirtualCurrencyCoin.Value, new Vector2(shopx + 11f, shopy + 75f), null, Color.White, 0f, modInstance.VirtualCurrencyCoin.Size() / 2f, CurrencyDrawScale, SpriteEffects.None, 0f);
		Utils.DrawBorderStringFourWay(sb, FontAssets.ItemStack.Value, totalCoins.ToString(), shopx + (float)shift, shopy + 75f, Color.White, Color.Black, new Vector2(0.3f), 0.75f);
	}

	// Important: Needed so you can buy items without having the currency item.
	public override long CountCurrency(out bool overFlowing, Item[] inv, params int[] ignoreSlots)
	{
		overFlowing = false;
		return Main.LocalPlayer.GetModPlayer<VirtualCurrencyPlayer>().VirtualCurrencyBalance;
	}

	// Needed for DrawSavingsMoney to show up at all times.
	public override long CombineStacks(out bool overFlowing, params long[] coinCounts)
	{
		overFlowing = false;
		// Add one so it'll never be 0. If it is 0, DrawSavingsMoney doesn't show up.
		return Main.LocalPlayer.GetModPlayer<VirtualCurrencyPlayer>().VirtualCurrencyBalance + 1;
	}
}

Accepts() and CountCurrency() are important so that we have buy items without having any items in our inventory.

The reason we add one to CombineStacks() is because we want the currency to show up in the Savings list, even if we have 0. If we do have 0, though, the balance won't show up. Adding 1 ensures that the CombineStacks() value never reports 0 (unless you make it so you can go into dept or something) and so the Savings list always shows up. We correct the additional count in DrawSavingsMoney(). Don't worry about this affecting the real balance, that is stored in your ModPlayer class which we aren't changing here.

Even though we don't have an item for our currency, we still want a currency icon that shows up in the Savings list. So, we load a texture that is stored in our mod class. VirtualCurrencyCoin

TryPurchasing() is where our purchasing logic is. The default logic for CustomCurrencySingleCoin is very complex because it checks for items in the players inventories and all of their banks. But since our currency is virtual, we don't need such complex logic.

Loading the currency and texture in the Mod class **Click to Expand**
internal Asset<Texture2D> VirtualCurrencyCoin; // Where our icon for the virtual currency is stored.

public override void Load()
{
	// Create a virtual currency. ItemID.None is set as the item because this currency has no item.
	VirtualCurrency.VirtualCurrencySystem = new VirtualCurrency(ItemID.None, 9999L, "Mods.TownNPCGuide.Currency.VirtualCurrency");
	VirtualCurrency.VirtualCurrencyID = CustomCurrencyManager.RegisterCurrency(VirtualCurrency.VirtualCurrencySystem);
}

It is up to you on how the player obtains this virtual currency. To give the player currency, it is usually as simple as player.GetModPlayer<VirtualCurrencyPlayer>().VirtualCurrencyBalance++ or similar.

Using the Currencies in a Shop

So we've made these custom currencies; how do we actually use it in our shops? It's easy! We do just like what we would do for setting a custom price, but we add an additional shopSpecialCurrency constructor and set that to our currency's ID.

npcShop.Add(new Item(ItemID.IronPickaxe) { shopSpecialCurrency = TutorialCurrency.TutorialCurrencyID, shopCustomPrice = 1});
npcShop.Add(new Item(ItemID.IronShortsword) { shopSpecialCurrency = TutorialCurrency.TutorialCurrencyID, shopCustomPrice = 25});
npcShop.Add(new Item(ItemID.IronBroadsword) { shopSpecialCurrency = TutorialCurrency.TutorialCurrencyID, shopCustomPrice = 50});

npcShop.Add(new Item(ItemID.Leather) { shopSpecialCurrency = VanillaItemAsCurrency.VanillaItemAsCurrencyID, shopCustomPrice = 5 });

npcShop.Add(new Item(ItemID.EchoBlock) { shopSpecialCurrency = VirtualCurrency.VirtualCurrencyID, shopCustomPrice = 3 });

Advanced - Cross Mod Things

What if we want our Town NPC to interact with things from other mods, such as selling items, happiness, and chat? We can do all of those things without adding the other mods as a strong or weak reference.

Chat

In GetChat(), we can do something like this:

// First check if the mod is enabled
if (ModLoader.TryGetMod("ExampleMod", out Mod exampleMod)) {
	// Next, try to find the other Town NPC.
	// Using TryFind<>() is safer than using Find<>()
	// If the NPC we are trying to find doesn't exist, our mod will continue to work.
	if (exampleMod.TryFind<ModNPC>("ExamplePerson", out ModNPC examplePersonModNPC)) {
		int examplePerson = NPC.FindFirstNPC(examplePersonModNPC.Type);
		if (examplePerson >= 0) {
			chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.ExamplePerson", Main.npc[examplePerson].FullName), 2);
		}
	}
}

When finding the other Town NPC, we need to search for the NPC's class name.

Shop

In AddShops(), we can do something like this:

// First check if the mod is enabled
if (ModLoader.TryGetMod("ExampleMod", out Mod exampleMod)) {
	// Next, try to find the item.
	// Using TryFind<>() is safer than using Find<>()
	// If the item we are trying to find doesn't exist, our mod will continue to work.
	if (exampleMod.TryFind<ModItem>("ExampleJoustingLance", out ModItem exampleJoustingLance)) {
		npcShop2.Add(exampleJoustingLance.Type);
	}
}

// Actually, we don't even need to check to see if the mod is enabled.
// We can specify the mod name when we try to find the item. This is still safe because the item won't get added if TryFind fails.
if (ModContent.TryFind<ModItem>("ExampleMod/ExamplePaperAirplane", out ModItem examplePaperAirplane)) {
	npcShop2.Add(examplePaperAirplane.Type);
}

When finding the other item, we need to search for the item's class name.

Make sure the item exists before you try adding the item to the shop. If you try to add null as the item, your mod will fail to load with other mod enabled.

Happiness

In our GobalNPC class in SetStaticDefaults(), we can do something like this:

// First check if the mod is enabled
if (ModLoader.TryGetMod("ExampleMod", out Mod exampleMod)) {
	// Next, try to find the other Town NPC.
	// Using TryFind<>() is safer than using Find<>()
	// If the NPC we are trying to find doesn't exist, our mod will continue to work.
	if (exampleMod.TryFind<ModNPC>("ExamplePerson", out ModNPC examplePersonModNPC)) {
		// Get their happiness
		var examplePersonHappiness = NPCHappiness.Get(examplePersonModNPC.Type);
		var tutorialTownNPCHappiness = NPCHappiness.Get(tutorialTownNPC);

		// Make them love each other!
		examplePersonHappiness.SetNPCAffection(tutorialTownNPC, AffectionLevel.Love);
		tutorialTownNPCHappiness.SetNPCAffection(examplePersonModNPC.Type, AffectionLevel.Love);
	}
}

Advanced - Shop

public override void AddShops() {
	// We can use the chaining syntax on our shops too.
	// Notice how there are no semi colons until the end.
	var npcShop2 = new NPCShop(Type, Shop2)
		.Add(ItemID.Starfury)
		.Add(ItemID.TerraBlade, Condition.DownedPlantera, Condition.Eclipse)
		.Add(ItemID.RainbowRod, Condition.DownedMechBossAll)
		.Add(ItemID.Terragrim, Condition.PlayerCarriesItem(ItemID.EnchantedSword))
		.Add(ItemID.Megashark, Condition.NpcIsPresent(NPCID.ArmsDealer));

	// This will hide one of the items in the shop. It will throw an exception if the item is not found, though.
	npcShop2.GetEntry(ItemID.RainbowRod).Disable();

	// TryGetEntry will not throw an exception if it fails.
	if (npcShop2.TryGetEntry(ItemID.Megashark, out var entry)) {
		// We can add additional conditions.
		entry.AddCondition(Condition.DownedMechBossAny);
	}

	npcShop2.Add(ItemID.LightsBane, Condition.DownedEowOrBoc);
	// The Space Gun will be added to the shop before the Light's Bane
	npcShop2.InsertBefore(ItemID.LightsBane, ItemID.SpaceGun);
	// The Influx Waver will be added after the Terra Blade.
	npcShop2.InsertAfter(ItemID.TerraBlade, ItemID.InfluxWaver, Condition.DownedMartians);

	npcShop2.Add(ItemID.DD2BallistraTowerT1Popper, Condition.DownedOldOnesArmyAny);
	npcShop2.GetEntry(ItemID.DD2BallistraTowerT1Popper).ReserveSlot(); // We reserve this slot it always appear here.
}

The ModifyActiveShop() hook can be used to edit the shop inventory each time the shop is openned, as opposed to once during mod load like with AddShops(). This hook should only really be used to editing exist items and you should avoid adding new items with this hook.

public override void ModifyActiveShop(string shopName, Item[] items) {
	// ModifyActiveShop() can modify the shop each time the shop is opened (as opposed to once during mod load).
	// This hook should mostly be used for modifying items already in the shop instead of adding new ones.

	// Here is an example using a foreach loop
	foreach (Item item in items) {
		// If the item in the list doesn't exist, continue to the next item.
		if (item is null)
		{
			continue;
		}
		// If the item is a Terragrim, set it's price to double it's value.
		if (item.type == ItemID.Terragrim) {
			item.shopCustomPrice = item.value * 2;
		}
		// If the item is a Starfury in the second shop, set its modifier to Legendary.
		if (item.type == ItemID.Starfury && shopName == NPCShopDatabase.GetShopName(Type, Shop2)) {
			item.Prefix(PrefixID.Legendary);
		}
		// If the player has the Discount Card equipped, then make the prices even cheaper for our Town NPC.
		if (Main.LocalPlayer.discountAvailable) {
			// We want to discount the shopCustomPrice number if it exists. If it doesn't, then we discount the value instead.
			// Changing the price like this is multiplicative with the discount from the Discount Card. (So 0.8f * 0.8f == 0.64f or 36% discount)
			item.shopCustomPrice = (int?)((item.shopCustomPrice ?? item.value) * 0.8f);
		}
	}

	// Here is an example using a for loop
	for (int i = 0; i < items.Length; i++) {
		// Here we find the first item that doesn't exist and set it to the Universal Pylon, then we break out of the loop to stop it.
		if (items[i] is null) {
			items[i] = new Item(ItemID.TeleportationPylonVictory);
			break;
		}
	}

	// Here is an example adding an item to the last slot of the shop.
	items[^1] = new Item(ItemID.PoopBlock) { shopCustomPrice = Item.buyPrice(platinum: 1) };
}
Click here for Pre-1.4.4 Shop code **Click to Expand**

Instead of nextSlot, we can specify a specific slot that our item will appear in. This is similar to how the Tavernkeep's shop is set up. We can also use AddItemToShop() to add an Item object instead just its type.

// The Tavernkeep has his items set in specific locations in his shop. Instead of using nextSlot, we can give a specific slot.
// Slots range from 0 to 39, with Chest.maxItems corresponding to 40.
// Keep in mind that doing it this way could replace items that would already be existing there. Or, other items can replace this one.
shop.item[10].SetDefaults(ItemID.MarshmallowonaStick); // This will go in the first slot on the second row.
shop.item[Chest.maxItems - 1].SetDefaults(ItemID.IceCream); // This will go in the last slot of the shop.

// AddItemToShop() allows us to pass an Item object instead of just its type. We can use this to get a specific stack of items in the shop.
// This isn't really a limited stock, though. The item will reappear when the shop is closed and reopened.
shop.AddItemToShop(new Item(ItemID.IronBar, 5));

// Here we create an item object and change a bunch aspects about it.
// It is not recommended to create items this way. The stats (except reforge modifier) will reset when rejoining the world.
Item modifiedItem = new Item(ItemID.GoldBroadsword, 1, PrefixID.Legendary) {
	rare = ItemRarityID.Red,
	value = Item.buyPrice(gold: 50),
	color = Color.DeepPink,
	damage = 60,
	knockBack = 12f,
	scale = 1.5f
};
shop.AddItemToShop(modifiedItem);

Advanced - ITownNPCProfile

The normal StackedNPCProfile will work for a large majority of Town NPCs. However, if we want more freedom, we can create our own ITownNPCProfile class.

// Defining our own Town NPC Profile gives us a little more freedom, but is rarely needed.
public class TutorialTownNPCProfile : ITownNPCProfile {
	public int RollVariation() => 0;

	// If your Town NPC has no random names, return null here.
	public string GetNameForVariant(NPC npc) => npc.getNewNPCName();

	public Asset<Texture2D> GetTextureNPCShouldUse(NPC npc) {
		// Here we can give our Town NPC a bunch of different textures if we wanted.
		// For example, we can make the texture in the Bestiary different.
		if (npc.IsABestiaryIconDummy && !npc.ForcePartyHatOn) {
			return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_BestiaryExample");
		}

		// Shimmered and party
		if (npc.IsShimmerVariant && npc.altTexture == 1) {
			return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Shimmer_Party");
		}
		// Shimmered and no party
		if (npc.IsShimmerVariant && npc.altTexture != 1) {
			return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Shimmer");
		}
		// Not shimmered and party
		if (!npc.IsShimmerVariant && npc.altTexture == 1) {
			return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Party");
		}
		// Not shimmered and no party
		return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC");
	}

	public int GetHeadTextureIndex(NPC npc) {
		if (npc.IsShimmerVariant) {
			return TutorialTownNPC.ShimmerHeadIndex;
		}
		return ModContent.GetModHeadSlot("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Head");
	}

	// In our Town NPC's class, we'll need to change a few things:
	// internal static int ShimmerHeadIndex; // Change private to internal so we can access it here.
	// private static ITownNPCProfile NPCProfile; // Change Profiles.StackedNPCProfile to ITownNPCProfile.

	// In SetStaticDefaults(), change
	//	NPCProfile = new Profiles.StackedNPCProfile(
	//		new Profiles.DefaultNPCProfile(Texture, NPCHeadLoader.GetHeadSlot(HeadTexture), Texture + "_Party"),
	//		new Profiles.DefaultNPCProfile(Texture + "_Shimmer", ShimmerHeadIndex, Texture + "_Shimmer_Party")
	//	);
	// To:
	// NPCProfile = new TutorialTownNPCProfile();
}

Advanced - Drawing

In this example, we have two glow masks that we want to draw on top of our Town NPC; one of which should only be drawn while our Town NPC is attacking.

For this guide, we will be using these sprites **Click to Expand**

TutorialTownNPC_GlowMask TutorialTownNPC_GlowMask TutorialTownNPC_GlowMaskAttacking TutorialTownNPC_GlowMaskAttacking

// Load our textures
private readonly Asset<Texture2D> glowMask = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_GlowMask");
private readonly Asset<Texture2D> glowMaskAttacking = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_GlowMaskAttacking");
public override void PostDraw(SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) {
	// Flip the glow mask to match which direction the Town NPC is facing.
	SpriteEffects spriteEffects = NPC.spriteDirection > 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
	// Draw our glow mask in full bright.
	Color color = Color.White;

	// Move the position up by 4 pixels plus the gfxOffY value (that is for climbing half blocks).
	// Main.NPCAddHeight() makes it so if the Town NPC is sitting, it also moves the glow mask up by 4 more pixels.
	Vector2 verticalOffset = new(0, -4 + NPC.gfxOffY + Main.NPCAddHeight(NPC));

	// Draw our glow mask
	spriteBatch.Draw(glowMask.Value, NPC.Center - screenPos + verticalOffset, NPC.frame, color, NPC.rotation, NPC.frame.Size() / 2f, NPC.scale, spriteEffects, 1f);

	// Only draw our extra attacking glow mask while attacking, which are frames 21+
	if (NPC.frame.Y > 20 * NPC.frame.Height) { 
		spriteBatch.Draw(glowMaskAttacking.Value, NPC.Center - screenPos + verticalOffset, NPC.frame, color, NPC.rotation, NPC.frame.Size() / 2f, NPC.scale, spriteEffects, 1f);
	}
}

Advanced - Emote Location

We can change the location that the emote will appear on our Town NPC. This may be desirable if your Town NPC is a different size than the standard player size. To do this, we need to use a Detour. This is an advanced technique, make sure you read about Detours here. tModLoader does not have a built in hook for this that we can override, so a detour is necessary.

Emote Position Detour **Click to Expand**
public override void Load()
{
	// Here we load our other hook. Note: You can have only one Load() override in the same class.
	Terraria.GameContent.UI.On_EmoteBubble.GetPosition += EmoteBubble_Hook_GetPosition;
}

private delegate void orig_EmoteBubble_GetPosition(EmoteBubble self);

private static Vector2 EmoteBubble_Hook_GetPosition(Terraria.GameContent.UI.On_EmoteBubble.orig_GetPosition orig, EmoteBubble self, out SpriteEffects effect)
{
	// Only change for our Town NPC.
	if (self.anchor.type == WorldUIAnchor.AnchorType.Entity && self.anchor.entity is NPC npc && npc.type == ModContent.NPCType<TutorialTownNPC>())
	{
		// Flip the bubble to the opposite side of where it is normally.
		effect = ((self.anchor.entity.direction == -1) ? SpriteEffects.FlipHorizontally : SpriteEffects.None);

		// Move it to the front of the entity and move it out more.
		float distance = 2f; // The 0.75f multiplier in the original code moves it closer (bigger numbers move it away).

		return new Vector2(self.anchor.entity.Top.X, self.anchor.entity.VisualPosition.Y) + new Vector2((float)(self.anchor.entity.direction * self.anchor.entity.width), distance) - Main.screenPosition;
		
		// Original vanilla code:
		// effect = ((anchor.entity.direction != -1) ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
		// return new Vector2(anchor.entity.Top.X, anchor.entity.VisualPosition.Y) + new Vector2((float)(-anchor.entity.direction * anchor.entity.width) * 0.75f, 2f) - Main.screenPosition;
	}

	// Do the original code if not our Town NPC.
	return orig(self, out effect);
}

Advanced - AI

When using vanilla Town NPC AI, NPC.ai[] and NPC.localAI[] are used to keep track of different things. Here are what some of them mean and a few little things we can do with them. This is not a complete list.

public override void PostAI() {
	// NPC.ai[0] = current action
	//  0 == no action
	//  1 == walking
	//  2 == 
	//  3-4 == chatting
	//  5 == sitting
	//  7 == Talking to player
	//  8 == Decide to run from enemy? Immediately set to 1.
	//  9 == Go sit in a chair?
	//  10 == attacking
	//  12-15 == attacking related
	//  16 == player rock paper scissors
	//  19 == Talking to player (responding to the player)
	//  24 == magic attack aura
	//  25 == change to a different action

	// NPC.ai[1] = time before changing actions
	//  299 when talking to a player

	// NPC.ai[2] = The player index that the Town NPC is speaking to? (Player.whoAmI) 
	//  Reset to 0 most of the time

	// NPC.ai[3] =
	//  Used by the Old Man

	// NPC.localAI[0] is open
	//  This is used by the Mechanic to know to stay still while her boomerang is returning to her.
	// NPC.localAI[1] = Attacking cooldown
	//  0 most of the time
	// NPC.localAI[2] =
	//  1 most of the time
	// NPC.localAI[3] = Attacking animation time
	//  -1 or 0 most of the time when not attacking
	//  -99 when talking to a player

	// If just started to attack.
	if (NPC.ai[0] == 10 && NPC.localAI[3] == 1) {
		EmoteBubble.NewBubble(EmoteID.ItemSword, new WorldUIAnchor(NPC), 120); // Display an emote above their head.
	}
	// Standing still about to do something else and very hurt.
	if (NPC.ai[0] == 0 && NPC.ai[1] == 10 && NPC.life < NPC.lifeMax * 0.25f) {
		EmoteBubble.NewBubble(EmoteID.ItemTombstone, new WorldUIAnchor(NPC), 120); // Display an emote above their head.
	}
	// If talking to the player.
	if (NPC.ai[0] == 7 || NPC.ai[0] == 19) {
		NPC.AddBuff(BuffID.Lovestruck, 2); // Give them a buff.
	}
}

Advanced - Town Pets

Town Pets are much more involved because there are several extra things that are needed to get them to function like the vanilla Town Pets.

This example is a clone of the Town Cat. I highly recommend you use any of the existing Town Pets or Slimes for your sprite designs, otherwise you will need to write custom AI to make the frames work.

Pet Sprites

As mentioned right above, I highly recommend basing your sprites off of the existing Town Pets or Slimes. Here are the sprites that are used in this example. Note that TutorialTownPet and TutorialTownPet_0 are the same sprite. The non number one is needed for autoloading the texture, and the numbered one is actually used by the ITownNPCProfile. (Technically you could write the code in a way that you don't need both.)

This Town Pet has 5 variants **Click to Expand**

TutorialTownPet TutorialTownPet

TutorialTownPet_0 TutorialTownPet_0

TutorialTownPet_1 TutorialTownPet_1

TutorialTownPet_1_Head TutorialTownPet_1_Head

TutorialTownPet_2 TutorialTownPet_2

TutorialTownPet_2_Head TutorialTownPet_2_Head

TutorialTownPet_3 TutorialTownPet_3

TutorialTownPet_3_Head TutorialTownPet_3_Head

TutorialTownPet_4 TutorialTownPet_4

TutorialTownPet_4_Head TutorialTownPet_4_Head

TutorialTownPet_Head TutorialTownPet_Head

ModSystem World Bool

First, we should make a bool for unlocking our Town Pet. Use a ModSystem class to store this bool and save and load it.

Here is a very basic example:

ModSystem Code **Click to Expand**
public class TownNPCGuideWorld : ModSystem {
	public static bool boughtTutorialTownPet = false;

	public override void SaveWorldData(TagCompound tag) {
		if (boughtTutorialTownPet) {
			tag["boughtTutorialTownPet"] = true;
		}
	}

	public override void LoadWorldData(TagCompound tag) {
		boughtTutorialTownPet = tag.ContainsKey("boughtTutorialTownPet");
	}

	public override void NetSend(BinaryWriter writer) {
		BitsByte flags = new BitsByte();
		flags[0] = boughtTutorialTownPet;
		writer.Write(flags);
	}

	public override void NetReceive(BinaryReader reader) {
		BitsByte flags = reader.ReadByte();
		boughtTutorialTownPet = flags[0];
	}
}

For more information, see DownedBossSystem

Exchange ModPacket

Pet Licenses not only unlock the Town Pet, but they can also be used to change the variation of the Town Pet. To make exchanging work for our Town Pet will need to create a ModPacket for it. The reason for this will be more obvious in the License code. We need to use HandlePacket() in your Mod class.

Here is a very basic example for using packets:

HandlePacket Code **Click to Expand**
public class TownNPCGuide : Mod
{
	public override void HandlePacket(BinaryReader reader, int whoAmI)
	{
		TownNPCGuideMessageType msgType = (TownNPCGuideMessageType)reader.ReadByte();

		switch (msgType)
		{
			case TownNPCGuideMessageType.TownPetUnlockOrExchange:
				// Call a custom function that we made in our License item.
				TutorialTownPetLicense.TutorialTownPetUnlockOrExchangePet(ref TownNPCGuideWorld.boughtTutorialTownPet, ModContent.NPCType<TutorialTownPet>(), "Mods.TownNPCGuide.NPCs.TutorialTownPet.UI.LicenseTutorialTownPetUse");
				break;
			default:
				break;
		}
	}
}

internal enum TownNPCGuideMessageType : byte
{
	TownPetUnlockOrExchange
}

The packet is super simple. It just calls a function once it is received to change the variation for the client.

Pet Class

The Town Pet's class is where most of the action takes place, of course. We will create a new class inheriting ModNPC like you'd expect. A lot of the things we need here are similar to "regular" Town NPCs.

Town Pet ModNPC Code **Click to Expand**
[AutoloadHead]
public class TutorialTownPet : ModNPC
{
	// Where our additional head sprites will be stored.
	internal static int HeadIndex1;
	internal static int HeadIndex2;
	internal static int HeadIndex3;
	internal static int HeadIndex4;

	private static ITownNPCProfile NPCProfile;

	public override void Load()
	{
		// Adds our variant heads to the NPCHeadLoader.
		HeadIndex1 = Mod.AddNPCHeadTexture(Type, Texture + "_1_Head");
		HeadIndex2 = Mod.AddNPCHeadTexture(Type, Texture + "_2_Head");
		HeadIndex3 = Mod.AddNPCHeadTexture(Type, Texture + "_3_Head");
		HeadIndex4 = Mod.AddNPCHeadTexture(Type, Texture + "_4_Head");
	}

	public override void SetStaticDefaults()
	{
		Main.npcFrameCount[Type] = 28;
		NPCID.Sets.ExtraFramesCount[Type] = 18; // The number of frames after the walking frames.
		NPCID.Sets.AttackFrameCount[Type] = 0; // Town Pets don't have any attacking frames.
		NPCID.Sets.DangerDetectRange[Type] = 250; // How far away the NPC will detect danger. Measured in pixels.
		NPCID.Sets.AttackType[Type] = -1;
		NPCID.Sets.AttackTime[Type] = -1;
		NPCID.Sets.AttackAverageChance[Type] = 1;
		NPCID.Sets.HatOffsetY[Type] = 0; // An offset for where the party hat sits on the sprite.
		NPCID.Sets.ShimmerTownTransform[Type] = false; // Town Pets don't have a Shimmer variant.
		NPCID.Sets.SpecificDebuffImmunity[Type][BuffID.Shimmer] = true; // But they are still immune to Shimmer.
		NPCID.Sets.SpecificDebuffImmunity[Type][BuffID.Confused] = true; // And Confused.
		NPCID.Sets.ExtraTextureCount[Type] = 0; // Even though we have several variation textures, we don't use this set.
		NPCID.Sets.NPCFramingGroup[Type] = 4; // How the party hat is animated to match the walking animation. Town Cat = 4, Town Dog = 5, Town Bunny = 6, Town Slimes = 7

		NPCID.Sets.IsTownPet[Type] = true; // Our NPC is a Town Pet
		NPCID.Sets.CannotSitOnFurniture[Type] = false; // True by default, but Town Cats can sit on furniture.
		NPCID.Sets.TownNPCBestiaryPriority.Add(Type); // Puts our NPC with all of the other Town NPCs.

		// Influences how the NPC looks in the Bestiary
		NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new()
		{
			Velocity = 0.25f, // Draws the NPC in the bestiary as if its walking +0.25 tiles in the x direction
		};

		NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers);

		NPCProfile = new TutorialTownPetProfile(); // Assign our profile.
	}
	public override void SetDefaults()
	{
		NPC.townNPC = true;
		NPC.friendly = true;
		NPC.width = 18;
		NPC.height = 20;
		NPC.aiStyle = NPCAIStyleID.Passive;
		NPC.damage = 10;
		NPC.defense = 15;
		NPC.lifeMax = 250;
		NPC.HitSound = SoundID.NPCHit1;
		NPC.DeathSound = SoundID.NPCDeath6;
		NPC.knockBackResist = 0.5f;
		NPC.housingCategory = 1; // This means it can share a house with a normal Town NPC.
		AnimationType = NPCID.TownCat; // This example matches the animations of the Town Cat.
	}
	public override void SetBestiary(BestiaryDatabase database, BestiaryEntry bestiaryEntry)
	{
		bestiaryEntry.Info.AddRange(new IBestiaryInfoElement[]
		{
			BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Biomes.Surface,
			new FlavorTextBestiaryInfoElement("Mods.TownNPCGuide.NPCs.TutorialTownPet.Bestiary")
		});
	}

	public override bool CanTownNPCSpawn(int numTownNPCs)
	{
		// If we've used the License, our Town Pet can freely respawn.
		if (TownNPCGuideWorld.boughtTutorialTownPet)
		{
			return true;
		}
		return false;
	}

	public override ITownNPCProfile TownNPCProfile()
	{
		// Vanilla Town Pets use Profiles.VariantNPCProfile() to set the variants, but that doesn't work for us because
		// it uses Main.Assets.Request<>() which won't find mod assets (ModContent.Request<>() is needed instead).
		// So, we make our own NPCProfile. (See Below)
		return NPCProfile;
	}

	// Create a bunch of lists for our names. Each variant gets its own list of names.
	public readonly List<string> NameList0 = new ()
	{
		"Tutorial", "Guide", "Example"
	};
	public readonly List<string> NameList1 = new()
	{
		"Bob", "Bill", "Billy-bob"
	};
	public readonly List<string> NameList2 = new()
	{
		"John", "Jane"
	};
	public readonly List<string> NameList3 = new()
	{
		"Sir Fluffykins of the Dungeon", "Nugget", "Snausages", "big butts and I cannot lie", "Skadoodles"
	};
	public readonly List<string> NameList4 = new()
	{
		"Blinky", "Pinky", "Inky", "Clyde"
	};

	public override List<string> SetNPCNameList()
	{
		return NPC.townNpcVariationIndex switch // Change the name based on the variation.
		{
			0 => NameList0,
			1 => NameList1, // Will be the Shimmered variant if your NPC has a shimmer variant.
			2 => NameList2,
			3 => NameList3,
			4 => NameList4,
			_ => NameList0
		};
	}

	public override string GetChat()
	{
		WeightedRandom<string> chat = new();

		chat.Add("*Tutorial Town Pet noises*");

		return chat;
	}

	public override void SetChatButtons(ref string button, ref string button2)
	{
		button = Language.GetTextValue("UI.PetTheAnimal"); // Pet
	}

	public override bool CanGoToStatue(bool toKingStatue)
	{
		return false; // Don't go to King or Queen statues. (Default is false so this technically isn't needed.)
	}
}

Similarly to Shimmer variants, we have several head variants that we need to load and store for our Town Pet. This example is has 5 variants and is based on the Town Cat.

Some of the important differences are:

  • NPCID.Sets.IsTownPet[Type] = true This will tell the game that your NPC is a Town Pet.
  • NPCID.Sets.CannotSitOnFurniture[Type] = true Town Cats can sit on furniture, but Town Dogs, Town Bunnies, and Town Slimes cannot.
  • NPCID.Sets.TownNPCBestiaryPriority.Add(Type) Groups your NPC with the other Town NPCs.
  • NPC.housingCategory = 1 This tells the game that this NPC can share homes with other Town NPCs, like the other Town Pets.

Vanilla Town Pets do not have Shimmer variants. If you decide to make it so your Town Pet has a Shimmer variant, keep in mind that variant 1 will be the Shimmer variant. More on this in the ITownNPCProfile section.

Pet ITownNPCProfile

Vanilla Town Pets use Profiles.VariantNPCProfile() to define all of their variants. This won't work for us, unfortunately. VariantNPCProfile() uses Main.Assets.Request<>() to load the textures which only works for vanilla ones. We need ModContent.Request<>() for modded textures. So, we have to create our own ITownNPCProfile.

Town Pet Profile Code **Click to Expand**
public class TutorialTownPetProfile : ITownNPCProfile
{
	// Some helper strings so we don't have to type the same things multiple times.
	// This assumes that your name this class the class name of your Town NPC + Profile.
	private string Namespace => GetType().Namespace.Replace('.', '/');
	private string NPCName => (GetType().Name.Split("Profile")[0]).Replace('.', '/');
	private string FilePath => (Namespace + "/" + NPCName);

	public int RollVariation()
	{
		int random = Main.rand.Next(5); // 5 variants; 0 through 4.

		// If your Town Pet has a shimmer variant:
		// townNpcVariationIndex of 1 makes it shimmered, even when it isn't.
		// if (random == 1) {
		//	random = 5; // So variation 1 becomes number 5.
		// }
	
		return random;
	}

	public string GetNameForVariant(NPC npc) => npc.getNewNPCName();

	public Asset<Texture2D> GetTextureNPCShouldUse(NPC npc)
	{
		return ModContent.Request<Texture2D>(FilePath + "_" + npc.townNpcVariationIndex);
	}

	public int GetHeadTextureIndex(NPC npc)
	{
		return npc.townNpcVariationIndex switch
		{
			0 => ModContent.GetModHeadSlot(FilePath + "_Head"),
			1 => TutorialTownPet.HeadIndex1, // Will be the Shimmered variant if your NPC has a shimmer variant.
			2 => TutorialTownPet.HeadIndex2,
			3 => TutorialTownPet.HeadIndex3,
			4 => TutorialTownPet.HeadIndex4,
			_ => ModContent.GetModHeadSlot(FilePath + "_Head")
		};
	}
}

Pet License

Now onto our Pet License item. TutorialTownPetLicense

Town Pet License Code **Click to Expand**
public class TutorialTownPetLicense : ModItem
{
	public override void SetDefaults()
	{
		Item.useStyle = ItemUseStyleID.HoldUp;
		Item.consumable = true;
		Item.useAnimation = 45;
		Item.useTime = 45;
		Item.UseSound = SoundID.Item92;
		Item.width = 28;
		Item.height = 28;
		Item.maxStack = Item.CommonMaxStack;
		Item.SetShopValues(ItemRarityColor.Green2, Item.buyPrice(0, 5));
	}

	public override void OnConsumeItem(Player player)
	{
		int npcType = ModContent.NPCType<TutorialTownPet>(); // The NPC Type for the Town Pet.
		if (player.whoAmI == Main.myPlayer && player.itemAnimation > 0)
		{
			// Only do something if the License hasn't been used before or the Town Pet exists in the world.
			if (!TownNPCGuideWorld.boughtTutorialTownPet || NPC.AnyNPCs(npcType))
			{
				player.ApplyItemTime(Item); // Make it so the player uses the item for the useAnimation.
				TutorialTownPetUnlockOrExchangePet(ref TownNPCGuideWorld.boughtTutorialTownPet, npcType, "Mods.TownNPCGuide.NPCs.TutorialTownPet.UI.LicenseTutorialTownPetUse"); // Modified NPC.UnlockOrExchangePet method.
			}
		}
	}
	public override bool? UseItem(Player player)
	{
		// Only consume the item if it is going to do something.
		if (!TownNPCGuideWorld.boughtTutorialTownPet || NPC.AnyNPCs(ModContent.NPCType<TutorialTownPet>()))
		{
			return true;
		}
		return null;
	}

	/// <summary>
	/// <br>The vanilla method NPC.UnlockOrExchangePet will not work for our modded Town Pets because the NetMessage only works with vanilla NPCs.</br>
	/// <br>This version uses a ModPacket for that instead.</br>
	/// </summary>
	/// <param name="petBoughtFlag">The bool that determines if the License has been used once. Doesn't really have anything to do with buying.</param>
	/// <param name="npcType">The NPC Type for the Town Pet.</param>
	/// <param name="textKeyForLicense">The localization path for when the License has been used for the first time.</param>
	public static void TutorialTownPetUnlockOrExchangePet(ref bool petBoughtFlag, int npcType, string textKeyForLicense)
	{
		Color color = new(50, 255, 130);
		if (Main.netMode == NetmodeID.MultiplayerClient)
		{
			if (!petBoughtFlag || NPC.AnyNPCs(npcType))
			{
				ModPacket packet = ModContent.GetInstance<TownNPCGuide>().GetPacket();
				packet.Write((byte)TownNPCGuideMessageType.TownPetUnlockOrExchange);
				packet.Send();
			}
		}
		else if (!petBoughtFlag)
		{
			petBoughtFlag = true;
			ChatHelper.BroadcastChatMessage(NetworkText.FromKey(textKeyForLicense), color);
			NetMessage.TrySendData(MessageID.WorldData);
		}
		else if (NPC.RerollVariationForNPCType(npcType))
		{
			ChatHelper.BroadcastChatMessage(NetworkText.FromKey("Misc.PetExchangeSuccess"), color);
		}
		else
		{
			ChatHelper.BroadcastChatMessage(NetworkText.FromKey("Misc.PetExchangeFail"), color);
		}
	}
}

We need to create our own copy of NPC.UnlockOrExchangePet() because the vanilla one uses a NetMessage that is hardcoded to only support the vanilla Town Pets. Instead, we send our ModPacket that we defined earlier. Using the item only runs client side, so unlocking and exchanging the pet won't work. The packet is sent out to other players so that they also run the code that does the unlocking and exchanging.

Petting Detour

Our Town Pet is complete! However, depending on the sprite of your Town Pet, you may notice that the player stands in an unideal spot when petting them and/or the angle of their arm isn't correct. The only way we can change this is to use a Detour.

Additionally, if your Town Pet is allowed to sit on chairs, you may notice that they don't actually raise up to sit at the height of the chair. We can use a Detour to fix that, too.

Detour Code **Click to Expand**
public class PetDetour : ModSystem
{
	// The position of the player and the angle of their arm when petting is hardcoded.
	// We can use a Detour to change it for our Town Pet.

	public override void Load()
	{
		Terraria.On_Player.GetPettingInfo += Player_Hook_GetPettingInfo; // Load and apply our Detour.
		Terraria.On_Main.NPCAddHeight += Main_Hook_NPCAddHeight;
	}

	private delegate void orig_Player_GetPettingInfo(Player self); // Create a delegate because the original method is private.

	// Create our Detour
	private static void Player_Hook_GetPettingInfo(Terraria.On_Player.orig_GetPettingInfo orig, Player self, int animalNpcIndex, out int targetDirection, out Vector2 playerPositionWhenPetting, out bool isPetSmall)
	{
		// Run the vanilla code first.
		orig(self, animalNpcIndex, out targetDirection, out playerPositionWhenPetting, out isPetSmall);

		// Vanilla code
		/*
		int num = 36; // <--- distance multiplier
		isPetSmall = false;
		switch (nPC.type)
		{
			case 637: // <--- Town Cat
				isPetSmall = true;
				num = 28;
				break;
			case 656:
				isPetSmall = true;
				num = 24;
				break;
			case 670:
			case 678:
			case 679:
			case 680:
			case 681:
			case 683:
				isPetSmall = true;
				num = 26;
				break;
			case 682:
				isPetSmall = true;
				num = 22;
				break;
			case 684:
				isPetSmall = true;
				num = 20;
				break;
		}

		playerPositionWhenPetting = nPC.Bottom + new Vector2(-targetDirection * num, 0f);
		*/

		// If the NPC type is our Town Pet
		if (Main.npc[animalNpcIndex].type == ModContent.NPCType<TutorialTownPet>())
		{
			// Since the default distance multiplier is 36 and our Town Pet mimics the Town Cat which as a multiplier of 28,
			// we will move the position by 8 to match.
			// The position also depends on the width of the NPC's hitbox.
			playerPositionWhenPetting += new Vector2(targetDirection * 8f, 0f);

			// isPetSmall will make the arm angled down. Otherwise it is angled slightly up.
			isPetSmall = true;
		}
	}

	// Since our Town Pet can sit in chairs, we want to them to be moved up visually to match the height of the chair.
	private static float Main_Hook_NPCAddHeight(Terraria.On_Main.orig_NPCAddHeight orig, NPC theNPC)
	{
		// The NPC is our Town Pet and the Town Pet is sitting.
		if (theNPC.type == ModContent.NPCType<TutorialTownPet>() && theNPC.ai[0] == 5f)
		{
			// Move 14 pixels up. This matches the Town Cat.
			return -14f * theNPC.scale;
		}

		// Otherwise, run the vanilla code.
		return orig(theNPC);
	}

	// Still have other issues:
	// Chat bubble when hovering over the NPC isn't shifted up when sitting. Main.DrawNPCChatBubble()
	// Party hat isn't in the correct position. Main.DrawNPCExtras()
}

Shifting the drawing of the Town Pet up when sitting on chairs opens other cans of worms. Now the chat bubble when hovering over the Town Pet isn't shifted up either. You may also notice that the party hat is not really in the correct spot ever. Unfortunately, both of those issues are much more difficult to fix. The way to adjust them are in Main.DrawNPCChatBubble() and Main.DrawNPCExtras() respectively and would require IL Edits to change them.

Full Code

Here is the full code used for the TutorialTownNPC in this guide. Please do not blindly copy and paste this entire thing. The code contains many examples and alternate methods that you likely do not need and/or do not fit your own Town NPC.

TutorialTownNPC full code **Click to Expand**
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System.Collections.Generic;
using Terraria;
using Terraria.Chat;
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.Utilities;
using TownNPCGuide.Content.Items;
using TownNPCGuide.EmoteBubbles;

namespace TownNPCGuide.Content.NPCs.TownNPCs
{
	[AutoloadHead]
	public class TutorialTownNPC : ModNPC
	{
		public const string Shop1 = "Shop1"; // The string we assign for the first shop. We define it here so we can use the variable without having the chance to mess up typing the string.
		public const string Shop2 = "Shop2";
		internal static int ShimmerHeadIndex; // The index of the NPC head for when the Town NPC is in its shimmered variant.
		private static Profiles.StackedNPCProfile NPCProfile; // The Town NPC Profile.
		// private static ITownNPCProfile NPCProfile; // Advanced - ITownNPCProfile()

		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. You may need to change this based on how many frames your sprite sheet 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] = 700; // The amount of pixels away from the center of the NPC that it tries to attack enemies.
			NPCID.Sets.AttackType[Type] = 0; // The type of attack the Town NPC performs. 0 = throwing, 1 = shooting, 2 = magic, 3 = melee
			NPCID.Sets.AttackTime[Type] = 90; // The amount of time it takes for the NPC's attack animation to be over once it starts. Measured in ticks.
			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. Adjust this number to change where on your NPC's head the party hat sits.
			NPCID.Sets.ShimmerTownTransform[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.MagicAuraColor[Type] = Color.Yellow; // Magic attacks create an aura around the Town NPC. It is white by default, but we can set it to a color here.

			// We could use this set to disallow our Town NPC from opening doors. This defaults to true, so it isn't actually needed in this case.
			// Note: This set ONLY works for Town NPC AI. This set won't do anything for any other types of AI.
			NPCID.Sets.AllowDoorInteraction[Type] = true;

			// Changing the Framing Group will change where the party hat is offset on the sprite.
			// The default is 0 and that matches the player's walking animation.
			// See the guide on the details about the other groups.
			NPCID.Sets.NPCFramingGroup[Type] = 0;

			// Connects this NPC with a custom emote.
			// This makes it when the NPC is in the world, other NPCs will "talk about them".
			// By setting this you don't have to override the PickEmote method for the emote to appear.
			NPCID.Sets.FaceEmote[Type] = ModContent.EmoteBubbleType<TutorialTownNPCEmote>();

			// Influences how the NPC looks in the Bestiary
			NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new() {
				Velocity = 1f, // Draws the NPC in the bestiary as if it's walking +1 tiles in the x direction
				Direction = -1 // Faces left.
			};
			NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers);

			// In SetStaticDefaults(),
			// We use the NPC.Happiness hook to set our happiness. Here we can set our Town NPC's opinions on biomes and other Town NPCs.
			// We can use the chaining syntax similar to recipes to reduce the verbosity of the code.
			// Notice how we don't have any semicolons ; at the end of each line and instead a single semicolon at the very end.
			NPC.Happiness
				// All of the vanilla Town NPCs only LIKE and DISLIKE one biome, but we can set LOVE and HATE as well as many as we want.
				.SetBiomeAffection<HallowBiome>(AffectionLevel.Love) // Our Town NPC will love the Hallow.
				.SetBiomeAffection<ForestBiome>(AffectionLevel.Like) // Our Town NPC will like the Forest.
				.SetBiomeAffection<DesertBiome>(AffectionLevel.Dislike) // Our Town NPC will dislike the Desert.
				.SetBiomeAffection<UndergroundBiome>(AffectionLevel.Dislike) // Our Town NPC will hate the Underground/Caverns/Underworld.
				.SetBiomeAffection<DungeonBiome>(AffectionLevel.Love)

				.SetNPCAffection(NPCID.Guide, AffectionLevel.Love) // Our Town NPC loves living near the Guide.
				.SetNPCAffection(NPCID.PartyGirl, AffectionLevel.Like) // Our Town NPC likes living near the Party Girl.
				.SetNPCAffection(NPCID.BestiaryGirl, AffectionLevel.Like) // Our Town NPC also like living near the Zoologist. Use auto complete to find the NPCIDs.
				.SetNPCAffection(NPCID.DD2Bartender, AffectionLevel.Dislike) // Our Town NPC dislike living near the Tavernkeep. Use auto complete to find the NPCIDs.
				.SetNPCAffection(NPCID.GoblinTinkerer, AffectionLevel.Hate) // Our Town NPC hates living near the Goblin Tinkerer.
				// Use ModContent.NPCType<YourOtherTownNPC>() to set the affection towards the other Town NPCs in your mod.
				// All Town NPCs are automatically set to like the Princess.
			; // < Mind the semicolon!

			
			// This creates a "profile" for our Town NPC, 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")
			);
			
			// Advanced - ITownNPCProfile()
			// NPCProfile = new TutorialTownNPCProfile();
		}
		public override void SetDefaults() {
			NPC.townNPC = true; // Sets NPC to be a Town NPC
			NPC.friendly = true; // The NPC Will not attack player
			NPC.width = 18; // The width of the hitbox (hurtbox)
			NPC.height = 40; // The height of the hitbox (hurtbox)
			NPC.aiStyle = NPCAIStyleID.Passive; // Copies the AI of passive NPCs. This is AI Style 7.
			NPC.damage = 10; // This is the amount of damage the NPC will deal as contact damage. This is NOT the damage dealt by the Town NPC's attack.
			NPC.defense = 15; // All vanilla Town NPCs have a base defense of 15. This will increases as more bosses are defeated.
			NPC.lifeMax = 250; // All vanilla Town NPCs have 250 HP.
			NPC.HitSound = SoundID.NPCHit1; // The sound that is played when the NPC takes damage.
			NPC.DeathSound = SoundID.NPCDeath1; // The sound that is played with the NPC dies.
			NPC.knockBackResist = 0.5f; // All vanilla Town NPCs have 50% knockback resistances. Think of this more as knockback susceptibility. 1f = 0% resistance, 0f = 100% resistance.

			AnimationType = NPCID.Guide; // Sets the animation style to follow the animation of your chosen vanilla Town NPC.
		}

		public override void SetBestiary(BestiaryDatabase database, BestiaryEntry bestiaryEntry) {
			bestiaryEntry.Info.AddRange(new IBestiaryInfoElement[] {
				// The first line is for the background. Auto complete is recommended to see the available options.
				// Generally, this is set to the background of the biome that the Town NPC most loves/likes, but it is not automatic.
				BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Biomes.TheHallow,
				// Examples for how to modify the background
				// BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Visuals.Blizzard,
				// BestiaryDatabaseNPCsPopulator.CommonTags.SpawnConditions.Times.NightTime,

				// This line is for the description of the entry. We are accessing a localization key here.
				new FlavorTextBestiaryInfoElement("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Bestiary")
				// You can add several descriptions just by adding another new FlavorTextBestiaryInfoElement
			});
		}

		public override void HitEffect(NPC.HitInfo hitInfo) {
			// Create gore when the NPC is killed.
			// HitEffect() is called every time the NPC takes damage.
			// We need to check that the gore is not spawned on a server and that the NPC is actually dead.
			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. (8 total gores)
				// The way this is set up is that in the Gores folder, our gore sprites are named "TutorialTownNPC_Gore_[Shimmer]_[Party]_<BodyPart>".
				// For example, the normal head gore is called "TutorialTownNPC_Gore_Head".
				// The shimmered party head gore is called "TutorialTownNPC_Gore_Shimmer_Party_Head".
				// Your naming system does not need to match this, but it is convenient because this the following code will work for all of your Town NPCs.

				string shimmer = ""; // Create an empty string.
				string party = ""; // Create an empty string.
				if (NPC.IsShimmerVariant) {
					shimmer += "_Shimmer"; // If the Town NPC is shimmered, add "_Shimmer" to the file path.
				}
				if (NPC.altTexture == 1) {
					party += "_Party";  // If the Town NPC has a different texture for parties, add "_Party" to the file path.
				}
				int hatGore = NPC.GetPartyHatGore(); // Get the party hat gore for the party hat that the Town NPC is currently wearing.
				int headGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}{party}_Head").Type; // Find the correct gore.
				int armGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}_Arm").Type; // {Name} will be replaced with the class name of the Town NPC.
				int legGore = Mod.Find<ModGore>($"{Name}_Gore{shimmer}_Leg").Type; // {shimmer} and {party} will add the extra bits of the string if it exists.

				// 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); // Spawn the party hat gore if there is one.
				}
				Gore.NewGore(NPC.GetSource_Death(), NPC.position, NPC.velocity, headGore, 1f);
				// Remember, positive Y values go down.
				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) {

			// If our Town NPC hasn't been rescued, don't arrive.
			if (!TownNPCGuideWorld.rescuedTutorialTownNPC) {
				return false;
			}

			// Search through all of the players
			for (int k = 0; k < Main.maxPlayers; k++) {
				Player player = Main.player[k];
				// If the player is not active (disconnected/doesn't exist), continue to the next player.
				if (!player.active) {
					continue;
				}

				// Player has at least 100 maximum mana, return true.
				// statManaMax2 includes the "temporary" mana. This includes accessories/potions/etc. that give extra mana.
				if (player.statManaMax2 >= 100) {
					return true;
				}
			}
			return false;
		}

		public override bool CheckConditions(int left, int right, int top, int bottom) {
			// This code is very similar to how the Truffle checks if it is a surface mushroom biome. In this example, we are checking it is a surface Hallow biome instead.

			// If the bottom is below the surface height, return false. Remember, positive Y values go down.
			if (bottom > Main.worldSurface) {
				return false;
			}

			// We call this function to get the bounds of the "biome". We are going to count how many Hallow blocks are in the area and see if it is enough to count as a Hallow biome.
			WorldGen.Housing_GetTestedRoomBounds(out int startX, out int endX, out int startY, out int endY);
			int score = 0;
			for (int i = startX + 1; i < endX; i++) {
				for (int j = startY + 2; j < endY + 2; j++) {
					Tile tile = Main.tile[i, j];
					// If the tile we are searching is Hallowed Grass, Pearlstone, Pearlsand, Pearlsandstone, Hardened Pearlsand, or Pink Ice, increase the score.
					if (tile.HasTile && (tile.TileType == TileID.HallowedGrass || tile.TileType == TileID.Pearlstone || tile.TileType == TileID.Pearlsand || tile.TileType == TileID.HallowSandstone || tile.TileType == TileID.HallowHardenedSand || tile.TileType == TileID.HallowedIce)) {
						score++;
					}
				}
			}

			// If the score matches the threshold for the biome, return true. In this case, 125 tiles are needed to count as a Hallow biome.
			if (score >= SceneMetrics.HallowTileThreshold) {
				return true;
			}

			return false;
		}

		public override ITownNPCProfile TownNPCProfile() {
			return NPCProfile; // Set the Town NPC Profile to the profile we defined in SetStaticDefaults().
		}

		public override List<string> SetNPCNameList() {
			// Return a list of strings for our random name.
			return new List<string>() {
				"blushiemagic",
				"Chicken Bones",
				"jopojelly",
				"Jofairden",
				"Mirsario",
				"Solxan"
			};
		}

		public override string GetChat() {
			// WeightedRandom<string> is an easy and convenient way to add chat. We don't have to deal with making our own randomness or deal with large switch/if else statements.
			WeightedRandom<string> chat = new();

			// Here we have 4 standard messages that the Town NPC can say
			chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue1"));
			chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue2"));
			chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue3"));
			chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.StandardDialogue4"));
			
			// If the local player has a Terra Toilet in their inventory.
			// We can use Main.LocalPlayer here because GetChat() runs client side. So, the only player who can see the chat is the one chatting with the Town NPC.
			if (Main.LocalPlayer.HasItem(ItemID.TerraToilet)) {
				// This message is 2 times as common.
				chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.PlayerHasTerraToilet"), 2);
			}
			// If the local player is located in the desert.
			// As mentioned briefly before, only players know which biome they are in. Since the player is standing right next to the Town NPC to chat, checking the player is not a big deal.
			if (Main.LocalPlayer.ZoneDesert) {
				// This message is 2 times as rare.
				chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.DesertDialogue"), 0.5);
			}
			// We check that an Angler is present in the world. NPC.FindFirstNPC() returns the index of the NPC in Main.npc[]
			int angler = NPC.FindFirstNPC(NPCID.Angler);
			if (angler >= 0) { // If the Angler is not present, NPC.FindFirstNPC() will return -1.
				// We've set up our localization key value pair in such a way that {0} will be replaced with the name of the Angler.
				// You can use Main.npc[angler].FullName instead if you want it to say "Adam the Angler" instead of just "Adam".
				chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.AnglerDialogue", Main.npc[angler].GivenName));
			}
			// If Moon Lord has been defeated in this world.
			if (NPC.downedMoonlord) {
				// We've set this message up so that {0} will be replaced the name of the player and {1} will be replaced with the name of the world.
				chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.DownedMoonLord", Main.LocalPlayer.name, Main.worldName));
			}

			// Cross mod dialogue

			// First check if the mod is enabled
			if (ModLoader.TryGetMod("ExampleMod", out Mod exampleMod)) {
				// Next, try to find the other Town NPC.
				// Using TryFind<>() is safer than using Find<>()
				// If the NPC we are trying to find doesn't exist, our mod will continue to work.
				if (exampleMod.TryFind<ModNPC>("ExamplePerson", out ModNPC examplePersonModNPC)) {
					int examplePerson = NPC.FindFirstNPC(examplePersonModNPC.Type);
					if (examplePerson >= 0) {
						// FullName will give the given name and their "profession". For example: Blocky the Example Person
						// GivenName will give just the given name of the Town NPC: For example: Blocky
						chat.Add(Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.ExamplePerson", Main.npc[examplePerson].FullName), 2);
					}
				}
			}

			return chat;
		}

		public override void SetChatButtons(ref string button, ref string button2) {
			button = Language.GetTextValue("LegacyInterface.28"); // This will automatically be translated to say "Shop".
			
			// We can add a second button to our Town NPC by assigning button2. In this case, the second button will only appear during the day time.
			if (Main.dayTime) {
				button2 = Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.UI.SecondButton");
			}
		}

		public override void OnChatButtonClicked(bool firstButton, ref string shop) {
			// If the first button, the shop button, was clicked, open the shop.
			if (firstButton) {
				shop = Shop1;
			}
			// If the button that was clicked wasn't the first button, aka it was the second button, do something else.
			// In this case, we are setting the text to something else and opening the second shop.
			// (The Close or Happiness buttons are not considered here.)
			if (!firstButton) {
				// Main.npcChatText = Language.GetTextValue("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.SecondButtonChat");
				shop = Shop2;
			}
		}

		public override void AddShops() {
			// First, create our new shop.
			// The "Type" parameter assigns the shop to our Town NPC.
			// The "Shop1" parameter is a string that refers to which shop to add. We defined this variable at the beginning of our ModNPC.
			// Most Town NPCs only have one shop, but adding multiple shops is easy with this method.
			var npcShop = new NPCShop(Type, Shop1);

			// Here we set the first slot to an Iron Pickaxe.
			// By default, the prices set to the value of the item which is 5 times the sell price.
			npcShop.Add(ItemID.IronPickaxe);
			// Add an item to the next slot.
			npcShop.Add(ItemID.Starfury);
			// Here is how you would set a modded item from your mod.
			npcShop.Add(ModContent.ItemType<TutorialItem>());

			// If want to change the price of an item, you can set the shopCustomPrice value.
			// To do this, we need to create a new Item object with a constructor setting the shopCustomPrice variable.
			// 1 = 1 copper coin, 100 = 1 silver coin, 10000 = 1 gold coin, and 1000000 = 1 platinum coin.
			// In this example, our Stone Blocks will cost 10 copper.
			npcShop.Add(new Item(ItemID.StoneBlock) { shopCustomPrice = 10 });
			// An easier way to set the price is to use Item.buyPrice()
			npcShop.Add(new Item(ItemID.DirtBlock) { shopCustomPrice = Item.buyPrice(silver: 5) });

			// We can use the Condition class to change the availability of items.
			// We put the condition after our item separated by a comma.
			// Here, our item will be available after any Mechanical Boss has been defeated.
			npcShop.Add(ItemID.HallowedBar, Condition.DownedMechBossAny);
			// Here our item will be available during Full Moons and New Moons.
			npcShop.Add(ItemID.MoonCharm, Condition.MoonPhases04);
			// Here our item will be available in the Hallow biome AND while below the surface.
			// Adding multiple conditions will require that ALL conditions are met for the item to appear.
			npcShop.Add(ItemID.CrystalShard, Condition.InHallow, Condition.InBelowSurface);

			// We can create our own conditions if the default ones don't work for us.
			// For this example, we are saying the Boomstick is available if in the Jungle OR Queen Bee has been defeated.
			// Conditions take two arguments: a string and a Func<bool>

			// The string is the availability written in plain language.
			// In our localization file, we have "When in the Jungle or after Queen Bee has been defeated"

			// The Func<bool> is a fancy form of a boolean expression.
			// We can write any expression as long as it will be evaluate as a boolean expression.
			// We can use existing Conditions with IsMet() to get their boolean values.
			// This is convenient because those Conditions are already written and we know they are correct.
			// Alternately, we could write "() => Main.LocalPlayer.ZoneJungle || NPC.downedQueenBee" if we don't want to use the existing Conditions.
			npcShop.Add(ItemID.Boomstick, new Condition(Language.GetTextValue("Mods.TownNPCGuide.Conditions.JungleOrDownedQueenBee"), () => Condition.InJungle.IsMet() || Condition.DownedQueenBee.IsMet()));

			// If we are to use a custom condition that we wrote several times, it might be wise to create our own class.
			// That way we can just define the Condition once instead of every time we need it.
			// This condition is defined in a different class that we created called CustomCondtions.
			npcShop.Add(ItemID.BirdieRattle, CustomConditions.TravelingMerchantPresentOrHardmode);

			// You may be temped to use Main.rand to set the availability of your item, this may not do what you want it do to.
			// In this example, there is a 50% chance of our item being in the shop.
			// However, this will chance will run every time the shop is opened. So, players can just close and reopen the shop to roll the chance again.
			npcShop.Add(ItemID.BoneTorch, new Condition("Not recommended, this won't work", () => Main.rand.NextBool(2)));

			// Finally, we register or shop. If you forget this, nothing will shop up in your shop!
			npcShop.Register();

			// Advanced

			// We can use the chaining syntax on our shops too.
			// Notice how there are no semi colons until the end.
			var npcShop2 = new NPCShop(Type, Shop2)
				.Add(ItemID.Starfury)
				.Add(ItemID.TerraBlade, Condition.DownedPlantera, Condition.Eclipse)
				.Add(ItemID.RainbowRod, Condition.DownedMechBossAll)
				.Add(ItemID.Terragrim, Condition.PlayerCarriesItem(ItemID.EnchantedSword))
				.Add(ItemID.Megashark, Condition.NpcIsPresent(NPCID.ArmsDealer));

			// This will hide one of the items in the shop. It will throw an exception if the item is not found, though.
			npcShop2.GetEntry(ItemID.RainbowRod).Disable();

			// TryGetEntry will not throw an exception if it fails.
			if (npcShop2.TryGetEntry(ItemID.Megashark, out var entry)) {
				// We can add additional conditions.
				entry.AddCondition(Condition.DownedMechBossAny);
			}

			npcShop2.Add(ItemID.LightsBane, Condition.DownedEowOrBoc);
			// The Space Gun will be added to the shop before the Light's Bane
			npcShop2.InsertBefore(ItemID.LightsBane, ItemID.SpaceGun);
			// The Influx Waver will be added after the Terra Blade.
			npcShop2.InsertAfter(ItemID.TerraBlade, ItemID.InfluxWaver, Condition.DownedMartians);

			npcShop2.Add(ItemID.DD2BallistraTowerT1Popper, Condition.DownedOldOnesArmyAny);
			npcShop2.GetEntry(ItemID.DD2BallistraTowerT1Popper).ReserveSlot(); // We reserve this slot it always appear here.

			// Cross mod shop items

			// First check if the mod is enabled
			if (ModLoader.TryGetMod("ExampleMod", out Mod exampleMod)) {
				// Next, try to find the item.
				// Using TryFind<>() is safer than using Find<>()
				// If the item we are trying to find doesn't exist, our mod will continue to work.
				if (exampleMod.TryFind<ModItem>("ExampleJoustingLance", out ModItem exampleJoustingLance)) {
					npcShop2.Add(exampleJoustingLance.Type);
				}
			}

			// Actually, we don't even need to check to see if the mod is enabled.
			// We can specify the mod name when we try to find the item. This is still safe because the item won't get added if TryFind fails.
			if (ModContent.TryFind<ModItem>("ExampleMod/ExamplePaperAirplane", out ModItem examplePaperAirplane)) {
				npcShop2.Add(examplePaperAirplane.Type);
			}

			npcShop2.Register();
		}

		public override void ModifyActiveShop(string shopName, Item[] items) {
			// ModifyActiveShop() can modify the shop each time the shop is opened (as opposed to once during mod load).
			// This hook should mostly be used for modifying items already in the shop instead of adding new ones.

			// Here is an example using a foreach loop
			foreach (Item item in items) {
				// If the item in the list doesn't exist, continue to the next item.
				if (item is null)
				{
					continue;
				}
				// If the item is a Terragrim, set it's price to double it's value.
				if (item.type == ItemID.Terragrim) {
					item.shopCustomPrice = item.value * 2;
				}
				// If the item is a Starfury in the second shop, set its modifier to Legendary.
				if (item.type == ItemID.Starfury && shopName == NPCShopDatabase.GetShopName(Type, Shop2)) {
					item.Prefix(PrefixID.Legendary);
				}
				// If the player has the Discount Card equipped, then make the prices even cheaper for our Town NPC.
				if (Main.LocalPlayer.discountAvailable) {
					// We want to discount the shopCustomPrice number if it exists. If it doesn't, then we discount the value instead.
					// Changing the price like this is multiplicative with the discount from the Discount Card. (So 0.8f * 0.8f == 0.64f or 36% discount)
					item.shopCustomPrice = (int?)((item.shopCustomPrice ?? item.value) * 0.8f);
				}
			}

			// Here is an example using a for loop
			for (int i = 0; i < items.Length; i++) {
				// Here we find the first item that doesn't exist and set it to the Universal Pylon, then we break out of the loop to stop it.
				if (items[i] is null) {
					items[i] = new Item(ItemID.TeleportationPylonVictory);
					break;
				}
			}

			// Here is an example adding an item to the last slot of the shop.
			items[^1] = new Item(ItemID.PoopBlock) { shopCustomPrice = Item.buyPrice(platinum: 1) };
		}

		public override bool CanGoToStatue(bool toKingStatue) {
			// Can teleport to a King Statue
			return toKingStatue;
			// Return `!toKingStatue` for Queen Statues
			// Return `true` for both
		}

		public override void OnGoToStatue(bool toKingStatue) {
			// Display "Woah!" in chat. The message comes from our localization file.
			ChatHelper.BroadcastChatMessage(NetworkText.FromKey("Mods.TownNPCGuide.NPCs.TutorialTownNPC.Dialogue.OnTeleportToStatue"), Color.White);
		}

		public override bool UsesPartyHat() {
			// Is set to true by default. This is not needed unless you want to set it to false.
			return true;
		}

		public override void ModifyNPCLoot(NPCLoot npcLoot) {
			// Our Town NPC will drop an item only if it is named a specific name.
			// See the Basic NPC Drops and Loot guide for more information about NPC loot.
			LeadingConditionRule specificName = new LeadingConditionRule(new Conditions.NamedNPC("blushiemagic"));
			specificName.OnSuccess(ItemDropRule.Common(ModContent.ItemType<TutorialItem>()));
			npcLoot.Add(specificName);
		}

		public override int? PickEmote(Player closestPlayer, List<int> emoteList, WorldUIAnchor otherAnchor) {
			// Add some more emotes to the list.
			emoteList.Add(EmoteID.CritterBird);
			emoteList.Add(EmoteID.PartyPresent);

			// We can use if statements to change when an emote will be added.
			if (!Main.dayTime) {
				emoteList.Add(EmoteID.EmoteSleep);
			}

			// If the nearest player is in the snow biome, display a blizzard emote.
			// The NPC doesn't know what biome it is in -- it is all based on the player's biome.
			// The player is probably standing pretty close to the Town NPC so it doesn't matter that much.
			if (closestPlayer.ZoneSnow) {
				emoteList.Add(EmoteID.WeatherSnowstorm);
			}

			// Here this emote will only be added if our Town NPC is talking to another specific Town NPC.
			//   The `otherAnchor?.entity is NPC entityNPC` checks that the other thing that our Town NPC is
			//   trying to talk to actually exists and is an NPC. We assign a variable to that and then check
			//   if the NPC type is the Town NPC we want.
			if (otherAnchor?.entity is NPC entityNPC && entityNPC.type == NPCID.Guide) {
				emoteList.Add(EmoteID.EmotionLove);
			}

			// Here this emote will only be added if our Town NPC is talking to a player.
			// You might try to do something like `otherAnchor?.entity is Player` but that will never be true.
			// If the Town NPC is speaking to the player, the otherAnchor is never set, so we'll never know if it is for the player.
			// However, we can look at the NPC AI to find out that ai[0] == 7 and ai[0] == 19 is when the Town NPC will speak to the player.
			if (NPC.ai[0] == 7f || NPC.ai[0] == 19f) {
				// For this example, we clear the list so that it becomes empty.
				// We then add our ModEmote to the list so that is the only thing that our Town NPC can say. 
				emoteList.Clear();
				emoteList.Add(ModContent.EmoteBubbleType<TutorialTownNPCEmote>());
			}

			// Make sure you return base.PickEmote here. Otherwise you'll override all other NPCs and all other emotes.
			return base.PickEmote(closestPlayer, emoteList, otherAnchor);
		}

		public override void TownNPCAttackStrength(ref int damage, ref float knockback) {
			// The amount of base damage the attack will do.
			// This is NOT the same as NPC.damage (that is for contact damage).
			// Remember, the damage will increase as more bosses are defeated.
			damage = 20;
			// The amount of knockback the attack will deal.
			// This value does not scale like damage does.
			knockback = 4f;
		}

		public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) {
			// How long, in ticks, the Town NPC must wait before they can attack again.
			// The actual length will be: cooldown <= length < (cooldown + randExtraCooldown)
			cooldown = 30;
			randExtraCooldown = 30;
		}

		public override void TownNPCAttackProj(ref int projType, ref int attackDelay) {
			// For throwing, shooting, and magic attacks.

			
			// Throwing
			projType = ProjectileID.Shuriken; // Set the type of projectile the Town NPC will attack with.
			attackDelay = 10; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.
			

			/*
			// Shooting
			projType = ProjectileID.Bullet; // Set the type of projectile the Town NPC will attack with.
			attackDelay = 1; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.

			// If the world is Hardmode, change the projectile to something else.
			if (Main.hardMode) {
				projType = ProjectileID.CursedBullet;
			}
			*/

			/*
			// Magic
			projType = ProjectileID.MagicMissile; // Set the type of projectile the Town NPC will attack with.
			attackDelay = 1; // This is the amount of time, in ticks, before the projectile will actually be spawned after the attack animation has started.
			*/
		}

		public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) {
			// For throwing, shooting, and magic attacks.

			
			// Throwing
			multiplier = 12f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
			gravityCorrection = 2f; // This will affect how high the Town NPC will aim to correct for gravity.
			randomOffset = 1f; // This will affect the speed of the projectile (which also affects how accurate it will be).
			

			/*
			// Shooting
			multiplier = 16f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
			randomOffset = 0.1f; // This will affect the speed of the projectile (which also affects how accurate it will be).
			*/

			/*
			// Magic
			multiplier = 16f; // multiplier is similar to shootSpeed. It determines how fast the projectile will move.
			randomOffset = 5f; // This will affect the speed of the projectile (which also affects how accurate it will be).
			*/
		}

		public override void TownNPCAttackShoot(ref bool inBetweenShots) {
			// Only used for shooting attacks.

			// If this is true, it means that the Town NPC has already created a projectile and will continue to create projectiles as part of the same attack.
			// This is like how the Steampunker shoots a three round burst with her Clockwork Assault Rifle.
			// inBetweenShots = false;
		}

		public override void DrawTownAttackGun(ref Texture2D item, ref Rectangle itemFrame, ref float scale, ref int horizontalHoldoutOffset) {
			// Only used for shooting attacks.

			// Here is an example on how we would change which weapon is displayed. Omit this part if only want one weapon.
			// In Pre-Hardmode, display the first gun.
			if (!Main.hardMode) {
				// This hook takes a Texture2D instead of an int for the item. That means the weapon our Town NPC uses doesn't need to be an existing item.
				// But, that also means we need to load the texture ourselves. Luckily, GetItemDrawFrame() can do the work for us.
				// The first parameter is what you set as the item.
				// Then, there are two "out" parameters. We can use those out parameters.
				Main.GetItemDrawFrame(ItemID.FlintlockPistol, out Texture2D itemTexture, out Rectangle itemRectangle);

				// Set the item texture to the item texture.
				item = itemTexture;

				// This is the source rectangle for the texture that will be drawn.
				// In this case, it is just the entire bounds of the texture because it has only one frame.
				// You could change this if your texture has multiple frames to be animated.
				itemFrame = itemRectangle;

				scale = 1f; // How large the item is drawn.
				horizontalHoldoutOffset = 12; // How close it is drawn to the Town NPC. Adjust this if the item isn't in the Town NPC's hand.

				return; // Return early so the Hardmode code doesn't run.
			}

			// If the world is in Hardmode, change the item to something else.
			Main.GetItemDrawFrame(ItemID.VenusMagnum, out Texture2D itemTexture2, out Rectangle itemRectangle2);
			item = itemTexture2;
			itemFrame = itemRectangle2;
			scale = 0.75f;
			horizontalHoldoutOffset = 15;
		}

		public override void TownNPCAttackMagic(ref float auraLightMultiplier) {
			// Only used for magic attacks.

			// auraLightMultiplier = 1f; // How strong the light is from the magic attack. 1f is the default.
		}

		public override void TownNPCAttackSwing(ref int itemWidth, ref int itemHeight) {
			// Only used for melee attacks.

			/*
			// This is the hitbox of the melee swing. It recommended to set this to the resolution of the sprite you want to use.
			// Below, we've set the Exotic Scimitar as the weapon which has a resolution of 40x48.
			itemWidth = 40;
			itemHeight = 48;
			*/
		}

		public override void DrawTownAttackSwing(ref Texture2D item, ref Rectangle itemFrame, ref int itemSize, ref float scale, ref Vector2 offset) {
			// Only used for melee attacks.

			/*
			// This hook takes a Texture2D instead of an int for the item. That means the weapon our Town NPC uses doesn't need to be an existing item.
			// But, that also means we need to load the texture ourselves. Luckily, GetItemDrawFrame() can do the work for us.
			// The first parameter is what you set as the item.
			// Then, there are two "out" parameters. We can use those out parameters.
			Main.GetItemDrawFrame(ItemID.DyeTradersScimitar, out Texture2D itemTexture, out Rectangle itemRectangle);

			// To retrieve a sprite from your mod.
			// Texture2D itemTexture2 = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/SwordWithNoItem").Value;

			// Set the item texture to the item texture.
			item = itemTexture;

			// This is the source rectangle for the texture that will be drawn.
			// In this case, it is just the entire bounds of the texture because it has only one frame.
			// You could change this if your texture has multiple frames to be animated.
			itemFrame = itemRectangle;

			// Set the size of the item to the size of one of the dimensions. This will always be a square, but it doesn't matter that much.
			// itemSize is only used to determine how far into the swing animation it should be.
			itemSize = itemRectangle.Width;

			// The scale affects how far the arc of the swing is from the Town NPC.
			// This is not how large the item will be drawn on the screen.
			// A scale of 0 will draw the swing directly on the Town NPC.
			// We set it to 0.15f so it the arc is slightly in front of the Town NPC.
			scale = 0.15f;

			// offset will change the position of the item.
			// Change this to match with the location of the Town NPC's hand.
			// Remember, positive Y values go down.
			offset = new Vector2(0, 12f);

			// It is also recommended to change the following sets in SetStaticDefaults() to better match the behavior of melee attacks.
			// NPCID.Sets.DangerDetectRange[Type] = 60; // The amount of pixels away from the center of the NPC that it tries to attack enemies.
			// NPCID.Sets.AttackType[Type] = 3; // The type of attack the Town NPC performs. 0 = throwing, 1 = shooting, 2 = magic, 3 = melee
			// NPCID.Sets.AttackTime[Type] = 15; // The amount of time it takes for the NPC's attack animation to be over once it starts. Measured in ticks.
			// NPCID.Sets.AttackAverageChance[Type] = 1; // The denominator for the chance for a Town NPC to attack. Lower numbers make the Town NPC appear more aggressive.

			// And then change the numbers in TownNPCAttackCooldown()
			// cooldown = 12;
			// randExtraCooldown = 6;
			*/
		}

		public override void PostAI() {
			// Main.NewText("NPC.ai[0] " + NPC.ai[0] + " NPC.ai[1] " + NPC.ai[1] + " NPC.ai[2] " + NPC.ai[2] + " NPC.ai[3] " + NPC.ai[3]);
			// Main.NewText("NPC.localAI[0] " + NPC.localAI[0] + " NPC.localAI[1] " + NPC.localAI[1] + " NPC.localAI[2] " + NPC.localAI[2] + " NPC.localAI[3] " + NPC.localAI[3]);

			// NPC.ai[0] = current action
			//	0 == no action
			//  1 == walking
			//  2 == 
			//  3-4 == chatting
			//  5 == sitting
			//  7 == Talking to player
			//  8 == Decide to run from enemy? Immediately set to 1.
			//  9 == Go sit in a chair?
			//  10 == attacking
			//  12-15 == attacking related
			//	16 == player rock paper scissors
			//  19 == Talking to player (responding to the player)
			//  24 == magic attack aura
			//  25 == change to a different action

			// NPC.ai[1] = time before changing actions
			//  299 when talking to a player

			// NPC.ai[2] = The player index that the Town NPC is speaking to? (Player.whoAmI) 
			//  Reset to 0 most of the time

			// NPC.ai[3] =
			//  Used by the Old Man

			// NPC.localAI[0] is open
			//  This is used by the Mechanic to know to stay still while her boomerang is returning to her.
			// NPC.localAI[1] = Attacking cooldown
			//  0 most of the time
			// NPC.localAI[2] =
			//  1 most of the time
			// NPC.localAI[3] = Attacking animation time
			//  -1 or 0 most of the time when not attacking
			//  -99 when talking to a player

			// If just started to attack.
			if (NPC.ai[0] == 10 && NPC.localAI[3] == 1) {
				EmoteBubble.NewBubble(EmoteID.ItemSword, new WorldUIAnchor(NPC), 120); // Display an emote above their head.
			}
			// Standing still about to do something else and very hurt.
			if (NPC.ai[0] == 0 && NPC.ai[1] == 10 && NPC.life < NPC.lifeMax * 0.25f) {
				EmoteBubble.NewBubble(EmoteID.ItemTombstone, new WorldUIAnchor(NPC), 120); // Display an emote above their head.
			}
			// If talking to the player.
			if (NPC.ai[0] == 7 || NPC.ai[0] == 19) {
				NPC.AddBuff(BuffID.Lovestruck, 2); // Give them a buff.
			}
		}

		// Load our textures
		private readonly Asset<Texture2D> glowMask = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_GlowMask");
		private readonly Asset<Texture2D> glowMaskAttacking = ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_GlowMaskAttacking");
		public override void PostDraw(SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) {
			// Flip the glow mask to match which direction the Town NPC is facing.
			SpriteEffects spriteEffects = NPC.spriteDirection > 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
			// Draw our glow mask in full bright.
			Color color = Color.White;

			// Move the position up by 4 pixels plus the gfxOffY value (that is for climbing half blocks).
			// Main.NPCAddHeight() makes it so if the Town NPC is sitting, it also moves the glow mask up by 4 more pixels.
			Vector2 verticalOffset = new(0, -4 + NPC.gfxOffY + Main.NPCAddHeight(NPC));

			// Draw our glow mask
			spriteBatch.Draw(glowMask.Value, NPC.Center - screenPos + verticalOffset, NPC.frame, color, NPC.rotation, NPC.frame.Size() / 2f, NPC.scale, spriteEffects, 1f);

			// Only draw our extra attacking glow mask while attacking, which are frames 21+
			if (NPC.frame.Y > 20 * NPC.frame.Height) { 
				spriteBatch.Draw(glowMaskAttacking.Value, NPC.Center - screenPos + verticalOffset, NPC.frame, color, NPC.rotation, NPC.frame.Size() / 2f, NPC.scale, spriteEffects, 1f);
			}
		}
	}

	// Defining our own Town NPC Profile gives us a little more freedom, but is rarely needed.
	public class TutorialTownNPCProfile : ITownNPCProfile {
		public int RollVariation() => 0;

		// If your Town NPC has no random names, return null here.
		public string GetNameForVariant(NPC npc) => npc.getNewNPCName();

		public Asset<Texture2D> GetTextureNPCShouldUse(NPC npc) {
			// Here we can give our Town NPC a bunch of different textures if we wanted.
			// For example, we can make the texture in the Bestiary different.
			if (npc.IsABestiaryIconDummy && !npc.ForcePartyHatOn) {
				return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/Advanced/TutorialTownNPC_BestiaryExample");
			}

			// Shimmered and party
			if (npc.IsShimmerVariant && npc.altTexture == 1) {
				return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Shimmer_Party");
			}
			// Shimmered and no party
			if (npc.IsShimmerVariant && npc.altTexture != 1) {
				return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Shimmer");
			}
			// Not shimmered and party
			if (!npc.IsShimmerVariant && npc.altTexture == 1) {
				return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Party");
			}
			// Not shimmered and no party
			return ModContent.Request<Texture2D>("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC");
		}

		public int GetHeadTextureIndex(NPC npc) {
			if (npc.IsShimmerVariant) {
				return TutorialTownNPC.ShimmerHeadIndex;
			}
			return ModContent.GetModHeadSlot("TownNPCGuide/Content/NPCs/TownNPCs/TutorialTownNPC_Head");
		}

		// In our Town NPC's class, we'll need to change a few things:
		// internal static int ShimmerHeadIndex; // Change private to internal so we can access it here.
		// private static ITownNPCProfile NPCProfile; // Change Profiles.StackedNPCProfile to ITownNPCProfile.

		// In SetStaticDefaults(), change
		//	NPCProfile = new Profiles.StackedNPCProfile(
		//		new Profiles.DefaultNPCProfile(Texture, NPCHeadLoader.GetHeadSlot(HeadTexture), Texture + "_Party"),
		//		new Profiles.DefaultNPCProfile(Texture + "_Shimmer", ShimmerHeadIndex, Texture + "_Shimmer_Party")
		//	);
		// To:
		// NPCProfile = new TutorialTownNPCProfile();
	}
}

Conclusion

As you can see, there can be a lot that goes into creating a Town NPC. This guide has hopefully made the process much easier. Since Town NPCs are just a specialized NPC, there is even more that could be customized with them. You could create your own AI and drawing frames. You could create a quest system for the player to complete. That's for you to explore, though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment