Assorted Additions is a "medium" sized content mod for Terraria that adds all sorts of things to the game like the name implies. In its current state the mod adds a little over 200 new items (from basic blocks to more complex weapons) for the player to discover. The mod also adds 15 new enemies and two bosses to the game along with a bunch of tweaks. For example, the mod adds a new category of "accessory" to the game called runes and improves the loot the player can find in underground mini biome chests.
I was doing a modded playthrough of Terraria with some friends and at some point we were discussing the question "How hard could making a mod be" probably inspired by some bug we had found. A few moments later my friend had jokingly modded in an artillery cannon, albeit a very scuffed one. After this I also decided to give modding a go.
It turns out that getting started on modding is very easy even with zero knowledge of C# or modding in general. To be fair, I did have some experience in Java through my studies so the C# syntax was easy to understand and I had general knowledge on programming concepts. On top of this tModLoader, the modding API for Terraria, provides plenty of examples and documentation. In no time I had added a basic steel sword to the game. After that I kept adding new things as new ideas came up.
It did take a ton of time and effort to start actually making things that were decent. On top of having to learn the modding API I had to learn how to make sprites. I did struggle a lot with making sprites and still do. Coming up with a visual idea and actually implementing it is very time consuming. To answer the question of "how hard could making a mod be" I would say that it depends. It is as hard and complex as the thing you are trying to implement. A basic sword can be added in minutes, an unique and complex boss can take weeks (taken you have the neccessary knowledge already).
The game world of Terraria is made out of different blocks which are called Tiles. These are different from the items that they drop and that are used to place them down. I wanted to add an item to the game that gives a chance to double ore drops when the player mines an ore while wearing it. Sounds simple right? Nope.
Problem #1: It turns out that the tiles do not know which item they drop and that logic is instead handled inside a massive switch statement. Modded tiles do know their drops, but any vanilla tiles don't
Problem #2: Tiles don't know which player breaks it. Problematic because ores should only double drop if the player breaking it is wearing the item.
The solution? I had to detour a vanilla method that runs whenever a player hits a tile. Luckily tModLoader provided an API for detouring some vanilla methods so that was easy. I also defined the vanilla ore tiles and their drops in a dictionary so they could be obtained when checking what type of tile was being hit. The below snippet is just the detour implementation, the rest can be found here
private static void CanDoubleOreDrop(On_Player.orig_PickTile orig, Player self, int x, int y, int pickPower) {
Tile tile = Main.tile[x, y]; // What tile is being hit
ushort type = tile.TileType; // Store it and its type for later use
// If wearing the rune, the tile is an ore and the double drop chance returns true
if (self.GetModPlayer().isWearingRuneOfProsperity && TileID.Sets.Ore[tile.TileType] && Main.rand.NextBool(7)) {
// Check if it's a mod tile and gets its drops if it is (needed later)
ModTile modTile = TileLoader.GetTile(type);
IEnumerable- modTileDrops = modTile?.GetItemDrops(x, y);
// Checked and stored here before the orig call since the tile wont be at the coords after breaking
// retrieving the drops later would not work
orig(self, x, y, pickPower); // Call orig (vanilla PickTile will run meaning the tile gets hit)
// Check if tile broke and if it did, do the double drop
if (!tile.HasTile) {
// If the tile was not modded, get the vanilla item drop from the dictionary
if (modTile == null && vanillaOreTileDrops.TryGetValue(type, out int itemDrop)) {
self.QuickSpawnItem(WorldGen.GetItemSource_FromTileBreak(x, y), itemDrop, 1);
}
// If it was modded, get the drops from modTileDrops (safety null check)
else if(modTileDrops != null) {
foreach (var modDrop in modTileDrops) {
self.QuickSpawnItem(WorldGen.GetItemSource_FromTileBreak(x, y), modDrop.type, 1);
}
}
}
}
else { // Conditions not met, just run vanilla method
orig(self, x, y, pickPower);
}
}
This is not the most optimal solution, but it works. The code does nothing to prevent players from placing down tiles and breaking them again to easily duplicate ores. To prevent that I would have had to implement a way to track tiles placed by the player (yup, this is not tracked either). This has been done before by others, but I decided it was not worth the effort. The code also runs every time the player hits a tile at least until the first if check. I should probably have stored the last ore tile type and its drop and checked if the next tile is the same to reduce the amount of code run each time.