Skip to content

Attacking

Rijam edited this page Mar 26, 2024 · 1 revision

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.



Clone this wiki locally