Skip to content

Too much like the real Terraria source code. #2

@PoroCYon

Description

@PoroCYon

I was a Terraria modder and a member of the tConfig and tAPI dev teams. (I also made another modding API for 1.3, but it didn't take off.), and I can say you didn't only try to clone the game, but also the code quality of Terraria:

Blocks and items, among many other things, are represented by magic numbers instead of enums.

Terraria does the exact same thing.

There are 417 lines of block comments at the top of TerraFrame.java that serve as manual translation tables.

We (Terraria modders) had no such thing, so the table must be made manually (or rather, we used some code to generate it). Some methods exist (such as Item.SetDefaults) to give an Item's fields (not properties, but raw fields) the desired values. (There are no subclasses or anything, and no Item/... definition lookup tables/... .) It is an infinite sea of if (type == foo) { init(); } .... Note that it uses if without else or return, drastically slowing it down, but it got changed to a switch in 1.2. (More on SetDefaults later.) In 1.2, a Terraria.ID namespace was added containing (static) classes containing constants for easy Item/... -> numerical ID lookup as well.

Terraria entities also have a netID, which slightly differs from their type: entities with the same type have the same texture, but they can have differing netIDs. The latter defines the actual properties (max life, damage, ...). Thus, separate netDefaults methods exist as well...

So as to avoid needing to declare local variables and loop indices, all of the variables for everything are declared globally at the class level. Here is one of the several hundred lines of declarations in TerraFrame.java

Terraria didn't do this, but the compiler mangled the variable names enough, and the combination with huge methods and the usage of next to no non-primitive types resulted in lots of occurrences of locals called num256 etc. (Yes, methods with >100 locals exist.)

Although there are a few other classes, the bulk of the code is in the God Class TerraFrame, which spans over 6,500 lines of code.

This is nothing compared to Main, spanning several 10 000s of lines of code. It containts the global game state, global loading/unloading, >75% of all the rendering code, sound handling, ... Item, NPC, Player, Projectile, WorldGen ... are huge as well.

The TerraFrame.init() method, which is over 1,300 lines long, actually grew so large that the Java compiler started running out of memory trying to compile it! The solution? Copy half of the init() code into a new method, called codeTooLarge(), and call that from init().

The compiler manages to live with extremely huge methods, but the decompiler chokes on them. (I don't know anything about the compilation of the original source code side of things, but some badness might happen there as well.) Item.SetDefaults is one of them, and it is split in 4 (!) separate methods, called SetDefaults1 through 4, plus the netDefaults one. (This was done by the dev team. If this wasn't the case, we (the modders) would have to fix the binary using Mono.Cecil or dnlib and edit the raw MSIL (the C# equivalent of Java bytecode) programmatically...). NPC.AI, WorldGen.generate, Player.itemCheck are insanely large as well. As of 1.3, Terraria explicitely invokes the JIT compiler on startup to make it compile everything at once, so no slowdown occurs during gameplay due to the JIT. (This does happen, however, when loading textures ingame...)

FYI, decompilation alone takes hours while eating all of your CPU and RAM.

Frankly horrifying inline data tables

Those were all over the place, especially for tiles (which didn't even have a SetDefaults method). This only got worse in 1.2.

Over 1,000 lines of filling globally declared HashMaps and ArrayLists with magic numbers and strings one by one.

Map data, localised strings, ... are all managed this way, but Recipe.FillRecipes (or something like that, my memory is a bit hazy) is the absolute worst in this regard.

The control flow is so labyrinthine that some of the code is actually indented by 23 tabs.

This is commonplace. NPC.AI, Player.ItemCheck, Projectile.Update, WorldGen.generate, to name a few.

There are random print statements scattered throughout the codebase, with helpful messages like [DEBUG2R] and [DEBUG2A].

There was zero debugging information in the Terraria binary... except for something that logged every single keypress to the console, annoying everyone who used Console.WriteLine for debugging mods.

Why use a pre-existing GUI framework for your text boxes when you can easily roll your own?

There is no (official) GUI framework for XNA (although third-party libs exist), and Terraria uses an "imgui", but without any sense of structure. It simply uses raw SpriteBatch.Draw calls etc, the current 'page' is also identified by a magic number. This got better in 1.2.3 (iirc, but it could as well be 1.2.0 or 1.3.0), though. Ingame inventory/... rendering is even worse.

I could go on and on and on and on about this. See this or this (NOTE: I worked on this.) to get an idea of the hacks required to make modding possible, or grab a decompiler and let it run overnight to witness the horror yourself. (NOTE: I advise against all this if you still want to live a happy and carefree life.)


Sorry for giving you a braindump, but I need emotional relief. Working with this codebase gave me a PTSD of some sort.

(NOTE: I only had access to the decompiled source code, but MSIL->C# translation is quite accurate, so I can safely say that this applies to the original source code as well.)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions