eishiya / tiled-scripts

Assorted scripts for Tiled Map Editor.
41 stars 3 forks source link

LargeTileStampTool2 #3

Closed ManPython closed 3 weeks ago

ManPython commented 3 weeks ago

I tried do something like this: LargeTileStampTool2/LargeTileStampTool2.js To resolve this: https://github.com/mapeditor/tiled/issues/4036#issuecomment-2295423012

var tool = tiled.registerTool("LargeTileStampTool2", {
    alignToTopLeft: true, // Should large tiles be offset such that they behave as if they have top left alignment?

    name: "Large Tile Stamp Tool 2",
    icon: "LargeTileStamp2.png",
    usesSelectedTiles: true,
    targetLayerType: Layer.TileLayerType,
    mouseDown: false,

    lineMode: false, // Controlled via Shift modifier
    lineStart: {x: -1, y: -1},

    tileStep: {x: 1, y: 1}, // How many cells does each large tile occupy?
    stampSize: {width: 0, height: 0}, // How many cells does the stamp cover?

    activated: function() {
        this.isActive = true;
        console.log("Tool activated.");
    },

    deactivated: function() {
        this.isActive = false;
        console.log("Tool deactivated.");
    },

    mouseReleased: function (button, x, y, modifiers) {
        if(button == 1) { // left-click
            this.mouseDown = false;
            if(this.lineMode) { // Commit line mode on release
                if(!this.map || !this.preview) return;
                this.map.merge(this.preview);
            }
        }
    },

    mousePressed: function (button, x, y, modifiers) {
        if(this.isActive) {
            if(button == 1) { // left-click, commit the preview
                if(!this.map || !this.preview) return;
                this.map.merge(this.preview);
                this.mouseDown = true;
                if(this.lineMode) {
                    this.lineStart.x = this.tilePosition.x;
                    this.lineStart.y = this.tilePosition.y;
                }
            } else if(button == 2) { // right-click, sample the tile on the selected layer
                if(!this.map || this.map.selectedLayers.length < 1) return;
                let selectedLayer = this.map.selectedLayers[0];
                if(!selectedLayer || !selectedLayer.isTileLayer) return;
                let newTile = selectedLayer.tileAt(this.tilePosition.x, this.tilePosition.y);
                let newBrush = new TileMap();
                newBrush.width = 1;
                newBrush.height = 1;
                let newLayer = new TileLayer();
                newBrush.addLayer(newLayer);
                let layerEdit = newLayer.edit();
                if(newTile) {
                    layerEdit.setTile(0, 0, newTile, selectedLayer.flagsAt(this.tilePosition.x, this.tilePosition.y));
                } else {
                    // Try sampling the nearest tile based on the last measured stamp dimensions:
                    let sampleX = Math.floor(this.tilePosition.x / this.tileStep.x) * this.tileStep.x;
                    let sampleY = Math.floor(this.tilePosition.y / this.tileStep.y) * this.tileStep.y;
                    if(this.alignToTopLeft)
                        sampleY += (this.tileStep.y - 1);
                    newTile = selectedLayer.tileAt(sampleX, sampleY);
                    layerEdit.setTile(0, 0, newTile, selectedLayer.flagsAt(sampleX, sampleY));
                }
                layerEdit.apply();
                tiled.mapEditor.currentBrush = newBrush;
                this.showPreview();
            }
        }
    },

    tilePositionChanged: function () {
        if(this.isActive) {
            this.showPreview();
            if(this.mouseDown && !this.lineMode) { // Don't commit immediately in line mode
                if(!this.map || !this.preview) return;
                this.map.merge(this.preview, true);
            }
        }
    },

    modifiersChanged: function(modifiers) {
        if(this.isActive) {
            if(modifiers & Qt.ShiftModifier)
                this.lineMode = true;
            else
                this.lineMode = false;
        }
    },

    measureStamp: function(stamp) {
        if (!stamp || !this.map) {
            console.log("Error: Missing stamp or map.");
            return;
        }

        let cellSize = { x: this.map.tileWidth, y: this.map.tileHeight };
        let tileSize = { x: cellSize.x, y: cellSize.y }; // Default to cell size

        // Iterate through layers in the stamp and find the maximum tile size
        function checkLayer(layer) {
            if (!layer) return;
            if (layer.isTileLayer) {
                for (rect of layer.region().rects) {
                    for (x = rect.x; x < rect.x + rect.width; x++) {
                        for (y = rect.y; y < rect.y + rect.height; y++) {
                            let tile = layer.tileAt(x, y);
                            // Check if the tile exists before reading its properties
                            if (tile) {
                                if (tile.width > tileSize.x)
                                    tileSize.x = tile.width;
                                if (tile.height > tileSize.y)
                                    tileSize.y = tile.height;
                            }
                        }
                    }
                }
            } else if (layer.isGroupLayer || layer.isTileMap) {
                for (let gi = 0; gi < layer.layerCount; ++gi) {
                    checkLayer(layer.layerAt(gi));
                }
            }
        }
        checkLayer(stamp);

        // Calculate tileStep based on the size of the large tile compared to the cell size
        this.tileStep.x = Math.ceil(tileSize.x / cellSize.x);
        this.tileStep.y = Math.ceil(tileSize.y / cellSize.y);
        this.stampSize.width = this.tileStep.x * stamp.width;
        this.stampSize.height = this.tileStep.y * stamp.height;
    },

    drawStamp: function(x, y, stamp, map) {
        if (!stamp || !map) {
            console.log("Error: Missing stamp or map.");
            return;
        }

        let cellSize = { x: this.map.tileWidth, y: this.map.tileHeight };
        let stampSize = { width: stamp.width * cellSize.x, height: stamp.height * cellSize.y };

        let offset = {
            x: x - Math.floor(stampSize.width / 2),
            y: y - Math.floor(stampSize.height / 2)
        };

        if (this.alignToTopLeft) {
            offset.y += (stampSize.height - cellSize.y);
        }

        for (let sy = 0; sy < stampSize.height; sy += cellSize.y) {
            for (let sx = 0; sx < stampSize.width; sx += cellSize.x) {
                let tile = stamp.tileAt(sx / cellSize.x, sy / cellSize.y);
                if (tile) {
                    map.setTile(
                        offset.x + sx,
                        offset.y + sy,
                        tile
                    );
                }
            }
        }
    },

    showPreview: function() {
        if(!this.map || this.map.selectedLayers.length < 1) return;

        let stamp = tiled.mapEditor.currentBrush;
        if (!stamp) {
            console.log("No stamp (currentBrush) available.");
            return;
        }

        console.log("Creating stamp preview...");
        this.preparePreview();
        this.measureStamp(stamp); // Set the step and stamp size

        let destinationMap = this.preview;
        if(this.lineMode) {
            // Implement line mode if necessary
        } else {
            this.drawStamp(this.tilePosition.x, this.tilePosition.y, stamp, destinationMap);
        }
        this.preview = destinationMap;
    },

    preparePreview: function() {
        var preview = new TileMap();
        preview.setSize(this.map.width, this.map.height);
        preview.setTileSize(this.map.tileWidth, this.map.tileHeight);
        preview.infinite = this.map.infinite;
        // Prepare layers:
        function addLayer(layer) {
            if(!layer) return;
            if(layer.isTileLayer) {
                let newLayer = new TileLayer(layer.name);
                preview.addLayer(newLayer);
            } else if(layer.isGroupLayer || layer.isTileMap) {
                // Process its child layers recursively:
                for(let gi = 0; gi < layer.layerCount; ++gi) {
                    addLayer(layer.layerAt(gi));
                }
            }
        }
        addLayer(tiled.mapEditor.currentBrush);
        this.preview = preview;
    }
});

https://github.com/eishiya/tiled-scripts/blob/98150bdb532d0517e292bdb04d963ab1b8f76108/Large%20Tile%20Stamp%20Tool/LargeTileStampTool.js#L1 LargeTileStamp2

eishiya commented 3 weeks ago

What is this issue actually about? What do you expect me to do?

ManPython commented 3 weeks ago

I'm trying enhance function to draw 4x4 if map is 32x32 and sprite tile 64x64 or 128x128, etc. as bigger. Not have experience with extensions, looks easy to modify, but can't resolve this case. Maybe you can help?

eishiya commented 3 weeks ago

The Large Tile Stamp tool as I wrote it should already do that, unless I misunderstood what you're trying to do. It dynamically determines how many cells to "skip" over based on the size of the tile. For for example, if your grid size is 32x32, then a 64x64 tile will be drawn every other cell, and a 128x128 tile will be drawn every 4th cell, ensuring no overlap between tiles.

If your goal is to do something different, I recommend writing your own script from scratch, using mine only as reference to help you find the relevant parts of the Tiled API. That way, you won't have to deal with strange irrelevant code, and anyone trying to help you will have a better idea of what you're trying to do because they won't be distracted by irrelevant code. Also, the issues section on this repo is for people who need help using the scripts in this repo or find a problem with them, it's not the place to get help with writing your own.

ManPython commented 3 weeks ago

Your script also not working in the way as predicted - not filling 4 cells as 64x64 sprite in 32x32 grids. I was sure that only corners was painted and focused due image: https://github.com/mapeditor/tiled/issues/3436#issuecomment-1361696016