This document provides a tested reference for what works and what doesn't in the OpenTTD GameScript API, based on systematic testing in both scenario editor and live gameplay modes.
Last Updated: 2025-11-05 OpenTTD Version: Tested with GameScript API v15 Testing Context: Scenario Editor + Live Gameplay
- Trees -
GSTree.PlantTree() - Roads -
GSRoad.BuildRoad(), road types, depots - Bridges -
GSBridge.BuildBridge()(road/rail) - Tunnels -
GSTunnel.BuildTunnel()(road/rail) - Towns -
GSTown.FoundTown(),GSTown.ExpandTown()(houses + roads) - Signs -
GSSign.BuildSign()
- Industries -
GSIndustryType.BuildIndustry()- Needs more testing, some newGRF packs are hard to place due to all kinds of restrictions.
- Houses - No
BuildHouse()API exists (use town expansion instead) - Rivers - No
BuildRiver()API exists (canals are different) - Objects -
GSObjectType.BuildObject()fails for GameScripts - Town Action: Fund Buildings - Only works in live game, NOT scenario editor
- Rails - Track building
- Depots - Rail depots (road depots work)
- Stations - Train/bus/truck stations
- Verified Working Features
- Partially Working Features
- Non-Working Features
- Not Yet Tested
- Critical Discoveries
- API Gotchas
Status: Verified working in scenario editor
API:
GSTree.PlantTree(tile)Notes:
- Single tree planting works
- Multiple plantings on same tile creates thick forest
- No company mode required
Status: Verified working in scenario editor
API:
// List available road types
GSRoadTypeList(GSRoad.ROADTRAMTYPES_ROAD)
// Set current road type
GSRoad.SetCurrentRoadType(road_type)
// Build road between two tiles
GSRoad.BuildRoad(tile1, tile2)
// Build road depot
GSRoad.BuildRoadDepot(tile, front_tile)Notes:
- All road types detected correctly (including "city" type)
- T-intersection building works
- Can search for specific road types by name (e.g., "city", "highway")
- Falls back to first available road type if specific type not found
Example - Finding City Roads:
local town_road_type = null;
local road_types = GSRoadTypeList(GSRoad.ROADTRAMTYPES_ROAD);
foreach (road_type, _ in road_types) {
local road_name = GSRoad.GetName(road_type);
if (road_name.find("city") != null || road_name.find("City") != null) {
town_road_type = road_type;
break;
}
}
// Fallback to first available if not found
if (town_road_type == null && road_types.Count() > 0) {
town_road_type = road_types.Begin();
}
GSRoad.SetCurrentRoadType(town_road_type);Status: Verified working in scenario editor
API:
GSBridge.BuildBridge(vehicle_type, bridge_id, start_tile, end_tile)Parameters:
vehicle_type: GSVehicle.VT_ROAD or GSVehicle.VT_RAILbridge_id: Bridge type ID (get from GSBridgeList)start_tile: Starting tile coordinateend_tile: Ending tile coordinate
Notes:
- Only needs start and end coordinates
- Game automatically determines bridge height and validates path
- Works for both road and rail bridges
Example:
local bridges = GSBridgeList();
local bridge_id = bridges.Begin();
local start = GSMap.GetTileIndex(x1, y1);
local end = GSMap.GetTileIndex(x2, y2);
GSBridge.BuildBridge(GSVehicle.VT_ROAD, bridge_id, start, end);Status: Verified working in scenario editor
API:
GSTunnel.BuildTunnel(vehicle_type, start_tile)Parameters:
vehicle_type: GSVehicle.VT_ROAD or GSVehicle.VT_RAILstart_tile: Starting tile (must be a slope facing the tunnel direction)
Notes:
- Only needs start coordinate
- Game automatically calculates tunnel end point
- Start tile must have proper slope orientation
- Works for both road and rail tunnels
Status: Verified working in both scenario editor and live game
APIs:
GSTown.FoundTown(tile, size, is_city, layout, town_name)Parameters:
tile: Center tile for townsize: GSTown.TOWN_SIZE_SMALL / MEDIUM / LARGEis_city: true/false (cities grow faster)layout: GSTown.ROAD_LAYOUT_ORIGINAL / BETTER_ROADS / 2x2_GRID / 3x3_GRIDtown_name: String name for the town
Notes:
- Works without company mode in scenario editor
- Returns town ID via
GSTile.GetTownAuthority(tile)after founding
GSTown.ExpandTown(town_id, houses)Parameters:
town_id: ID of the townhouses: Number of houses to build
Notes:
- CRITICAL: Must be called WITHOUT GSCompanyMode wrapper!
- Builds houses AND roads immediately
- Works in both scenario editor and live game
WRONG:
{
local mode = GSCompanyMode(company_id);
GSTown.ExpandTown(town_id, houses); // FAILS!
}RIGHT:
GSTown.ExpandTown(town_id, houses); // Works!GSTown.PerformTownAction(town_id, 5)Parameters:
town_id: ID of the town5: Action constant for TOWN_ACTION_FUND_BUILDINGS
Notes:
- CRITICAL: Must be called WITH GSCompanyMode wrapper!
- Only works in LIVE GAME (not scenario editor)
- Funds building construction over 3 economy-months (time-based, not immediate)
- Requires town population > 0
- The parameter is the numeric index
5, not an enum constant
Example:
{
local mode = GSCompanyMode(company_id);
local success = GSTown.PerformTownAction(town_id, 5);
if (success) {
local duration = GSTown.GetFundBuildingsDuration(town_id);
GSLog.Info("Funding for " + duration + " months");
}
}GSTown.IsValidTown(town_id)
GSTown.GetName(town_id)
GSTown.GetPopulation(town_id)
GSTown.GetRating(town_id, company_id)
GSTown.GetFundBuildingsDuration(town_id)
GSTown.IsActionAvailable(town_id, action)Status: Verified working in scenario editor
API:
GSSign.BuildSign(tile, text)Parameters:
tile: Tile coordinate for sign placementtext: String text to display
Returns:
- Sign ID (integer >= 0) on success
- null or negative value on failure
Notes:
- Signs may be hidden by transparency settings in game UI
- Check View > Transparency Settings > Signs if not visible
- No company mode required
Example:
local tile = GSMap.GetTileIndex(x, y);
local sign_id = GSSign.BuildSign(tile, "My Sign Text");
if (sign_id != null && sign_id >= 0) {
GSLog.Info("Sign placed successfully");
}Status: Partially working - some industries succeed, others fail
API:
GSIndustryType.BuildIndustry(industry_type, tile)Test Results:
- ✅ Oil Rig: Works (in water tiles)
- ❌ Forest: Fails (no clear error message)
Known Issues:
- No reliable way to verify if industry was actually built
- Some industry types have hidden placement requirements
- Error messages are often unclear or missing
TODO:
- Improve verification logic:
- Try to build industry
- Check if industry exists at location after building
- Report detailed success/failure information
- Automatically try different locations if placement fails
Example - Current Approach:
local industry_types = GSIndustryTypeList();
foreach (ind_type, _ in industry_types) {
local name = GSIndustryType.GetName(ind_type);
if (name == "Oil Rig") {
local tile = /* water tile */;
local built = GSIndustryType.BuildIndustry(ind_type, tile);
// built may return true even if placement failed!
}
}Status: API does not exist
Attempted API:
GSTown.BuildHouse() // DOES NOT EXISTNotes:
- GameScript CANNOT build houses directly
- Only towns can build houses (via ExpandTown or natural growth)
- GameScript can detect existing houses:
GSTile.IsHouseTile(tile)- Can get house information but not create them
Alternative:
- Use
GSTown.ExpandTown()to let the town build houses
Status: API does not exist (canals are different)
Attempted API:
GSRiver.BuildRiver() // DOES NOT EXISTNotes:
- GameScript can detect rivers:
GSTile.IsRiverTile(tile) - GameScript CAN build canals:
GSMarine.BuildCanal(tile) - Canals create water tiles but NOT "river" tiles
- Rivers and canals are fundamentally different features
- No known way to build actual rivers via GameScript
Status: BuildObject fails for GameScripts
API:
GSObjectType.BuildObject(object_type, tile)Test Results:
- Object types can be enumerated correctly
- Tile validation passes
- BuildObject fails with errors:
- View 0: ERR_UNKNOWN
- Other views: ERR_PRECONDITION_FAILED
- Tested both with and without GSCompanyMode wrapper - both fail
Tested Objects:
- Lighthouse (ID: 1) - Failed in both scenario editor and live game
Conclusion:
GSObjectType.BuildObject()may not be available to GameScripts- Objects can be placed manually in editor but not via API
- This may be a limitation by design
Status: Not yet tested
Expected API:
GSRail.BuildRail(tile1, tile2)
GSRail.BuildRailTrack(tile, track)Status: Not yet tested
Expected API:
GSRoad.BuildRoadDepot(tile, front_tile)
GSRail.BuildRailDepot(tile, direction)Status: Not yet tested
Expected API:
GSStation.BuildRoadStation()
GSStation.BuildRailStation()Different APIs have opposite requirements for GSCompanyMode:
| API | Requires Company Mode? | Notes |
|---|---|---|
GSTown.ExpandTown() |
❌ NO | Must call WITHOUT wrapper |
GSTown.PerformTownAction() |
✅ YES | Must call WITH wrapper |
GSTile.DemolishTile() |
✅ YES | Requires company context |
GSTree.PlantTree() |
❌ NO | Works without wrapper |
GSRoad.BuildRoad() |
Varies | Context-dependent |
Key Insight: Always check if an API fails - try both with and without GSCompanyMode!
Some features only work in specific contexts:
| Feature | Scenario Editor | Live Game |
|---|---|---|
GSTown.ExpandTown() |
✅ | ✅ |
GSTown.PerformTownAction() |
❌ | ✅ |
| Most building APIs | ✅ | ✅ |
Time-based actions (like FUND_BUILDINGS) require a running game with advancing time.
Some enum constants are NOT prefixed with the class name:
WRONG:
GSTown.PerformTownAction(town_id, GSTown.TOWN_ACTION_FUND_BUILDINGS)RIGHT:
GSTown.PerformTownAction(town_id, 5)Multi-tile buildings (2x2, 3x3 houses) can usually be cleared in one pass:
GSTile.DemolishTile(any_tile_of_building)For unknow reason, buildings are not always removed.
When searching for road types by name, search for lowercase and capitalized variants:
if (road_name.find("city") != null || road_name.find("City") != null)Some APIs return success but actually failed:
- Always verify results when possible
- Check if expected object exists at location
- Some APIs provide poor error messages
Signs created via API may be hidden by transparency settings:
- UI: View > Transparency Settings > Signs
- Players need to enable sign visibility manually
Always specify your API version in info.nut:
function GetAPIVersion() {
return "15";
}This ensures compatibility and reduces warning messages.
function TestFeature() {
GSLog.Info("TEST: Feature Name");
local tile = GSMap.GetTileIndex(x, y);
local built = false;
// Attempt to build
local result = GSFeature.Build(tile);
if (result) {
built = true;
GSLog.Info(" SUCCESS: Feature built");
} else {
local error = GSError.GetLastErrorString();
GSLog.Info(" FAILED: " + error);
}
GSLog.Info(" Status: " + (built ? "SUCCESS" : "FAILED"));
GSLog.Info("");
}If an API fails, try both modes:
// Attempt 1: Without company mode
local success = GSFeature.Build(tile);
if (!success) {
// Attempt 2: With company mode
{
local mode = GSCompanyMode(company_id);
success = GSFeature.Build(tile);
}
}
if (!success) {
// Attempt 3: With deity mode
{
local deity_mode = GSCompanyMode(GSCompany.COMPANY_INVALID);
success = GSFeature.Build(tile);
}
}function GetTestTile(x_offset, y_offset) {
local map_x = GSMap.GetMapSizeX();
local map_y = GSMap.GetMapSizeY();
local center_x = map_x / 2;
local center_y = map_y / 2;
return GSMap.GetTileIndex(center_x + x_offset, center_y + y_offset);
}function Sleep(ticks) {
if (!GSController.GetSetting("debug_signs")) return;
local current_tick = GSController.GetTick();
while (GSController.GetTick() < current_tick + ticks) {
GSController.Sleep(1);
}
}- Official API Documentation: https://docs.openttd.org/gs-api/
- OpenTTD Wiki: https://wiki.openttd.org/
- Development Forum: https://www.tt-forums.net/
- Discord: OpenTTD Development Community
This document is based on systematic testing. If you discover:
- New working features
- Corrections to existing information
- Additional gotchas or edge cases
Please update this document and note the date and API version tested.
Document Version: 1.0 Contributors: Testing conducted via TreePlacer GameScript test suite