Compare commits
8 Commits
3eb94f2ef8
...
5fe346372b
Author | SHA1 | Date | |
---|---|---|---|
|
5fe346372b | ||
|
0439c52076 | ||
|
557c8285ba | ||
|
b9985a573d | ||
|
ce802a8499 | ||
|
443797017f | ||
|
62eaa16b28 | ||
|
14effd2455 |
|
@ -35,6 +35,8 @@ export default function evaluate(node, {scope} = {}) {
|
||||||
return evaluators.binary(node, {evaluate, scope});
|
return evaluators.binary(node, {evaluate, scope});
|
||||||
case 'MemberExpression':
|
case 'MemberExpression':
|
||||||
return evaluators.member(node, {evaluate, scope});
|
return evaluators.member(node, {evaluate, scope});
|
||||||
|
case 'NewExpression':
|
||||||
|
return evaluators.new(node, {evaluate, scope});
|
||||||
case 'ObjectExpression':
|
case 'ObjectExpression':
|
||||||
return evaluators.object(node, {evaluate, scope});
|
return evaluators.object(node, {evaluate, scope});
|
||||||
case 'UnaryExpression':
|
case 'UnaryExpression':
|
||||||
|
|
21
app/astride/evaluators/new.js
Normal file
21
app/astride/evaluators/new.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export default function(node, {evaluate, scope}) {
|
||||||
|
let asyncArgs = false;
|
||||||
|
const args = [];
|
||||||
|
for (let i = 0; i < node.arguments.length; i++) {
|
||||||
|
const arg = node.arguments[i];
|
||||||
|
const {async, value} = evaluate(arg, {scope});
|
||||||
|
asyncArgs ||= async;
|
||||||
|
args.push(value);
|
||||||
|
}
|
||||||
|
const {callee} = node;
|
||||||
|
const {async, value} = evaluate(callee, {scope});
|
||||||
|
if (asyncArgs || async) {
|
||||||
|
return {
|
||||||
|
async: true,
|
||||||
|
value: Promise
|
||||||
|
.all([value, Promise.all(args)])
|
||||||
|
.then(([callee, args]) => new callee(...args)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {value: new value(...args)};
|
||||||
|
}
|
48
app/astride/evaluators/new.test.js
Normal file
48
app/astride/evaluators/new.test.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import {expect, test} from 'vitest';
|
||||||
|
|
||||||
|
import evaluate from '@/astride/evaluate.js';
|
||||||
|
import expression from '@/astride/test/expression.js';
|
||||||
|
|
||||||
|
const scopeTest = test.extend({
|
||||||
|
scope: async ({}, use) => {
|
||||||
|
await use({
|
||||||
|
S: {O: {}},
|
||||||
|
get(k) { return this.S[k]; },
|
||||||
|
set(k, v) { return this.S[k] = v; }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
class C {
|
||||||
|
foo = 'bar';
|
||||||
|
constructor(a, b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeTest('creates instances', async ({scope}) => {
|
||||||
|
scope.set('C', C);
|
||||||
|
const evaluated = evaluate(await expression('new C(1, 2)'), {scope});
|
||||||
|
expect(evaluated.value)
|
||||||
|
.to.deep.include({
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
scopeTest('creates instances with async dependencies', async ({scope}) => {
|
||||||
|
scope.set('C', C);
|
||||||
|
scope.set('a', Promise.resolve(1));
|
||||||
|
scope.set('b', Promise.resolve(2));
|
||||||
|
const evaluated = evaluate(await expression('new C(await a, await b)'), {scope});
|
||||||
|
expect(evaluated.async)
|
||||||
|
.to.equal(true);
|
||||||
|
expect(await evaluated.value)
|
||||||
|
.to.deep.include({
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
|
});
|
|
@ -150,7 +150,7 @@ export default class Sandbox {
|
||||||
case 'ObjectExpression':
|
case 'ObjectExpression':
|
||||||
case 'Identifier':
|
case 'Identifier':
|
||||||
case 'MemberExpression':
|
case 'MemberExpression':
|
||||||
case 'UnaryExpression':
|
case 'NewExpression':
|
||||||
case 'UpdateExpression': {
|
case 'UpdateExpression': {
|
||||||
result = this.evaluateToResult(node);
|
result = this.evaluateToResult(node);
|
||||||
if (result.yield) {
|
if (result.yield) {
|
||||||
|
@ -523,6 +523,31 @@ export default class Sandbox {
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'UnaryExpression': {
|
||||||
|
if ('delete' === node.operator) {
|
||||||
|
let property;
|
||||||
|
if (node.argument.computed) {
|
||||||
|
property = this.executeSync(node.argument.property, depth + 1);
|
||||||
|
if (property.yield) {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
property = {value: node.argument.property.name, yield: YIELD_NONE};
|
||||||
|
}
|
||||||
|
const scope = this.scopes.get(node);
|
||||||
|
const object = scope.get(node.argument.object.name, undefined);
|
||||||
|
delete object[property.value];
|
||||||
|
result = {value: true, yield: YIELD_NONE};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = this.evaluateToResult(node);
|
||||||
|
if (result.yield) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'VariableDeclaration': {
|
case 'VariableDeclaration': {
|
||||||
let skipping = isReplaying;
|
let skipping = isReplaying;
|
||||||
for (const child of node.declarations) {
|
for (const child of node.declarations) {
|
||||||
|
|
|
@ -121,6 +121,69 @@ test('runs arbitrary number of ops', async () => {
|
||||||
.to.equal(150);
|
.to.equal(150);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('instantiates', async () => {
|
||||||
|
const sandbox = new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
const x = new C(1, 2);
|
||||||
|
const y = new C(await a, await b);
|
||||||
|
`),
|
||||||
|
{
|
||||||
|
a: Promise.resolve(1),
|
||||||
|
b: Promise.resolve(2),
|
||||||
|
C: class {
|
||||||
|
foo = 'bar';
|
||||||
|
constructor(a, b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await finish(sandbox);
|
||||||
|
expect(sandbox.context.x)
|
||||||
|
.to.deep.include({
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
|
expect(sandbox.context.y)
|
||||||
|
.to.deep.include({
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deletes', async () => {
|
||||||
|
const sandbox = new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
delete foo.one;
|
||||||
|
delete foo['two'];
|
||||||
|
const x = 'three';
|
||||||
|
delete foo[x];
|
||||||
|
const y = 'four';
|
||||||
|
delete foo[await y];
|
||||||
|
`),
|
||||||
|
{
|
||||||
|
foo: {
|
||||||
|
one: 1,
|
||||||
|
two: 2,
|
||||||
|
three: 3,
|
||||||
|
four: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await finish(sandbox);
|
||||||
|
expect(sandbox.context.foo.one)
|
||||||
|
.to.be.undefined;
|
||||||
|
expect(sandbox.context.foo.two)
|
||||||
|
.to.be.undefined;
|
||||||
|
expect(sandbox.context.foo.three)
|
||||||
|
.to.be.undefined;
|
||||||
|
expect(sandbox.context.foo.four)
|
||||||
|
.to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
test('evaluates conditional branches', async () => {
|
test('evaluates conditional branches', async () => {
|
||||||
const sandbox = new Sandbox(
|
const sandbox = new Sandbox(
|
||||||
await parse(`
|
await parse(`
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const TRAVERSAL_PATH = {
|
||||||
Identifier: [],
|
Identifier: [],
|
||||||
IfStatement: ['alternate', 'consequent', 'test'],
|
IfStatement: ['alternate', 'consequent', 'test'],
|
||||||
MemberExpression: ['object', 'property'],
|
MemberExpression: ['object', 'property'],
|
||||||
|
NewExpression: ['arguments', 'callee'],
|
||||||
Literal: [],
|
Literal: [],
|
||||||
LogicalExpression: ['left', 'right'],
|
LogicalExpression: ['left', 'right'],
|
||||||
ObjectExpression: ['properties'],
|
ObjectExpression: ['properties'],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
import {distance, intersects} from '@/util/math.js';
|
import {distance, intersects, transform} from '@/util/math.js';
|
||||||
|
|
||||||
import vector2d from './helpers/vector-2d';
|
import vector2d from './helpers/vector-2d';
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ export default class Collider extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const {ecs} = this;
|
const {ecs} = this;
|
||||||
return class ColliderInstance extends super.instanceFromSchema() {
|
return class ColliderInstance extends super.instanceFromSchema() {
|
||||||
|
$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
||||||
|
$$aabbs = [];
|
||||||
collidingWith = {};
|
collidingWith = {};
|
||||||
get aabb() {
|
get aabb() {
|
||||||
const {Position: {x: px, y: py}} = ecs.get(this.entity);
|
const {Position: {x: px, y: py}} = ecs.get(this.entity);
|
||||||
|
@ -76,34 +78,36 @@ export default class Collider extends Component {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
updateAabbs() {
|
||||||
}
|
this.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
||||||
async load(instance) {
|
this.$$aabbs = [];
|
||||||
instance.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
const {bodies} = this;
|
||||||
instance.$$aabbs = [];
|
const {Direction: {direction = 0} = {}} = ecs.get(this.entity);
|
||||||
const {bodies} = instance;
|
|
||||||
for (const body of bodies) {
|
for (const body of bodies) {
|
||||||
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
||||||
for (const point of body.points) {
|
for (const point of transform(body.points, {rotation: direction})) {
|
||||||
const x = point.x;
|
const {x, y} = point;
|
||||||
const y = point.y;
|
|
||||||
if (x < x0) x0 = x;
|
if (x < x0) x0 = x;
|
||||||
if (x < instance.$$aabb.x0) instance.$$aabb.x0 = x;
|
if (x < this.$$aabb.x0) this.$$aabb.x0 = x;
|
||||||
if (x > x1) x1 = x;
|
if (x > x1) x1 = x;
|
||||||
if (x > instance.$$aabb.x1) instance.$$aabb.x1 = x;
|
if (x > this.$$aabb.x1) this.$$aabb.x1 = x;
|
||||||
if (y < y0) y0 = y;
|
if (y < y0) y0 = y;
|
||||||
if (y < instance.$$aabb.y0) instance.$$aabb.y0 = y;
|
if (y < this.$$aabb.y0) this.$$aabb.y0 = y;
|
||||||
if (y > y1) y1 = y;
|
if (y > y1) y1 = y;
|
||||||
if (y > instance.$$aabb.y1) instance.$$aabb.y1 = y;
|
if (y > this.$$aabb.y1) this.$$aabb.y1 = y;
|
||||||
}
|
}
|
||||||
instance.$$aabbs.push({
|
this.$$aabbs.push({
|
||||||
x0: x0 > x1 ? x1 : x0,
|
x0: x0 > x1 ? x1 : x0,
|
||||||
x1: x0 > x1 ? x0 : x1,
|
x1: x0 > x1 ? x0 : x1,
|
||||||
y0: y0 > y1 ? y1 : y0,
|
y0: y0 > y1 ? y1 : y0,
|
||||||
y1: y0 > y1 ? y0 : y1,
|
y1: y0 > y1 ? y0 : y1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async load(instance) {
|
||||||
|
instance.updateAabbs();
|
||||||
// heavy handed...
|
// heavy handed...
|
||||||
if ('undefined' !== typeof window) {
|
if ('undefined' !== typeof window) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,6 +11,9 @@ export default class Sprite extends Component {
|
||||||
return super.animation;
|
return super.animation;
|
||||||
}
|
}
|
||||||
set animation(animation) {
|
set animation(animation) {
|
||||||
|
if (this.$$animation === animation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
super.animation = animation;
|
super.animation = animation;
|
||||||
// eslint-disable-next-line no-self-assign
|
// eslint-disable-next-line no-self-assign
|
||||||
this.frame = this.frame;
|
this.frame = this.frame;
|
||||||
|
@ -31,6 +34,27 @@ export default class Sprite extends Component {
|
||||||
}
|
}
|
||||||
return this.$$sourceJson.animations[this.animation].length;
|
return this.$$sourceJson.animations[this.animation].length;
|
||||||
}
|
}
|
||||||
|
hasAnimation(animation) {
|
||||||
|
if (
|
||||||
|
!this.$$sourceJson.animations
|
||||||
|
|| !(animation in this.$$sourceJson.animations)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get rotates() {
|
||||||
|
if (!this.$$sourceJson.meta) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return 'rotation' in this.$$sourceJson.meta;
|
||||||
|
}
|
||||||
|
get rotation() {
|
||||||
|
if (!this.$$sourceJson.meta) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this.$$sourceJson.meta.rotation;
|
||||||
|
}
|
||||||
get scale() {
|
get scale() {
|
||||||
return {x: this.scaleX, y: this.scaleY};
|
return {x: this.scaleX, y: this.scaleY};
|
||||||
}
|
}
|
||||||
|
@ -51,6 +75,7 @@ export default class Sprite extends Component {
|
||||||
super.markChange(entityId, key, value);
|
super.markChange(entityId, key, value);
|
||||||
}
|
}
|
||||||
static properties = {
|
static properties = {
|
||||||
|
alpha: {defaultValue: 1, type: 'float32'},
|
||||||
anchorX: {defaultValue: 0.5, type: 'float32'},
|
anchorX: {defaultValue: 0.5, type: 'float32'},
|
||||||
anchorY: {defaultValue: 0.5, type: 'float32'},
|
anchorY: {defaultValue: 0.5, type: 'float32'},
|
||||||
animation: {type: 'string'},
|
animation: {type: 'string'},
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default class Wielder extends Component {
|
||||||
const {Inventory, Wielder} = ecs.get(this.entity);
|
const {Inventory, Wielder} = ecs.get(this.entity);
|
||||||
return Inventory.item(Wielder.activeSlot + 1);
|
return Inventory.item(Wielder.activeSlot + 1);
|
||||||
}
|
}
|
||||||
useActiveItem(state) {
|
useActiveItem([state, where]) {
|
||||||
const entity = ecs.get(this.entity);
|
const entity = ecs.get(this.entity);
|
||||||
const {Ticking} = entity;
|
const {Ticking} = entity;
|
||||||
const activeItem = this.activeItem();
|
const activeItem = this.activeItem();
|
||||||
|
@ -19,6 +19,7 @@ export default class Wielder extends Component {
|
||||||
script = script.clone();
|
script = script.clone();
|
||||||
script.context.ecs = ecs;
|
script.context.ecs = ecs;
|
||||||
script.context.item = activeItem;
|
script.context.item = activeItem;
|
||||||
|
script.context.where = where;
|
||||||
script.context.wielder = entity;
|
script.context.wielder = entity;
|
||||||
Ticking.add(script.ticker());
|
Ticking.add(script.ticker());
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,28 +242,17 @@ export default class Ecs {
|
||||||
this.Components[i].destroyMany(destroying[i]);
|
this.Components[i].destroyMany(destroying[i]);
|
||||||
}
|
}
|
||||||
for (const entityId of entityIds) {
|
for (const entityId of entityIds) {
|
||||||
this.$$entities[entityId] = undefined;
|
delete this.$$entities[entityId];
|
||||||
this.diff[entityId] = false;
|
this.diff[entityId] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get entities() {
|
get entities() {
|
||||||
const it = Object.values(this.$$entities).values();
|
const ids = [];
|
||||||
return {
|
for (const entity of Object.values(this.$$entities)) {
|
||||||
[Symbol.iterator]() {
|
ids.push(entity.id);
|
||||||
return this;
|
|
||||||
},
|
|
||||||
next: () => {
|
|
||||||
let result = it.next();
|
|
||||||
while (!result.done && !result.value) {
|
|
||||||
result = it.next();
|
|
||||||
}
|
}
|
||||||
if (result.done) {
|
return ids;
|
||||||
return {done: true, value: undefined};
|
|
||||||
}
|
|
||||||
return {done: false, value: result.value.id};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(entityId) {
|
get(entityId) {
|
||||||
|
|
|
@ -40,7 +40,16 @@ export default class Colliders extends System {
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
const collisions = new Map();
|
const collisions = 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'])) {
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
|
if (!entity.Collider) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.updateHash(entity);
|
this.updateHash(entity);
|
||||||
}
|
}
|
||||||
for (const entity of this.ecs.changed(['Position'])) {
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {System} from '@/ecs/index.js';
|
import {System} from '@/ecs/index.js';
|
||||||
import {HALF_PI} from '@/util/math.js';
|
import {TAU} from '@/util/math.js';
|
||||||
|
|
||||||
export default class ControlDirection extends System {
|
export default class ControlDirection extends System {
|
||||||
|
|
||||||
|
@ -9,18 +9,20 @@ export default class ControlDirection extends System {
|
||||||
if (locked) {
|
if (locked) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (moveUp > 0) {
|
if (
|
||||||
Direction.direction = HALF_PI * 3;
|
0 === moveRight
|
||||||
}
|
&& 0 === moveDown
|
||||||
if (moveDown > 0) {
|
&& 0 === moveLeft
|
||||||
Direction.direction = HALF_PI * 1;
|
&& 0 === moveUp
|
||||||
}
|
) {
|
||||||
if (moveLeft > 0) {
|
continue;
|
||||||
Direction.direction = HALF_PI * 2;
|
|
||||||
}
|
|
||||||
if (moveRight > 0) {
|
|
||||||
Direction.direction = HALF_PI * 0;
|
|
||||||
}
|
}
|
||||||
|
Direction.direction = (
|
||||||
|
TAU + Math.atan2(
|
||||||
|
moveDown - moveUp,
|
||||||
|
moveRight - moveLeft,
|
||||||
|
)
|
||||||
|
) % TAU;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default class SpriteDirection extends System {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Direction) {
|
if (Direction) {
|
||||||
|
if (!Sprite.rotates) {
|
||||||
const name = {
|
const name = {
|
||||||
0: 'right',
|
0: 'right',
|
||||||
1: 'down',
|
1: 'down',
|
||||||
|
@ -32,10 +33,13 @@ export default class SpriteDirection extends System {
|
||||||
};
|
};
|
||||||
parts.push(name[Direction.quantize(4)]);
|
parts.push(name[Direction.quantize(4)]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
|
if (Sprite.hasAnimation(parts.join(':'))) {
|
||||||
Sprite.animation = parts.join(':');
|
Sprite.animation = parts.join(':');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,9 @@ function textureFromAsset(asset, animation, frame) {
|
||||||
}
|
}
|
||||||
let texture;
|
let texture;
|
||||||
if (asset.data.animations) {
|
if (asset.data.animations) {
|
||||||
|
if (!animation) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
texture = asset.animations[animation][frame];
|
texture = asset.animations[animation][frame];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -23,7 +26,7 @@ export default function Sprite({entity, ...rest}) {
|
||||||
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 {anchor, animation, frame, scale, source} = entity.Sprite;
|
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(() => {
|
||||||
|
@ -59,8 +62,10 @@ export default function Sprite({entity, ...rest}) {
|
||||||
<>
|
<>
|
||||||
{texture && (
|
{texture && (
|
||||||
<PixiSprite
|
<PixiSprite
|
||||||
|
alpha={alpha}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
ref={setMounted}
|
ref={setMounted}
|
||||||
|
{...(rotates ? {rotation: entity.Direction.direction + rotation} : {})}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
texture={texture}
|
texture={texture}
|
||||||
x={Math.round(entity.Position.x)}
|
x={Math.round(entity.Position.x)}
|
||||||
|
@ -70,8 +75,10 @@ export default function Sprite({entity, ...rest}) {
|
||||||
)}
|
)}
|
||||||
{normalsTexture && (
|
{normalsTexture && (
|
||||||
<PixiSprite
|
<PixiSprite
|
||||||
|
alpha={alpha}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
ref={setNormalsMounted}
|
ref={setNormalsMounted}
|
||||||
|
{...(rotates ? {rotation: entity.Direction.direction + rotation} : {})}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
texture={normalsTexture}
|
texture={normalsTexture}
|
||||||
x={Math.round(entity.Position.x)}
|
x={Math.round(entity.Position.x)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {memo, useEffect, useRef, useState} from 'react';
|
import {memo, 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';
|
||||||
|
@ -166,10 +166,6 @@ function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ' ': {
|
|
||||||
actionPayload = {type: 'use', value: KEY_MAP[type]};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Tab': {
|
case 'Tab': {
|
||||||
if ('keyDown' === type) {
|
if ('keyDown' === type) {
|
||||||
if (isInventoryOpen) {
|
if (isInventoryOpen) {
|
||||||
|
@ -390,6 +386,9 @@ function Ui({disconnected}) {
|
||||||
if (!update) {
|
if (!update) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (update.Direction && entity.Collider) {
|
||||||
|
entity.Collider.updateAabbs();
|
||||||
|
}
|
||||||
if (update.Sound?.play) {
|
if (update.Sound?.play) {
|
||||||
for (const sound of update.Sound.play) {
|
for (const sound of update.Sound.play) {
|
||||||
(new Audio(sound)).play();
|
(new Audio(sound)).play();
|
||||||
|
@ -430,6 +429,30 @@ function Ui({disconnected}) {
|
||||||
document.body.removeEventListener('contextmenu', onContextMenu);
|
document.body.removeEventListener('contextmenu', onContextMenu);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
const computePosition = useCallback(({clientX, clientY}) => {
|
||||||
|
if (!gameRef.current || !mainEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {top, left, width} = gameRef.current.getBoundingClientRect();
|
||||||
|
const master = ecs.get(1);
|
||||||
|
if (!master) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {Camera} = ecs.get(mainEntity);
|
||||||
|
const size = width / RESOLUTION.x;
|
||||||
|
const camera = {
|
||||||
|
x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
|
||||||
|
y: ((Camera.y * scale) - (RESOLUTION.y / 2)),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: (((clientX - left) / size) + camera.x) / scale,
|
||||||
|
y: (((clientY - top) / size) + camera.y) / scale,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
ecs,
|
||||||
|
mainEntity,
|
||||||
|
scale,
|
||||||
|
]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.ui}
|
className={styles.ui}
|
||||||
|
@ -455,39 +478,19 @@ function Ui({disconnected}) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const where = computePosition(event);
|
||||||
switch (event.button) {
|
switch (event.button) {
|
||||||
case 0:
|
case 0:
|
||||||
if (devtoolsIsOpen) {
|
if (devtoolsIsOpen) {
|
||||||
if (!gameRef.current || !mainEntity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {top, left, width} = gameRef.current.getBoundingClientRect();
|
|
||||||
const master = ecs.get(1);
|
|
||||||
if (!master) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {Camera} = ecs.get(mainEntity);
|
|
||||||
const size = width / RESOLUTION.x;
|
|
||||||
const client = {
|
|
||||||
x: (event.clientX - left) / size,
|
|
||||||
y: (event.clientY - top) / size,
|
|
||||||
};
|
|
||||||
const camera = {
|
|
||||||
x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
|
|
||||||
y: ((Camera.y * scale) - (RESOLUTION.y / 2)),
|
|
||||||
}
|
|
||||||
devEventsChannel.invoke(
|
devEventsChannel.invoke(
|
||||||
'click',
|
'click',
|
||||||
{
|
where,
|
||||||
x: (client.x + camera.x) / scale,
|
|
||||||
y: (client.y + camera.y) / scale,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
client.send({
|
client.send({
|
||||||
type: 'Action',
|
type: 'Action',
|
||||||
payload: {type: 'use', value: 1},
|
payload: {type: 'use', value: [1, where]},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -508,11 +511,12 @@ function Ui({disconnected}) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const where = computePosition(event);
|
||||||
switch (event.button) {
|
switch (event.button) {
|
||||||
case 0:
|
case 0:
|
||||||
client.send({
|
client.send({
|
||||||
type: 'Action',
|
type: 'Action',
|
||||||
payload: {type: 'use', value: 0},
|
payload: {type: 'use', value: [0, where]},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
|
|
@ -88,6 +88,26 @@ export default async function createHomestead(id) {
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Interlocutor: {},
|
Interlocutor: {},
|
||||||
|
Inventory: {
|
||||||
|
slots: {
|
||||||
|
2: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/watering-can/watering-can.json',
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/tomato-seeds/tomato-seeds.json',
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/hoe/hoe.json',
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/brush/brush.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Position: {x: 200, y: 200},
|
Position: {x: 200, y: 200},
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorX: 0.5,
|
anchorX: 0.5,
|
||||||
|
|
|
@ -26,22 +26,6 @@ export default async function createPlayer(id) {
|
||||||
qty: 100,
|
qty: 100,
|
||||||
source: '/assets/potion/potion.json',
|
source: '/assets/potion/potion.json',
|
||||||
},
|
},
|
||||||
2: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/watering-can/watering-can.json',
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/tomato-seeds/tomato-seeds.json',
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/hoe/hoe.json',
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/brush/brush.json',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Health: {health: 100},
|
Health: {health: 100},
|
||||||
|
|
|
@ -316,3 +316,52 @@ export function removeCollinear([...vertices]) {
|
||||||
}
|
}
|
||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transform(
|
||||||
|
vertices,
|
||||||
|
{
|
||||||
|
rotation = 0,
|
||||||
|
scale = 1,
|
||||||
|
translation = {x: 0, y: 0},
|
||||||
|
origin = {x: 0, y: 0},
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
// nop
|
||||||
|
if (0 === rotation && 1 === scale && 0 === translation.x && 0 === translation.y) {
|
||||||
|
return vertices;
|
||||||
|
}
|
||||||
|
const transformed = [];
|
||||||
|
// scale
|
||||||
|
for (const vertice of vertices) {
|
||||||
|
if (1 === scale) {
|
||||||
|
transformed.push({x: vertice.x, y: vertice.y});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
transformed.push({
|
||||||
|
x: origin.x + (vertice.x - origin.x) * scale,
|
||||||
|
y: origin.y + (vertice.y - origin.y) * scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// rotation
|
||||||
|
rotation = rotation % TAU;
|
||||||
|
if (0 !== rotation) {
|
||||||
|
for (const vertice of transformed) {
|
||||||
|
let a = rotation + Math.atan2(
|
||||||
|
vertice.y - origin.y,
|
||||||
|
vertice.x - origin.x,
|
||||||
|
);
|
||||||
|
a = (a >= 0 || a < TAU) ? a : (a % TAU + TAU) % TAU;
|
||||||
|
const d = distance(vertice, origin);
|
||||||
|
vertice.x = origin.x + d * Math.cos(a);
|
||||||
|
vertice.y = origin.y + d * Math.sin(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// translation
|
||||||
|
if (0 !== translation.x || 0 !== translation.y) {
|
||||||
|
for (const vertice of transformed) {
|
||||||
|
vertice.x += translation.x;
|
||||||
|
vertice.y += translation.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
87
app/util/math.test.js
Normal file
87
app/util/math.test.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import {expect, test} from 'vitest';
|
||||||
|
|
||||||
|
import * as MathUtil from './math.js';
|
||||||
|
|
||||||
|
test('transforms vertices', async () => {
|
||||||
|
const expectCloseTo = (l, r) => {
|
||||||
|
expect(l.length)
|
||||||
|
.to.equal(r.length);
|
||||||
|
for (let i = 0; i < l.length; ++i) {
|
||||||
|
expect(l[i].x)
|
||||||
|
.to.be.closeTo(r[i].x, 0.0001);
|
||||||
|
expect(l[i].y)
|
||||||
|
.to.be.closeTo(r[i].y, 0.0001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const vertices = [
|
||||||
|
{x: -1, y: -1},
|
||||||
|
{x: 1, y: -1},
|
||||||
|
{x: 1, y: 1},
|
||||||
|
{x: -1, y: 1},
|
||||||
|
];
|
||||||
|
expect(MathUtil.transform(vertices, {}))
|
||||||
|
.to.deep.equal(vertices);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {scale: 2}),
|
||||||
|
[
|
||||||
|
{x: -2, y: -2},
|
||||||
|
{x: 2, y: -2},
|
||||||
|
{x: 2, y: 2},
|
||||||
|
{x: -2, y: 2},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI}),
|
||||||
|
[
|
||||||
|
{x: 0, y: -Math.sqrt(2)},
|
||||||
|
{x: Math.sqrt(2), y: 0},
|
||||||
|
{x: 0, y: Math.sqrt(2)},
|
||||||
|
{x: -Math.sqrt(2), y: 0},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, scale: 2}),
|
||||||
|
[
|
||||||
|
{x: 0, y: -Math.sqrt(2) * 2},
|
||||||
|
{x: Math.sqrt(2) * 2, y: 0},
|
||||||
|
{x: 0, y: Math.sqrt(2) * 2},
|
||||||
|
{x: -Math.sqrt(2) * 2, y: 0},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {translation: {x: 10, y: 10}}),
|
||||||
|
[
|
||||||
|
{x: 9, y: 9},
|
||||||
|
{x: 11, y: 9},
|
||||||
|
{x: 11, y: 11},
|
||||||
|
{x: 9, y: 11},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, translation: {x: 10, y: 10}}),
|
||||||
|
[
|
||||||
|
{x: 10, y: 10 - Math.sqrt(2)},
|
||||||
|
{x: 10 + Math.sqrt(2), y: 10},
|
||||||
|
{x: 10, y: 10 + Math.sqrt(2)},
|
||||||
|
{x: 10 - Math.sqrt(2), y: 10},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {scale: 2, translation: {x: 10, y: 10}}),
|
||||||
|
[
|
||||||
|
{x: 8, y: 8},
|
||||||
|
{x: 12, y: 8},
|
||||||
|
{x: 12, y: 12},
|
||||||
|
{x: 8, y: 12},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expectCloseTo(
|
||||||
|
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, scale: 2, translation: {x: 10, y: 10}}),
|
||||||
|
[
|
||||||
|
{x: 10, y: 10 - Math.sqrt(2) * 2},
|
||||||
|
{x: 10 + Math.sqrt(2) * 2, y: 10},
|
||||||
|
{x: 10, y: 10 + Math.sqrt(2) * 2},
|
||||||
|
{x: 10 - Math.sqrt(2) * 2, y: 10},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user