It all started in 2016 when I picked up the hit farming simulator Stardew Valley (SDV), a game developed solely by Eric Barone. After many hours of gameplay (but I swear it only felt like an hour), I became frustrated with the lack of a feature to locate NPCs in the game. And after a brief search for mods (which are user-created plugins to add modifications or additional features to the game), I was unable to find any that would serve my purpose. So on a whim, I just decided to try making it.
The challenges I faced before starting this project was that it was written in a language I didn't know, and that there was little to no documentation on the modding framework (it was still in development and very new at the time). At this point I had some programming experience with just a few Java courses and a couple of tiny side-projects under my belt. Learning C# was a relatively smooth transition from Java. But without any documentation on how to make a Stardew Valley mod, I had to rely on studying the few existing mods created by others. This led to me creating some of the most atrocious baggage of code I'd ever been responsible for, Frankenstein-ing a mod by stitching together various lines of code from many different mods and solutions from stackoverflow. But it worked, and that was more than I'd ever expect working in an uncharted territory without any maps or guides.
In that manner, NPC Map Locations was born. It was a mod designed to do exactly what I wanted--to locate the villagers on the game map UI. It was something a lot of users wanted, so it gained traction and I was privileged with a lot of wonderful support. This would become my primary driving force to keep on developing the mod to what it is today (and it's waaaay better now).
This section details the development process over roughly 3 years. It is more anecdotal and can be skipped for the next section where I discuss what I learned from this project.
NPC Map Locations 1.0 was very modest, with no additional features apart from showing NPCs in their 'general' locations (general because the in-game map is actually a stylized representation of the game world, and not to scale at all). Without going too much into detail, it was a very tedious process that involved somewhat manually placing the NPC on the map based on their position in the current location. A lot of busywork, not much complexity. Then as it gained traction, I had a handful of users requesting features for conditionally showing NPCs on the map. I was excited with the attention I was receiving and happily implemented every feature upon request.
Little by little the mod's complexity grew, which forced me to adapt and find new ways to figure out how to implement things when the few existing mod examples were not sufficient. The logical thing to do was ask these mod authors how they figured things out which led me to the decompiled game code to see what classes and methods and variables were available. A lot of these were accessible only through the modding framework (which was still in beta at the time), so there were some restrictions until the framework was updated to allow the access and modification of more game data. But now, I theoretically had everything I needed to do anything I wanted?
As my mod improved and the game grew more popular, I steadily gained more and more users. There was much less cushy discussion about adding new features and much more bug reports since the many more users would discover bugs at a faster rate. This was very beneficial to the progress of development and it was nice being able to rely on users for testing. I also began to take the mod very seriously, and adopted a perfectionist approach to everything. I would implement an in-game menu to allow the user to change the various mod settings, add more useful info and additional UI components, lots of improvements to quality of life, etc. The end goal for me was to create something professional and seamless. This included lots of little features that most users would take for granted and wouldn't notice they were there unless they were gone.
This was way easier said than done. Back then I had no idea how to even get started on any of these things, yet... As I just powered through and did a lot of thinking, I was eventually able to find a way one way or the other. It was important to break the problem down into smaller parts, figure out how to do each smaller part, and work my way up. In this way I slowly added on to the mod, learning as I go, and it became something truly impressive in several ways.
The mod had become impressively spoiled. Its code was irresponsible, putting unnecessary amounts of the burden on user. It was a disastrous mess for the future me, creating tech debt upon tech debt with short-sighted solutions. It was painfully amateurish in its implementation and was anything but elegant. All my bad practices had finally caught up to me as I had naively turned a blind eye to them.
Almost 1,000 lines of code in a single file. Long chains of if-statements that needed its own scrollbars. No comments or documentation to be found. In the distance, a scream.
To recover, I went on a crusade to refactor the entire code-base. In the process, I even ended up reworking the whole mod for the sake of switching to a new paradigm that was much smarter and much less tedious. So I basically rewrote the whole thing, and damn it felt good. I had redeemed myself.
Writing code that works, now and in the future.
PC games nowadays are iterative, meaning you can expect many updates even after its release. And usually whenever Stardew Valley was updated, some of its mods broke. As a newbie, I lacked this foresight and hard-coded a lot of things so whenever certain numbers, strings, or objects were changed, all those hard-coded values would have to be changed. It was very important to seek 'single source of truth' in the decompiled game code, or to use a single variable to refer to all of those same values.
Writing code that works for everyone.
Well sort of, it's impossible to accommodate for everyone. Since my mod is being used by many users with varying PC specifications, I had to be more mindful of code runs well. This means no nested loops that are running 60 times a second (woops!). Once I had a better understanding of the game loop and the update and render cycle, I would have to re-define my methods so that they performed only tasks that were necessary only at necessary times. I was not able to notice during testing because my gaming PC was able to handle the load well.
I also had the opportunity to experience the woes of cross-platform compatibility and localization for other languages. Luckily there were tools available to make this a no-brainer (but still more work) but it was something I previously had never thought about. This whole modding experience was really forcing me to adapt and evolve.
The man working on the modern modding framework (he goes by Pathoschild and he's the best person ever) did an amazing job making all this work out of the box now. Plus there's also tons of documentation now.
Writing code that is compatible with other mods.
Since every user had some different setup with different mods installed, I had to test for compatibility with a bunch of other mods that users would use and add support for them. This would be never-ending process as there are only so many mods I could test, but every step took me towards a mod that generally works with anything and doesn't crash. I would add editable configurations for the mod so the user can resolve any mod conflicts. I could also no longer directly modify parts of the game that other mods could depend on, instead I would have to come up with way around it, usually by cloning game objects and tweaking them for my needs.
Writing code that can be understood by humans.
I had my code on GitHub, which to me at the time just meant I can fetch my code from any device, and that only I would see it. But now that people other than myself cared about this project, I could be shamed and judged for my Franken-code (and deservedly so). I also had no understanding of open-source but it might as well have been closed-source with the amount of involuntary obfuscation that was present in the code.
And it wasn't after a 1-year hiatus on the project, during which I was able to develop my skills in a more formal learning environment at work, that I was able to discern how badly my code was written, and was able to go back and refactor it. And now my approach to writing code in general has been to neatly organize it and document it as if other people would see it as well. There's no reason not to, and it will always make it easier for future-me.
When I didn't know how to do something, I simply googled it. But just copy-and-pasting the first stackoverflow solution usually lead to problems down the road when I'm trying to track down a bug and don't understand code that I hadn't written. So to really understand, I would go into reading documentations for C#, for the game engine Stardew Valley was built on, and for the modding framework if there were any at the time. So basically, I had to brush-up on my google-fu, and to not stop at stackoverflow solutions.
It's easy to get caught in a false sense of understanding by watching video tutorials or just reading guides. Even if I thought it made sense, unless I actually practice and applied what I just consumed, I'd forget everything. Since I was applying them as I picked them up, I was able to avoid this issue. I can't stress learning by doing enough.
While diving in head first may have helped me when getting started, after some experience I found it much more efficient to plan it out in writing (whiteboards are awesome), broken down into the many parts I'd have to implement to get it working. Then I would build it piece by piece. After implementation came actually testing. I'd never written a test before so I can't say my testing was efficient, but at least I learned that when testing and tracking down bugs, utilizing breakpoints is absolutely necessary if I wanted to get anything done.
Starting is often the hardest part. Start small and just work on it without thinking too much about implementation details, just to get the wheels rolling. I didn't try to understand everything from the start, I just focused on making what I wanted to make. Even if it's extremely dirty, I can always go back and clean it up (but don't wait too long).
I went from knowing just if-statements, loops, and basic data structures to... Well, all of these things I learned from it. Granted, you could build everything knowing just those things. But maybe that's the confidence everyone needs. That they are already equipped with the knowledge to build anything, no matter how daunting. Or at the very least, equipped with the knowledge to gain more knowledge in this endless information age.
I'm certainly convinced that working on side projects is the best way to learn. The first thing that comes to mind when I learn something new is usually 'how can I apply this to a project'. And it can be fun and not purely educational, you have all the creative liberty. Just keep working on those side projects and make it 'til you make it.