tiled icon indicating copy to clipboard operation
tiled copied to clipboard

Add scripting API for the project

Open bjorn opened this issue 4 years ago • 11 comments

Scripts should be able to access the files in the project and the project properties.

Split off from #1665.

bjorn avatar Sep 29 '20 14:09 bjorn

So i could do with this feature for my use case.

I want to be able to open every map in a project and run a set of Actions which automate a whole bunch of stuff. Mostly auto mapping.

For the current work around im having to hardcode the map paths into the triggering action.

/// <reference types="@mapeditor/tiled-api" />

const automapAllMaps = tiled.registerAction("AutomapAllMaps", function(action) {
    tiled.trigger("CloseAll");

    processMap("X:\\Source\\MangoDungeon\\Mango\\Assets\\Tiled\\Maps\\Test.tmx");
});

function processMap(path:string) {
    tiled.open(path);
    tiled.trigger("AutomapExtra");
    tiled.trigger("Close");
}

automapAllMaps.text = "Automap All Maps";
automapAllMaps.checkable = false;

tiled.extendMenu("Edit", [
    { action: "AutomapAllMaps" }
]);`


jonny64bit avatar Apr 14 '22 15:04 jonny64bit

For the current work around im having to hardcode the map paths into the triggering action.

Just one thing to note, there is API available to access a list of files in a folder, so you may only need to hardcode the project path instead of the paths to each map.

Since you're applying AutoMapping to all files in your project, I hope you noticed the Tiled 1.9 Alpha release that greatly improves the speed of AutoMapping. Any feedback regarding AutoMapping is very welcome.

bjorn avatar May 02 '22 08:05 bjorn

from #3527 - I also have a use case for this. I would like to get the path to the current project. I'm working on an extension to edit maps for Cataclysm: Dark Days Ahead. It involves importing tilesets and mapping the game's IDs to each sprite and preparing maps with data from the game files.

I generate tilesets, maps, and supplemental files to the project folder which I prompt from the user and then store in a config file to autofill next time the command is run for slight convenience.

solerante avatar Nov 17 '22 23:11 solerante

I think I've asked for this elsewhere, but I think it makes sense being included here: the Project would be a very convenient place for scripts to store script configuration, which at present has to be either set every time the user restarts Tiled, hard-coded by the user, or stored in config files that clutter the user's directories. To this end, I'd like to be able to set Custom Properties or something like them on the project, both via scripts and via the GUI.

Edit: Oh, looks like there's an issue for that already: #2903

eishiya avatar Dec 10 '22 00:12 eishiya

After #3622 we have access to the basic properties of the project, including the list of folders, which is usually more relevant than the project file path. However, I don't think we can close this issue, since there are at least two shortcomings:

  • There is still no convenient way to access the files in a project, due to:
    • Recursive search for files needs to be implemented manually.
    • We can't easily get all files of a certain type, for example all files that can likely be read as map files.
  • The project also stores the custom types, which needs to be accessible as well (see also #3419, which maybe could be considered to cover this aspect).

Regarding file search, the just added tiled.project could be entry point of convenience functions like project.maps, project.tilesets and project.worlds (or maybe project.assets(Asset.TileMap) would be better?). I also wonder whether these functions should return file names, or actually loaded assets (which could be convenient, but would in some cases result in performance issues). A more generic file search like, project.files("*.tmx") could also be useful.

bjorn avatar Mar 21 '23 11:03 bjorn

While testing #3622, I ended up writing that big recursive search for map files:

function collectMaps(folder) {
  //First, get all the files in this folder
  let files = File.directoryEntries(folder, File.Files | File.Readable | File.NoDotAndDotDot);
  for(file of files) {
	 let path = folder+"/"+file;
	 let format = tiled.mapFormatForFile(path);
	 if(format) {
		let map = getOpenMap(path);
		if(map) //If it's already open, use that instance
		   maps.push(map);
		else //save the path to open later
		   maps.push(path); 
	 } //else there's no map format that can read this file, it's not a Tiled map, skip it.
  }
  //Then, look at any subfolders:
  files = File.directoryEntries(folder, File.Dirs | File.Readable | File.NoDotAndDotDot);
  for(file of files) {
	 collectMaps(folder+"/"+file);
  }
}
//Find all the maps in each project directory:
if(tiled.project) {
    let folders = tiled.project.folders;
    for(folder of folders)
        collectMaps(folder);
}

As you can see, I ended up saving a list of paths, because opening all those maps at once was terrible for performance xP Opening them one by one made for a much more pleasant experience. They need to be the actual map documents in my case (i.e. tiled.open, not MapFormat.read), so there was no avoiding the overhead of that. I think returning a list of paths gives scripts the most options - they can WhateverFormat.read or tiled.open all of them, they can read them one at a time, read them in pairs for comparisons, whatever, all with the minimal memory impact possible for their given task.

It might also be nice if projects supported getting a list of images within the project, as that's another file type commonly used in Tiled, even if Tiled doesn't usually create them. Tiled knows best what it can open as an image xP However, I do also quite like the idea of project.assets(Asset.TileMap), and that wouldn't make sense for images.

Whatever the method for getting files of a particular type, I think it would be improved by the option to specify a path to look within, e.g. a script might only want the maps in /automap or in /jungle, e.g. project.assets(Asset.TileMap, '/jungle'). If possible, both absolute paths (e.g. from project.folders) and relative paths (treated as relative to the project root) should be allowed, and any attempts to search outside the project should return nothing.

A generic file search with filename wildcards would be pretty nice to have too. Perhaps this could be combined with all of the above, e.g. "find all the Maps in /automap/ with the name filter jungle*": project.files(Asset.TileMap, '/automap', 'jungle*'). All those parameters would get in the way when you just want the filter OR the path though.

eishiya avatar Mar 21 '23 13:03 eishiya

I would agree that the map/tileset/etc. list functions should just return the paths, especially because if you did want to load them all, you could just loop through the paths yourself without too much code.

All those parameters would get in the way when you just want the filter OR the path though.

Maybe passing '', null, or undefined as the search directory would default it to the project root

dogboydog avatar Mar 21 '23 17:03 dogboydog

All those parameters would get in the way when you just want the filter OR the path though.

Maybe passing '', null, or undefined as the search directory would default it to the project root

Yeah, it'd have to be done that way if we have a single three-parameter files method xP It's just not the prettiest, is all.

eishiya avatar Mar 21 '23 17:03 eishiya