Phew! I can already feel a whole lot lighter now that v0.5 is out! In fact, I feel so light that I decided to use this time to significantly refactor some of the game's oldest code from when CoG was in its earliest stages!
That's right, this month's topic of discussion is the internal workings of the project. The last time I've gone this into detail about the game's inner systems was over a year ago in the very short-lived "How's it made?" posts, of which I did one for art and one for the technical aspect.
... and you'll notice the technical post actually barely goes into how the game is actually built, it's actually more of an introduction than anything!
And since refactoring old code is the primary work I've been doing, why not tackle it for this month's development log? This is easily one of the most technical development logs so far. There will be a whole lot of text, and very little art or creative discussion, so buckle up!
To start things off, let's explain the fancy words. For those who aren't familiar, refactoring in software development refers to the act of revisiting existing code and rewriting it so that it still works exactly the same, but is designed in a better way that allows you to work with it more easily.
This is important because it'll keep the doors open in the future for when you need to make changes or additions, and it typically also makes troubleshooting and bug fixing much easier.
A lot of what I've been doing the past two weeks has been completely revamping some of the awful code design, which I first employed because I didn't expect the game to grow like it did! I've been reaching into some of the oldest, first ever written code for the game!
First up, let's talk about how the character models work in the game. In the How's it Made for art, I actually talked about how the folder structure of the various sprites are made, so allow me to reiterate. Each character model consists of 6 main parts.
Body: the base of the entire model
Clothing: so that I can put them all in silly little outfits
Eyebrows: to change their expression
Eyes: so they can blink and also to change their expression
Mouth: so I can change their expression
FaceEffects: for things like sweating and blushing
Notice how the eyebrows, eyes and mouth are all separated? This means I can mix and match various eyebrow and mouth sprites instead of just having one single sprite for representing each facial expression.
Every time I draw the model for a new character, I give all of them three base expressions: neutral, concerned, horrified. These actually correspond to the faces they make during battle as the match progresses and gets closer to the end.

(Character expressions: neutral, concerned, horrified.)
Now the next part is important: the eyebrow and mouth both have a sprite for each of these expression sand the file is named in the exact same way. For example:
For eyebrows, the neutral expression is in a folder called "Eyebrows" and named "neutral.png"
For mouths, the neutral expression is in a folder called "Mouth" and named "neutral.png"
The code is written in a way that: if I want to set the expression to neutral, concerned or horrified, I don't have to set the eyebrows or mouth separately, I just tell it to use the "neutral" expression, and it will take care of both of the facial parts.
... and why am I bringing this up? Take a look at this:

(Player expressions: neutral, concerned, horrified.)
These are the expressions for the Player character. As you can see, it also uses the same three base expressions: neutral, concerned and horrified.
... now look at the way those files are named. It's a goddamn mess!
The eyebrows for neutral is named "default.png" instead..? I do remember very early at the beginning when the neutral expressions were called "default", but I later decided to change it to neutral as it was more descriptive.
... clearly I didn't care enough to update it for the Player Model, instead only doing it for NPC Models.
The concerned expression naming convention is completely non-sensical. Eyebrows are "Eyebrows/oneraised.png"? Mouth uses some "Mouth/distressed.png"?
This means that instead of just looking for mouth and eyebrow sprites both named "concerned.png", the Player Model had to manually make sure it searched "Mouth/distressed.png" and "Eyebrows/oneraised.png"
The horrified expression is the worst of these. The mouth is, again, named some random "Mouth/scared.png"
... but look closer and: the horrified eyebrow uses some sort of "Eyebrow/concerned2.png"?
What? Concerned doesn't use a sprite called "concerned.png", but for some reason horrified uses "concerned2.png" Where did it the first one go?
Well the answer to that lies in the following:

There's... a fourth extra expression that the Player Model uses during battles, that occurs in between neutral and concerned. It's called "huh". What an apt name for the mess that this is. Two things:
The huh expression is the one to actually use the "Eyebrow/concerned.png" for some reason.
And... that's all it does. It doesn't change the mouth at all. Just the eyebrow.
But it doesn't end there, the player model actually needs to also have a separate layer for the inside of the mouth, that is for the mouth sprites where it's open. This is because the outline of the mouth needs to get tinted with the player's skin tone, but the teeth and fleshy insides most definitely do not.
... so how did I name these teeth layers?

distressed_teeth.png
scared_back.png
W-what... what in the world? Why do they both use different wording!? The hell was I doing? Truly all over the place...
Ok, so Player Model needed its own unique code unlike NPC Models to deal with this whole mess, but it was going to need that anyways, actually. There's some extra work required in the Player Model due to the character customization, which means the code also needs to pull the correct eyebrow, mouth and eye base sprite was selected at character creation.
... but the problem is: until now the Player Model wasn't even considered to be the same as NPC Models. Internally, the Player Model was seen as its own different thing, which meant it sometimes couldn't even be used in places where other NPC models were used. This meant that things like the Mode Viewer perk had to always do things like check if the model was an NPC Model or Player Model, and do different code depending on which it was.
And that... everybody, is the mess that is CoG code. In conclusion: lots of folder and file reorganization was done, on top of rewriting a ton of Model display code so that NPC Models and Player Model actually use the same code.
There was actually another issue with how the escalating battle expressions were tied to and updated by the model itself.
As I said earlier, the models escalate from looking neutral, to concerned, and finally: horrified, as the battle goes on. But instead of this being called by the battle... I just had it tied to the character models themselves, and they updated the expression all on their own whenever they updated the Wgt sprite! Here's the problem with that:
This meant the character models automatically changed their expressions when spawned in, even in places like dialogue, when they're merely supposed to just appear on screen.
To fix this, I added a little switch to the model updating function that told it whether to automatically change the expression or not.
The problem with that is... the model updating function is already a little cramped and the whole point is that the battle escalation code really isn't supposed to be here in the first place.
... so the solution is clear, right? I just needed to move that code over to something used in the battle code. And for that I already had the perfect structure: Battle Flavor Text.
Throughout battles, characters will let out various little quips and reactions as the match progresses. The way it works is that: whenever the Battle manager registers a Wgt change, it notifies something called the TextBase, which then displays the appropriate text, and sometimes even plays sound effects.
Each character has their own TextBase that reacts at various different times with different text, so all the Battle manager needs to do is load in the corresponding TextBase of a character, and then let it do all the work.
... this means it also happens to be the perfect location to house the new escalating expression code! Not just that, it also meant I could customize each character individually, to use expressions other than just neutral, concerned and horrified in that order, and it also means I can trigger them at times other than 1/4 through the battle... halfway through the battle... etc.
You may have also noticed that characters smile at the beginning of a battle. That's actually the full cycle during a battle: smile -> neutral -> concerned -> horrified. However, them smiling does not make sense in a few of the scenarios: most notably the Carver battle during MQ3, where you basically ambush him while he's trying to run away.
And so when that originally came out, I basically tacked on some code as a temporary fix that overwrote that behaviour and made Carver look dour throughout his entire battle. With this change, I finally got back to it and finally finished it up all proper and stuff!
Ahh, just like getting your spring cleaning done.
Now here comes the big one. The one that I knew needed a big refactor from the very beginning, but didn't get around to doing it because it would be so much work: the BuildData class.
This class holds the information about the Wgt of all your opponents. It's how the game tracks their Wgt in the background while you're out and about wandering the streets of Plursdott.
... but that's not the only thing it holds, no. It also has their current decks and battle data loaded in, at all times. This is because the game prototype was originally just a single battle between you and Cedric: the overworld and time mechanic wasn't even conceived even at all back at this point!
And so, it would seem that when I expanded CoG into the exploration game it is today, I decided to just keep and use the entire battle data of the opponent, which included the Wgt variable I needed.
There's a few things wrong with that:
The overworld tracked the Wgt of a character using a chunk of data meant to be used for battle.
In a well-designed project, the overworld should have nothing to do with that kind of info. It shouldn't even know battle character data exists. All it needs is to track the Wgt only, and nothing else.
The build is stored in memory, always, taking up space even though it isn't being used. Mind you, this meant every single character had their entire deck and battle parameters loaded in into your device's memory at all times.
Characters' builds change over time, when story progresses. If this system was well-designed, I would only need to seek and retrieve the appropriate build when a battle is started.
How did the old, terrible design handle this? Well, anytime a character's story flag was progressed, BuildData would have to check for if a new build had to be loaded, and then loaded it into memory.
The solution to this one was obvious: separate the build data containing the deck from the actual Wgt variable needed to be tracked in the background. That way, when it came time to start a battle, the BuildData would simply load the correct deck info and pass it over to the Battle manager.
This also meant that the build wouldn't be held in the BuildData itself, but some kind of new structure that had to be made. So all in all, that meant this part of the game needed three separate objects:
BuildManager: renamed from BuildData, the big dog managing each of the character's individual build containers.
BuildInfo: a single build container for a specific character. Now that it no longer holds that data, it just tracks the Wg of the character, and is also used to look up and retrieve the battle build when a battle with the character is initiated.
BuildInstance: the actual build and character data that is obtained by asking Buildnfo. What is actually passed on to the battle manager to fight.
This was all simple and straight-forward work, but the problem was it meant a lot of existing code in other places had to be changed, and so it was going to take a lot of time.
With a bit of clever searching and replacing, I managed to update most of the code to use the new BuildManager with surprisingly minimal amount of work!
Unfortunately I ran into an issue while refactoring: the characters also have minimum and maximum Wgt ranges. Those also change depending on storyline progress with the character, and it is also something the overworld needs to track in the background.
Until now, they were both also held in the build data, which meant I had to find a way to track and update those ranges independently outside of battle. But... should a character update their build and Wgt ranges at different times, or use the exact same "storyline time to update values" code?
Aside from that, there was also one other big issue caused by the "duct tape" style of building the game: One-sided battles (I still haven't decided on a new name for it... appreciate all the ideas in the comments previous post though, thanks everybody!)
Because One-sided battles were a later addition to the game after all of the main battle mechanics were already implemented and finished, I didn't do a whole of redesigning of the existing build data structures to accommodate for them.
Instead... I just implemented one-sided builds by tacking on all the extra parameters to normal builds. This means that... internally, the code considers one-sided builds to be normal builds, just with the special conditions, turn count, and all that tacked on top of it. So... technically, the one-sided builds also have decks, hands, battle AI... they just doesn't use them.
This was once again remedied by just taking the time, being responsible, and redesigning the existing build data to accommodate two seperate and independent structures: RegularBuilds and OnesidedBuilds. Inside the code, OnesidedBuilds were also called "SoloBuilds", so I had to rename a few of them as well.
... how about one final caveat? This one concerns content that isn't in the game currently, so this is a case of refactoring code so that future additions will be possible. This one's about Ricky and how his build is done internally.
Currently in the game, Ricky features in one battle, a One-sided battle. Despite being a Stout, the fight is one-sided due to the circumstances of him letting you do it freely without fighting back. However, I also have plans to implement a proper, regular fight with him, which meant his BuildInfo needed to be able to handle both RegularBuilds and OnesidedBuilds at the same time.
This actually ended up affecting every other BuildInfo as well, because it meant each of them had to differentiate between whether they were returning a RegularBuild, or a OnesidedBuild! This meant some changes even had to be made for the Free Battle Mode, as it relied on the old code to be able to see which builds a character had available.
Phew..! With all of that finally done, I could let out a sigh of relief at last . Originally I thought all this build refactoring work would just be very tedious and repetitive, but these few bits of design dilemmas at the end actually had me stumped for a bit as I mulled over how I could redesign it!
If anyone reading this is an aspiring game dev who's just getting into coding: don't let all of what I just talked about deter you. In fact: I encourage you to do as shoddy of a job as I did, because badly written code is better than no code at all.
Hell, despite all of the above, you'll notice that the game doesn't run that bad at all! There are a few moments in the game where it might stutter as it attempts to load a ton of stuff, such as when you open the deck editor with all your cards... but the the things I updated are completely unrelated to this.
In fact, all the changes I made likely won't affect the performance at all! Luckily for me, this is a fairly simple 2D game which takes far fewer resources than... say, an a 3D open-world game.
This won't be the end of all the refactoring though, as there are still a lot of structures in the game that need an update. However, I'll leave those for some later development cycle, and get back to creating content for now. Speaking of...
The farm MQ4, while featuring a pretty much fully written scene with a conclusion, is not over yet! There's still one missing post-discussion scene where we need to talk about what happens afterwards! Especially with how much there is to talk to depending on how it all turned out, and away from prying eyes...

Here's a sneak-peek from the scene, and in the interest of not spoiling it any further, that's all I have to show you today!
There's no card of the month this time because I've mostly been tinkering with the code, and the upcoming side storyline updates, which will largely involve talking and more narrative-focused story beats and not battles.
Hope you didn't mind the far wordier and more technical development log today! With that said: once again, thank you for reading and for supporting the project!
Mass
2024-09-30 21:29:44 +0000 UTC