Save and Load
NoAI Framework

NoAI Main Page

Development

Development milestones
Suggested API changes

AI Programming

API Documentation
Introduction
info.nut file
main.nut file
Basics
Using the road pathfinder
Using the rail pathfinder
Saving / Loading data
Things you need to know
Squirrel
Home page
Common mistakes
Lists
Coping with OTTD errors
Trams
AI Libraries
Forum
Forum FAQ
AI Behavior
ShortNames in use

AIs

TestAI
WrightAI
Convoy
SimpleAI
Comparison of AIs
See forum
See BaNaNaS
All articles in NoAI category

Contents

Save

If you create a Save() function in your AI, you can store some data inside the savegame. The function should return a table with the information you want to store. You can only store:

Instances of classes can't be saved. Please note that this includes AIList, so if you want to store an AIList, you have to convert it to an array or table on save and convert it back on load.

As soon as the user saves the game, the Save() function of your AI is called. This happens while your AI is still active doing whatever you made it do. For example, in the middle of a pathfinding, or maybe when you are changing an internal array. The point here is, that your AI won't notice it, besides the fact that the Save() is called for a moment. When you return from that function, your AI continues like nothing happened. Warning: if you change a variable that you also store, and want to make absolutely sure no race-conditions happen, do it right after a Sleep(). The chances of the Squirrel fair-scheduler kicking in just after a Sleep() are very slim, so you should be relatively safe.

Note: No other information is saved, so for example all pending events are lost as soon as the game is loaded. To circumvent this you can store all events (converted to allowed types) in the savegame yourself. However, the game will continue normally after the Save() function is called, so you also need to handle those events.

Load

If a savegame with an AI in it is loaded, OpenTTD tries the following. As soon as one step succeeds, the sequence is stopped:

First the constructor of your AI is called (to create an instance of the AI). Then your Load(version, data) function is called with the version number of the AI used to save and the saved data itself, and finally the Start() function is called to start the program. If an AI didn't save any data, the Load() function is not called. (The constructor is called to make an instance, and then Start() is called.)

What data to save?

Of course what data you save exactly is up to you, but as a general rule you shouldn't save anything that can be easily detected by reading the map again. So you can store a mapping of truck station to industry, but storing just a station list is useless.

What you can't do in your Save() and Load() function

Both the Save() and Load function() are called from within the main thread. This means that it is not possible to execute any command that builds / removes / changes something in the game. You can execute some API functions, but only those that fetch data. Please try to minimize the code in Save() and Load(), as the user has to wait for those functions everytime they save and load a game.

Handling of save versions

When a game is saved, the name and version of each AI is saved. When OpenTTD loads a game it tries to find a version of the same AI that can load the game state from the version of the AI that was used in the save. By declaring MinVersionToLoad() in your info.nut and make it return the minimum version of your AI from which it can load the data.

Additionally there is a version argument to Load() that tells the load function which version of your AI that was used to save the game state.

Example Save() / Load() code

/* info.nut */
class testai extends AIInfo
{
  /* ... */

  function GetVersion() { return 3; }

  /* Accept to load games saved by version >= 2 of this AI */
  function MinVersionToLoad() { return 2; } 

  /* ... */
}
/* main.nut */
class testai extends AIController
{
  constructor()
  {
    counter = 0;
  }

  counter = null;
};

function testai::Save()
{
  local table = {counter_value = this.counter};
  return table;
}

function testai::Load(version, data)
{
  if (data.rawin("counter_value")) {
    this.counter = data.rawget("counter_value");
  }
}

function testai::Start()
{
  while(true) {
    AILog.Info("Tick " + this.counter);
    this.Sleep(70);
    this.counter++;
  }
}

Savegame ids

Since the various newgrfs can change many of the different ID types used in the game, here is a list of ID types which you can/can't trust to be the same when the savegame is loaded.

ID type Meaning Store in savegames Hardcode into AIs Note
BridgeID Type of bridge (ex. tubular) [0] No Use the AIBridge::* function to find suitable bridge type.
CargoID Type of cargo [0] No You can filter the AICargoList using AICargo::CargoClass (ex. CC_PASSENGERS) or AICargo::TownEffect (ex. TE_PASSENGERS) to get the passenger cargoID
EngineID Engine type [0] No Use the AIEngine::* functions to find an engineID that suits your need.
GroupID Vehicle group Yes ---

IndustryID Specific industry somewhere on the map Yes ---

IndustryType Type of industry, ex. coal mine [0] No Use AIIndustryList_CargoAccepting and AIIndustryList_CargoProducing to find matching pairs.
SignID A sign somewhere on the map yes ---

StationID Specific station somewhere on the map Yes ---

SubsidyID subsidy Yes (but is reused when subsidy expires) ---

TileIndex A tile somewhere on map Yes ---

TownID A town somewhere on the map Yes ---

VehicleID Vehicle yes ---

WaypointID Waypoint somewhat --- When upgrading savegames from before r16909, so in effect from 0.7.x to 1.0.x or later, the IDs will change if there are stations in the game

[0] These should be safe to store, but they can change when the newgrf config changes. Normally users shouldn't change it, so if your AI breaks because one of these changes, blame the user. Note that the newgrf config can also change during a game, and in that case there is nothing you can do if one of these IDs changes. (You could recompute the IDs every time you use them, but that's very unpractical).