2020-05-09 14:45:23 +00:00
|
|
|
/* typehints:start */
|
|
|
|
import { DrawParameters } from "../core/draw_parameters";
|
|
|
|
import { Component } from "./component";
|
|
|
|
/* typehints:end */
|
|
|
|
|
2020-06-28 17:34:10 +00:00
|
|
|
import { GameRoot, enumLayer } from "./root";
|
2020-05-09 14:45:23 +00:00
|
|
|
import { globalConfig } from "../core/config";
|
2020-05-14 19:54:11 +00:00
|
|
|
import { enumDirectionToVector, enumDirectionToAngle } from "../core/vector";
|
2020-05-09 14:45:23 +00:00
|
|
|
import { BasicSerializableObject, types } from "../savegame/serialization";
|
|
|
|
import { EntityComponentStorage } from "./entity_components";
|
|
|
|
import { Loader } from "../core/loader";
|
|
|
|
import { drawRotatedSprite } from "../core/draw_utils";
|
2020-05-14 19:54:11 +00:00
|
|
|
import { gComponentRegistry } from "../core/global_registries";
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
export class Entity extends BasicSerializableObject {
|
|
|
|
/**
|
|
|
|
* @param {GameRoot} root
|
|
|
|
*/
|
|
|
|
constructor(root) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle to the global game root
|
|
|
|
*/
|
|
|
|
this.root = root;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The components of the entity
|
|
|
|
*/
|
|
|
|
this.components = new EntityComponentStorage();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether this entity was registered on the @see EntityManager so far
|
|
|
|
*/
|
|
|
|
this.registered = false;
|
|
|
|
|
2020-06-28 17:34:10 +00:00
|
|
|
/**
|
|
|
|
* On which layer this entity is
|
|
|
|
*/
|
|
|
|
this.layer = enumLayer.regular;
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Internal entity unique id, set by the @see EntityManager
|
|
|
|
*/
|
|
|
|
this.uid = 0;
|
|
|
|
|
|
|
|
/* typehints:start */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores if this entity is destroyed, set by the @see EntityManager
|
|
|
|
* @type {boolean} */
|
|
|
|
this.destroyed;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores if this entity is queued to get destroyed in the next tick
|
|
|
|
* of the @see EntityManager
|
|
|
|
* @type {boolean} */
|
|
|
|
this.queuedForDestroy;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores the reason why this entity was destroyed
|
|
|
|
* @type {string} */
|
|
|
|
this.destroyReason;
|
|
|
|
|
|
|
|
/* typehints:end */
|
|
|
|
}
|
|
|
|
|
|
|
|
static getId() {
|
|
|
|
return "Entity";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see BasicSerializableObject.getSchema
|
|
|
|
* @returns {import("../savegame/serialization").Schema}
|
|
|
|
*/
|
|
|
|
static getSchema() {
|
|
|
|
return {
|
|
|
|
uid: types.uint,
|
2020-05-14 20:46:31 +00:00
|
|
|
components: types.keyValueMap(types.objData(gComponentRegistry)),
|
2020-06-28 17:34:10 +00:00
|
|
|
layer: types.enum(enumLayer),
|
2020-05-09 14:45:23 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-27 12:30:59 +00:00
|
|
|
* Returns a clone of this entity without contents
|
2020-05-09 14:45:23 +00:00
|
|
|
*/
|
2020-05-27 12:30:59 +00:00
|
|
|
duplicateWithoutContents() {
|
|
|
|
const clone = new Entity(this.root);
|
|
|
|
for (const key in this.components) {
|
|
|
|
clone.components[key] = this.components[key].duplicateWithoutContents();
|
|
|
|
}
|
2020-06-28 17:34:10 +00:00
|
|
|
clone.layer = this.layer;
|
2020-05-27 12:30:59 +00:00
|
|
|
return clone;
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal destroy callback
|
|
|
|
*/
|
|
|
|
internalDestroyCallback() {
|
|
|
|
assert(!this.destroyed, "Can not destroy entity twice");
|
|
|
|
this.destroyed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new component, only possible until the entity is registered on the entity manager,
|
|
|
|
* after that use @see EntityManager.addDynamicComponent
|
|
|
|
* @param {Component} componentInstance
|
|
|
|
* @param {boolean} force Used by the entity manager. Internal parameter, do not change
|
|
|
|
*/
|
|
|
|
addComponent(componentInstance, force = false) {
|
2020-05-20 13:51:06 +00:00
|
|
|
if (!force && this.registered) {
|
|
|
|
this.root.entityMgr.attachDynamicComponent(this, componentInstance);
|
|
|
|
return;
|
|
|
|
}
|
2020-05-09 14:45:23 +00:00
|
|
|
assert(force || !this.registered, "Entity already registered, use EntityManager.addDynamicComponent");
|
|
|
|
const id = /** @type {typeof Component} */ (componentInstance.constructor).getId();
|
|
|
|
assert(!this.components[id], "Component already present");
|
|
|
|
this.components[id] = componentInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a given component, only possible until the entity is registered on the entity manager,
|
|
|
|
* after that use @see EntityManager.removeDynamicComponent
|
|
|
|
* @param {typeof Component} componentClass
|
2020-05-20 13:51:06 +00:00
|
|
|
* @param {boolean} force
|
2020-05-09 14:45:23 +00:00
|
|
|
*/
|
2020-05-20 13:51:06 +00:00
|
|
|
removeComponent(componentClass, force = false) {
|
|
|
|
if (!force && this.registered) {
|
|
|
|
this.root.entityMgr.removeDynamicComponent(this, componentClass);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(
|
|
|
|
force || !this.registered,
|
|
|
|
"Entity already registered, use EntityManager.removeDynamicComponent"
|
|
|
|
);
|
2020-05-09 14:45:23 +00:00
|
|
|
const id = componentClass.getId();
|
|
|
|
assert(this.components[id], "Component does not exist on entity");
|
|
|
|
delete this.components[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the entity, to override use @see Entity.drawImpl
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
*/
|
2020-06-13 08:57:29 +00:00
|
|
|
drawDebugOverlays(parameters) {
|
2020-05-09 14:45:23 +00:00
|
|
|
const context = parameters.context;
|
|
|
|
const staticComp = this.components.StaticMapEntity;
|
|
|
|
|
|
|
|
if (G_IS_DEV && staticComp && globalConfig.debug.showEntityBounds) {
|
|
|
|
if (staticComp) {
|
|
|
|
const transformed = staticComp.getTileSpaceBounds();
|
|
|
|
context.strokeStyle = "rgba(255, 0, 0, 0.5)";
|
|
|
|
context.lineWidth = 2;
|
|
|
|
// const boundsSize = 20;
|
|
|
|
context.beginPath();
|
|
|
|
context.rect(
|
|
|
|
transformed.x * globalConfig.tileSize,
|
|
|
|
transformed.y * globalConfig.tileSize,
|
|
|
|
transformed.w * globalConfig.tileSize,
|
|
|
|
transformed.h * globalConfig.tileSize
|
|
|
|
);
|
|
|
|
context.stroke();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (G_IS_DEV && staticComp && globalConfig.debug.showAcceptorEjectors) {
|
|
|
|
const ejectorComp = this.components.ItemEjector;
|
|
|
|
|
|
|
|
if (ejectorComp) {
|
|
|
|
const ejectorSprite = Loader.getSprite("sprites/debug/ejector_slot.png");
|
|
|
|
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
|
|
|
const slot = ejectorComp.slots[i];
|
2020-06-28 17:34:10 +00:00
|
|
|
if (slot.layer !== this.root.currentLayer) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-05-09 14:45:23 +00:00
|
|
|
const slotTile = staticComp.localTileToWorld(slot.pos);
|
|
|
|
const direction = staticComp.localDirectionToWorld(slot.direction);
|
|
|
|
const directionVector = enumDirectionToVector[direction];
|
2020-06-27 08:51:52 +00:00
|
|
|
const angle = Math.radians(enumDirectionToAngle[direction]);
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
context.globalAlpha = slot.item ? 1 : 0.2;
|
|
|
|
drawRotatedSprite({
|
|
|
|
parameters,
|
|
|
|
sprite: ejectorSprite,
|
|
|
|
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
|
|
|
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
|
|
|
angle,
|
|
|
|
size: globalConfig.tileSize * 0.25,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const acceptorComp = this.components.ItemAcceptor;
|
|
|
|
|
|
|
|
if (acceptorComp) {
|
|
|
|
const acceptorSprite = Loader.getSprite("sprites/debug/acceptor_slot.png");
|
|
|
|
for (let i = 0; i < acceptorComp.slots.length; ++i) {
|
|
|
|
const slot = acceptorComp.slots[i];
|
2020-06-28 17:34:10 +00:00
|
|
|
if (slot.layer !== this.root.currentLayer) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-05-09 14:45:23 +00:00
|
|
|
const slotTile = staticComp.localTileToWorld(slot.pos);
|
|
|
|
for (let k = 0; k < slot.directions.length; ++k) {
|
|
|
|
const direction = staticComp.localDirectionToWorld(slot.directions[k]);
|
|
|
|
const directionVector = enumDirectionToVector[direction];
|
2020-06-27 08:51:52 +00:00
|
|
|
const angle = Math.radians(enumDirectionToAngle[direction] + 180);
|
2020-05-09 14:45:23 +00:00
|
|
|
context.globalAlpha = 0.4;
|
|
|
|
drawRotatedSprite({
|
|
|
|
parameters,
|
|
|
|
sprite: acceptorSprite,
|
|
|
|
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
|
|
|
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
|
|
|
angle,
|
|
|
|
size: globalConfig.tileSize * 0.25,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context.globalAlpha = 1;
|
|
|
|
}
|
|
|
|
// this.drawImpl(parameters);
|
|
|
|
}
|
|
|
|
|
|
|
|
///// Helper interfaces
|
|
|
|
|
|
|
|
///// Interface to override by subclasses
|
|
|
|
|
|
|
|
/**
|
|
|
|
* override, should draw the entity
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
*/
|
|
|
|
drawImpl(parameters) {
|
|
|
|
abstract;
|
|
|
|
}
|
|
|
|
}
|