Last active
October 24, 2020 06:46
-
-
Save Meshiest/2a253b88a514610772aa03625d41e23a to your computer and use it in GitHub Desktop.
tool for scanning bricks in saves
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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