Skip to content

Instantly share code, notes, and snippets.

@sokol815
Last active April 4, 2020 02:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sokol815/9199c3a074cec94209dd2278b46d23f1 to your computer and use it in GitHub Desktop.
Save sokol815/9199c3a074cec94209dd2278b46d23f1 to your computer and use it in GitHub Desktop.
Entity Component System in javascript
var App = App || {};
if( !Acc ) {
console.warn('Acc is required for App.ECS to function');
}
App.ECS = function( parent ) {
this.parent = parent;
this.systems = {};
this.components = {};
this.entities = [];
this.componentFamilies = {}; // keys of components with values being arrays of entities having those components. Updated any-time an entity adds or removes a component.
this.events = {};
this.listeners = {
addComponent: {},
removeComponent: {}
};
this._entityComponentLookup = {};
this._prefabs = {};
this._eventQueue = [];
};
App.ECS.prototype._eventNames = ['init','onKeyDown','onKeyUp','onClick','update'];
App.ECS.prototype._reservedWords = ['delta','components','base','name'];
App.ECS.prototype.FilterReserved = function( values ) {
var self = this;
return values.filter(function(val){ return self._reservedWords.indexOf( val ) < 0; });
};
App.ECS.prototype.IsReserved = function(name) {
if( this._reservedWords.indexOf( name ) > -1 ) {
console.warn(name + ' is a reserved ECS word. No Components, systems or properties can be named that.');
}
};
App.ECS.prototype.Init = function() {
var self = this;
document.onkeydown = function(e) {
self.TriggerEvent( 'onKeyDown', e );
};
document.onkeyup = function(e) {
self.TriggerEvent( 'onKeyUp', e );
};
document.onclick = function(e) {
self.TriggerEvent( 'onClick', e );
};
console.log('ECS init triggered');
this.TriggerEvent('init');
};
App.ECS.prototype.Clean = function() {
this.entities = [];
this._eventQueue = [];
this._entityComponentLookup = {};
this._defaultEntity.nextId = 0;
this._defaultComponent.nextId = 0;
};
App.ECS.prototype.TriggerEvent = function( eventName, eventParams ) {
var eventList = this.events[eventName];
if( eventList != undefined ) {
for( var i = 0; i < eventList.length; i++ ) {
var consumed = eventList[i].functions[eventName].call( eventList[i], this, eventParams );
if( consumed ) {
return;
}
}
}
};
App.ECS.prototype.CloneWithDefault = function( obj, def ) {
var res = Acc.Clone( def );
var keys = Object.keys( obj );
for( var i = 0; i < keys.length; i++ ) {
if( keys[i] == 'functions' && def['functions'] != undefined ) {
var objFuncKeys = Object.keys(obj['functions']);
var defFuncKeys = Object.keys(def['functions']);
for( var j = 0; j < objFuncKeys.length; j++ ) {
res['functions'][objFuncKeys[j]] = obj['functions'][objFuncKeys[j]];
}
} else {
res[keys[i]] = obj[keys[i]];
}
}
return res;
};
App.ECS.prototype._defaultSystem = {
name: 'default',
functions: {
'init': function( ecs ) {
console.log(this.name + ' system initialized');
}
}
};
App.ECS.prototype.RegisterSystem = function( system ) {
if( this.systems[system.name] != undefined ) {
console.warn("System type " + system.name + " already exists.");
return;
}
if( this.IsReserved( system.name ) ) {
console.warn("Cannot name system '" + system.name + "' it is a reserved word." );
return;
}
if( system.functions.init == undefined ) {
system.functions.init = this._defaultSystem.functions.init;
}
var sys = system;//this.CloneWithDefault( system, this._defaultSystem );
var keys = Object.keys( sys.functions );
for( var i = 0; i < keys.length; i++ ) {
if( this._eventNames.indexOf( keys[i] ) > -1 ) {
if( this.events[keys[i]] == undefined ) {
this.events[keys[i]] = [];
}
this.events[keys[i]].push(sys);
}
}
if( sys.listen ) {
for( type in sys.listen ) {
if( this.listeners[type] ) {
for( var i = 0; i < sys.listen[type].length; i++ ) {
var cur = sys.listen[type][i];
if( this.listeners[type][cur.name] == undefined ) {
this.listeners[type][cur.name] = [];
}
this.listeners[type][cur.name].push({
system: sys,
name: cur.name,
functionToExecute: cur.functionToExecute,
priority: cur.priority
});
}
}
}
}
this.systems[sys.name] = sys;
};
App.ECS.prototype._defaultComponent = {
name: 'default',
nextId: 0,
getNextId: function(){
return this.nextId++;
},
values: {
}
};
App.ECS.prototype.RegisterComponent = function( component ) {
if( this.components[component.name] != undefined ) {
console.warn("Component type " + component.name + " already exists." );
return;
}
if( this.IsReserved( component.name ) ) {
console.warn("Cannot name component '" + component.name + "' it is a reserved word." );
return;
}
var comp = this.CloneWithDefault( component, this._defaultComponent );
this.components[comp.name] = comp;
};
App.ECS.prototype._defaultEntity = {
nextId: 0,
getNextId: function() {
return this.nextId++;
}
};
App.ECS.prototype.CreateEntity = function( components ) {
if( components == undefined ) {
components = [];
}
var entity = {
type: 'entity',
id: this._defaultEntity.getNextId(),
components: {}
};
this.entities.binaryInsert( entity, this._UniqueEntityCompare );
for( var i = 0; i < components.length; i++ ) {
this.AddComponentToEntity( components[i], entity );
}
return entity;
};
App.ECS.prototype.RemoveEntity = function( entity ) {
for( var comp in entity.components ) {
this.RemoveComponentFromEntity( comp, entity );
}
var index = this.entities.binaryIndexOf( entity, this._UniqueEntityCompare );
if( index < 0 ) {
console.warn('entity not found... something is wrong', entity );
} else {
this.entities.removeAtIndex( index );
}
};
App.ECS.prototype.EventTriggerAddComponent = function( compName, entity ) {
if( this.listeners.addComponent[compName] !== undefined ) {
var set = this.listeners.addComponent[compName];
for( var i = 0; i < set.length; i++ ) {
set[i].system.functions[set[i].functionToExecute]( entity );
}
}
};
App.ECS.prototype.AddComponentToEntity = function( compName, entity ) {
var comp = this.components[compName];
if( comp != undefined && entity.components[compName] == undefined ) {
entity.components[compName] = Acc.Clone( comp.values );
this.CacheComponentAdded( compName, entity );
}
this.EventTriggerAddComponent( compName, entity );
};
App.ECS.prototype.EventTriggerRemoveComponent = function( compName, entity ) {
if( this.listeners.removeComponent[compName] !== undefined ) {
var set = this.listeners.removeComponent[compName];
for( var i = 0; i < set.length; i++ ) {
set[i].system.functions[set[i].functionToExecute]( entity );
}
}
};
App.ECS.prototype.RemoveComponentFromEntity = function( compName, entity ) {
var comp = this.components[compName];
if( comp != undefined && entity.components[compName] != undefined ) {
delete entity.components[compName];
this.CacheComponentRemoved( compName, entity );
}
this.EventTriggerRemoveComponent( compName, entity );
};
App.ECS.prototype._UniqueEntityCompare = function( a, b ) {
if( a.id < b.id ) { return -1; }
if( a.id > b.id ) { return 1; }
return 0;
};
App.ECS.prototype.CacheComponentAdded = function( compName, entity ) {
if( this._entityComponentLookup[compName] == undefined ) {
this._entityComponentLookup[compName] = [];
}
this._entityComponentLookup[compName].binaryInsert( entity, this._UniqueEntityCompare );
};
App.ECS.prototype.CacheComponentRemoved = function( compName, entity ) {
if( this._entityComponentLookup[compName] == undefined ) {
this._entityComponentLookup[compName] = [];
return;
}
var index = this._entityComponentLookup[compName].binaryIndexOf(entity, this._UniqueEntityCompare );
if( index > -1 ) {
this._entityComponentLookup[compName].removeAtIndex(index);
}
};
App.ECS.prototype.CreatePrefab = function( name, componentData ) {
var constructedPrefab = {name: name};
componentData = Acc.Clone( componentData );
if( componentData.type == 'entity' ) {
delete componentData.type;
}
if( this.IsReserved( name ) ) {
console.warn("Cannot name prefab '" + name + "' it is a reserved word." );
return;
}
var componentNames = this.FilterReserved(Object.keys(componentData));
for( var i = 0; i < componentNames.length; i++ ) {
this._AddComponentToPrefab( constructedPrefab, componentNames[i], componentData[componentNames[i]] );
}
this._prefabs[name] = constructedPrefab;
console.log('prefab ' + name + ' registered.');
return constructedPrefab;
};
App.ECS.prototype._AddComponentToPrefab = function( prefab, componentName, componentData ) {
var comp = this.components[componentName];
if( comp == undefined ) {
console.warn('Could not find Component by name of "'+componentName+'".');
return;
}
prefab[componentName] = Acc.Clone( comp.values );
if( Array.isArray( comp.values )){
// --- if we have an array, it's because this is a list of entities
prefab[componentName] = [];
for( var j = 0; j < componentData.length; j++ ) {
// --- each of these is an entity reference.
var curChildPrefab = componentData[j];
var childName = prefab.name+'.'+componentName+'.'+j;
var childPrefab = this.CreatePrefab( childName, curChildPrefab['components'] );
prefab[componentName].push({ type: 'prefab', value: childName });
}
} else {
var keys = Object.keys(componentData);
for( var j = 0; j < keys.length; j++ ) {
if( prefab[componentName][keys[j]] === undefined ) {
console.warn( 'Invalid property "'+keys[j]+'" on component "' + componentName + '" in the "' + prefab.name + '" prefab.');
} else if( typeof componentData[keys[j]] == 'object' && comp.values[keys[j]] === null ) {
// --- this is intended to be a reference to an entity
var curChildPrefab = componentData[keys[j]];
var childName = prefab.name+'.'+componentName+'.'+keys[j];
var childPrefab = this.CreatePrefab( childName, curChildPrefab['components'] );
prefab[componentName][keys[j]] = {type: 'prefab', value: childName };
} else {
prefab[componentName][keys[j]] = componentData[keys[j]];
}
}
}
};
App.ECS.prototype.CreatePrefabs = function( prefabsObject ) {
var keys = Object.keys( prefabsObject );
var children = {};
for( var i = 0; i < keys.length; i++ ) {
var cur = prefabsObject[keys[i]];
if( cur.base != undefined ) {
children[keys[i]] = cur;
} else {
this.CreatePrefab( keys[i], cur );
}
}
this.SolvePrefabDependencies( children );
};
App.ECS.prototype.SolvePrefabDependencies = function( children ) {
var keys = Object.keys( children );
for( var i = 0; i < keys.length; i++ ) {
var childPrefabData = children[keys[i]];
if( this._prefabs[childPrefabData.base] == undefined ) {
console.warn("could not find base prefab named '" + childPrefabData.base + "'" );
// --- do nothing else for this prefab.
} else {
var newChild = Acc.Clone( this._prefabs[childPrefabData.base] );
newChild.name = keys[i];
// --- at this point we have the prefab as it's parent defined it
var childKeys = this.FilterReserved( Object.keys( childPrefabData ));
for( var j = 0; j < childKeys.length; j++ ) {
if( newChild[childKeys[j]] == undefined ) {
// --- component newly added to prefab
this._AddComponentToPrefab( newChild, childKeys[j], childPrefabData[childKeys[j]] );
} else {
// --- component exists on prefab
if( Array.isArray( newChild[childKeys[j]] ) ) {
// --- component is an array type values.
newChild[childKeys[j]] = [];
for( var k = 0; k < childPrefabData[childKeys[j]].length; k++ ) {
// --- each item on here should be a prefab
newChild[childKeys[j]].push( Acc.Clone( childPrefabData[childKeys[j]][k] ));
}
} else {
var compValues = Object.keys( newChild[childKeys[j]] );
/// --- component has key: value pairs. each key: value pair may be an array, too.
for( var k = 0; k < compValues.length; k++ ) {
if( Array.isArray( newChild[childKeys[j]][compValues[k]])) {
newChild[childKeys[j]][compValues[k]] = [];
if( childPrefabData[childKeys[j]][compValues[k]] != undefined ) {
// --- list of entities
for( var l = 0; l < childPrefabData[childKeys[j]][compValues[k]].length; l++ ) {
newChild[childKeys[j]][compValues[k]].push(childPrefabData[childKeys[j]][compValues[k]][l]);
}
}
} else {
// --- just a value
if( childPrefabData[childKeys[j]][compValues[k]] != undefined ) {
newChild[childKeys[j]][compValues[k]] = childPrefabData[childKeys[j]][compValues[k]];
}
}
}
}
}
}
this._prefabs[keys[i]] = newChild;
console.log('prefab ' + keys[i] + ' registered.');
}
}
};
App.ECS.prototype.Serialize = function( entity ) {
var output = JSON.stringify( entity );
return output;
};
App.ECS.prototype.Deserialize = function( entity ) {
if( typeof entity !== 'string' ) {
entity = JSON.stringify( entity );
}
var output = JSON.parse( entity );
// --- re-key all IDs
this.ReKey( output );
this.RecursiveRegister( output );
return output;
};
App.ECS.prototype.RecursiveRegister = function( obj, objName ) {
if( Array.isArray( obj )) {
// --- we have an array of entities
for( var i = 0; i < obj.length; i++ ) {
// -- each entity
this.RegisterEntity( obj[i] );
}
} else if( obj.type == 'entity' ) {
// --- single entity
this.RegisterEntity( obj );
} else {
// --- component?
var component = this.components[objName];
if( Array.isArray(component.values)) {
for( var i = 0; i < obj.length; i++ ) {
this.RegisterEntity( obj[i] );
}
} else {
for( var property in component.values ) {
if( component.values[property] === null ) {
// --- direct entity reference
if( obj[property] !== null ) {
this.RegisterEntity( obj[property] );
}
} else {
// --- just a component property.
}
}
}
}
};
App.ECS.prototype.RegisterEntity = function( entity ) {
this.entities.binaryInsert( entity, this._UniqueEntityCompare );
for( var componentName in entity.components ) {
this.CacheComponentAdded( componentName, entity );
this.EventTriggerAddComponent( componentName, entity );
this.ReKey( entity.components[componentName], componentName );
}
};
App.ECS.prototype.ReKey = function( obj, objName ) {
if( Array.isArray( obj )) {
// --- we have an array of entities
for( var i = 0; i < obj.length; i++ ) {
// -- each entity
this.ReKeyEntity( obj[i] );
}
} else if( obj.type == 'entity' ) {
// --- single entity
this.ReKeyEntity( obj );
} else {
// --- component?
var component = this.components[objName];
if( Array.isArray(component.values)) {
for( var i = 0; i < obj.length; i++ ) {
this.ReKeyEntity( obj[i] );
}
} else {
for( var property in component.values ) {
if( component.values[property] === null ) {
// --- direct entity reference
if( obj[property] !== null ) {
this.ReKeyEntity( obj[property] );
}
} else {
// --- just a component property.
}
}
}
}
};
App.ECS.prototype.ReKeyEntity = function( entity ) {
entity.id = this._defaultEntity.getNextId();
for( var componentName in entity.components ) {
this.ReKey( entity.components[componentName], componentName );
}
};
App.ECS.prototype._reservedPrefabWords = ['type','value'];
App.ECS.prototype.CreateEntityFromPrefab = function( prefabName ) {
var self = this;
var prefab = this._prefabs[prefabName];
if( prefab == undefined ) {
console.warn('Could not find prefab by name of "' + prefabName + '".')
return null;
}
var entity = this.CreateEntity();
var keys = this.FilterReserved( Object.keys(prefab) );
for( var i = 0; i < keys.length; i++ ) {
this.AddComponentToEntity( keys[i], entity );
entity.components[keys[i]] = Acc.Clone(prefab[keys[i]]);
var curComp = entity.components[keys[i]];
this.ResolvePrefabComponent( curComp );
}
return entity;
};
App.ECS.prototype.ResolvePrefabComponent = function( component, externalData ) {
if( Array.isArray( component )) {
for( var i = 0; i < component.length; i++ ) {
component[i] = this.ResolvePrefabEntity( component[i] );
}
} else if( component.values === null ) {
// --- pointer to entity
console.warn('not entirely sure how components pointing to another entity should work.', component);
} else {
var dataSource = (externalData == undefined ? component : externalData );
var dataKeys = Object.keys( dataSource );
for( var j = 0; j < dataKeys.length; j++ ) {
if( Array.isArray( dataSource[dataKeys[j]] )) { // --- array of entities
component[dataKeys[j]] = this.ResolvePrefabArray( dataSource[dataKeys[j]] );
} else if( typeof dataSource[dataKeys[j]] == 'object' ) { // --- pointer to single entity
component[dataKeys[j]] = this.ResolvePrefabEntity( dataSource[dataKeys[j]] );
} else { // --- just regular key:value data
component[dataKeys[j]] = dataSource[dataKeys[j]];
}
}
}
};
App.ECS.prototype.ResolvePrefabArray = function( array ) {
// --- possibly a list of prefabs in here
for( var k = 0; k < array.length; k++ ) {
array[k] = this.ResolvePrefabEntity( array[k] );
}
return array;
};
App.ECS.prototype.ResolvePrefabEntity = function( prefab ) {
if( prefab == null ) {
return null;
}
switch( prefab.type ) {
case 'prefab':
var data = Acc.Clone( prefab );
var entity = this.CreateEntityFromPrefab( prefab.value );
delete data.value;
delete data.type;
var prefabComponentOverrides = Object.keys(data);
for( var l = 0; l < prefabComponentOverrides.length; l++ ) {
if( entity.components[prefabComponentOverrides[l]] != undefined ) {
// --- the component exists on this entity
this.ResolvePrefabComponent( entity.components[prefabComponentOverrides[l]], data[prefabComponentOverrides[l]] );
}
}
return entity;
default:
console.warn('unknown element type on list of prefab entities "' + prefab.type + '".', prefab );
return null;
}
};
App.ECS.prototype.GetEntitiesWithComponents = function( componentNames ) {
if( !Array.isArray( componentNames )) {
componentNames = [componentNames];
}
var list = [];
for( var i = 0; i < componentNames.length; i++ ) {
if( i == 0 ) {
// --- just add all components with this type to the list
var compCache = this._entityComponentLookup[componentNames[i]];
if( compCache != undefined ) {
for( var j = 0; j < compCache.length; j++ ) {
list.push(compCache[j]);
}
}
} else {
for( var j = 0; j < list.length; j++ ) {
if( list[j].components[componentNames[j]] == undefined ) {
list.removeAtIndex(j);
j--;
}
}
// --- ensure that all components in our list have the given compenentName
}
}
return list;
};
App.ECS.prototype.GetAllChildrenEntities = function( entity, componentFilters ) {
if( componentFilters == undefined ) {
componentFilters = [];
} else if ( !Array.isArray( componentFilters )) {
componentFilters= [componentFilters];
}
var children = this.RecursiveChildrenEntities( entity );
if( componentFilters != undefined && componentFilters.length > 0 ) {
children = children.filter(function(c){
var keys = Object.keys( c.components );
for( var i = 0; i < componentFilters.length; i++ ) {
if( keys.indexOf( componentFilters[i] ) < 0 ) {
return false;
}
}
return true;
});
}
return children;
}
App.ECS.prototype.RecursiveChildrenEntities = function( entity ) {
var children = [];
var compKeys = Object.keys(entity.components);
for( var i = 0; i < compKeys.length; i++ ) {
if( Array.isArray(entity.components[compKeys[i]] )) {
// --- list of entities
for( var j = 0; j < entity.components[compKeys[i]].length; j++ ) {
children.push( entity.components[compKeys[i]][j] );
children = children.concat(this.GetAllChildrenEntities(entity.components[compKeys[i]][j]));
}
} else if( entity.components[compKeys[i]] !== null && entity.components[compKeys[i]].components != undefined ) {
// --- component is a singleton entity reference
children.push( entity.components[compKeys[i]] );
children = children.concat(this.GetAllChildrenEntities(entity.components[compKeys[i]]));
} else {
// look through each attribute on the component to find singleton or array entities
var curComp = entity.components[compKeys[i]];
var keys = Object.keys(curComp);
for( var j = 0; j < keys.length; j++ ) {
if( Array.isArray( curComp[keys[j]] )) {
// --- list of entities
for( var k = 0; k < curComp[keys[j]].length; k++ ) {
children.push( curComp[keys[j]][k] );
children = children.concat(this.GetAllChildrenEntities(curComp[keys[j]][k]));
}
} else if( curComp[keys[j]] !== null && curComp[keys[j]].components != undefined ) {
children.push( curComp[keys[j]] );
children = children.concat( this.GetAllChildrenEntities( curComp[keys[j]]));
} else {
// --- not an entity... ignore.
}
}
}
}
return children;
};
/*
Entity structure:
{
id: 0,
components: {}
}
if a component has an array value attribute, it can contain an array of entities
if a component has a value attribute equal to null, it can reference one entity.
*/
App.prototype.ECSData = {
Components: [
{
name: 'dungeon-branch', // --- name of component
values: { // --- fields on component
name: 'none',
baseDifficulty: 0,
levels: [], // --- array of entitites
completionRewards: []
}
},{
name: 'dungeon-level',
values: {
depth: 0,
difficultyModifier: 0,
encounters: [],
completionRewards: []
}
},{
name: 'control',
values: {
hasInput: false,
isActive: true,
bindings: []
}
},{
name: 'hp',
values: {
cur: 0,
max: 0,
regen: 0
}
},{
name: 'mana',
values: {
cur: 0,
max: 0,
regen: 0
}
},{
name: 'soul', // --- used for temporary actors (e.g. summoned) and the PC.
values: {
cur: 0,
decay: 0
}
},{
name: 'skill',
values: {
name: 'none',
effects: []
}
},{
name: 'experience',
values: {
total: 0,
levelFormula: '{0}*({0}-1))*(10)',
levelFormula0: 'level', // --- the higher learnRate is, the faster level will increment
maxLevel: 30,
learnModifier: 0, // each point is +25% or -10% learn rate.
type: ''
}
},{
name: 'timestamp',
values: {
time: 0
}
},{
name: 'body',
values: {
parts: [], // pointers to entities
partialSlots: [] // pointers to entities with itemSlots and partialCounts
}
},{
name: 'bodypart',
values: {
partType: '',
name: '',
size: 0
}
},{
name: 'itemslots',
values: []
},{
name: 'itemslot',
values: {
itemType: '',
item: null
}
},{
name: 'backpack',
values: [] // pointers to entities
},{
name: 'identity',
values: {
isProper: false,
gender: 'n',
plural: 's',
title: '',
article: 'a',
pronoun: 'it',
pluralPronoun: 'they',
name: 'none'
}
},{
name: 'item',
values: {
count: 1,
maxStack: 1,
quality: 5,
state: 1
}
},{
name: 'attack',
values: {
type: 'kinetic',
damage: '',
bonus: '',
toHit: '',
timeCost: '',
repeat: 0
}
},{
name: 'skillset',
values: [] // pointers to entities
},{
name: 'action',
values: {
type: '',
}
},{
name: 'actionlist',
values: [], // pointers to entities
},{
name: 'partialitemslot',
values: {
itemType: '',
capacity: 0
}
},{
name: 'partialitemslots',
values: []
},{
name: 'cumulativeStats',
values: {
coldResistance: 0,
fireResistance: 0,
negativeResistance: 0,
magicResistance: 0,
poisonResistance: false,
lightningResistance: false,
seeInvisible: false,
hp: 0,
hpRegen: 0,
mana: 0,
manaRegen: 0,
ac: 0,
sh: 0,
stealth: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
meleeSlaying: 0,
rangedSlaying: 0,
meleeToHit: 0,
rangedToHit: 0,
spellEnhancement: 0,
encumberance: 0
}
},{
name: 'coldResistance',
values: {
value: 0
}
},{
name: 'fireResistance',
values: {
value: 0
}
},{
name: 'negativeResistance',
values: {
value: 0
}
},{
name: 'magicResistance',
values: {
value: 0
}
},{
name: 'poisonResistance',
values: {
value: 0
}
},{
name: 'lightningResistance',
values: {
value: 0
}
},{
name: 'seeInvisible',
values: {
value: false
}
},{
name: 'ac',
values: {
value: 0
}
},{
name: 'sh',
values: {
value: 0
}
},{
name: 'stealth',
values: {
value: 0
}
},{
name: 'strength',
values: {
value: 0
}
},{
name: 'intelligence',
values: {
value: 0
}
},{
name: 'dexterity',
values: {
value: 0
}
},{
name: 'meleeSlaying',
values: {
value: 0
}
},{
name: 'rangedSlaying',
values: {
value: 0
}
},{
name: 'meleeToHit',
values: {
value: 0
}
},{
name: 'rangedToHit',
values: {
value: 0
}
},{
name: 'spellEnhancement',
values: {
value: 0
}
},{
name: 'encumberance',
values: {
value: 0
}
},{
name: 'damage',
values: {
value: '1d1',
type: 'normal'
}
},{
name: 'updateDelta',
values: {
delta: 0
}
}
],
Prefabs: {
actor: {
skillset: [],
timestamp: {},
identity: { gender: 'male', article: 'a', pronoun: 'he', name: 'actor' },
action: {},
body: {},
experience: {},
hp: {},
mana: {},
cumulativeStats: {},
coldResistance: {},
fireResistance: {},
negativeResistance: {},
magicResistance: {},
poisonResistance: {},
lightningResistance: {},
seeInvisible: {},
ac: {},
sh: {},
stealth: {},
strength: {},
intelligence: {},
dexterity: {},
meleeSlaying: {},
rangedSlaying: {},
meleeToHit: {},
rangedToHit: {},
spellEnhancement: {},
encumberance: {}
},
humanoid: {
base: 'actor', // --- indicator that this prefab is based on the "actor" prefab.
body: {
parts: [
{ type: 'prefab', value: 'head' },
{ type: 'prefab', value: 'torso' },
{ type: 'prefab', value: 'hand', bodypart: { name: 'left hand'}},
{ type: 'prefab', value: 'hand', bodypart: { name: 'right hand'}},
{ type: 'prefab', value: 'foot', bodypart: { name: 'left foot'}},
{ type: 'prefab', value: 'foot', bodypart: { name: 'right foot'}},
]
},
backpack: {},
itemslots: [],
},
head: {
bodypart: { partType: 'head', name: 'head', size: 6 },
itemslots: [{
components: {
itemslot: {
itemType: 'helmet'
}
}
}]
},
torso: {
bodypart: { partType: 'torso', name: 'torso', size: 40 },
itemslots: [{
components: {
itemslot: {
itemType: 'body-armour'
}
}
},{
components: {
itemslot:{
itemType: 'amulet'
}
}
},{
components: {
itemslot: {
itemType: 'cloak'
}
}
}],
},
hand: {
bodypart: { partType: 'hand', name: 'hand', size: 8 },
partialitemslots: [{
components: {
partialitemslot: {
itemType: 'gloves',
capacity: 0.5
}
}
},{
components: {
partialitemslot: {
itemType: 'hand',
capacity: 1
}
}
}],
itemslots: [{
components: {
itemslot: {
itemType: 'ring'
}
}
}]
},
foot: {
bodypart: { partType: 'foot', name: 'foot', size: 14 },
partialitemslots: [{
components: {
partialitemslot: {
itemType: 'boots',
capacity: 0.5,
}
}
}],
},
'skill-fighting': {
skill: {
name: 'fighting',
effects: []
},
experience: {
type: 'skill'
}
},
'skill-fighting': {
skill: {
name: 'fighting',
effects: []
},
experience: {
}
}
}
};
// -- clock is a system to run with the ECS above.
(function(){
var self = {
name: 'clock',
requestedFPS: 30,
msBetweenUpdates: 0,
msLastUpdate: 0,
frameId: 0,
ecs: null,
updateRunning: false,
documentIsHidden: false,
documentIsHiddenTimestamp: 0,
listen: {
addComponent: [
//{ name: 'sample', functionToExecute: 'processSamples', priority: 1 }
],
removeComponent: [
//{ name: 'sample', functionToExecute: 'processSamples', priority: 1 }
]
},
functions: {
init: function( ecs ) {
self.ecs = ecs;
console.log('init clock called');
self.msBetweenUpdates = 1000 / self.requestedFPS;
window.requestAnimationFrame( self.functions.scheduleUpdate );
var hidden, visibilityState, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden", visibilityChange = "visibilitychange", visibilityState = "visibilityState";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden", visibilityChange = "msvisibilitychange", visibilityState = "msVisibilityState";
}
var document_hidden = document[hidden];
document.addEventListener(visibilityChange, function() {
if(document_hidden != document[hidden]) {
if(document[hidden]) {
// Document hidden
self.documentIsHidden = true;
self.documentIsHiddenTimestamp = Date.now();
console.log('document is now hidden');
} else {
// Document shown
self.documentIsHidden = false;
var elapsedTime = (self.documentIsHiddenTimestamp == 0 ? 0 : Date.now() - self.documentIsHiddenTimestamp);
// --- do something with this elapsed time?
console.log('document is back after '+elapsedTime+'ms');
var frames = ( elapsedTime / self.msBetweenUpdates ) >> 0;
console.log('catching up with ' + frames + ' missed frames.');
if( frames > 0 ) {
self.frameId += frames;
self.functions.doUpdate( frames, ( self.msBetweenUpdates * frames ) / 1000 );
}
self.documentIsHiddenTimestamp = 0;
}
document_hidden = document[hidden];
}
});
},
setFPS: function( ecs, fps ) {
self.requestedFPS = fps;
self.msBetweenUpdates = 1000 / self.requestedFPS;
},
scheduleUpdate: function( timeStamp ) {
var ecs = self.ecs;
var frames = (( timeStamp - self.msLastUpdate ) / self.msBetweenUpdates ) >> 0;
if( frames > 0 ) {
// --- update needs to occur
self.msLastUpdate += self.msBetweenUpdates * frames;
self.functions.doUpdate( frames, (self.msBetweenUpdates * frames) / 1000 );
self.frameId += frames;
}
window.requestAnimationFrame( self.functions.scheduleUpdate );
},
doUpdate: function( frames, seconds ) {
if( self.updateRunning ) { return; }
var ecs = self.ecs;
self.updateRunning = true;
ecs.TriggerEvent('update', { frames: frames, seconds: seconds });
self.updateRunning = false;
ecs.parent.view.Render( ecs.parent.game.world );
}
}
};
App.prototype._Systems.push( self );
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment