1
0
mirror of https://github.com/teoxoy/factorio-blueprint-editor.git synced 2025-11-23 22:15:01 +02:00
Files
factorio-blueprint-editor/parser/processRawData.js
2018-03-29 07:54:56 +02:00

467 lines
16 KiB
JavaScript

const fse = require('fs-extra')
const nsg = require('node-sprite-generator')
const Jimp = require('jimp')
const util = require('util')
//const factorioDirectory = 'C:/SteamLibrary/steamapps/common/Factorio/data/'
const factorioDirectory = 'C:/_Programs/Steam/steamapps/common/Factorio/data/'
const outDir = '../src/bundles/'
const spritesheetsOutDir = '../src/spritesheets/'
function nameMapping(imagePath) {
const sP = imagePath.split('/')
return sP.splice(sP.length - 2).join('/').split('.')[0]
}
let rawData = JSON.parse(fse.readFileSync('./temp.json').toString()
.replace(/"(up|down|left|right|north|south|west|east)"/g, function(match, capture) {
if (capture === 'north' || capture === 'up') return '"0"'
if (capture === 'east' || capture === 'left') return '"2"'
if (capture === 'south' || capture === 'down') return '"4"'
if (capture === 'west' || capture === 'right') return '"6"'
}))
let tiles = {}
for (const k in rawData.tile) {
if (rawData.tile[k].minable) tiles[k] = rawData.tile[k]
}
console.log('Tiles: ' + Object.keys(tiles).length)
fse.writeFileSync(outDir + 'tileBundle.json', JSON.stringify(tiles, null, 2).replace(/__base__|__core__/g, 'factorio-data'))
console.log('Recipes: ' + Object.keys(rawData.recipe).length)
fse.writeFileSync(outDir + 'recipeBundle.json', JSON.stringify(rawData.recipe, null, 2).replace(/__base__|__core__/g, 'factorio-data'))
let inventory = []
let items = {}
let placeableEntities = ['curved-rail']
const blacklistedGroups = [
'environment',
'enemies',
'other'
]
for (const k in rawData['item-group']) {
const group = rawData['item-group'][k]
if (!blacklistedGroups.includes(group.name)) {
group.subgroups = []
inventory.push(group)
}
}
for (const k in rawData['item-subgroup']) {
const subgroup = rawData['item-subgroup'][k]
subgroup.items = []
for (const group of inventory) {
if (group.name === subgroup.group) {
group.subgroups.push(subgroup)
break
}
}
}
function findAllItems(data) {
if (data.constructor === Object) {
if (data.hasOwnProperty('subgroup')) {
addItem(data)
} else {
for (const k in data) {
if (data.hasOwnProperty(k)) {
findAllItems(data[k])
}
}
}
}
}
findAllItems(rawData)
for (const k in rawData['fluid']) {
const fluid = rawData['fluid'][k]
fluid.subgroup = 'fluid'
addItem(fluid)
}
function addItem(item) {
if ((item.flags && item.flags.includes('hidden')) || !(item.icon || item.icons) || !item.order || item.collision_box) return
for (let j = 0; j < inventory.length; j++) {
for (let k = 0; k < inventory[j].subgroups.length; k++) {
if (inventory[j].subgroups[k].name === item.subgroup) {
inventory[j].subgroups[k].items.push(item)
if (item.place_result) placeableEntities.push(item.place_result)
items[item.name] = item
return
}
}
}
}
console.log('Items: ' + Object.keys(items).length)
fse.writeFileSync(outDir + 'itemBundle.json', JSON.stringify(items, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
return '"icon:' + nameMapping(capture) + '"'
}))
// sort and remove extra info from inventoryBundle
inventory.sort(sortByOrder)
for (let i = 0; i < inventory.length; i++) {
inventory[i].subgroups.sort(sortByOrder)
for (let j = 0; j < inventory[i].subgroups.length; j++) {
inventory[i].subgroups[j].items.sort(sortByOrder)
for (let k = 0; k < inventory[i].subgroups[j].items.length; k++) {
removeExtraInfo(inventory[i].subgroups[j].items[k])
}
removeExtraInfo(inventory[i].subgroups[j])
}
removeExtraInfo(inventory[i])
}
function sortByOrder(a, b) {
// https://forums.factorio.com/viewtopic.php?f=25&t=3236#p23818
// https://forums.factorio.com/viewtopic.php?f=25&t=24163#p152955
if (a.order < b.order) return -1
if (a.order > b.order) return 1
return 0
}
function removeExtraInfo(obj) {
for (const k of Object.keys(obj)) {
if (!['subgroups', 'items', 'name', 'icon', 'icons'].includes(k)) delete obj[k]
}
}
fse.writeFileSync(outDir + 'inventoryBundle.json', JSON.stringify(inventory, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
return '"icon:' + nameMapping(capture) + '"'
}))
let paths = []
for (let i = 0, l = inventory.length; i < l; i++) {
paths.push(factorioDirectory + inventory[i].icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
for (let j = 0, l2 = inventory[i].subgroups.length; j < l2; j++) {
for (let k = 0, l3 = inventory[i].subgroups[j].items.length; k < l3; k++) {
const item = inventory[i].subgroups[j].items[k]
if (item.icon) {
paths.push(factorioDirectory + item.icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
} else {
for (let l = 0; l < item.icons.length; l++) {
paths.push(factorioDirectory + item.icons[l].icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
}
}
}
}
}
paths = Array.from(new Set(paths).values())
console.log('Icon sprites: ' + paths.length)
nsg({
src: paths,
spritePath: spritesheetsOutDir + 'iconSpritesheet.png',
stylesheet: './json-icon.tpl',
stylesheetPath: spritesheetsOutDir + 'iconSpritesheet.json',
stylesheetOptions: {
prefix: 'icon:',
nameMapping: nameMapping
},
compositor: 'jimp',
layout: 'packed',
layoutOptions: {
padding: 2
}
}, function(err) {
if (err)
console.log(err)
else
console.log('Icon sprite atlas generated!')
})
let entities = {}
function findAllEntities(data) {
if (data.constructor === Object) {
if (placeableEntities.includes(data.name) && data.hasOwnProperty('collision_box') && (!data.flags.includes('placeable-off-grid') || data.name === 'land-mine')) {
entities[data.name] = data
} else {
for (let k in data) {
if (data.hasOwnProperty(k)) {
findAllEntities(data[k])
}
}
}
}
}
findAllEntities(rawData)
const regexNameMatches = [
'combinator',
'underground-belt',
'transport-belt',
'splitter',
'inserter',
'turret',
'mining-drill',
'pump'
]
let nameMatches = [
'assembling-machine-2',
'assembling-machine-3',
'pipe-to-ground',
'oil-refinery',
'chemical-plant',
'heat-exchanger',
'boiler',
'train-stop'
]
for (let k in entities) {
// Size
const box = entities[k].selection_box
entities[k].size = {
width: Math.ceil(Math.abs(box[0][0]) + Math.abs(box[1][0])),
height: Math.ceil(Math.abs(box[0][1]) + Math.abs(box[1][1]))
}
// Move out splitters and underground-belts from transport-belt fast_replaceable_group
if (k.search('splitter') !== -1) {
entities[k].fast_replaceable_group = 'splitter'
}
if (k.search('underground-belt') !== -1) {
entities[k].fast_replaceable_group = 'underground-belt'
}
// Possible Rotations
for (let j = 0; j < regexNameMatches.length; j++) {
if (k.includes(regexNameMatches[j])) {
nameMatches.push(k)
}
}
}
// Actual land size of the offshore pump
entities['offshore-pump'].size = { width: 1, height: 1 }
for (let i = 0; i < nameMatches.length; i++) {
entities[nameMatches[i]].possible_rotations = [0, 2, 4, 6]
}
entities['storage-tank'].possible_rotations = [0, 2]
entities['gate'].possible_rotations = [0, 2]
entities['steam-engine'].possible_rotations = [0, 2]
entities['steam-turbine'].possible_rotations = [0, 2]
entities['straight-rail'].possible_rotations = [0, 2]
entities['rail-signal'].possible_rotations = [0, 1, 2, 3, 4, 5, 6, 7]
entities['rail-chain-signal'].possible_rotations = [0, 1, 2, 3, 4, 5, 6, 7]
// End Possible Rotations
// switch dir 2 and 6 for pipe-to-ground
let dir2 = Object.assign({}, entities['pipe-to-ground'].pictures['2'])
entities['pipe-to-ground'].pictures['2'] = entities['pipe-to-ground'].pictures['6']
entities['pipe-to-ground'].pictures['6'] = dir2
// shift.y-1 for dir 4 wall patch of gate
let wp4 = entities['gate'].wall_patch['4'].layers[0]
wp4.shift = [wp4.shift[0], wp4.shift[1] - 1]
if (wp4.hr_version) {
wp4.hr_version.shift = [wp4.hr_version.shift[0], wp4.hr_version.shift[1] - 1]
}
// fix shifts
entities['storage-tank'].pictures.window_background.shift = [0, 1]
entities['storage-tank'].pictures.window_background.hr_version.shift = [0, 1]
add_to_shift([0, -0.6875], entities['artillery-turret'].base_picture.layers[0])
add_to_shift([0, -0.6875], entities['artillery-turret'].cannon_barrel_pictures.layers[0])
add_to_shift([0, -0.6875], entities['artillery-turret'].cannon_base_pictures.layers[0])
function add_to_shift(shift, tab) {
if (tab.shift) {
tab.shift = [shift[0] + tab.shift[0], shift[1] + tab.shift[1]]
} else {
tab.shift = shift
}
if (tab.hr_version) {
if (tab.hr_version.shift) {
tab.hr_version.shift = [shift[0] + tab.hr_version.shift[0], shift[1] + tab.hr_version.shift[1]]
} else {
tab.hr_version.shift = shift
}
}
return tab
}
console.log('Entities: ' + Object.keys(entities).length)
fse.writeFileSync(outDir + 'entityBundle.json', JSON.stringify(entities, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
return '"entity:' + nameMapping(capture) + '"'
}))
graphicsBundle()
async function graphicsBundle() {
let paths = []
let hrPaths = []
let re = /"filename":\s*"([^.]+?\.png)"/g
let str = JSON.stringify(entities)
let match
const excludeKeywords = [
'explosion',
'cloud',
'smoke',
'fire',
'muzzle-flash',
'-light\.padding',
'steam\.png',
'-shadow\.png',
'-shadow-',
'load-standup',
'flamethrower-turret-gun(-[^e]|[^-])',
'pump-[a-z]+?-liquid',
'pump-[a-z]+?-glass',
'accumulator-[a-z]+?-animation',
'connector\/(hr-)?.-.-',
'heated',
'gun-turret-gun-[m12]',
'roboport-recharging',
'segment-visualisation',
'graphics\/[^/]*$',
'-light\.png',
'-lights-color',
'boiling-green',
'power-switch-electricity',
'electric-furnace-heater',
'integration',
'arrows',
'hole',
'rocket-over',
'working',
'hand-closed'
]
const excludeKeywordsRegex = new RegExp(excludeKeywords.join('|'), 'g')
while ((match = re.exec(str)) !== null) {
let path = match[1].replace(/__base__/g, 'base').replace(/__core__/g, 'core')
if (match[1].search(excludeKeywordsRegex) === -1) {
if (match[1].search(/\/hr-/g) === -1) {
if (!paths.includes(path)) {
paths.push(path)
}
} else {
if (!hrPaths.includes(path)) {
hrPaths.push(path)
}
}
}
}
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-1.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-5.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-9.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-13.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-1.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-5.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-9.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-13.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-1.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-5.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-9.png')
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-13.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-1.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-5.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-9.png')
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-13.png')
console.log('Entity images: ' + paths.length)
console.log('Entity HR images: ' + hrPaths.length)
let cropImages = [
['artillery-wagon-cannon', 4, 4],
['flamethrower-turret-gun-extension', 5, 1],
['gun-turret-gun-extension', 5, 1],
['laser-turret-gun-start', 15, 1],
['burner-mining-drill', 4, 8],
['electric-mining-drill', 8, 8],
['pumpjack-horsehead', 8, 5],
['assembling-machine-[1-3]\.png', 8, 4],
['centrifuge', 8, 8],
['lab.png', 11, 3],
['[^e]-pump-', 8, 4],
['splitter', 8, 4],
['radar', 8, 8],
['steam-engine', 8, 4],
['steam-turbine', 4, 2],
['transport-belt', 16, 1],
['laser-turret-gun', 8, 1],
['beacon-antenna', 8, 4],
['roboport-door-', 16, 1],
['gate(-rail(-base)?)?-[a-z]+?(-(left|right))?\.png', 8, 2],
['arm', 4, 3],
['rail-signal\.png', 3, 1],
['rail-chain-signal\.png', 4, 1],
['power-switch', 2, 3]
]
let addedHrPaths = []
let imagesToCrop = []
for (let i = 0; i < paths.length; i++) {
let pArr = paths[i].split('/')
if (pArr[pArr.length - 1] === 'electric-furnace-base.png') {
pArr[pArr.length - 1] = 'electric-furnace.png'
}
pArr[pArr.length - 1] = 'hr-' + pArr[pArr.length - 1]
let hrVersion = pArr.join('/')
if (hrPaths.includes(hrVersion)) {
paths[i] = hrVersion
addedHrPaths.push(hrVersion)
}
paths[i] = factorioDirectory + paths[i]
// Crop spritesheet
for (let j = 0, len2 = cropImages.length; j < len2; j++) {
if (paths[i].search(new RegExp(cropImages[j][0], 'g')) !== -1) {
let p = './temp/' + nameMapping(paths[i]) + '.png'
imagesToCrop.push({
path: paths[i],
outPath: p,
cropImgIndex: j
})
paths[i] = p
break
}
}
}
for (let i = 0; i < hrPaths.length; i++) {
if (!addedHrPaths.includes(hrPaths[i])) {
paths.push(factorioDirectory + hrPaths[i])
}
}
//Jison.write doesn't return a Promise
//https://github.com/oliver-moran/jimp/issues/90
Jimp.prototype.writeAsync = util.promisify(Jimp.prototype.write)
let res = Promise.all(imagesToCrop.map(data => Jimp.read(data.path).then(img =>
img
.crop(0, 0, img.bitmap.width / cropImages[data.cropImgIndex][1], img.bitmap.height / cropImages[data.cropImgIndex][2])
.writeAsync(data.outPath)
)))
res.then(() => {
console.log('Final entity images: ' + paths.length)
nsg({
src: paths,
spritePath: spritesheetsOutDir + 'entitySpritesheet.png',
stylesheet: './json-entity.tpl',
stylesheetPath: spritesheetsOutDir + 'entitySpritesheet.json',
stylesheetOptions: {
prefix: 'entity:',
nameMapping: nameMapping
},
compositor: 'jimp',
layout: 'packed',
layoutOptions: {
padding: 2
}
}, function(err) {
if (err) {
console.log(err)
} else {
fse.remove('./temp')
console.log('Entity sprite atlas generated!')
}
})
})
}