Skip to content

Instantly share code, notes, and snippets.

@Meshiest
Last active October 24, 2020 06:46
Show Gist options
  • Save Meshiest/2a253b88a514610772aa03625d41e23a to your computer and use it in GitHub Desktop.
Save Meshiest/2a253b88a514610772aa03625d41e23a to your computer and use it in GitHub Desktop.
tool for scanning bricks in saves
window.$tool = save => {
// shallow equality
const eq = (a, b) => a === b || a.every && a.every((v, i) => v === b[i]);
// Helper function to build dictionaries from arrays, I could technically just use indexOf but w/e
const indexify = (arr, key) =>
Object.fromEntries(Object.entries(arr || []).map(a =>
[key ? a[1][key] : a[1], Number(a[0])]));
// Lookup tables to parse from string to value
const matTable = indexify(save.materials);
const assetTable = indexify(save.brick_assets);
const nameTable = indexify(save.brick_owners, 'name'); // both of these are off by 1 due to 1-indexing
const idTable = indexify(save.brick_owners, 'id'); // both of these are off by 1 due to 1-indexing
let $;
return $ = {
// Convert x, y, z to studs
studs: (x, y, z) => [x * 10, y * 10, z * 4],
// Get all bricks above a specific brick
above: b => {
const size = b.rotation === 1 || b.rotation == 3 ? [b.size[1], b.size[0], b.size[2]] : b.size;
const min = [b.position[0] - size[0] - 2, b.position[1] - size[1] - 2, b.position[2] + 1];
const max = [b.position[0] + size[0] + 2, b.position[1] + size[1] + 2, Infinity];
return save.bricks.filter(b => b.position.every((p, i) => p >= min[i] && p <= max[i]));
},
// Get all plates 1 above a plate
aboveBP: b => {
const size = b.rotation === 1 || b.rotation == 3 ? [b.size[1], b.size[0], b.size[2]] : b.size;
const min = [b.position[0] - size[0], b.position[1] - size[1], b.position[2] + 4];
const max = [b.position[0] + size[0], b.position[1] + size[1], b.position[2] + 4];
return save.bricks.filter(b => b.position.every((p, i) => p >= min[i] && p <= max[i]));
},
// Deep copy an object
copy: obj => JSON.parse(JSON.stringify(obj)),
// Get the center of mass of multiple bricks
center: bricks => {
let pos = [0, 0, 0];
bricks.forEach(b => b.position.forEach((p, i) => pos[i] += p));
return pos.map(p => Math.round(p / bricks.length));
},
// Shift a group of bricks
shifted: (bricks, shift) => {
return bricks.map(b => {
const copy = $.copy(b);
copy.position.forEach((p, i) => copy.position[i] = p + shift[i]);
return copy;
});
},
// Query the save, or a specified brick list
find: ({asset='', size, rotation=-1, material='', id='', color, name=''}, bricks) =>
(bricks || save.bricks).filter(b =>
(!size || eq(b.size, size)) &&
(typeof color === 'undefined' || eq(b.color, color)) &&
(!asset || b.asset_name_index === assetTable[asset]) &&
(!material || b.material_index === matTable[material]) &&
(!id || b.owner_index === idTable[id] + 1) &&
(!name || b.owner_index === nameTable[name] + 1) &&
(rotation === -1 || b.rotation === rotation)
),
findAsset: name => assetTable[name],
findMaterial: name => matTable[name],
brick: ({asset='PB_DefaultBrick', material='BMC_Plastic', position, rotation=0, direction=4, color=[255, 255, 255, 255], size=[10,10,10]}) => ({
asset_name_index: $.findAsset(asset),
color,
material_index: typeof material === 'number' ? material : $.findMaterial(material),
size,
rotation,
direction,
position,
}),
// Parse a tile from a baseplate
tile: plate => {
// Plates on top of the baseplate
const plates = $.aboveBP(plate);
// Certain fields are denoted by materials
const id = $.find({material: 'BMC_Glow', color: 0}, plates);
const directionBrick = $.find({material: 'BMC_Metallic', color: 0}, plates);
const tile = $.find({material: 'BMC_Plastic', color: 0}, plates);
// Invalid tile
if (id.length !== 1 || tile.length !== 1 || directionBrick.length > 1)
return null;
const knights = $.find({asset: 'B_Knight'}, $.above(directionBrick[0]));
const cones = $.find({asset: 'B_Cone'}, $.above(directionBrick[0]));
// color based directions
const links = Array.from({length: save.colors.length}).map(() => new Set());
const axis = Array.from({length: save.colors.length}).map(() => new Set());
for (let k of knights) {
links[k.color].add(k.rotation);
}
for (let c of cones) {
axis[c.color].add(c.direction);
}
let obj;
return obj = {
id: $.above(id[0]).map(b => save.brick_assets[b.asset_name_index].replace(/^\w+?_/, '')).sort().join(' '),
size: tile[0].size[0] / 5,
axis,
links,
owner: save.brick_owners[tile[0].owner_index - 1],
mask: (i=0) => ( // masks make comparing directions much easier
!links[i] || links[i].size === 0 ? 0 :
(links[i].has(3) ? 1 : 0) << 3 |
(links[i].has(2) ? 1 : 0) << 2 |
(links[i].has(1) ? 1 : 0) << 1 |
(links[i].has(0) ? 1 : 0) << 0
),
bricks: [],
init: () => {
obj.bricks = $.shifted($.above(tile[0]), tile[0].position.map(p => -p));
}
};
},
write: bricks => {
const author = {
id: '039b96e9-1646-4b7d-9434-4c726218c6fa',
name: 'Generator',
};
// overwrite ownership
bricks.forEach(b => {
b.owner_index = 1;
});
console.log('Output Brick Count:', bricks.length);
const output = {
author,
description: 'Generated Save',
map: 'brs-js',
brick_owners: [author],
materials: save.materials,
brick_assets: save.brick_assets,
colors: save.colors,
bricks,
};
console.time('Saving');
const blob = new Blob([BRS.write(output)]);
console.timeEnd('Saving');
jsonElem.innerText += '\nWrote ' + bricks.length + ' bricks';
anchor.href = URL.createObjectURL(blob);
},
matTable,
assetTable,
nameTable,
idTable,
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment