OpenLoco v25.08 is out! We have a huge amount of behind the scenes changes for this month. Company AI is now fully implemented! All vehicle route finding has been implemented! And there has been a variety of bug fixes. We are now in the very final stages of reimplementing the game in C++. Hopefully, in the next few months, we will be standalone from the original game (for the engine), and we can bring native support for 64-bit and maybe even macOS support again.

For a summary of changes, please find the changelog on GitHub.

Bug Fixes

Company AI implementation

The last sections of Company AI have now been merged. The final part was all to do with the path finding for laying down track and road. I know a lot of people think that the AI path finding is rubbish, but it does at least mostly work. With it now implemented, we can start to think about how we could improve it. One idea I’ve had is to borrow the path finding code for use with a normal player. We could have a point to point pathing mode where it lays down the track for you. This wouldn’t be too hard to do, and perhaps it will help root out the bugs the AI has when creating a path.

Another idea I have, for Company AI in particular, is to try make it into a plugin. The idea being that the community could write their own AI that specialises in particular tasks. It is perhaps a little early to do just yet, but no harm having a think about what would be needed or require changing for it to happen.

Road Removal Game Command

@AaronVanGeffen implemented the road removal game command a few months ago. Unfortunately, it had a few little issues, and no-one at the time went through and fixed. After finishing the AI, I was free to fix the remaining issues. There wasn’t anything too revolutionary in the code, but we did make a very very minor tweak to the road removal cost calculation: it now rounds towards 0 instead of rounding towards -1. I’m sure no one will notice this tiny change, but it simplifies our code.

With Road Removal implemented, we now have all the core game commands implemented. In theory, you could play the game without hitting any of the unimplemented ones. However, there are two that you may hit: the road add/remove mods commands, which are used by tram track to add/remove catenary wires. It’s pretty useless to have a tram track without catenary wires, though.

Game commands are the core mechanism that makes multiplayer work, so with them mostly all done now we could start looking at multiplayer properly!

Miscellaneous Implementation

This month we had three contributions outside of the core team that were focused on implementing functions. Thanks everyone who contributed!

@spacek531 implemented the connect jacobs bogies code a few months ago. Much like road removal, it had been sitting in the review queue for a while, sorry about that. It is now finally merged.

@fionasoft implemented destroyTrain, which is the code that handles train crashing. It was a fun one to test!

@petergaal implemented a number of scenario setup functions. These functions handle working out the best road for towns to place and initialise some lookup functions that speedup calculations.

I implemented updateTree, a surprisingly complex update function. Trees, it turns out, can grow, multiply, die, and be covered in snow. There are a huge amount of things that can trigger the tree death! And tree death is a whole animation in itself; it’s not just a simple case of removing the tree.

Vehicle Route Finding

With the AI now finished, it was time to move focus to the remaining vehicle functions. The majority of the remaining vehicle code all had interactions with routing. I had purposely left these functions until the end to ensure we understood how things worked. I’m still not 100% sure, but I think I get the gist of it.

All vehicles are comprised of a number of cars and a number of hidden components. All of these components have their own positions and are updated separately. They all share what I have called a routing. A routing is a list of track ids that the vehicle will follow. The maximum length of the routing is 64 track ids. (This is why you can’t place a train that takes up more than 64 track pieces.) It’s not quite as simple as ‘head of train is the first routing entry’, though. There is an invisible component that runs in front of the head of the train (I think) and there is an invisible component trailing at the back of the train (I think).

Whenever a vehicle comes up to a junction, it has to decide on a routing. This is the route finding code. It works mostly how you would expect, i.e. find the shortest path to the destination. It can only look ahead a certain distance, and only a max of 5 functions. It also has to take into account signals, which will hopefully indicate if there is a train blocking a route. Track and road route finding look to be mostly the same, but with the additional complexity of signals for track and multiple lanes for road. There are three variations of the route finding code. One for normal path finding, one for aimless wandering (when you don’t set orders) and one for longest path. This last one is a bit curious, and it appears to be used with placing down vehicles. I haven’t quite got to the bottom of what it is there for yet.

Windows and UI

@LeftOfZen has finally fixed the issue with scrollbars when you have a huge number of items. The scrollbar should no longer break.

@ZehMatt has been fixing more of the fallout of adjusting how coordinates work, so hopefully now there aren’t any graphical issues when you have the news window open. He also identified an issue with the landscape generation window resetting its state every tick, which presented itself in problems scrolling the terrain options.

Last but not least, @LeeSpork has been adjusting the map window ensuring its minimum size is sensible.

Preparation for standalone

As we are nearing the end of the reimplementation effort, we need to start getting ready for being standalone. This month, @ZehMatt has been replacing stored pointers in object code with memory offsets instead. This is a neat trick that will simplify the code when we move to 64-bit.

I started looking at how we can track all our reading/writing of original game memory. We will need to make sure that we don’t read/write in original game memory space if we want to be standalone. It’s a bit of a dull task but it will help with understanding the code as well.

Italian translation

New contributor @Martinocom has added an Italian translation to the game. It’s great to have new contributors and new languages!

Infrastructure

New contributor @tyrone-sudeium has added a Linux statically linked build type which uses vcpkg for dependencies. This works much the same as the Windows build, which is also statically linked. I know that traditional Linux developers may see this as sacrilege, but it does make things much simpler. We will of course not be removing the existing dynamically linked setup.

@janisozaur has been fixing a few small warnings on newer compilers, ensuring when we update it all runs smoothly.

Updated: