Welcome to the first small dev log for Idle Obelisk Miner!
First a small introduction for anyone new here.
I am FrozenAra. I work on a few things but most of my free time is spent on dealing with lots of different things for the game Idle Obelisk Miner on Android and iOS made by Alex with art by Celio. Huge shoutout to Alex! It’s an fantastic idle game. Check it out if you haven’t yet.
I’ll even throw in a code that can be used in the game for some extra stuff:
OMGPETS
Warning! I tried to keep this simple and understandable but it is still fairly in depth. I hope devs and players can enjoy this read either way.
Okay now let’s get to the fun stuff!
Let me tell you about some bugs.
I always loved reading about Devs finding interesting bugs. So I was very excited when that happened in IOM.
And not just one. We managed to find two of them in a short period of time!
Game Maker Studio 2 and Game Maker Language
I need to start with a little opener about the engine IOM uses.
IOM is written in GML, a programming language used by Game Maker Studio 2. It is a very high level language. That means it does a lot of the work for you. This makes it very beginner friendly. But it still has a lot of features that make it possible for experienced devs to stand above beginners.
I don’t want to go to deep into GameMaker so I will only mention the features that are relevant to the bug.
Firstly, GM uses an object system. This means that you can create an object that has a set of so called events that get triggered by different things. The step and create events are arguably the most used ones. The create event runs once whenever the object gets created. Think of an enemy spawning and we set the direction its looking, hp etc. The step event gets run 60 times every second. There are a lot of other events but those two are plenty to explain this.
Bug #1:
First let’s describe what the bug actually did.
On the surface the bug made a lot of weird things happening.
In the bi-monthly event for example it very rarely crashed a players game.
In rare cases the game crashed when buying something in the shop.
And sometimes it was just the menu that randomly scrolled up or down.
Very odd behaviour.
Alex finally figured out that this always happened when the game’s autosave ran.
Now the question was: Why?
After some more lengthy searching we figured out why.
The issue stems from the way Game Maker handles variables in combination with a coding mistake.
Inside our save function we look at different states of the game and loop over them to save them to the save file.
One line might look like this:
for(i=0; i<global.items; i++)
{
ini_write_real("items", "amount", get_item_amount(i));
}
This would get the id of the items and save the amount the player has to the save file.
The issue that triggers the bug is this part:
i=0
// for(i=0; i<global.items; i++)
To explain why this leads to issues I need to show an example.
Let’s say we have an object called tower that has a create and a step event like this:
In the create event we give it some health and a value to see if it was hit by an attack.
And inside the step event we check if it was attacked, and if yes we remove a health point.
Now to the issue.
There are two ways to create a variable in GM.
This way:
health_points = 100;
And this way:
var health_points = 100;
Using the tower object above as example:
I have to create the variables without using var.
Because using var makes it so the variable is only accessible in this event.
This would mean I cannot access the towers health points or if it was attacked in the step event.
However reversely if you only need a variable in one place then it does not matter if you use var or not.
But this leads to issues if you reuse variable names without care.
And that’s exactly what triggered our first bug.
Let’s look at that part of the save function again:
for(i=0; i<global.items; i++) // wrong
for(var i=0; i<global.items; i++) // correct
Not using var here in this case is dangerous.
Lots of loops in the game use i
to count the amount of iterations a loop ran through.
This means that if both loops are not using var we will “leak” the value of i
.
Now this is really only an issue if the i var is not correctly reset. Which normally always happens.
But in our case the auto save is on a timer that gets called every minute or so.
The actual bug was triggered because the autosave function and a loop that handles store buttons are in the same object.
Following that I can show this example:
Scenario:
You buy something in the shop
Internally the game runs through a loop to check which button was pressed.
Let’s say the pressed button has an ID of 3 and there are 6 buttons overall:
for(i=0; i<global.button_id; i++)
{
if(button_pressed(i))
{
on_button_press(i);
}
}
For iteration 0, 1 and 2 nothing will happen.
When it hits 3 button_pressed will be true and the game will trigger the function of the 3rd button.
Now we have the autosave.
Let’s assume that the timer for the one minute auto save triggers when the loop sits at 0.
The game essentially runs the loop code and the save at the same time.
The save function loops through the 20 items and finishes saving to the file.
And we jump back to the button function. But now because we did not use var for the loop we are re-using the last value i had in the save function. Which will be 20. The game tries to find button 20 and crashes because it never expected to handle more then its 6 buttons.
Very easy fix. Simply always use the var keyword in for loops.
To sum this issue up:
Forgetting use of the var keyword led to the variable “i
” being set to the wrong value inside every object that uses the autosave function.
This lead to random crashes when specific things were trying while the game was trying to save.
This post took me way longer to write then expected.
I will post more here in the future. I hope it won’t take too long for the next one!
FrozenAra
Good explanation as to what is going on. I am not familiar with GML but, it looks very java-ish. Just wanted to point out the difference between using and not using var that might help.
using var is a declaration of a new variable and runs a function in the background to create a new variable. With many higher level languages if this is omitted and the variable doesn’t exist, it will be created anyway. However, if you don’t use var and their is a variable with the same name in scope (hopefully, some languages like java don’t always handle in scope and out of scope very well) the program will use the existing variable. Using var to declare the variable in the for loop header makes it explicit that you want a new/separate variable.
In reality, if you had them both in for loops like the excerpt you posted, their scope should be limited to inside that for loop and not exposed to other parts of the game. (but again, as this looks closely related to java, scope is not a thing all languages handle very well.)
TL;DR; “var” initiates the creation of a variable or declares it. without “var”, this language seems to automatically initiates the creation if it doesn’t exist but, will use it if it does. That inconsistent action can cause inconsistent results.
In GameMaker the var keyword specifically makes the variable private to that single event.
Without the var keyword its public to the whole object as well as being able to be accessed from the outside. Its not a great solution in my eye.