OpenLoco v24.02 is out! This month represents a huge step forward in our percentage of reimplementation. There isn’t much in terms of features, but there have been a healthy dose of bug fixes in this release.

We hope you’ll enjoy reading this dev log, and will take the new release for a spin as well.

As always, please find the changelog on GitHub.

Implement Train Station Placement Game Command (#2202)

Implementing all of the game commands is a long running project for the team. We had been ignoring all of the station (train, road, airport, dock) functions so far, as they are quite complex. But during the winter break, we finally decided to bash one of them out. Implementing the function showed there was a number of little issues in vanilla Locomotion related to train stations.

In vanilla, when an AI preallocates a station, it is allowed to do this without first placing a track piece. We aren’t exactly sure what this allowed, but to in order allow this there is multiple special case bits of code. Within one of those special cases, the function gets confused trying to work out if there is vertical space for the station. The bug will cause the AI to then assume that a station could be placed where it can’t. Ultimately, no invalid station gets created, but it does change where the AI places its stations. Let’s hope the other stations (road, airport and dock) don’t have similar issues.

Implement Last Journey Average Speed (#2173)

@duncanspumpkin implemented this a few months ago, but there was some odd code in the implementation that did not sit right. After leaving it on the shelf for a few months, he realised that the odd bit of code was all to avoid doing a 64-bit division. Locomotion was created in assembly and uses only 32-bit x86 instructions, and unfortunately there is no 64-bit division so Chris Sawyer had to implement the divide as multiple instructions. This is all pretty standard, but if you aren’t expecting it, the code looks a little unorthodox. Fortunately, as we write in C++, we don’t need to worry about this and the compiler will happily create an equivalent.

Implement Paint Wall (#2222)

Locomotion’s rendering uses the painters algorithm. To achieve this, it converts every map element (building, surface, track, etc.) and map entity (vehicle, money effect, smoke, etc.) into an image and a 3d bounding box. It will then sort the 3d bounding boxes and draw the images from back to front. We call the translation of a map item into its bounding box and image a ‘paint’ function. @ZehMatt decided that the time was ripe to implement the wall paint function. It exposed a lot of features of walls that are not currently being exploited by custom object creators. Did you know walls can have: transparent (glass) elements; or that they can have two sided images? Well now you do, and we have documented exactly what flags need to be set to enable these features in objects.

Implement Paint Track and Track Additions (#2259, #2263, #2276)

The track paint functions represent a massive ~5% of the vanilla game. They are very repetitive, and were likely implemented using some sort of automated generation code. When we worked on OpenRCT2, which uses the same engine as OpenLoco, @marijnvdwerf realised that we needed a way to verify that our implementation of each paint function matched the output of vanilla. So to do this, he installed some scaffolding code around the paint function. This was called TestPaint. After a few days of writing this, though, @marijnvdwerf realised we could also just make the verification function output out a new C++ version of the original function. For OpenRCT2, this meant we suddenly went from ~80% completion to 99.999% completion in one day! We always planned to do something similar for OpenLoco, but the code worked a bit differently, so no one had attempted it – until now!

The exact same technique was used, but the generated code was structured very differently. One of the big issues on OpenRCT2 is how challenging it is to add new track pieces. One of the reasons it is difficult is because the paint functions are hard to write correctly. For OpenLoco, we didn’t want to repeat this, and now instead of ~400 functions, we have 2 short 10 line functions that achieve exactly the same output! To add a new track piece’s drawing function, it’s a simple matter of adding the images and their bounding boxes to a table, and the rest is taken care of.

As we measure percentage completion of the C++ reimplementation efforts by how many vanilla functions we have reimplemented, this is a massive jump in progress!

Implement Paint Building (#2279)

The building paint functions are very similar to industry paint functions, but we just hadn’t gotten around to writing them. With all the other paint related functions this month, @duncanspumpkin decided to just go ahead and implement this one as well. One item of note is that buildings have a unique animation sequence that is just for elevators to work. If anyone wants to make a custom building object, we now have extensive information on exactly what flags and fields do what.

Game Command Headers (#2258)

@ilmoro93 finished off a refactoring of the game command headers, putting each of the individual commands into different files. It was long overdue, and will help improve the speed at which we can implement the game commands. Good work, @ilmoro93.

Misc. Refactorings (#2256, #2268, #2264, #2269, #2270, #2272)

@ilmoro93, @AaronVanGeffen and @LeftofZen all worked on a number of miscellaneous refactoring items in the codebase. @ilmoro93 focused on reducing our use of raw pointers in the window functions. @AaronVanGeffen relocated chunks of input handling code to more logical files. @LeftofZen updated various game structures with the latest knowledge we have about how they work. All of these help improve the codebase and ensure we have logical, easy to understand code. Great work everyone!

Implement Company AI End Update (#2283)

Not content with just doing a ton of paint functions, this month @duncanspumpkin decided to finish off an old PR that implemented an AI function. This function handled how the AI sells off all of its assets when it has gone bankrupt and then finally deletes itself. After implementing it, he realised that the code was never being reached. Turns out, we had a mistake in a different area of the code that prevent the AI from ever entering the final sell-off and delete-self state. Doh! At least its fixed now.

One more thing: OpenLoco Object Editor!

Over the past few months, @LeftofZen has been working on a new, user-friendly way to edit Locomotion object files. This has resulted in the public release of the OpenLoco Object Editor! We hope the GUI nature of this new tool will allow more players to make their own custom objects. Please let us know how you’re getting on in our Discord channels!

Updated: