2012-04-20 19:21:04 +02:00
var g _ProgressInterval = 1000 ;
const MAX _QUEUE _SIZE = 16 ;
function ProductionQueue ( ) { }
ProductionQueue . prototype . Schema =
"<a:help>Allows the building to train new units and research technologies</a:help>" +
"<a:example>" +
2012-11-25 17:23:59 +01:00
"<BatchTimeModifier>0.7</BatchTimeModifier>" +
2012-04-20 19:21:04 +02:00
"<Entities datatype='tokens'>" +
"\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"</Entities>" +
"</a:example>" +
2012-11-25 17:23:59 +01:00
"<element name='BatchTimeModifier' a:help='Modifier that influences the time benefit for batch training'>" +
2012-11-25 03:52:13 +01:00
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
2012-04-20 19:21:04 +02:00
"<optional>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this building can train. The special string \"{civ}\" will be automatically replaced by the building's four-character civ code'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Technologies' a:help='Space-separated list of technology names that this building can research.'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" ;
ProductionQueue . prototype . Init = function ( )
{
this . nextID = 1 ;
this . queue = [ ] ;
// Queue items are:
// {
// "id": 1,
// "player": 1, // who paid for this batch; we need this to cope with refunds cleanly
// "unitTemplate": "units/example",
// "count": 10,
// "resources": { "wood": 100, ... }, // resources per unit, multiply by count to get total
// "population": 1, // population per unit, multiply by count to get total
// "productionStarted": false, // true iff we have reserved population
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }
//
// {
// "id": 1,
// "player": 1, // who paid for this research; we need this to cope with refunds cleanly
// "technologyTemplate": "example_tech",
// "resources": { "wood": 100, ... }, // resources needed for research
// "productionStarted": false, // true iff production has started
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }
this . timer = undefined ; // g_ProgressInterval msec timer, active while the queue is non-empty
this . entityCache = [ ] ;
this . spawnNotified = false ;
} ;
/ *
* Returns list of entities that can be trained by this building .
* /
ProductionQueue . prototype . GetEntitiesList = function ( )
{
if ( ! this . template . Entities )
return [ ] ;
var string = this . template . Entities . _string ;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine . QueryInterface ( this . entity , IID _Identity ) ;
if ( cmpIdentity )
string = string . replace ( /\{civ\}/g , cmpIdentity . GetCiv ( ) ) ;
return string . split ( /\s+/ ) ;
} ;
/ *
* Returns list of technologies that can be researched by this building .
* /
ProductionQueue . prototype . GetTechnologiesList = function ( )
{
if ( ! this . template . Technologies )
return [ ] ;
var string = this . template . Technologies . _string ;
2012-07-20 03:54:24 +02:00
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
if ( ! cmpTechnologyManager )
2012-04-20 19:21:04 +02:00
return [ ] ;
var techs = string . split ( /\s+/ ) ;
2012-04-21 23:40:07 +02:00
var techList = [ ] ;
2012-04-20 19:21:04 +02:00
var superseded = { } ; // Stores the tech which supersedes the key
// Add any top level technologies to an array which corresponds to the displayed icons
// Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...}
for ( var i in techs )
{
var tech = techs [ i ] ;
2012-07-20 03:54:24 +02:00
var template = cmpTechnologyManager . GetTechnologyTemplate ( tech ) ;
2012-04-20 19:21:04 +02:00
if ( ! template . supersedes || techs . indexOf ( template . supersedes ) === - 1 )
2012-04-21 23:40:07 +02:00
techList . push ( tech ) ;
2012-04-20 19:21:04 +02:00
else
superseded [ template . supersedes ] = tech ;
}
// Now make researched/in progress techs invisible
2012-04-21 23:40:07 +02:00
for ( var i in techList )
2012-04-20 19:21:04 +02:00
{
2012-04-21 23:40:07 +02:00
var tech = techList [ i ] ;
while ( this . IsTechnologyResearchedOrInProgress ( tech ) )
2012-04-20 19:21:04 +02:00
{
tech = superseded [ tech ] ;
}
2012-04-21 23:40:07 +02:00
techList [ i ] = tech ;
}
var ret = [ ]
2012-11-24 20:47:36 +01:00
// This inserts the techs into the correct positions to line up the technology pairs
2012-04-25 19:02:41 +02:00
for ( var i = 0 ; i < techList . length ; i ++ )
2012-04-21 23:40:07 +02:00
{
var tech = techList [ i ] ;
if ( ! tech )
{
ret [ i ] = undefined ;
continue ;
}
2012-07-20 03:54:24 +02:00
var template = cmpTechnologyManager . GetTechnologyTemplate ( tech ) ;
2012-04-21 23:40:07 +02:00
if ( template . top )
2012-04-25 19:02:41 +02:00
ret [ i ] = { "pair" : true , "top" : template . top , "bottom" : template . bottom } ;
2012-04-21 23:40:07 +02:00
else
ret [ i ] = tech ;
2012-04-20 19:21:04 +02:00
}
return ret ;
} ;
2012-04-21 23:40:07 +02:00
ProductionQueue . prototype . IsTechnologyResearchedOrInProgress = function ( tech )
{
if ( ! tech )
return false ;
2012-07-20 03:54:24 +02:00
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
2012-04-21 23:40:07 +02:00
2012-07-20 03:54:24 +02:00
var template = cmpTechnologyManager . GetTechnologyTemplate ( tech ) ;
2012-04-21 23:40:07 +02:00
if ( template . top )
{
2012-07-20 03:54:24 +02:00
return ( cmpTechnologyManager . IsTechnologyResearched ( template . top ) || cmpTechnologyManager . IsInProgress ( template . top )
|| cmpTechnologyManager . IsTechnologyResearched ( template . bottom ) || cmpTechnologyManager . IsInProgress ( template . bottom ) )
2012-04-21 23:40:07 +02:00
}
else
{
2012-07-20 03:54:24 +02:00
return ( cmpTechnologyManager . IsTechnologyResearched ( tech ) || cmpTechnologyManager . IsInProgress ( tech ) )
2012-04-21 23:40:07 +02:00
}
} ;
2012-04-20 19:21:04 +02:00
/ *
* Adds a new batch of identical units to train or a technology to research to the production queue .
* /
ProductionQueue . prototype . AddBatch = function ( templateName , type , count , metadata )
{
// TODO: there should probably be a limit on the number of queued batches
// TODO: there should be a way for the GUI to determine whether it's going
// to be possible to add a batch (based on resource costs and length limits)
var cmpPlayer = QueryOwnerInterface ( this . entity , IID _Player ) ;
if ( this . queue . length < MAX _QUEUE _SIZE )
{
if ( type == "unit" )
{
// Find the template data so we can determine the build costs
var cmpTempMan = Engine . QueryInterface ( SYSTEM _ENTITY , IID _TemplateManager ) ;
var template = cmpTempMan . GetTemplate ( templateName ) ;
if ( ! template )
return ;
// Apply a time discount to larger batches.
2012-11-24 23:04:29 +01:00
var timeMult = this . GetBatchTime ( count ) ;
2012-07-20 03:54:24 +02:00
// We need the costs after tech modifications
// Obviously we don't have the entities yet, so we must use template data
var costs = { } ;
var totalCosts = { } ;
var buildTime = + template . Cost . BuildTime ;
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
if ( cmpTechnologyManager )
buildTime = cmpTechnologyManager . ApplyModificationsTemplate ( "Cost/BuildTime" , buildTime , template ) ;
var time = timeMult * buildTime ;
2012-04-20 19:21:04 +02:00
2012-07-20 03:54:24 +02:00
for ( var r in template . Cost . Resources )
{
costs [ r ] = + template . Cost . Resources [ r ] ;
if ( cmpTechnologyManager )
costs [ r ] = cmpTechnologyManager . ApplyModificationsTemplate ( "Cost/Resources/" + r , costs [ r ] , template ) ;
totalCosts [ r ] = Math . floor ( count * costs [ r ] ) ;
}
2012-04-20 19:21:04 +02:00
2012-07-20 03:54:24 +02:00
var population = + template . Cost . Population ;
2012-04-20 19:21:04 +02:00
// TrySubtractResources should report error to player (they ran out of resources)
if ( ! cmpPlayer . TrySubtractResources ( totalCosts ) )
return ;
2012-11-07 18:56:14 +01:00
// Update entity count in the EntityLimits component
if ( template . TrainingRestrictions )
{
var unitCategory = template . TrainingRestrictions . Category ;
var cmpPlayerEntityLimits = QueryOwnerInterface ( this . entity , IID _EntityLimits ) ;
cmpPlayerEntityLimits . IncreaseCount ( unitCategory , count ) ;
}
2012-04-20 19:21:04 +02:00
this . queue . push ( {
"id" : this . nextID ++ ,
"player" : cmpPlayer . GetPlayerID ( ) ,
"unitTemplate" : templateName ,
"count" : count ,
"metadata" : metadata ,
2012-07-20 03:54:24 +02:00
"resources" : costs ,
2012-04-20 19:21:04 +02:00
"population" : population ,
"productionStarted" : false ,
"timeTotal" : time * 1000 ,
"timeRemaining" : time * 1000 ,
} ) ;
}
else if ( type == "technology" )
{
// Load the technology template
var cmpTechTempMan = Engine . QueryInterface ( SYSTEM _ENTITY , IID _TechnologyTemplateManager ) ;
var template = cmpTechTempMan . GetTemplate ( templateName ) ;
if ( ! template )
return ;
2012-08-05 17:24:26 +02:00
var cmpPlayer = QueryOwnerInterface ( this . entity , IID _Player ) ;
var time = template . researchTime * cmpPlayer . cheatTimeMultiplier ;
2012-04-20 19:21:04 +02:00
var cost = { } ;
for each ( var r in [ "food" , "wood" , "stone" , "metal" ] )
cost [ r ] = Math . floor ( template . cost [ r ] ) ;
// TrySubtractResources should report error to player (they ran out of resources)
if ( ! cmpPlayer . TrySubtractResources ( cost ) )
return ;
// Tell the technology manager that we have started researching this so that people can't research the same
// thing twice.
2012-07-20 03:54:24 +02:00
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
2012-07-31 04:03:25 +02:00
cmpTechnologyManager . QueuedResearch ( templateName , this . entity ) ;
if ( this . queue . length == 0 )
cmpTechnologyManager . StartedResearch ( templateName ) ;
2012-04-20 19:21:04 +02:00
this . queue . push ( {
"id" : this . nextID ++ ,
"player" : cmpPlayer . GetPlayerID ( ) ,
"count" : 1 ,
"technologyTemplate" : templateName ,
"resources" : deepcopy ( template . cost ) , // need to copy to avoid serialization problems
"productionStarted" : false ,
"timeTotal" : time * 1000 ,
"timeRemaining" : time * 1000 ,
} ) ;
}
else
{
warn ( "Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue" ) ;
return ;
}
Engine . PostMessage ( this . entity , MT _ProductionQueueChanged , { } ) ;
// If this is the first item in the queue, start the timer
if ( ! this . timer )
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
this . timer = cmpTimer . SetTimeout ( this . entity , IID _ProductionQueue , "ProgressTimeout" , g _ProgressInterval , { } ) ;
}
}
else
{
var notification = { "player" : cmpPlayer . GetPlayerID ( ) , "message" : "The production queue is full." } ;
var cmpGUIInterface = Engine . QueryInterface ( SYSTEM _ENTITY , IID _GuiInterface ) ;
cmpGUIInterface . PushNotification ( notification ) ;
}
} ;
/ *
* Removes an existing batch of units from the production queue .
* Refunds resource costs and population reservations .
* /
ProductionQueue . prototype . RemoveBatch = function ( id )
{
// Destroy any cached entities (those which didn't spawn for some reason)
for ( var i = 0 ; i < this . entityCache . length ; ++ i )
{
Engine . DestroyEntity ( this . entityCache [ i ] ) ;
}
this . entityCache = [ ] ;
for ( var i = 0 ; i < this . queue . length ; ++ i )
{
var item = this . queue [ i ] ;
if ( item . id != id )
continue ;
// Now we've found the item to remove
var cmpPlayer = QueryPlayerIDInterface ( item . player , IID _Player ) ;
2012-11-07 18:56:14 +01:00
// Update entity count in the EntityLimits component
if ( item . unitTemplate )
{
var cmpTempMan = Engine . QueryInterface ( SYSTEM _ENTITY , IID _TemplateManager ) ;
var template = cmpTempMan . GetTemplate ( item . unitTemplate ) ;
if ( template . TrainingRestrictions )
{
var unitCategory = template . TrainingRestrictions . Category ;
var cmpPlayerEntityLimits = QueryOwnerInterface ( this . entity , IID _EntityLimits ) ;
cmpPlayerEntityLimits . DecreaseCount ( unitCategory , item . count ) ;
}
}
2012-04-20 19:21:04 +02:00
// Refund the resource cost for this batch
var totalCosts = { } ;
2012-08-29 07:13:45 +02:00
var cmpStatisticsTracker = QueryOwnerInterface ( this . entity , IID _StatisticsTracker ) ;
2012-04-20 19:21:04 +02:00
for each ( var r in [ "food" , "wood" , "stone" , "metal" ] )
2012-08-29 07:13:45 +02:00
{
2012-04-20 19:21:04 +02:00
totalCosts [ r ] = Math . floor ( item . count * item . resources [ r ] ) ;
2012-08-29 07:13:45 +02:00
if ( cmpStatisticsTracker )
cmpStatisticsTracker . IncreaseResourceUsedCounter ( r , - totalCosts [ r ] ) ;
}
2012-04-20 19:21:04 +02:00
cmpPlayer . AddResources ( totalCosts ) ;
// Remove reserved population slots if necessary
if ( item . productionStarted && item . unitTemplate )
cmpPlayer . UnReservePopulationSlots ( item . population * item . count ) ;
// Mark the research as stopped if we cancel it
if ( item . technologyTemplate )
{
2012-08-13 01:40:04 +02:00
// item.player is used as this.entity's owner may be invalid (deletion, etc.)
var cmpTechnologyManager = QueryPlayerIDInterface ( item . player , IID _TechnologyManager ) ;
2012-07-20 03:54:24 +02:00
cmpTechnologyManager . StoppedResearch ( item . technologyTemplate ) ;
2012-04-20 19:21:04 +02:00
}
// Remove from the queue
// (We don't need to remove the timer - it'll expire if it discovers the queue is empty)
this . queue . splice ( i , 1 ) ;
Engine . PostMessage ( this . entity , MT _ProductionQueueChanged , { } ) ;
return ;
}
} ;
/ *
* Returns basic data from all batches in the production queue .
* /
ProductionQueue . prototype . GetQueue = function ( )
{
var out = [ ] ;
for each ( var item in this . queue )
{
out . push ( {
"id" : item . id ,
"unitTemplate" : item . unitTemplate ,
"technologyTemplate" : item . technologyTemplate ,
"count" : item . count ,
"progress" : 1 - ( item . timeRemaining / item . timeTotal ) ,
"metadata" : item . metadata ,
} ) ;
}
return out ;
} ;
/ *
* Removes all existing batches from the queue .
* /
ProductionQueue . prototype . ResetQueue = function ( )
{
// Empty the production queue and refund all the resource costs
// to the player. (This is to avoid players having to micromanage their
// buildings' queues when they're about to be destroyed or captured.)
while ( this . queue . length )
this . RemoveBatch ( this . queue [ 0 ] . id ) ;
} ;
2012-11-24 23:04:29 +01:00
/ *
* Returns batch build time .
* /
ProductionQueue . prototype . GetBatchTime = function ( batchSize )
{
var cmpPlayer = QueryOwnerInterface ( this . entity , IID _Player ) ;
2012-11-25 03:52:13 +01:00
2012-11-25 17:23:59 +01:00
var batchTimeModifier = + this . template . BatchTimeModifier ;
2012-11-25 03:52:13 +01:00
var cmpTechMan = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
if ( cmpTechMan )
2012-11-25 17:23:59 +01:00
batchTimeModifier = cmpTechMan . ApplyModifications ( "ProductionQueue/BatchTimeModifier" , batchTimeModifier , this . entity ) ;
2012-11-25 03:52:13 +01:00
2012-11-24 23:04:29 +01:00
// TODO: work out what equation we should use here.
2012-11-25 17:23:59 +01:00
return Math . pow ( batchSize , batchTimeModifier ) * cmpPlayer . cheatTimeMultiplier ;
2012-11-24 23:04:29 +01:00
} ;
2012-04-20 19:21:04 +02:00
ProductionQueue . prototype . OnOwnershipChanged = function ( msg )
{
if ( msg . from != - 1 )
{
// Unset flag that previous owner's training may be blocked
var cmpPlayer = QueryPlayerIDInterface ( msg . from , IID _Player ) ;
if ( cmpPlayer && this . queue . length > 0 )
cmpPlayer . UnBlockTraining ( ) ;
}
// Reset the production queue whenever the owner changes.
// (This should prevent players getting surprised when they capture
// an enemy building, and then loads of the enemy's civ's soldiers get
// created from it. Also it means we don't have to worry about
// updating the reserved pop slots.)
this . ResetQueue ( ) ;
} ;
ProductionQueue . prototype . OnDestroy = function ( )
{
// Reset the queue to refund any resources
this . ResetQueue ( ) ;
if ( this . timer )
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
cmpTimer . CancelTimer ( this . timer ) ;
}
} ;
/ *
* This function creates the entities and places them in world if possible .
* returns the number of successfully spawned entities .
* /
ProductionQueue . prototype . SpawnUnits = function ( templateName , count , metadata )
{
var cmpFootprint = Engine . QueryInterface ( this . entity , IID _Footprint ) ;
var cmpPosition = Engine . QueryInterface ( this . entity , IID _Position ) ;
var cmpOwnership = Engine . QueryInterface ( this . entity , IID _Ownership ) ;
var cmpRallyPoint = Engine . QueryInterface ( this . entity , IID _RallyPoint ) ;
var spawnedEnts = [ ] ;
if ( this . entityCache . length == 0 )
{
// We need entities to test spawning, but we don't want to waste resources,
// so only create them once and use as needed
for ( var i = 0 ; i < count ; ++ i )
{
2012-11-07 18:56:14 +01:00
var ent = Engine . AddEntity ( templateName ) ;
this . entityCache . push ( ent ) ;
// Decrement entity count in the EntityLimits component
// since it will be increased by EntityLimits.OnGlobalOwnershipChanged function,
// i.e. we replace a 'trained' entity to an 'alive' one
var cmpTrainingRestrictions = Engine . QueryInterface ( ent , IID _TrainingRestrictions ) ;
if ( cmpTrainingRestrictions )
{
var unitCategory = cmpTrainingRestrictions . GetCategory ( ) ;
var cmpPlayerEntityLimits = QueryOwnerInterface ( this . entity , IID _EntityLimits ) ;
cmpPlayerEntityLimits . DecrementCount ( unitCategory ) ;
}
2012-04-20 19:21:04 +02:00
}
}
for ( var i = 0 ; i < count ; ++ i )
{
var ent = this . entityCache [ 0 ] ;
var pos = cmpFootprint . PickSpawnPoint ( ent ) ;
if ( pos . y < 0 )
{
// Fail: there wasn't any space to spawn the unit
break ;
}
else
{
// Successfully spawned
var cmpNewPosition = Engine . QueryInterface ( ent , IID _Position ) ;
cmpNewPosition . JumpTo ( pos . x , pos . z ) ;
// TODO: what direction should they face in?
var cmpNewOwnership = Engine . QueryInterface ( ent , IID _Ownership ) ;
cmpNewOwnership . SetOwner ( cmpOwnership . GetOwner ( ) ) ;
var cmpPlayerStatisticsTracker = QueryOwnerInterface ( this . entity , IID _StatisticsTracker ) ;
cmpPlayerStatisticsTracker . IncreaseTrainedUnitsCounter ( ) ;
// Play a sound, but only for the first in the batch (to avoid nasty phasing effects)
if ( spawnedEnts . length == 0 )
PlaySound ( "trained" , ent ) ;
this . entityCache . shift ( ) ;
spawnedEnts . push ( ent ) ;
}
}
if ( spawnedEnts . length > 0 )
{
// If a rally point is set, walk towards it (in formation) using a suitable command based on where the
// rally point is placed.
if ( cmpRallyPoint )
{
2012-05-24 20:25:31 +02:00
var rallyPos = cmpRallyPoint . GetPositions ( ) [ 0 ] ;
2012-04-20 19:21:04 +02:00
if ( rallyPos )
{
2012-05-24 20:25:31 +02:00
var commands = GetRallyPointCommands ( cmpRallyPoint , spawnedEnts ) ;
for each ( var com in commands )
{
ProcessCommand ( cmpOwnership . GetOwner ( ) , com ) ;
}
2012-04-20 19:21:04 +02:00
}
}
Engine . PostMessage ( this . entity , MT _TrainingFinished , {
"entities" : spawnedEnts ,
"owner" : cmpOwnership . GetOwner ( ) ,
"metadata" : metadata ,
} ) ;
}
return spawnedEnts . length ;
} ;
/ *
* Increments progress on the first batch in the production queue , and blocks the
* queue if population limit is reached or some units failed to spawn .
* /
ProductionQueue . prototype . ProgressTimeout = function ( data )
{
// Allocate the 1000msecs to as many queue items as it takes
// until we've used up all the time (so that we work accurately
// with items that take fractions of a second)
var time = g _ProgressInterval ;
var cmpPlayer = QueryOwnerInterface ( this . entity , IID _Player ) ;
while ( time > 0 && this . queue . length )
{
var item = this . queue [ 0 ] ;
if ( ! item . productionStarted )
{
// If the item is a unit then do population checks
if ( item . unitTemplate )
{
// Batch's training hasn't started yet.
// Try to reserve the necessary population slots
if ( item . unitTemplate && ! cmpPlayer . TryReservePopulationSlots ( item . population * item . count ) )
{
// No slots available - don't train this batch now
// (we'll try again on the next timeout)
// Set flag that training is blocked
cmpPlayer . BlockTraining ( ) ;
break ;
}
// Unset flag that training is blocked
cmpPlayer . UnBlockTraining ( ) ;
}
2012-07-31 04:03:25 +02:00
if ( item . technologyTemplate )
{
// Mark the research as started.
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
cmpTechnologyManager . StartedResearch ( item . technologyTemplate ) ;
}
2012-04-20 19:21:04 +02:00
item . productionStarted = true ;
}
// If we won't finish the batch now, just update its timer
if ( item . timeRemaining > time )
{
item . timeRemaining -= time ;
break ;
}
if ( item . unitTemplate )
{
var numSpawned = this . SpawnUnits ( item . unitTemplate , item . count , item . metadata ) ;
if ( numSpawned == item . count )
{
// All entities spawned, this batch finished
cmpPlayer . UnReservePopulationSlots ( item . population * numSpawned ) ;
time -= item . timeRemaining ;
this . queue . shift ( ) ;
// Unset flag that training is blocked
cmpPlayer . UnBlockTraining ( ) ;
this . spawnNotified = false ;
Engine . PostMessage ( this . entity , MT _ProductionQueueChanged , { } ) ;
}
else
{
if ( numSpawned > 0 )
{
// Only partially finished
cmpPlayer . UnReservePopulationSlots ( item . population * numSpawned ) ;
item . count -= numSpawned ;
Engine . PostMessage ( this . entity , MT _ProductionQueueChanged , { } ) ;
}
// Some entities failed to spawn
// Set flag that training is blocked
cmpPlayer . BlockTraining ( ) ;
if ( ! this . spawnNotified )
{
var cmpPlayer = QueryOwnerInterface ( this . entity , IID _Player ) ;
var notification = { "player" : cmpPlayer . GetPlayerID ( ) , "message" : "Can't find free space to spawn trained units" } ;
var cmpGUIInterface = Engine . QueryInterface ( SYSTEM _ENTITY , IID _GuiInterface ) ;
cmpGUIInterface . PushNotification ( notification ) ;
this . spawnNotified = true ;
}
break ;
}
}
else if ( item . technologyTemplate )
{
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
cmpTechnologyManager . ResearchTechnology ( item . technologyTemplate ) ;
time -= item . timeRemaining ;
this . queue . shift ( ) ;
Engine . PostMessage ( this . entity , MT _ProductionQueueChanged , { } ) ;
}
}
// If the queue's empty, delete the timer, else repeat it
if ( this . queue . length == 0 )
{
this . timer = undefined ;
// Unset flag that training is blocked
// (This might happen when the player unqueues all batches)
cmpPlayer . UnBlockTraining ( ) ;
}
else
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
this . timer = cmpTimer . SetTimeout ( this . entity , IID _ProductionQueue , "ProgressTimeout" , g _ProgressInterval , data ) ;
}
}
Engine . RegisterComponentType ( IID _ProductionQueue , "ProductionQueue" , ProductionQueue ) ;