Skip to content
Rijam edited this page Mar 26, 2024 · 1 revision

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. We place this line outside of any override hooks, but still inside of our class. This line in context

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.



Clone this wiki locally