screepsmod-map-tool
Version:
# Usage:
1,118 lines (1,041 loc) • 29.8 kB
JavaScript
window.terrain = []
window.q = window.Q
let floodTolerance = 37
let generateOptions = {}
let mp = { x: 0, y: 0 }
let mb = { left: false, right: false }
let vp = { x: 0, y: 0, z: 1 }
let scale = 1
let flood = false
let overlay = ''
// let currentTool = 'gen'
let currentTool = 'block'
function getTool() {
return tools[currentTool]
}
prefetch()
function prefetch() {
getAllTerrain(true)
.then(loadRooms)
}
function loadRooms(ts) {
for (const t of ts) {
let { room } = t
let [x, y] = utils.roomNameToXY(room)
window.terrainCache[room] = Object.assign(t, {
room,
x,
y,
remote: true
})
window.terrain.push(window.terrainCache[room])
}
console.log('Rooms loaded')
}
function dropJSON(targetEl, callback) {
// disable default drag & drop functionality
targetEl.addEventListener('dragenter', function (e) { e.preventDefault(); });
targetEl.addEventListener('dragover', function (e) { e.preventDefault(); });
targetEl.addEventListener('drop', function (event) {
var reader = new FileReader();
reader.onloadend = function () {
var data = JSON.parse(this.result);
callback(data);
};
reader.readAsText(event.dataTransfer.files[0]);
event.preventDefault();
});
}
dropJSON(
canvas,
function (data) {
window.terrain.splice(0, window.terrain.length)
loadRooms(data.rooms)
window.terrain.forEach(t => t.remote = false)
}
);
function previewTypes() {
let queue = []
for (let y = 0; y < 12; y++) {
for (let x = 1; x <= 28; x++) {
let room = utils.roomNameFromXY(x, y)
queue.push({ room, terrainType: x })
window.terrainCache[room] = null
}
}
let proc = function () {
let { room, terrainType } = queue.pop()
gen(room, { terrainType })
if (queue.length) setTimeout(proc, 100)
}
proc()
}
canvas.addEventListener('wheel', e => {
const oldScale = scale
if (e.deltaY > 0 && scale > 1) scale--
if (e.deltaY < 0 && scale < 8) scale++
if (oldScale !== scale) {
mp = { x: e.clientX, y: e.clientY }
let [x, y] = [-vp.x, -vp.y]
x += mp.x
y += mp.y
x /= oldScale
y /= oldScale
x *= scale
y *= scale
x -= mp.x
y -= mp.y
vp.x = -x
vp.y = -y
}
// if (mb.left) {
// let { x, y, ovp } = mb.left
// let dx = mp.x - x
// let dy = mp.y - y
// if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
// vp.x = ovp.x + dx
// vp.y = ovp.y + dy
// mb.left.drag = true
// }
// }
})
canvas.addEventListener('mousemove', e => {
let [x, y] = [e.clientX, e.clientY]
x -= vp.x
y -= vp.y
if (x < 0) x -= 50 * scale
if (y < 0) y -= 50 * scale
let rx = Math.floor((x / scale) % 50)
let ry = Math.floor((y / scale) % 50)
mp = { x: e.clientX, y: e.clientY, rx, ry }
if (mb.left) {
let { x, y, ovp } = mb.left
let dx = mp.x - x
let dy = mp.y - y
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
vp.x = ovp.x + dx
vp.y = ovp.y + dy
mb.left.drag = true
}
}
})
canvas.addEventListener('mousedown', e => {
let btns = ['left', 'middle', 'right']
let btn = btns[e.button] || 'right'
let x = e.clientX
let y = e.clientY
mb[btn] = { x, y, ovp: Object.assign({}, vp) }
})
const tools = {
gen: [
{ key: 'left', action: ({ room }) => gen(room) },
{ key: 'ctrl+left', action: ({ room }) => generateSector(room) },
{
key: 'middle',
action: ({ e }) => {
flood = flood ? false : { x: e.clientX, y: e.clientY }
}
},
{ key: 'right', action: ({ room }) => del(room) },
{ key: 'ctrl+right', action: ({ room }) => deleteSector(room) }
],
edit: [
{ key: 'left', action: ({ room, x, y }) => editTerrain(room, x, y, 'wall') },
{ key: 'middle', action: ({ room, x, y }) => editTerrain(room, x, y, 'swamp') },
{ key: 'right', action: ({ room, x, y }) => editTerrain(room, x, y, 'plain') }
],
access: [
{ key: 'left', action: ({ room }) => changeRoomStatus(getRoomFromName(room), 'normal') },
{ key: 'ctrl+left', action: ({ room }) => changeSectorStatus(room, 'normal') },
{ key: 'right', action: ({ room }) => changeRoomStatus(getRoomFromName(room), 'out of borders') },
{ key: 'ctrl+right', action: ({ room }) => changeSectorStatus(room, 'out of borders') }
],
block: [
{ key: 'left', action: ({ room, x, y }) => logMapClick(room, x, y) },
{ key: 'middle', action: ({ room, x, y }) => logMapClick(room, x, y) },
{ key: 'right', action: ({ room, x, y }) => logMapClick(room, x, y) }
]
}
function logMapClick(room, x, y) {
console.log(room, x, y)
}
function getRoomFromName(room) { return terrain.find(r => r.room === room) }
function getRoomFromXY(x, y) { return terrain.find(r => r.x == x && r.y == y) }
function changeRoomStatus(room, status) {
//expects room to be the object and not just the name
console.log(room, room.status, "->", status)
room.status = status
room.remote = false
}
function changeSectorStatus(roomNameInSector, status) {
let { start, end } = getSectorBounds(roomNameInSector, "none")
for (let x = start.x; x < end.x; x++) {
for (let y = start.y; y < end.y; y++) {
changeRoomStatus(getRoomFromXY(x, y), status)
}
}
}
function editTerrain(room, x, y, type) {
const map = ['plain', 'wall', 'swamp']
type = map.indexOf(type)
const r = terrain.find(r => r.room === room)
const ind = x + (y * 50)
const part1 = r.terrain.slice(0, ind)
const part2 = r.terrain.slice(ind + 1)
r.terrain = terrainCache[room].terrain = part1 + type + part2
r.remote = false
}
canvas.addEventListener('mouseup', e => {
let room = utils.roomNameFromXY(cell.x, cell.y)
let btns = ['left', 'middle', 'right']
let btn = btns[e.button] || 'right'
let { ctrlKey, shiftKey, altKey, metaKey } = e
let { drag } = mb[btn]
mb[btn] = {}
if (drag) return
const keys = []
if (ctrlKey) keys.unshift('ctrl')
if (shiftKey) keys.unshift('shift')
if (altKey) keys.unshift('alt')
if (metaKey) keys.unshift('meta')
keys.push(btn)
const key = keys.join('+')
const tool = getTool()
if (tool) {
const { action } = tool.find(t => t.key === key) || {}
if (action) {
action({
room,
x: mp.rx,
y: mp.ry,
e
})
}
}
e.preventDefault()
return true
})
function center(room) {
const rp = utils.roomNameToXY(room)
vp.x = rp[0] * scale * 50 * -1 + (window.innerWidth / 2)
vp.y = rp[1] * scale * 50 * -1 + (window.innerHeight / 2)
}
function arrow(e) {
const speed = 50
if (e.keyCode == '38') {
// up arrow
vp.y += speed * scale
}
else if (e.keyCode == '40') {
// down arrow
vp.y -= speed * scale
}
else if (e.keyCode == '37') {
// left arrow
vp.x += speed * scale
}
else if (e.keyCode == '39') {
// right arrow
vp.x -= speed * scale
}
}
window.addEventListener('keyup', (e) => arrow(e))
window.oncontextmenu = function (event) {
event.preventDefault()
event.stopPropagation()
return false
}
let cell = {}
function isInView(x, y, w, h) {
const canvas = document.getElementById('canvas')
const l1 = { x, y }
const r1 = { x: x + w, y: y + h }
const l2 = { x: -vp.x, y: -vp.y }
const r2 = { x: -vp.x + canvas.width, y: -vp.y + canvas.height }
if (l1.x > r2.x || l2.x > r1.x) return false
if (l1.y > r2.y || l2.y > r1.y) return false
return true
}
function render() {
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
ctx.save()
ctx.beginPath()
ctx.rect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = '#555'
ctx.fill()
ctx.translate(vp.x, vp.y)
terrain.forEach(room => {
if (!isInView(room.x * 50 * scale, room.y * 50 * scale, 50 * scale, 50 * scale)) {
return // console.log('skipped', room)
}
renderRoom(ctx, room)
})
if (flood) {
ctx.save()
// console.log('fill', flood)
ctx.fillStyle = 'rgba(0,255,0,1)'
ctx.fillFlood(flood.x, flood.y, floodTolerance)
ctx.restore()
}
{
let { x, y } = mp
x -= vp.x
y -= vp.y
if (x < 0) x -= 50 * scale
if (y < 0) y -= 50 * scale
let rx = x - x % (50 * scale)
let ry = y - y % (50 * scale)
cell = { x: rx / (50 * scale), y: ry / (50 * scale) }
cell.room = utils.roomNameFromXY(cell.x, cell.y)
ctx.beginPath()
ctx.rect(rx, ry, (50 * scale), (50 * scale))
ctx.strokeStyle = 'red'
ctx.stroke()
let { start, end } = getSectorBounds(cell.room)
let s = 50 * scale
ctx.beginPath()
let w = Math.abs(end.x - start.x)
let h = Math.abs(end.y - start.y)
// if (start.x >= 0) start.x += 1
// if (start.y >= 0) start.y += 1
ctx.rect(start.x * s, start.y * s, w * s, h * s)
ctx.strokeStyle = 'yellow'
ctx.stroke()
}
ctx.restore()
{
ctx.save()
ctx.translate(mp.x, mp.y)
ctx.beginPath()
ctx.rect(0, 0, 75, 80)
ctx.fillStyle = '#333333'
ctx.fill()
ctx.font = '20px Roboto'
ctx.fillStyle = 'white'
ctx.fillText(cell.room, 5, 25)
ctx.fillText(`(${cell.x},${cell.y})`, 5, 45)
let { x, y } = mp
x -= vp.x
y -= vp.y
if (x < 0) x -= 50 * scale
if (y < 0) y -= 50 * scale
let rx = (50 + Math.floor((x / scale) % 50)) % 50
let ry = (50 + Math.floor((y / scale) % 50)) % 50
ctx.fillText(`(${rx},${ry})`, 5, 65)
ctx.restore()
}
if (currentTool === 'edit') {
ctx.save()
ctx.translate(vp.x, vp.y)
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
let [xo, yo] = [
Math.floor(Math.abs(vp.x % scale)),
Math.floor(Math.abs(vp.y % scale))
]
let x = (mp.rx * scale) + (cell.x * scale) // (mp.x + xo)
let y = (mp.ry * scale) + (cell.y * scale) // (mp.y + yo)
ctx.beginPath()
ctx.rect(x, y, scale, scale)
ctx.fill()
ctx.restore()
}
if (overlay) {
ctx.save()
ctx.font = `32px Roboto`
ctx.fillStyle = 'red'
ctx.fillText(overlay, 10, 50)
// const { width } = ctx.measureText(overlay)
ctx.restore()
}
}
let imageCache = {}
function renderRoom(ctx, room) {
if (!room.terrain) return
let img = imageCache[room.terrain + scale] = imageCache[room.terrain + scale] || utils.writeTerrainToPng(room.terrain, scale)
let rx = room.x * 50 * scale
let ry = room.y * 50 * scale
ctx.putImageData(img, vp.x + rx, vp.y + ry)
if (flood) return
if (room.status !== 'normal') {
ctx.save()
ctx.beginPath()
ctx.fillStyle = 'rgba(0,0,0,0.5)'
ctx.fillRect(rx, ry, 50 * scale, 50 * scale)
ctx.restore()
}
if (showWalls.checked && room.exits) {
ctx.save()
ctx.beginPath()
let x2 = rx + (50 * scale)
let y2 = ry + (50 * scale)
ctx.moveTo(rx, ry)
ctx[room.exits.top ? 'moveTo' : 'lineTo'](x2, ry)
ctx[room.exits.right ? 'moveTo' : 'lineTo'](x2, y2)
ctx[room.exits.bottom ? 'moveTo' : 'lineTo'](rx, y2)
ctx[room.exits.left ? 'moveTo' : 'lineTo'](rx, ry)
ctx.strokeStyle = 'red'
ctx.stroke()
ctx.restore()
return
}
let mineral = ''
let colors = {
source: 'yellow',
keeperLair: 'red',
mineral: 'gray',
controller: 'lightGray',
L: ['#3F6147', '#89F4A5'],
U: ['#1B617F', '#88D6F7'],
K: ['#331A80', '#9370FF'],
Z: ['#594D33', '#F2D28B'],
X: ['#4F2626', '#FF7A7A'],
H: ['#4D4D4D', '#CCCCCC'],
O: ['#4D4D4D', '#CCCCCC']
}
room.objects.forEach(o => {
ctx.save()
let x = rx + (o.x * scale)
let y = ry + (o.y * scale)
ctx.beginPath()
ctx.rect(x, y, scale, scale)
ctx.fillStyle = colors[o.type] || 'blue'
ctx.fill()
if (o.type === 'mineral') {
mineral = o.mineralType
}
ctx.restore()
})
if (mineral && showMinerals.checked) {
if (!imageCache[mineral + scale]) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = 50 * scale
canvas.height = 50 * scale
let [primary, secondary] = colors[mineral].map(c => hexToRGB(c, 0.6))
ctx.beginPath()
ctx.arc(25 * scale, 25 * scale, 10 * scale, 0, Math.PI * 2)
ctx.fillStyle = primary
ctx.fill()
ctx.strokeStyle = secondary
ctx.lineWidth = 2 * scale
ctx.stroke()
ctx.font = `${scale * 16}px Roboto`
let off = ctx.measureText(mineral)
ctx.fillStyle = secondary
ctx.fillText(mineral, (25 * scale) - (off.width / 2), (25 * scale) + (scale * 6))
imageCache[mineral + scale] = canvas
}
ctx.drawImage(imageCache[mineral + scale], rx, ry)
}
}
function hexToRGB(hex, opacity = 1) {
let v = parseInt(hex.slice(1), 16)
let r = (v & 0xFF0000) >> 16
let g = (v & 0x00FF00) >> 8
let b = (v & 0x0000FF) >> 0
return `rgba(${r},${g},${b},${opacity})`
}
function loop() {
requestAnimationFrame(loop)
render()
}
loop()
function resize() {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize', resize)
let pool = {
count: 5,
workers: [],
idle: [],
active: [],
queue: [],
cbs: {}
}
setInterval(() => {
while (pool.idle.length && pool.queue.length) {
let worker = pool.idle.pop()
let job = pool.queue.pop()
worker.postMessage(job)
}
}, 100)
function gen(room) {
let ind = window.terrain.findIndex(r => r.room === room)
if (~ind) {
window.terrain.splice(ind, 1)
}
delete terrainCache[room]
let opts = generateOptions
// worker.postMessage({ action: 'generate', room, terrainCache, opts })
let id = Math.random().toString(36).slice(2)
pool.queue.unshift({
action: 'generate',
room,
terrainCache,
opts,
id,
twoSourcesChance: parseInt(document.querySelector('[name=twoSourcesChance]').value) / 100,
wallChance: parseInt(document.querySelector('[name=wallChance]').value) / 100
})
return new Promise((resolve, reject) => {
pool.cbs[id] = { resolve, reject }
})
}
function del(room) {
if (!roomsToBrick.includes(room)) roomsToBrick.push(room)
terrainCache[room] = null
let ind = terrain.findIndex(r => r.room === room)
if (~ind) terrain.splice(ind, 1)
}
for (let i = 0; i < pool.count; i++) {
let worker = new Worker('worker.js')
worker.addEventListener('message', msg => {
if (pool.idle.indexOf(worker) === -1) {
pool.idle.push(worker)
}
//console.log(msg)
if (msg.data.action === 'generate') {
let r = msg.data.room
if (r.opts) {
let { exits } = r.opts
delete r.opts
r.exits = {}
for (let dir in exits) {
r.exits[dir] = !!exits[dir].length
}
}
window.terrain.push(r)
window.terrainCache[r.room] = r
//console.log(r)
output.value = JSON.stringify(terrain.filter(r => !r.remote))
}
let { id } = msg.data
if (id && pool.cbs[id]) {
pool.cbs[id].resolve(msg)
delete pool.cbs[id]
}
})
pool.workers.push(worker)
worker.postMessage({ action: 'ping' })
}
function save(active) {
if (!confirm('Are you sure you want to save?')) return
if (roomsToBrick.length > 0) {
for (var room of roomsToBrick) {
const idx = terrain.findIndex(r => r.room === room)
if (idx === -1) {
const [x, y] = utils.roomNameToXY(room)
// console.log('makeSolidRoom ', room, x, y)
makeSolidRoom(x, y)
}
}
}
terrain.forEach(r => r.status = r.status || (active ? 'normal' : 'out of borders'))
let json = JSON.stringify(terrain.filter(r => !r.remote))
fetch(`${server}/api/maptool/set`, {
method: 'POST',
body: json,
headers: {
'content-type': 'application/json'
},
credentials: 'same-origin'
})
}
function makeNovice() {
let t = Date.now() + 1 * 60 * 60 * 1000
let d = new Date()
d.setMonth(7)
d.setDate(1)
d = d.getTime()
terrain.forEach((r) => {
let { room, objects } = r
r.objects = r.objects.filter(o => o.type != 'constructedWall')
if (room.match(/0S/) || room.match(/0$/)) {
r.bus = true
let x, y
for (let i = 0; i < 50; i++) {
if (room.match(/^E0S1[1-9]$/)) {
x = 49
y = i
}
if (room.match(/^E10S1[1-9]$/)) {
x = 0
y = i
}
if (room.match(/^E[1-9]S10$/)) {
x = i
y = 49
}
if (room.match(/^E[1-9]S20$/)) {
x = i
y = 0
}
r.objects.push({ type: 'constructedWall', room, x, y, decayTime: { timestamp: d } })
}
}
if (!r.bus) {
r.novice = d
}
delete r.remote
r.openTime = t
})
}
function getSectorBounds(roomNameInSector, busOption) {
let [x, y] = utils.roomNameToXY(roomNameInSector)
if (x < 0) x -= 9
if (y < 0) y -= 9
let sx = x - (x % 10)
let sy = y - (y % 10)
let start = { x: sx, y: sy }
let end = { x: sx + 10, y: sy + 10 }
if (busOption !== undefined) {
switch(busOption) {
case "default":
break;
case "all":
if (x < 0 && y > -1) {
//wxsx
start.x -= 1
end.y += 1
} else if (x < 0 && y < 0) {
//wxnx
start.x -= 1
start.y -= 1
} else if (x > -1 && y < 0) {
//exnx
start.y -= 1
end.x += 1
} else {
//exsx
end.x += 1
end.y += 1
}
break;
case "none":
if (x < 0 && y > -1) {
//wxsx
end.x -= 1
start.y += 1
} else if (x < 0 && y < 0) {
//wxnx
end.x -= 1
end.y -= 1
} else if (x > -1 && y < 0) {
//exnx
end.y -= 1
start.x += 1
} else {
//exsx
start.x += 1
start.y += 1
}
break;
}
}
return { start, end }
}
function makeRespawnSectorWall(room, borderSide, decayTime) {
let x, y, x2, y2
// Edges
for (let i = 0; i < 50; i++) {
switch (borderSide) {
//borderSide is related to the sector side not the room side
case 'left':
x = 30
y = i
break
case 'right':
x = 20
y = i
break
case 'top':
x = i
y = 30
break
case 'bottom':
x = i
y = 20
break
}
if (x !== undefined && y !== undefined) {
room.objects.push({ type: 'constructedWall', room: room.room, x, y, decayTime: { timestamp: decayTime } })
}
}
// Corners
for (let i = 0; i < 20; i++) {
switch (borderSide) {
case 'bottomLeft':
x = 30
y = i
x2 = 30 + i
y2 = 20
break
case 'topLeft':
x = 30 + i
y = 30
x2 = 30
y2 = 30 + i
break
case 'bottomRight':
x = i
y = 20
x2 = 20
y2 = i
break
case 'topRight':
x = i
y = 30
x2 = 20
y2 = 30 + i
break
}
if (x !== undefined && y !== undefined && x < 50 && y < 50) {
room.objects.push({ type: 'constructedWall', room: room.room, x: x, y: y, decayTime: { timestamp: decayTime } })
}
if (x2 !== undefined && y2 !== undefined && x2 < 50 && y2 < 50) {
room.objects.push({ type: 'constructedWall', room: room.room, x: x2, y: y2, decayTime: { timestamp: decayTime } })
}
}
if (borderSide === 'bottomRight') {
room.objects.push({ type: 'constructedWall', room: room.room, x: 20, y: 20, decayTime: { timestamp: decayTime } })
}
}
function makeRespawnSector(roomInSector, openTime, decayTime) {
//default to opening in 1 minute
if (openTime === undefined) { openTime = Date.now() + (1 * 1000 * 60) }
//default to decaying in 7 days
if (decayTime === undefined) {
decayTime = new Date()
decayTime.setDate(decayTime.getDate() + 7)
decayTime = decayTime.getTime()
}
let { start, end } = getSectorBounds(roomInSector, "all")
for (let x = start.x; x < end.x; x++) {
for (let y = start.y; y < end.y; y++) {
let room = terrain.find(r => r.x == x && r.y == y)
room.remote = false
room.status = 'normal'
let [, hor, horx, ver, very] = room.name.match(/^(\w)(\d+)(\w)(\d+)$/)
if (horx % 10 == 0 || very % 10 == 0) {
room.bus = true
if (x == start.x && y == start.y) {
makeRespawnSectorWall(room, 'topLeft', decayTime)
} else if (x == start.x && y == (start.y + 10)) {
makeRespawnSectorWall(room, 'bottomLeft', decayTime)
} else if (y == start.y && x == (start.x + 10)) {
makeRespawnSectorWall(room, 'topRight', decayTime)
} else if (y == (start.y + 10) && x == (start.x + 10)) {
makeRespawnSectorWall(room, 'bottomRight', decayTime)
} else if (x == start.x && y > start.y && y < (start.y + 10)) {
makeRespawnSectorWall(room, 'left', decayTime)
} else if (x == (start.x + 10) && y > start.y && y < (start.y + 10)) {
makeRespawnSectorWall(room, 'right', decayTime)
} else if ((y == start.y && x > start.x && x < (start.x + 10))) {
makeRespawnSectorWall(room, 'top', decayTime)
} else if ((y == (start.y + 10) && x > start.x && x < (start.x + 10))) {
makeRespawnSectorWall(room, 'bottom', decayTime)
}
} else {
room.respawnArea = decayTime
room.openTime = openTime
}
}
}
}
function findBounds() {
const solidTerrain = '1'.repeat(2500)
let start = { x: 100, y: 100 }
let end = { x: -100, y: -100 }
terrain.forEach(r => {
if (r.terrain === solidTerrain) return // Skip solid rooms
start.x = Math.min(start.x, r.x)
start.y = Math.min(start.y, r.y)
end.x = Math.max(end.x, r.x)
end.y = Math.max(end.y, r.y)
})
return { start, end }
}
function generateSolidWall() {
let { start, end } = findBounds()
for (let x = start.x - 1; x <= end.x + 1; x++) {
for (let y = start.y - 1; y <= end.y + 1; y++) {
let room = terrain.find(r => r.x === x && r.y === y)
if (!room) {
makeSolidRoom(x, y)
}
}
}
// let start = { x:
// for (let x = start.x - 1; x <= end.x + 1; x++) {
// makeSolidRoom(x, start.y - 1)
// makeSolidRoom(x, end.y + 1)
// }
// for (let y = start.y; y <= end.y; y++) {
// makeSolidRoom(start.x - 1, y)
// makeSolidRoom(end.x + 1, y)
// }
}
function makeSolidRoom(x, y) {
let room = utils.roomNameFromXY(x, y)
let terrain = '1'.repeat(2500)
let objects = []
let status = 'out of borders'
let obj = { room, x, y, terrain, objects, status }
let data = window.terrain.find(r => r.x === x && r.y === y)
if (data) Object.assign(data, obj)
else window.terrain.push(obj)
}
async function generateSector(room) {
let p1 = []
let p2 = []
let { start, end } = getSectorBounds(room)
if (start.x < 0) {
start.x -= 1
end.x -= 1
}
if (start.y < 0) {
start.y -= 1
end.y -= 1
}
for (let x = start.x; x < end.x + 1; x++) {
for (let y = start.y; y < end.y + 1; y++) {
let room = utils.roomNameFromXY(x, y)
if (x % 2 === y % 2) {
p1.push(room)
} else {
p2.push(room)
}
let ind = terrain.findIndex(r => r.room === room)
if (~ind) terrain.splice(ind, 1)
}
}
// console.log(sx, sy, start, end, p1, p2)
await Promise.all(p1.map(room => gen(room)))
await Promise.all(p2.map(room => gen(room)))
}
function deleteSector(room) {
let p1 = []
let p2 = []
let { start, end } = getSectorBounds(room)
for (let x = start.x; x < end.x; x++) {
for (let y = start.y; y < end.y; y++) {
makeSolidRoom(x, y)
}
}
}
function createGrid() {
let nodes = []
let edges = []
let rooms = []
let xyToInd = (x, y) => ((y - 1) * 9) + x
for (let y = 1; y < 10; y++) {
for (let x = 1; x < 10; x++) {
nodes.push({ x, y })
if (x < 9) {
edges.push([
xyToInd(x + 0, y + 0),
xyToInd(x + 1, y + 0)
])
}
if (y < 9) {
edges.push([
xyToInd(x + 0, y + 0),
xyToInd(x + 0, y + 1)
])
}
rooms.push([
xyToInd(x + 0, y + 0),
xyToInd(x + 1, y + 0),
xyToInd(x + 1, y + 1),
xyToInd(x + 0, y + 1)
])
}
}
}
async function autoGen(sizex, sizey) {
if (sizex % 2 !== 0 || sizey % 2 !== 0) {
alert(`autoGen aborted!\nautoGen requires even numbers for width and height`)
return
}
if (!confirm('Warning: Auto Gen erases all current rooms, are you sure?')) return
terrain.splice(0, Number.MAX_VALUE)
for (const k of Object.keys(terrainCache)) {
delete terrainCache[k]
}
const hw = sizex / 2
const hh = sizey / 2
const rooms = new Set()
for (let sy = -hh - 1; sy <= hh; sy++) {
const startx = (-hw * 10) - 1
const endx = (hw * 10)
let y = sy * 10
if (y < 0) y += 9
for (let x = startx; x < endx; x++) {
rooms.add(utils.roomNameFromXY(x, y))
}
}
for (let sx = -hw - 1; sx <= hw; sx++) {
const starty = (-hh * 10) - 1
const endy = (hh * 10) + 1
let x = sx * 10
if (x < 0) x += 9
for (let y = starty; y < endy; y++) {
rooms.add(utils.roomNameFromXY(x, y))
}
}
for (let ry = -hh * 10; ry <= hh * 10; ry++) {
const startx = (-hw * 10) - 1
const endx = (hw * 10)
let y = ry
for (let x = startx; x < endx; x++) {
const r = utils.roomNameFromXY(x, y)
if (!rooms.has(r)) {
rooms.add(r)
}
}
}
const q1 = []
const q2 = []
for (const roomName of rooms) {
const [x, y] = utils.roomNameToXY(roomName)
// makeSolidRoom(x, y)
if (q1.length === q2.length) {
q1.push(roomName)
} else {
q2.push(roomName)
}
}
let complete = 0
let total = q1.length + q2.length
let stage = 'rooms'
let update = () => {
complete++
overlay = `Generating ${stage} ${complete} of ${total} completed`
console.log(overlay)
}
const ps = []
for (const roomName of q1) {
ps.push(gen(roomName).then(update))
}
await Promise.all(ps)
ps.splice(0, Number.MAX_VALUE)
for (const roomName of q2) {
ps.push(gen(roomName).then(update))
}
await Promise.all(ps)
generateSolidWall()
overlay = ''
alert('autoGen completed successfully')
}
// terrain.forEach(t => {
// t.remote = true
// t.objects = t.objects.map((o, i) => {
// if (o.type === 'controller') {
// let { type, room, x, y } = o
// t.remote = false
// return { type, room, x, y, level: 0 }
// }
// if (o.type === 'spawn') {
// t.remote = false
// return null
// }
// return o
// }).filter(o => o)
// })
function getExits(room) {
const ret = {
top: '',
bottom: '',
left: '',
right: ''
}
const t = room.length === 2500 ? room : terrain.find(r => r.room === room).terrain
for (let i = 0; i < 50; i++) {
ret.top += t[i]
ret.bottom += t[(50 * 49) + i]
ret.left += t[i * 50]
ret.right += t[(i * 50) + 49]
}
ret.top = ret.top.replace(/2/g, '0').replace(/3/g, '1')
ret.bottom = ret.bottom.replace(/2/g, '0').replace(/3/g, '1')
ret.left = ret.left.replace(/2/g, '0').replace(/3/g, '1')
ret.right = ret.right.replace(/2/g, '0').replace(/3/g, '1')
return ret
}
async function fixExits() {
const found = new Set()
const wall = '1'.repeat(2500)
const ps = []
for (const room of terrain) {
if (room.terrain === wall) continue
if (found.has(room.room)) continue
const exits = getExits(room.terrain)
const { x, y } = room
const offs = [
['left', 'right', -1, 0],
['right', 'left', 1, 0],
['top', 'bottom', 0, -1],
['bottom', 'top', 0, 1]
]
const forceGen = new Set()
for (const [dir, rdir, xo, yo] of offs) {
const r = terrain.find(r => r.x === (x + xo) && r.y === (y + yo))
if (r && r.terrain !== wall && !found.has(r)) {
const nexits = getExits(r.terrain)
if (exits[dir] !== nexits[rdir]) {
console.log(`Found mismatch:\n ${dir} ${room.room} ${exits[dir]}\n ${rdir} ${r.room} ${nexits[rdir]}`)
// forceGen.add(room.room)
forceGen.add(r.room)
found.add(room.room)
found.add(r.room)
}
}
}
for (const r of forceGen) {
delete terrainCache[r]
}
ps.push((async () => {
for (const r of forceGen) {
await gen(r)
}
})())
}
console.log(`Found ${found.size} rooms with mismatched exits`)
await Promise.all(ps)
if (found.size) await fixExits()
}
async function fixFloating() {
const rooms = terrain.filter(r => {
const obj = r.objects.find(o => r.terrain[o.x + (o.y * 50)] != '1' && ['controller', 'mineral', 'source', 'keeperLair'].includes(o.type))
return !!obj
})
console.log(`Found ${rooms.length} rooms with unsupported structures`)
await Promise.all(rooms.map(r => gen(r.room)))
}
async function fixDups() {
const rooms = terrain
.filter(r => {
const types = _.groupBy(r.objects, 'type')
if (types.controller && types.controller.length > 1) return true
if (types.mineral && types.mineral.length > 1) return true
if (types.source && types.source.length > (types.controller ? 2 : 3)) return true
if (types.keeperLair && types.keeperLair.length > 4) return true
return false
})
await Promise.all(rooms.map(r => gen(r.room)))
console.log(`Found ${rooms.length} rooms with too many objects`)
}
async function fixAll() {
if (!confirm('Warning: Fix All is a destructive process. Rooms will be forcibly regenerated. Are you sure you want to continue?')) return
for (const room of terrain) {
room.terrain = room.terrain.replace(/3/g, '1')
}
await fixFloating()
await fixDups()
await fixExits()
console.log(`All rooms fixed. Run save to apply.`)
alert(`All rooms fixed. Run save to apply.`)
}