1
0
mirror of https://github.com/teoxoy/factorio-blueprint-editor.git synced 2025-01-14 02:23:21 +02:00

added tiles support

This commit is contained in:
Teoxoy 2018-10-01 20:46:46 +02:00
parent 9ee34bff4d
commit 141daea0cb
12 changed files with 437 additions and 199 deletions

View File

@ -35,6 +35,8 @@ Feel free to contribute to this project, if you have any questions you can conta
- a: 'a'
- s: 's'
- d: 'd'
- increaseTileArea: ']'
- decreaseTileArea: '['
### How to change them:
Add `keybinds:ACTION=KEY,ACTION=KEY,ACTION=KEY` as a parameter to the URL
@ -53,7 +55,6 @@ Example: `https://teoxoy.github.io/factorio-blueprint-editor?keybinds:rotate=t,p
- throughput calculator/bp analyzer/bottleneck detector
- highlight lone underground pipes/belts
- train-stop station name
- tiles support
- poles range, wires and rotations
- rotate bp
- implement circuit_wire_max_distance with visualization ((x - center_x)^2 + (y - center_y)^2 <= radius^2)
@ -61,3 +62,5 @@ Example: `https://teoxoy.github.io/factorio-blueprint-editor?keybinds:rotate=t,p
- rail custom bounding box
- rail rotations
- belt endings
- tile edges
- tile history

View File

@ -8,6 +8,8 @@ import iconSpritesheetPNG from 'factorio-data/data/graphics/iconSpritesheet.png'
import iconSpritesheetJSON from 'factorio-data/data/graphics/iconSpritesheet.json'
import utilitySpritesheetPNG from 'factorio-data/data/graphics/utilitySpritesheet.png'
import utilitySpritesheetJSON from 'factorio-data/data/graphics/utilitySpritesheet.json'
import tilesSpritesheetPNG from './textures/tilesSpritesheet.png'
import tilesSpritesheetJSON from './textures/tilesSpritesheet.json'
import * as PIXI from 'pixi.js'
import keyboardJS from 'keyboardjs'
@ -19,13 +21,14 @@ import util from './util'
import { InventoryContainer } from './containers/inventory'
import G from './globals'
import { EntityContainer } from './containers/entity'
import { PaintContainer } from './containers/paint'
import { EntityPaintContainer } from './containers/entityPaint'
import { BlueprintContainer } from './containers/blueprint'
import { ToolbarContainer } from './containers/toolbar'
import { Blueprint } from './factorio-data/blueprint'
import { EditEntityContainer } from './containers/editEntity'
import { InfoContainer } from './containers/info'
import FileSaver from 'file-saver'
import { TilePaintContainer } from './containers/tilePaint';
let doorbellButton: HTMLElement
window.doorbellOptions = {
@ -94,7 +97,9 @@ const keybinds = {
w: 'w',
a: 'a',
s: 's',
d: 'd'
d: 'd',
increaseTileArea: ']',
decreaseTileArea: '['
}
const params = window.location.search.slice(1).split('&')
@ -169,7 +174,8 @@ Promise.all([bpSource ? util.findBPString(bpSource) : undefined]
.concat([
[ entitySpritesheetPNG, entitySpritesheetJSON ],
[ iconSpritesheetPNG, iconSpritesheetJSON ],
[ utilitySpritesheetPNG, utilitySpritesheetJSON ]
[ utilitySpritesheetPNG, utilitySpritesheetJSON ],
[ tilesSpritesheetPNG, tilesSpritesheetJSON ]
].map(data =>
new Promise((resolve, reject) => {
const image = new Image()
@ -265,7 +271,7 @@ keyboardJS.bind(keybinds.picture, () => {
if (G.renderOnly) G.BPC.cacheAsBitmap = true
G.BPC.updateViewportCulling()
texture.frame = G.BPC.getEntitySpritesBounds()
texture.frame = G.BPC.getBlueprintBounds()
texture._updateUvs()
G.app.renderer.plugins.extract.canvas(new PIXI.Sprite(texture)).toBlob((blob: Blob) => {
@ -313,7 +319,7 @@ keyboardJS.bind(keybinds.pippete, () => {
const hoverContainer = G.BPC.hoverContainer
G.BPC.hoverContainer.pointerOutEventHandler()
const entity = G.bp.entity(hoverContainer.entity_number)
G.BPC.paintContainer = new PaintContainer(entity.name,
G.BPC.paintContainer = new EntityPaintContainer(entity.name,
entity.directionType === 'output' ? (entity.direction + 4) % 8 : entity.direction,
hoverContainer.position)
G.BPC.paintContainer.moveTo({
@ -329,6 +335,18 @@ keyboardJS.bind(keybinds.pippete, () => {
}
})
keyboardJS.bind(keybinds.increaseTileArea, () => {
if (G.BPC.paintContainer instanceof TilePaintContainer) {
G.BPC.paintContainer.increaseSize()
}
})
keyboardJS.bind(keybinds.decreaseTileArea, () => {
if (G.BPC.paintContainer instanceof TilePaintContainer) {
G.BPC.paintContainer.decreaseSize()
}
})
keyboardJS.bind(keybinds.undo, () => {
G.bp.undo(
hist => pre(hist, 'add'),

View File

@ -6,7 +6,9 @@ import { EntitySprite } from '../entitySprite'
import { AdjustmentFilter } from '@pixi/filter-adjustment'
import { EntityContainer } from './entity'
import { OverlayContainer } from './overlay'
import { PaintContainer } from './paint'
import { EntityPaintContainer } from './entityPaint'
import { TileContainer } from './tile'
import { TilePaintContainer } from './tilePaint'
export class BlueprintContainer extends PIXI.Container {
@ -15,8 +17,10 @@ export class BlueprintContainer extends PIXI.Container {
wiresContainer: WiresContainer
overlayContainer: OverlayContainer
underlayContainer: UnderlayContainer
tiles: PIXI.Container
entities: PIXI.Container
movingEntityFilter: AdjustmentFilter
tileSprites: PIXI.Container
entitySprites: PIXI.Container
movementSpeed: number
zoomPan: ZoomPan
@ -25,7 +29,7 @@ export class BlueprintContainer extends PIXI.Container {
pgOverlay: PIXI.Graphics
hoverContainer: undefined | EntityContainer
movingContainer: undefined | EntityContainer
paintContainer: undefined | PaintContainer
paintContainer: undefined | EntityPaintContainer | TilePaintContainer
constructor() {
super()
@ -62,11 +66,21 @@ export class BlueprintContainer extends PIXI.Container {
this.underlayContainer = new UnderlayContainer()
this.addChild(this.underlayContainer)
this.tileSprites = new PIXI.Container()
this.tileSprites.interactive = false
this.tileSprites.interactiveChildren = false
this.addChild(this.tileSprites)
this.entitySprites = new PIXI.Container()
this.entitySprites.interactive = false
this.entitySprites.interactiveChildren = false
this.addChild(this.entitySprites)
this.tiles = new PIXI.Container()
this.tiles.interactive = false
this.tiles.interactiveChildren = true
this.addChild(this.tiles)
this.entities = new PIXI.Container()
this.entities.interactive = false
this.entities.interactiveChildren = true
@ -131,6 +145,9 @@ export class BlueprintContainer extends PIXI.Container {
for (const entity_number of G.bp.rawEntities.keys()) {
this.entities.addChild(new EntityContainer(entity_number, false))
}
G.bp.tiles.forEach((v, k) => {
this.tiles.addChild(new TileContainer(v, { x: Number(k.split(',')[0]), y: Number(k.split(',')[1]) }))
})
this.sortEntities()
this.wiresContainer.drawWires()
@ -145,7 +162,9 @@ export class BlueprintContainer extends PIXI.Container {
clearData() {
const opt = { children: true }
this.tiles.destroy(opt)
this.entities.destroy(opt)
this.tileSprites.destroy(opt)
this.entitySprites.destroy(opt)
this.underlayContainer.destroy(opt)
this.overlayContainer.destroy(opt)
@ -161,10 +180,18 @@ export class BlueprintContainer extends PIXI.Container {
this.underlayContainer = new UnderlayContainer()
this.tileSprites = new PIXI.Container()
this.tileSprites.interactive = false
this.tileSprites.interactiveChildren = false
this.entitySprites = new PIXI.Container()
this.entitySprites.interactive = false
this.entitySprites.interactiveChildren = false
this.tiles = new PIXI.Container()
this.tiles.interactive = false
this.tiles.interactiveChildren = true
this.entities = new PIXI.Container()
this.entities.interactive = false
this.entities.interactiveChildren = true
@ -173,7 +200,10 @@ export class BlueprintContainer extends PIXI.Container {
this.overlayContainer = new OverlayContainer()
this.addChild(this.grid, this.underlayContainer, this.entitySprites, this.entities, this.wiresContainer, this.overlayContainer)
this.addChild(
this.grid, this.underlayContainer, this.tileSprites, this.entitySprites,
this.tiles, this.entities, this.wiresContainer, this.overlayContainer
)
G.currentMouseState = G.mouseStates.NONE
}
@ -217,6 +247,11 @@ export class BlueprintContainer extends PIXI.Container {
}
}
transparentEntities(bool = true) {
this.entities.interactiveChildren = !bool
this.entitySprites.alpha = bool ? 0.5 : 1
}
// For testing
updateOverlay() {
// const TEMP = G.bp.entityPositionGrid.getAllPositions()
@ -229,7 +264,7 @@ export class BlueprintContainer extends PIXI.Container {
}
centerViewport() {
if (G.bp.rawEntities.size === 0) {
if (G.bp.isEmpty()) {
this.zoomPan.setPosition(-G.sizeBPContainer.width / 2, -G.sizeBPContainer.height / 2)
this.zoomPan.updateTransform()
return
@ -258,9 +293,10 @@ export class BlueprintContainer extends PIXI.Container {
this.updateViewportCulling()
}
getEntitySpritesBounds() {
getBlueprintBounds() {
const bounds = new PIXI.Bounds()
for (const sprite of this.entitySprites.children as PIXI.Sprite[]) {
const sprites = this.entitySprites.children.concat(this.tileSprites.children) as PIXI.Sprite[]
for (const sprite of sprites) {
const sB = new PIXI.Bounds()
const W = sprite.width * sprite.anchor.x
const H = sprite.height * sprite.anchor.y
@ -274,11 +310,13 @@ export class BlueprintContainer extends PIXI.Container {
}
enableRenderableOnChildren() {
this.tileSprites.children.forEach(c => c.renderable = true)
this.entitySprites.children.forEach(c => c.renderable = true)
this.overlayContainer.overlay.children.forEach(c => c.renderable = true)
}
updateViewportCulling() {
cullChildren(this.tileSprites.children)
cullChildren(this.entitySprites.children)
cullChildren(this.overlayContainer.overlay.children)

View File

@ -5,7 +5,7 @@ import { EntityContainer } from './entity'
import { AdjustmentFilter } from '@pixi/filter-adjustment'
import { UnderlayContainer } from './underlay'
export class PaintContainer extends PIXI.Container {
export class EntityPaintContainer extends PIXI.Container {
areaVisualization: PIXI.Sprite | PIXI.Sprite[] | undefined
holdingRightClick: boolean
directionType: string
@ -139,7 +139,7 @@ export class PaintContainer extends PIXI.Container {
this.placeEntityContainer()
} else if (e.data.button === 2) {
this.holdingRightClick = true
this.removeContainer()
this.removeContainerUnder()
}
}
@ -161,7 +161,7 @@ export class PaintContainer extends PIXI.Container {
y: (newPosition.y - newPosition.y % 16) / 16
}
if (newCursorPos.x !== G.gridCoords16.x || newCursorPos.y !== G.gridCoords16.y) {
if (this.holdingRightClick) this.removeContainer()
if (this.holdingRightClick) this.removeContainerUnder()
switch (this.name) {
case 'straight_rail':
@ -191,7 +191,7 @@ export class PaintContainer extends PIXI.Container {
}
}
removeContainer() {
removeContainerUnder() {
const position = EntityContainer.getGridPosition(this.position)
const c = EntityContainer.mappings.get(G.bp.entityPositionGrid.getCellAtPosition(position))
if (c) {

View File

@ -67,7 +67,8 @@ export class InfoContainer extends PIXI.Container {
'F',
'W / A / S / D',
'click + drag in blueprint area',
'mouse wheel'
'mouse wheel',
'[ / ]'
], { x: this.iWidth / 2 - 4, y: 40 }, 1)
this.writeColumn([
@ -92,7 +93,8 @@ export class InfoContainer extends PIXI.Container {
'focuses viewport on blueprint',
'move',
'move',
'zoom in / out'
'zoom in / out',
'decrease / increase tile area'
], { x: this.iWidth / 2 + 4, y: 40 })
this.writeColumn([
@ -107,17 +109,17 @@ export class InfoContainer extends PIXI.Container {
' (F12) to check if something is wrong.',
'Entities with placeable-off-grid flag will not be added to the positionGrid',
' (ex. landmine).'
], { x: 4, y: 500 })
], { x: 4, y: 520 })
this.writeColumn([
'Please leave your suggestions, ideas, new features or bug reports here:'
], { x: this.iWidth / 2, y: 730 }, 0.5, true)
], { x: this.iWidth / 2, y: 750 }, 0.5, true)
const link = new PIXI.Text('Reddit Post')
link.interactive = true
link.buttonMode = true
link.on('click', () => window.open('https://redd.it/87zysk', '_blank'))
link.position.set(this.iWidth / 2, 750)
link.position.set(this.iWidth / 2, 770)
link.style.fontSize = 16
link.style.fill = G.UIColors.link
link.anchor.set(0.5, 0)
@ -127,7 +129,7 @@ export class InfoContainer extends PIXI.Container {
link2.interactive = true
link2.buttonMode = true
link2.on('click', () => window.open('https://github.com/Teoxoy/factorio-blueprint-editor', '_blank'))
link2.position.set(this.iWidth / 2, 770)
link2.position.set(this.iWidth / 2, 790)
link2.style.fontSize = 16
link2.style.fill = G.UIColors.link
link2.anchor.set(0.5, 0)
@ -137,7 +139,7 @@ export class InfoContainer extends PIXI.Container {
'Copyright © 2018 Tanasoaia Teodor Andrei',
'All art assets, spritesheets and other Factorio game data used in this project',
'belong to Wube Software Ltd and are not for redistribution.'
], { x: this.iWidth / 2, y: 810 }, 0.5, true, 14)
], { x: this.iWidth / 2, y: 830 }, 0.5, true, 14)
}
writeColumn(data: Array<string | [string, number]>, offset: IPoint, anchorX = 0, bold = false, fontSize = 16) {

View File

@ -5,7 +5,9 @@ import factorioData from '../factorio-data/factorioData'
import { AdjustmentFilter } from '@pixi/filter-adjustment'
import util from '../util'
import G from '../globals'
import { PaintContainer } from './paint'
import { EntityPaintContainer } from './entityPaint'
import { EntityContainer } from './entity'
import { TilePaintContainer } from './tilePaint'
export class InventoryContainer extends PIXI.Container {
@ -99,8 +101,10 @@ export class InventoryContainer extends PIXI.Container {
let subgroupHasItem = false
for (const subgroup of inventoryBundle[i].subgroups) {
for (const item of subgroup.items) {
const placeResult = factorioData.getItem(item.name).place_result
if ((!filteredItems && placeResult && factorioData.getEntity(placeResult)) ||
const itemData = factorioData.getItem(item.name)
const tileResult = itemData.place_as_tile && itemData.place_as_tile.result
const placeResult = itemData.place_result || tileResult
if ((!filteredItems && placeResult && (factorioData.getEntity(placeResult) || factorioData.getTile(placeResult))) ||
filteredItems && filteredItems.includes(item.name)
) {
const img = InventoryContainer.createIcon(item)
@ -128,12 +132,28 @@ export class InventoryContainer extends PIXI.Container {
G.currentMouseState = G.mouseStates.PAINTING
const newPosition = e.data.getLocalPosition(G.BPC)
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(placeResult).size, 0)
G.BPC.paintContainer = new PaintContainer(placeResult, 0, {
x: newPosition.x - newPosition.x % 32 + (size.x % 2 * 16),
y: newPosition.y - newPosition.y % 32 + (size.y % 2 * 16)
})
G.BPC.addChild(G.BPC.paintContainer)
if (tileResult) {
G.BPC.paintContainer = new TilePaintContainer(
placeResult,
EntityContainer.getPositionFromData(
newPosition,
{ x: TilePaintContainer.size, y: TilePaintContainer.size }
)
)
G.BPC.tiles.addChild(G.BPC.paintContainer)
} else {
G.BPC.paintContainer = new EntityPaintContainer(
placeResult,
0,
EntityContainer.getPositionFromData(
newPosition,
util.switchSizeBasedOnDirection(factorioData.getEntity(placeResult).size, 0)
)
)
G.BPC.addChild(G.BPC.paintContainer)
}
this.close()
}
})

54
src/containers/tile.ts Normal file
View File

@ -0,0 +1,54 @@
import G from '../globals'
export class TileContainer extends PIXI.Container {
static mappings: Map<string, TileContainer> = new Map()
static generateSprite(name: string, position: IPoint) {
// TODO: maybe optimize this with PIXI.extras.TilingSprite and masks
const X = Math.floor(position.x) % 8
const Y = Math.floor(position.y) % 8
const textureKey = `${name}-${X}-${Y}`
let texture = PIXI.utils.TextureCache[textureKey]
if (!texture) {
const spriteData = PIXI.Texture.fromFrame(name)
texture = new PIXI.Texture(spriteData.baseTexture, new PIXI.Rectangle(
spriteData.frame.x + X * 64,
spriteData.frame.y + Y * 64,
64,
64
))
PIXI.Texture.addToCache(texture, textureKey)
}
const s = new PIXI.Sprite(texture)
s.scale.set(0.5)
s.anchor.set(0.5)
return s
}
tileSprites: PIXI.Sprite[]
constructor(name: string, position: IPoint) {
super()
this.name = name
this.interactive = false
this.interactiveChildren = false
this.position.set(position.x * 32, position.y * 32)
TileContainer.mappings.set(`${position.x},${position.y}`, this)
this.tileSprites = []
const sprite = TileContainer.generateSprite(this.name, position)
sprite.position = this.position
this.tileSprites.push(sprite)
G.BPC.tileSprites.addChild(sprite)
}
destroy() {
TileContainer.mappings.delete(`${this.position.x / 32},${this.position.y / 32}`)
for (const s of this.tileSprites) s.destroy()
super.destroy()
}
}

171
src/containers/tilePaint.ts Normal file
View File

@ -0,0 +1,171 @@
import G from '../globals'
import { EntityContainer } from './entity'
import { AdjustmentFilter } from '@pixi/filter-adjustment'
import { TileContainer } from './tile'
export class TilePaintContainer extends PIXI.Container {
static size = 2
static getTilePositions() {
return [...Array(Math.pow(TilePaintContainer.size, 2)).keys()].map(i => {
const offset = TilePaintContainer.size / 2 - 0.5
return {
x: i % TilePaintContainer.size - offset,
y: (i - i % TilePaintContainer.size) / TilePaintContainer.size - offset
}
})
}
areaVisualization: PIXI.Sprite | PIXI.Sprite[] | undefined
directionType: string
direction: string
holdingLeftClick: boolean
holdingRightClick: boolean
filter: AdjustmentFilter
constructor(name: string, position: IPoint) {
super()
this.name = name
this.direction = 'left'
this.position.set(position.x, position.y)
this.filter = new AdjustmentFilter({ red: 0.4, green: 1, blue: 0.4 })
this.filters = [this.filter]
this.interactive = true
this.interactiveChildren = false
this.buttonMode = true
this.holdingLeftClick = false
G.BPC.transparentEntities()
this.on('pointerdown', this.pointerDownEventHandler)
this.on('pointerup', this.pointerUpEventHandler)
this.on('pointerupoutside', this.pointerUpEventHandler)
this.redraw()
}
destroy() {
G.BPC.transparentEntities(false)
super.destroy()
}
increaseSize() {
if (TilePaintContainer.size === 20) return
TilePaintContainer.size++
this.reposition()
this.redraw()
}
decreaseSize() {
if (TilePaintContainer.size === 1) return
TilePaintContainer.size--
this.reposition()
this.redraw()
}
reposition() {
const pos = EntityContainer.getPositionFromData(
{ x: G.gridCoords16.x * 16, y: G.gridCoords16.y * 16 },
{ x: TilePaintContainer.size, y: TilePaintContainer.size }
)
this.position.set(pos.x, pos.y)
}
rotate() {
if (this.name.includes('hazard')) {
this.name = this.name.includes('left') ?
this.name.replace('left', 'right') :
this.name.replace('right', 'left')
this.redraw()
}
}
redraw() {
this.removeChildren()
this.addChild(...TilePaintContainer.getTilePositions().map(p => {
const s = TileContainer.generateSprite(this.name, { x: p.x + this.position.x, y: p.y + this.position.y })
s.position.set(p.x * 32, p.y * 32)
s.alpha = 0.5
return s
}))
this.hitArea = new PIXI.Rectangle(
-TilePaintContainer.size * 16,
-TilePaintContainer.size * 16,
TilePaintContainer.size * 32,
TilePaintContainer.size * 32
)
}
pointerDownEventHandler(e: PIXI.interaction.InteractionEvent) {
if (e.data.button === 0) {
this.holdingLeftClick = true
this.placeEntityContainer()
} else if (e.data.button === 2) {
this.holdingRightClick = true
this.removeContainerUnder()
}
}
pointerUpEventHandler(e: PIXI.interaction.InteractionEvent) {
if (e.data.button === 0) {
this.holdingLeftClick = false
} else if (e.data.button === 2) {
this.holdingRightClick = false
}
}
moveTo(newPosition: IPoint) {
const newCursorPos = {
x: (newPosition.x - newPosition.x % 16) / 16,
y: (newPosition.y - newPosition.y % 16) / 16
}
if (newCursorPos.x !== G.gridCoords16.x || newCursorPos.y !== G.gridCoords16.y) {
if (this.holdingRightClick) this.removeContainerUnder()
const pos = EntityContainer.getPositionFromData(
newPosition,
{ x: TilePaintContainer.size, y: TilePaintContainer.size }
)
this.position.set(pos.x, pos.y)
if (this.holdingLeftClick) this.placeEntityContainer()
G.gridCoords16 = newCursorPos
}
}
removeContainerUnder() {
const position = EntityContainer.getGridPosition(this.position)
TilePaintContainer.getTilePositions()
.map(p => ({ x: p.x + position.x, y: p.y + position.y }))
.forEach(p => {
const tileUnder = TileContainer.mappings.get(`${p.x},${p.y}`)
if (tileUnder) {
tileUnder.destroy()
G.bp.removeTile(p)
}
})
}
placeEntityContainer() {
const position = EntityContainer.getGridPosition(this.position)
TilePaintContainer.getTilePositions()
.map(p => ({ x: p.x + position.x, y: p.y + position.y }))
.forEach(p => {
const tileUnder = TileContainer.mappings.get(`${p.x},${p.y}`)
if (tileUnder) tileUnder.destroy()
G.bp.createTile(this.name, p)
G.BPC.tiles.addChild(new TileContainer(this.name, p))
})
}
}

View File

@ -1,6 +1,5 @@
import getEntity from './entity'
import factorioData from './factorioData'
import { Tile } from './tile'
import { PositionGrid } from './positionGrid'
import Immutable from 'immutable'
import G from '../globals'
@ -11,8 +10,7 @@ export class Blueprint {
name: string
icons: any[]
tiles: Tile[]
tilePositionGrid: any
tiles: Immutable.Map<string, string>
version: number
connections: ConnectionsManager
next_entity_number: number
@ -26,36 +24,22 @@ export class Blueprint {
this.name = 'Blueprint'
this.icons = []
this.rawEntities = Immutable.Map()
this.tiles = []
this.tilePositionGrid = {}
this.tiles = Immutable.Map()
this.version = undefined
this.next_entity_number = 1
if (data) {
if (!data.tiles) data.tiles = []
if (!data.icons) data.icons = []
this.name = data.label
this.version = data.version
if (data.icons) data.icons.forEach((icon: any) => this.icons[icon.index - 1] = icon.signal.name)
this.next_entity_number += data.entities.length
this.rawEntities = this.rawEntities.withMutations(map => {
for (const entity of data.entities) {
map.set(entity.entity_number, Immutable.fromJS(entity))
// this.entityPositionGrid.setTileData(entity.entity_number)
}
})
data.tiles.forEach((tile: any) => {
this.createTile(tile.name, tile.position)
})
this.icons = []
data.icons.forEach((icon: any) => {
this.icons[icon.index - 1] = icon.signal.name
})
this.setTileIds()
// TODO: if entity has placeable-off-grid flag then take the next one
const firstEntityTopLeft = this.firstEntity().topLeft()
@ -63,21 +47,19 @@ export class Blueprint {
const offsetY = G.sizeBPContainer.height / 64 - (firstEntityTopLeft.y % 1 !== 0 ? -0.5 : 0)
this.rawEntities = this.rawEntities.withMutations(map => {
map.keySeq().forEach(k => {
// tslint:disable-next-line:no-parameter-reassignment
map.updateIn([k, 'position', 'x'], (x: number) => x += offsetX)
// tslint:disable-next-line:no-parameter-reassignment
map.updateIn([k, 'position', 'y'], (y: number) => y += offsetY)
})
map.keySeq().forEach(k => map
.updateIn([k, 'position', 'x'], x => x + offsetX)
.updateIn([k, 'position', 'y'], y => y + offsetY)
)
})
// tslint:disable-next-line:no-dynamic-delete
this.tiles.forEach(tile => delete this.tilePositionGrid[`${tile.position.x},${tile.position.y}`])
this.tiles.forEach(tile => {
tile.position.x += offsetX
tile.position.y += offsetY
this.tilePositionGrid[`${tile.position.x},${tile.position.y}`] = tile
})
if (data.tiles) {
this.tiles = this.tiles.withMutations(map =>
data.tiles.forEach((tile: any) =>
map.set(`${tile.position.x + offsetX + 0.5},${tile.position.y + offsetY + 0.5}`, tile.name)
)
)
}
}
this.entityPositionGrid = new PositionGrid(this, [...this.rawEntities.keys()])
@ -252,104 +234,43 @@ export class Blueprint {
return fR ? fR.toJS() : undefined
}
// placeBlueprint(bp, position, direction = 0, allowOverlap) { // direction is 0, 1, 2, or 3
// const entitiesCreated = []
// bp.entities.forEach(ent => {
// const data = ent.getData()
// data.direction += direction * 2
// data.direction %= 8
// if (direction === 3) data.position = { x: data.position.y, y: -data.position.x }
// else if (direction === 2) data.position = { x: -data.position.x, y: -data.position.y }
// else if (direction === 1) data.position = { x: -data.position.y, y: data.position.x }
// data.position.x += position.x
// data.position.y += position.y
// entitiesCreated.push(this.createEntityWithData(data, allowOverlap, true, true))
// })
// entitiesCreated.forEach(e => {
// e.place(this.entitiesCreated)
// })
// bp.tiles.forEach(tile => {
// const data = tile.getData()
// if (direction === 3) data.position = { x: data.position.y, y: -data.position.x }
// else if (direction === 2) data.position = { x: -data.position.x, y: -data.position.y }
// else if (direction === 1) data.position = { x: -data.position.y, y: data.position.x }
// data.position.x += position.x
// data.position.y += position.y
// this.createTileWithData(data)
// })
// return this
// }
// createEntityWithData(data: any, allowOverlap: boolean, noPlace: boolean) {
// const ent = new Entity(data, this)
// if (allowOverlap || this.entityPositionGrid.checkNoOverlap(ent)) {
// if (!noPlace) ent.place(this.entities)
// this.entities.push(ent)
// return ent
// } else {
// // const otherEnt = ent.getOverlap(this.entityPositionGrid)
// // throw new Error('Entity ' + data.name + ' overlaps ' + otherEnt.name +
// // ' entity (' + data.position.x + ', ' + data.position.y + ')')
// }
// }
createTile(name: string, position: IPoint) {
return this.createTileWithData({ name, position })
this.tiles = this.tiles.set(`${position.x},${position.y}`, name)
}
createTileWithData(data: any) {
const tile = new Tile(data, this)
const key = `${data.position.x},${data.position.y}`
if (this.tilePositionGrid[key]) this.removeTile(this.tilePositionGrid[key])
this.tilePositionGrid[key] = tile
this.tiles.push(tile)
return tile
}
removeTile(tile: Tile) {
if (!tile) return false
else {
const index = this.tiles.indexOf(tile)
if (index === -1) return tile
this.tiles.splice(index, 1)
return tile
}
}
setTileIds() {
this.tiles.forEach((tile, i) => {
tile.id = i + 1
})
return this
removeTile(position: IPoint) {
this.tiles = this.tiles.remove(`${position.x},${position.y}`)
}
isEmpty() {
return this.rawEntities.isEmpty() && this.tiles.length === 0
return this.rawEntities.isEmpty() && this.tiles.isEmpty()
}
// Get corner/center positions
getPosition(f: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight', xcomp: any, ycomp: any) {
if (!this.rawEntities.size) return { x: 0, y: 0 }
if (this.isEmpty()) return { x: 0, y: 0 }
const positions =
[...this.rawEntities.keys()]
.map(k => this.entity(k)[f]()).concat(
[...this.tiles.keys()]
.map(k => ({ x: Number(k.split(',')[0]), y: Number(k.split(',')[1]) }))
.map(p => tileCorners(p)[f]))
return {
x: [...this.rawEntities.keys()].reduce(
(best: number, ent: any) => xcomp(best, this.entity(ent)[f]().x),
this.firstEntity()[f]().x
),
y: [...this.rawEntities.keys()].reduce(
(best: number, ent: any) => ycomp(best, this.entity(ent)[f]().y),
this.firstEntity()[f]().y
)
// tslint:disable-next-line:no-unnecessary-callback-wrapper
x: positions.map(p => p.x).reduce((p, v) => xcomp(p, v), positions[0].x),
// tslint:disable-next-line:no-unnecessary-callback-wrapper
y: positions.map(p => p.y).reduce((p, v) => ycomp(p, v), positions[0].y)
}
function tileCorners(position: IPoint) {
return {
topLeft: { x: position.x - 0.5, y: position.y - 0.5 },
topRight: { x: position.x + 0.5, y: position.y - 0.5 },
bottomLeft: { x: position.x - 0.5, y: position.y + 0.5 },
bottomRight: { x: position.x + 0.5, y: position.y + 0.5 }
}
}
}
@ -366,19 +287,29 @@ export class Blueprint {
generateIcons() {
// TODO: make this behave more like in Factorio
const entities: Map<string, number> = new Map()
if (!this.rawEntities.isEmpty()) {
const entities: Map<string, number> = new Map()
for (const i of [...this.rawEntities.keys()]) {
const name = this.entity(i).name
for (const i of [...this.rawEntities.keys()]) {
const name = this.entity(i).name
const value = entities.get(name)
entities.set(name, value ? (value + 1) : 0)
const value = entities.get(name)
entities.set(name, value ? (value + 1) : 0)
}
const sortedEntities = [...entities.entries()].sort((a, b) => a[1] - b[1])
this.icons[0] = sortedEntities[0][0]
if (sortedEntities.length > 1) this.icons[1] = sortedEntities[1][0]
} else {
this.icons[0] = factorioData.getTile(
[...Immutable.Seq(this.tiles)
.reduce((acc, tile) =>
acc.set(tile, acc.has(tile) ? (acc.get(tile) + 1) : 0)
, new Map() as Map<string, number>).entries()]
.sort((a, b) => b[1] - a[1])[0][0]
).minable.result
}
const sortedEntities = [...entities.entries()].sort((a, b) => a[1] - b[1])
this.icons[0] = sortedEntities[0][0]
if (sortedEntities.length > 1) this.icons[1] = sortedEntities[1][0]
}
getEntitiesForExport() {
@ -400,7 +331,6 @@ export class Blueprint {
}
toObject() {
this.setTileIds()
if (!this.icons.length) this.generateIcons()
const entityInfo = this.getEntitiesForExport()
const center = this.center()
@ -413,19 +343,18 @@ export class Blueprint {
e.position.x -= center.x
e.position.y -= center.y
}
const tileInfo = this.tiles.map(tile => tile.getData())
for (const t of tileInfo) {
t.position.x -= center.x
t.position.y -= center.y
}
const tileInfo = this.tiles.map((v, k) => ({
position: { x: Number(k.split(',')[0]) - center.x - 0.5, y: Number(k.split(',')[1]) - center.y - 0.5 },
name: v
})).valueSeq().toArray()
const iconData = this.icons.map((icon, i) => (
{ signal: { type: factorioData.getItemTypeForBp(icon), name: icon }, index: i + 1 }
))
return {
blueprint: {
icons: iconData,
entities: this.rawEntities.size ? entityInfo : undefined,
tiles: this.tiles.length ? tileInfo : undefined,
entities: this.rawEntities.isEmpty() ? undefined : entityInfo,
tiles: this.tiles.isEmpty() ? undefined : tileInfo,
item: 'blueprint',
version: this.version || 0,
label: this.name

View File

@ -1,30 +0,0 @@
import { Blueprint } from './blueprint'
export class Tile {
id: number
bp: any
name: any
position: any
constructor(data: any, bp: Blueprint) {
this.id = -1
this.bp = bp
this.name = data.name
if (!data.position || data.position.x === undefined || data.position.y === undefined) {
throw new Error(`Invalid position provided: ${data.position}`)
}
this.position = data.position
}
remove() {
return this.bp.removeTile(this)
}
getData() {
return {
name: this.name,
position: this.position
}
}
}

View File

@ -0,0 +1,33 @@
{
"frames": {
"stone_path": {
"frame": { "x": 0, "y": 0, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"refined_concrete": {
"frame": { "x": 0, "y": 512, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"concrete": {
"frame": { "x": 512, "y": 0, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"hazard_concrete_left": {
"frame": { "x": 512, "y": 512, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"hazard_concrete_right": {
"frame": { "x": 0, "y": 1024, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"refined_hazard_concrete_left": {
"frame": { "x": 512, "y": 1024, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
},
"refined_hazard_concrete_right": {
"frame": { "x": 1024, "y": 0, "w": 512, "h": 512 },
"sourceSize": { "w": 512, "h": 512 }
}
},
"meta": { "image": "./tilesSpritesheet.png" }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB