Compare commits
16 Commits
15d4abaad1
...
3d4d29625d
Author | SHA1 | Date | |
---|---|---|---|
|
3d4d29625d | ||
|
41baef2571 | ||
|
532792595a | ||
|
9f33e4df8a | ||
|
4397011b25 | ||
|
f9bf9096ee | ||
|
466e80aa23 | ||
|
10d6638452 | ||
|
c848b07668 | ||
|
ed5d12b4a8 | ||
|
e0a30ddb0b | ||
|
348a24d8a2 | ||
|
c344b65534 | ||
|
ccb0be9dd9 | ||
|
8e4fd46959 | ||
|
8907658710 |
|
@ -25,8 +25,21 @@ export default class ClientEcs extends Ecs {
|
||||||
return cache.get(uri);
|
return cache.get(uri);
|
||||||
}
|
}
|
||||||
async readJson(uri) {
|
async readJson(uri) {
|
||||||
const chars = await this.readAsset(uri);
|
const key = ['$$json', uri].join(':');
|
||||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
if (!cache.has(key)) {
|
||||||
|
let promise, resolve, reject;
|
||||||
|
promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
cache.set(key, promise);
|
||||||
|
this.readAsset(uri)
|
||||||
|
.then((chars) => {
|
||||||
|
resolve(chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {});
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
return cache.get(key);
|
||||||
}
|
}
|
||||||
async readScript(uri, context = {}) {
|
async readScript(uri, context = {}) {
|
||||||
const code = await this.readAsset(uri);
|
const code = await this.readAsset(uri);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
export const CHUNK_SIZE = 32;
|
||||||
|
|
||||||
export const CLIENT_LATENCY = 0;
|
export const CLIENT_LATENCY = 0;
|
||||||
|
|
||||||
export const CLIENT_PREDICTION = true;
|
export const CLIENT_PREDICTION = true;
|
||||||
|
|
147
app/create-forest.js
Normal file
147
app/create-forest.js
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
import alea from 'alea';
|
||||||
|
import {createNoise2D} from 'simplex-noise';
|
||||||
|
|
||||||
|
import {createRandom, Generator} from '@/util/math.js';
|
||||||
|
|
||||||
|
import createEcs from './create-ecs.js';
|
||||||
|
|
||||||
|
const seed = 42069;
|
||||||
|
const prng = alea(seed);
|
||||||
|
const rawNoise = createNoise2D(prng);
|
||||||
|
const noise = (x, y) => (1 + rawNoise(x, y)) / 2;
|
||||||
|
const random = createRandom(seed);
|
||||||
|
|
||||||
|
const indexComputer = (indices, randomizer) => (
|
||||||
|
(position) => indices[Math.floor(indices.length * randomizer(position))]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [w, h] = [256, 256];
|
||||||
|
|
||||||
|
const Forest = new Generator({
|
||||||
|
calculate: indexComputer(
|
||||||
|
[1, 3, 4, 10, 11, 12], // grass
|
||||||
|
({x, y}) => noise(x * 30, y * 30),
|
||||||
|
),
|
||||||
|
covers: () => true,
|
||||||
|
size: {w, h},
|
||||||
|
children: [
|
||||||
|
new Generator({
|
||||||
|
calculate: indexComputer(
|
||||||
|
[342, 456], // dirt
|
||||||
|
({x, y}) => noise(x * 30, y * 30),
|
||||||
|
),
|
||||||
|
covers: ({x, y}) => noise(x * (1 / 16), y * (1 / 16)) < 0.5,
|
||||||
|
size: {w, h},
|
||||||
|
children: [
|
||||||
|
new Generator({
|
||||||
|
calculate: indexComputer(
|
||||||
|
[407, 408, 423, 424], // stone path
|
||||||
|
({x, y}) => noise(x * 30, y * 30),
|
||||||
|
),
|
||||||
|
covers: ({x, y}) => noise(x * (1 / 16), y * (1 / 16)) < 0.1625,
|
||||||
|
size: {w, h},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new Generator({
|
||||||
|
calculate: indexComputer(
|
||||||
|
[103, 104], // water
|
||||||
|
({x, y}) => noise(x * 30, y * 30),
|
||||||
|
),
|
||||||
|
covers: ({x, y}) => noise(x * (1 / 32), y * (1 / 32)) < 0.3,
|
||||||
|
size: {w, h},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default async function createForest(Ecs) {
|
||||||
|
const ecs = createEcs(Ecs);
|
||||||
|
const area = {x: w, y: h};
|
||||||
|
const master = ecs.get(await ecs.create({
|
||||||
|
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||||
|
Ticking: {},
|
||||||
|
TileLayers: {
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
area,
|
||||||
|
data: Array(w * h).fill(0),
|
||||||
|
source: '/assets/tileset.json',
|
||||||
|
tileSize: {x: 16, y: 16},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area,
|
||||||
|
data: Array(w * h).fill(0),
|
||||||
|
source: '/assets/tileset.json',
|
||||||
|
tileSize: {x: 16, y: 16},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Time: {},
|
||||||
|
}));
|
||||||
|
Forest.generate();
|
||||||
|
const layer0 = master.TileLayers.layers[0];
|
||||||
|
layer0.data = Forest.matrix;
|
||||||
|
for (let y = 0; y < h; ++y) {
|
||||||
|
for (let x = 0; x < w; ++x) {
|
||||||
|
const dirt = Forest.children[0].matrix[y * w + x];
|
||||||
|
if (dirt) {
|
||||||
|
layer0.data[y * w + x] = dirt;
|
||||||
|
}
|
||||||
|
const stone = Forest.children[0].children[0].matrix[y * w + x];
|
||||||
|
if (stone) {
|
||||||
|
layer0.data[y * w + x] = stone;
|
||||||
|
}
|
||||||
|
const water = Forest.children[1].matrix[y * w + x];
|
||||||
|
if (water) {
|
||||||
|
layer0.data[y * w + x] = water;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityPosition = (x, y) => ({
|
||||||
|
x: (8 + (x * 16) + random() * 8 - 4),
|
||||||
|
y: (8 + (y * 16) + random() * 8 - 4),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let y = 0; y < h; ++y) {
|
||||||
|
for (let x = 0; x < w; ++x) {
|
||||||
|
let v = noise(x * (1 / 24), y * (1 / 24));
|
||||||
|
if (v > 0.2) {
|
||||||
|
v = noise(x * (1 / 7), y * (1 / 7));
|
||||||
|
if (v < 0.15) {
|
||||||
|
await ecs.create({
|
||||||
|
Position: entityPosition(x, y),
|
||||||
|
Sprite: {
|
||||||
|
anchorY: 0.7,
|
||||||
|
source: '/assets/ambient/shrub.json',
|
||||||
|
},
|
||||||
|
VisibleAabb: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (v < 0.17) {
|
||||||
|
await ecs.create({
|
||||||
|
Position: entityPosition(x, y),
|
||||||
|
Sprite: {
|
||||||
|
anchorY: 0.875,
|
||||||
|
source: '/assets/ambient/tree.json',
|
||||||
|
},
|
||||||
|
VisibleAabb: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
v = noise(x * (1 / 12), y * (1 / 12));
|
||||||
|
if (v < 0.08) {
|
||||||
|
await ecs.create({
|
||||||
|
Position: entityPosition(x, y),
|
||||||
|
Sprite: {
|
||||||
|
anchorY: 0.7,
|
||||||
|
source: '/assets/ambient/flower.json',
|
||||||
|
},
|
||||||
|
VisibleAabb: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ecs;
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default async function createPlayer(id) {
|
||||||
},
|
},
|
||||||
Controlled: {},
|
Controlled: {},
|
||||||
Direction: {direction: 2},
|
Direction: {direction: 2},
|
||||||
Ecs: {path: ['homesteads', `${id}`].join('/')},
|
Ecs: {path: ['forests', `${id}`].join('/')},
|
||||||
Emitter: {},
|
Emitter: {},
|
||||||
Forces: {},
|
Forces: {},
|
||||||
Interacts: {},
|
Interacts: {},
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
export default class AreaSize extends Component {
|
export default class AreaSize extends Component {
|
||||||
static properties = {
|
static properties = {
|
||||||
x: {type: 'uint16'},
|
x: {type: 'uint32'},
|
||||||
y: {type: 'uint16'},
|
y: {type: 'uint32'},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,36 @@
|
||||||
|
import {CHUNK_SIZE} from '@/constants.js';
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
||||||
|
|
||||||
import vector2d from './helpers/vector-2d';
|
import vector2d from './helpers/vector-2d';
|
||||||
|
|
||||||
class LayerProxy {
|
class LayerProxy {
|
||||||
$$sourceJson;
|
$$chunks = [];
|
||||||
|
$$sourceJson = {};
|
||||||
constructor(instance, Component, index) {
|
constructor(instance, Component, index) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.Component = Component;
|
this.Component = Component;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
this.$$chunks = Array(
|
||||||
|
Math.ceil(this.area.x / CHUNK_SIZE) * Math.ceil(this.area.y / CHUNK_SIZE)
|
||||||
|
).fill(0).map(() => ({}));
|
||||||
}
|
}
|
||||||
get area() {
|
get area() {
|
||||||
return this.layer.area;
|
return this.layer.area;
|
||||||
}
|
}
|
||||||
|
clone() {
|
||||||
|
const {$$chunks, $$sourceJson} = this.instance.$$layersProxies[this.index];
|
||||||
|
const proxy = new LayerProxy(this.instance, this.Component, this.index);
|
||||||
|
proxy.$$chunks = [...$$chunks];
|
||||||
|
proxy.$$sourceJson = $$sourceJson;
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
compute(index) {
|
||||||
|
const cx = Math.floor((index % this.area.x) / CHUNK_SIZE);
|
||||||
|
const cy = Math.floor(Math.floor(index / this.area.x) / CHUNK_SIZE);
|
||||||
|
const ax = Math.ceil(this.area.x / CHUNK_SIZE);
|
||||||
|
return cy * ax + cx;
|
||||||
|
}
|
||||||
get data() {
|
get data() {
|
||||||
return this.layer.data;
|
return this.layer.data;
|
||||||
}
|
}
|
||||||
|
@ -119,23 +137,66 @@ class LayerProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TileLayers extends Component {
|
export default class TileLayers extends Component {
|
||||||
async insertMany(entities) {
|
async createMany(entities) {
|
||||||
for (const [id, {layerChange}] of entities) {
|
for (const [, {layerChange, layers}] of entities) {
|
||||||
|
if (layers) {
|
||||||
|
for (const layer of layers) {
|
||||||
|
const area = layer.area.x * layer.area.y;
|
||||||
|
if (layer.data.length < area) {
|
||||||
|
for (let i = 0; i < area; ++i) {
|
||||||
|
if (!layer.data[i]) {
|
||||||
|
layer.data[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (layerChange) {
|
if (layerChange) {
|
||||||
const component = this.get(id);
|
|
||||||
const {layers} = component;
|
|
||||||
for (const layerIndex in layerChange) {
|
for (const layerIndex in layerChange) {
|
||||||
for (const calculated in layerChange[layerIndex]) {
|
for (const calculated in layerChange[layerIndex]) {
|
||||||
const tile = layerChange[layerIndex][calculated];
|
const tile = layerChange[layerIndex][calculated];
|
||||||
layers[layerIndex].data[calculated] = tile;
|
layers[layerIndex].data[calculated] = tile;
|
||||||
}
|
}
|
||||||
layers[layerIndex] = {...layers[layerIndex]};
|
|
||||||
component.$$layersProxies[layerIndex] = new LayerProxy(component, this, layerIndex);
|
|
||||||
await component.$$layersProxies[layerIndex].load();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.insertMany(entities);
|
return super.createMany(entities);
|
||||||
|
}
|
||||||
|
async insertMany(entities) {
|
||||||
|
for (const [, {layers}] of entities) {
|
||||||
|
if (layers) {
|
||||||
|
for (const layer of layers) {
|
||||||
|
const area = layer.area.x * layer.area.y;
|
||||||
|
if (layer.data.length < area) {
|
||||||
|
for (let i = 0; i < area; ++i) {
|
||||||
|
if (!layer.data[i]) {
|
||||||
|
layer.data[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await super.insertMany(entities);
|
||||||
|
for (const [id, {layerChange}] of entities) {
|
||||||
|
if (layerChange) {
|
||||||
|
const component = this.get(id);
|
||||||
|
const {layers} = component;
|
||||||
|
for (const layerIndex in layerChange) {
|
||||||
|
const proxy = component.$$layersProxies[layerIndex].clone();
|
||||||
|
const chunksChanged = new Set();
|
||||||
|
for (const tileIndex in layerChange[layerIndex]) {
|
||||||
|
chunksChanged.add(proxy.compute(tileIndex));
|
||||||
|
const tile = layerChange[layerIndex][tileIndex];
|
||||||
|
layers[layerIndex].data[tileIndex] = tile;
|
||||||
|
}
|
||||||
|
for (const chunkChanged of chunksChanged) {
|
||||||
|
proxy.$$chunks[chunkChanged] = {};
|
||||||
|
}
|
||||||
|
component.$$layersProxies[layerIndex] = proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
return class TileLayersInstance extends super.instanceFromSchema() {
|
return class TileLayersInstance extends super.instanceFromSchema() {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
// import {RESOLUTION} from '@/constants.js'
|
||||||
import {System} from '@/ecs/index.js';
|
import {System} from '@/ecs/index.js';
|
||||||
|
|
||||||
|
// const [hx, hy] = [RESOLUTION.x / 2, RESOLUTION.y / 2];
|
||||||
|
|
||||||
export default class FollowCamera extends System {
|
export default class FollowCamera extends System {
|
||||||
|
|
||||||
static queries() {
|
static queries() {
|
||||||
|
@ -24,6 +27,11 @@ export default class FollowCamera extends System {
|
||||||
updateCamera(portion, entity) {
|
updateCamera(portion, entity) {
|
||||||
const {Camera, Position} = entity;
|
const {Camera, Position} = entity;
|
||||||
if (Camera && Position) {
|
if (Camera && Position) {
|
||||||
|
// const {AreaSize: {x, y}} = this.ecs.get(1);
|
||||||
|
// const [px, py] = [
|
||||||
|
// Math.max(hx, Math.min(Math.round(Position.x), x - hx)),
|
||||||
|
// Math.max(hy, Math.min(Math.round(Position.y), y - hy)),
|
||||||
|
// ];
|
||||||
const [px, py] = [
|
const [px, py] = [
|
||||||
Math.round(Position.x),
|
Math.round(Position.x),
|
||||||
Math.round(Position.y),
|
Math.round(Position.y),
|
||||||
|
|
148
app/engine.js
148
app/engine.js
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
CHUNK_SIZE,
|
||||||
RESOLUTION,
|
RESOLUTION,
|
||||||
TPS,
|
TPS,
|
||||||
} from '@/constants.js';
|
} from '@/constants.js';
|
||||||
|
@ -7,10 +8,17 @@ import {decode, encode} from '@/packets/index.js';
|
||||||
import Script from '@/util/script.js';
|
import Script from '@/util/script.js';
|
||||||
|
|
||||||
import createEcs from './create-ecs.js';
|
import createEcs from './create-ecs.js';
|
||||||
|
import createForest from './create-forest.js';
|
||||||
import createHomestead from './create-homestead.js';
|
import createHomestead from './create-homestead.js';
|
||||||
import createHouse from './create-house.js';
|
import createHouse from './create-house.js';
|
||||||
import createPlayer from './create-player.js';
|
import createPlayer from './create-player.js';
|
||||||
|
|
||||||
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
|
const cache = new LRUCache({
|
||||||
|
max: 128,
|
||||||
|
});
|
||||||
|
|
||||||
export default class Engine {
|
export default class Engine {
|
||||||
|
|
||||||
connectedPlayers = new Map();
|
connectedPlayers = new Map();
|
||||||
|
@ -38,11 +46,35 @@ export default class Engine {
|
||||||
return engine.frame;
|
return engine.frame;
|
||||||
}
|
}
|
||||||
async readAsset(uri) {
|
async readAsset(uri) {
|
||||||
return server.readAsset(uri);
|
if (!cache.has(uri)) {
|
||||||
|
let promise, resolve, reject;
|
||||||
|
promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
cache.set(uri, promise);
|
||||||
|
server.readAsset(uri)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
return cache.get(uri);
|
||||||
}
|
}
|
||||||
async readJson(uri) {
|
async readJson(uri) {
|
||||||
const chars = await this.readAsset(uri);
|
const key = ['$$json', uri].join(':');
|
||||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
if (!cache.has(key)) {
|
||||||
|
let promise, resolve, reject;
|
||||||
|
promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
cache.set(key, promise);
|
||||||
|
this.readAsset(uri)
|
||||||
|
.then((chars) => {
|
||||||
|
resolve(chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {});
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
return cache.get(key);
|
||||||
}
|
}
|
||||||
async readScript(uri, context) {
|
async readScript(uri, context) {
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
|
@ -61,7 +93,10 @@ export default class Engine {
|
||||||
// remove entity link to connection to start queueing actions and pause updates
|
// remove entity link to connection to start queueing actions and pause updates
|
||||||
delete connectedPlayer.entity;
|
delete connectedPlayer.entity;
|
||||||
// forget previous state
|
// forget previous state
|
||||||
connectedPlayer.memory.clear();
|
connectedPlayer.memory = {
|
||||||
|
chunks: new Map(),
|
||||||
|
nearby: new Set(),
|
||||||
|
};
|
||||||
// inform client of the upcoming change
|
// inform client of the upcoming change
|
||||||
server.send(
|
server.send(
|
||||||
connection,
|
connection,
|
||||||
|
@ -183,7 +218,10 @@ export default class Engine {
|
||||||
{
|
{
|
||||||
entity: ecs.get(entity),
|
entity: ecs.get(entity),
|
||||||
id,
|
id,
|
||||||
memory: new Set(),
|
memory: {
|
||||||
|
chunks: new Map(),
|
||||||
|
nearby: new Set(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -230,6 +268,11 @@ export default class Engine {
|
||||||
['houses', `${id}`].join('/'),
|
['houses', `${id}`].join('/'),
|
||||||
house,
|
house,
|
||||||
);
|
);
|
||||||
|
const forest = await createForest(this.Ecs, id);
|
||||||
|
await this.saveEcs(
|
||||||
|
['forests', `${id}`].join('/'),
|
||||||
|
forest,
|
||||||
|
);
|
||||||
buffer = await createPlayer(id);
|
buffer = await createPlayer(id);
|
||||||
await this.server.writeData(
|
await this.server.writeData(
|
||||||
['players', `${id}`].join('/'),
|
['players', `${id}`].join('/'),
|
||||||
|
@ -323,12 +366,14 @@ export default class Engine {
|
||||||
y1: y0 + (RESOLUTION.y * 2),
|
y1: y0 + (RESOLUTION.y * 2),
|
||||||
});
|
});
|
||||||
// Master entity.
|
// Master entity.
|
||||||
nearby.add(ecs.get(1));
|
const master = ecs.get(1);
|
||||||
const lastMemory = new Set(memory.values());
|
nearby.add(master);
|
||||||
|
const lastNearby = new Set(memory.nearby.values());
|
||||||
|
const firstUpdate = 0 === lastNearby.size;
|
||||||
for (const entity of nearby) {
|
for (const entity of nearby) {
|
||||||
const {id} = entity;
|
const {id} = entity;
|
||||||
lastMemory.delete(id);
|
lastNearby.delete(id);
|
||||||
if (!memory.has(id)) {
|
if (!memory.nearby.has(id)) {
|
||||||
update[id] = entity.toJSON();
|
update[id] = entity.toJSON();
|
||||||
if (mainEntityId === id) {
|
if (mainEntityId === id) {
|
||||||
update[id].MainEntity = {};
|
update[id].MainEntity = {};
|
||||||
|
@ -337,12 +382,91 @@ export default class Engine {
|
||||||
else if (ecs.diff[id]) {
|
else if (ecs.diff[id]) {
|
||||||
update[id] = ecs.diff[id];
|
update[id] = ecs.diff[id];
|
||||||
}
|
}
|
||||||
memory.add(id);
|
memory.nearby.add(id);
|
||||||
}
|
}
|
||||||
for (const id of lastMemory) {
|
for (const id of lastNearby) {
|
||||||
memory.delete(id);
|
memory.nearby.delete(id);
|
||||||
update[id] = false;
|
update[id] = false;
|
||||||
}
|
}
|
||||||
|
// Tile layer chunking
|
||||||
|
const {TileLayers} = master;
|
||||||
|
const {layers} = TileLayers;
|
||||||
|
let layerChange;
|
||||||
|
for (const i in layers) {
|
||||||
|
const layer = TileLayers.layer(i);
|
||||||
|
const cx = CHUNK_SIZE * layer.tileSize.x;
|
||||||
|
const cy = CHUNK_SIZE * layer.tileSize.y;
|
||||||
|
const rx = 1 + Math.ceil((RESOLUTION.x * 2) / cx);
|
||||||
|
const ry = 1 + Math.ceil((RESOLUTION.y * 2) / cy);
|
||||||
|
const lx = Math.floor((entity.Position.x - RESOLUTION.x) / cx);
|
||||||
|
const ly = Math.floor((entity.Position.y - RESOLUTION.y) / cy);
|
||||||
|
const ax = Math.ceil(layer.area.x / CHUNK_SIZE);
|
||||||
|
for (let wy = 0; wy < ry; ++wy) {
|
||||||
|
for (let wx = 0; wx < rx; ++wx) {
|
||||||
|
const iy = wy + ly;
|
||||||
|
const ix = wx + lx;
|
||||||
|
if (
|
||||||
|
ix >= 0
|
||||||
|
&& iy >= 0
|
||||||
|
&& ix < Math.ceil(layer.area.x / CHUNK_SIZE)
|
||||||
|
&& iy < Math.ceil(layer.area.y / CHUNK_SIZE)
|
||||||
|
) {
|
||||||
|
const chunk = iy * ax + ix;
|
||||||
|
if (!memory.chunks.has(i)) {
|
||||||
|
memory.chunks.set(i, new Set());
|
||||||
|
}
|
||||||
|
if (!memory.chunks.get(i).has(chunk)) {
|
||||||
|
memory.chunks.get(i).add(chunk);
|
||||||
|
if (!layerChange) {
|
||||||
|
layerChange = {};
|
||||||
|
}
|
||||||
|
if (!layerChange[i]) {
|
||||||
|
layerChange[i] = {};
|
||||||
|
}
|
||||||
|
for (let y = 0; y < CHUNK_SIZE; ++y) {
|
||||||
|
for (let x = 0; x < CHUNK_SIZE; ++x) {
|
||||||
|
const ty = (iy * CHUNK_SIZE) + y;
|
||||||
|
const tx = (ix * CHUNK_SIZE) + x;
|
||||||
|
if (
|
||||||
|
tx < 0
|
||||||
|
|| ty < 0
|
||||||
|
|| tx >= layers[i].area.x
|
||||||
|
|| ty >= layers[i].area.y
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const computed = ((iy * CHUNK_SIZE) + y) * layers[i].area.x + ((ix * CHUNK_SIZE) + x);
|
||||||
|
layerChange[i][computed] = layers[i].data[computed];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstUpdate && update['1']) {
|
||||||
|
const {TileLayers} = update['1'];
|
||||||
|
if (TileLayers) {
|
||||||
|
const layersUpdate = [];
|
||||||
|
const {layers} = TileLayers;
|
||||||
|
for (const l in layers) {
|
||||||
|
layersUpdate[l] = {
|
||||||
|
...layers[l],
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
update['1'].TileLayers = {layers: layersUpdate};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (layerChange) {
|
||||||
|
if (!update['1']) {
|
||||||
|
update['1'] = {};
|
||||||
|
}
|
||||||
|
if (!update['1'].TileLayers) {
|
||||||
|
update['1'].TileLayers = {};
|
||||||
|
}
|
||||||
|
update['1'].TileLayers.layerChange = layerChange;
|
||||||
|
}
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {del, get, set} from 'idb-keyval';
|
||||||
|
|
||||||
import {encode} from '@/packets/index.js';
|
import {encode} from '@/packets/index.js';
|
||||||
|
|
||||||
|
import '../../create-forest.js';
|
||||||
import '../../create-homestead.js';
|
import '../../create-homestead.js';
|
||||||
import '../../create-player.js';
|
import '../../create-player.js';
|
||||||
|
|
||||||
|
@ -92,6 +93,18 @@ if (import.meta.hot) {
|
||||||
// await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
// await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
||||||
resolver.resolve();
|
resolver.resolve();
|
||||||
});
|
});
|
||||||
|
import.meta.hot.accept('../../create-forest.js', async ({default: createForest}) => {
|
||||||
|
const resolver = createResolver();
|
||||||
|
resolvers.push(resolver);
|
||||||
|
await beforeResolver;
|
||||||
|
delete engine.ecses['forests/0'];
|
||||||
|
await engine.server.removeData('forests/0');
|
||||||
|
const last = performance.now();
|
||||||
|
const forest = await createForest(engine.Ecs, '0');
|
||||||
|
console.log((performance.now() - last) / 1000);
|
||||||
|
await engine.saveEcs('forests/0', forest);
|
||||||
|
resolver.resolve();
|
||||||
|
});
|
||||||
import.meta.hot.accept('../../create-homestead.js', async ({default: createHomestead}) => {
|
import.meta.hot.accept('../../create-homestead.js', async ({default: createHomestead}) => {
|
||||||
const resolver = createResolver();
|
const resolver = createResolver();
|
||||||
resolvers.push(resolver);
|
resolvers.push(resolver);
|
||||||
|
@ -99,7 +112,6 @@ if (import.meta.hot) {
|
||||||
// delete engine.ecses['homesteads/0'];
|
// delete engine.ecses['homesteads/0'];
|
||||||
// await engine.server.removeData('homesteads/0');
|
// await engine.server.removeData('homesteads/0');
|
||||||
// const homestead = await createHomestead(engine.Ecs, '0');
|
// const homestead = await createHomestead(engine.Ecs, '0');
|
||||||
// homestead.get(2).Ecs.path = 'houses/0';
|
|
||||||
// await engine.saveEcs('homesteads/0', homestead);
|
// await engine.saveEcs('homesteads/0', homestead);
|
||||||
resolver.resolve();
|
resolver.resolve();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import {useState} from 'react';
|
||||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||||
import 'react-tabs/style/react-tabs.css';
|
import 'react-tabs/style/react-tabs.css';
|
||||||
|
|
||||||
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
|
import {useMainEntity} from '@/context/main-entity.js';
|
||||||
|
|
||||||
import styles from './devtools.module.css';
|
import styles from './devtools.module.css';
|
||||||
|
|
||||||
import Tiles from './devtools/tiles.jsx';
|
import Tiles from './devtools/tiles.jsx';
|
||||||
|
@ -10,15 +14,22 @@ export default function Devtools({
|
||||||
eventsChannel,
|
eventsChannel,
|
||||||
setApplyFilters,
|
setApplyFilters,
|
||||||
}) {
|
}) {
|
||||||
|
const [ecs] = useEcs();
|
||||||
|
const [mainEntity] = useMainEntity();
|
||||||
|
const [mainEntityJson, setMainEntityJson] = useState({});
|
||||||
|
useEcsTick(() => {
|
||||||
|
if (!ecs || !mainEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMainEntityJson(ecs.get(mainEntity).toJSON());
|
||||||
|
}, [ecs, mainEntity]);
|
||||||
return (
|
return (
|
||||||
<div className={styles.devtools}>
|
<div className={styles.devtools}>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Dashboard</Tab>
|
<Tab>Dashboard</Tab>
|
||||||
<Tab>Tiles</Tab>
|
<Tab>Tiles</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<div className={styles.dashboard}>
|
<div className={styles.dashboard}>
|
||||||
<form>
|
<form>
|
||||||
|
@ -35,9 +46,9 @@ export default function Devtools({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<pre><code><small>{JSON.stringify(mainEntityJson, null, 2)}</small></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<Tiles
|
<Tiles
|
||||||
eventsChannel={eventsChannel}
|
eventsChannel={eventsChannel}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default function Tiles({eventsChannel}) {
|
||||||
const wrapperRef = useRef();
|
const wrapperRef = useRef();
|
||||||
const imageRef = useRef();
|
const imageRef = useRef();
|
||||||
const imageRect = useRect(imageRef);
|
const imageRect = useRect(imageRef);
|
||||||
|
const [indices, setIndices] = useState([]);
|
||||||
const [selection, setSelection] = useState({x: 0, y: 0, w: 1, h: 1});
|
const [selection, setSelection] = useState({x: 0, y: 0, w: 1, h: 1});
|
||||||
const [moveStart, setMoveStart] = useState();
|
const [moveStart, setMoveStart] = useState();
|
||||||
const [layer, setLayer] = useState(0);
|
const [layer, setLayer] = useState(0);
|
||||||
|
@ -35,17 +36,19 @@ export default function Tiles({eventsChannel}) {
|
||||||
if (at.x < 0 || at.y < 0 || at.x >= area.x || at.y >= area.y) {
|
if (at.x < 0 || at.y < 0 || at.x >= area.x || at.y >= area.y) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = {
|
|
||||||
brush,
|
|
||||||
layer,
|
|
||||||
stamp: {
|
|
||||||
at,
|
|
||||||
data: stamp,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client.send({
|
client.send({
|
||||||
type: 'AdminAction',
|
type: 'AdminAction',
|
||||||
payload: {type: 'paint', value: payload},
|
payload: {
|
||||||
|
type: 'paint',
|
||||||
|
value: {
|
||||||
|
brush,
|
||||||
|
layer,
|
||||||
|
stamp: {
|
||||||
|
at,
|
||||||
|
data: stamp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
eventsChannel.addListener('click', onClick);
|
eventsChannel.addListener('click', onClick);
|
||||||
|
@ -64,6 +67,7 @@ export default function Tiles({eventsChannel}) {
|
||||||
const {sourceJson, tileSize} = TileLayers.layer(0);
|
const {sourceJson, tileSize} = TileLayers.layer(0);
|
||||||
const {w, h} = sourceJson.meta.size;
|
const {w, h} = sourceJson.meta.size;
|
||||||
const factor = (imageRect?.width ?? 1) / w;
|
const factor = (imageRect?.width ?? 1) / w;
|
||||||
|
const wt = w / tileSize.x;
|
||||||
return (
|
return (
|
||||||
<div className={styles.tiles}>
|
<div className={styles.tiles}>
|
||||||
<form>
|
<form>
|
||||||
|
@ -125,8 +129,14 @@ export default function Tiles({eventsChannel}) {
|
||||||
const y = Math.floor((c.y - top) / (tileSize.y * factor));
|
const y = Math.floor((c.y - top) / (tileSize.y * factor));
|
||||||
setMoveStart({x, y});
|
setMoveStart({x, y});
|
||||||
setSelection({x, y, w: 1, h: 1});
|
setSelection({x, y, w: 1, h: 1});
|
||||||
|
setStamp([[y * wt + x]]);
|
||||||
|
setIndices([y * wt + x]);
|
||||||
}}
|
}}
|
||||||
onMouseMove={(event) => {
|
onMouseMove={(event) => {
|
||||||
|
if (0 === event.buttons) {
|
||||||
|
setMoveStart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!moveStart) {
|
if (!moveStart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -145,21 +155,39 @@ export default function Tiles({eventsChannel}) {
|
||||||
);
|
);
|
||||||
const mx = Math.min(sx, x);
|
const mx = Math.min(sx, x);
|
||||||
const my = Math.min(sy, y);
|
const my = Math.min(sy, y);
|
||||||
setSelection({x: mx, y: my, w: Math.abs(sx - x) + 1, h: Math.abs(sy - y) + 1});
|
const sw = Math.abs(sx - x) + 1;
|
||||||
}}
|
const sh = Math.abs(sy - y) + 1;
|
||||||
onMouseUp={() => {
|
const newSelection = {
|
||||||
setMoveStart();
|
x: mx,
|
||||||
|
y: my,
|
||||||
|
w: sw,
|
||||||
|
h: sh,
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
selection.x === newSelection.x
|
||||||
|
&& selection.y === newSelection.y
|
||||||
|
&& selection.w === newSelection.w
|
||||||
|
&& selection.h === newSelection.h
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelection(newSelection);
|
||||||
const stamp = [];
|
const stamp = [];
|
||||||
const {x, y, w: sw, h: sh} = selection;
|
|
||||||
const tw = w / tileSize.x;
|
|
||||||
for (let iy = 0; iy < sh; ++iy) {
|
for (let iy = 0; iy < sh; ++iy) {
|
||||||
const row = [];
|
const row = [];
|
||||||
for (let ix = 0; ix < sw; ++ix) {
|
for (let ix = 0; ix < sw; ++ix) {
|
||||||
row.push((y + iy) * tw + x + ix);
|
row.push((my + iy) * wt + mx + ix);
|
||||||
}
|
}
|
||||||
stamp.push(row);
|
stamp.push(row);
|
||||||
}
|
}
|
||||||
setStamp(stamp);
|
setStamp(stamp);
|
||||||
|
const indices = [];
|
||||||
|
for (let sy = 0; sy < newSelection.h; ++sy) {
|
||||||
|
for (let sx = 0; sx < newSelection.w; ++sx) {
|
||||||
|
indices.push(((newSelection.y + sy) * wt) + (newSelection.x + sx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIndices(indices);
|
||||||
}}
|
}}
|
||||||
className={styles.selectionWrapper}
|
className={styles.selectionWrapper}
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
|
@ -179,6 +207,25 @@ export default function Tiles({eventsChannel}) {
|
||||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.status}
|
||||||
|
>
|
||||||
|
<code className={styles.selectionStatus}>
|
||||||
|
Sel:
|
||||||
|
{' {'}
|
||||||
|
{selection.x}{', '}
|
||||||
|
{selection.y}{', '}
|
||||||
|
{selection.w}{', '}
|
||||||
|
{selection.h}
|
||||||
|
{'}'}
|
||||||
|
</code>
|
||||||
|
<code>
|
||||||
|
Idx:
|
||||||
|
{' ['}
|
||||||
|
{indices.join(', ')}
|
||||||
|
{']'}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -28,3 +28,16 @@
|
||||||
background-color: #ffffff44;
|
background-color: #ffffff44;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
align-items: end;
|
||||||
|
display: flex;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
gap: 32px;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectionStatus {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import {simplex2D} from '@leodeslf/simplex-noise';
|
|
||||||
import {Texture} from '@pixi/core';
|
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {Sprite} from '@pixi/sprite';
|
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import {RESOLUTION} from '@/constants.js';
|
||||||
import {usePacket} from '@/context/client.js';
|
import {usePacket} from '@/context/client.js';
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/context/main-entity.js';
|
||||||
import {bresenham, TAU} from '@/util/math.js';
|
|
||||||
|
|
||||||
import Entities from './entities.jsx';
|
import Entities from './entities.jsx';
|
||||||
import TargetingGhost from './targeting-ghost.jsx';
|
import TargetingGhost from './targeting-ghost.jsx';
|
||||||
|
@ -32,60 +28,13 @@ function calculateDarkness(hour) {
|
||||||
return Math.floor(darkness * 1000) / 1000;
|
return Math.floor(darkness * 1000) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLayerMask(layer) {
|
|
||||||
const {area, hulls, tileSize} = layer;
|
|
||||||
const canvas = window.document.createElement('canvas');
|
|
||||||
if (0 === hulls.length) {
|
|
||||||
return Texture.from(canvas);
|
|
||||||
}
|
|
||||||
[canvas.width, canvas.height] = [area.x * tileSize.x, area.y * tileSize.y];
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
|
||||||
for (let i = 0; i < hulls.length; ++i) {
|
|
||||||
const hull = [...hulls[i]];
|
|
||||||
hull.push(hull[0]);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(hull[0].x, hull[0].y);
|
|
||||||
for (let j = 0; j < hull.length - 1; j++) {
|
|
||||||
const p0 = hull[j + 0];
|
|
||||||
const p1 = hull[j + 1];
|
|
||||||
const points = bresenham(p0, p1);
|
|
||||||
const isReversed = (
|
|
||||||
(p0.x > p1.x && points[0].x < points[points.length - 1].x)
|
|
||||||
|| (p0.y > p1.y && points[0].y < points[points.length - 1].y)
|
|
||||||
);
|
|
||||||
for (
|
|
||||||
let k = (isReversed ? points.length - 1 : 0);
|
|
||||||
(isReversed ? k >= 0 : k < points.length);
|
|
||||||
k += (isReversed ? -1 : 1)
|
|
||||||
) {
|
|
||||||
const {x, y} = points[k];
|
|
||||||
const ANGLE_SCALE = 1000;
|
|
||||||
const WALK_SCALE = 10;
|
|
||||||
const MAGNITUDE = ((tileSize.x + tileSize.y) / 2) * simplex2D(x * 100, y * 100);
|
|
||||||
const w = simplex2D(x * WALK_SCALE, y * WALK_SCALE);
|
|
||||||
const r = TAU * simplex2D(x * ANGLE_SCALE, y * ANGLE_SCALE)
|
|
||||||
ctx.lineTo(
|
|
||||||
x + Math.cos(r) * w * MAGNITUDE,
|
|
||||||
y + -Math.sin(r) * w * MAGNITUDE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
return Texture.from(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Ecs({applyFilters, scale}) {
|
export default function Ecs({applyFilters, scale}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
|
const [filters, setFilters] = useState([]);
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
const [hour, setHour] = useState(10);
|
const [hour, setHour] = useState(10);
|
||||||
const [night, setNight] = useState();
|
const [night, setNight] = useState();
|
||||||
const [mask, setMask] = useState();
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function buildNightFilter() {
|
async function buildNightFilter() {
|
||||||
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
||||||
|
@ -131,12 +80,6 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
if (update.Time) {
|
if (update.Time) {
|
||||||
setHour(Math.round(ecs.get(1).Time.hour * 60) / 60);
|
setHour(Math.round(ecs.get(1).Time.hour * 60) / 60);
|
||||||
}
|
}
|
||||||
if (update.TileLayers) {
|
|
||||||
const layer1 = ecs.get(1).TileLayers.layer(1);
|
|
||||||
if (layer1) {
|
|
||||||
setMask(new Sprite(createLayerMask(layer1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updatedEntities[id] = ecs.get(id);
|
updatedEntities[id] = ecs.get(id);
|
||||||
if (update.Emitter?.emit) {
|
if (update.Emitter?.emit) {
|
||||||
|
@ -149,6 +92,13 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
}
|
}
|
||||||
setEntities(updatedEntities);
|
setEntities(updatedEntities);
|
||||||
}, [ecs, entities, mainEntity]);
|
}, [ecs, entities, mainEntity]);
|
||||||
|
useEffect(() => {
|
||||||
|
setFilters(
|
||||||
|
applyFilters
|
||||||
|
? [night]
|
||||||
|
: [],
|
||||||
|
);
|
||||||
|
}, [applyFilters, night])
|
||||||
if (!ecs || !mainEntity) {
|
if (!ecs || !mainEntity) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -162,10 +112,6 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
const {TileLayers, Water: WaterEcs} = ecs.get(1);
|
const {TileLayers, Water: WaterEcs} = ecs.get(1);
|
||||||
const layer0 = TileLayers.layer(0);
|
const layer0 = TileLayers.layer(0);
|
||||||
const layer1 = TileLayers.layer(1);
|
const layer1 = TileLayers.layer(1);
|
||||||
const filters = [];
|
|
||||||
if (applyFilters && night) {
|
|
||||||
filters.push(night);
|
|
||||||
}
|
|
||||||
const [cx, cy] = [
|
const [cx, cy] = [
|
||||||
Math.round((Camera.x * scale) - RESOLUTION.x / 2),
|
Math.round((Camera.x * scale) - RESOLUTION.x / 2),
|
||||||
Math.round((Camera.y * scale) - RESOLUTION.y / 2),
|
Math.round((Camera.y * scale) - RESOLUTION.y / 2),
|
||||||
|
@ -185,7 +131,6 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
/>
|
/>
|
||||||
{layer1 && (
|
{layer1 && (
|
||||||
<TileLayer
|
<TileLayer
|
||||||
mask={mask}
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
tileLayer={layer1}
|
tileLayer={layer1}
|
||||||
/>
|
/>
|
||||||
|
@ -193,7 +138,6 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
</Container>
|
</Container>
|
||||||
{WaterEcs && (
|
{WaterEcs && (
|
||||||
<Water
|
<Water
|
||||||
mask={mask}
|
|
||||||
tileLayer={layer0}
|
tileLayer={layer0}
|
||||||
water={WaterEcs.water}
|
water={WaterEcs.water}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default function Entities({entities, filters}) {
|
||||||
const isHighlightedInteraction = id == willInteractWith;
|
const isHighlightedInteraction = id == willInteractWith;
|
||||||
renderables.push(
|
renderables.push(
|
||||||
<Entity
|
<Entity
|
||||||
filters={filters.concat(isHighlightedInteraction ? interactionFilters : [])}
|
filters={isHighlightedInteraction ? interactionFilters : null}
|
||||||
entity={entities[id]}
|
entity={entities[id]}
|
||||||
key={id}
|
key={id}
|
||||||
/>
|
/>
|
||||||
|
@ -48,6 +48,7 @@ export default function Entities({entities, filters}) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
|
filters={filters}
|
||||||
sortableChildren
|
sortableChildren
|
||||||
>
|
>
|
||||||
{renderables}
|
{renderables}
|
||||||
|
|
|
@ -3,42 +3,64 @@ import {PixiComponent} from '@pixi/react';
|
||||||
import '@pixi/spritesheet'; // NECESSARY!
|
import '@pixi/spritesheet'; // NECESSARY!
|
||||||
import {CompositeTilemap} from '@pixi/tilemap';
|
import {CompositeTilemap} from '@pixi/tilemap';
|
||||||
|
|
||||||
|
import {CHUNK_SIZE} from '@/constants.js';
|
||||||
import {useAsset} from '@/context/assets.js';
|
import {useAsset} from '@/context/assets.js';
|
||||||
|
|
||||||
const TileLayerInternal = PixiComponent('TileLayer', {
|
const TileLayerInternal = PixiComponent('TileLayer', {
|
||||||
create: () => {
|
create: ({tileLayer}) => {
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
container.addChild(new CompositeTilemap());
|
const cy = Math.ceil(tileLayer.area.y / CHUNK_SIZE);
|
||||||
|
const cx = Math.ceil(tileLayer.area.x / CHUNK_SIZE);
|
||||||
|
for (let iy = 0; iy < cy; ++iy) {
|
||||||
|
for (let ix = 0; ix < cx; ++ix) {
|
||||||
|
const tilemap = new CompositeTilemap();
|
||||||
|
tilemap.x = tileLayer.tileSize.x * CHUNK_SIZE * ix;
|
||||||
|
tilemap.y = tileLayer.tileSize.y * CHUNK_SIZE * iy;
|
||||||
|
container.addChild(tilemap);
|
||||||
|
}
|
||||||
|
}
|
||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
applyProps: (container, {mask: oldMask, tileLayer: oldTileLayer}, props) => {
|
applyProps: (container, {tileLayer: oldTileLayer}, props) => {
|
||||||
const {asset, mask, tileLayer} = props;
|
const {asset, tileLayer} = props;
|
||||||
const extless = tileLayer.source.slice('/assets/'.length, -'.json'.length);
|
const extless = tileLayer.source.slice('/assets/'.length, -'.json'.length);
|
||||||
const {textures} = asset;
|
const {textures} = asset;
|
||||||
if (tileLayer === oldTileLayer) {
|
if (tileLayer === oldTileLayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (oldMask) {
|
for (const i in tileLayer.$$chunks) {
|
||||||
container.removeChildAt(1);
|
if (!oldTileLayer || oldTileLayer.$$chunks[i] !== tileLayer.$$chunks[i]) {
|
||||||
container.mask = undefined;
|
const tilemap = container.children[i];
|
||||||
}
|
tilemap.clear();
|
||||||
if (mask) {
|
const ax = Math.ceil(tileLayer.area.x / CHUNK_SIZE);
|
||||||
container.addChild(mask);
|
const cy = Math.floor(i / ax);
|
||||||
container.mask = mask;
|
const cx = i % ax;
|
||||||
}
|
for (let y = 0; y < CHUNK_SIZE; ++y) {
|
||||||
const tilemap = container.children[0];
|
for (let x = 0; x < CHUNK_SIZE; ++x) {
|
||||||
tilemap.clear();
|
const ty = (cy * CHUNK_SIZE) + y;
|
||||||
let i = 0;
|
const tx = (cx * CHUNK_SIZE) + x;
|
||||||
for (let y = 0; y < tileLayer.area.y; ++y) {
|
if (
|
||||||
for (let x = 0; x < tileLayer.area.x; ++x) {
|
tx < 0
|
||||||
tilemap.tile(textures[`${extless}/${tileLayer.data[i++]}`], tileLayer.tileSize.x * x, tileLayer.tileSize.y * y);
|
|| ty < 0
|
||||||
|
|| tx >= tileLayer.area.x
|
||||||
|
|| ty >= tileLayer.area.y
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tilemap.tile(
|
||||||
|
textures[`${extless}/${tileLayer.data[ty * tileLayer.area.x + tx]}`],
|
||||||
|
tileLayer.tileSize.x * x,
|
||||||
|
tileLayer.tileSize.y * y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export default function TileLayer(props) {
|
export default function TileLayer(props) {
|
||||||
const {mask, tileLayer} = props;
|
const {tileLayer} = props;
|
||||||
const asset = useAsset(tileLayer.source);
|
const asset = useAsset(tileLayer.source);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -48,7 +70,6 @@ export default function TileLayer(props) {
|
||||||
<TileLayerInternal
|
<TileLayerInternal
|
||||||
{...props}
|
{...props}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
mask={mask}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -98,7 +98,7 @@ export default function Ui({disconnected}) {
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case '-':
|
case '-':
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
setScale((scale) => scale > 1 ? scale - 1 : 0.666);
|
setScale((scale) => scale > 1 ? scale - 1 : 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import alea from 'alea';
|
||||||
|
import {createNoise2D as createSimplexNoise2D} from 'simplex-noise';
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
abs,
|
abs,
|
||||||
acos,
|
acos,
|
||||||
|
@ -110,6 +113,14 @@ export function clamp(n, min, max) {
|
||||||
return Math.max(min, Math.min(max, n));
|
return Math.max(min, Math.min(max, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createNoise2D(seed = 0) {
|
||||||
|
return createSimplexNoise2D(createRandom(seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRandom(seed = 0) {
|
||||||
|
return alea(seed);
|
||||||
|
}
|
||||||
|
|
||||||
export function distance({x: lx, y: ly}, {x: rx, y: ry}) {
|
export function distance({x: lx, y: ly}, {x: rx, y: ry}) {
|
||||||
const xd = lx - rx;
|
const xd = lx - rx;
|
||||||
const yd = ly - ry;
|
const yd = ly - ry;
|
||||||
|
@ -167,6 +178,50 @@ export function floodwalk2D(eligible, data, {x, y, w, h}, {diagonal = false} = {
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Generator {
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
calculate,
|
||||||
|
children = [],
|
||||||
|
covers,
|
||||||
|
size: {w, h},
|
||||||
|
}) {
|
||||||
|
this.calculate = calculate;
|
||||||
|
this.children = children;
|
||||||
|
this.covers = covers;
|
||||||
|
this.size = {w, h};
|
||||||
|
this.matrix = new Array(w * h).fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
compute(i, position) {
|
||||||
|
if (!this.covers(position)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.matrix[i] = this.calculate(position);
|
||||||
|
if (!this.children) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let j = 0; j < this.children.length; j++) {
|
||||||
|
this.children[j].compute(i, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
const {w, h} = this.size;
|
||||||
|
let i = 0;
|
||||||
|
const position = {x: 0, y: 0};
|
||||||
|
for (let y = 0; y < h; ++y) {
|
||||||
|
for (let x = 0; x < w; ++x) {
|
||||||
|
this.compute(i++, position);
|
||||||
|
position.x += 1;
|
||||||
|
}
|
||||||
|
position.x -= w;
|
||||||
|
position.y += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function intersects(l, r) {
|
export function intersects(l, r) {
|
||||||
if (l.x0 > r.x1) return false;
|
if (l.x0 > r.x1) return false;
|
||||||
if (l.y0 > r.y1) return false;
|
if (l.y0 > r.y1) return false;
|
||||||
|
|
56
app/util/noise.js
Normal file
56
app/util/noise.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// import {Texture} from '@pixi/core';
|
||||||
|
// import {Sprite} from '@pixi/sprite';
|
||||||
|
|
||||||
|
import {bresenham, createNoise2D, TAU} from '@/util/math.js';
|
||||||
|
|
||||||
|
const simplex2D = createNoise2D();
|
||||||
|
|
||||||
|
// TODO - 3d noise around a circumference of length polygon perimeter
|
||||||
|
|
||||||
|
export function createLayerMask(layer) {
|
||||||
|
const {area, hulls, tileSize} = layer;
|
||||||
|
const canvas = window.document.createElement('canvas');
|
||||||
|
if (0 === hulls.length) {
|
||||||
|
return Texture.from(canvas);
|
||||||
|
}
|
||||||
|
[canvas.width, canvas.height] = [area.x * tileSize.x, area.y * tileSize.y];
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||||
|
for (let i = 0; i < hulls.length; ++i) {
|
||||||
|
const hull = [...hulls[i]];
|
||||||
|
hull.push(hull[0]);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(hull[0].x, hull[0].y);
|
||||||
|
for (let j = 0; j < hull.length - 1; j++) {
|
||||||
|
const p0 = hull[j + 0];
|
||||||
|
const p1 = hull[j + 1];
|
||||||
|
const points = bresenham(p0, p1);
|
||||||
|
const isReversed = (
|
||||||
|
(p0.x > p1.x && points[0].x < points[points.length - 1].x)
|
||||||
|
|| (p0.y > p1.y && points[0].y < points[points.length - 1].y)
|
||||||
|
);
|
||||||
|
for (
|
||||||
|
let k = (isReversed ? points.length - 1 : 0);
|
||||||
|
(isReversed ? k >= 0 : k < points.length);
|
||||||
|
k += (isReversed ? -1 : 1)
|
||||||
|
) {
|
||||||
|
const {x, y} = points[k];
|
||||||
|
const ANGLE_SCALE = 1000;
|
||||||
|
const WALK_SCALE = 10;
|
||||||
|
const MAGNITUDE = ((tileSize.x + tileSize.y) / 2) * simplex2D(x * 100, y * 100);
|
||||||
|
const w = simplex2D(x * WALK_SCALE, y * WALK_SCALE);
|
||||||
|
const r = TAU * simplex2D(x * ANGLE_SCALE, y * ANGLE_SCALE)
|
||||||
|
ctx.lineTo(
|
||||||
|
x + Math.cos(r) * w * MAGNITUDE,
|
||||||
|
y + -Math.sin(r) * w * MAGNITUDE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
return Texture.from(canvas);
|
||||||
|
}
|
||||||
|
|
43
package-lock.json
generated
43
package-lock.json
generated
|
@ -6,7 +6,6 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "silphius-next",
|
"name": "silphius-next",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@leodeslf/simplex-noise": "^1.0.0",
|
|
||||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||||
"@pixi/filter-adjustment": "^5.1.1",
|
"@pixi/filter-adjustment": "^5.1.1",
|
||||||
"@pixi/filter-color-matrix": "^7.4.2",
|
"@pixi/filter-color-matrix": "^7.4.2",
|
||||||
|
@ -20,6 +19,7 @@
|
||||||
"@remix-run/node": "^2.9.2",
|
"@remix-run/node": "^2.9.2",
|
||||||
"@remix-run/react": "^2.9.2",
|
"@remix-run/react": "^2.9.2",
|
||||||
"acorn": "^8.12.0",
|
"acorn": "^8.12.0",
|
||||||
|
"alea": "^1.0.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
|
"simplex-noise": "^4.0.1",
|
||||||
"ws": "^8.17.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"image-size": "^1.1.1",
|
||||||
"storybook": "^8.1.6",
|
"storybook": "^8.1.6",
|
||||||
"vite": "^5.1.0",
|
"vite": "^5.1.0",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^1.6.0"
|
||||||
|
@ -3026,11 +3028,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||||
},
|
},
|
||||||
"node_modules/@leodeslf/simplex-noise": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@leodeslf/simplex-noise/-/simplex-noise-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-hsp+CfDnT9jxUjDUqiV2+eLkkdAKz6szlpcyMyXvrgs+LO8a0Gepyri1V+c6FbOfqc3ReWB282GtIE8y3Idi1A=="
|
|
||||||
},
|
|
||||||
"node_modules/@mdx-js/mdx": {
|
"node_modules/@mdx-js/mdx": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz",
|
||||||
|
@ -7452,6 +7449,11 @@
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/alea": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/alea/-/alea-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-QU+wv+ziDXaMxRdsQg/aH7sVfWdhKps5YP97IIwFkHCsbDZA3k87JXoZ5/iuemf4ntytzIWeScrRpae8+lDrXA=="
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
@ -11355,6 +11357,21 @@
|
||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/image-size": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"queue": "6.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"image-size": "bin/image-size.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
|
@ -15196,6 +15213,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/queue": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "~2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
@ -16488,6 +16514,11 @@
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/simplex-noise": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-zl/+bdSqW7HJOQ0oDbxrNYaF4F5ik0i7M6YOYmEoIJNtg16NpvWaTTM1Y7oV/7T0jFljawLgYPS81Uu2rsfo1A=="
|
||||||
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
"test": "vitest app"
|
"test": "vitest app"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@leodeslf/simplex-noise": "^1.0.0",
|
|
||||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||||
"@pixi/filter-adjustment": "^5.1.1",
|
"@pixi/filter-adjustment": "^5.1.1",
|
||||||
"@pixi/filter-color-matrix": "^7.4.2",
|
"@pixi/filter-color-matrix": "^7.4.2",
|
||||||
|
@ -27,6 +26,7 @@
|
||||||
"@remix-run/node": "^2.9.2",
|
"@remix-run/node": "^2.9.2",
|
||||||
"@remix-run/react": "^2.9.2",
|
"@remix-run/react": "^2.9.2",
|
||||||
"acorn": "^8.12.0",
|
"acorn": "^8.12.0",
|
||||||
|
"alea": "^1.0.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
|
"simplex-noise": "^4.0.1",
|
||||||
"ws": "^8.17.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"image-size": "^1.1.1",
|
||||||
"storybook": "^8.1.6",
|
"storybook": "^8.1.6",
|
||||||
"vite": "^5.1.0",
|
"vite": "^5.1.0",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^1.6.0"
|
||||||
|
|
1
public/assets/ambient/blossom.json
Normal file
1
public/assets/ambient/blossom.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"frames":{"":{"frame":{"x":0,"y":0,"w":128,"h":160},"spriteSourceSize":{"x":0,"y":0,"w":128,"h":160},"sourceSize":{"w":128,"h":160}}},"meta":{"format":"RGBA8888","image":"./blossom.png","scale":1,"size":{"w":128,"h":160}}}
|
BIN
public/assets/ambient/blossom.png
Normal file
BIN
public/assets/ambient/blossom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
1
public/assets/ambient/flower.json
Normal file
1
public/assets/ambient/flower.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"frames":{"":{"frame":{"x":0,"y":0,"w":32,"h":32},"spriteSourceSize":{"x":0,"y":0,"w":32,"h":32},"sourceSize":{"w":32,"h":32}}},"meta":{"format":"RGBA8888","image":"./flower.png","scale":1,"size":{"w":32,"h":32}}}
|
BIN
public/assets/ambient/flower.png
Normal file
BIN
public/assets/ambient/flower.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
1
public/assets/ambient/shrub.json
Normal file
1
public/assets/ambient/shrub.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"frames":{"":{"frame":{"x":0,"y":0,"w":32,"h":32},"spriteSourceSize":{"x":0,"y":0,"w":32,"h":32},"sourceSize":{"w":32,"h":32}}},"meta":{"format":"RGBA8888","image":"./shrub.png","scale":1,"size":{"w":32,"h":32}}}
|
BIN
public/assets/ambient/shrub.png
Normal file
BIN
public/assets/ambient/shrub.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
1
public/assets/ambient/tree.json
Normal file
1
public/assets/ambient/tree.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"frames":{"":{"frame":{"x":0,"y":0,"w":96,"h":160},"spriteSourceSize":{"x":0,"y":0,"w":96,"h":160},"sourceSize":{"w":96,"h":160}}},"meta":{"format":"RGBA8888","image":"./tree.png","scale":1,"size":{"w":96,"h":160}}}
|
BIN
public/assets/ambient/tree.png
Normal file
BIN
public/assets/ambient/tree.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -6,16 +6,25 @@ import {basename, dirname, extname, join} from 'node:path';
|
||||||
import imageSize from 'image-size';
|
import imageSize from 'image-size';
|
||||||
|
|
||||||
const tileset = process.argv[2];
|
const tileset = process.argv[2];
|
||||||
const w = parseInt(process.argv[3]);
|
let w = parseInt(process.argv[3]);
|
||||||
const h = parseInt(process.argv[4]);
|
let h = parseInt(process.argv[4]);
|
||||||
|
|
||||||
const {width, height} = imageSize(tileset);
|
const {width, height} = imageSize(tileset);
|
||||||
|
|
||||||
|
if (0 === w) {
|
||||||
|
w = width;
|
||||||
|
}
|
||||||
|
if (0 === h) {
|
||||||
|
h = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = (width / w) * (height / h);
|
||||||
|
|
||||||
const json = {
|
const json = {
|
||||||
frames: {},
|
frames: {},
|
||||||
meta: {
|
meta: {
|
||||||
format: 'RGBA8888',
|
format: 'RGBA8888',
|
||||||
image: tileset,
|
image: ['.', basename(tileset)].join('/'),
|
||||||
scale: 1,
|
scale: 1,
|
||||||
size: {w: width, h: height},
|
size: {w: width, h: height},
|
||||||
},
|
},
|
||||||
|
@ -26,7 +35,7 @@ const extlessPath = join(dirname(tileset), basename(tileset, extname(tileset)));
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (let y = 0; y < height; y += h) {
|
for (let y = 0; y < height; y += h) {
|
||||||
for (let x = 0; x < width; x += w) {
|
for (let x = 0; x < width; x += w) {
|
||||||
json.frames[join(extlessPath, `${i++}`)] = {
|
json.frames[1 === total ? '' : join(extlessPath, `${i++}`)] = {
|
||||||
frame: {x, y, w, h},
|
frame: {x, y, w, h},
|
||||||
spriteSourceSize: {x: 0, y: 0, w, h},
|
spriteSourceSize: {x: 0, y: 0, w, h},
|
||||||
sourceSize: {w, h},
|
sourceSize: {w, h},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user