Compare commits
24 Commits
5c619b26c0
...
c9e7b33d25
Author | SHA1 | Date | |
---|---|---|---|
|
c9e7b33d25 | ||
|
01d63e5ee0 | ||
|
a1a6b98639 | ||
|
3191ee83d4 | ||
|
d168b9b580 | ||
|
83d8924517 | ||
|
4b46b2f0ab | ||
|
2becc0afb9 | ||
|
32fa9ee257 | ||
|
4f59ddd731 | ||
|
ba360d6b4f | ||
|
5a43859e56 | ||
|
723d2fc9c3 | ||
|
392735cf99 | ||
|
90ab588133 | ||
|
64df3b882f | ||
|
b719e3227b | ||
|
82e37f9b91 | ||
|
1e588900bb | ||
|
4777a2a3a5 | ||
|
99b8d0f633 | ||
|
ab626f8f9a | ||
|
ce9a1aeba7 | ||
|
b37d3513f6 |
|
@ -699,7 +699,7 @@ export default class Sandbox {
|
||||||
stack: [],
|
stack: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let result = this.executeSync(this.ast, 0);
|
const result = this.executeSync(this.ast, 0);
|
||||||
const stepResult = {async: false, done: false, value: undefined};
|
const stepResult = {async: false, done: false, value: undefined};
|
||||||
switch (result.yield) {
|
switch (result.yield) {
|
||||||
case YIELD_PROMISE: {
|
case YIELD_PROMISE: {
|
||||||
|
|
|
@ -121,20 +121,7 @@ export default class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(entities) {
|
async insertMany(entities) {
|
||||||
const creating = [];
|
await this.createMany(entities);
|
||||||
for (let i = 0; i < entities.length; i++) {
|
|
||||||
const [entityId, values] = entities[i];
|
|
||||||
if (!this.get(entityId)) {
|
|
||||||
creating.push([entityId, values]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const instance = this.get(entityId);
|
|
||||||
for (const i in values) {
|
|
||||||
instance[i] = values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.createMany(creating);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
|
@ -152,12 +139,22 @@ export default class Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
destroy() {}
|
destroy() {}
|
||||||
toNet() {
|
toNet(recipient, data) {
|
||||||
return Component.constructor.filterDefaults(this);
|
return data || Component.constructor.filterDefaults(this);
|
||||||
}
|
}
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return Component.constructor.filterDefaults(this);
|
return Component.constructor.filterDefaults(this);
|
||||||
}
|
}
|
||||||
|
update(values) {
|
||||||
|
for (const key in values) {
|
||||||
|
if (concrete.properties[key]) {
|
||||||
|
this[`$$${key}`] = values[key];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this[key] = values[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const properties = {};
|
const properties = {};
|
||||||
properties.entity = {
|
properties.entity = {
|
||||||
|
@ -216,4 +213,11 @@ export default class Component {
|
||||||
return this.constructor.schema.sizeOf(this.get(entityId));
|
return this.constructor.schema.sizeOf(this.get(entityId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateMany(entities) {
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
const [entityId, values] = entities[i];
|
||||||
|
this.get(entityId).update(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ export default class Collider extends Component {
|
||||||
}
|
}
|
||||||
closest(aabb) {
|
closest(aabb) {
|
||||||
const entity = ecs.get(this.entity);
|
const entity = ecs.get(this.entity);
|
||||||
return Array.from(ecs.system('Colliders').within(aabb))
|
return Array.from(ecs.system('MaintainColliderHash').within(aabb))
|
||||||
.filter((other) => other !== entity)
|
.filter((other) => other !== entity)
|
||||||
.sort(({Position: l}, {Position: r}) => {
|
.sort(({Position: l}, {Position: r}) => {
|
||||||
return distance(entity.Position, l) > distance(entity.Position, r) ? -1 : 1;
|
return distance(entity.Position, l) > distance(entity.Position, r) ? -1 : 1;
|
||||||
|
|
|
@ -1,13 +1,38 @@
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
export default class Emitter extends Component {
|
import Emitter from '@/particles/emitter.js';
|
||||||
|
import {Ticker as TickerPromise} from '@/util/promise.js';
|
||||||
|
|
||||||
|
export default class EmitterComponent extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Component = this;
|
const Component = this;
|
||||||
|
const {ecs} = this;
|
||||||
return class EmitterInstance extends super.instanceFromSchema() {
|
return class EmitterInstance extends super.instanceFromSchema() {
|
||||||
emitting = {};
|
emitting = {};
|
||||||
id = 0;
|
id = 0;
|
||||||
emit(specification) {
|
emit(specification) {
|
||||||
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
|
if (specification.server) {
|
||||||
|
const {Ticker} = ecs.get(1);
|
||||||
|
if (Ticker) {
|
||||||
|
const emitter = new Emitter(ecs);
|
||||||
|
const promise = new Promise((resolve) => {
|
||||||
|
emitter.emit().onEnd(resolve);
|
||||||
|
});
|
||||||
|
Ticker.add(
|
||||||
|
new TickerPromise(
|
||||||
|
(resolve) => {
|
||||||
|
promise.then(resolve);
|
||||||
|
},
|
||||||
|
(elapsed) => {
|
||||||
|
this.emitter.tick(elapsed);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ class ItemProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Inventory extends Component {
|
export default class Inventory extends Component {
|
||||||
async insertMany(entities) {
|
async updateMany(entities) {
|
||||||
for (const [id, {cleared, given, qtyUpdated, swapped}] of entities) {
|
for (const [id, {cleared, given, qtyUpdated, swapped}] of entities) {
|
||||||
const instance = this.get(id);
|
const instance = this.get(id);
|
||||||
const {$$items, slots} = instance;
|
const {$$items, slots} = instance;
|
||||||
|
@ -137,12 +137,14 @@ export default class Inventory extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (swapped) {
|
if (swapped) {
|
||||||
for (const [l, r] of swapped) {
|
for (const [l, otherEntityId, r] of swapped) {
|
||||||
|
const otherInstance = this.get(otherEntityId);
|
||||||
|
const {$$items: $$otherItems, slots: otherSlots} = otherInstance;
|
||||||
const tmp = [$$items[l], slots[l]];
|
const tmp = [$$items[l], slots[l]];
|
||||||
[$$items[l], slots[l]] = [$$items[r], slots[r]];
|
[$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]];
|
||||||
[$$items[r], slots[r]] = tmp;
|
[$$otherItems[r], otherSlots[r]] = tmp;
|
||||||
if ($$items[r]) {
|
if ($$otherItems[r]) {
|
||||||
$$items[r].slot = r;
|
$$otherItems[r].slot = r;
|
||||||
}
|
}
|
||||||
if ($$items[l]) {
|
if ($$items[l]) {
|
||||||
$$items[l].slot = l;
|
$$items[l].slot = l;
|
||||||
|
@ -150,7 +152,7 @@ export default class Inventory extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await super.insertMany(entities);
|
await super.updateMany(entities);
|
||||||
for (const [id, {slots}] of entities) {
|
for (const [id, {slots}] of entities) {
|
||||||
if (slots) {
|
if (slots) {
|
||||||
const instance = this.get(id);
|
const instance = this.get(id);
|
||||||
|
@ -189,18 +191,26 @@ export default class Inventory extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
swapSlots(l, r) {
|
swapSlots(l, OtherInventory, r) {
|
||||||
const {$$items, slots} = this;
|
const {$$items, slots} = this;
|
||||||
|
const {$$items: $$otherItems, slots: otherSlots} = OtherInventory;
|
||||||
const tmp = [$$items[l], slots[l]];
|
const tmp = [$$items[l], slots[l]];
|
||||||
[$$items[l], slots[l]] = [$$items[r], slots[r]];
|
[$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]];
|
||||||
[$$items[r], slots[r]] = tmp;
|
[$$otherItems[r], otherSlots[r]] = tmp;
|
||||||
if (undefined === slots[l]) {
|
if (undefined === slots[l]) {
|
||||||
delete slots[l];
|
delete slots[l];
|
||||||
}
|
}
|
||||||
if (undefined === slots[r]) {
|
if (undefined === otherSlots[r]) {
|
||||||
delete slots[r];
|
delete otherSlots[r];
|
||||||
}
|
}
|
||||||
Component.markChange(this.entity, 'swapped', [[l, r]]);
|
Component.markChange(this.entity, 'swapped', [[l, OtherInventory.entity, r]]);
|
||||||
|
Component.markChange(OtherInventory.entity, 'swapped', [[r, this.entity, l]]);
|
||||||
|
}
|
||||||
|
toNet(recipient, data) {
|
||||||
|
if (recipient.id !== this.entity && this !== recipient.Player.openInventory) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return super.toNet(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
export default class Light extends Component {
|
export default class Light extends Component {
|
||||||
static properties = {
|
static properties = {
|
||||||
|
brightness: {defaultValue: 1, type: 'float32'},
|
||||||
radius: {type: 'uint8'},
|
radius: {type: 'uint8'},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,51 @@
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
export default class Player extends Component {}
|
const State = {
|
||||||
|
CLOSED: 0,
|
||||||
|
OPENING: 1,
|
||||||
|
OPEN: 2,
|
||||||
|
CLOSING: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Player extends Component {
|
||||||
|
instanceFromSchema() {
|
||||||
|
const {ecs} = this;
|
||||||
|
return class PlayerInstance extends super.instanceFromSchema() {
|
||||||
|
$$openInventory;
|
||||||
|
$$openInventoryState = State.CLOSED;
|
||||||
|
closeInventory() {
|
||||||
|
this.$$openInventoryState = State.CLOSING;
|
||||||
|
}
|
||||||
|
updateAttachments(update) {
|
||||||
|
if (!this.$$openInventory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (![State.OPENING, State.CLOSING].includes(this.$$openInventoryState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ecs.get(this.$$openInventory.entity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!update[this.$$openInventory.entity]) {
|
||||||
|
update[this.$$openInventory.entity] = {};
|
||||||
|
}
|
||||||
|
if (this.$$openInventoryState === State.OPENING) {
|
||||||
|
update[this.$$openInventory.entity].Inventory = this.$$openInventory.toNet(ecs.get(this.entity))
|
||||||
|
this.$$openInventoryState = State.OPEN;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
update[this.$$openInventory.entity].Inventory = {closed: true};
|
||||||
|
this.$$openInventory = undefined;
|
||||||
|
this.$$openInventoryState = State.CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get openInventory() {
|
||||||
|
return this.$$openInventory;
|
||||||
|
}
|
||||||
|
set openInventory(Inventory) {
|
||||||
|
this.$$openInventoryState = State.OPENING;
|
||||||
|
this.$$openInventory = Inventory;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,23 @@ import Component from '@/ecs/component.js';
|
||||||
export default class Sprite extends Component {
|
export default class Sprite extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
return class SpriteInstance extends super.instanceFromSchema() {
|
return class SpriteInstance extends super.instanceFromSchema() {
|
||||||
|
$$anchor = {x: 0.5, y: 0.5};
|
||||||
|
$$scale = {x: 1, y: 1};
|
||||||
$$sourceJson = {};
|
$$sourceJson = {};
|
||||||
get anchor() {
|
get anchor() {
|
||||||
return {x: this.anchorX, y: this.anchorY};
|
return this.$$anchor;
|
||||||
|
}
|
||||||
|
get anchorX() {
|
||||||
|
return this.$$anchor.x;
|
||||||
|
}
|
||||||
|
set anchorX(anchorX) {
|
||||||
|
this.$$anchor = {x: anchorX, y: this.anchorY};
|
||||||
|
}
|
||||||
|
get anchorY() {
|
||||||
|
return this.$$anchor.y;
|
||||||
|
}
|
||||||
|
set anchorY(anchorY) {
|
||||||
|
this.$$anchor = {x: this.anchorX, y: anchorY};
|
||||||
}
|
}
|
||||||
get animation() {
|
get animation() {
|
||||||
return super.animation;
|
return super.animation;
|
||||||
|
@ -56,17 +70,40 @@ export default class Sprite extends Component {
|
||||||
return this.$$sourceJson.meta.rotation;
|
return this.$$sourceJson.meta.rotation;
|
||||||
}
|
}
|
||||||
get scale() {
|
get scale() {
|
||||||
return {x: this.scaleX, y: this.scaleY};
|
return this.$$scale;
|
||||||
}
|
}
|
||||||
toNet() {
|
get scaleX() {
|
||||||
|
return this.$$scale.x;
|
||||||
|
}
|
||||||
|
set scaleX(scaleX) {
|
||||||
|
this.$$scale = {x: scaleX, y: this.scaleY};
|
||||||
|
}
|
||||||
|
get scaleY() {
|
||||||
|
return this.$$scale.y;
|
||||||
|
}
|
||||||
|
set scaleY(scaleY) {
|
||||||
|
this.$$scale = {x: this.scaleX, y: scaleY};
|
||||||
|
}
|
||||||
|
get size() {
|
||||||
|
if (!this.$$sourceJson.frames) {
|
||||||
|
return {x: 16, y: 16};
|
||||||
|
}
|
||||||
|
const frame = this.animation
|
||||||
|
? this.$$sourceJson.animations[this.animation][this.frame]
|
||||||
|
: '';
|
||||||
|
return this.$$sourceJson.frames[frame].sourceSize;
|
||||||
|
}
|
||||||
|
toNet(recipient, data) {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const {elapsed, ...rest} = super.toNet();
|
const {elapsed, ...rest} = super.toNet(recipient, data);
|
||||||
return rest;
|
return rest;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async load(instance) {
|
async load(instance) {
|
||||||
instance.$$sourceJson = await this.ecs.readJson(instance.source);
|
if (instance.source) {
|
||||||
|
instance.$$sourceJson = await this.ecs.readJson(instance.source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
markChange(entityId, key, value) {
|
markChange(entityId, key, value) {
|
||||||
if ('elapsed' === key) {
|
if ('elapsed' === key) {
|
||||||
|
@ -86,5 +123,6 @@ export default class Sprite extends Component {
|
||||||
scaleY: {defaultValue: 1, type: 'float32'},
|
scaleY: {defaultValue: 1, type: 'float32'},
|
||||||
source: {type: 'string'},
|
source: {type: 'string'},
|
||||||
speed: {type: 'float32'},
|
speed: {type: 'float32'},
|
||||||
|
tint: {type: 'uint32'},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,23 @@ export default class Ticking extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
return class TickingInstance extends super.instanceFromSchema() {
|
return class TickingInstance extends super.instanceFromSchema() {
|
||||||
|
|
||||||
$$finished = [];
|
|
||||||
$$tickers = [];
|
$$tickers = [];
|
||||||
|
|
||||||
add(ticker) {
|
add(ticker) {
|
||||||
this.$$tickers.push(ticker);
|
this.$$tickers.push(ticker);
|
||||||
ticker.then(() => {
|
ticker.then(() => {
|
||||||
this.$$finished.push(ticker);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.$$finished = [];
|
|
||||||
this.$$tickers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
tick(elapsed) {
|
|
||||||
for (const ticker of this.$$finished) {
|
|
||||||
this.$$tickers.splice(
|
this.$$tickers.splice(
|
||||||
this.$$tickers.indexOf(ticker),
|
this.$$tickers.indexOf(ticker),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
this.$$finished = [];
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.$$tickers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(elapsed) {
|
||||||
for (const ticker of this.$$tickers) {
|
for (const ticker of this.$$tickers) {
|
||||||
ticker.tick(elapsed);
|
ticker.tick(elapsed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ export default class TileLayers extends Component {
|
||||||
}
|
}
|
||||||
return super.createMany(entities);
|
return super.createMany(entities);
|
||||||
}
|
}
|
||||||
async insertMany(entities) {
|
async updateMany(entities) {
|
||||||
for (const [, {layers}] of entities) {
|
for (const [, {layers}] of entities) {
|
||||||
if (layers) {
|
if (layers) {
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
|
@ -179,7 +179,7 @@ export default class TileLayers extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await super.insertMany(entities);
|
await super.updateMany(entities);
|
||||||
for (const [id, {layerChange}] of entities) {
|
for (const [id, {layerChange}] of entities) {
|
||||||
if (layerChange) {
|
if (layerChange) {
|
||||||
const component = this.get(id);
|
const component = this.get(id);
|
||||||
|
|
22
app/ecs/components/ttl.js
Normal file
22
app/ecs/components/ttl.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
|
export default class Ttl extends Component {
|
||||||
|
instanceFromSchema() {
|
||||||
|
const {ecs} = this;
|
||||||
|
return class TtlInstance extends super.instanceFromSchema() {
|
||||||
|
$$elapsed = 0;
|
||||||
|
$$reset() {
|
||||||
|
this.$$elapsed = 0;
|
||||||
|
}
|
||||||
|
tick(elapsed) {
|
||||||
|
this.$$elapsed += elapsed;
|
||||||
|
if (this.$$elapsed >= this.ttl) {
|
||||||
|
ecs.destroy(this.entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static properties = {
|
||||||
|
ttl: {type: 'float32'},
|
||||||
|
};
|
||||||
|
}
|
|
@ -23,6 +23,9 @@ export default class Vulnerable extends Component {
|
||||||
id = 0;
|
id = 0;
|
||||||
Types = DamageTypes;
|
Types = DamageTypes;
|
||||||
damage(specification) {
|
damage(specification) {
|
||||||
|
if (this.isInvulnerable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const {Alive} = Component.ecs.get(this.entity);
|
const {Alive} = Component.ecs.get(this.entity);
|
||||||
if (Alive) {
|
if (Alive) {
|
||||||
switch (specification.type) {
|
switch (specification.type) {
|
||||||
|
@ -31,8 +34,15 @@ export default class Vulnerable extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component.markChange(this.entity, 'damage', {[this.id++]: specification});
|
Component.markChange(
|
||||||
|
this.entity,
|
||||||
|
'damage',
|
||||||
|
{[`${this.entity}-${this.id++}`]: specification},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
static properties = {
|
||||||
|
isInvulnerable: {type: 'uint8'},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Encoder, Decoder} from '@msgpack/msgpack';
|
import {Encoder, Decoder} from '@msgpack/msgpack';
|
||||||
import {LRUCache} from 'lru-cache';
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
import {withResolvers} from '@/util/promise.js';
|
import {Ticker, withResolvers} from '@/util/promise.js';
|
||||||
import Script from '@/util/script.js';
|
import Script from '@/util/script.js';
|
||||||
|
|
||||||
import EntityFactory from './entity-factory.js';
|
import EntityFactory from './entity-factory.js';
|
||||||
|
@ -59,6 +59,7 @@ export default class Ecs {
|
||||||
async apply(patch) {
|
async apply(patch) {
|
||||||
const creating = [];
|
const creating = [];
|
||||||
const destroying = [];
|
const destroying = [];
|
||||||
|
const inserting = [];
|
||||||
const removing = [];
|
const removing = [];
|
||||||
const updating = [];
|
const updating = [];
|
||||||
for (const entityIdString in patch) {
|
for (const entityIdString in patch) {
|
||||||
|
@ -82,7 +83,23 @@ export default class Ecs {
|
||||||
removing.push([entityId, componentsToRemove]);
|
removing.push([entityId, componentsToRemove]);
|
||||||
}
|
}
|
||||||
if (this.$$entities[entityId]) {
|
if (this.$$entities[entityId]) {
|
||||||
updating.push([entityId, componentsToUpdate]);
|
const entity = this.$$entities[entityId];
|
||||||
|
const entityInserts = {};
|
||||||
|
const entityUpdates = {};
|
||||||
|
for (const componentName in componentsToUpdate) {
|
||||||
|
if (entity[componentName]) {
|
||||||
|
entityUpdates[componentName] = componentsToUpdate[componentName];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entityInserts[componentName] = componentsToUpdate[componentName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(entityInserts).length > 0) {
|
||||||
|
inserting.push([entityId, entityInserts]);
|
||||||
|
}
|
||||||
|
if (Object.keys(entityUpdates).length > 0) {
|
||||||
|
updating.push([entityId, entityUpdates]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
creating.push([entityId, componentsToUpdate]);
|
creating.push([entityId, componentsToUpdate]);
|
||||||
|
@ -91,14 +108,19 @@ export default class Ecs {
|
||||||
if (destroying.length > 0) {
|
if (destroying.length > 0) {
|
||||||
this.destroyMany(destroying);
|
this.destroyMany(destroying);
|
||||||
}
|
}
|
||||||
if (updating.length > 0) {
|
const promises = [];
|
||||||
await this.insertMany(updating);
|
if (inserting.length > 0) {
|
||||||
|
promises.push(this.insertMany(inserting));
|
||||||
}
|
}
|
||||||
if (removing.length > 0) {
|
if (updating.length > 0) {
|
||||||
this.removeMany(removing);
|
promises.push(this.updateMany(updating));
|
||||||
}
|
}
|
||||||
if (creating.length > 0) {
|
if (creating.length > 0) {
|
||||||
await this.createManySpecific(creating);
|
promises.push(this.createManySpecific(creating));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
if (removing.length > 0) {
|
||||||
|
this.removeMany(removing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,8 +484,8 @@ export default class Ecs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const destroying = new Set();
|
const destroying = new Set();
|
||||||
for (const [entityId, {promises, resolvers}] of this.$$destructionDependencies) {
|
for (const [entityId, {promises}] of this.$$destructionDependencies) {
|
||||||
if (0 === promises.size && resolvers) {
|
if (0 === promises.size) {
|
||||||
destroying.add(entityId);
|
destroying.add(entityId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,4 +517,25 @@ export default class Ecs {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateMany(entities) {
|
||||||
|
const updating = {};
|
||||||
|
const unique = new Set();
|
||||||
|
for (const [entityId, components] of entities) {
|
||||||
|
this.rebuild(entityId);
|
||||||
|
for (const componentName in components) {
|
||||||
|
if (!updating[componentName]) {
|
||||||
|
updating[componentName] = [];
|
||||||
|
}
|
||||||
|
updating[componentName].push([entityId, components[componentName]]);
|
||||||
|
}
|
||||||
|
unique.add(entityId);
|
||||||
|
}
|
||||||
|
const promises = [];
|
||||||
|
for (const componentName in updating) {
|
||||||
|
promises.push(this.Components[componentName].updateMany(updating[componentName]));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
this.reindex(unique.values());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,23 @@ export default class EntityFactory {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Object.defineProperties(Entity.prototype, properties);
|
Object.defineProperties(Entity.prototype, properties);
|
||||||
|
Entity.prototype.updateAttachments = new Function('update', `
|
||||||
|
${
|
||||||
|
sorted
|
||||||
|
.filter((componentName) => (
|
||||||
|
Components[componentName].Instance.prototype.updateAttachments
|
||||||
|
))
|
||||||
|
.map((type) => `this.${type}.updateAttachments(update)`).join('; ')
|
||||||
|
}
|
||||||
|
`);
|
||||||
Entity.prototype.toJSON = new Function('', `
|
Entity.prototype.toJSON = new Function('', `
|
||||||
return {
|
return {
|
||||||
${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')}
|
${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')}
|
||||||
};
|
};
|
||||||
`);
|
`);
|
||||||
Entity.prototype.toNet = new Function('', `
|
Entity.prototype.toNet = new Function('recipient', `
|
||||||
return {
|
return {
|
||||||
${sorted.map((type) => `${type}: this.${type}.toNet()`).join(', ')}
|
${sorted.map((type) => `${type}: this.${type}.toNet(recipient)`).join(', ')}
|
||||||
};
|
};
|
||||||
`);
|
`);
|
||||||
walk.class = Entity;
|
walk.class = Entity;
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default class Attract extends System {
|
||||||
};
|
};
|
||||||
let s = Magnet.strength;
|
let s = Magnet.strength;
|
||||||
s = s * s;
|
s = s * s;
|
||||||
for (const other of this.ecs.system('Colliders').within(aabb)) {
|
for (const other of this.ecs.system('MaintainColliderHash').within(aabb)) {
|
||||||
if (other === entity || !other.Magnetic) {
|
if (other === entity || !other.Magnetic) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ export default class ClampPositions extends System {
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
const {AreaSize} = this.ecs.get(1);
|
const {AreaSize} = this.ecs.get(1);
|
||||||
|
if (!AreaSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const {Position} of this.ecs.changed(['Position'])) {
|
for (const {Position} of this.ecs.changed(['Position'])) {
|
||||||
if (Position.x < 0) {
|
if (Position.x < 0) {
|
||||||
Position.x = 0;
|
Position.x = 0;
|
||||||
|
|
|
@ -1,53 +1,15 @@
|
||||||
import {System} from '@/ecs/index.js';
|
import {System} from '@/ecs/index.js';
|
||||||
import SpatialHash from '@/util/spatial-hash.js';
|
|
||||||
|
|
||||||
export default class Colliders extends System {
|
export default class Colliders extends System {
|
||||||
|
|
||||||
hash;
|
|
||||||
|
|
||||||
deindex(entities) {
|
|
||||||
super.deindex(entities);
|
|
||||||
for (const id of entities) {
|
|
||||||
this.hash.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get priority() {
|
static get priority() {
|
||||||
return {
|
return {
|
||||||
after: 'IntegratePhysics',
|
after: 'MaintainColliderHash',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reindex(entities) {
|
|
||||||
for (const id of entities) {
|
|
||||||
if (1 === id) {
|
|
||||||
this.hash = new SpatialHash(this.ecs.get(1).AreaSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.reindex(entities);
|
|
||||||
for (const id of entities) {
|
|
||||||
this.updateHash(this.ecs.get(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHash(entity) {
|
|
||||||
if (!entity.Collider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hash.update(entity.Collider.aabb, entity.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
const checked = new Map();
|
const checked = new Map();
|
||||||
for (const entity of this.ecs.changed(['Direction'])) {
|
|
||||||
if (!entity.Collider) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
entity.Collider.updateAabbs();
|
|
||||||
}
|
|
||||||
for (const entity of this.ecs.changed(['Position'])) {
|
|
||||||
this.updateHash(entity);
|
|
||||||
}
|
|
||||||
for (const entity of this.ecs.changed(['Position'])) {
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
if (!entity.Collider) {
|
if (!entity.Collider) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -55,7 +17,7 @@ export default class Colliders extends System {
|
||||||
if (!checked.has(entity)) {
|
if (!checked.has(entity)) {
|
||||||
checked.set(entity, new Set());
|
checked.set(entity, new Set());
|
||||||
}
|
}
|
||||||
const within = this.within(entity.Collider.aabb);
|
const within = this.ecs.system('MaintainColliderHash').within(entity.Collider.aabb);
|
||||||
for (const other of within) {
|
for (const other of within) {
|
||||||
if (entity === other || !other.Collider) {
|
if (entity === other || !other.Collider) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -77,12 +39,4 @@ export default class Colliders extends System {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
within(query) {
|
|
||||||
const within = new Set();
|
|
||||||
for (const id of this.hash.within(query)) {
|
|
||||||
within.add(this.ecs.get(id));
|
|
||||||
}
|
|
||||||
return within;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
24
app/ecs/systems/inventory-closer.js
Normal file
24
app/ecs/systems/inventory-closer.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {System} from '@/ecs/index.js';
|
||||||
|
import {distance} from '@/util/math.js';
|
||||||
|
|
||||||
|
export default class InventoryCloser extends System {
|
||||||
|
|
||||||
|
static queries() {
|
||||||
|
return {
|
||||||
|
default: ['Player'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
for (const {Player, Position} of this.select('default')) {
|
||||||
|
const {openInventory} = Player;
|
||||||
|
if (openInventory) {
|
||||||
|
const {Position: inventoryPosition} = this.ecs.get(openInventory.entity);
|
||||||
|
if (distance(Position, inventoryPosition) > 64) {
|
||||||
|
Player.closeInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
app/ecs/systems/kill-perishable.js
Normal file
17
app/ecs/systems/kill-perishable.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {System} from '@/ecs/index.js';
|
||||||
|
|
||||||
|
export default class KillPerishable extends System {
|
||||||
|
|
||||||
|
static queries() {
|
||||||
|
return {
|
||||||
|
default: ['Ttl'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(elapsed) {
|
||||||
|
for (const {Ttl} of this.select('default')) {
|
||||||
|
Ttl.tick(elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
app/ecs/systems/maintain-collider-hash.js
Normal file
63
app/ecs/systems/maintain-collider-hash.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import {System} from '@/ecs/index.js';
|
||||||
|
import SpatialHash from '@/util/spatial-hash.js';
|
||||||
|
|
||||||
|
export default class MaintainColliderHash extends System {
|
||||||
|
|
||||||
|
hash;
|
||||||
|
|
||||||
|
deindex(entities) {
|
||||||
|
super.deindex(entities);
|
||||||
|
for (const id of entities) {
|
||||||
|
this.hash.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get priority() {
|
||||||
|
return {
|
||||||
|
after: 'IntegratePhysics',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
reindex(entities) {
|
||||||
|
for (const id of entities) {
|
||||||
|
if (1 === id) {
|
||||||
|
const {AreaSize} = this.ecs.get(1);
|
||||||
|
if (AreaSize) {
|
||||||
|
this.hash = new SpatialHash(AreaSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.reindex(entities);
|
||||||
|
for (const id of entities) {
|
||||||
|
this.updateHash(this.ecs.get(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHash(entity) {
|
||||||
|
if (!entity.Collider || !this.hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hash.update(entity.Collider.aabb, entity.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
for (const entity of this.ecs.changed(['Direction'])) {
|
||||||
|
if (!entity.Collider) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entity.Collider.updateAabbs();
|
||||||
|
}
|
||||||
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
|
this.updateHash(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
within(query) {
|
||||||
|
const within = new Set();
|
||||||
|
for (const id of this.hash.within(query)) {
|
||||||
|
within.add(this.ecs.get(id));
|
||||||
|
}
|
||||||
|
return within;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,15 +21,18 @@ export default class VisibleAabbs extends System {
|
||||||
reindex(entities) {
|
reindex(entities) {
|
||||||
for (const id of entities) {
|
for (const id of entities) {
|
||||||
if (1 === id) {
|
if (1 === id) {
|
||||||
const {x, y} = this.ecs.get(1).AreaSize;
|
const {AreaSize} = this.ecs.get(1)
|
||||||
if (
|
if (AreaSize) {
|
||||||
!this.hash ||
|
const {x, y} = AreaSize;
|
||||||
(
|
if (
|
||||||
this.hash.area.x !== x
|
!this.hash ||
|
||||||
|| this.hash.area.y !== y
|
(
|
||||||
)
|
this.hash.area.x !== x
|
||||||
) {
|
|| this.hash.area.y !== y
|
||||||
this.hash = new SpatialHash(this.ecs.get(1).AreaSize);
|
)
|
||||||
|
) {
|
||||||
|
this.hash = new SpatialHash(this.ecs.get(1).AreaSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +43,9 @@ export default class VisibleAabbs extends System {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHash(entity) {
|
updateHash(entity) {
|
||||||
|
if (!this.hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!entity.VisibleAabb) {
|
if (!entity.VisibleAabb) {
|
||||||
this.hash.remove(entity.id);
|
this.hash.remove(entity.id);
|
||||||
return;
|
return;
|
||||||
|
@ -53,10 +59,7 @@ export default class VisibleAabbs extends System {
|
||||||
if (VisibleAabb) {
|
if (VisibleAabb) {
|
||||||
let size = undefined;
|
let size = undefined;
|
||||||
if (Sprite) {
|
if (Sprite) {
|
||||||
const frame = Sprite.animation
|
size = Sprite.size;
|
||||||
? Sprite.$$sourceJson.animations[Sprite.animation][Sprite.frame]
|
|
||||||
: '';
|
|
||||||
size = Sprite.$$sourceJson.frames[frame].sourceSize;
|
|
||||||
}
|
}
|
||||||
/* v8 ignore next 3 */
|
/* v8 ignore next 3 */
|
||||||
if (!size) {
|
if (!size) {
|
||||||
|
|
93
app/particles/emitter.js
Normal file
93
app/particles/emitter.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import K from 'kefir';
|
||||||
|
|
||||||
|
export default class Emitter {
|
||||||
|
constructor(ecs) {
|
||||||
|
this.ecs = ecs;
|
||||||
|
this.scheduled = [];
|
||||||
|
}
|
||||||
|
async allocate({entity, shape}) {
|
||||||
|
const allocated = this.ecs.get(await this.ecs.create(entity));
|
||||||
|
if (shape) {
|
||||||
|
switch (shape.type) {
|
||||||
|
case 'filledRect': {
|
||||||
|
allocated.Position.x += Math.random() * shape.payload.width - (shape.payload.width / 2);
|
||||||
|
allocated.Position.y += Math.random() * shape.payload.height - (shape.payload.height / 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
emit(particle) {
|
||||||
|
particle = {
|
||||||
|
...particle,
|
||||||
|
entity: {
|
||||||
|
Position: {},
|
||||||
|
Sprite: {},
|
||||||
|
VisibleAabb: {},
|
||||||
|
...particle.entity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let {count = 1} = particle;
|
||||||
|
const {frequency = 0} = particle;
|
||||||
|
const stream = K.stream((emitter) => {
|
||||||
|
if (0 === frequency) {
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < count; ++i) {
|
||||||
|
promises.push(
|
||||||
|
this.allocate(particle)
|
||||||
|
.then((entity) => {
|
||||||
|
emitter.emit(entity);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
emitter.end();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const promise = this.allocate(particle)
|
||||||
|
.then((entity) => {
|
||||||
|
emitter.emit(entity);
|
||||||
|
});
|
||||||
|
count -= 1;
|
||||||
|
if (0 === count) {
|
||||||
|
promise.then(() => {
|
||||||
|
emitter.end();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const promises = [promise];
|
||||||
|
let accumulated = 0;
|
||||||
|
const scheduled = (elapsed) => {
|
||||||
|
accumulated += elapsed;
|
||||||
|
while (accumulated > frequency && count > 0) {
|
||||||
|
promises.push(
|
||||||
|
this.allocate(particle)
|
||||||
|
.then((entity) => {
|
||||||
|
emitter.emit(entity);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
accumulated -= frequency;
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
if (0 === count) {
|
||||||
|
this.scheduled.splice(this.scheduled.indexOf(scheduled), 1);
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
emitter.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.scheduled.push(scheduled);
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(elapsed) {
|
||||||
|
for (const ticker of this.scheduled) {
|
||||||
|
ticker(elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
app/particles/emitter.test.js
Normal file
52
app/particles/emitter.test.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {expect, test} from 'vitest';
|
||||||
|
|
||||||
|
import Components from '@/ecs/components/index.js';
|
||||||
|
import Ecs from '@/ecs/ecs.js';
|
||||||
|
|
||||||
|
import Emitter from './emitter.js';
|
||||||
|
|
||||||
|
test('emits particles at once', async () => {
|
||||||
|
const ecs = new Ecs({
|
||||||
|
Components,
|
||||||
|
});
|
||||||
|
const emitter = new Emitter(ecs);
|
||||||
|
const stream = emitter.emit({
|
||||||
|
count: 5,
|
||||||
|
frequency: 0,
|
||||||
|
entity: {},
|
||||||
|
});
|
||||||
|
expect(await stream.scan((r) => r + 1, 0).toPromise()).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emits particles over time', async () => {
|
||||||
|
const ecs = new Ecs({
|
||||||
|
Components,
|
||||||
|
});
|
||||||
|
const emitter = new Emitter(ecs);
|
||||||
|
const stream = emitter.emit({
|
||||||
|
count: 2,
|
||||||
|
frequency: 0.1,
|
||||||
|
});
|
||||||
|
const current = stream.toProperty();
|
||||||
|
expect(await new Promise((resolve) => {
|
||||||
|
current.onValue(resolve);
|
||||||
|
}))
|
||||||
|
.to.deep.include({id: 1});
|
||||||
|
expect(ecs.get(1))
|
||||||
|
.to.not.be.undefined;
|
||||||
|
expect(ecs.get(2))
|
||||||
|
.to.be.undefined;
|
||||||
|
emitter.tick(0.06);
|
||||||
|
expect(await new Promise((resolve) => {
|
||||||
|
current.onValue(resolve);
|
||||||
|
}))
|
||||||
|
.to.deep.include({id: 1});
|
||||||
|
emitter.tick(0.06);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
expect(await new Promise((resolve) => {
|
||||||
|
current.onValue(resolve);
|
||||||
|
}))
|
||||||
|
.to.deep.include({id: 2});
|
||||||
|
expect(ecs.get(2))
|
||||||
|
.to.not.be.undefined;
|
||||||
|
});
|
|
@ -11,6 +11,7 @@ export default class ClientEcs extends Ecs {
|
||||||
constructor(specification) {
|
constructor(specification) {
|
||||||
super(specification);
|
super(specification);
|
||||||
[
|
[
|
||||||
|
'MaintainColliderHash',
|
||||||
].forEach((defaultSystem) => {
|
].forEach((defaultSystem) => {
|
||||||
const System = this.system(defaultSystem);
|
const System = this.system(defaultSystem);
|
||||||
if (System) {
|
if (System) {
|
||||||
|
@ -22,7 +23,7 @@ export default class ClientEcs extends Ecs {
|
||||||
if (!cache.has(uri)) {
|
if (!cache.has(uri)) {
|
||||||
const {promise, resolve, reject} = withResolvers();
|
const {promise, resolve, reject} = withResolvers();
|
||||||
cache.set(uri, promise);
|
cache.set(uri, promise);
|
||||||
fetch(new URL(uri, window.location.origin))
|
fetch(new URL(uri, location.origin))
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,9 +10,7 @@ import styles from './devtools.module.css';
|
||||||
import Tiles from './devtools/tiles.jsx';
|
import Tiles from './devtools/tiles.jsx';
|
||||||
|
|
||||||
export default function Devtools({
|
export default function Devtools({
|
||||||
applyFilters,
|
|
||||||
eventsChannel,
|
eventsChannel,
|
||||||
setApplyFilters,
|
|
||||||
}) {
|
}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
|
@ -34,16 +32,6 @@ export default function Devtools({
|
||||||
<div className={styles.dashboard}>
|
<div className={styles.dashboard}>
|
||||||
<form>
|
<form>
|
||||||
<div className={styles.engineBar}>
|
<div className={styles.engineBar}>
|
||||||
<label>
|
|
||||||
<span>Apply filters</span>
|
|
||||||
<input
|
|
||||||
checked={applyFilters}
|
|
||||||
onChange={() => {
|
|
||||||
setApplyFilters(!applyFilters);
|
|
||||||
}}
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<pre><code><small>{mainEntityJson}</small></code></pre>
|
<pre><code><small>{mainEntityJson}</small></code></pre>
|
||||||
|
|
|
@ -1,54 +1,27 @@
|
||||||
import styles from './bag.module.css';
|
import styles from './bag.module.css';
|
||||||
import Slot from './slot.jsx';
|
|
||||||
|
import Grid from './grid.jsx';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inventory bag. 10-40 slots of inventory.
|
* Inventory bag. 10-40 slots of inventory.
|
||||||
*/
|
*/
|
||||||
export default function Bag({
|
export default function Bag({
|
||||||
isInventoryOpen,
|
isInventoryOpen,
|
||||||
|
onActivate,
|
||||||
slots,
|
slots,
|
||||||
}) {
|
}) {
|
||||||
const Slots = slots.map((slot, i) => (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
[styles.slotWrapper]
|
|
||||||
.filter(Boolean).join(' ')
|
|
||||||
}
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
<Slot
|
|
||||||
icon={slot?.icon}
|
|
||||||
// onMouseDown={(event) => {
|
|
||||||
// onActivate(i)
|
|
||||||
// event.stopPropagation();
|
|
||||||
// }}
|
|
||||||
// onMouseUp={(event) => {
|
|
||||||
// event.stopPropagation();
|
|
||||||
// }}
|
|
||||||
// onDragOver={(event) => {
|
|
||||||
// event.preventDefault();
|
|
||||||
// }}
|
|
||||||
// onDragStart={(event) => {
|
|
||||||
// if (!slot) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// }
|
|
||||||
// event.dataTransfer.setData('silphius/item', i);
|
|
||||||
// onActivate(i);
|
|
||||||
// }}
|
|
||||||
// onDrop={(event) => {
|
|
||||||
// event.preventDefault();
|
|
||||||
// onActivate(i);
|
|
||||||
// }}
|
|
||||||
qty={slot?.qty}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.bag}
|
className={styles.bag}
|
||||||
style={isInventoryOpen ? {transition: 'none'} : {left: '-440px'}}
|
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}}
|
||||||
>
|
>
|
||||||
{Slots}
|
<Grid
|
||||||
|
color="rgba(02, 02, 28, 0.6)"
|
||||||
|
columns={10}
|
||||||
|
label="Bag"
|
||||||
|
onActivate={onActivate}
|
||||||
|
slots={slots}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,7 @@
|
||||||
.bag {
|
.bag {
|
||||||
align-self: left;
|
left: 20px;
|
||||||
--border: calc(var(--unit) * 3px);
|
opacity: 1;
|
||||||
background-color: rgba(02, 02, 28, 0.6);
|
|
||||||
border: var(--border) solid #444444;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
left: calc(var(--unit) * 20px);
|
|
||||||
line-height: 0;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(var(--unit) * 90px);
|
top: 74px;
|
||||||
transition: left 150ms;
|
transition: left 150ms, opacity 200ms;
|
||||||
max-width: 430.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slotWrapper {
|
|
||||||
border: var(--border) solid #999999;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
&:not(:nth-child(10n)) {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
&:not(:nth-last-of-type(-n+10)) {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import {memo, useEffect, useState} from 'react';
|
|
||||||
|
|
||||||
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
|
||||||
|
|
||||||
import styles from './damage.module.css';
|
|
||||||
|
|
||||||
const damageTypeMap = {
|
|
||||||
[DamageTypes.PAIN]: styles.pain,
|
|
||||||
[DamageTypes.HEALING]: styles.healing,
|
|
||||||
[DamageTypes.MANA]: styles.mana,
|
|
||||||
};
|
|
||||||
|
|
||||||
function Damage({
|
|
||||||
camera,
|
|
||||||
damage,
|
|
||||||
scale,
|
|
||||||
zIndex,
|
|
||||||
}) {
|
|
||||||
const [randomness] = useState({
|
|
||||||
radians: Math.random() * (Math.PI / 16) - (Math.PI / 32),
|
|
||||||
x: 1 * (Math.random() - 0.5),
|
|
||||||
y: Math.random(),
|
|
||||||
})
|
|
||||||
useEffect(() => {
|
|
||||||
const handle = setTimeout(() => {
|
|
||||||
damage.onClose();
|
|
||||||
}, 1_500);
|
|
||||||
return () => {
|
|
||||||
clearTimeout(handle);
|
|
||||||
};
|
|
||||||
}, [damage]);
|
|
||||||
const {amount, position} = damage;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.damage}
|
|
||||||
style={{
|
|
||||||
'--magnitude': Math.max(1, Math.floor(Math.log10(Math.abs(amount)))),
|
|
||||||
'--positionX': `${position.x * scale - camera.x}px`,
|
|
||||||
'--positionY': `${position.y * scale - camera.y}px`,
|
|
||||||
'--randomnessX': randomness.x,
|
|
||||||
'--randomnessY': randomness.y,
|
|
||||||
'--shimmer': damageTypeMap[damage.type],
|
|
||||||
rotate: `${randomness.radians}rad`,
|
|
||||||
zIndex,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>{Math.abs(amount)}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(Damage);
|
|
|
@ -13,11 +13,6 @@
|
||||||
inherits: false;
|
inherits: false;
|
||||||
syntax: '<number>';
|
syntax: '<number>';
|
||||||
}
|
}
|
||||||
@property --shimmer {
|
|
||||||
initial-value: '';
|
|
||||||
inherits: false;
|
|
||||||
syntax: '<custom-ident>';
|
|
||||||
}
|
|
||||||
@property --offsetX {
|
@property --offsetX {
|
||||||
initial-value: 0;
|
initial-value: 0;
|
||||||
inherits: false;
|
inherits: false;
|
||||||
|
@ -47,12 +42,6 @@
|
||||||
--scale: 0.35;
|
--scale: 0.35;
|
||||||
--background: hsl(var(--hue) 100% 12.5%);
|
--background: hsl(var(--hue) 100% 12.5%);
|
||||||
--foreground: hsl(var(--hue) 100% 50%);
|
--foreground: hsl(var(--hue) 100% 50%);
|
||||||
animation:
|
|
||||||
fade 1.5s linear forwards,
|
|
||||||
grow 1.5s ease-in forwards,
|
|
||||||
move 1.5s cubic-bezier(0.5, 1, 0.89, 1),
|
|
||||||
var(--shimmer) 300ms infinite cubic-bezier(0.87, 0, 0.13, 1)
|
|
||||||
;
|
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-size: calc(10px + (var(--magnitude) * 12px));
|
font-size: calc(10px + (var(--magnitude) * 12px));
|
||||||
opacity: var(--opacity);
|
opacity: var(--opacity);
|
||||||
|
@ -75,75 +64,8 @@
|
||||||
calc(-50% + (1px * var(--offsetY)) + var(--positionY))
|
calc(-50% + (1px * var(--offsetY)) + var(--positionY))
|
||||||
;
|
;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
will-change: color, scale, opacity, translate, transform;
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes move {
|
|
||||||
0% {
|
|
||||||
--offsetX: 0;
|
|
||||||
--offsetY: 0;
|
|
||||||
}
|
|
||||||
33%, 91.6% {
|
|
||||||
--offsetX: calc(80 * var(--randomnessX));
|
|
||||||
--offsetY: calc(-1 * (10 + var(--randomnessY) * 90));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade {
|
|
||||||
0% {
|
|
||||||
--opacity: 0.75;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
--opacity: 1;
|
|
||||||
}
|
|
||||||
91.6% {
|
|
||||||
--opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
--opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes grow {
|
|
||||||
0% {
|
|
||||||
--scale: 0.35;
|
|
||||||
}
|
|
||||||
33% {
|
|
||||||
--scale: 1;
|
|
||||||
}
|
|
||||||
45% {
|
|
||||||
--scale: 1;
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
--scale: 1.5;
|
|
||||||
}
|
|
||||||
45% {
|
|
||||||
--scale: 1;
|
|
||||||
}
|
|
||||||
91.6% {
|
|
||||||
--scale: 1;
|
|
||||||
}
|
|
||||||
95% {
|
|
||||||
--scale: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pain {
|
|
||||||
0% { --hue: 0 }
|
|
||||||
50% { --hue: 30 }
|
|
||||||
100% { --hue: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes healing {
|
|
||||||
0% { --hue: 120 }
|
|
||||||
50% { --hue: 90 }
|
|
||||||
100% { --hue: 120 }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mana {
|
|
||||||
0% { --hue: 220 }
|
|
||||||
50% { --hue: 215 }
|
|
||||||
100% { --hue: 220 }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,23 +1,172 @@
|
||||||
|
import {memo, useCallback, useRef} from 'react';
|
||||||
|
|
||||||
|
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
||||||
|
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
||||||
|
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
|
||||||
|
|
||||||
import styles from './damages.module.css';
|
import styles from './damages.module.css';
|
||||||
|
|
||||||
import Damage from './damage.jsx';
|
function damageHue(type) {
|
||||||
|
let hue;
|
||||||
export default function Damages({camera, damages, scale}) {
|
switch(type) {
|
||||||
const elements = [];
|
case DamageTypes.PAIN: {
|
||||||
for (const key in damages) {
|
hue = [0, 30];
|
||||||
elements.push(
|
break;
|
||||||
<Damage
|
}
|
||||||
camera={camera}
|
case DamageTypes.HEALING: {
|
||||||
damage={damages[key]}
|
hue = [90, 120];
|
||||||
key={key}
|
break;
|
||||||
scale={scale}
|
}
|
||||||
zIndex={key}
|
case DamageTypes.MANA: {
|
||||||
/>
|
hue = [215, 220];
|
||||||
);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (0 === elements.length) {
|
return hue;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return <div className={styles.damages}>{elements}</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDamageNode() {
|
||||||
|
const damage = document.createElement('div');
|
||||||
|
damage.classList.add(styles.damage);
|
||||||
|
damage.appendChild(document.createElement('p'));
|
||||||
|
return damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Damages({damages, scale}) {
|
||||||
|
const animations = useRef({});
|
||||||
|
const pool = useRef([]);
|
||||||
|
const damagesRef = useRef();
|
||||||
|
const frame = useCallback((elapsed) => {
|
||||||
|
if (!damagesRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (0 === pool.current.length) {
|
||||||
|
for (let i = 0; i < 512; ++i) {
|
||||||
|
const damage = createDamageNode();
|
||||||
|
damagesRef.current.appendChild(damage);
|
||||||
|
pool.current.push(damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const keys = Object.keys(animations.current);
|
||||||
|
for (const key of keys) {
|
||||||
|
const animation = animations.current[key];
|
||||||
|
if (!damages[key]) {
|
||||||
|
if (animation.element) {
|
||||||
|
if (pool.current.length < 512) {
|
||||||
|
pool.current.push(animation.element);
|
||||||
|
}
|
||||||
|
animation.element = undefined;
|
||||||
|
}
|
||||||
|
delete animations.current[key];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!animation.element) {
|
||||||
|
const {amount, position} = damages[key];
|
||||||
|
let damage;
|
||||||
|
if (pool.current.length > 0) {
|
||||||
|
damage = pool.current.pop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
damage = createDamageNode();
|
||||||
|
damagesRef.current.appendChild(damage);
|
||||||
|
}
|
||||||
|
const p = damage.querySelector('p');
|
||||||
|
p.style.scale = scale / 2;
|
||||||
|
p.innerText = Math.abs(amount);
|
||||||
|
damage.style.setProperty('--randomnessX', animation['--randomnessX']);
|
||||||
|
damage.style.setProperty('--randomnessY', animation['--randomnessY']);
|
||||||
|
damage.style.setProperty('rotate', animation['rotate']);
|
||||||
|
damage.style.setProperty('rotate', animation['rotate']);
|
||||||
|
damage.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount)))));
|
||||||
|
damage.style.setProperty('--positionX', `${position.x * scale}px`);
|
||||||
|
damage.style.setProperty('--positionY', `${position.y * scale}px`);
|
||||||
|
damage.style.setProperty('zIndex', key);
|
||||||
|
animation.element = damage;
|
||||||
|
}
|
||||||
|
animation.elapsed += elapsed;
|
||||||
|
animation.step += elapsed;
|
||||||
|
const offsetX = 20 * scale * animation['--randomnessX'];
|
||||||
|
const offsetY = -1 * (10 + animation['--randomnessY'] * 45 * scale);
|
||||||
|
// offset
|
||||||
|
if (animation.elapsed <= 0.5) {
|
||||||
|
animation['--offsetX'] = easeOutQuad(animation.elapsed, 0, offsetX, 0.5);
|
||||||
|
animation['--offsetY'] = easeOutQuad(animation.elapsed, 0, offsetY, 0.5);
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 1.375) {
|
||||||
|
animation['--offsetX'] = offsetX - easeOutQuad(animation.elapsed - 1.375, 0, offsetX, 0.125);
|
||||||
|
animation['--offsetY'] = offsetY - easeOutQuad(animation.elapsed - 1.375, 0, offsetY, 0.125);
|
||||||
|
}
|
||||||
|
// scale
|
||||||
|
if (animation.elapsed <= 0.5) {
|
||||||
|
animation['--scale'] = easeOutQuad(animation.elapsed, 0, 1, 0.5);
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 0.5 && animation.elapsed < 0.6) {
|
||||||
|
animation['--scale'] = linear(animation.elapsed - 0.5, 1, 0.5, 0.1);
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 0.6 && animation.elapsed < 0.675) {
|
||||||
|
animation['--scale'] = 1.5 - easeInQuint(animation.elapsed - 0.6, 1, 0.5, 0.075);
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 0.675 && animation.elapsed < 1.375) {
|
||||||
|
animation['--scale'] = 1;
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 1.375) {
|
||||||
|
animation['--scale'] = 1 - easeOutQuad(animation.elapsed - 1.375, 0, 1, 0.125);
|
||||||
|
}
|
||||||
|
// fade
|
||||||
|
if (animation.elapsed <= 0.375) {
|
||||||
|
animation['--opacity'] = linear(animation.elapsed, 0.75, 0.25, 0.375);
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 0.375 && animation.elapsed < 1.375) {
|
||||||
|
animation['--opacity'] = 1;
|
||||||
|
}
|
||||||
|
else if (animation.elapsed > 1.375) {
|
||||||
|
animation['--opacity'] = 1 - linear(animation.elapsed - 1.375, 0, 1, 0.125);
|
||||||
|
}
|
||||||
|
// hue
|
||||||
|
const h = Math.abs((animation.elapsed % 0.3) - 0.15) / 0.15;
|
||||||
|
animation['--hue'] = easeInOutExpo(h, animation.hue[0], animation.hue[1], 1);
|
||||||
|
const step = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
|
||||||
|
if (animation.step > step) {
|
||||||
|
animation.step = animation.step % step;
|
||||||
|
animation.element.style.setProperty('--hue', animation['--hue']);
|
||||||
|
animation.element.style.setProperty('--opacity', animation['--opacity']);
|
||||||
|
animation.element.style.setProperty('--offsetX', animation['--offsetX']);
|
||||||
|
animation.element.style.setProperty('--offsetY', animation['--offsetY']);
|
||||||
|
animation.element.style.setProperty('--scale', animation['--scale']);
|
||||||
|
}
|
||||||
|
if (animation.elapsed > 1.5) {
|
||||||
|
if (pool.current.length < 512) {
|
||||||
|
pool.current.push(animation.element);
|
||||||
|
}
|
||||||
|
animation.element.style.setProperty('--opacity', 0);
|
||||||
|
animation.element = undefined;
|
||||||
|
damages[key].onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [damages, scale]);
|
||||||
|
useAnimationFrame(frame);
|
||||||
|
for (const key in damages) {
|
||||||
|
if (!animations.current[key]) {
|
||||||
|
animations.current[key] = {
|
||||||
|
elapsed: 0,
|
||||||
|
hue: damageHue(damages[key].type),
|
||||||
|
rotate: `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`,
|
||||||
|
step: 0,
|
||||||
|
'--scale': 0.35,
|
||||||
|
'--opacity': 0.75,
|
||||||
|
'--offsetX': 0,
|
||||||
|
'--offsetY': 0,
|
||||||
|
'--randomnessX': 1 * (Math.random() - 0.5),
|
||||||
|
'--randomnessY': Math.random(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.damages}
|
||||||
|
ref={damagesRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Damages);
|
||||||
|
|
|
@ -1,3 +1,75 @@
|
||||||
|
@property --hue {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --opacity {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --scale {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --offsetX {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --offsetY {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --randomnessX {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
@property --randomnessY {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage {
|
||||||
|
--hue: 0;
|
||||||
|
--opacity: 0.75;
|
||||||
|
--randomnessX: 0;
|
||||||
|
--randomnessY: 0;
|
||||||
|
--scale: 0.35;
|
||||||
|
--background: hsl(var(--hue) 100% 12.5%);
|
||||||
|
--foreground: hsl(var(--hue) 100% 50%);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: calc(10px + (var(--magnitude) * 12px));
|
||||||
|
opacity: var(--opacity);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow:
|
||||||
|
0px -1px 0px var(--background),
|
||||||
|
1px 0px 0px var(--background),
|
||||||
|
0px 1px 0px var(--background),
|
||||||
|
-1px 0px 0px var(--background),
|
||||||
|
0px -2px 0px var(--background),
|
||||||
|
2px 0px 0px var(--background),
|
||||||
|
0px 2px 0px var(--background),
|
||||||
|
-2px 0px 0px var(--background)
|
||||||
|
;
|
||||||
|
scale: var(--scale);
|
||||||
|
translate:
|
||||||
|
calc(-50% + (1px * var(--offsetX)) + var(--positionX))
|
||||||
|
calc(-50% + (1px * var(--offsetY)) + var(--positionY))
|
||||||
|
;
|
||||||
|
user-select: none;
|
||||||
|
will-change: color, scale, opacity, translate, transform;
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.damages {
|
.damages {
|
||||||
font-family: Joystix, 'Courier New', Courier, monospace;
|
font-family: Joystix, 'Courier New', Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,21 +26,21 @@ export default function DialogueCaret({
|
||||||
};
|
};
|
||||||
const left = Math.max(
|
const left = Math.max(
|
||||||
Math.min(
|
Math.min(
|
||||||
position.x * scale - camera.x,
|
position.x * scale,
|
||||||
RESOLUTION.x - bounds.x * scale - 16,
|
RESOLUTION.x - bounds.x * scale - 16 + camera.x,
|
||||||
),
|
),
|
||||||
bounds.x * scale + 16,
|
bounds.x * scale + 16 + camera.x,
|
||||||
);
|
);
|
||||||
const top = Math.max(
|
const top = Math.max(
|
||||||
Math.min(
|
Math.min(
|
||||||
position.y * scale - camera.y,
|
position.y * scale - dimensions.h / 2,
|
||||||
RESOLUTION.y - bounds.y * scale - 16,
|
RESOLUTION.y - bounds.y * scale - 16 + camera.y,
|
||||||
),
|
),
|
||||||
bounds.y * scale + 88,
|
bounds.y * scale + 16 + camera.y,
|
||||||
);
|
);
|
||||||
const offsetPosition = {
|
const offsetPosition = {
|
||||||
x: ((position.x * scale - camera.x) - left) / scale,
|
x: ((position.x * scale) - left) / scale,
|
||||||
y: ((position.y * scale - camera.y) - top) / scale,
|
y: ((position.y * scale) - top) / scale,
|
||||||
};
|
};
|
||||||
const difference = {
|
const difference = {
|
||||||
x: origin.x - position.x + offsetPosition.x,
|
x: origin.x - position.x + offsetPosition.x,
|
||||||
|
|
|
@ -87,25 +87,27 @@ export default function Dialogue({
|
||||||
};
|
};
|
||||||
const left = Math.max(
|
const left = Math.max(
|
||||||
Math.min(
|
Math.min(
|
||||||
position.x * scale - camera.x,
|
position.x * scale,
|
||||||
RESOLUTION.x - bounds.x * scale - 16,
|
RESOLUTION.x - bounds.x * scale - 16 + camera.x,
|
||||||
),
|
),
|
||||||
bounds.x * scale + 16,
|
bounds.x * scale + 16 + camera.x,
|
||||||
);
|
);
|
||||||
const top = Math.max(
|
const top = Math.max(
|
||||||
Math.min(
|
Math.min(
|
||||||
position.y * scale - camera.y,
|
position.y * scale - dimensions.h / 2,
|
||||||
RESOLUTION.y - bounds.y * scale - 16,
|
RESOLUTION.y - bounds.y * scale - 16 + camera.y,
|
||||||
),
|
),
|
||||||
bounds.y * scale + 16,
|
bounds.y * scale + 16 + camera.y,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.dialogue}
|
className={styles.dialogue}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
left: `${left}px`,
|
translate: `
|
||||||
top: `${top}px`,
|
${left}px
|
||||||
|
${top}px
|
||||||
|
`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogueCaret
|
<DialogueCaret
|
||||||
|
@ -113,7 +115,6 @@ export default function Dialogue({
|
||||||
dialogue={dialogue}
|
dialogue={dialogue}
|
||||||
dimensions={dimensions}
|
dimensions={dimensions}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<p className={styles.letters}>
|
<p className={styles.letters}>
|
||||||
{localRender(caret, radians)}
|
{localRender(caret, radians)}
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
border: solid 3px white;
|
border: solid 3px white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
color: white;
|
||||||
|
left: 0;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-right: -66%;
|
margin-right: -66%;
|
||||||
text-shadow:
|
text-shadow:
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
0px 1px 0px black,
|
0px 1px 0px black,
|
||||||
-1px 0px 0px black
|
-1px 0px 0px black
|
||||||
;
|
;
|
||||||
|
top: 0;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
max-width: 66%;
|
max-width: 66%;
|
||||||
|
|
|
@ -25,14 +25,13 @@ export default function Dom({children}) {
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', onResize);
|
window.removeEventListener('resize', onResize);
|
||||||
}
|
}
|
||||||
});
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={styles.dom} ref={ref}>
|
<div className={styles.dom} ref={ref}>
|
||||||
{scale > 0 && (
|
{scale > 0 && (
|
||||||
<style>{`
|
<style>{`
|
||||||
.${styles.dom}{
|
.${styles.dom}{
|
||||||
--scale: ${scale};
|
--scale: ${scale};
|
||||||
--unit: calc(${RESOLUTION.x} / 1000);
|
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default function Entities({
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
const [damages, setDamages] = useState({});
|
const [damages, setDamages] = useState({});
|
||||||
|
const [pendingDamage] = useState({accumulated: [], handle: undefined});
|
||||||
usePacket('EcsChange', async () => {
|
usePacket('EcsChange', async () => {
|
||||||
setEntities({});
|
setEntities({});
|
||||||
}, [setEntities]);
|
}, [setEntities]);
|
||||||
|
@ -93,17 +94,31 @@ export default function Entities({
|
||||||
const {damage} = update.Vulnerable || {};
|
const {damage} = update.Vulnerable || {};
|
||||||
if (damage) {
|
if (damage) {
|
||||||
for (const key in damage) {
|
for (const key in damage) {
|
||||||
const composite = [id, key].join('-');
|
|
||||||
damage[key].onClose = () => {
|
damage[key].onClose = () => {
|
||||||
setDamages((damages) => {
|
setDamages((damages) => {
|
||||||
const {[composite]: _, ...rest} = damages; // eslint-disable-line no-unused-vars
|
const {[key]: _, ...rest} = damages; // eslint-disable-line no-unused-vars
|
||||||
return rest;
|
return rest;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
setDamages((damages) => ({
|
}
|
||||||
...damages,
|
pendingDamage.accumulated.push(damage);
|
||||||
[composite]: damage[key],
|
if (!pendingDamage.handle) {
|
||||||
}));
|
pendingDamage.handle = setTimeout(() => {
|
||||||
|
const update = {};
|
||||||
|
for (const damage of pendingDamage.accumulated) {
|
||||||
|
for (const key in damage) {
|
||||||
|
update[key] = damage[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pendingDamage.accumulated.length = 0;
|
||||||
|
setDamages((damages) => {
|
||||||
|
return {
|
||||||
|
...damages,
|
||||||
|
...update,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pendingDamage.handle = undefined;
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,13 +144,19 @@ export default function Entities({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
|
style={{
|
||||||
|
translate: `
|
||||||
|
calc(-1px * ${camera.x})
|
||||||
|
calc(-1px * ${camera.y})
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{renderables}
|
{renderables}
|
||||||
<Damages
|
<Damages
|
||||||
camera={camera}
|
|
||||||
damages={damages}
|
damages={damages}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
27
app/react/components/dom/external.jsx
Normal file
27
app/react/components/dom/external.jsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import styles from './external.module.css';
|
||||||
|
|
||||||
|
import Grid from './grid.jsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External inventory.
|
||||||
|
*/
|
||||||
|
export default function External({
|
||||||
|
isInventoryOpen,
|
||||||
|
onActivate,
|
||||||
|
slots,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.external}
|
||||||
|
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, top: '450px'}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
color="rgba(57, 02, 02, 0.6)"
|
||||||
|
columns={10}
|
||||||
|
label="Chest"
|
||||||
|
onActivate={onActivate}
|
||||||
|
slots={slots}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
7
app/react/components/dom/external.module.css
Normal file
7
app/react/components/dom/external.module.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.external {
|
||||||
|
left: 20px;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 274px;
|
||||||
|
transition: top 150ms, opacity 200ms;
|
||||||
|
}
|
69
app/react/components/dom/grid.jsx
Normal file
69
app/react/components/dom/grid.jsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import styles from './grid.module.css';
|
||||||
|
import Slot from './slot.jsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory grid.
|
||||||
|
*/
|
||||||
|
export default function Grid({
|
||||||
|
active = -1,
|
||||||
|
color,
|
||||||
|
columns,
|
||||||
|
label,
|
||||||
|
onActivate,
|
||||||
|
slots,
|
||||||
|
}) {
|
||||||
|
const Slots = slots.map((slot, i) => (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
[styles.slot, active === i && styles.active]
|
||||||
|
.filter(Boolean).join(' ')
|
||||||
|
}
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<Slot
|
||||||
|
icon={slot?.icon}
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
onActivate(i)
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onMouseUp={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onDragOver={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
onDragStart={(event) => {
|
||||||
|
if (!slot) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
event.dataTransfer.setData('silphius/item', i);
|
||||||
|
onActivate(i);
|
||||||
|
}}
|
||||||
|
onDrop={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onActivate(i);
|
||||||
|
}}
|
||||||
|
qty={slot?.qty}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<div className={styles.gridWrapper}>
|
||||||
|
<p className={styles.label}> {label} </p>
|
||||||
|
<div
|
||||||
|
className={styles.grid}
|
||||||
|
style={{
|
||||||
|
'--color': color,
|
||||||
|
'--columns': columns,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles.innerGrid}
|
||||||
|
>
|
||||||
|
{Slots}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
45
app/react/components/dom/grid.module.css
Normal file
45
app/react/components/dom/grid.module.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.grid {
|
||||||
|
--border: 2.5px;
|
||||||
|
border: var(--border) solid #444444;
|
||||||
|
line-height: 0;
|
||||||
|
opacity: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.innerGrid {
|
||||||
|
background-color: var(--color);
|
||||||
|
border-bottom: var(--border) solid #999999;
|
||||||
|
border-right: var(--border) solid #999999;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--columns), 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow:
|
||||||
|
0px -1px 0px black,
|
||||||
|
1px 0px 0px black,
|
||||||
|
0px 1px 0px black,
|
||||||
|
-1px 0px 0px black
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
border-left: var(--border) solid #999999;
|
||||||
|
border-top: var(--border) solid #999999;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
border: var(--border) solid yellow;
|
||||||
|
margin-bottom: calc(-1 * var(--border));
|
||||||
|
margin-right: calc(-1 * var(--border));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import styles from './hotbar.module.css';
|
import styles from './hotbar.module.css';
|
||||||
import Slot from './slot.jsx';
|
import gridStyles from './grid.module.css';
|
||||||
|
|
||||||
|
import Grid from './grid.jsx';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hotbar. 10 slots of inventory with an active selection.
|
* The hotbar. 10 slots of inventory with an active selection.
|
||||||
|
@ -10,48 +12,24 @@ export default function Hotbar({
|
||||||
onActivate,
|
onActivate,
|
||||||
slots,
|
slots,
|
||||||
}) {
|
}) {
|
||||||
const Slots = slots.map((slot, i) => (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
[styles.slotWrapper, active === i && styles.active]
|
|
||||||
.filter(Boolean).join(' ')
|
|
||||||
}
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
<Slot
|
|
||||||
icon={slot?.icon}
|
|
||||||
onMouseDown={(event) => {
|
|
||||||
onActivate(i)
|
|
||||||
event.stopPropagation();
|
|
||||||
}}
|
|
||||||
onMouseUp={(event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
}}
|
|
||||||
onDragOver={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
onDragStart={(event) => {
|
|
||||||
if (!slot) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
event.dataTransfer.setData('silphius/item', i);
|
|
||||||
onActivate(i);
|
|
||||||
}}
|
|
||||||
onDrop={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
onActivate(i);
|
|
||||||
}}
|
|
||||||
qty={slot?.qty}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.hotbar}
|
className={styles.hotbar}
|
||||||
style={hotbarIsHidden ? {top: '-50px'} : {transition: 'none'}}
|
style={hotbarIsHidden ? {opacity: 0, top: '-50px'} : {transition: 'opacity 50ms'}}
|
||||||
>
|
>
|
||||||
<p className={styles.label}>{slots[active] && slots[active].label}</p>
|
<style>{`
|
||||||
{Slots}
|
.${styles.hotbar} .${gridStyles.label} {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<Grid
|
||||||
|
active={active}
|
||||||
|
color="rgba(02, 02, 57, 0.6)"
|
||||||
|
columns={10}
|
||||||
|
label={slots[active] && slots[active].label}
|
||||||
|
onActivate={onActivate}
|
||||||
|
slots={slots}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,7 @@
|
||||||
.hotbar {
|
.hotbar {
|
||||||
align-self: left;
|
left: 20px;
|
||||||
--border: calc(var(--unit) * 3px);
|
opacity: 1;
|
||||||
background-color: rgba(02, 02, 57, 0.6);
|
|
||||||
border: var(--border) solid #444444;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
left: calc(var(--unit) * 20px);
|
|
||||||
line-height: 0;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(var(--unit) * 20px);
|
top: 4px;
|
||||||
transition: top 150ms;
|
transition: top 150ms, opacity 200ms;
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
background-color: transparent;
|
|
||||||
color: white;
|
|
||||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
|
||||||
left: 50%;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
text-shadow:
|
|
||||||
0px -1px 0px black,
|
|
||||||
1px 0px 0px black,
|
|
||||||
0px 1px 0px black,
|
|
||||||
-1px 0px 0px black
|
|
||||||
;
|
|
||||||
top: -17.5px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slotWrapper {
|
|
||||||
border: var(--border) solid #999999;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&.active + .slotWrapper {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-right: var(--border) solid #999999;
|
|
||||||
border-color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.slot {
|
.slot {
|
||||||
--size: calc(var(--unit) * 50px);
|
--size: 40px;
|
||||||
--space: calc(var(--unit) * 10px);
|
--space: 7px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
36
app/react/components/particle-worker.js
Normal file
36
app/react/components/particle-worker.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import Emitter from '@/particles/emitter.js';
|
||||||
|
import createEcs from '@/server/create/ecs.js';
|
||||||
|
|
||||||
|
import ClientEcs from './client-ecs.js';
|
||||||
|
|
||||||
|
const ecs = createEcs(ClientEcs);
|
||||||
|
ecs.$$caret = Math.pow(2, 31);
|
||||||
|
|
||||||
|
const emitter = new Emitter(ecs);
|
||||||
|
|
||||||
|
addEventListener('message', (particle) => {
|
||||||
|
if (!ecs.get(1)) {
|
||||||
|
ecs.createManySpecific([[1, particle.data]]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emitter.emit(particle.data)
|
||||||
|
.onEnd(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
let last = Date.now();
|
||||||
|
function tick() {
|
||||||
|
const now = Date.now();
|
||||||
|
const elapsed = (now - last) / 1000;
|
||||||
|
last = now;
|
||||||
|
if (ecs.get(1)) {
|
||||||
|
ecs.tick(elapsed);
|
||||||
|
emitter.tick(elapsed);
|
||||||
|
if ('1' in ecs.diff) {
|
||||||
|
delete ecs.diff['1'];
|
||||||
|
}
|
||||||
|
postMessage(ecs.diff);
|
||||||
|
ecs.setClean();
|
||||||
|
}
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(tick);
|
|
@ -1,10 +1,8 @@
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {useState} from 'react';
|
||||||
|
|
||||||
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
import {useMainEntity} from '@/react/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
// import {useRadians} from '@/react/context/radians.js';
|
|
||||||
// import {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';
|
||||||
|
@ -12,79 +10,14 @@ import TargetingGrid from './targeting-grid.jsx';
|
||||||
import TileLayer from './tile-layer.jsx';
|
import TileLayer from './tile-layer.jsx';
|
||||||
import Water from './water.jsx';
|
import Water from './water.jsx';
|
||||||
|
|
||||||
const NIGHTNESS = 0.1;
|
export default function Ecs({camera, monopolizers, particleWorker, scale}) {
|
||||||
|
|
||||||
function calculateDarkness(hour) {
|
|
||||||
let darkness = 0;
|
|
||||||
if (hour >= 21 || hour < 4) {
|
|
||||||
darkness = 0.8;
|
|
||||||
}
|
|
||||||
if (hour >= 4 && hour < 7) {
|
|
||||||
darkness = 0.8 * ((7 - hour) / 3);
|
|
||||||
}
|
|
||||||
if (hour >= 18 && hour < 21) {
|
|
||||||
darkness = 0.8 * ((3 - (21 - hour)) / 3);
|
|
||||||
}
|
|
||||||
return Math.floor(darkness * 1000) / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [filters, setFilters] = useState([]);
|
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
const [layers, setLayers] = useState([]);
|
const [layers, setLayers] = useState([]);
|
||||||
const [hour, setHour] = useState(10);
|
const [hour, setHour] = useState(10);
|
||||||
const [night, setNight] = useState();
|
|
||||||
const [projected, setProjected] = useState([]);
|
const [projected, setProjected] = useState([]);
|
||||||
const [position, setPosition] = useState({x: 0, y: 0});
|
const [position, setPosition] = useState({x: 0, y: 0});
|
||||||
const [water, setWater] = useState();
|
const [water, setWater] = useState();
|
||||||
// const radians = useRadians();
|
|
||||||
// const [sine, setSine] = useState();
|
|
||||||
// useEffect(() => {
|
|
||||||
// async function buildSineFilter() {
|
|
||||||
// const {default: SineFilter} = await import('./filters/horizontal-sine.js');
|
|
||||||
// const sine = new SineFilter();
|
|
||||||
// sine.frequency = 1;
|
|
||||||
// sine.magnitude = 3;
|
|
||||||
// setSine(sine);
|
|
||||||
// }
|
|
||||||
// buildSineFilter();
|
|
||||||
// }, []);
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!sine) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const r = (radians / 8) % TAU;
|
|
||||||
// sine.offset = 6 * (camera.y + r);
|
|
||||||
// sine.magnitude = 2 * (r > Math.PI ? TAU - r : r);
|
|
||||||
// }, [camera, radians, scale, sine]);
|
|
||||||
useEffect(() => {
|
|
||||||
async function buildNightFilter() {
|
|
||||||
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
|
||||||
class NightFilter extends ColorMatrixFilter {
|
|
||||||
setIntensity(intensity) {
|
|
||||||
const double = NIGHTNESS * 2;
|
|
||||||
const half = NIGHTNESS / 2;
|
|
||||||
const redDown = 1 - (intensity * (1 + double));
|
|
||||||
const blueUp = 1 - (intensity * (1 - half));
|
|
||||||
const scale = intensity * NIGHTNESS;
|
|
||||||
this.uniforms.m = [
|
|
||||||
redDown, -scale, 0, 0, 0,
|
|
||||||
-scale, (1 - intensity), scale, 0, 0,
|
|
||||||
0, scale, blueUp, 0, 0,
|
|
||||||
0, 0, 0, 1, 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNight(new NightFilter());
|
|
||||||
}
|
|
||||||
buildNightFilter();
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
if (night) {
|
|
||||||
night.setIntensity(calculateDarkness(hour));
|
|
||||||
}
|
|
||||||
}, [hour, night]);
|
|
||||||
useEcsTick((payload) => {
|
useEcsTick((payload) => {
|
||||||
const entity = ecs.get(mainEntity);
|
const entity = ecs.get(mainEntity);
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
|
@ -111,28 +44,15 @@ export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
||||||
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.quantize(4)));
|
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.quantize(4)));
|
||||||
}
|
}
|
||||||
}, [ecs, mainEntity, scale]);
|
}, [ecs, mainEntity, scale]);
|
||||||
useEffect(() => {
|
|
||||||
setFilters(
|
|
||||||
applyFilters
|
|
||||||
? [
|
|
||||||
...(false && night ? [night] : []),
|
|
||||||
// ...(sine ? [sine] : []),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
);
|
|
||||||
}, [applyFilters, night])
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
scale={scale}
|
scale={scale}
|
||||||
x={-camera.x}
|
x={-camera.x}
|
||||||
y={-camera.y}
|
y={-camera.y}
|
||||||
>
|
>
|
||||||
<Container
|
<Container>
|
||||||
filters={filters}
|
|
||||||
>
|
|
||||||
{layers.map((layer, i) => (
|
{layers.map((layer, i) => (
|
||||||
<TileLayer
|
<TileLayer
|
||||||
filters={filters}
|
|
||||||
key={i}
|
key={i}
|
||||||
tileLayer={layer}
|
tileLayer={layer}
|
||||||
/>
|
/>
|
||||||
|
@ -152,8 +72,8 @@ export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Entities
|
<Entities
|
||||||
filters={filters}
|
|
||||||
monopolizers={monopolizers}
|
monopolizers={monopolizers}
|
||||||
|
particleWorker={particleWorker}
|
||||||
/>
|
/>
|
||||||
{projected?.length > 0 && layers[0] && (
|
{projected?.length > 0 && layers[0] && (
|
||||||
<TargetingGhost
|
<TargetingGhost
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {AdjustmentFilter} from '@pixi/filter-adjustment';
|
import {AdjustmentFilter} from '@pixi/filter-adjustment';
|
||||||
import {GlowFilter} from '@pixi/filter-glow';
|
import {GlowFilter} from '@pixi/filter-glow';
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {usePacket} from '@/react/context/client.js';
|
import {usePacket} from '@/react/context/client.js';
|
||||||
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
|
@ -10,7 +10,7 @@ import {useRadians} from '@/react/context/radians.js';
|
||||||
|
|
||||||
import Entity from './entity.jsx';
|
import Entity from './entity.jsx';
|
||||||
|
|
||||||
export default function Entities({filters, monopolizers}) {
|
export default function Entities({monopolizers, particleWorker}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
|
@ -20,6 +20,37 @@ export default function Entities({filters, monopolizers}) {
|
||||||
new AdjustmentFilter(),
|
new AdjustmentFilter(),
|
||||||
new GlowFilter({color: 0x0}),
|
new GlowFilter({color: 0x0}),
|
||||||
]);
|
]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ecs || !particleWorker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
async function onMessage(diff) {
|
||||||
|
await ecs.apply(diff.data);
|
||||||
|
const deleted = {};
|
||||||
|
const updated = {};
|
||||||
|
for (const id in diff.data) {
|
||||||
|
if (!diff.data[id]) {
|
||||||
|
deleted[id] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updated[id] = ecs.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntities((entities) => {
|
||||||
|
for (const id in deleted) {
|
||||||
|
delete entities[id];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...entities,
|
||||||
|
...updated,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
particleWorker.addEventListener('message', onMessage);
|
||||||
|
return () => {
|
||||||
|
particleWorker.removeEventListener('message', onMessage);
|
||||||
|
};
|
||||||
|
}, [ecs, particleWorker]);
|
||||||
const pulse = (Math.cos(radians / 4) + 1) * 0.5;
|
const pulse = (Math.cos(radians / 4) + 1) * 0.5;
|
||||||
interactionFilters[0].brightness = (pulse * 0.75) + 1;
|
interactionFilters[0].brightness = (pulse * 0.75) + 1;
|
||||||
interactionFilters[1].outerStrength = pulse * 0.5;
|
interactionFilters[1].outerStrength = pulse * 0.5;
|
||||||
|
@ -43,10 +74,9 @@ export default function Entities({filters, monopolizers}) {
|
||||||
}
|
}
|
||||||
updating[id] = ecs.get(id);
|
updating[id] = ecs.get(id);
|
||||||
if (update.Emitter?.emit) {
|
if (update.Emitter?.emit) {
|
||||||
updating[id].Emitter.emitting = {
|
for (const id in update.Emitter.emit) {
|
||||||
...updating[id].Emitter.emitting,
|
particleWorker?.postMessage(update.Emitter.emit[id]);
|
||||||
...update.Emitter.emit,
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setEntities((entities) => {
|
setEntities((entities) => {
|
||||||
|
@ -58,7 +88,7 @@ export default function Entities({filters, monopolizers}) {
|
||||||
...updating,
|
...updating,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [ecs]);
|
}, [ecs, particleWorker]);
|
||||||
useEcsTick(() => {
|
useEcsTick(() => {
|
||||||
if (!ecs) {
|
if (!ecs) {
|
||||||
return;
|
return;
|
||||||
|
@ -81,7 +111,6 @@ export default function Entities({filters, monopolizers}) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
filters={filters}
|
|
||||||
sortableChildren
|
sortableChildren
|
||||||
>
|
>
|
||||||
{renderables}
|
{renderables}
|
||||||
|
|
|
@ -4,9 +4,8 @@ import {memo, useCallback} from 'react';
|
||||||
import {useDebug} from '@/react/context/debug.js';
|
import {useDebug} from '@/react/context/debug.js';
|
||||||
import {useMainEntity} from '@/react/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
|
|
||||||
import Emitter from './emitter.jsx';
|
import Light from './light.jsx';
|
||||||
// import Light from './light.jsx';
|
import SpriteComponent from './sprite.jsx';
|
||||||
import Sprite from './sprite.jsx';
|
|
||||||
|
|
||||||
function Aabb({color, width = 0.5, x0, y0, x1, y1, ...rest}) {
|
function Aabb({color, width = 0.5, x0, y0, x1, y1, ...rest}) {
|
||||||
const draw = useCallback((g) => {
|
const draw = useCallback((g) => {
|
||||||
|
@ -23,7 +22,7 @@ function Aabb({color, width = 0.5, x0, y0, x1, y1, ...rest}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Crosshair({x, y}) {
|
function Crosshair() {
|
||||||
const draw = useCallback((g) => {
|
const draw = useCallback((g) => {
|
||||||
g.clear();
|
g.clear();
|
||||||
g.lineStyle(1, 0x000000);
|
g.lineStyle(1, 0x000000);
|
||||||
|
@ -42,7 +41,7 @@ function Crosshair({x, y}) {
|
||||||
g.drawCircle(0, 0, 3);
|
g.drawCircle(0, 0, 3);
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Graphics draw={draw} x={x} y={y} />
|
<Graphics draw={draw} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,30 +51,39 @@ function Entity({entity, ...rest}) {
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const {Direction, id, Sprite} = entity;
|
||||||
return (
|
return (
|
||||||
<Container
|
<>
|
||||||
zIndex={entity.Position?.y || 0}
|
<Container
|
||||||
>
|
x={entity.Position.x}
|
||||||
{entity.Sprite && (
|
y={entity.Position.y}
|
||||||
<Sprite
|
zIndex={entity.Position?.y || 0}
|
||||||
entity={entity}
|
>
|
||||||
{...rest}
|
{entity.Sprite && (
|
||||||
/>
|
<SpriteComponent
|
||||||
)}
|
alpha={Sprite.alpha}
|
||||||
{entity.Emitter && (
|
anchor={Sprite.anchor}
|
||||||
<Emitter
|
animation={Sprite.animation}
|
||||||
entity={entity}
|
direction={Direction?.direction}
|
||||||
/>
|
frame={Sprite.frame}
|
||||||
)}
|
id={id}
|
||||||
{/* {entity.Light && (
|
scale={Sprite.scale}
|
||||||
<Light
|
rotates={Sprite.rotates}
|
||||||
x={entity.Position.x}
|
rotation={Sprite.rotation}
|
||||||
y={entity.Position.y}
|
source={Sprite.source}
|
||||||
/>
|
tint={Sprite.tint}
|
||||||
)} */}
|
{...rest}
|
||||||
{debug && entity.Position && (
|
/>
|
||||||
<Crosshair x={entity.Position.x} y={entity.Position.y} />
|
)}
|
||||||
)}
|
{entity.Light && (
|
||||||
|
<Light
|
||||||
|
brightness={entity.Light.brightness}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{debug && entity.Position && (
|
||||||
|
<Crosshair />
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
{debug && (
|
{debug && (
|
||||||
<Aabb
|
<Aabb
|
||||||
color={0xff00ff}
|
color={0xff00ff}
|
||||||
|
@ -105,7 +113,7 @@ function Entity({entity, ...rest}) {
|
||||||
{...entity.Interacts.aabb()}
|
{...entity.Interacts.aabb()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@ import {PixiComponent} from '@pixi/react';
|
||||||
import {PointLight} from './lights.js';
|
import {PointLight} from './lights.js';
|
||||||
|
|
||||||
const LightInternal = PixiComponent('Light', {
|
const LightInternal = PixiComponent('Light', {
|
||||||
create({x, y}) {
|
create({brightness}) {
|
||||||
const light = new PointLight(0xffffff - 0x2244cc, 1);
|
const light = new PointLight(
|
||||||
light.position.set(x, y);
|
0xffffff - 0x2244cc,
|
||||||
|
brightness,
|
||||||
|
);
|
||||||
// light.shader.program.fragmentSrc = light.shader.program.fragmentSrc.replace(
|
// light.shader.program.fragmentSrc = light.shader.program.fragmentSrc.replace(
|
||||||
// 'float D = length(lightVector)',
|
// 'float D = length(lightVector)',
|
||||||
// 'float D = length(lightVector) / 1.0',
|
// 'float D = length(lightVector) / 1.0',
|
||||||
|
@ -16,20 +18,16 @@ const LightInternal = PixiComponent('Light', {
|
||||||
// );
|
// );
|
||||||
// light.falloff = [0.5, 5, 50];
|
// light.falloff = [0.5, 5, 50];
|
||||||
// light.falloff = light.falloff.map((n, i) => n / (2 + i));
|
// light.falloff = light.falloff.map((n, i) => n / (2 + i));
|
||||||
// light.parentGroup = entityLighting.lightGroup;
|
// light.parentGroup = deferredLighting.lightGroup;
|
||||||
// delete light.parentGroup;
|
// delete light.parentGroup;
|
||||||
return light;
|
return light;
|
||||||
},
|
},
|
||||||
applyProps(light, oldProps, {x, y}) {
|
|
||||||
light.position.set(x, y);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Light({x, y}) {
|
export default function Light({brightness}) {
|
||||||
return (
|
return (
|
||||||
<LightInternal
|
<LightInternal
|
||||||
x={x}
|
brightness={brightness}
|
||||||
y={y}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const Stage = ({children, ...props}) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Pixi({applyFilters, camera, monopolizers, scale}) {
|
export default function Pixi({camera, monopolizers, particleWorker, scale}) {
|
||||||
return (
|
return (
|
||||||
<Stage
|
<Stage
|
||||||
className={styles.stage}
|
className={styles.stage}
|
||||||
|
@ -68,9 +68,9 @@ export default function Pixi({applyFilters, camera, monopolizers, scale}) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ecs
|
<Ecs
|
||||||
applyFilters={applyFilters}
|
|
||||||
camera={camera}
|
camera={camera}
|
||||||
monopolizers={monopolizers}
|
monopolizers={monopolizers}
|
||||||
|
particleWorker={particleWorker}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
/>
|
/>
|
||||||
</Stage>
|
</Stage>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Sprite as PixiSprite} from '@pixi/react';
|
import {Sprite as PixiSprite} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {memo, useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {useAsset} from '@/react/context/assets.js';
|
import {useAsset} from '@/react/context/assets.js';
|
||||||
|
|
||||||
|
@ -22,11 +22,23 @@ function textureFromAsset(asset, animation, frame) {
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sprite({entity, ...rest}) {
|
function Sprite(props) {
|
||||||
|
const {
|
||||||
|
alpha,
|
||||||
|
anchor,
|
||||||
|
animation,
|
||||||
|
direction,
|
||||||
|
frame,
|
||||||
|
scale,
|
||||||
|
rotates,
|
||||||
|
rotation,
|
||||||
|
source,
|
||||||
|
tint,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
const [mounted, setMounted] = useState();
|
const [mounted, setMounted] = useState();
|
||||||
const [normals, setNormals] = useState();
|
const [normals, setNormals] = useState();
|
||||||
const [normalsMounted, setNormalsMounted] = useState();
|
const [normalsMounted, setNormalsMounted] = useState();
|
||||||
const {alpha, anchor, animation, frame, scale, rotates, rotation, source} = entity.Sprite;
|
|
||||||
const asset = useAsset(source);
|
const asset = useAsset(source);
|
||||||
const normalsAsset = useAsset(normals);
|
const normalsAsset = useAsset(normals);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -65,11 +77,10 @@ export default function Sprite({entity, ...rest}) {
|
||||||
alpha={alpha}
|
alpha={alpha}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
ref={setMounted}
|
ref={setMounted}
|
||||||
{...(rotates ? {rotation: entity.Direction.direction + rotation} : {})}
|
{...(rotates ? {rotation: direction + rotation} : {})}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
texture={texture}
|
texture={texture}
|
||||||
x={Math.round(entity.Position.x)}
|
{...(0 !== tint ? {tint} : {})}
|
||||||
y={Math.round(entity.Position.y)}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -78,14 +89,14 @@ export default function Sprite({entity, ...rest}) {
|
||||||
alpha={alpha}
|
alpha={alpha}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
ref={setNormalsMounted}
|
ref={setNormalsMounted}
|
||||||
{...(rotates ? {rotation: entity.Direction.direction + rotation} : {})}
|
{...(rotates ? {rotation: direction + rotation} : {})}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
texture={normalsTexture}
|
texture={normalsTexture}
|
||||||
x={Math.round(entity.Position.x)}
|
|
||||||
y={Math.round(entity.Position.y)}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default memo(Sprite);
|
||||||
|
|
|
@ -10,12 +10,13 @@ const TargetingGhostInternal = PixiComponent('TargetingGhost', {
|
||||||
create: () => {
|
create: () => {
|
||||||
// Solid target square.
|
// Solid target square.
|
||||||
const target = new Graphics();
|
const target = new Graphics();
|
||||||
|
target.alpha = 0.7;
|
||||||
target.lineStyle(1, 0xffffff);
|
target.lineStyle(1, 0xffffff);
|
||||||
target.drawRect(0.5, 0.5, tileSize.x, tileSize.y);
|
target.drawRect(0.5, 0.5, tileSize.x, tileSize.y);
|
||||||
target.pivot = {x: tileSize.x / 2, y: tileSize.y / 2};
|
target.pivot = {x: tileSize.x / 2, y: tileSize.y / 2};
|
||||||
// Inner spinny part.
|
// Inner spinny part.
|
||||||
const targetInner = new Graphics();
|
const targetInner = new Graphics();
|
||||||
targetInner.alpha = 0.6;
|
targetInner.alpha = 0.3;
|
||||||
targetInner.lineStyle(3, 0x333333);
|
targetInner.lineStyle(3, 0x333333);
|
||||||
targetInner.beginFill(0xdddddd);
|
targetInner.beginFill(0xdddddd);
|
||||||
targetInner.pivot = {x: tileSize.x / 2, y: tileSize.y / 2};
|
targetInner.pivot = {x: tileSize.x / 2, y: tileSize.y / 2};
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import {Container, Graphics} from '@pixi/react';
|
import {Container, Graphics} from '@pixi/react';
|
||||||
import {forwardRef, useCallback, useEffect, useRef, useState} from 'react';
|
import {forwardRef, memo, useCallback, useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
|
import {deferredLighting} from './lights.js';
|
||||||
|
|
||||||
const WaterTile = forwardRef(function WaterTile({height, width}, ref) {
|
const WaterTile = forwardRef(function WaterTile({height, width}, ref) {
|
||||||
const draw = useCallback((g) => {
|
const draw = useCallback((g) => {
|
||||||
|
@ -15,7 +17,7 @@ const WaterTile = forwardRef(function WaterTile({height, width}, ref) {
|
||||||
return <Graphics alpha={0} draw={draw} ref={ref} />
|
return <Graphics alpha={0} draw={draw} ref={ref} />
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Water({tileLayer, water}) {
|
function Water({tileLayer, water}) {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
@ -39,7 +41,11 @@ export default function Water({tileLayer, water}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container ref={(element) => {
|
||||||
|
if (element) {
|
||||||
|
element.parentGroup = deferredLighting.diffuseGroup;
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<WaterTile
|
<WaterTile
|
||||||
height={tileLayer.tileSize.y}
|
height={tileLayer.tileSize.y}
|
||||||
ref={waterTile}
|
ref={waterTile}
|
||||||
|
@ -49,3 +55,5 @@ export default function Water({tileLayer, water}) {
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default memo(Water);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {memo, useCallback, useEffect, useRef, useState} from 'react';
|
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {useClient, usePacket} from '@/react/context/client.js';
|
import {useClient, usePacket} from '@/react/context/client.js';
|
||||||
import {useDebug} from '@/react/context/debug.js';
|
import {useDebug} from '@/react/context/debug.js';
|
||||||
|
@ -14,6 +14,7 @@ import Chat from './dom/chat/chat.jsx';
|
||||||
import Bag from './dom/bag.jsx';
|
import Bag from './dom/bag.jsx';
|
||||||
import Dom from './dom/dom.jsx';
|
import Dom from './dom/dom.jsx';
|
||||||
import Entities from './dom/entities.jsx';
|
import Entities from './dom/entities.jsx';
|
||||||
|
import External from './dom/external.jsx';
|
||||||
import HotBar from './dom/hotbar.jsx';
|
import HotBar from './dom/hotbar.jsx';
|
||||||
import Pixi from './pixi/pixi.jsx';
|
import Pixi from './pixi/pixi.jsx';
|
||||||
import Devtools from './devtools.jsx';
|
import Devtools from './devtools.jsx';
|
||||||
|
@ -25,7 +26,7 @@ const KEY_MAP = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function emptySlots() {
|
function emptySlots() {
|
||||||
return Array(10).fill(undefined);
|
return Array(50).fill(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const devEventsChannel = new EventEmitter();
|
const devEventsChannel = new EventEmitter();
|
||||||
|
@ -43,12 +44,11 @@ function Ui({disconnected}) {
|
||||||
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
||||||
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
||||||
const [camera, setCamera] = useState({x: 0, y: 0});
|
const [camera, setCamera] = useState({x: 0, y: 0});
|
||||||
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
const [inventorySlots, setInventorySlots] = useState(emptySlots());
|
||||||
const [activeSlot, setActiveSlot] = useState(0);
|
const [activeSlot, setActiveSlot] = useState(0);
|
||||||
const [scale, setScale] = useState(2);
|
const [scale, setScale] = useState(2);
|
||||||
const [Components, setComponents] = useState();
|
const [Components, setComponents] = useState();
|
||||||
const [Systems, setSystems] = useState();
|
const [Systems, setSystems] = useState();
|
||||||
const [applyFilters, setApplyFilters] = useState(true);
|
|
||||||
const [monopolizers, setMonopolizers] = useState([]);
|
const [monopolizers, setMonopolizers] = useState([]);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [chatIsOpen, setChatIsOpen] = useState(false);
|
const [chatIsOpen, setChatIsOpen] = useState(false);
|
||||||
|
@ -59,6 +59,15 @@ function Ui({disconnected}) {
|
||||||
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
|
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
|
||||||
const [hotbarHideHandle, setHotbarHideHandle] = useState();
|
const [hotbarHideHandle, setHotbarHideHandle] = useState();
|
||||||
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
||||||
|
const [externalInventory, setExternalInventory] = useState();
|
||||||
|
const [externalInventorySlots, setExternalInventorySlots] = useState();
|
||||||
|
const [particleWorker, setParticleWorker] = useState();
|
||||||
|
const refreshEcs = useCallback(() => {
|
||||||
|
class ClientEcsPerf extends ClientEcs {
|
||||||
|
markChange() {}
|
||||||
|
}
|
||||||
|
setEcs(new ClientEcsPerf({Components, Systems}));
|
||||||
|
}, [Components, Systems, setEcs]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function setEcsStuff() {
|
async function setEcsStuff() {
|
||||||
const {default: Components} = await import('@/ecs/components/index.js');
|
const {default: Components} = await import('@/ecs/components/index.js');
|
||||||
|
@ -69,8 +78,8 @@ function Ui({disconnected}) {
|
||||||
setEcsStuff();
|
setEcsStuff();
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEcs(new ClientEcs({Components, Systems}));
|
refreshEcs();
|
||||||
}, [Components, setEcs, Systems]);
|
}, [refreshEcs]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let handle;
|
let handle;
|
||||||
if (disconnected) {
|
if (disconnected) {
|
||||||
|
@ -123,6 +132,17 @@ function Ui({disconnected}) {
|
||||||
chatIsOpen,
|
chatIsOpen,
|
||||||
client,
|
client,
|
||||||
]);
|
]);
|
||||||
|
const keepHotbarOpen = useCallback(() => {
|
||||||
|
if (!isInventoryOpen) {
|
||||||
|
setHotbarIsHidden(false);
|
||||||
|
if (hotbarHideHandle) {
|
||||||
|
clearTimeout(hotbarHideHandle);
|
||||||
|
}
|
||||||
|
setHotbarHideHandle(setTimeout(() => {
|
||||||
|
setHotbarIsHidden(true);
|
||||||
|
}, 4000));
|
||||||
|
}
|
||||||
|
}, [hotbarHideHandle, isInventoryOpen]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return addKeyListener(document.body, ({event, type, payload}) => {
|
return addKeyListener(document.body, ({event, type, payload}) => {
|
||||||
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
|
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
|
||||||
|
@ -166,7 +186,10 @@ function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Tab': {
|
case '`': {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (isInventoryOpen) {
|
if (isInventoryOpen) {
|
||||||
setHotbarIsHidden(true);
|
setHotbarIsHidden(true);
|
||||||
|
@ -199,150 +222,70 @@ function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
case '1': {
|
case '1': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 1};
|
actionPayload = {type: 'changeSlot', value: 1};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '2': {
|
case '2': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 2};
|
actionPayload = {type: 'changeSlot', value: 2};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '3': {
|
case '3': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 3};
|
actionPayload = {type: 'changeSlot', value: 3};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '4': {
|
case '4': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 4};
|
actionPayload = {type: 'changeSlot', value: 4};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '5': {
|
case '5': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 5};
|
actionPayload = {type: 'changeSlot', value: 5};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '6': {
|
case '6': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 6};
|
actionPayload = {type: 'changeSlot', value: 6};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '7': {
|
case '7': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 7};
|
actionPayload = {type: 'changeSlot', value: 7};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '8': {
|
case '8': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 8};
|
actionPayload = {type: 'changeSlot', value: 8};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '9': {
|
case '9': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 9};
|
actionPayload = {type: 'changeSlot', value: 9};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '0': {
|
case '0': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (!isInventoryOpen) {
|
keepHotbarOpen();
|
||||||
setHotbarIsHidden(false);
|
|
||||||
if (hotbarHideHandle) {
|
|
||||||
clearTimeout(hotbarHideHandle);
|
|
||||||
}
|
|
||||||
setHotbarHideHandle(setTimeout(() => {
|
|
||||||
setHotbarIsHidden(true);
|
|
||||||
}, 4000));
|
|
||||||
}
|
|
||||||
actionPayload = {type: 'changeSlot', value: 10};
|
actionPayload = {type: 'changeSlot', value: 10};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -362,15 +305,16 @@ function Ui({disconnected}) {
|
||||||
devtoolsIsOpen,
|
devtoolsIsOpen,
|
||||||
hotbarHideHandle,
|
hotbarHideHandle,
|
||||||
isInventoryOpen,
|
isInventoryOpen,
|
||||||
|
keepHotbarOpen,
|
||||||
monopolizers,
|
monopolizers,
|
||||||
setDebug,
|
setDebug,
|
||||||
setScale,
|
setScale,
|
||||||
]);
|
]);
|
||||||
usePacket('EcsChange', async () => {
|
usePacket('EcsChange', async () => {
|
||||||
setEcs(new ClientEcs({Components, Systems}));
|
refreshEcs();
|
||||||
setMainEntity(undefined);
|
setMainEntity(undefined);
|
||||||
setMonopolizers([]);
|
setMonopolizers([]);
|
||||||
}, [Components, Systems, setEcs, setMainEntity]);
|
}, [refreshEcs, setMainEntity, setMonopolizers]);
|
||||||
usePacket('Tick', async (payload, client) => {
|
usePacket('Tick', async (payload, client) => {
|
||||||
if (0 === Object.keys(payload.ecs).length) {
|
if (0 === Object.keys(payload.ecs).length) {
|
||||||
return;
|
return;
|
||||||
|
@ -378,6 +322,22 @@ function Ui({disconnected}) {
|
||||||
await ecs.apply(payload.ecs);
|
await ecs.apply(payload.ecs);
|
||||||
client.emitter.invoke(':Ecs', payload.ecs);
|
client.emitter.invoke(':Ecs', payload.ecs);
|
||||||
}, [ecs]);
|
}, [ecs]);
|
||||||
|
useEcsTick((payload) => {
|
||||||
|
if (!('1' in payload) || particleWorker) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const localParticleWorker = new Worker(
|
||||||
|
new URL('./particle-worker.js', import.meta.url),
|
||||||
|
{type: 'module'},
|
||||||
|
);
|
||||||
|
localParticleWorker.postMessage(ecs.get(1).toJSON());
|
||||||
|
setParticleWorker((particleWorker) => {
|
||||||
|
if (particleWorker) {
|
||||||
|
particleWorker.terminate();
|
||||||
|
}
|
||||||
|
return localParticleWorker;
|
||||||
|
});
|
||||||
|
}, [particleWorker]);
|
||||||
useEcsTick((payload) => {
|
useEcsTick((payload) => {
|
||||||
let localMainEntity = mainEntity;
|
let localMainEntity = mainEntity;
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
|
@ -397,15 +357,34 @@ function Ui({disconnected}) {
|
||||||
if (update.MainEntity) {
|
if (update.MainEntity) {
|
||||||
setMainEntity(localMainEntity = id);
|
setMainEntity(localMainEntity = id);
|
||||||
}
|
}
|
||||||
if (localMainEntity === id) {
|
if (update.Inventory) {
|
||||||
if (update.Inventory) {
|
if (localMainEntity === id) {
|
||||||
setBufferSlot(entity.Inventory.item(0));
|
setBufferSlot(entity.Inventory.item(0));
|
||||||
const newHotbarSlots = emptySlots();
|
const newInventorySlots = emptySlots();
|
||||||
for (let i = 1; i < 11; ++i) {
|
for (let i = 1; i < 41; ++i) {
|
||||||
newHotbarSlots[i - 1] = entity.Inventory.item(i);
|
newInventorySlots[i - 1] = entity.Inventory.item(i);
|
||||||
}
|
}
|
||||||
setHotbarSlots(newHotbarSlots);
|
setInventorySlots(newInventorySlots);
|
||||||
}
|
}
|
||||||
|
else if (update.Inventory.slots) {
|
||||||
|
const newInventorySlots = Array(30).fill(undefined);
|
||||||
|
for (let i = 0; i < 30; ++i) {
|
||||||
|
newInventorySlots[i] = entity.Inventory.item(i);
|
||||||
|
}
|
||||||
|
setExternalInventory(entity.id)
|
||||||
|
setExternalInventorySlots(newInventorySlots);
|
||||||
|
setIsInventoryOpen(true);
|
||||||
|
setHotbarIsHidden(false);
|
||||||
|
if (hotbarHideHandle) {
|
||||||
|
clearTimeout(hotbarHideHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (update.Inventory.closed) {
|
||||||
|
setExternalInventory();
|
||||||
|
setExternalInventorySlots();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (localMainEntity === id) {
|
||||||
if (update.Wielder && 'activeSlot' in update.Wielder) {
|
if (update.Wielder && 'activeSlot' in update.Wielder) {
|
||||||
setActiveSlot(update.Wielder.activeSlot);
|
setActiveSlot(update.Wielder.activeSlot);
|
||||||
}
|
}
|
||||||
|
@ -419,7 +398,7 @@ function Ui({disconnected}) {
|
||||||
setCamera({x, y});
|
setCamera({x, y});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [camera, ecs, mainEntity, scale]);
|
}, [camera, ecs, hotbarHideHandle, mainEntity, scale]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onContextMenu(event) {
|
function onContextMenu(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -558,9 +537,9 @@ function Ui({disconnected}) {
|
||||||
ref={gameRef}
|
ref={gameRef}
|
||||||
>
|
>
|
||||||
<Pixi
|
<Pixi
|
||||||
applyFilters={applyFilters}
|
|
||||||
camera={camera}
|
camera={camera}
|
||||||
monopolizers={monopolizers}
|
monopolizers={monopolizers}
|
||||||
|
particleWorker={particleWorker}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
/>
|
/>
|
||||||
<Dom>
|
<Dom>
|
||||||
|
@ -568,17 +547,36 @@ function Ui({disconnected}) {
|
||||||
active={activeSlot}
|
active={activeSlot}
|
||||||
hotbarIsHidden={hotbarIsHidden}
|
hotbarIsHidden={hotbarIsHidden}
|
||||||
onActivate={(i) => {
|
onActivate={(i) => {
|
||||||
|
keepHotbarOpen();
|
||||||
client.send({
|
client.send({
|
||||||
type: 'Action',
|
type: 'Action',
|
||||||
payload: {type: 'swapSlots', value: [0, i + 1]},
|
payload: {type: 'swapSlots', value: [0, mainEntity, i + 1]},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
slots={hotbarSlots}
|
slots={inventorySlots.slice(0, 10)}
|
||||||
/>
|
/>
|
||||||
<Bag
|
<Bag
|
||||||
isInventoryOpen={isInventoryOpen}
|
isInventoryOpen={isInventoryOpen}
|
||||||
slots={Array(30).fill(undefined)}
|
onActivate={(i) => {
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'swapSlots', value: [0, mainEntity, i + 11]},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
slots={inventorySlots.slice(10, 20)}
|
||||||
/>
|
/>
|
||||||
|
{externalInventory && (
|
||||||
|
<External
|
||||||
|
isInventoryOpen={isInventoryOpen}
|
||||||
|
onActivate={(i) => {
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'swapSlots', value: [0, externalInventory, i]},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
slots={externalInventorySlots}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Entities
|
<Entities
|
||||||
camera={camera}
|
camera={camera}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
|
@ -607,15 +605,15 @@ function Ui({disconnected}) {
|
||||||
)}
|
)}
|
||||||
</Dom>
|
</Dom>
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
{devtoolsIsOpen && (
|
||||||
<Devtools
|
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
||||||
applyFilters={applyFilters}
|
<Devtools
|
||||||
eventsChannel={devEventsChannel}
|
eventsChannel={devEventsChannel}
|
||||||
setApplyFilters={setApplyFilters}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(Ui);
|
export default Ui;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {Texture} from '@pixi/core';
|
||||||
import {Assets} from '@pixi/assets';
|
import {Assets} from '@pixi/assets';
|
||||||
import {createContext, useContext, useEffect} from 'react';
|
import {createContext, useContext, useEffect} from 'react';
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ export function useAsset(source) {
|
||||||
const [assets, setAssets] = useContext(context);
|
const [assets, setAssets] = useContext(context);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (!assets[source]) {
|
if (!assets[source]) {
|
||||||
if (!loading[source]) {
|
if (!loading[source]) {
|
||||||
|
@ -24,5 +25,5 @@ export function useAsset(source) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [assets, setAssets, source]);
|
}, [assets, setAssets, source]);
|
||||||
return source ? assets[source] : undefined;
|
return source ? assets[source] : {data: {meta: {}}, textures: {'': Texture.WHITE}};
|
||||||
}
|
}
|
||||||
|
|
19
app/react/hooks/use-animation-frame.js
Normal file
19
app/react/hooks/use-animation-frame.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import {useEffect, useRef} from 'react';
|
||||||
|
|
||||||
|
export default function useAnimationFrame(callback) {
|
||||||
|
const handle = useRef();
|
||||||
|
const last = useRef();
|
||||||
|
function animate(time) {
|
||||||
|
if (last.current != undefined) {
|
||||||
|
callback((time - last.current) / 1000)
|
||||||
|
}
|
||||||
|
last.current = time;
|
||||||
|
handle.current = requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
handle.current = requestAnimationFrame(animate);
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(handle.current);
|
||||||
|
};
|
||||||
|
}, [callback]);
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ export default function createEcs(Ecs) {
|
||||||
'PlantGrowth',
|
'PlantGrowth',
|
||||||
'FollowCamera',
|
'FollowCamera',
|
||||||
'VisibleAabbs',
|
'VisibleAabbs',
|
||||||
|
'MaintainColliderHash',
|
||||||
'Colliders',
|
'Colliders',
|
||||||
'ControlDirection',
|
'ControlDirection',
|
||||||
'SpriteDirection',
|
'SpriteDirection',
|
||||||
|
@ -21,6 +22,8 @@ export default function createEcs(Ecs) {
|
||||||
'RunTickingPromises',
|
'RunTickingPromises',
|
||||||
'Water',
|
'Water',
|
||||||
'Interactions',
|
'Interactions',
|
||||||
|
'InventoryCloser',
|
||||||
|
'KillPerishable',
|
||||||
];
|
];
|
||||||
defaultSystems.forEach((defaultSystem) => {
|
defaultSystems.forEach((defaultSystem) => {
|
||||||
const System = ecs.system(defaultSystem);
|
const System = ecs.system(defaultSystem);
|
||||||
|
|
|
@ -98,13 +98,14 @@ export default async function createHomestead(id) {
|
||||||
Interactive: {
|
Interactive: {
|
||||||
interacting: 1,
|
interacting: 1,
|
||||||
interactScript: `
|
interactScript: `
|
||||||
subject.Interlocutor.dialogue({
|
initiator.Player.openInventory = subject.Inventory;
|
||||||
body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!",
|
// subject.Interlocutor.dialogue({
|
||||||
monopolizer: true,
|
// body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!",
|
||||||
offset: {x: 0, y: -48},
|
// monopolizer: true,
|
||||||
origin: 'track',
|
// offset: {x: 0, y: -48},
|
||||||
position: 'track',
|
// origin: 'track',
|
||||||
})
|
// position: 'track',
|
||||||
|
// })
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Interlocutor: {},
|
Interlocutor: {},
|
||||||
|
@ -132,7 +133,7 @@ export default async function createHomestead(id) {
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorX: 0.5,
|
anchorX: 0.5,
|
||||||
anchorY: 0.7,
|
anchorY: 0.7,
|
||||||
source: '/assets/chest.json',
|
source: '/assets/chest/chest.json',
|
||||||
},
|
},
|
||||||
Ticking: {},
|
Ticking: {},
|
||||||
VisibleAabb: {},
|
VisibleAabb: {},
|
||||||
|
@ -174,7 +175,7 @@ export default async function createHomestead(id) {
|
||||||
subject.Interlocutor.dialogue({
|
subject.Interlocutor.dialogue({
|
||||||
body: line,
|
body: line,
|
||||||
linger: 2,
|
linger: 2,
|
||||||
offset: {x: 0, y: -32},
|
offset: {x: 0, y: -16},
|
||||||
origin: 'track',
|
origin: 'track',
|
||||||
position: 'track',
|
position: 'track',
|
||||||
})
|
})
|
||||||
|
@ -194,7 +195,7 @@ export default async function createHomestead(id) {
|
||||||
VisibleAabb: {},
|
VisibleAabb: {},
|
||||||
Vulnerable: {},
|
Vulnerable: {},
|
||||||
};
|
};
|
||||||
for (let i = 0; i < 10; ++i) {
|
for (let i = 0; i < 50; ++i) {
|
||||||
entities.push(kitty);
|
entities.push(kitty);
|
||||||
}
|
}
|
||||||
entities.push({
|
entities.push({
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default async function createPlayer(id) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Health: {health: 100},
|
Health: {health: 100},
|
||||||
Light: {},
|
Light: {brightness: 0},
|
||||||
Magnet: {strength: 24},
|
Magnet: {strength: 24},
|
||||||
Player: {},
|
Player: {},
|
||||||
Position: {x: 128, y: 448},
|
Position: {x: 128, y: 448},
|
||||||
|
|
|
@ -168,7 +168,7 @@ export default class Engine {
|
||||||
Interlocutor.dialogue({
|
Interlocutor.dialogue({
|
||||||
body: payload.value,
|
body: payload.value,
|
||||||
linger: 5,
|
linger: 5,
|
||||||
offset: {x: 0, y: -40},
|
offset: {x: 0, y: -24},
|
||||||
origin: 'track',
|
origin: 'track',
|
||||||
position: 'track',
|
position: 'track',
|
||||||
});
|
});
|
||||||
|
@ -201,7 +201,11 @@ export default class Engine {
|
||||||
}
|
}
|
||||||
case 'swapSlots': {
|
case 'swapSlots': {
|
||||||
if (!Controlled.locked) {
|
if (!Controlled.locked) {
|
||||||
Inventory.swapSlots(...payload.value);
|
const [l, other, r] = payload.value;
|
||||||
|
const {Inventory: OtherInventory} = ecs.get(other);
|
||||||
|
if (OtherInventory) {
|
||||||
|
Inventory.swapSlots(l, OtherInventory, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -413,7 +417,6 @@ export default class Engine {
|
||||||
updateFor(connection) {
|
updateFor(connection) {
|
||||||
const update = {};
|
const update = {};
|
||||||
const {entity, memory} = this.connectedPlayers.get(connection);
|
const {entity, memory} = this.connectedPlayers.get(connection);
|
||||||
const mainEntityId = entity.id;
|
|
||||||
const ecs = this.ecses[entity.Ecs.path];
|
const ecs = this.ecses[entity.Ecs.path];
|
||||||
// Entities within half a screen offscreen.
|
// Entities within half a screen offscreen.
|
||||||
const x0 = entity.Position.x - RESOLUTION.x;
|
const x0 = entity.Position.x - RESOLUTION.x;
|
||||||
|
@ -429,17 +432,24 @@ export default class Engine {
|
||||||
nearby.add(master);
|
nearby.add(master);
|
||||||
const lastNearby = new Set(memory.nearby.values());
|
const lastNearby = new Set(memory.nearby.values());
|
||||||
const firstUpdate = 0 === lastNearby.size;
|
const firstUpdate = 0 === lastNearby.size;
|
||||||
for (const entity of nearby) {
|
for (const nearbyEntity of nearby) {
|
||||||
const {id} = entity;
|
const {id} = nearbyEntity;
|
||||||
lastNearby.delete(id);
|
lastNearby.delete(id);
|
||||||
if (!memory.nearby.has(id)) {
|
if (!memory.nearby.has(id)) {
|
||||||
update[id] = entity.toNet();
|
update[id] = nearbyEntity.toNet(entity);
|
||||||
if (mainEntityId === id) {
|
if (entity.id === id) {
|
||||||
update[id].MainEntity = {};
|
update[id].MainEntity = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ecs.diff[id]) {
|
else if (ecs.diff[id]) {
|
||||||
update[id] = ecs.diff[id];
|
const nearbyEntityDiff = {};
|
||||||
|
for (const componentName in ecs.diff[id]) {
|
||||||
|
nearbyEntityDiff[componentName] = nearbyEntity[componentName].toNet(
|
||||||
|
entity,
|
||||||
|
ecs.diff[id][componentName],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
update[id] = nearbyEntityDiff;
|
||||||
}
|
}
|
||||||
memory.nearby.add(id);
|
memory.nearby.add(id);
|
||||||
}
|
}
|
||||||
|
@ -447,6 +457,7 @@ export default class Engine {
|
||||||
memory.nearby.delete(id);
|
memory.nearby.delete(id);
|
||||||
update[id] = false;
|
update[id] = false;
|
||||||
}
|
}
|
||||||
|
entity.updateAttachments(update);
|
||||||
// Tile layer chunking
|
// Tile layer chunking
|
||||||
const {TileLayers} = master;
|
const {TileLayers} = master;
|
||||||
const {layers} = TileLayers;
|
const {layers} = TileLayers;
|
||||||
|
|
159
app/util/easing.js
Normal file
159
app/util/easing.js
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
export function linear(t, b, c, d) {
|
||||||
|
return b + c * t/d
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInQuad(t, b, c, d) {
|
||||||
|
return c*(t/=d)*t + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutQuad(t, b, c, d) {
|
||||||
|
return -c *(t/=d)*(t-2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutQuad(t, b, c, d) {
|
||||||
|
if ((t/=d/2) < 1) return c/2*t*t + b;
|
||||||
|
return -c/2 * ((--t)*(t-2) - 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInCubic(t, b, c, d) {
|
||||||
|
return c*(t/=d)*t*t + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutCubic(t, b, c, d) {
|
||||||
|
return c*((t=t/d-1)*t*t + 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutCubic(t, b, c, d) {
|
||||||
|
if ((t/=d/2) < 1) return c/2*t*t*t + b;
|
||||||
|
return c/2*((t-=2)*t*t + 2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInQuart(t, b, c, d) {
|
||||||
|
return c*(t/=d)*t*t*t + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutQuart(t, b, c, d) {
|
||||||
|
return -c * ((t=t/d-1)*t*t*t - 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutQuart(t, b, c, d) {
|
||||||
|
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
|
||||||
|
return -c/2 * ((t-=2)*t*t*t - 2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInQuint(t, b, c, d) {
|
||||||
|
return c*(t/=d)*t*t*t*t + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutQuint(t, b, c, d) {
|
||||||
|
return c*((t=t/d-1)*t*t*t*t + 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutQuint(t, b, c, d) {
|
||||||
|
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
|
||||||
|
return c/2*((t-=2)*t*t*t*t + 2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInSine(t, b, c, d) {
|
||||||
|
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutSine(t, b, c, d) {
|
||||||
|
return c * Math.sin(t/d * (Math.PI/2)) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutSine(t, b, c, d) {
|
||||||
|
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInExpo(t, b, c, d) {
|
||||||
|
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutExpo(t, b, c, d) {
|
||||||
|
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutExpo(t, b, c, d) {
|
||||||
|
if (t==0) return b;
|
||||||
|
if (t==d) return b+c;
|
||||||
|
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
|
||||||
|
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInCirc(t, b, c, d) {
|
||||||
|
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutCirc(t, b, c, d) {
|
||||||
|
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutCirc(t, b, c, d) {
|
||||||
|
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
|
||||||
|
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInElastic(t, b, c, d) {
|
||||||
|
var s=1.70158;var p=0;var a=c;
|
||||||
|
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||||
|
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||||
|
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||||
|
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutElastic(t, b, c, d) {
|
||||||
|
var s=1.70158;var p=0;var a=c;
|
||||||
|
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||||
|
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||||
|
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||||
|
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutElastic(t, b, c, d) {
|
||||||
|
var s=1.70158;var p=0;var a=c;
|
||||||
|
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
|
||||||
|
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||||
|
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||||
|
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||||
|
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInBack(t, b, c, d, s) {
|
||||||
|
if (s == undefined) s = 1.70158;
|
||||||
|
return c*(t/=d)*t*((s+1)*t - s) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutBack(t, b, c, d, s) {
|
||||||
|
if (s == undefined) s = 1.70158;
|
||||||
|
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutBack(t, b, c, d, s) {
|
||||||
|
if (s == undefined) s = 1.70158;
|
||||||
|
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
|
||||||
|
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInBounce(t, b, c, d) {
|
||||||
|
return c - easeOutBounce(d-t, 0, c, d) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeOutBounce(t, b, c, d) {
|
||||||
|
if ((t/=d) < (1/2.75)) {
|
||||||
|
return c*(7.5625*t*t) + b;
|
||||||
|
} else if (t < (2/2.75)) {
|
||||||
|
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
|
||||||
|
} else if (t < (2.5/2.75)) {
|
||||||
|
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
|
||||||
|
} else {
|
||||||
|
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function easeInOutBounce(t, b, c, d) {
|
||||||
|
if (t < d/2) return easeInBounce(t*2, 0, c, d) * .5 + b;
|
||||||
|
return easeOutBounce(t*2-d, 0, c, d) * .5 + c*.5 + b;
|
||||||
|
}
|
||||||
|
/* eslint-enable */
|
|
@ -24,13 +24,14 @@ export const cache = new LRUCache({
|
||||||
|
|
||||||
export default class Script {
|
export default class Script {
|
||||||
|
|
||||||
constructor(sandbox) {
|
constructor(sandbox, code) {
|
||||||
|
this.code = code;
|
||||||
this.sandbox = sandbox;
|
this.sandbox = sandbox;
|
||||||
this.promise = null;
|
this.promise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new this.constructor(this.sandbox.clone());
|
return new this.constructor(this.sandbox.clone(), this.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
get context() {
|
get context() {
|
||||||
|
@ -76,6 +77,7 @@ export default class Script {
|
||||||
}
|
}
|
||||||
return new this(
|
return new this(
|
||||||
new Sandbox(await cache.get(code), this.createContext(context)),
|
new Sandbox(await cache.get(code), this.createContext(context)),
|
||||||
|
code,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +104,19 @@ export default class Script {
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
this.sandbox.context.elapsed = elapsed;
|
this.sandbox.context.elapsed = elapsed;
|
||||||
const {async, done, value} = this.sandbox.step();
|
let async, done, value;
|
||||||
|
try {
|
||||||
|
({async, done, value} = this.sandbox.step());
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const node = this.sandbox.$$execution.stack.pop();
|
||||||
|
console.warn('Script ran into a problem at', this.code.slice(node.start, node.end));
|
||||||
|
console.warn(error);
|
||||||
|
if (resolve) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (async) {
|
if (async) {
|
||||||
this.promise = value;
|
this.promise = value;
|
||||||
value
|
value
|
||||||
|
|
|
@ -1,136 +1,6 @@
|
||||||
import {Ticker} from '@/util/promise.js';
|
import {Ticker} from '@/util/promise.js';
|
||||||
|
|
||||||
/* eslint-disable */
|
import * as Easing from './easing';
|
||||||
const Easing = {
|
|
||||||
linear: function (t, b, c, d) {
|
|
||||||
return b + c * t/d
|
|
||||||
},
|
|
||||||
easeInQuad: function (t, b, c, d) {
|
|
||||||
return c*(t/=d)*t + b;
|
|
||||||
},
|
|
||||||
easeOutQuad: function (t, b, c, d) {
|
|
||||||
return -c *(t/=d)*(t-2) + b;
|
|
||||||
},
|
|
||||||
easeInOutQuad: function (t, b, c, d) {
|
|
||||||
if ((t/=d/2) < 1) return c/2*t*t + b;
|
|
||||||
return -c/2 * ((--t)*(t-2) - 1) + b;
|
|
||||||
},
|
|
||||||
easeInCubic: function (t, b, c, d) {
|
|
||||||
return c*(t/=d)*t*t + b;
|
|
||||||
},
|
|
||||||
easeOutCubic: function (t, b, c, d) {
|
|
||||||
return c*((t=t/d-1)*t*t + 1) + b;
|
|
||||||
},
|
|
||||||
easeInOutCubic: function (t, b, c, d) {
|
|
||||||
if ((t/=d/2) < 1) return c/2*t*t*t + b;
|
|
||||||
return c/2*((t-=2)*t*t + 2) + b;
|
|
||||||
},
|
|
||||||
easeInQuart: function (t, b, c, d) {
|
|
||||||
return c*(t/=d)*t*t*t + b;
|
|
||||||
},
|
|
||||||
easeOutQuart: function (t, b, c, d) {
|
|
||||||
return -c * ((t=t/d-1)*t*t*t - 1) + b;
|
|
||||||
},
|
|
||||||
easeInOutQuart: function (t, b, c, d) {
|
|
||||||
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
|
|
||||||
return -c/2 * ((t-=2)*t*t*t - 2) + b;
|
|
||||||
},
|
|
||||||
easeInQuint: function (t, b, c, d) {
|
|
||||||
return c*(t/=d)*t*t*t*t + b;
|
|
||||||
},
|
|
||||||
easeOutQuint: function (t, b, c, d) {
|
|
||||||
return c*((t=t/d-1)*t*t*t*t + 1) + b;
|
|
||||||
},
|
|
||||||
easeInOutQuint: function (t, b, c, d) {
|
|
||||||
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
|
|
||||||
return c/2*((t-=2)*t*t*t*t + 2) + b;
|
|
||||||
},
|
|
||||||
easeInSine: function (t, b, c, d) {
|
|
||||||
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
|
|
||||||
},
|
|
||||||
easeOutSine: function (t, b, c, d) {
|
|
||||||
return c * Math.sin(t/d * (Math.PI/2)) + b;
|
|
||||||
},
|
|
||||||
easeInOutSine: function (t, b, c, d) {
|
|
||||||
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
|
|
||||||
},
|
|
||||||
easeInExpo: function (t, b, c, d) {
|
|
||||||
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
|
|
||||||
},
|
|
||||||
easeOutExpo: function (t, b, c, d) {
|
|
||||||
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
|
|
||||||
},
|
|
||||||
easeInOutExpo: function (t, b, c, d) {
|
|
||||||
if (t==0) return b;
|
|
||||||
if (t==d) return b+c;
|
|
||||||
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
|
|
||||||
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
|
|
||||||
},
|
|
||||||
easeInCirc: function (t, b, c, d) {
|
|
||||||
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
|
|
||||||
},
|
|
||||||
easeOutCirc: function (t, b, c, d) {
|
|
||||||
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
|
|
||||||
},
|
|
||||||
easeInOutCirc: function (t, b, c, d) {
|
|
||||||
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
|
|
||||||
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
|
|
||||||
},
|
|
||||||
easeInElastic: function (t, b, c, d) {
|
|
||||||
var s=1.70158;var p=0;var a=c;
|
|
||||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
|
||||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
|
||||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
|
||||||
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
|
||||||
},
|
|
||||||
easeOutElastic: function (t, b, c, d) {
|
|
||||||
var s=1.70158;var p=0;var a=c;
|
|
||||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
|
||||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
|
||||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
|
||||||
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
|
|
||||||
},
|
|
||||||
easeInOutElastic: function (t, b, c, d) {
|
|
||||||
var s=1.70158;var p=0;var a=c;
|
|
||||||
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
|
|
||||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
|
||||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
|
||||||
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
|
||||||
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
|
|
||||||
},
|
|
||||||
easeInBack: function (t, b, c, d, s) {
|
|
||||||
if (s == undefined) s = 1.70158;
|
|
||||||
return c*(t/=d)*t*((s+1)*t - s) + b;
|
|
||||||
},
|
|
||||||
easeOutBack: function (t, b, c, d, s) {
|
|
||||||
if (s == undefined) s = 1.70158;
|
|
||||||
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
|
|
||||||
},
|
|
||||||
easeInOutBack: function (t, b, c, d, s) {
|
|
||||||
if (s == undefined) s = 1.70158;
|
|
||||||
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
|
|
||||||
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
|
|
||||||
},
|
|
||||||
easeInBounce: function (t, b, c, d) {
|
|
||||||
return c - easing.easeOutBounce (d-t, 0, c, d) + b;
|
|
||||||
},
|
|
||||||
easeOutBounce: function (t, b, c, d) {
|
|
||||||
if ((t/=d) < (1/2.75)) {
|
|
||||||
return c*(7.5625*t*t) + b;
|
|
||||||
} else if (t < (2/2.75)) {
|
|
||||||
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
|
|
||||||
} else if (t < (2.5/2.75)) {
|
|
||||||
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
|
|
||||||
} else {
|
|
||||||
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
easeInOutBounce: function (t, b, c, d) {
|
|
||||||
if (t < d/2) return easing.easeInBounce (t*2, 0, c, d) * .5 + b;
|
|
||||||
return easing.easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
export default function transition(object, properties) {
|
export default function transition(object, properties) {
|
||||||
const transitions = {};
|
const transitions = {};
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -26,6 +26,7 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
|
"kefir": "^3.8.8",
|
||||||
"lru-cache": "^10.2.2",
|
"lru-cache": "^10.2.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"pixi.js": "^7.4.2",
|
"pixi.js": "^7.4.2",
|
||||||
|
@ -12697,6 +12698,11 @@
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kefir": {
|
||||||
|
"version": "3.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/kefir/-/kefir-3.8.8.tgz",
|
||||||
|
"integrity": "sha512-xWga7QCZsR2Wjy2vNL3Kq/irT+IwxwItEWycRRlT5yhqHZK2fmEhziP+LzcJBWSTAMranGKtGTQ6lFpyJS3+jA=="
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
|
"kefir": "^3.8.8",
|
||||||
"lru-cache": "^10.2.2",
|
"lru-cache": "^10.2.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"pixi.js": "^7.4.2",
|
"pixi.js": "^7.4.2",
|
||||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -9,104 +9,30 @@ if (projected?.length > 0) {
|
||||||
Controlled.locked = 1
|
Controlled.locked = 1
|
||||||
const [, direction] = Sprite.animation.split(':')
|
const [, direction] = Sprite.animation.split(':')
|
||||||
|
|
||||||
const dirtParticles = {
|
|
||||||
behaviors: [
|
|
||||||
{
|
|
||||||
type: 'moveAcceleration',
|
|
||||||
config: {
|
|
||||||
accel: {
|
|
||||||
x: 0,
|
|
||||||
y: 200,
|
|
||||||
},
|
|
||||||
minStart: 0,
|
|
||||||
maxStart: 0,
|
|
||||||
rotate: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'moveSpeed',
|
|
||||||
config: {
|
|
||||||
speed: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
time: 0,
|
|
||||||
value: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: 1,
|
|
||||||
value: 10
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'rotation',
|
|
||||||
config: {
|
|
||||||
accel: 0,
|
|
||||||
minSpeed: 0,
|
|
||||||
maxSpeed: 0,
|
|
||||||
minStart: 225,
|
|
||||||
maxStart: 320
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'scale',
|
|
||||||
config: {
|
|
||||||
scale: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
value: 0.25,
|
|
||||||
time: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 0.125,
|
|
||||||
time: 1,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'textureSingle',
|
|
||||||
config: {
|
|
||||||
texture: 'tileset/7',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
lifetime: {
|
|
||||||
min: 0.25,
|
|
||||||
max: 0.25,
|
|
||||||
},
|
|
||||||
frequency: 0.01,
|
|
||||||
emitterLifetime: 0.25,
|
|
||||||
pos: {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < 2; ++i) {
|
for (let i = 0; i < 2; ++i) {
|
||||||
Sound.play('/assets/hoe/dig.wav');
|
Sound.play('/assets/hoe/dig.wav');
|
||||||
for (const {x, y} of projected) {
|
for (const {x, y} of projected) {
|
||||||
Emitter.emit({
|
Emitter.emit({
|
||||||
...dirtParticles,
|
count: 25,
|
||||||
behaviors: [
|
frequency: 0.01,
|
||||||
...dirtParticles.behaviors,
|
shape: {
|
||||||
{
|
type: 'filledRect',
|
||||||
type: 'spawnShape',
|
payload: {width: 16, height: 16},
|
||||||
config: {
|
},
|
||||||
type: 'rect',
|
entity: {
|
||||||
data: {
|
Forces: {forceY: -50},
|
||||||
x: x * layer.tileSize.x,
|
Position: {
|
||||||
y: y * layer.tileSize.y,
|
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
|
||||||
w: layer.tileSize.x,
|
y: y * layer.tileSize.y + (layer.tileSize.y / 2),
|
||||||
h: layer.tileSize.y,
|
},
|
||||||
}
|
Sprite: {
|
||||||
}
|
scaleX: 0.2,
|
||||||
}
|
scaleY: 0.2,
|
||||||
]
|
tint: 0x552200,
|
||||||
})
|
},
|
||||||
|
Ttl: {ttl: 0.125},
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Sprite.animation = ['moving', direction].join(':');
|
Sprite.animation = ['moving', direction].join(':');
|
||||||
await wait(0.3)
|
await wait(0.3)
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -29,6 +29,7 @@ for (let i = 0; i < N; ++i) {
|
||||||
Controlled: {},
|
Controlled: {},
|
||||||
Direction: {direction: Math.TAU * (i / N)},
|
Direction: {direction: Math.TAU * (i / N)},
|
||||||
Forces: {},
|
Forces: {},
|
||||||
|
Light: {brightness: 0},
|
||||||
Owned: {owner: Player ? Player.id : 0},
|
Owned: {owner: Player ? Player.id : 0},
|
||||||
Position: {x: Position.x, y: Position.y},
|
Position: {x: Position.x, y: Position.y},
|
||||||
Speed: {},
|
Speed: {},
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const {Sprite, Ticking} = entity;
|
const {Sprite, Ticking, Vulnerable} = entity;
|
||||||
|
if (Vulnerable) {
|
||||||
|
Vulnerable.isInvulnerable = 1;
|
||||||
|
}
|
||||||
if (Sprite) {
|
if (Sprite) {
|
||||||
const {promise} = transition(
|
const {promise} = transition(
|
||||||
entity.Sprite,
|
entity.Sprite,
|
||||||
|
|
|
@ -17,15 +17,15 @@ for (let i = 0; i < 10; ++i) {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
collisionStartScript: `
|
collisionStartScript: [
|
||||||
if (other.Inventory) {
|
'if (other.Inventory) {',
|
||||||
other.Inventory.give({
|
' other.Inventory.give({',
|
||||||
qty: 1,
|
' qty: 1,',
|
||||||
source: '/assets/tomato/tomato.json',
|
" source: '/assets/tomato/tomato.json',",
|
||||||
})
|
' })',
|
||||||
ecs.destroy(entity.id)
|
' ecs.destroy(entity.id)',
|
||||||
}
|
'}',
|
||||||
`,
|
].join('\n'),
|
||||||
},
|
},
|
||||||
Forces: {},
|
Forces: {},
|
||||||
Magnetic: {},
|
Magnetic: {},
|
||||||
|
|
|
@ -6,7 +6,7 @@ const filtered = []
|
||||||
for (const position of projected) {
|
for (const position of projected) {
|
||||||
const x0 = position.x * tileSize.x;
|
const x0 = position.x * tileSize.x;
|
||||||
const y0 = position.y * tileSize.y;
|
const y0 = position.y * tileSize.y;
|
||||||
const entities = ecs.system('Colliders').within({
|
const entities = ecs.system('MaintainColliderHash').within({
|
||||||
x0,
|
x0,
|
||||||
x1: x0 + tileSize.x - 1,
|
x1: x0 + tileSize.x - 1,
|
||||||
y0,
|
y0,
|
||||||
|
|
|
@ -38,84 +38,27 @@ if (projected?.length > 0) {
|
||||||
VisibleAabb: {},
|
VisibleAabb: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const seedParticles = {
|
Emitter.emit({
|
||||||
behaviors: [
|
count: 25,
|
||||||
{
|
|
||||||
type: 'moveAcceleration',
|
|
||||||
config: {
|
|
||||||
accel: {
|
|
||||||
x: 0,
|
|
||||||
y: 400,
|
|
||||||
},
|
|
||||||
minStart: 0,
|
|
||||||
maxStart: 0,
|
|
||||||
rotate: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'moveSpeed',
|
|
||||||
config: {
|
|
||||||
speed: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
time: 0,
|
|
||||||
value: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: 1,
|
|
||||||
value: 10
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'rotation',
|
|
||||||
config: {
|
|
||||||
accel: 0,
|
|
||||||
minSpeed: 0,
|
|
||||||
maxSpeed: 0,
|
|
||||||
minStart: 225,
|
|
||||||
maxStart: 320
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'scale',
|
|
||||||
config: {
|
|
||||||
scale: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
value: 0.75,
|
|
||||||
time: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 0.5,
|
|
||||||
time: 1,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'textureSingle',
|
|
||||||
config: {
|
|
||||||
texture: 'tomato-plant/tomato-plant/0',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
lifetime: {
|
|
||||||
min: 0.25,
|
|
||||||
max: 0.25,
|
|
||||||
},
|
|
||||||
frequency: 0.01,
|
frequency: 0.01,
|
||||||
emitterLifetime: 0.75,
|
shape: {
|
||||||
pos: {
|
type: 'filledRect',
|
||||||
x: Position.x,
|
payload: {width: 16, height: 16},
|
||||||
y: Position.y,
|
|
||||||
},
|
},
|
||||||
};
|
entity: {
|
||||||
|
Forces: {forceY: -100},
|
||||||
Emitter.emit(seedParticles)
|
Position: {
|
||||||
|
x: Position.x,
|
||||||
|
y: Position.y,
|
||||||
|
},
|
||||||
|
Sprite: {
|
||||||
|
scaleX: 0.125,
|
||||||
|
scaleY: 0.125,
|
||||||
|
tint: 0x221100,
|
||||||
|
},
|
||||||
|
Ttl: {ttl: 0.25},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Sound.play('/assets/sow.wav');
|
Sound.play('/assets/sow.wav');
|
||||||
Sprite.animation = ['moving', direction].join(':');
|
Sprite.animation = ['moving', direction].join(':');
|
||||||
|
|
|
@ -10,98 +10,32 @@ if (projected?.length > 0) {
|
||||||
const [, direction] = Sprite.animation.split(':')
|
const [, direction] = Sprite.animation.split(':')
|
||||||
Sprite.animation = ['idle', direction].join(':');
|
Sprite.animation = ['idle', direction].join(':');
|
||||||
|
|
||||||
const waterParticles = {
|
|
||||||
behaviors: [
|
|
||||||
{
|
|
||||||
type: 'moveAcceleration',
|
|
||||||
config: {
|
|
||||||
accel: {
|
|
||||||
x: 0,
|
|
||||||
y: 1500,
|
|
||||||
},
|
|
||||||
minStart: 0,
|
|
||||||
maxStart: 0,
|
|
||||||
rotate: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'moveSpeed',
|
|
||||||
config: {
|
|
||||||
speed: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
time: 0,
|
|
||||||
value: 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: 1,
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'scale',
|
|
||||||
config: {
|
|
||||||
scale: {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
value: 0.25,
|
|
||||||
time: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 0.125,
|
|
||||||
time: 1,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'textureSingle',
|
|
||||||
config: {
|
|
||||||
texture: 'tileset/38',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
lifetime: {
|
|
||||||
min: 0.25,
|
|
||||||
max: 0.25,
|
|
||||||
},
|
|
||||||
frequency: 0.01,
|
|
||||||
emitterLifetime: 0.25,
|
|
||||||
pos: {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
},
|
|
||||||
rotation: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Sound.play('/assets/watering-can/water.wav');
|
Sound.play('/assets/watering-can/water.wav');
|
||||||
for (let i = 0; i < 2; ++i) {
|
|
||||||
for (const {x, y} of projected) {
|
for (const {x, y} of projected) {
|
||||||
Emitter.emit({
|
Emitter.emit({
|
||||||
...waterParticles,
|
count: 25,
|
||||||
behaviors: [
|
frequency: 0.01,
|
||||||
...waterParticles.behaviors,
|
shape: {
|
||||||
{
|
type: 'filledRect',
|
||||||
type: 'spawnShape',
|
payload: {width: 16, height: 16},
|
||||||
config: {
|
},
|
||||||
type: 'rect',
|
entity: {
|
||||||
data: {
|
Forces: {forceY: 100},
|
||||||
x: x * layer.tileSize.x,
|
Position: {
|
||||||
y: y * layer.tileSize.y - (layer.tileSize.y * 0.5),
|
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
|
||||||
w: layer.tileSize.x,
|
y: y * layer.tileSize.y - layer.tileSize.y,
|
||||||
h: layer.tileSize.y,
|
},
|
||||||
}
|
Sprite: {
|
||||||
}
|
scaleX: 0.2,
|
||||||
}
|
scaleY: 0.2,
|
||||||
]
|
tint: 0x0022aa,
|
||||||
})
|
},
|
||||||
}
|
Ttl: {ttl: 0.25},
|
||||||
await wait(0.25);
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
await wait(0.5);
|
||||||
|
|
||||||
for (const {x, y} of projected) {
|
for (const {x, y} of projected) {
|
||||||
const tileIndex = layer.area.x * y + x
|
const tileIndex = layer.area.x * y + x
|
||||||
|
|
Loading…
Reference in New Issue
Block a user