@pixelvibing/cli 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3456,6 +3456,11 @@ function numbers(args, names) {
3456
3456
  return read;
3457
3457
  }
3458
3458
  const mapShow = reading("map show", "Print the linked project's map outline: its revision, size, levels and the sectors they hold", "/map");
3459
+ const mapObjects = reading(
3460
+ "map objects",
3461
+ "Print the linked project's map's named points and areas, with their types and properties (specs/ide-map-editor/ R2.1)",
3462
+ "/map/objects"
3463
+ );
3459
3464
  const mapVersions = reading(
3460
3465
  "map versions",
3461
3466
  "Print the version of each sector a level of the linked project's map holds",
@@ -4080,6 +4085,188 @@ function packSheets(items, side = SHEET_SIDE, gap = SHEET_GAP) {
4080
4085
  placements: placements.sort(byName$1)
4081
4086
  };
4082
4087
  }
4088
+ const TILED_MAP = "map.tiled.json";
4089
+ const TILES_IMAGE = "tiles.png";
4090
+ const BLOCKING_IMAGE = "blocking.png";
4091
+ const propertyOf = (name, value) => ({
4092
+ name,
4093
+ type: typeof value === "boolean" ? "bool" : typeof value === "number" ? Number.isInteger(value) ? "int" : "float" : "string",
4094
+ value
4095
+ });
4096
+ const plainTile = (sprite) => sprite.width === TILE_SIZE && sprite.height === TILE_SIZE && sprite.animation === null && sprite.x === sprite.tile[0] * TILE_SIZE && sprite.y === sprite.tile[1] * TILE_SIZE;
4097
+ function tiledMap(input, codec2) {
4098
+ const { width, height } = input;
4099
+ const tileNames = [...new Set(input.sprites.filter(plainTile).map((sprite) => sprite.frame))].filter((name) => input.rasters.has(name)).sort();
4100
+ const columns = Math.max(1, Math.ceil(Math.sqrt(tileNames.length)));
4101
+ const rows = Math.max(1, Math.ceil(tileNames.length / columns));
4102
+ const sheet = blank(columns * TILE_SIZE, rows * TILE_SIZE);
4103
+ tileNames.forEach((name, index) => {
4104
+ const raster = input.rasters.get(name);
4105
+ const left = index % columns * TILE_SIZE;
4106
+ const top = Math.floor(index / columns) * TILE_SIZE;
4107
+ for (let y = 0; y < TILE_SIZE; y += 1)
4108
+ sheet.data.set(raster.data.subarray(y * TILE_SIZE * 4, (y + 1) * TILE_SIZE * 4), ((top + y) * sheet.width + left) * 4);
4109
+ });
4110
+ const gidOf = new Map(tileNames.map((name, index) => [name, index + 1]));
4111
+ const blockingGid = tileNames.length + 1;
4112
+ const mark = blank(TILE_SIZE, TILE_SIZE);
4113
+ for (let at = 0; at < mark.data.length; at += 4) mark.data.set([220, 40, 40, 128], at);
4114
+ const levels = [
4115
+ .../* @__PURE__ */ new Set([...input.sprites.map((each) => each.level), ...input.blocking.map((each) => each.level), ...input.places.map((each) => each.level)])
4116
+ ].sort((a, b) => a - b);
4117
+ let nextLayer = 1;
4118
+ let nextObject = 1;
4119
+ const layerPaths = [];
4120
+ const groups = levels.map((level) => {
4121
+ const group = `level-${level}`;
4122
+ const bands = /* @__PURE__ */ new Map();
4123
+ for (const sprite of input.sprites) {
4124
+ if (sprite.level !== level || !plainTile(sprite) || !gidOf.has(sprite.frame)) continue;
4125
+ const [x, y] = sprite.tile;
4126
+ if (x < 0 || y < 0 || x >= width || y >= height) continue;
4127
+ const stack = bands.get(sprite.layer) ?? [];
4128
+ bands.set(sprite.layer, stack);
4129
+ const index = y * width + x;
4130
+ let data = stack.find((layer) => layer[index] === 0);
4131
+ if (data === void 0) {
4132
+ data = new Array(width * height).fill(0);
4133
+ stack.push(data);
4134
+ }
4135
+ data[index] = gidOf.get(sprite.frame);
4136
+ }
4137
+ const tileLayers = [...bands].flatMap(
4138
+ ([band, stack]) => stack.map((data, index) => {
4139
+ const name = index === 0 ? band : `${band}-${index + 1}`;
4140
+ layerPaths.push(`${group}/${name}`);
4141
+ return { id: nextLayer++, name, type: "tilelayer", x: 0, y: 0, width, height, opacity: 1, visible: true, data };
4142
+ })
4143
+ );
4144
+ const blocking = new Array(width * height).fill(0);
4145
+ for (const cell of input.blocking)
4146
+ if (cell.level === level && cell.x >= 0 && cell.y >= 0 && cell.x < width && cell.y < height) blocking[cell.y * width + cell.x] = blockingGid;
4147
+ layerPaths.push(`${group}/blocking`);
4148
+ const objects = [
4149
+ ...input.sprites.filter((sprite) => sprite.level === level && !plainTile(sprite)).map((sprite) => ({
4150
+ id: nextObject++,
4151
+ name: sprite.frame,
4152
+ type: "sprite",
4153
+ x: sprite.x,
4154
+ y: sprite.y,
4155
+ width: 0,
4156
+ height: 0,
4157
+ rotation: 0,
4158
+ visible: true,
4159
+ point: true,
4160
+ properties: [
4161
+ propertyOf("frame", sprite.frame),
4162
+ ...sprite.animation === null ? [] : [propertyOf("animation", sprite.animation)],
4163
+ propertyOf("width", sprite.width),
4164
+ propertyOf("height", sprite.height),
4165
+ propertyOf("band", sprite.layer)
4166
+ ]
4167
+ })),
4168
+ ...input.places.filter((place) => place.level === level).map((place) => ({
4169
+ id: nextObject++,
4170
+ name: place.name,
4171
+ type: place.type,
4172
+ x: place.x,
4173
+ y: place.y,
4174
+ width: place.kind === "area" ? place.width : 0,
4175
+ height: place.kind === "area" ? place.height : 0,
4176
+ rotation: 0,
4177
+ visible: true,
4178
+ ...place.kind === "point" ? { point: true } : {},
4179
+ properties: Object.entries(place.properties).map(([name, value]) => propertyOf(name, value))
4180
+ }))
4181
+ ];
4182
+ layerPaths.push(`${group}/objects`);
4183
+ return {
4184
+ id: nextLayer++,
4185
+ name: group,
4186
+ type: "group",
4187
+ x: 0,
4188
+ y: 0,
4189
+ opacity: 1,
4190
+ visible: true,
4191
+ layers: [
4192
+ ...tileLayers,
4193
+ { id: nextLayer++, name: "blocking", type: "tilelayer", x: 0, y: 0, width, height, opacity: 0.5, visible: false, data: blocking },
4194
+ { id: nextLayer++, name: "objects", type: "objectgroup", x: 0, y: 0, opacity: 1, visible: true, draworder: "topdown", objects }
4195
+ ]
4196
+ };
4197
+ });
4198
+ const map = {
4199
+ type: "map",
4200
+ version: "1.10",
4201
+ tiledversion: "1.12.0",
4202
+ orientation: "orthogonal",
4203
+ renderorder: "right-down",
4204
+ infinite: false,
4205
+ compressionlevel: -1,
4206
+ width,
4207
+ height,
4208
+ tilewidth: TILE_SIZE,
4209
+ tileheight: TILE_SIZE,
4210
+ nextlayerid: nextLayer,
4211
+ nextobjectid: nextObject,
4212
+ layers: groups,
4213
+ tilesets: [
4214
+ {
4215
+ firstgid: 1,
4216
+ name: "tiles",
4217
+ image: TILES_IMAGE,
4218
+ imagewidth: sheet.width,
4219
+ imageheight: sheet.height,
4220
+ tilewidth: TILE_SIZE,
4221
+ tileheight: TILE_SIZE,
4222
+ tilecount: tileNames.length,
4223
+ columns,
4224
+ margin: 0,
4225
+ spacing: 0,
4226
+ // Each tile's asset name, so the game and Tiled both say what a tile is.
4227
+ tiles: tileNames.map((name, id2) => ({ id: id2, properties: [propertyOf("asset", name)] }))
4228
+ },
4229
+ {
4230
+ firstgid: blockingGid,
4231
+ name: "blocking",
4232
+ image: BLOCKING_IMAGE,
4233
+ imagewidth: TILE_SIZE,
4234
+ imageheight: TILE_SIZE,
4235
+ tilewidth: TILE_SIZE,
4236
+ tileheight: TILE_SIZE,
4237
+ tilecount: 1,
4238
+ columns: 1,
4239
+ margin: 0,
4240
+ spacing: 0
4241
+ }
4242
+ ]
4243
+ };
4244
+ const typesOf = (kind) => [...new Set(input.places.filter((place) => place.kind === kind).map((place) => place.type))].sort();
4245
+ return {
4246
+ files: [
4247
+ { path: TILED_MAP, bytes: new TextEncoder().encode(`${JSON.stringify(map, null, 2)}
4248
+ `) },
4249
+ { path: TILES_IMAGE, bytes: codec2.encodePng(sheet) },
4250
+ { path: BLOCKING_IMAGE, bytes: codec2.encodePng(mark) }
4251
+ ],
4252
+ layers: layerPaths,
4253
+ pointTypes: typesOf("point"),
4254
+ areaTypes: typesOf("area")
4255
+ };
4256
+ }
4257
+ function mapNames(output) {
4258
+ const list2 = (values) => `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
4259
+ return [
4260
+ "// Written by PixelVibing's export with the map: the names its layers, points and areas have.",
4261
+ `export const MAP_LAYERS = ${list2(output.layers)} as const;`,
4262
+ "export type MapLayer = (typeof MAP_LAYERS)[number];",
4263
+ `export const POINT_TYPES = ${list2(output.pointTypes)} as const;`,
4264
+ "export type PointType = (typeof POINT_TYPES)[number];",
4265
+ `export const AREA_TYPES = ${list2(output.areaTypes)} as const;`,
4266
+ "export type AreaType = (typeof AREA_TYPES)[number];",
4267
+ ""
4268
+ ].join("\n");
4269
+ }
4083
4270
  const ENGINES = ["default", "pixi", "phaser", "godot"];
4084
4271
  const EXPORT_FORMAT = 1;
4085
4272
  const DEFAULT_GODOT_ROOT = "res://pixelvibing/";
@@ -4302,10 +4489,27 @@ function phaserFiles(made) {
4302
4489
  return [
4303
4490
  json("atlas.json", atlas),
4304
4491
  json("animations.json", anims),
4492
+ // The placed sprites, for a game written before the Tiled map (specs/ide-map-editor/ R3.5).
4305
4493
  json("map.json", defaultMap(made)),
4306
- ...made.nineGrids.size === 0 ? [] : [json("nine-slices.json", { format: EXPORT_FORMAT, atlas: PHASER_ATLAS_KEY, frames: nineSlices })]
4494
+ ...made.nineGrids.size === 0 ? [] : [json("nine-slices.json", { format: EXPORT_FORMAT, atlas: PHASER_ATLAS_KEY, frames: nineSlices })],
4495
+ ...tiledFiles(made)
4307
4496
  ];
4308
4497
  }
4498
+ function tiledFiles(made) {
4499
+ if (made.map === null) return [];
4500
+ const output = tiledMap(
4501
+ {
4502
+ width: made.map.width,
4503
+ height: made.map.height,
4504
+ sprites: made.sprites,
4505
+ blocking: made.map.tiles.filter((tile) => tile.blocking !== 0).map((tile) => ({ x: tile.x, y: tile.y, level: tile.z })),
4506
+ rasters: made.rasters,
4507
+ places: made.places
4508
+ },
4509
+ made.codec
4510
+ );
4511
+ return [...output.files, text("map-names.ts", mapNames(output))];
4512
+ }
4309
4513
  const godotString = (value) => JSON.stringify(value);
4310
4514
  const number = (value) => Number.isInteger(value) ? `${value}` : `${Number(value.toFixed(6))}`;
4311
4515
  const rect = (placed) => `Rect2(${placed.x}, ${placed.y}, ${placed.width}, ${placed.height})`;
@@ -4457,7 +4661,7 @@ function exportProject(input, engine, codec2, options = {}) {
4457
4661
  const animations = animationsOf(metadata, rasters, omitted);
4458
4662
  const sprites = placedSprites(input, metadata, rasters, omitted);
4459
4663
  const sheets = sheetsOf(rasters, codec2);
4460
- const made = { sheets, animations, sprites, map: input.map, nineGrids: nineGridsOf(metadata, rasters) };
4664
+ const made = { sheets, animations, sprites, map: input.map, nineGrids: nineGridsOf(metadata, rasters), rasters, places: input.places ?? [], codec: codec2 };
4461
4665
  const godotRoot = options.godotRoot ?? DEFAULT_GODOT_ROOT;
4462
4666
  const described = engine === "default" ? defaultFiles(made) : engine === "pixi" ? pixiFiles(made) : engine === "phaser" ? phaserFiles(made) : godotFiles(made, godotRoot.endsWith("/") ? godotRoot : `${godotRoot}/`);
4463
4667
  return {
@@ -4642,7 +4846,7 @@ async function exportFolder(art, engine, options = {}) {
4642
4846
  try {
4643
4847
  const boundary = new Boundary([art]);
4644
4848
  const read = await readProjectArt(boundary, art);
4645
- const mirrored2 = await readMirroredProjectMap(boundary, art);
4849
+ const mirrored2 = options.map ?? await readMirroredProjectMap(boundary, art);
4646
4850
  const mapText = mirrored2 === null ? await readProjectMap(boundary, art) : null;
4647
4851
  checkExportable(
4648
4852
  read.pictures.map((picture) => {
@@ -4662,7 +4866,8 @@ async function exportFolder(art, engine, options = {}) {
4662
4866
  pictures,
4663
4867
  entries: read.entries,
4664
4868
  map: mirrored2 ?? (mapText === null ? null : parseMapText(mapText, "pixelvibing/map.json").document),
4665
- idMap: read.idMap === null ? null : parseIdMap(JSON.parse(read.idMap))
4869
+ idMap: read.idMap === null ? null : parseIdMap(JSON.parse(read.idMap)),
4870
+ ...options.places === void 0 ? {} : { places: options.places }
4666
4871
  },
4667
4872
  engine,
4668
4873
  codec$1,
@@ -7506,7 +7711,7 @@ function terminalOf(streams) {
7506
7711
  step: (title, at = "step") => drawStep(streams.stderr, title, at)
7507
7712
  };
7508
7713
  }
7509
- const version = "0.71.0";
7714
+ const version = "0.73.0";
7510
7715
  const manifest = {
7511
7716
  version
7512
7717
  };
@@ -7562,6 +7767,7 @@ const COMMANDS = [
7562
7767
  artTileset,
7563
7768
  artDraft,
7564
7769
  mapShow,
7770
+ mapObjects,
7565
7771
  mapVersions,
7566
7772
  mapSectors,
7567
7773
  mapWrite,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelvibing/cli",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "description": "PixelVibing: pixel art game assets from the command line, and the Skills that teach coding assistants to make them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://pixelvibing.com",