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:
parent
9ee34bff4d
commit
141daea0cb
@ -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
|
||||
|
28
src/app.ts
28
src/app.ts
@ -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'),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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) {
|
@ -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) {
|
||||
|
@ -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
54
src/containers/tile.ts
Normal 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
171
src/containers/tilePaint.ts
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
33
src/textures/tilesSpritesheet.json
Normal file
33
src/textures/tilesSpritesheet.json
Normal 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" }
|
||||
}
|
BIN
src/textures/tilesSpritesheet.png
Normal file
BIN
src/textures/tilesSpritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 MiB |
Loading…
Reference in New Issue
Block a user