2013-03-05 23:52:48 +01:00
// Shared script handling templates and basic terrain analysis
function SharedScript ( settings )
{
if ( ! settings )
return ;
2013-03-24 10:10:32 +01:00
this . _players = settings . players ;
this . _templates = settings . templates ;
this . _derivedTemplates = { } ;
this . _techTemplates = settings . techTemplates ;
2013-03-05 23:52:48 +01:00
this . _entityMetadata = { } ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-05 23:52:48 +01:00
this . _entityMetadata [ this . _players [ i ] ] = { } ;
// always create for 8 + gaia players, since _players isn't aware of the human.
this . _techModifications = { 0 : { } , 1 : { } , 2 : { } , 3 : { } , 4 : { } , 5 : { } , 6 : { } , 7 : { } , 8 : { } } ;
// array of entity collections
this . _entityCollections = [ ] ;
// each name is a reference to the actual one.
this . _entityCollectionsName = { } ;
this . _entityCollectionsByDynProp = { } ;
this . _entityCollectionsUID = 0 ;
this . turn = 0 ;
}
//Return a simple object (using no classes etc) that will be serialized
//into saved games
2013-03-11 20:58:29 +01:00
//TODO: that
// note: we'll need to serialize much more than that before this can work.
2013-03-05 23:52:48 +01:00
SharedScript . prototype . Serialize = function ( )
{
2013-03-11 20:58:29 +01:00
// serializing entities without using the class.
var entities = [ ] ;
for ( var id in this . _entities )
{
var ent = this . _entities [ id ] ;
entities . push ( [ ent . _template , ent . _entity , ent . _templateName ] ) ;
}
2013-03-24 10:10:32 +01:00
2013-03-11 20:58:29 +01:00
// serialiazing metadata will be done by each AI on a AI basis and they shall update the shared script with that info on deserialization (using DeserializeMetadata() ).
2013-03-24 10:10:32 +01:00
// TODO: this may not be the most clever method.
return { "entities" : entities , "techModifs" : this . _techModifications , "passabClasses" : this . passabilityClasses , "passabMap" : this . passabilityMap ,
"timeElapsed" : this . timeElapsed , "techTemplates" : this . _techTemplates , "players" : this . _players } ;
2013-03-05 23:52:48 +01:00
} ;
2013-03-24 10:10:32 +01:00
// Called after the constructor when loading a saved game, with 'data' being
// whatever Serialize() returned
// todo: very not-finished. Mostly hacky, and ugly too.
2013-03-05 23:52:48 +01:00
SharedScript . prototype . Deserialize = function ( data )
{
2013-03-11 20:58:29 +01:00
this . _entities = { } ;
2013-03-24 10:10:32 +01:00
this . _players = data . players ;
this . _entityMetadata = { } ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-24 10:10:32 +01:00
this . _entityMetadata [ this . _players [ i ] ] = { } ;
this . _techModifications = data . techModifs ;
this . techModifications = data . techModifs ; // needed for entities
this . passabilityClasses = data . passabClasses ;
this . passabilityMap = data . passabMap ;
var dataArray = [ ] ;
2013-05-13 00:28:02 +02:00
for ( var i in this . passabilityMap . data )
2013-03-24 10:10:32 +01:00
dataArray . push ( this . passabilityMap . data [ i ] ) ;
this . passabilityMap . data = dataArray ;
// TODO: this is needlessly slow (not to mention a hack)
// Should probably call a "init" function rather to avoid this and serialize the terrainanalyzer state.
var fakeState = { "passabilityClasses" : this . passabilityClasses , "passabilityMap" : this . passabilityMap } ;
this . terrainAnalyzer = new TerrainAnalysis ( this , fakeState ) ;
this . accessibility = new Accessibility ( fakeState , this . terrainAnalyzer ) ;
this . _techTemplates = data . techTemplates ;
this . timeElapsed = data . timeElapsed ;
// deserializing entities;
2013-05-13 00:28:02 +02:00
for ( var i in data . entities )
2013-03-11 20:58:29 +01:00
{
var entData = data . entities [ i ] ;
2013-03-24 10:10:32 +01:00
entData [ 1 ] . template = entData [ 2 ] ;
this . _entities [ entData [ 1 ] . id ] = new Entity ( this , entData [ 1 ] ) ;
2013-03-11 20:58:29 +01:00
}
2013-03-24 10:10:32 +01:00
// entity collection updated on create/destroy event.
this . entities = new EntityCollection ( this , this . _entities ) ;
//deserializing game states.
fakeState [ "timeElapsed" ] = this . timeElapsed ;
fakeState [ "players" ] = this . _players ;
this . gameState = { } ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-24 10:10:32 +01:00
this . gameState [ this . _players [ i ] ] = new GameState ( this , fakeState , this . _players [ i ] ) ;
2013-03-05 23:52:48 +01:00
} ;
// Components that will be disabled in foundation entity templates.
// (This is a bit yucky and fragile since it's the inverse of
// CCmpTemplateManager::CopyFoundationSubset and only includes components
// that our EntityTemplate class currently uses.)
var g _FoundationForbiddenComponents = {
"ProductionQueue" : 1 ,
"ResourceSupply" : 1 ,
"ResourceDropsite" : 1 ,
"GarrisonHolder" : 1 ,
} ;
// Components that will be disabled in resource entity templates.
// Roughly the inverse of CCmpTemplateManager::CopyResourceSubset.
var g _ResourceForbiddenComponents = {
"Cost" : 1 ,
"Decay" : 1 ,
"Health" : 1 ,
"UnitAI" : 1 ,
"UnitMotion" : 1 ,
"Vision" : 1
} ;
SharedScript . prototype . GetTemplate = function ( name )
{
if ( this . _templates [ name ] )
return this . _templates [ name ] ;
if ( this . _derivedTemplates [ name ] )
return this . _derivedTemplates [ name ] ;
// If this is a foundation template, construct it automatically
if ( name . indexOf ( "foundation|" ) !== - 1 )
{
var base = this . GetTemplate ( name . substr ( 11 ) ) ;
var foundation = { } ;
for ( var key in base )
if ( ! g _FoundationForbiddenComponents [ key ] )
foundation [ key ] = base [ key ] ;
this . _derivedTemplates [ name ] = foundation ;
return foundation ;
}
else if ( name . indexOf ( "resource|" ) !== - 1 )
{
var base = this . GetTemplate ( name . substr ( 9 ) ) ;
var resource = { } ;
for ( var key in base )
if ( ! g _ResourceForbiddenComponents [ key ] )
resource [ key ] = base [ key ] ;
this . _derivedTemplates [ name ] = resource ;
return resource ;
}
error ( "Tried to retrieve invalid template '" + name + "'" ) ;
return null ;
} ;
// initialize the shared component using a given gamestate (the initial gamestate after map creation, usually)
// this is called right at the end of map generation, before you actually reach the map.
SharedScript . prototype . initWithState = function ( state ) {
this . passabilityClasses = state . passabilityClasses ;
this . passabilityMap = state . passabilityMap ;
2013-03-12 22:58:25 +01:00
this . territoryMap = state . territoryMap ;
2013-03-05 23:52:48 +01:00
2013-05-13 00:28:02 +02:00
for ( var o in state . players )
2013-03-05 23:52:48 +01:00
this . _techModifications [ o ] = state . players [ o ] . techModifications ;
this . techModifications = this . _techModifications ;
this . _entities = { } ;
for ( var id in state . entities )
{
this . _entities [ id ] = new Entity ( this , state . entities [ id ] ) ;
}
// entity collection updated on create/destroy event.
this . entities = new EntityCollection ( this , this . _entities ) ;
// create the terrain analyzer
this . terrainAnalyzer = new TerrainAnalysis ( this , state ) ;
this . accessibility = new Accessibility ( state , this . terrainAnalyzer ) ;
this . gameState = { } ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-05 23:52:48 +01:00
{
this . gameState [ this . _players [ i ] ] = new GameState ( this , state , this . _players [ i ] ) ;
}
} ;
// General update of the shared script, before each AI's update
// applies entity deltas, and each gamestate.
SharedScript . prototype . onUpdate = function ( state )
{
this . ApplyEntitiesDelta ( state ) ;
Engine . ProfileStart ( "onUpdate" ) ;
this . events = state . events ;
this . passabilityClasses = state . passabilityClasses ;
this . passabilityMap = state . passabilityMap ;
this . players = this . _players ;
this . playersData = state . players ;
this . territoryMap = state . territoryMap ;
this . timeElapsed = state . timeElapsed ;
2013-05-13 00:28:02 +02:00
for ( var o in state . players )
2013-03-05 23:52:48 +01:00
this . _techModifications [ o ] = state . players [ o ] . techModifications ;
2013-05-13 00:28:02 +02:00
for ( var i in this . gameState )
2013-03-05 23:52:48 +01:00
this . gameState [ i ] . update ( this , state ) ;
this . terrainAnalyzer . updateMapWithEvents ( this ) ;
//this.OnUpdate();
this . turn ++ ;
Engine . ProfileStop ( ) ;
} ;
SharedScript . prototype . ApplyEntitiesDelta = function ( state )
{
Engine . ProfileStart ( "Shared ApplyEntitiesDelta" ) ;
for each ( var evt in state . events )
{
if ( evt . type == "Create" )
{
if ( ! state . entities [ evt . msg . entity ] )
{
continue ; // Sometimes there are things like foundations which get destroyed too fast
}
this . _entities [ evt . msg . entity ] = new Entity ( this , state . entities [ evt . msg . entity ] ) ;
this . entities . addEnt ( this . _entities [ evt . msg . entity ] ) ;
// Update all the entity collections since the create operation affects static properties as well as dynamic
for each ( var entCollection in this . _entityCollections )
{
entCollection . updateEnt ( this . _entities [ evt . msg . entity ] ) ;
}
}
else if ( evt . type == "Destroy" )
{
2013-03-16 10:59:43 +01:00
// A small warning: javascript "delete" does not actually delete, it only removes the reference in this object.
2013-03-07 22:33:57 +01:00
// the "deleted" object remains in memory, and any older reference to it will still reference it as if it were not "deleted".
2013-03-05 23:52:48 +01:00
// Worse, they might prevent it from being garbage collected, thus making it stay alive and consuming ram needlessly.
// So take care, and if you encounter a weird bug with deletion not appearing to work correctly, this is probably why.
2013-03-16 10:59:43 +01:00
2013-03-05 23:52:48 +01:00
if ( ! this . _entities [ evt . msg . entity ] )
continue ;
2013-03-07 22:33:57 +01:00
2013-03-05 23:52:48 +01:00
// The entity was destroyed but its data may still be useful, so
// remember the entity and this AI's metadata concerning it
2013-03-07 22:33:57 +01:00
evt . msg . metadata = { } ;
2013-03-05 23:52:48 +01:00
evt . msg . entityObj = ( evt . msg . entityObj || this . _entities [ evt . msg . entity ] ) ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-07 22:33:57 +01:00
evt . msg . metadata [ this . _players [ i ] ] = this . _entityMetadata [ this . _players [ i ] ] [ evt . msg . entity ] ;
2013-03-05 23:52:48 +01:00
for each ( var entCol in this . _entityCollections )
{
entCol . removeEnt ( this . _entities [ evt . msg . entity ] ) ;
}
this . entities . removeEnt ( this . _entities [ evt . msg . entity ] ) ;
delete this . _entities [ evt . msg . entity ] ;
2013-05-13 00:28:02 +02:00
for ( var i in this . _players )
2013-03-05 23:52:48 +01:00
delete this . _entityMetadata [ this . _players [ i ] ] [ evt . msg . entity ] ;
}
else if ( evt . type == "TrainingFinished" )
{
// Apply metadata stored in training queues
for each ( var ent in evt . msg . entities )
{
2013-05-13 00:28:02 +02:00
for ( var key in evt . msg . metadata )
2013-03-05 23:52:48 +01:00
{
this . setMetadata ( evt . msg . owner , this . _entities [ ent ] , key , evt . msg . metadata [ key ] )
}
}
}
}
for ( var id in state . entities )
{
var changes = state . entities [ id ] ;
for ( var prop in changes )
{
this . _entities [ id ] . _entity [ prop ] = changes [ prop ] ;
this . updateEntityCollections ( prop , this . _entities [ id ] ) ;
}
}
Engine . ProfileStop ( ) ;
} ;
SharedScript . prototype . registerUpdatingEntityCollection = function ( entCollection , noPush )
{
if ( ! noPush ) {
this . _entityCollections . push ( entCollection ) ;
}
entCollection . setUID ( this . _entityCollectionsUID ) ;
for each ( var prop in entCollection . dynamicProperties ( ) )
{
this . _entityCollectionsByDynProp [ prop ] = this . _entityCollectionsByDynProp [ prop ] || [ ] ;
this . _entityCollectionsByDynProp [ prop ] . push ( entCollection ) ;
}
this . _entityCollectionsUID ++ ;
} ;
SharedScript . prototype . removeUpdatingEntityCollection = function ( entCollection )
{
for ( var i in this . _entityCollections )
{
if ( this . _entityCollections [ i ] . getUID ( ) === entCollection . getUID ( ) )
{
this . _entityCollections . splice ( i , 1 ) ;
}
}
for each ( var prop in entCollection . dynamicProperties ( ) )
{
for ( var i in this . _entityCollectionsByDynProp [ prop ] )
{
if ( this . _entityCollectionsByDynProp [ prop ] [ i ] . getUID ( ) === entCollection . getUID ( ) )
{
this . _entityCollectionsByDynProp [ prop ] . splice ( i , 1 ) ;
}
}
}
} ;
SharedScript . prototype . updateEntityCollections = function ( property , ent )
{
if ( this . _entityCollectionsByDynProp [ property ] !== undefined )
{
for each ( var entCollection in this . _entityCollectionsByDynProp [ property ] )
{
entCollection . updateEnt ( ent ) ;
}
}
}
SharedScript . prototype . setMetadata = function ( player , ent , key , value )
{
var metadata = this . _entityMetadata [ player ] [ ent . id ( ) ] ;
if ( ! metadata )
metadata = this . _entityMetadata [ player ] [ ent . id ( ) ] = { } ;
metadata [ key ] = value ;
this . updateEntityCollections ( 'metadata' , ent ) ;
this . updateEntityCollections ( 'metadata.' + key , ent ) ;
} ;
SharedScript . prototype . getMetadata = function ( player , ent , key )
{
var metadata = this . _entityMetadata [ player ] [ ent . id ( ) ] ;
if ( ! metadata || ! ( key in metadata ) )
return undefined ;
return metadata [ key ] ;
} ;
function copyPrototype ( descendant , parent ) {
var sConstructor = parent . toString ( ) ;
var aMatch = sConstructor . match ( /\s*function (.*)\(/ ) ;
if ( aMatch != null ) { descendant . prototype [ aMatch [ 1 ] ] = parent ; }
for ( var m in parent . prototype ) {
descendant . prototype [ m ] = parent . prototype [ m ] ;
}
} ;