avocado-old/packages/physics/traits/collider.trait.js
2020-06-23 19:44:31 -05:00

315 lines
7.2 KiB
JavaScript

import {Actions, compile, Context} from '@avocado/behavior';
import {compose, TickingPromise} from '@avocado/core';
import {StateProperty, Trait} from '@avocado/entity';
import {Rectangle, Vector} from '@avocado/math';
const decorate = compose(
StateProperty('isCheckingCollisions'),
StateProperty('isColliding'),
);
export default class Collider extends decorate(Trait) {
static behaviorTypes() {
return {
collidesWith: {
advanced: true,
type: 'bool',
label: 'I would collide with $1.',
args: [
['other', {
type: 'entity',
}],
],
},
doesNotCollideWith: {
advanced: true,
type: 'bool',
label: 'I would not collide with $1.',
args: [
['other', {
type: 'entity',
}],
],
},
setDoesCollideWith: {
advanced: true,
type: 'void',
label: 'Set $1 as colliding with myself.',
args: [
['other', {
type: 'entity',
}],
],
},
setDoesNotCollideWith: {
advanced: true,
type: 'void',
label: 'Set $1 as not colliding with myself.',
args: [
['other', {
type: 'entity',
}],
],
},
};
}
static defaultParams() {
return {
activeCollision: false,
collidesWithGroups: [
'default',
],
collisionEndActions: {
type: 'expressions',
expressions: [],
},
collisionStartActions: {
type: 'expressions',
expressions: [],
},
collisionGroup: 'default',
isSensor: false,
}
}
static defaultState() {
return {
isCheckingCollisions: true,
isColliding: true,
}
}
static describeParams() {
return {
activeCollision: {
type: 'bool',
label: 'Actively check collisions',
},
collidesWithGroups: {
type: 'object',
label: 'Collides with groups',
},
collisionEndActions: {
type: 'expressions',
label: 'Collision ending actions',
},
collisionStartActions: {
type: 'expressions',
label: 'Collision starting actions',
},
collisionGroup: {
type: 'string',
label: 'Collision group',
},
isSensor: {
type: 'bool',
label: 'Is sensor',
},
};
}
static describeState() {
return {
isCheckingCollisions: {
type: 'bool',
label: 'Is checking collisions',
},
isColliding: {
type: 'bool',
label: 'Is able to collide',
},
}
}
static type() {
return 'collider';
}
constructor(entity, params, state) {
super(entity, params, state);
const {
collidesWithGroups,
collisionEndActions,
collisionGroup,
collisionStartActions,
isSensor,
} = this.params;
this._collidesWithGroups = collidesWithGroups;
this._collisionEndActions = collisionEndActions.length > 0
? new Actions(compile(collisionEndActions))
: undefined;
this._collisionStartActions = collisionStartActions.length > 0
? new Actions(compile(collisionStartActions))
: undefined;
this._collisionGroup = collisionGroup;
this._doesNotCollideWith = [];
this._isCollidingWith = [];
this._isSensor = isSensor;
}
destroy() {
this.releaseAllCollisions();
}
checkActiveCollision() {
if (!this.params.activeCollision) {
return;
}
const layer = this.entity.layer;
if (!layer) {
return;
}
const query = Rectangle.compose(
Vector.sub(this.entity.position, [32, 32]),
[64, 64],
);
const thisAabb = Rectangle.translated(
this.entity.shape.aabb,
this.entity.position
);
const entities = layer.visibleEntities(query);
for (let i = 0; i < entities.length; ++i) {
const entity = entities[i];
if (entity === this.entity) {
continue;
}
if (!entity.is('collider') || !entity.is('shaped') || entity.is('physical')) {
continue;
}
const otherAabb = Rectangle.translated(
entity.shape.aabb,
entity.position
);
if (Rectangle.intersects(thisAabb, otherAabb)) {
if (!this.isCollidingWithEntity(entity)) {
this.entity.emit('collisionStart', entity);
entity.emit('collisionStart', this.entity);
}
}
else {
if (this.isCollidingWithEntity(entity)) {
this.entity.emit('collisionEnd', entity);
entity.emit('collisionEnd', this.entity);
}
}
}
}
get collisionGroup() {
return this._collisionGroup;
}
get collidesWithGroups() {
return this._collidesWithGroups;
}
get isCollidingWith() {
return this._isCollidingWith;
}
isCollidingWithEntity(entity) {
return -1 !== this._isCollidingWith.indexOf(entity);
}
get isSensor() {
return this._isSensor;
}
pushCollisionTickingPromise(actions, other) {
const context = new Context({
entity: [this.entity, 'entity'],
other: [this.entity, 'entity'],
});
this.entity.addTickingPromise(actions.tickingPromise(context));
}
releaseAllCollisions() {
for (let i = 0; i < this._isCollidingWith.length; i++) {
const entity = this._isCollidingWith[i];
entity.emit('collisionEnd', this.entity);
}
this._isCollidingWith = [];
}
// eslint-disable-next-line class-methods-use-this
hooks() {
return {
contextTypeHints: () => [['other', 'entity']],
};
}
listeners() {
return {
collisionEnd: (other) => {
const index = this._isCollidingWith.indexOf(other);
if (-1 !== index) {
this._isCollidingWith.splice(index, 1);
if (this._collisionEndActions) {
this.pushCollisionTickingPromise(this._collisionEndActions, other);
}
}
},
collisionStart: (other) => {
const index = this._isCollidingWith.indexOf(other);
if (-1 === index) {
this._isCollidingWith.push(other);
if (this._collisionStartActions) {
this.pushCollisionTickingPromise(this._collisionStartActions, other);
}
}
},
removedFromRoom: () => {
this.releaseAllCollisions();
},
};
}
methods() {
return {
collidesWith: (entity) => {
if (!this.entity.isColliding || !entity.isColliding) {
return false;
}
if (-1 !== this._doesNotCollideWith.indexOf(entity)) {
return false;
}
const collisionGroup = entity.collisionGroup;
return -1 !== this._collidesWithGroups.indexOf(collisionGroup);
},
doesNotCollideWith: (entity) => {
return !this.entity.collidesWith(entity);
},
setDoesCollideWith: (entity) => {
const index = this._doesNotCollideWith.indexOf(entity);
if (-1 !== index) {
this._doesNotCollideWith.splice(index, 1);
}
},
setDoesNotCollideWith: (entity) => {
if (-1 === this._doesNotCollideWith.indexOf(entity)) {
this._doesNotCollideWith.push(entity);
}
},
};
}
tick(elapsed) {
if (AVOCADO_SERVER) {
this.checkActiveCollision();
}
}
}