Some API extension ideas

Discuss the new AI features ("NoAI") introduced into OpenTTD 0.7, allowing you to implement custom AIs, and the new Game Scripts available in OpenTTD 1.2 and higher.

Moderator: OpenTTD Developers

krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Some API extension ideas

Post by krinn »

You can only get this approximation:
tilelist = list of tiles, best method would be take all map, but you know you shouldn't do that right :)

Code: Select all

	tilelist.Valuate(AITile.GetTownAuthority);
	tilelist.KeepValue(town_id);
	tilelist.Valuate(AITile.IsBuildable);
	tilelist.RemoveValue(1);
	tilelist.Valuate(AITile.IsWaterTile);
	tilelist.RemoveValue(1);
	tilelist.Valuate(AIRoad.IsRoadTile);
	tilelist.RemoveValue(1);
	tilelist.Valuate(AITile.GetOwner);
	tilelist.KeepValue(-1);
	tilelist.Valuate(AIIndustry.GetIndustryID);
	tilelist.KeepValue(65535);
remain tiles are house, number of tiles = tilelist.Count()
It will remain approximative (but close to reality), as any house more than 1 tile will count for x houses (a 4 tile house = 4 houses). And highly depend on the town size (as GetTownAuthority depend on it if i remember well), size of closest town (i'm not sure when authority overlap what answer you get)and of course how you catch tiles at first to fill the tilelist (too few tiles you may miss houses)

(in case you miss it, your AITile.IsIndustry solve was given within it, as i removed industry tiles within the town, in case a bank exist)
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

Thanks for your help, I made a version using both your ideas that seems to work nicely. Its quick, accurate and should work with newgrf. It uses MinchinWeb's spiral walker (I modified one for game scripts) to spiral out from town centre to build a list of buildings that accept passengers, as well as some other criteria. Interestingly the GSTown.GetHouseCount() functions seems to ignore some building tiles, maybe multitile buildings only count 1?

Also included is the function to get the max/min X/Y tiles and some example code to run the whole thing on the largest town. The full code, with debug stuff and comments will be in my industry GS soon.

Code: Select all

// Imports
import("util.MinchinWeb", "MinchinWeb", 6);

SpiralWalker <- MinchinWeb.SpiralWalker;


// Town house list function, returns a list of tiles with houses on from a town
function GetTownHouseList()
{
  local townlist = GSTownList();
  townlist.Valuate(GSTown.GetPopulation);
  townlist.Sort(GSList.SORT_BY_VALUE, false);
  local TOWN_ID = townlist.Begin();
  local cargolist = GSCargoList();
  local CARGO_PAXID = 0;
  foreach (CARGO_ID in cargolist)
  {
  	if(GSCargo.GetTownEffect(CARGO_ID) == GSCargo.TE_PASSENGERS) CARGO_PAXID = CARGO_ID;
  }
  
	// END EXAMPLE CODE
	// Below requires
	//	TOWN_ID // ID of town
	//	CARGO_PAXID	//	Passenger cargo ID
	local HOUSE_COUNT_FACTOR = 1.25;		// Account for multi tile buildings
	local MAX_TRIES = (128 * 128);			// Maximum size for search, to prevent infinite loop
	local TOWN_HOUSE_LIST = GSTileList();
	local TOWN_HOUSE_LIST_COUNT = 0;
	local CARGO_COUNTER = 0;
	GSLog.Info(GSCargo.GetCargoLabel(CARGO_PAXID));
	local TOWN_HOUSE_COUNT = GSTown.GetHouseCount(TOWN_ID);
	local CURRENT_TILE = GSTown.GetLocation(TOWN_ID);
	local SPIRAL_WALKER = SpiralWalker();
	SPIRAL_WALKER.Start(CURRENT_TILE);
	local TRIES = 0;
	while(TOWN_HOUSE_LIST_COUNT < (TOWN_HOUSE_COUNT * HOUSE_COUNT_FACTOR) && TRIES < MAX_TRIES)
	{		
		TRIES++;
		SPIRAL_WALKER.Walk();
		CURRENT_TILE = SPIRAL_WALKER.GetTile();
		if(GSTile.GetTownAuthority(CURRENT_TILE) != TOWN_ID) continue;
		if(GSTile.IsBuildable(CURRENT_TILE) != false) continue;
		if(GSTile.GetOwner(CURRENT_TILE) != -1) continue;
		if(GSTile.IsStationTile(CURRENT_TILE) != false) continue;
		CARGO_COUNTER = GSTile.GetCargoAcceptance(CURRENT_TILE, CARGO_PAXID,1,1,0);
		if(GSTile.GetCargoAcceptance(CURRENT_TILE, CARGO_PAXID,1,1,0) > 0)
		{
			TOWN_HOUSE_LIST.AddTile(CURRENT_TILE);
			TOWN_HOUSE_LIST_COUNT++;
  			GSSign.BuildSign(CURRENT_TILE,"H: " + TOWN_HOUSE_LIST_COUNT);
				
		}
	}
	TOWN_HOUSE_LIST.Valuate(GSIndustry.GetIndustryID);
   	TOWN_HOUSE_LIST.KeepValue(65535);
	GSLog.Info("Created list of " + TOWN_HOUSE_LIST.Count() + " of " + TOWN_HOUSE_COUNT + " houses in town " + GSTown.GetName(TOWN_ID));
	ListMinMaxXY(TOWN_HOUSE_LIST);
	return TOWN_HOUSE_LIST;
}

// Min/Max X/Y list function, returns a till list with X Max, X Min, Y Max, Y Min, or null on fail.
function ListMinMaxXY(TILE_LIST)
{
	local LOCAL_LIST=GSList();	
	local X_MAX=-1;
	local X_MIN=-1;
	local Y_MAX=-1;
	local Y_MIN=-1;
	
	LOCAL_LIST.AddList(TILE_LIST);
	
	if(!LOCAL_LIST.IsEmpty())
	{
		LOCAL_LIST.Valuate(GSMap.GetTileX);
		LOCAL_LIST.Sort(GSList.SORT_BY_VALUE, false);
		X_MAX = LOCAL_LIST.Begin();		
		LOCAL_LIST.Sort(GSList.SORT_BY_VALUE, true);
		X_MIN = LOCAL_LIST.Begin();
		LOCAL_LIST.Valuate(GSMap.GetTileY);
		LOCAL_LIST.Sort(GSList.SORT_BY_VALUE, false);
		Y_MAX = LOCAL_LIST.Begin();
		LOCAL_LIST.Sort(GSList.SORT_BY_VALUE, true);
		Y_MIN = LOCAL_LIST.Begin();

		local OUTPUT_TILE_LIST = GSTileList();
		OUTPUT_TILE_LIST.AddTile(X_MAX);
		OUTPUT_TILE_LIST.AddTile(X_MIN);
		OUTPUT_TILE_LIST.AddTile(Y_MAX);
		OUTPUT_TILE_LIST.AddTile(Y_MIN);
		
		return OUTPUT_TILE_LIST;	

	}
	else GSLog.Error("ListMinMaxXY: List is Empty!");
	
	return null;
}
Test.png
Test.png (377.25 KiB) Viewed 6086 times
I still think IsIndustry and IsHouse are good ideas for API :P
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Some API extension ideas

Post by krinn »

R2dical you should open your own thread

- break when you look for passenger cargo and found it
- the IsStationTile test is not need, nobody can make a station with tile owner == -1
- :( function IsIndustryTile(Tile) { return (AIIndustry.GetIndustryID(Tile) != 65535); }
- I speak about that few days ago with rubidium : acceptance need to be > 7 or is invalid, recheck API : if(GSTile.GetCargoAcceptance(CURRENT_TILE, CARGO_PAXID,1,1,0) > 7)
- Better valuate your LOCAL_LIST with *Map.IsValidTile else your list migth not be empty because full of invalid tiles
- And trust me, it will ease your life to return an empty List on error instead of null
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

Ok I opened up a new thread, to avoid further going off topic here.

Thanks for your feedback (especially function IsIndustryTile(Tile)), I added all of them except "acceptance need to be > 7". I ran a debug script and some houses accept only 2,3 or so, making the value that will ignore those houses.
Steffl
Engineer
Engineer
Posts: 103
Joined: 23 Feb 2010 15:44

Re: Some API extension ideas

Post by Steffl »

Hi,
krinn wrote: - I speak about that few days ago with rubidium : acceptance need to be > 7 or is invalid, recheck API : if(GSTile.GetCargoAcceptance(CURRENT_TILE, CARGO_PAXID,1,1,0) > 7)
The acceptance for one kind of cargo in the catchment area of a station has to be 8 or bigger to accept this cargo, this is right. But I don't see how you can detect if a tile has a house on it when you just keep tiles with acceptance >7. I think not much houses are left then because acceptance is measured in 1/8's. If the acceptance of all houses in catchment area sums up to 8 (8/8 = 1) or more a station accepts this cargo. If houses are teared down or rebuild with other houses a station can lose the acceptance for a kind of cargo. So it's not too bad for an AI to only build a station when you get a value a little bigger than 8 just to be more safe from this.
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

I have a suggestion, for nogo:

GSTown.GetTownCount(GSTile, TownSize, IsCity)

also for noai

AITown.GetTownCount(AITile, TownSize, IsCity)

As far as I know there is no way to do this currently, and since the player can do it seems fair the AI/GS can also :)
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Some API extension ideas

Post by Zuu »

You can implement that yourself using GSTownList:

Code: Select all

function GetTownCount(tile, is_city) {
    local towns = GSTownList();
    towns.Valuate(GSTown.IsCity);
    towns.KeepValue(is_city);
    return towns.Count();
}
With TownSize I assume you refer to the small, medium and large size that you set when a town is founded. This information is currently not available to AIs nor GSes. If you refer to population classes, then you need to define the population limits.
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Some API extension ideas

Post by Zuu »

Aphid wrote:bool GSTown::FoundTown(TileIndex X, int city = 0)

Uses a trool argument. If city = 1, the town founded is always a city. city = 2 it is never a city. City = 0 --> it's a city with probability as defined in the advanced settings, otherwise a normal town. Returns true if it succeeds. If a valid CompanyMode is active the company pays the costs.
Note: I am unsure whether this should succeed if founding towns is disabled via advanced settings.
I've started on a patch for this: http://devs.openttd.org/~zuu/found_town3.patch

It doesn't have your city probability parameter, but just a boolean city switch. (I didn't look at your post before implementing it)
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

Oops, I actually meant:

GS/AITown.BuildTown(GS/AITile, TownSize, IsCity)

Allowing a GS or AI to build a town or city on a given tile. Like the found town window for a player. Townsize being the small/medium/large/etc button modifiers like you say.

Thats what I get for copy/paste from the nogo docs...sorry about that :oops:

Edit: Saw the patch, maybe you figured what I was trying to suggest...and in a pretty cool display of prescience started a patch doing exactly what I suggest before I suggested it :shock: :D
Last edited by R2dical on 26 Aug 2013 17:27, edited 1 time in total.
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Some API extension ideas

Post by krinn »

[code]EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY || _settings_game.economy.found_town != TF_FORBIDDEN);[/code]

So the GS must use a valid company to pay the founded town.

It seems now really the time to rethink about GS costs, the current scheme is too restrictive.
At first you're limit by the company money you're using, but primary you're just limited as it's not your company and you cannot really use someone company like yours.

I'm not sure he was really looking at that function like this ;)

[code]+ if (ScriptObject::GetCompany() != OWNER_DEITY && _settings_game.economy.found_town != TF_CUSTOM_LAYOUT) {[/code]
And a proof you were thinking about that too :)


edit: mistake the precondition, you enforce no company in fact. I'm not sure i get the last check != OWNER_DEITY so
User avatar
Core Xii
Traffic Manager
Traffic Manager
Posts: 228
Joined: 08 Apr 2008 09:47
Location: Finland

Re: Some API extension ideas

Post by Core Xii »

I'd really like some functions for getting the tiles for a given industry or station. You can currently query the location, but not the extents, of these constructions; especially problematic because they can have disconnected sections, so a simple neighbor search can't find them. Human players have access to this information visually. Not as critical for towns as town buildings are always connected to the roads, and can thus be searched easily. Note that airports can already be measured with AIAirport.GetAirportWidth/Height().

static AITileList AIIndustry::GetTiles(IndustryID industry_id)
static AITileList AIBaseStation::GetTiles(StationID station_id)
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

What about:

Code: Select all

	local ind_tile_list = Tile.MakeTileRectAroundTile(AIIndustry.GetLocation(my_industry_id), 20);  // A square of 41 * 41 centered on N station tile
	ind_tile_list.Valuate(AIIndustry.GetIndustryID);
	ind_tile_list.KeepValue(my_industry_id);

Note: Tile.MakeTileRectAroundTile is from superlib.
The same can be done for a station, maybe using the station_spread setting value for the radius.

EDIT: The AITileList_StationType is probably a better way to get station tiles:

Code: Select all

	local station_tile_list = AITileList_StationType(my_station_id, AIStation.STATION_ANY);
Likewise take a look at AITileList_IndustryProducing and AITileList_IndustryAccepting for industry.
User avatar
Core Xii
Traffic Manager
Traffic Manager
Posts: 228
Joined: 08 Apr 2008 09:47
Location: Finland

Re: Some API extension ideas

Post by Core Xii »

Good suggestions, I'll use those for the time being, but they scale poorly; a search over such huge lists of tiles takes a long time.
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Some API extension ideas

Post by R2dical »

Huh? That operation only takes a couple ticks and a radius of 20 is waaay overkill.

If you are sure that your first tile (eg AIIndustry.GetLocation) is the northernmost tile (newgrf - not sure but maybe this is a rule?) you can 1/4 the size of the list by using "AITileList.AddRectangle" instead. Also you can tweak the max x/y length to fit if you know the largest object you are looking for:

Code: Select all

local ind_tile_list = AITileList();
		local max_x = 20;
		local max_y = 20;
		ind_tile_list.AddRectangle(AIIndustry.GetLocation(industry_id), AIIndustry.GetLocation(my_industry_id) + AIMap.GetTileIndex(max_x, max_y));
		ind_tile_list.Valuate(AIIndustry.GetIndustryID);
		ind_tile_list.KeepValue(my_industry_id);
That takes less than a tick.
User avatar
Core Xii
Traffic Manager
Traffic Manager
Posts: 228
Joined: 08 Apr 2008 09:47
Location: Finland

Re: Some API extension ideas

Post by Core Xii »

I don't know the largest object I'm looking for; that's the problem. Making assumptions about the size or span of industries invites potential incompatibility with NewGRFs.

But I digress. You've provided usable solutions and justified them well; thank you.
hpfx
Engineer
Engineer
Posts: 43
Joined: 09 Nov 2013 00:19

Re: Some API extension ideas

Post by hpfx »

Hello,
I know it's possible to clone a vehicule, but I didn't see in API how to copy (or share) orders ?
it would be helpfull to copy(/share) orders without cloning vehicule itself (for example for converting to a new railtype)

proposal : GSVehicle::CopyOrders(VehiculeID source, VehiculeID destination, bool share_orders)
User avatar
Core Xii
Traffic Manager
Traffic Manager
Posts: 228
Joined: 08 Apr 2008 09:47
Location: Finland

Re: Some API extension ideas

Post by Core Xii »

hpfx wrote:I know it's possible to clone a vehicule, but I didn't see in API how to copy (or share) orders ?
it would be helpfull to copy(/share) orders without cloning vehicule itself (for example for converting to a new railtype)

proposal : GSVehicle::CopyOrders(VehiculeID source, VehiculeID destination, bool share_orders)
What about these?

static bool AIOrder::CopyOrders(VehicleID vehicle_id, VehicleID main_vehicle_id)
static bool AIOrder::ShareOrders(VehicleID vehicle_id, VehicleID main_vehicle_id)
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Some API extension ideas

Post by Zuu »

The GS API is not ment to be used to create helper scripts that automate game play. Therefore APIs to build/edit orders from AIs are missing in the GS API.

I know there are some APIs in the GS API that allow some company changing actions to be executed by the GS, which I guess *someone* could evaluate if they need to be available for GSes or not.
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
hpfx
Engineer
Engineer
Posts: 43
Joined: 09 Nov 2013 00:19

Re: Some API extension ideas

Post by hpfx »

Hi Zuu,
Zuu wrote:The GS API is not ment to be used to create helper scripts that automate game play. Therefore APIs to build/edit orders from AIs are missing in the GS API.
The ottd is not meant to provide helper feature that automate game play. it's agreed, I didn't ask for automation to be included in ottd.

Few people are able to build such patch (in c++) and it would probably for his own usage (never reach trunk). I'm not skill enough to change ottd in that way.

On another side, You the devs are providing us a set of tools to customize game play, and that's what is done with many scripts, like your great NAI.
Game script is opening us a world of possibilities, which have our imagination and skills as boundaries.
It would be a little bit limiting if you, the dev, can say which gameplay changes are allowed or not. Script designer and player should be free to try the game play they want, even if it's weird, unbalanced, unfair, or if it's far from dev thoughts. That won't alter standard ottd taste (For example some script make the game too easy, it does not hurt)

At the end, that's you that must decide if you want control what kind of change is allowed. I hope this request will be evaluated positively.
If not, I will continue to have pleasure with creating scripts and playing it.
Thank you.
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Some API extension ideas

Post by krinn »

hpfx wrote: It would be a little bit limiting if you, the dev, can say which gameplay changes are allowed or not.
I agree.
And i will add, don't limit GS because you can cheat with it : if people want a cheating GS, let them play it. Because to prevent those people using abusing GS that automate... you prevent any other idea from getting out.

And the CopyOrder is a fine example :
- With it you can do a GS that upgrade train & rails for the user : the "we don't want automation" side. (i don't really understand that with universal rail newGRF existance and this modo)
- But you can also do a GS that build a bus from X->Z as a reward to the player : not in an automation way, but more like an ability to grant gift (or some other funny things, like ability to alter order to move a bus on a train path).

If people don't like a GS that automate too much things or do bad/good things, people will just not use it, it's like the cheat function provide by openttd. Some use it, and some don't.
And if you really want to disallow this kind of script, blacklist them from bananas, but limiting the API is blocking "legit" GS usage & creativity.

Let's just take the worst case : build a GS that is a real AI, and so allowing any player to be handled by the GS ; so a GS that play fully for someone else : This might hurt the player experience, but the player need to run that GS. So i don't see what is wrong with this, as long as openttd doesn't provide it with itself and run it per default.
Post Reply

Return to “OpenTTD AIs and Game Scripts”

Who is online

Users browsing this forum: No registered users and 25 guests