init
This commit is contained in:
commit
6b926e53bf
19 changed files with 1365 additions and 0 deletions
1
CNAME
Normal file
1
CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
altboxels.qazox.dev
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# altboxels
|
||||
A sandbox game inspired by https://sandboxels.r74n.com/, with a cleaner codebase and secure/simple modding support in mind.
|
||||
|
||||
Some of the elements are derived from other games in the falling sand genre, especially Sandboxels. However, the physics engine and other backend code are custom and independently developed.
|
44
css/core.css
Normal file
44
css/core.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
body {
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
background: rgb(21, 21, 22);
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#no-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin: auto;
|
||||
display: block;
|
||||
background: rgb(181, 204, 253);
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
#main2 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
button, a, textarea {
|
||||
padding: 5px;
|
||||
border: none;
|
||||
margin: 6px 3px 0px 3px;
|
||||
|
||||
display: none;
|
||||
|
||||
background: rgb(44, 142, 255);
|
||||
color: white;
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
section:target button, .menu2 button, a, textarea {
|
||||
display: inline-block;
|
||||
}
|
80
index.html
Normal file
80
index.html
Normal file
|
@ -0,0 +1,80 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Metadata -->
|
||||
<title>Altboxels</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta
|
||||
name="description"
|
||||
content="Altboxels is a pixel sandbox game that enables anyone to empower their imagination. It shares many characteristics with Sandboxels, the Powder Toy, among other games in the genre. Many features are custom-built, including the physics engine!"
|
||||
>
|
||||
<meta name="keywords" content="sandbox, falling sand, powder toy">
|
||||
<meta name="author" content="qazox">
|
||||
<link rel="stylesheet" href="css/core.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Altboxels</h1>
|
||||
<canvas id='main' style=""></canvas>
|
||||
<canvas id='alt' style='display: none;'></canvas>
|
||||
|
||||
<span class='info'>
|
||||
N/A
|
||||
</span>
|
||||
|
||||
<div class='menu2'>
|
||||
<section>
|
||||
<a href='https://discord.gg/wtBVte4Syu'>Chat</a>
|
||||
<a href='https://github.com/qazox/altboxels'>Source</a>
|
||||
<a href='https://altboxels.qazox.dev/'>Website</a>
|
||||
<a href='https://abc.qazox.dev/'>Community</a>
|
||||
</section>
|
||||
<section>
|
||||
<button onclick="handler.noTick = !handler.noTick">Pause</button>
|
||||
<button onclick="openMods()">Mods</button>
|
||||
<button onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
<button onclick="canvas.radius++;">+Rad</button>
|
||||
<button onclick="canvas.radius = Math.max(canvas.radius - 1,0);">-Rad</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class='menu'>
|
||||
</div>
|
||||
|
||||
<div class='buttons'>
|
||||
</div>
|
||||
|
||||
<textarea id='code'>
|
||||
(save data here)
|
||||
</textarea>
|
||||
|
||||
<!-- Core code -->
|
||||
<script src="js/tile.js"></script>
|
||||
<script src="js/event.js"></script>
|
||||
|
||||
<!-- Tile modifiers -->
|
||||
<script src="js/gravity.js"></script>
|
||||
<script src="js/cohesion.js"></script>
|
||||
<script src="js/combine.js"></script>
|
||||
<script src="js/conway.js"></script>
|
||||
<script src="js/temperature.js"></script>
|
||||
<script src="js/state.js"></script>
|
||||
<script src="js/clone.js"></script>
|
||||
|
||||
<!-- Tile sets -->
|
||||
<script src="js/core_blocks.js"></script>
|
||||
|
||||
<!-- Game loop -->
|
||||
<script src="js/tick_handler.js"></script>
|
||||
<script src="js/core.js"></script>
|
||||
|
||||
<!-- Mod loader -->
|
||||
<script src="js/loader.js"></script>
|
||||
|
||||
<!-- Save/load -->
|
||||
<script src="js/save_load.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
60
js/clone.js
Normal file
60
js/clone.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Code for element combinations.
|
||||
TOOD: clean this up too
|
||||
*/
|
||||
|
||||
function duplicate(event) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let maxMass = -1;
|
||||
let currBlock = chunks.getBlock(cx, cy);
|
||||
let dir = [0,0];
|
||||
|
||||
for (let x = -1; x < 2; x++) {
|
||||
for (let y = 1; y >= -1; y--) {
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
|
||||
let mass = (blok != -1 && blok) ? mainTiles.tiles[blok].attributes.mass : 0;
|
||||
|
||||
if (mass > maxMass && blok != currBlock) {
|
||||
dir = [x,y];
|
||||
maxMass = mass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let offBlock = chunks.getBlock(cx + dir[0], cy + dir[1]);
|
||||
let offTemp = chunks.getBlock(cx + dir[0], cy + dir[1], true);
|
||||
|
||||
if (currBlock == -1 || offBlock == -1 || offBlock == currBlock) return;
|
||||
|
||||
for (let x = -1; x < 2; x++) {
|
||||
for (let y = 1; y >= -1; y--) {
|
||||
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
let oldTemp = chunks.getBlock(cx + x, cy + y, true);
|
||||
|
||||
if (oldTemp < -300) oldTemp = -300;
|
||||
|
||||
if (blok == currBlock) continue;
|
||||
chunks.setBlock(cx + x, cy + y, offBlock);
|
||||
chunks.setBlock(cx + x, cy + y, offTemp + oldTemp * 0.01, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Tile.prototype.duplicate = function () {
|
||||
let that = this;
|
||||
|
||||
that.interactions.push(function (event) {
|
||||
duplicate(event)
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
73
js/cohesion.js
Normal file
73
js/cohesion.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Code for cohesion.
|
||||
Allows water to stick to itself.
|
||||
|
||||
radius: How far out of blocks will contribute to sticking
|
||||
isAll:
|
||||
If this is false or undefined, cohesion occurs (sticks to self)
|
||||
If this is true, adhesion and cohesion ocucrs (sticks to non-air)
|
||||
|
||||
*/
|
||||
|
||||
function cohesion(event, radius, isAll = 0) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let dir = [0, 0];
|
||||
let force = [0,0];
|
||||
|
||||
let currBlock = chunks.getBlock(cx, cy);
|
||||
|
||||
for (let x = -radius; x <= radius; x ++) {
|
||||
for (let y = -radius; y <= radius; y++) {
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
|
||||
let factor = ((blok == currBlock) * (1-isAll)) + ((blok != air) * isAll);
|
||||
|
||||
if (factor == 0) continue;
|
||||
|
||||
let dist = 0.1 + (1/8 * (-x * -x - y * y + 8) * (x * x + y * y));
|
||||
|
||||
force[0] += x / dist * factor;
|
||||
force[1] += y / dist * factor;
|
||||
}
|
||||
}
|
||||
|
||||
if (force[0] == 0 && force[1] == 0) return;
|
||||
|
||||
dir[0] = (Math.abs(force[0]) < radius*0.1) ? 0 : Math.sign(force[0]);
|
||||
dir[1] = (Math.abs(force[1]) < radius*0.1) ? 0 : Math.sign(force[1]);
|
||||
|
||||
if (Math.abs(force[0]) < Math.abs(force[1])) {
|
||||
force[0] = 0
|
||||
} else {
|
||||
force[1] = 0
|
||||
}
|
||||
|
||||
let offBlock = chunks.getBlock(cx + dir[0], cy + dir[1]);
|
||||
|
||||
if (currBlock == -1 || offBlock == -1 || offBlock != air || currBlock == offBlock || chunks.noTick[(cx+dir[0])*chunks.height + (cy+dir[1])]) return;
|
||||
|
||||
chunks.setBlock(cx, cy, offBlock);
|
||||
chunks.setBlock(cx + dir[0], cy + dir[1], currBlock);
|
||||
|
||||
let t = chunks.getBlock(cx, cy,true);
|
||||
let t2 = chunks.getBlock(cx + dir[0], cy + dir[1],true);
|
||||
|
||||
if (t != undefined && t2 != undefined) {
|
||||
chunks.setBlock(cx, cy,t2, true);
|
||||
chunks.setBlock(cx + dir[0], cy + dir[1], t, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Tile.prototype.cohesion = function (radius, isAll) {
|
||||
this.interactions.push(function (event) {
|
||||
cohesion(event, radius, isAll)
|
||||
});
|
||||
return this;
|
||||
}
|
53
js/combine.js
Normal file
53
js/combine.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Code for element combinations.
|
||||
TOOD: clean this up too
|
||||
*/
|
||||
|
||||
function combine(event, inBlock, outBlock, outBlock2, mustAir = false) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let dir = [0, 0];
|
||||
let currBlock = chunks.getBlock(cx, cy);
|
||||
|
||||
for (let x = -1; x < 2; x++) {
|
||||
for (let y = 1; y >= -1; y--) {
|
||||
if (chunks.getBlock(cx + x, cy + y) == inBlock) {
|
||||
dir = [x, y];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let offBlock = chunks.getBlock(cx + dir[0], cy + dir[1]);
|
||||
let offBlock2 = chunks.getBlock(cx + dir[0], cy + dir[1] - 1);
|
||||
|
||||
if (mustAir && offBlock2 != air) return;
|
||||
if (currBlock == -1 || offBlock == -1 || offBlock != inBlock || currBlock == offBlock || chunks.noTick[(cx + dir[0]) * chunks.height + (cy + dir[1])]) return;
|
||||
|
||||
chunks.setBlock(cx, cy, outBlock);
|
||||
chunks.setBlock(cx + dir[0], cy + dir[1], outBlock2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Tile.prototype.combine = function (inBlock, outBlock2, outBlock, mustAir = false) {
|
||||
let that = this;
|
||||
|
||||
setTimeout(function() {
|
||||
|
||||
inBlock = mainTiles.resolveID(inBlock[0],inBlock[1]);
|
||||
outBlock = mainTiles.resolveID(outBlock[0],outBlock[1]);
|
||||
outBlock2 = mainTiles.resolveID(outBlock2[0],outBlock2[1]);
|
||||
|
||||
that.interactions.push(function (event) {
|
||||
|
||||
combine(event, inBlock, outBlock, outBlock2, mustAir)
|
||||
});
|
||||
},200)
|
||||
|
||||
return this;
|
||||
}
|
49
js/conway.js
Normal file
49
js/conway.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Conway's Game of Life.
|
||||
|
||||
*/
|
||||
|
||||
let queuedChanges = [];
|
||||
|
||||
function life(event, liveTile, deadTile) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let neighbors = 0;
|
||||
|
||||
let currBlock = chunks.getBlock(cx, cy);
|
||||
|
||||
liveTile = mainTiles.resolveID(liveTile[0], liveTile[1]);
|
||||
deadTile = mainTiles.resolveID(deadTile[0], deadTile[1]);
|
||||
|
||||
for (let x = -1; x <= 1; x++) {
|
||||
for (let y = -1; y <= 1; y++) {
|
||||
if (x == 0 && y == 0) continue;
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
neighbors += (blok == liveTile) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (currBlock == -1 || (currBlock != liveTile && currBlock != deadTile)) return;
|
||||
|
||||
if ((neighbors < 2 || neighbors > 3) && currBlock != deadTile) {
|
||||
chunks.queuedChanges.push([cx, cy, deadTile])
|
||||
}
|
||||
|
||||
if (neighbors == 3 && currBlock != liveTile) {
|
||||
chunks.queuedChanges.push([cx, cy, liveTile])
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Tile.prototype.life = function (liveTile, deadTile) {
|
||||
this.interactions.push(function (event) {
|
||||
life(event, liveTile, deadTile)
|
||||
});
|
||||
return this;
|
||||
}
|
218
js/core.js
Normal file
218
js/core.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
Code for rendering and startup.
|
||||
*/
|
||||
|
||||
function Canvas(width, height, upscale) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.upscale = upscale;
|
||||
this.radius = 2;
|
||||
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
|
||||
this.elem = document.querySelector('canvas');
|
||||
this.ctx = this.elem.getContext('2d');
|
||||
|
||||
this.blocks = new Uint16Array(width * height);
|
||||
this.temp = new Array(width * height); // This will not be saved in the world data.
|
||||
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
this.temp[i] = 0;
|
||||
}
|
||||
|
||||
this.sel = -1;
|
||||
|
||||
this.queuedChanges = [];
|
||||
|
||||
let that = this;
|
||||
|
||||
this.elem.addEventListener('mousedown', function (e) {
|
||||
that.clicked = true;
|
||||
});
|
||||
|
||||
this.elem.addEventListener('mousemove', function (e) {
|
||||
that.pageX = e.pageX;
|
||||
that.pageY = e.pageY;
|
||||
})
|
||||
|
||||
this.elem.addEventListener('wheel', function (e) {
|
||||
that.radius += Math.sign(e.deltaY);
|
||||
if (that.radius < 0) that.radius = 0;
|
||||
})
|
||||
|
||||
this.elem.addEventListener('mouseup', function (e) {
|
||||
that.clicked = false;
|
||||
|
||||
that.firstX = this.pageX;
|
||||
that.firstY = this.pageY;
|
||||
});
|
||||
|
||||
|
||||
this.clicked = false;
|
||||
this.pageX = 0;
|
||||
this.pageY = 0;
|
||||
|
||||
this.resize();
|
||||
}
|
||||
|
||||
Canvas.prototype.getBlock = function (x, y, doTemp) {
|
||||
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
||||
if (doTemp) return undefined;
|
||||
return -1;
|
||||
}
|
||||
return (doTemp ? this.temp : this.blocks)[x * this.height + y];
|
||||
}
|
||||
|
||||
Canvas.prototype.setBlock = function (x, y, block, doTemp) {
|
||||
if (this.getBlock(x, y) == -1) return;
|
||||
(doTemp ? this.temp : this.blocks)[x * this.height + y] = block
|
||||
}
|
||||
|
||||
Canvas.prototype.resize = function () {
|
||||
this.elem.style.width = this.width * this.upscale + 'px';
|
||||
this.elem.style.height = this.height * this.upscale + 'px';
|
||||
|
||||
this.elem.width = this.width;
|
||||
this.elem.height = this.height;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
Canvas.prototype.render = function () {
|
||||
this.ctx.clearRect(0, 0, this.width * this.upscale, this.height * this.upscale);
|
||||
|
||||
let imgData = this.ctx.getImageData(0, 0, this.width, this.height);
|
||||
let pixels = imgData.data;
|
||||
|
||||
let int = Math.ceil(this.width * this.height/8);
|
||||
for (let j = 0; j < 8; j++) {
|
||||
let that = this;
|
||||
(async function() {
|
||||
for (let i = j*int; i < (j+1)*int; i++) {
|
||||
if (i > that.width * that.height) break;
|
||||
let x = Math.floor(i / that.height);
|
||||
let y = i % that.height;
|
||||
|
||||
let i2 = x + y*that.width;
|
||||
|
||||
let block = mainTiles.tiles[that.blocks[i]];
|
||||
|
||||
let temp = that.temp[i];
|
||||
|
||||
if (block.color[0] != -1) {
|
||||
|
||||
let val = (temp + 310)/310;
|
||||
if (val < -2.861) val = -2.861;
|
||||
|
||||
pixels[i2*4] = (block.color[0] - temp / 1e28) * val ;
|
||||
pixels[i2*4+1] = (block.color[1] - temp / 1e28) * (val * 0.259 + 0.741);
|
||||
pixels[i2*4+2] = (block.color[2] - temp / 1e28) * (val * 0.023 + 0.977);
|
||||
pixels[i2*4+3] = block.color[3] * 255 + Math.abs(val-1) * 100 || 255;
|
||||
} else {
|
||||
let lg = Math.log(temp);
|
||||
pixels[i2*4] = ((handler.ticks*69 ) % (lg*0.6969)) * 255 / (lg*0.6969);
|
||||
pixels[i2*4+1] = ((handler.ticks*69) % (lg*0.420420)) * 255 /(lg*0.420420);
|
||||
pixels[i2*4+2] = ((handler.ticks*69) % (lg*0.13371337)) * 255 / (lg*0.13371337);
|
||||
pixels[i2*4+3] = 255;
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
/* TODO: clean up */
|
||||
|
||||
this.ctx.putImageData(imgData,0,0,0,0,this.width,this.height)
|
||||
|
||||
if (window.loc2 && loc2.get('only') == 'true') {
|
||||
this.stopNow = true;
|
||||
document.querySelector('canvas').id = 'main2';
|
||||
document.querySelector('body').id = 'no-overflow';
|
||||
return;
|
||||
}
|
||||
|
||||
let x = (this.pageX - this.elem.getBoundingClientRect().x - scrollX + this.x) - 0.5 - this.radius * this.upscale;
|
||||
let y = (this.pageY - this.elem.getBoundingClientRect().y - scrollY + this.y) - 0.5 - this.radius * this.upscale;
|
||||
|
||||
|
||||
this.ctx.globalAlpha = 1;
|
||||
|
||||
this.ctx.strokeStyle = 'rgb(255,255,255)';
|
||||
this.ctx.lineWidth = 2;
|
||||
this.ctx.strokeRect(x / this.upscale, y / this.upscale, this.radius * 2 + 2, this.radius * 2 + 2);
|
||||
|
||||
let theX = Math.floor(x/this.upscale + this.radius + 1);
|
||||
let theY = Math.floor(y/this.upscale + this.radius + 1);
|
||||
|
||||
|
||||
|
||||
let blok = mainTiles.tiles[this.getBlock(theX, theY)];
|
||||
let temp = this.getBlock(theX,theY, true);
|
||||
|
||||
if (blok) {
|
||||
document.querySelector('.info').textContent = `${blok.namespace}; ${blok.id}; ${Math.round(temp+23)}deg Celsius`
|
||||
} else {
|
||||
document.querySelector('.info').textContent = `Unknown`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* TODO: cleanup again */
|
||||
Canvas.prototype.click = function () {
|
||||
if (this.firstX == undefined) {
|
||||
this.firstX = this.pageX;
|
||||
this.firstY = this.pageY;
|
||||
return;
|
||||
}
|
||||
|
||||
let x = (this.pageX - this.elem.getBoundingClientRect().x - scrollX + this.x) / this.upscale;
|
||||
let y = (this.pageY - this.elem.getBoundingClientRect().y - scrollY + this.y) / this.upscale;
|
||||
|
||||
let x2 = (this.firstX - this.elem.getBoundingClientRect().x - scrollX + this.x) / this.upscale;
|
||||
let y2 = (this.firstY - this.elem.getBoundingClientRect().y - scrollY + this.y) / this.upscale;
|
||||
|
||||
x = Math.floor(x);
|
||||
y = Math.floor(y);
|
||||
|
||||
let x3 = x2 = Math.floor(x2);
|
||||
let y3 = y2 = Math.floor(y2);
|
||||
|
||||
do {
|
||||
if (Math.abs(x3 - x) > Math.abs(y3 - y)) {
|
||||
x3 += Math.sign(x - x3)
|
||||
} else {
|
||||
y3 += Math.sign(y - y3)
|
||||
}
|
||||
|
||||
for (let x4 = x3 - this.radius; x4 <= x3 + this.radius; x4++) {
|
||||
for (let y4 = y3 - this.radius; y4 <= y3 + this.radius; y4++) {
|
||||
|
||||
let blox = this.getBlock(x4, y4);
|
||||
|
||||
if (blox == -1) continue;
|
||||
|
||||
this.setBlock(x4, y4, mainTiles.tiles[mainTiles.sel].attributes.temperature, true);
|
||||
this.setBlock(x4, y4, mainTiles.sel);
|
||||
}
|
||||
}
|
||||
|
||||
} while (x3 != x && y3 != y)
|
||||
|
||||
this.firstX = this.pageX;
|
||||
this.firstY = this.pageY;
|
||||
}
|
||||
|
||||
var canvas = new Canvas(240, 135, 4);
|
||||
var handler = new TickHandler(canvas);
|
||||
|
||||
setInterval(() => {
|
||||
if (canvas.stopNow) return;
|
||||
handler.tick();
|
||||
}, 1000 / 60);
|
||||
|
||||
|
||||
setInterval(() => {
|
||||
if (canvas.stopNow) return;
|
||||
if (canvas.clicked) canvas.click();
|
||||
this.canvas.render();
|
||||
}, 1000 / 60);
|
239
js/core_blocks.js
Normal file
239
js/core_blocks.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
Code for most implemented blocks.
|
||||
|
||||
This code isn't in JSON form for reading purposes.
|
||||
|
||||
If you want to add a modification, use the JSON format
|
||||
documented in [js/loader.js].
|
||||
*/
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Air',
|
||||
[
|
||||
new Tile('none', 'Air').gravity(1.4 / 1000, 4, 200)
|
||||
.temperature(0,0.01)
|
||||
.state(['Vanilla/Air', 'Hot Air'],50,true),
|
||||
|
||||
new Tile('none', 'Hot Air').gravity(1.4 / 1100, 4, 100)
|
||||
.temperature(100,0.02)
|
||||
.state(['Vanilla/Air', 'Air'],50,false)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true),
|
||||
|
||||
new Tile('rgb(0,0,0)', 'Vacuum').gravity(0.01 / 1000 / 1000, 4, Infinity)
|
||||
.temperature(-269.15,0),
|
||||
|
||||
new Tile('rgb(180,156,229)', 'Hydrogen').gravity(0.8 / 1000, 4, 200) //H2
|
||||
.temperature(0,0.1)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.combine(['Vanilla/Air', 'Air'], ['Vanilla/Air', 'Hydrogen Flame'], ['Vanilla/Air', 'Hydrogen Flame'])
|
||||
.combine(['Vanilla/Air', 'Plasma'], ['Vanilla/Air', 'Helium'], ['Vanilla/Air', 'Vacuum']),
|
||||
|
||||
new Tile('rgb(255,0,239)', 'Plasma').gravity(1.4 / 5000, 4, 200)
|
||||
.temperature(3010,2)
|
||||
.state(['Vanilla/Air', 'Hydrogen'],3000,false)
|
||||
.state(['Vanilla/Air', '???'],1e30,true),
|
||||
|
||||
new Tile('random', '???').gravity(1e100, 4, 1e105)
|
||||
.temperature(1e31,2),
|
||||
|
||||
new Tile('rgb(200,186,249)', 'Hydrogen Flame').gravity(0.8 / 1000, 4, 200)
|
||||
.temperature(50,0.2)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.combine(['Vanilla/Air', 'Hydrogen'], ['Vanilla/Water', 'Steam'], ['Vanilla/Air', 'Hydrogen']),
|
||||
|
||||
new Tile('rgb(229,194,156)', 'Helium').gravity(0.7 / 1000, 4, 200)
|
||||
.temperature(0,0.001)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true),
|
||||
|
||||
new Tile('rgba(0,0,0,0.2)', 'Carbon Dioxide').gravity(1.3 / 1000, 4, 200)
|
||||
.temperature(0,0.03)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Carbon Dioxide'], ['Vanilla/Fire', 'Fire']),
|
||||
|
||||
new Tile('rgba(0,0,0,0.4)', 'Methane').gravity(1.2 / 1000, 4, 200)
|
||||
.temperature(0,0.04)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Methane'], ['Vanilla/Fire', 'Fire'])
|
||||
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Earth',
|
||||
[
|
||||
new Tile('rgb(153, 102, 51)', 'Earth').gravity(10, 1, 91),
|
||||
|
||||
new Tile('rgb(143, 92, 41)', 'Soil').gravity(10, 1, 93),
|
||||
|
||||
new Tile('rgb(255,0,0)', 'Barrier').unGravity(),
|
||||
|
||||
new Tile('rgb(20,20,20)', 'Charcoal').gravity(11, 1, 100)
|
||||
.temperature(0,0.5)
|
||||
.combine(['Vanilla/Air', 'Hot Air'], ['Vanilla/Fire', 'Fire'], ['Vanilla/Fire', 'Fire'])
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Carbon Dioxide'], ['Vanilla/Air', 'Vacuum'])
|
||||
.combine(['Vanilla/Water', 'Steam'], ['Vanilla/Air', 'Methane'], ['Vanilla/Air', 'Air']),
|
||||
|
||||
new Tile('rgb(53,46,32)', 'Mud').cohesion(2).gravity(12, 1.5, 113),
|
||||
|
||||
new Tile('rgb(43, 33, 42)', 'Mudstone').gravity(10, 1, 89)
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Mud'], ['Vanilla/Air', 'Air']),
|
||||
|
||||
new Tile('rgb(252,224,133)', 'Sand').gravity(10, 1, 93)
|
||||
.state(['Vanilla/Earth', 'Glass'],1650,true)
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Air'], ['Vanilla/Earth', 'Sand'])
|
||||
.combine(['Vanilla/Water', 'Acid'], ['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Clay']),
|
||||
|
||||
new Tile('rgb(117,111,86)', 'Wet Sand').cohesion(2).gravity(12, 1.5, 112)
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Air'], ['Vanilla/Earth', 'Packed Sand']),
|
||||
|
||||
new Tile('rgb(187,158,110)', 'Packed Sand').gravity(11, 1.5, 103)
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Air'], ['Vanilla/Earth', 'Sandstone']),
|
||||
|
||||
new Tile('rgb(167,138,90)', 'Sandstone').unGravity(),
|
||||
|
||||
new Tile('rgba(128,148,168,0.8)', 'Glass').gravity(10, 1, 93),
|
||||
|
||||
new Tile('rgb(128,128,128)', 'Gravel').gravity(10, 1, 932)
|
||||
.combine(['Vanilla/Water', 'Acid'], ['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Sand'])
|
||||
.combine(['Vanilla/Life', 'Mycelium'], ['Vanilla/Air', 'Air'], ['Vanilla/Earth', 'Earth']),
|
||||
|
||||
new Tile('rgb(56, 54, 52)', 'Basalt')
|
||||
.unGravity()
|
||||
.temperature(0,0.005)
|
||||
.state(['Vanilla/Fire', 'Lava'],1000,true),
|
||||
|
||||
new Tile('rgb(169,179,210)', 'Clay').gravity(10, 1, 89)
|
||||
.state(['Vanilla/Earth', 'Brick'],1000,true),
|
||||
|
||||
new Tile('rgb(211,108,108)', 'Brick').unGravity(),
|
||||
|
||||
new Tile('rgb(66, 64, 62)', 'Rock').gravity(10, 1, 89)
|
||||
.combine(['Vanilla/Water', 'Acid'], ['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Gravel']),
|
||||
|
||||
new Tile('rgb(56, 54, 52)', 'Rock Barrier').unGravity()
|
||||
.state(['Vanilla/Earth', 'Rock'],800,true)
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Rock'], ['Vanilla/Water', 'Water'])
|
||||
.combine(['Vanilla/Water', 'Acid'], ['Vanilla/Water', 'Water'], ['Vanilla/Earth', 'Rock']),
|
||||
|
||||
new Tile('rgb(235, 235, 235)', 'Salt')
|
||||
.gravity(10, 1, 110)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
]
|
||||
)
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Life',
|
||||
[
|
||||
new Tile('rgb(114, 204, 123)', 'Grass').gravity(10, 1, 91)
|
||||
.temperature(0,0.3)
|
||||
.combine(['Vanilla/Earth', 'Earth'], ['Vanilla/Life', 'Grass'], ['Vanilla/Life', 'Grass'], true)
|
||||
.state(['Vanilla/Earth', 'Charcoal'],500,true),
|
||||
|
||||
new Tile('rgb(97, 92, 97)', 'Mycelium').gravity(10, 1, 91)
|
||||
.temperature(0,0.3)
|
||||
.combine(['Vanilla/Earth', 'Earth'], ['Vanilla/Life', 'Mycelium'], ['Vanilla/Life', 'Mycelium'], true)
|
||||
.combine(['Vanilla/Earth', 'Earth'], ['Vanilla/Earth', 'Soil'], ['Vanilla/Life', 'Mycelium'])
|
||||
.state(['Vanilla/Earth', 'Charcoal'],500,true),
|
||||
|
||||
new Tile('rgb(245,245,245)', 'Alive Conway Cell').life(
|
||||
['Vanilla/Life', 'Alive Conway Cell'],
|
||||
['Vanilla/Life', 'Dead Conway Cell']),
|
||||
|
||||
new Tile('rgb(10,10,10)', 'Dead Conway Cell').life(
|
||||
['Vanilla/Life', 'Alive Conway Cell'],
|
||||
['Vanilla/Life', 'Dead Conway Cell']),
|
||||
|
||||
new Tile('rgb(25,30,35)', 'Conway Buffer').gravity(10, 1, 91)
|
||||
.combine(['Vanilla/Life', 'Alive Conway Cell'], ['Vanilla/Life', 'Conway Buffer'], ['Vanilla/Life', 'Grass'])
|
||||
.combine(['Vanilla/Life', 'Dead Conway Cell'], ['Vanilla/Life', 'Conway Buffer'], ['Vanilla/Life', 'Mycelium'])
|
||||
]
|
||||
);
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Water',
|
||||
[
|
||||
|
||||
new Tile('rgb(51, 153, 255)', 'Water').cohesion(2,0.2).gravity(1, 2, 110)
|
||||
.temperature(-5,0.05,5)
|
||||
.state(['Vanilla/Water', 'Steam'],100,true)
|
||||
.state(['Vanilla/Water', 'Ice'],-23,false)
|
||||
.combine(['Vanilla/Earth', 'Earth'], ['Vanilla/Earth', 'Mud'], ['Vanilla/Air', 'Air'])
|
||||
.combine(['Vanilla/Earth', 'Sand'], ['Vanilla/Earth', 'Wet Sand'], ['Vanilla/Air', 'Air']),
|
||||
|
||||
new Tile('rgb(45, 255, 15)', 'Acid').cohesion(2,0.3).gravity(1.01, 2.2, 110)
|
||||
.temperature(-5,0.05,5)
|
||||
.state(['Vanilla/Water', 'Steam'],100,true)
|
||||
.state(['Vanilla/Water', 'Ice'],-23,false),
|
||||
|
||||
new Tile('rgb(81, 200, 255)', 'Ice').unGravity()
|
||||
.temperature(-30,0.05,5)
|
||||
.state(['Vanilla/Water', 'Water'],-23,true),
|
||||
|
||||
new Tile('rgb(255, 255, 255)', 'Snow')
|
||||
.temperature(-30,0.05,5)
|
||||
.state(['Vanilla/Water', 'Water'],-23,true)
|
||||
.gravity(10, 1, 93),
|
||||
|
||||
new Tile('rgb(208,232,237)', 'Steam').gravity(1.2 / 1000, 3, 11)
|
||||
.temperature(80,0.1,0.5)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.state(['Vanilla/Water', 'Water'],77,false)
|
||||
.state(['Vanilla/Water', 'Snow'],-23,false),
|
||||
|
||||
new Tile('rgb(145,201,152)', 'Slime').cohesion(5,0.8).gravity(1.5, 2, 16)
|
||||
.combine(['Vanilla/Water', 'Acid'], ['Vanilla/Earth', 'Salt'], ['Vanilla/Water', 'Water'])
|
||||
.state(['Vanilla/Water', 'Steam'],100,true)
|
||||
.state(['Vanilla/Water', 'Ice'],-23,false)
|
||||
]
|
||||
)
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Fire',
|
||||
[
|
||||
new Tile('rgb(255, 64, 0)', 'Fire')
|
||||
.temperature(1200,1)
|
||||
.state(['Vanilla/Air', 'Carbon Dioxide'],500,false)
|
||||
.state(['Vanilla/Air', 'Plasma'],3000,true)
|
||||
.gravity(1.4 / 1000, 4, 200)
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Water', 'Steam'], ['Vanilla/Air', 'Carbon Dioxide'])
|
||||
.combine(['Vanilla/Air', 'Vacuum'], ['Vanilla/Air', 'Carbon Dioxide'], ['Vanilla/Air', 'Vacuum'])
|
||||
.combine(['Vanilla/Air', 'Air'], ['Vanilla/Fire', 'Fire'], ['Vanilla/Air', 'Hot Air'])
|
||||
.combine(['Vanilla/Air', 'Carbon Dioxide'],['Vanilla/Air', 'Carbon Dioxide'],['Vanilla/Air', 'Carbon Dioxide'],),
|
||||
|
||||
new Tile('rgb(128, 32, 0)', 'Lava').cohesion(2,0.2).gravity(1, 2, 11)
|
||||
.temperature(1125,0.1)
|
||||
.state(['Vanilla/Earth', 'Basalt'],1000,false)
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Water', 'Steam'], ['Vanilla/Earth', 'Basalt'])
|
||||
]
|
||||
)
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Machines',
|
||||
[
|
||||
new Tile('rgb(237, 162, 71)', 'Copper')
|
||||
.temperature(0,0.1),
|
||||
|
||||
new Tile('rgb(255,255,128)', 'Duplicator')
|
||||
.unGravity()
|
||||
.gravity(1000,0,0)
|
||||
.duplicate()
|
||||
]
|
||||
)
|
||||
|
||||
mainTiles.loadSet(
|
||||
'Vanilla/Sponge',
|
||||
[
|
||||
new Tile('rgb(158,150,91)', 'Sponge').unGravity()
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Air', 'Air'], ['Vanilla/Sponge', 'Wet Sponge']),
|
||||
|
||||
new Tile('rgb(86,81,39)', 'Wet Sponge').unGravity()
|
||||
.combine(['Vanilla/Fire', 'Fire'], ['Vanilla/Water', 'Steam'], ['Vanilla/Sponge', 'Sponge'])
|
||||
.combine(['Vanilla/Sponge', 'Sponge'], ['Vanilla/Sponge', 'Wet Sponge'], ['Vanilla/Sponge', 'Sponge']),
|
||||
|
||||
new Tile('rgb(255,255,0)', 'Infinite Sponge').unGravity()
|
||||
.combine(['Vanilla/Water', 'Water'], ['Vanilla/Air', 'Air'], ['Vanilla/Sponge', 'Infinite Sponge']),
|
||||
]
|
||||
);
|
||||
|
||||
let air = mainTiles.resolveID('Vanilla/Air','Air');
|
14
js/event.js
Normal file
14
js/event.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
Code for handling events.
|
||||
*/
|
||||
|
||||
function GameEvent(type, target, data, canvas) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
this.canvas = canvas;
|
||||
this.target = target;
|
||||
|
||||
for (let interaction of target.interactions) {
|
||||
if (interaction(this)) return this;
|
||||
}
|
||||
}
|
99
js/gravity.js
Normal file
99
js/gravity.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Code for gravity.
|
||||
|
||||
mass: Density (in kg/cm^3)
|
||||
fluid: Spread of substance
|
||||
saturation: Maximum mass for gravity reactions to occur
|
||||
*/
|
||||
|
||||
function gravity(event, mass, fluid, saturation) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let dir = [0, 0];
|
||||
let force = [0,0];
|
||||
let density = 0;
|
||||
|
||||
let currBlock = chunks.getBlock(cx, cy);
|
||||
|
||||
|
||||
for (let x = -1; x < 2; x ++) {
|
||||
for (let y = -1; y < 2; y++) {
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
|
||||
if (blok == -1) continue;
|
||||
|
||||
let mass2 = mainTiles.tiles[blok].attributes.mass;
|
||||
|
||||
density += mass2;
|
||||
if (density > saturation) break;
|
||||
|
||||
if (blok == currBlock) continue;
|
||||
|
||||
let massDiff = (mass / mass2) - (mass2 / mass);
|
||||
let x2 = x / fluid;
|
||||
let dirDiff = (y - 1 + fluid) / (1/8 * (x2 * x2 - y * y + 8) * (x2 * x2 + y * y));
|
||||
|
||||
|
||||
if (y == 0 && x == 0) dirDiff = 0;
|
||||
if (isNaN(dirDiff)) dirDiff = 0;
|
||||
|
||||
force[0] += massDiff * x2;
|
||||
force[1] += massDiff * dirDiff * y;
|
||||
}
|
||||
if (density > saturation) break;
|
||||
}
|
||||
|
||||
dir[0] = (Math.abs(force[0]) < .5) ? 0 : Math.sign(force[0]);
|
||||
dir[1] = (Math.abs(force[1]) < .5) ? 0 : Math.sign(force[1]);
|
||||
|
||||
if (density > saturation ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let offBlock = chunks.getBlock(cx + dir[0], cy + dir[1]);
|
||||
|
||||
if (currBlock == offBlock && density <= saturation) {
|
||||
dir[0] = Math.sign(force[0]);
|
||||
offBlock = chunks.getBlock(cx + dir[0], cy + dir[1]);
|
||||
}
|
||||
|
||||
if (currBlock == -1 || offBlock == -1 || currBlock == offBlock ||offBlock == undefined || mainTiles.tiles[offBlock].attributes.saturation / 9 < mass || chunks.noTick[(cx+dir[0])*chunks.height + (cy+dir[1])]) return;
|
||||
|
||||
|
||||
|
||||
if (!canGravity[offBlock]) return;
|
||||
|
||||
chunks.noTick[cx*chunks.height + cy] = true;
|
||||
chunks.noTick[(cx+dir[0])*chunks.height + (cy+dir[1])] = true;
|
||||
|
||||
chunks.setBlock(cx, cy, offBlock);
|
||||
chunks.setBlock(cx + dir[0], cy + dir[1], currBlock);
|
||||
|
||||
let t = chunks.getBlock(cx, cy,true);
|
||||
let t2 = chunks.getBlock(cx + dir[0], cy + dir[1],true);
|
||||
|
||||
if (t != undefined && t2 != undefined) {
|
||||
chunks.setBlock(cx, cy,t2, true);
|
||||
chunks.setBlock(cx + dir[0], cy + dir[1], t, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Tile.prototype.gravity = function (mass, fluid, saturation) {
|
||||
this.interactions.push(function (event) {
|
||||
gravity(event, mass, fluid, saturation)
|
||||
});
|
||||
this.attributes.mass = mass;
|
||||
this.attributes.saturation = saturation;
|
||||
return this;
|
||||
}
|
||||
|
||||
Tile.prototype.unGravity = function () {
|
||||
this.attributes.noGravity = true;
|
||||
return this;
|
||||
}
|
55
js/loader.js
Normal file
55
js/loader.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Secure JSON loader for modding purposes.
|
||||
|
||||
Unless you want to do something really fancy
|
||||
or provide a basis for more mods,
|
||||
do not use raw JavaScript.
|
||||
|
||||
Instead, use a JSON file to contain your mod's
|
||||
content, and ask me for essential features to be
|
||||
added.
|
||||
|
||||
This isn't finished entirely, but should give a
|
||||
decent basis for modding in the future.
|
||||
*/
|
||||
|
||||
legalFuncs = [
|
||||
"gravity",
|
||||
"cohesion",
|
||||
"combine",
|
||||
"unGravity",
|
||||
"life",
|
||||
"temperature",
|
||||
"state",
|
||||
"duplicate"
|
||||
]
|
||||
|
||||
function loadTiles(stuff) {
|
||||
for (item in stuff) {
|
||||
let params = stuff[item].params;
|
||||
stuff[item] = new Tile(stuff[item].color, stuff[item].name);
|
||||
for (let param of params) {
|
||||
if (legalFuncs.indexOf(param.func) == -1) {
|
||||
console.warn('This function is not supported!');
|
||||
continue;
|
||||
}
|
||||
stuff[item] = stuff[item][param.func](...param.options)
|
||||
}
|
||||
}
|
||||
return stuff;
|
||||
}
|
||||
|
||||
function loadMod(stuff) {
|
||||
for (let thing of stuff) {
|
||||
mainTiles.loadSet(
|
||||
thing.namespace,
|
||||
loadTiles(thing.content)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function openMods(stuff) {
|
||||
// TODO: don't use prompt
|
||||
let url = prompt('Type in the URL to the JSON of the mod you want to load.');
|
||||
loadMod(await (await fetch(url)).json())
|
||||
}
|
113
js/save_load.js
Normal file
113
js/save_load.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Code for saving and loading data.
|
||||
|
||||
Features a somewhat efficient compression algorithm.
|
||||
*/
|
||||
|
||||
function save() {
|
||||
let jason = {
|
||||
'pal': [],
|
||||
'data': [],
|
||||
'width': canvas.width,
|
||||
'height': canvas.height
|
||||
};
|
||||
|
||||
for (let item of mainTiles.tiles) {
|
||||
jason.pal.push([
|
||||
item.namespace,
|
||||
item.id
|
||||
])
|
||||
}
|
||||
|
||||
let json = jason.data;
|
||||
|
||||
for (let i = 0; i < canvas.blocks.length; i += 128) {
|
||||
let arr = canvas.blocks.slice(i, i + 128);
|
||||
|
||||
let pal = Object.values(arr.filter((v, i, a) => a.findIndex(v2 => (v2 === v)) === i).sort());
|
||||
let otherArray;
|
||||
if (pal.length < 9 && pal.length > 1) {
|
||||
otherArray = new Uint8Array(64);
|
||||
|
||||
for (let i in otherArray) {
|
||||
otherArray[i] = (((pal.indexOf(arr[i*2]) * 8) + pal.indexOf(arr[i*2+1])) + 'A'.charCodeAt()) % 128;
|
||||
}
|
||||
|
||||
} else if (pal.length > 8) {
|
||||
otherArray = new Uint8Array(128);
|
||||
|
||||
for (let i in otherArray) {
|
||||
otherArray[i] = (pal.indexOf(arr[i]) + 'A'.charCodeAt()) % 128;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
json[i / 128] = {
|
||||
'pal': pal,
|
||||
'dat': otherArray ? new TextDecoder('ascii').decode(otherArray) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
document.querySelector('#code').value = JSON.stringify(jason);
|
||||
}
|
||||
|
||||
function load() {
|
||||
let jason = JSON.parse(document.querySelector('#code').value);
|
||||
|
||||
let json = jason.data;
|
||||
|
||||
canvas.width = jason.width;
|
||||
canvas.height = jason.height;
|
||||
canvas.resize();
|
||||
|
||||
let mainPal = jason.pal.map(x => mainTiles.resolveID(x[0],x[1]));
|
||||
|
||||
console.log(mainPal);
|
||||
|
||||
for (let i in json) {
|
||||
let data = json[i];
|
||||
let pal = data.pal;
|
||||
let dat = new TextEncoder('ascii').encode(data.dat);
|
||||
|
||||
let otherArray = new Uint16Array(128);
|
||||
|
||||
if (pal.length < 2) {
|
||||
for (let i in otherArray) {
|
||||
otherArray[i] = mainPal[(pal[0])];
|
||||
}
|
||||
|
||||
} else if (pal.length < 9) {
|
||||
for (let i in dat) {
|
||||
otherArray[i*2] = mainPal[pal[((dat[i] - 'A'.charCodeAt()) & 0x38) / 8]];
|
||||
otherArray[i*2+1] = mainPal[pal[(dat[i] - 'A'.charCodeAt()) & 0x7]];
|
||||
}
|
||||
|
||||
} else {
|
||||
for (let i in dat) {
|
||||
otherArray[i] = mainPal[pal[(dat[i] - 'A'.charCodeAt()) & 0x7F]];
|
||||
}
|
||||
}
|
||||
|
||||
canvas.blocks.set(otherArray,Math.min(i*128,canvas.blocks.length - 128));
|
||||
}
|
||||
|
||||
for (let i in canvas.temp) {
|
||||
canvas.temp[i] = mainTiles.tiles[canvas.blocks[i]].attributes.temperature;
|
||||
}
|
||||
}
|
||||
|
||||
var loc3 = new URL(window.location).searchParams;
|
||||
let loc = loc3.get("embed");
|
||||
if (loc3.get('oops') == 'true') {
|
||||
alert('Oh no!');
|
||||
}
|
||||
var loc2;
|
||||
|
||||
if (loc) {
|
||||
(async function() {
|
||||
document.querySelector('#code').value = await fetch(loc).then(x => x.text());
|
||||
load();
|
||||
loc2 = loc3;
|
||||
})()
|
||||
}
|
27
js/state.js
Normal file
27
js/state.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Controls a block's state of matter.
|
||||
*/
|
||||
|
||||
function state(event, state, temperature, isMin) {
|
||||
if (event.type != 'tick') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
let temp = chunks.getBlock(cx, cy, true);
|
||||
|
||||
if (chunks.noTick[cx*chunks.height + cy]) return;
|
||||
|
||||
if ((temp > temperature) == isMin) {
|
||||
chunks.setBlock(cx, cy, mainTiles.resolveID(state[0],state[1]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Tile.prototype.state = function ( state2, temperature, isMin) {
|
||||
this.interactions.push(function (event) {
|
||||
state(event, state2, temperature, isMin)
|
||||
});
|
||||
return this;
|
||||
}
|
49
js/temperature.js
Normal file
49
js/temperature.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Controls a block's temperature,
|
||||
and how much it will conduct or insulate.
|
||||
|
||||
Temperature is in celsius,
|
||||
but offset from room temperature (23 celsius).
|
||||
*/
|
||||
|
||||
|
||||
function temperature(event, conduct, transfer = 0) {
|
||||
if (event.type != 'temp') return;
|
||||
|
||||
let cx = event.data[0];
|
||||
let cy = event.data[1];
|
||||
let chunks = event.canvas;
|
||||
|
||||
for (let x = -1; x < 2; x ++) {
|
||||
for (let y = -1; y < 2; y++) {
|
||||
let blok = chunks.getBlock(cx + x, cy + y);
|
||||
let temp = chunks.getBlock(cx, cy, true);
|
||||
let temp2 = chunks.getBlock(cx + x, cy + y, true);
|
||||
|
||||
if (temp2 == undefined || temp == undefined || (x == 0 && y == 0)) {
|
||||
if (temp != undefined) chunks.setBlock(cx, cy, Math.max(temp,-296.15), true);
|
||||
continue;
|
||||
};
|
||||
|
||||
let conduct2 = (blok != -1) ? (mainTiles.tiles[blok].attributes.conduct || 0) : 0;
|
||||
|
||||
let conductSum = Math.min(conduct * conduct2 * 10, 0.5);
|
||||
let s = conductSum*(temp2 - temp + transfer);
|
||||
|
||||
s += temp * -0.01;
|
||||
if (temp < -296.15 && temp2 > -296.15) s = temp2 - temp;
|
||||
|
||||
chunks.setBlock(cx, cy, Math.max(s + temp,-296.15), true);
|
||||
chunks.setBlock(cx+x, cy+y, Math.max(temp2 - s,-296.15), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tile.prototype.temperature = function (temp, conduct, transfer) {
|
||||
this.interactions.push(function (event) {
|
||||
temperature(event, conduct, transfer)
|
||||
});
|
||||
this.attributes.temperature = temp;
|
||||
this.attributes.conduct = conduct;
|
||||
return this;
|
||||
}
|
55
js/tick_handler.js
Normal file
55
js/tick_handler.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Code for global game ticks.
|
||||
*/
|
||||
|
||||
function TickHandler(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ticks = 0;
|
||||
this.noTick = false;
|
||||
}
|
||||
|
||||
TickHandler.prototype.tick = function () {
|
||||
if (this.noTick) return;
|
||||
|
||||
let canvas = this.canvas;
|
||||
|
||||
this.canvas.noTick = new Uint16Array(canvas.blocks.length);
|
||||
|
||||
for (let i = 0; i < canvas.width * canvas.height; i++) {
|
||||
let cx = Math.floor(i / canvas.height);
|
||||
let cy = i % canvas.height;
|
||||
|
||||
if (this.canvas.noTick[i]) continue;
|
||||
|
||||
let currBlock = this.canvas.blocks[i];
|
||||
|
||||
let allEq = true;
|
||||
|
||||
if (this.ticks % 10 == 0) {
|
||||
new GameEvent('temp', mainTiles.tiles[currBlock], [cx, cy, this.ticks], this.canvas);
|
||||
}
|
||||
|
||||
for (let x = -1; x < 2; x ++) {
|
||||
for (let y = -1; y < 2; y++) {
|
||||
let blok = this.canvas.getBlock(cx + x, cy + y);
|
||||
|
||||
allEq = (blok == currBlock);
|
||||
|
||||
if (!allEq) break;
|
||||
}
|
||||
if (!allEq) break;
|
||||
}
|
||||
|
||||
if (allEq) continue;
|
||||
|
||||
new GameEvent('tick', mainTiles.tiles[currBlock], [cx, cy, this.ticks], this.canvas);
|
||||
}
|
||||
|
||||
for (let change of canvas.queuedChanges) {
|
||||
canvas.setBlock(change[0], change[1], change[2]);
|
||||
}
|
||||
canvas.queuedChanges = [];
|
||||
|
||||
this.ticks++;
|
||||
this.ticks = this.ticks % 3600;
|
||||
}
|
108
js/tile.js
Normal file
108
js/tile.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Code for configuring and appending tiles.
|
||||
|
||||
Every item and block in the game is
|
||||
represented in one unified Tile class.
|
||||
|
||||
A prototype of an API is provided
|
||||
for players who wish to modify the game,
|
||||
to prevent conflicts from manually setting
|
||||
sections of an array.
|
||||
*/
|
||||
|
||||
var canGravity = [
|
||||
|
||||
];
|
||||
|
||||
function Tile(color, id) {
|
||||
this.color = color;
|
||||
|
||||
this.id = id;
|
||||
this.number = -1;
|
||||
this.interactions = [];
|
||||
this.attributes = {};
|
||||
this.attributes.temperature = 0;
|
||||
this.attributes.conduct = 0.01;
|
||||
|
||||
this.color = (color == 'none') ? [181,204,253,1/255] : color.replace(/^[^\(]+\(/,'').replace(/\)$/,'').split(',').map(x => 1 * x)
|
||||
if (color == 'random') this.color = [-1,-1,-1]; // ugly and hard-coded, but somehow faster?
|
||||
|
||||
/*
|
||||
Interactions are used for dynamic functions that
|
||||
depend on world state, while attributes are used
|
||||
for block attributes that are static, or modified
|
||||
by other interactions.
|
||||
|
||||
I highly suggest you define certain modifiers
|
||||
as a prototype of Tile that returns
|
||||
itself. This allows for prototype chaining
|
||||
within tile definitions.
|
||||
*/
|
||||
}
|
||||
|
||||
function TileManager(row, row2) {
|
||||
this.tiles = [];
|
||||
this.row = row;
|
||||
this.row2 = row2;
|
||||
this.sel = 0;
|
||||
|
||||
this.used = {};
|
||||
|
||||
}
|
||||
|
||||
TileManager.prototype.loadSet = function (namespace, tiles) {
|
||||
let path = namespace.split('/');
|
||||
|
||||
let elem = document.createElement('a');
|
||||
elem.textContent = namespace;
|
||||
elem.href = `#${namespace}`
|
||||
this.row.appendChild(elem);
|
||||
|
||||
let elem2 = document.createElement('section');
|
||||
elem2.id = namespace
|
||||
this.row2.appendChild(elem2);
|
||||
|
||||
|
||||
for (let tile of tiles) {
|
||||
tile.namespace = namespace;
|
||||
tile.number = this.tiles.length;
|
||||
this.tiles.push(tile);
|
||||
|
||||
canGravity[tile.number] = !tile.attributes.noGravity;
|
||||
|
||||
if (path.indexOf('secret') != -1) continue;
|
||||
|
||||
elem = document.createElement('button');
|
||||
elem.textContent = tile.id;
|
||||
elem2.appendChild(elem);
|
||||
|
||||
elem.addEventListener('click', () => {
|
||||
this.sel = tile.number;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
TileManager.prototype.resolveID = function (namespace, name) {
|
||||
let resolved = this.tiles
|
||||
.findIndex(tile =>
|
||||
tile.namespace == namespace &&
|
||||
tile.id == name
|
||||
);
|
||||
return resolved
|
||||
}
|
||||
|
||||
TileManager.prototype.resolve = function (namespace, name) {
|
||||
let id = this.resolveID(namespace, name);
|
||||
return this.tiles[id];
|
||||
}
|
||||
|
||||
var mainTiles = new TileManager(
|
||||
document.querySelector('.menu'),
|
||||
document.querySelector('.buttons')
|
||||
);
|
||||
|
||||
/*
|
||||
You can theoretically add more tile managers if desired,
|
||||
but you probably shouldn't
|
||||
if you don't know what you are doing.
|
||||
*/
|
24
json/TestMod.json
Normal file
24
json/TestMod.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
[
|
||||
{
|
||||
"namespace": "TestMod/CoolStuff",
|
||||
"content": [
|
||||
{
|
||||
"color": "rgba(69,69,69)",
|
||||
"name": "Cool Test Item",
|
||||
"params": [
|
||||
{
|
||||
"func": "cohesion",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"func": "gravity",
|
||||
"options": [
|
||||
50000,
|
||||
7
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue