zeel01 / scene-tiler

2 stars 1 forks source link

Programmatically create tile from scene #2

Closed ggagnon76 closed 3 years ago

ggagnon76 commented 3 years ago

More a request than an issue:

I am brainstorming my next module, which will be a randomly generated labyrinth made up of tiles. At first I thought of using Token Attacher to have a 'prefab' with background (the tile), walls, lights, sounds, tokens, etc..

Upon reflection, I am inclined to think that Scene Tiler might work better, especially if I want to make modifications to individual scenes during development. Also because of what I want to do with stuff in the flags of the scene.

My request would be an API (?) in Scene Tiler where I could generate a tile via a function I could call in my module. I would also need the flags from the scene to be copied to the flags on the tile.

Is this too much of an ask? If you want to discuss, I can be reached in the league discord via: Kerrec Snowmane#5264

Thanks!

zeel01 commented 3 years ago

This is something I've been thinking about actually, I just haven't had an actual reason to do it. I'll give you a ping on Discord when I have the opportunity.

zeel01 commented 3 years ago

You can call SceneTiler.createTile(source, uuid, x, y) where source is a Scene, and that should create the tile. You don't get much control over the process, but calling this directly should work just as well as drag & dropping does.

ggagnon76 commented 3 years ago

uuid is the id of the source scene?

And SceneTiler.createTile() is available in global scope?

zeel01 commented 3 years ago

It is available in global scope, yes. UUIDs are not just the ID of the document, they are a bit more complicated. See the dropCanvasData method for how I construct the UUID (these are a Foundry core concept, and are nessesary for locating scenes stored in compendiums).

Also, once you create the tile, if you programmatically update it to be locked, that should trigger the rest of the deployment.

ggagnon76 commented 3 years ago

I kind of see. I'm a hobbyist at this, so not able to follow completely at first glance. Here's how I'm reading this: If I store my scenes in a compendium (not in the scenes folder), I can incorporate them directly by creating 'source' from the UUID, then feeding source, UUID and x/y to your createTile() method. No need for the scene to ever be in the scene folder. Is that correct?

karpana commented 3 years ago

Hey @ggagnon76 I wanted to add my "vote" to this request as well. I'm also pondering the idea of "generated" dungeon.

My approach has been to use the awesome DungeonMorph maps (from Inkwell Ideas), which I've already extracted from the PDF I bought some time ago. I created rotated versions of every map (base 90 180 270 degrees), as well as mirrored version of the map. so I have some 800+ scenes that bulk imported.

I've then created a small randomizer (using roll tables) to randomize the selection of which map should be presented, and it shows that to the GM only.

The problem is I don't know how to "automagically" put the tile onto the scene (without dragging), which is why I came here.

So @zeel01 , perhaps we can consider this my vote for this. I am a developer by profession, but this Foundry VTT system (and java) are well outside my wheelhouse.

karpana commented 3 years ago

I will try to use the prior comments in this thread to try and make this work in my context. (my scenes are just jammed into a folder inside of the campaign, but themselves will be sourced from a compendium when I'm ready)

karpana commented 3 years ago

This is what I have at this point. I'll break the code up into parts, even though it's all one macro...

console.log ("====start====")
const Side        = await game.tables.entities.find(t => t.name === "DungeonMorph_Side");
const Deck        = await game.tables.entities.find(t => t.name === "DungeonMorph_Deck");
const Orientation = await game.tables.entities.find(t => t.name === "DungeonMorph_Orientation");
const Mode        = await game.tables.entities.find(t => t.name === "DungeonMorph_Mode");

SideResults        = await Side.roll()
DeckResults        = await Deck.roll()
OrientationResults = await Orientation.roll()
ModeResults        = await Mode.roll()

//console.log (SideResults)
//console.log (DeckResults)
//console.log (OrientationResults)
//console.log (ModeResults)

//console.log (SideResults.results[0].data.text)
//console.log (DeckResults.results[0].data.text)
//console.log (OrientationResults.results[0].data.text)
//console.log (ModeResults.results[0].data.text)

MapTile = SideResults.results[0].data.text + 
          DeckResults.results[0].data.text + 
          "_" +
          OrientationResults.results[0].data.text + 
          "_" +
          ModeResults.results[0].data.text 
console.log (MapTile)

At this point, I have the name of the "scene" I want to use as a tile in the variable MapTile but when I try to load the scene as a tile ... nothing happens. And all subsequent attemptws to use the the macro fail until I reload the browser.

Scene = game.scenes.find(t => t.name === MapTile)
console.log (Scene)

SceneTiler.createTile(Scene, Scene.uuid, 5000,5000)

console.log ("=====end=====")
karpana commented 3 years ago

after taking a look at dropCanvasData, I was able to get this working.

const Side        = await game.tables.entities.find(t => t.name === "DungeonMorph_Side");
const Deck        = await game.tables.entities.find(t => t.name === "DungeonMorph_Deck");
const Orientation = await game.tables.entities.find(t => t.name === "DungeonMorph_Orientation");
const Mode        = await game.tables.entities.find(t => t.name === "DungeonMorph_Mode");

SideResults        = await Side.roll()
DeckResults        = await Deck.roll()
OrientationResults = await Orientation.roll()
ModeResults        = await Mode.roll()

MapTile = SideResults.results[0].data.text + 
          DeckResults.results[0].data.text + 
          "_" +
          OrientationResults.results[0].data.text + 
          "_" +
          ModeResults.results[0].data.text 
console.log (MapTile)

Up to this point, all this was about was "generating" the scene name we want to turn into a tile, and store it in the MapTile Variable

const SceneTile = await game.scenes.find(t => t.name === MapTile)
const source = await fromUuid(SceneTile.uuid)
const SceneUuid = SceneTile.uuid
const LocalPosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.stage)

//SceneTiler is going to want to place the "middle" of the Scene as a token at the cursor.
//We need to adjust that...
x = LocalPosition.x + (SceneTile.dimensions.sceneHeight / 2)
y = LocalPosition.y + (SceneTile.dimensions.sceneWidth  / 2)

//Now we need to locate the top left corner of the "target grid square"
const LocalGridSize = canvas.scene.dimensions.size;
x = Math.floor(x / LocalGridSize ) * LocalGridSize 
y = Math.floor(y / LocalGridSize ) * LocalGridSize 

SceneTiler.createTile(source, SceneUuid, x, y)

The only thing left to figure out, is how to "Lock" the tile to pull forward all of the other properties (like walls and stuff), which I've not quite setup yet...

ggagnon76 commented 3 years ago

The module I'm in the process of creating (very slowly) is probably fits your use case. Unfortunately my free time is scarce and my progress is slow.

As for your request to Zeel, all the functions in Scene Tiler are already available globally. I was able to generate a scene as a tile from a compendium scene and successfully deploy it into my empty scene. NOTE that for Zeel's Scene Tiler to work, the scene has to be in a compendium.

I would hate to duplicate work, but I wouldn't want to inflict my development pace on any one else. You can look at my code and take what you need to get your implementation working.

https://github.com/ggagnon76/scene-zoetrope/blob/master/scripts/zoetrope.js

If you have any questions about the code and why I did things the way I did, you can open an issue in my repo and I will respond there, as it would be out of scope in this thread.

ggagnon76 commented 3 years ago

Oh, I also meant to point out that Zeel's Scene Tiler allows for the scene/tile to be rotated. So all your duplicate images with different orientations are not necessary.

karpana commented 3 years ago

Thanks for the tip about rotation... I figured out how to do that and now my script looks like this...

All I need to now is figure out how to lock the tile to bring forward the related elements like walls and stuff...

const Side        = await game.tables.entities.find(t => t.name === "DungeonMorph_Side");
const Deck        = await game.tables.entities.find(t => t.name === "DungeonMorph_Deck");
const Orientation = await game.tables.entities.find(t => t.name === "DungeonMorph_Orientation");
const Mode        = await game.tables.entities.find(t => t.name === "DungeonMorph_Mode");

SideResults        = await Side.roll()
DeckResults        = await Deck.roll()
OrientationResults = await Orientation.roll()
ModeResults        = await Mode.roll()

MapTile = SideResults.results[0].data.text + 
          DeckResults.results[0].data.text + 
          "_" +
          "000" +
          "_" +
          ModeResults.results[0].data.text 
console.log (MapTile)

const SceneTile = await game.scenes.find(t => t.name === MapTile)
const source = await fromUuid(SceneTile.uuid)
const SceneUuid = SceneTile.uuid
const LocalPosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.stage)

//SceneTiler is going to want to place the "middle" of the Scene as a token at the cursor.
//We need to adjust that...
x = LocalPosition.x + (SceneTile.dimensions.sceneHeight / 2)
y = LocalPosition.y + (SceneTile.dimensions.sceneWidth  / 2)

//Now we need to locate the top left corner of the "target grid square"
const LocalGridSize = canvas.scene.dimensions.size;
x = Math.floor(x / LocalGridSize ) * LocalGridSize 
y = Math.floor(y / LocalGridSize ) * LocalGridSize 

//Locate the tile we just created on the canvas, and rotate it...
NewTile = await SceneTiler.createTile(source, SceneUuid, x, y)
CanvasTile = canvas.scene.tiles.find(t => t.id === NewTile[0]._id)
await CanvasTile.update({rotation: parseInt(OrientationResults.results[0].data.text) })
ggagnon76 commented 3 years ago

I didn't lock it, since I want to be able to translate everything programmatically as necessary.

I used this part of Zeel's code:

SceneTiler.deploySceneTile(myTile[0].data);

You'll have to replace 'myTile[0].data' above with your equivalent. Should be enough to get you going.

zeel01 commented 3 years ago

You can lock the tile by updating it like:

await tile.update({ locked: true });

This is essentially exactly what happens when you click the lock button from the GUI.

zeel01 commented 3 years ago

Really cool stuff you guys have been doing, it may be useful to have an ongoing conversation regarding this on Discord. Feel free to give me a ping (zeel#4200) in the League of Foundry Devs Discord: https://discord.gg/feRvbhVnvf

I'm especially interested in any suggestions for how I could make using the the module's API better for integrations. I think all the right pieces are there, but I might be able to make working with them a bit simpler.

zeel01 commented 3 years ago

I think this is complete?