tessl install tessl/npm-ecos@0.2.0Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern
This guide covers how to add methods to entities using the ECOS factory system.
Methods are functions attached to entities that provide behavior. Methods are:
this contextconst { factory } = require('ecos');
factory.registerMethod('greet', function() {
console.log(`Hello, I'm ${this.name}!`);
});const personFactory = factory.create({
name: 'person',
props: ['name'],
methods: ['greet']
});const person = personFactory.create({ name: 'Alice' });
person.greet(); // "Hello, I'm Alice!"factory.registerMethod('takeDamage', function(amount) {
this.health = Math.max(0, this.health - amount);
console.log(`${this.name} took ${amount} damage. Health: ${this.health}`);
});
const combatFactory = factory.create({
name: 'combatant',
props: ['name', 'health'],
default: { health: 100 },
methods: ['takeDamage']
});
const warrior = combatFactory.create({ name: 'Warrior' });
warrior.takeDamage(30); // "Warrior took 30 damage. Health: 70"factory.registerMethod('calculateDamage', function(targetDefense) {
const baseDamage = this.attack;
const actualDamage = Math.max(1, baseDamage - targetDefense);
return actualDamage;
});
const attackerFactory = factory.create({
name: 'attacker',
props: ['name', 'attack'],
default: { attack: 20 },
methods: ['calculateDamage']
});
const attacker = attackerFactory.create({ name: 'Orc', attack: 50 });
const damage = attacker.calculateDamage(10); // Returns 40
console.log(`Damage dealt: ${damage}`);factory.registerMethod('heal', function(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
return this.health;
});
factory.registerMethod('fullHeal', function() {
return this.heal(this.maxHealth);
});
factory.registerMethod('isAlive', function() {
return this.health > 0;
});
const lifeFactory = factory.create({
name: 'living',
props: ['health', 'maxHealth'],
default: { health: 50, maxHealth: 100 },
methods: ['heal', 'fullHeal', 'isAlive']
});
const entity = lifeFactory.create();
entity.fullHeal(); // Calls heal() internally
console.log(entity.health); // 100
console.log(entity.isAlive()); // truefactory.registerMethod('levelUp', function() {
this.level += 1;
this.experience = 0;
this.maxHealth += 10;
this.health = this.maxHealth;
console.log(`Level up! Now level ${this.level}`);
});
factory.registerMethod('gainExperience', function(amount) {
this.experience += amount;
const expNeeded = this.level * 100;
if (this.experience >= expNeeded) {
this.levelUp();
}
});factory.registerMethod('canEquip', function(item) {
if (item.requiredLevel > this.level) {
return false;
}
if (item.requiredClass && item.requiredClass !== this.class) {
return false;
}
return true;
});
factory.registerMethod('equip', function(item) {
if (!this.canEquip(item)) {
console.log(`Cannot equip ${item.name}`);
return false;
}
this.equipment[item.slot] = item;
console.log(`Equipped ${item.name}`);
return true;
});factory.registerMethod('getTotalAttack', function() {
let total = this.baseAttack;
if (this.equipment.weapon) {
total += this.equipment.weapon.attackBonus;
}
return total;
});
factory.registerMethod('getTotalDefense', function() {
let total = this.baseDefense;
Object.values(this.equipment).forEach(item => {
if (item && item.defenseBonus) {
total += item.defenseBonus;
}
});
return total;
});const { entities } = require('ecos');
factory.registerMethod('attack', function(targetId) {
const target = entities.get(targetId);
if (!target) {
console.log('Target not found');
return;
}
const damage = this.getTotalAttack();
target.takeDamage(damage);
console.log(`${this.name} attacked ${target.name} for ${damage} damage`);
});
factory.registerMethod('heal', function(targetId, amount) {
const target = entities.get(targetId);
if (!target) {
console.log('Target not found');
return;
}
target.health = Math.min(target.maxHealth, target.health + amount);
console.log(`${this.name} healed ${target.name} for ${amount} HP`);
});Register multiple related methods and use them together:
// Register movement methods
factory.registerMethod('move', function(dx, dy) {
this.x += dx;
this.y += dy;
});
factory.registerMethod('moveTo', function(x, y) {
this.x = x;
this.y = y;
});
factory.registerMethod('getPosition', function() {
return { x: this.x, y: this.y };
});
factory.registerMethod('getDistanceTo', function(otherEntity) {
const dx = this.x - otherEntity.x;
const dy = this.y - otherEntity.y;
return Math.sqrt(dx * dx + dy * dy);
});
// Register combat methods
factory.registerMethod('takeDamage', function(amount) {
this.health = Math.max(0, this.health - amount);
});
factory.registerMethod('isAlive', function() {
return this.health > 0;
});
factory.registerMethod('isDead', function() {
return this.health <= 0;
});
// Use all methods in factory
const gameEntityFactory = factory.create({
name: 'gameEntity',
props: ['name', 'x', 'y', 'health', 'maxHealth'],
default: { x: 0, y: 0, health: 100, maxHealth: 100 },
methods: [
'move',
'moveTo',
'getPosition',
'getDistanceTo',
'takeDamage',
'isAlive',
'isDead'
]
});
const entity1 = gameEntityFactory.create({ name: 'Entity1' });
const entity2 = gameEntityFactory.create({ name: 'Entity2', x: 10, y: 10 });
entity1.move(5, 5);
const distance = entity1.getDistanceTo(entity2);
console.log(`Distance: ${distance}`);// Combat methods
factory.registerMethod('attack', function(target) { /* ... */ });
factory.registerMethod('defend', function() { /* ... */ });
factory.registerMethod('takeDamage', function(amount) { /* ... */ });
// Movement methods
factory.registerMethod('move', function(dx, dy) { /* ... */ });
factory.registerMethod('teleport', function(x, y) { /* ... */ });
// Inventory methods
factory.registerMethod('addItem', function(item) { /* ... */ });
factory.registerMethod('removeItem', function(itemId) { /* ... */ });
factory.registerMethod('hasItem', function(itemId) { /* ... */ });// ✅ Good - clear intent
factory.registerMethod('isAlive', function() { /* ... */ });
factory.registerMethod('canMove', function() { /* ... */ });
factory.registerMethod('getTotalDamage', function() { /* ... */ });
// ❌ Bad - unclear intent
factory.registerMethod('check', function() { /* ... */ });
factory.registerMethod('do', function() { /* ... */ });
factory.registerMethod('calc', function() { /* ... */ });// ✅ Good - single responsibility
factory.registerMethod('move', function(dx, dy) {
this.x += dx;
this.y += dy;
});
factory.registerMethod('checkBounds', function(minX, minY, maxX, maxY) {
return this.x >= minX && this.x <= maxX &&
this.y >= minY && this.y <= maxY;
});
// ❌ Bad - doing too much
factory.registerMethod('moveAndCheck', function(dx, dy, bounds) {
this.x += dx;
this.y += dy;
if (this.x < bounds.minX) this.x = bounds.minX;
if (this.x > bounds.maxX) this.x = bounds.maxX;
if (this.y < bounds.minY) this.y = bounds.minY;
if (this.y > bounds.maxY) this.y = bounds.maxY;
this.updateSprite();
this.playSound('move');
this.checkCollisions();
});const { factory, entities } = require('ecos');
// Register character methods
factory.registerMethod('gainExperience', function(amount) {
this.experience += amount;
const expNeeded = this.level * 100;
if (this.experience >= expNeeded) {
this.levelUp();
}
});
factory.registerMethod('levelUp', function() {
this.level += 1;
this.experience = 0;
this.maxHealth += 10;
this.health = this.maxHealth;
this.statPoints += 5;
console.log(`${this.name} leveled up to ${this.level}!`);
});
factory.registerMethod('allocateStat', function(stat, amount) {
if (this.statPoints < amount) {
console.log('Not enough stat points');
return false;
}
this[stat] = (this[stat] || 0) + amount;
this.statPoints -= amount;
return true;
});
factory.registerMethod('getStats', function() {
return {
level: this.level,
health: this.health,
maxHealth: this.maxHealth,
strength: this.strength,
intelligence: this.intelligence,
agility: this.agility
};
});
// Create factory
const characterFactory = factory.create({
name: 'character',
props: ['name', 'level', 'experience', 'health', 'maxHealth',
'statPoints', 'strength', 'intelligence', 'agility'],
default: {
level: 1,
experience: 0,
health: 100,
maxHealth: 100,
statPoints: 0,
strength: 10,
intelligence: 10,
agility: 10
},
methods: ['gainExperience', 'levelUp', 'allocateStat', 'getStats']
});
// Use the system
const hero = characterFactory.create({ name: 'Hero' });
hero.gainExperience(50); // No level up yet
hero.gainExperience(60); // Level up! (110 total exp >= 100 needed)
hero.allocateStat('strength', 3);
hero.allocateStat('agility', 2);
console.log(hero.getStats());
// {
// level: 2,
// health: 110,
// maxHealth: 110,
// strength: 13,
// intelligence: 10,
// agility: 12
// }const { factory, entities } = require('ecos');
// Register inventory methods
factory.registerMethod('addItem', function(itemId) {
if (this.inventory.length >= this.maxInventorySize) {
console.log('Inventory full');
return false;
}
this.inventory.push(itemId);
console.log(`Added item ${itemId} to inventory`);
return true;
});
factory.registerMethod('removeItem', function(itemId) {
const index = this.inventory.indexOf(itemId);
if (index === -1) {
console.log('Item not found in inventory');
return false;
}
this.inventory.splice(index, 1);
console.log(`Removed item ${itemId} from inventory`);
return true;
});
factory.registerMethod('hasItem', function(itemId) {
return this.inventory.includes(itemId);
});
factory.registerMethod('getInventoryItems', function() {
return this.inventory.map(itemId => entities.get(itemId)).filter(Boolean);
});
factory.registerMethod('getInventoryValue', function() {
return this.getInventoryItems().reduce((total, item) => {
return total + (item.value || 0);
}, 0);
});
// Create factories
const playerFactory = factory.create({
name: 'player',
props: ['name', 'inventory', 'maxInventorySize'],
default: { inventory: [], maxInventorySize: 10 },
methods: ['addItem', 'removeItem', 'hasItem', 'getInventoryItems', 'getInventoryValue']
});
const itemFactory = factory.create({
name: 'item',
props: ['name', 'type', 'value']
});
// Use the system
const player = playerFactory.create({ name: 'Player1' });
const sword = itemFactory.create({ name: 'Sword', type: 'weapon', value: 100 });
const potion = itemFactory.create({ name: 'Potion', type: 'consumable', value: 25 });
player.addItem(sword.id);
player.addItem(potion.id);
console.log(`Has sword: ${player.hasItem(sword.id)}`); // true
console.log(`Inventory value: ${player.getInventoryValue()}`); // 125Methods should handle errors gracefully:
factory.registerMethod('attack', function(targetId) {
// Validate target exists
const target = entities.get(targetId);
if (!target) {
console.error('Attack failed: target not found');
return { success: false, reason: 'target_not_found' };
}
// Validate target is alive
if (!target.isAlive || !target.isAlive()) {
console.error('Attack failed: target is dead');
return { success: false, reason: 'target_dead' };
}
// Validate attacker can attack
if (this.stamina < 10) {
console.error('Attack failed: not enough stamina');
return { success: false, reason: 'insufficient_stamina' };
}
// Perform attack
this.stamina -= 10;
const damage = this.calculateDamage();
target.takeDamage(damage);
return {
success: true,
damage: damage,
targetHealth: target.health
};
});