1 Exposed_Entity_Other_Functions
trac edited this page 2008-02-23 03:18:59 +00:00

Functions

issueCommand

  • Overview:
  • Issue a command (network message) to an entity or collection.
  • Syntax:
  • issueCommand(entity_or_collection, NMT_Goto);
  • Parameters:
    • either an entity- or entity collection object, message ID int, any further params needed by CNetMessage*
      CommandFromJSArgs
  • Returns:
  • command in serialized form string
  • Notes:
  • It is now possible for JS to give network synchronized orders to entities by using the issueCommand method like this:
  • selection[0].issueCommand(NMT_Goto, X, Y); or
  • selection[0].issueCommand(NMT_Gather, targetResourceEntity);
  • Sending to multiple entities is not yet possible, and I haven't even started on converting the input code to send JS events etc. I need to make some specs on what JS should be able to do and how it should do it - if nothing else to get a clear view of the new stuff. Well... tomorrow's problems -> tomorrow ;-)

// Call a function when the entity performs a certain event:

	<Event On="Attack" Function="entity_event_attack" />

localPlayer.resource

SelectUnit (UnitIndex) // Set the current selection to a certain unit index in the current selection.

SelectGroup (2) // Select a given ctrl group (eg set the selection to the group mapped to Ctrl+2).

selection.length // Number of units in current selection; if (selection.length) returns true if units are currently selected.

selection[0] // Reference the entity that's first in the selection.

'''(all these unique conditions are kind of beyond me at the moment ... May have to get Mark/Simon to document all these properties, so we can isolate the generic from the specific.)

// Container type for damage properties (crush, hack, pierce).

	dmg = new DamageType();
	dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
	dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
	dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);

// Inflict wound damage on a target.

	evt.target.damage( dmg, this );

// Inflict wound damage on a target using a projectile.

	evt.impacted.damage( this.damage, evt.originator );
	if( evt.impacted.player == evt.originator.player )
		console.write( "Friendly fire!" );

// Kill an entity. evt.target.kill();

// Get a function that's part of the GUI scope. getGUIGlobal().GiveResources("Ore", parseInt(this.traits.loot.ore));

// If unit is idle, tell it to attack the person attacking him.

		if( this.isIdle() )
			this.order( ORDER_ATTACK, evt.inflictor );
	evt.defaultAction = NMT_Goto;
	evt.defaultCursor = "arrow-default";
			evt.defaultAction = NMT_AttackMelee;
			evt.defaultCursor = "action-attack";
	// The parameters for Projectile are:
	// 1 - The actor to use as the projectile. There are two ways of specifying this:
	//     the first is by giving an entity. The projectile's actor is found by looking
	//     in the actor of that entity. This way is usual, and preferred - visual
	//     information, like the projectile model, should be stored in the actor files.
	//     The second way is to give a actor/file name string (e.g. "props/weapon/weap_
	//     arrow_front.xml"). This is only meant to be used for 'Acts of Gaia' where
	//     there is no originating entity. Right now, this entity is the one doing the
	//     firing, so pass this.
	// 2 - Where the projectile is coming from. This can be an entity or a Vector3D.
	//     For now, that's also us.
	// 3 - Where the projectile is going to. Again, either a vector (attack ground?) 
	//     or an entity. Let's shoot at our target, lest we get people terribly confused.
	// 4 - How fast the projectile should go. To keep things clear, we'll set it to 
	//     just a bit faster than the average cavalry.
	// 5 - Who fired it? Erm... yep, us again.
	// 6 - The function you'd like to call when it hits an entity.
	// There's also a seventh parameter, for a function to call when it misses (more
	//  accurately, when it hits the floor). At the moment, however, the default
	//  action (do nothing) is what we want.
	// Parameters 5, 6, and 7 are all optional.
	
	projectile = new Projectile( this, this, evt.target, 12.0, this, projectile_event_impact )
	// We'll attach the damage information to the projectile, just to show you can
	// do that like you can with most other objects. Could also do this by making
	// the function we pass a closure.
	
	projectile.damage = dmg;
	// Finally, tell the engine not to send this event to anywhere else - 
	// in particular, this shouldn't get to the melee event handler, above.
	
	evt.stopPropagation();
		 // Start the progress timer.
		 // - First parameter is target value (in this case, base build time in seconds)
		 // - Second parameter is increment per millisecond (use build rate modifier and correct for milliseconds)
		 // - Third parameter is the function to call when the timer finishes.
		 // - Fourth parameter is the scope under which to run that function (what the 'this' parameter should be)
		 this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
	// Code to find a free space around an object is tedious and slow, so 
	// I wrote it in C. Takes the template object so it can determine how
	// much space it needs to leave.
	position = this.getSpawnPoint( template );
                if (!selection.length)         // If no entity selected,
        DudeSpawnPoint = new Vector3D(x, y, z);
        new Entity(getEntityTemplate(MakeUnitName), DudeSpawnPoint, 1.0);
        // writeConsole(MakeUnitName + " created at " + DudeSpawnPoint);
        // Returns how many units selected.
        if( selection.length > 0 )
                return( selection[0] );
        return( null );

// ====================================================================

function selectEntity(handler) {

	endSelection();
	startSelection(function (event) {
			// Selection is performed when single-clicking the right mouse
			// button.
			if (event.button == SDL_BUTTON_RIGHT && event.clicks == 1)
			{
				handler(event.entity);
			}
			// End selection on first mouse-click
			endSelection();
		});

}

function selectLocation(handler) {

	endSelection();
	startSelection(function (event) {
			// Selection is performed when single-clicking the right mouse
			// button.
			if (event.button == SDL_BUTTON_RIGHT && event.clicks == 1)
			{
				handler(event.x, event.y);
			}
			// End selection on first mouse-click
			endSelection();
		});

}

function startSelection(handler) {

	gameView.startCustomSelection();
	getGlobal().selectionWorldClickHandler=handler;
	console.write("isSelecting(): "+isSelecting());

}

function endSelection() {

	if (!isSelecting())
		return;
	
	gameView.endCustomSelection();
	getGlobal().selectionWorldClickHandler = null;

}

function isSelecting() {

	return getGlobal().selectionWorldClickHandler != null;

}

// The world-click handler - called whenever the user clicks the terrain function worldClickHandler(event) {

	args=new Array(null, null);
	console.write("worldClickHandler: button "+event.button+", clicks "+event.clicks);
	if (isSelecting())
	{
		getGlobal().selectionWorldClickHandler(event);
		return;
	}
	// Right button single- or double-clicks
	if (event.button == SDL_BUTTON_RIGHT && event.clicks <= 2)
	{
		if (event.clicks == 1)
			cmd = event.command;
		else if (event.clicks == 2)
		{
			console.write("Issuing secondary command");
			cmd = event.secondaryCommand;
		}
	}
	else
		return;
	switch (cmd)
	{
		// location target commands
		case NMT_Goto:
		case NMT_Patrol:
			if (event.queued)
			{
				cmd = NMT_AddWaypoint;
			}
		case NMT_AddWaypoint:
			args[0]=event.x;
			args[1]=event.y;
			break;
		// entity target commands
		case NMT_AttackMelee:
		case NMT_Gather:
			args[0]=event.entity;
			args[1]=null;
			break;
		default:
			console.write("worldClickHandler: Unknown command: "+cmd);
			return;
	}
	issueCommand(selection, cmd, args[0], args[1]);

}

addGlobalHandler("worldClick", worldClickHandler);

			switch (SN_STATUS_PANE_COMMAND[list][tab].name)
			{
			case action_patrol:
				// setCursor(...)
				selectLocation(
					function (x, y) {
						issueCommand(selection, NMT_Patrol, x, y);
					});
				break;
			case action_attack:
				// setCursor(...)
				selectEntity(
					function (target) {
						issueCommand(selection, NMT_AttackMelee, target);
					});
				break;
			}
		// Attempt to add the entry to the queue.
		attempt_add_to_build_queue( selection[0], selection[0].traits.id.civ_code + "_" + SN_STATUS_PANE_COMMAND[list][tab].name, list, tab);

// ====================================================================

// Update-on-alteration trickery...

// We don't really want to update every single time we get a // selection-changed or property-changed event; that could happen // a lot. Instead, use this bunch of globals to cache any changes // that happened between GUI updates.

// This boolean determines whether the selection has been changed. var selectionChanged = false;

// This boolean determines what the template of the selected object // was when last we looked var selectionTemplate = null;

// This array holds the name of all properties that need to be updated var selectionPropertiesChanged = new Array();

// This array holds a list of all the objects we hold property-change // watches on var propertyWatches = new Array();

// This function resets all the update variables, above function resetUpdateVars() {

	if( selectionChanged )
	{
		for( watchedObject in propertyWatches )
			propertyWatches[watchedObject].unwatchAll( selectionWatchHandler ); // Remove the handler
		
		propertyWatches = new Array();
		if( selection[0] )
		{
			// Watch the object itself
			selection[0].watchAll( selectionWatchHandler );
			propertyWatches.push( selection[0] );
			// And every parent back up the tree (changes there will affect
			// displayed properties via inheritance)
			var parent = selection[0].template
			while( parent )
			{
				parent.watchAll( selectionWatchHandler );
				propertyWatches.push( selection[0] );
				parent = parent.parent;
			}
		}
	}
	selectionChanged = false;
	if( selection[0] ) 
	{
		selectionTemplate = selection[0].template;
	}
	else
		selectionTemplate = null;
		
	selectionPropertiesChanged = new Array();

}

// This function returns whether we should update a particular statistic // in the GUI (e.g. "actions.attack") - this should happen if: the selection // changed, the selection had its template altered (changing lots of stuff) // or an assignment has been made to that stat or any property within that // stat. function shouldUpdateStat( statname ) {

	if( selectionChanged || ( selectionTemplate != selection[0].template ) )
		return( true );
	for( var property in selectionPropertiesChanged )
	{
		// If property starts with statname
		if( selectionPropertiesChanged[property].substring( 0, statname.length ) == statname )
			return( true );
	}
	return( false );	

}

// This function is a handler for the 'selectionChanged' event, // it updates the selectionChanged flag function selectionChangedHandler() {

	selectionChanged = true;

}

// Register it. addGlobalHandler( "selectionChanged", selectionChangedHandler );

// This function is a handler for a watch event; it updates the // selectionPropertiesChanged array function selectionWatchHandler( propname, oldvalue, newvalue ) {

	selectionPropertiesChanged.push( propname );
	// This bit's important (watches allow the handler to change the value
	// before it gets written; we don't want to affect things, so make sure
	// the value we send back is the one that was going to be written)
	return( newvalue ); 

}

function entity_event_attack( evt ) {

	curr_hit = getGUIGlobal().newRandomSound("voice", "hit", this.traits.audio.path);
	curr_hit.play();
	// Attack logic.
	dmg = new DamageType();
	dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
	dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
	dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
	evt.target.damage( dmg, this );

}

// ====================================================================

function entity_event_attack_ranged( evt ) {

	// Create a projectile from us, to the target, that will do some damage when it hits them.
	dmg = new DamageType();
	dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
	dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
	dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
	
	// The parameters for Projectile are:
	// 1 - The actor to use as the projectile. There are two ways of specifying this:
	//     the first is by giving an entity. The projectile's actor is found by looking
	//     in the actor of that entity. This way is usual, and preferred - visual
	//     information, like the projectile model, should be stored in the actor files.
	//     The second way is to give a actor/file name string (e.g. "props/weapon/weap_
	//     arrow_front.xml"). This is only meant to be used for 'Acts of Gaia' where
	//     there is no originating entity. Right now, this entity is the one doing the
	//     firing, so pass this.
	// 2 - Where the projectile is coming from. This can be an entity or a Vector3D.
	//     For now, that's also us.
	// 3 - Where the projectile is going to. Again, either a vector (attack ground?) 
	//     or an entity. Let's shoot at our target, lest we get people terribly confused.
	// 4 - How fast the projectile should go. To keep things clear, we'll set it to 
	//     just a bit faster than the average cavalry.
	// 5 - Who fired it? Erm... yep, us again.
	// 6 - The function you'd like to call when it hits an entity.
	// There's also a seventh parameter, for a function to call when it misses (more
	//  accurately, when it hits the floor). At the moment, however, the default
	//  action (do nothing) is what we want.
	// Parameters 5, 6, and 7 are all optional.
	
	projectile = new Projectile( this, this, evt.target, 12.0, this, projectile_event_impact )
	
	// We'll attach the damage information to the projectile, just to show you can
	// do that like you can with most other objects. Could also do this by making
	// the function we pass a closure.
	
	projectile.damage = dmg;
	
	// Finally, tell the engine not to send this event to anywhere else - 
	// in particular, this shouldn't get to the melee event handler, above.
	
	evt.stopPropagation();
	
	console.write( "Fire!" );

}

// ====================================================================

function projectile_event_impact( evt ) {

	console.write( "Hit!" );
	evt.impacted.damage( this.damage, evt.originator );
	
	// Just so you know - there's no guarantee that evt.impacted is the thing you were
	// aiming at. This function gets called when the projectile hits *anything*.
	// For example:
	
	if( evt.impacted.player == evt.originator.player )
		console.write( "Friendly fire!" );
		
	// The three properties of the ProjectileImpact event are:
	// - impacted, the thing it hit
	// - originator, the thing that fired it (the fifth parameter of Projectile's
	//   constructor) - may be null
	// - position, the position the arrow was in the world when it hit.
	
	// The properties of the ProjectileMiss event (the one that gets sent to the
	// handler that was the seventh parameter of the constructor) are similar,
	// but it doesn't have 'impacted' - for obvious reasons.

}

// ====================================================================

function entity_event_gather( evt ) {

	if (this.actions.gather[evt.target.traits.supply.type][evt.target.traits.supply.subtype])
		gather_amt = parseInt( this.actions.gather[evt.target.traits.supply.type][evt.target.traits.supply.subtype].speed );
	else
		gather_amt = parseInt( this.actions.gather[evt.target.traits.supply.type].speed );
	if( evt.target.traits.supply.max > 0 )
	{
	    if( evt.target.traits.supply.curr <= gather_amt )
	    {
			gather_amt = evt.target.traits.supply.curr;
			evt.target.kill();
	    }
	    evt.target.traits.supply.curr -= gather_amt;
	    this.player.resource[evt.target.traits.supply.type.toString().toUpperCase()] += gather_amt;
	}

}

// ====================================================================

function entity_event_takesdamage( evt ) {

	// Apply armour and work out how much damage we actually take
	crushDamage = parseInt(evt.damage.crush - this.traits.armour.value * this.traits.armour.crush);
	if ( crushDamage < 0 ) crushDamage = 0;
	pierceDamage = parseInt(evt.damage.pierce - this.traits.armour.value * this.traits.armour.pierce);
	if ( pierceDamage < 0 ) pierceDamage = 0;
	hackDamage = parseInt(evt.damage.hack - this.traits.armour.value * this.traits.armour.hack);
	if ( hackDamage < 0 ) hackDamage = 0;
	totalDamage = parseInt(evt.damage.typeless + crushDamage + pierceDamage + hackDamage);
	// Minimum of 1 damage
	if( totalDamage < 1 ) totalDamage = 1;
	this.traits.health.curr -= totalDamage;
	if( this.traits.health.curr <= 0 )
	{
		// If the inflictor gains promotions, and he's capable of earning more ranks,
		if (evt.inflictor.traits.up && evt.inflictor.traits.up.curr && evt.inflictor.traits.up.req && evt.inflictor.traits.up.newentity && evt.inflictor.traits.up.newentity != "")
		{
			// Give him the fallen's upgrade points (if he has any).
			if (this.traits.loot.up)
				evt.inflictor.traits.up.curr = parseInt(evt.inflictor.traits.up.curr) + parseInt(this.traits.loot.up);
			// Notify player.
			if (this.traits.id.specific)
				console.write(this.traits.id.specific + " has earned " + this.traits.loot.up + " upgrade points!");
			else
				console.write("One of your units has earned " + this.traits.loot.up + " upgrade points!");
			// If he now has maximum upgrade points for his rank,
			if (evt.inflictor.traits.up.curr >= evt.inflictor.traits.up.req)
			{
				// Notify the player.
				if (this.traits.id.specific)
					console.write(this.traits.id.specific + " has gained a promotion!");
				else
					console.write("One of your units has gained a promotion!");
				
				// Reset his upgrade points.
				evt.inflictor.traits.up.curr = 0; 
				// Upgrade his portrait to the next level.
				evt.inflictor.traits.id.icon_cell++; 
				// Transmogrify him into his next rank.
				evt.inflictor.template = getEntityTemplate(evt.inflictor.traits.up.newentity);
			}
		}
		// If the fallen is worth any loot,
		if (this.traits.loot && (this.traits.loot.food || this.traits.loot.wood || this.traits.loot.stone || this.traits.loot.ore))
		{
			// Give the inflictor his resources.
			if (this.traits.loot.food)
				getGUIGlobal().GiveResources("Food", parseInt(this.traits.loot.food));
			if (this.traits.loot.wood)
				getGUIGlobal().GiveResources("Wood", parseInt(this.traits.loot.wood));
			if (this.traits.loot.stone)
				getGUIGlobal().GiveResources("Stone", parseInt(this.traits.loot.stone));
			if (this.traits.loot.ore)
				getGUIGlobal().GiveResources("Ore", parseInt(this.traits.loot.ore));
		}
		// Notify player.
		if( evt.inflictor )
			console.write( this.traits.id.generic + " got the point of " + evt.inflictor.traits.id.generic + "'s weapon." );
		else
			console.write( this.traits.id.generic + " died in mysterious circumstances." );
		// Make him cry out in pain.
		curr_pain = getGUIGlobal().newRandomSound("voice", "pain", this.traits.audio.path);
		curr_pain.play();
		// We've taken what we need. Kill the swine.
		this.kill();
	}
	else if( evt.inflictor && this.actions.attack )
	{
		// If we're not already doing something else, take a measured response - hit 'em back.
		// You know, I think this is quite possibly the first AI code the AI divlead has written
		// for 0 A.D....
		if( this.isIdle() )
			this.order( ORDER_ATTACK, evt.inflictor );
	}

}

function entity_event_targetchanged( evt ) {

	// This event lets us know when the user moves his/her cursor to a different unit (provided this
	// unit is selected) - use it to tell the engine what context cursor should be displayed, given
	// the target.
	// Attack iff there's a target, it's our enemy, and we're armed. Otherwise, if we can gather, and
	// the target supplies, gather. If all else fails, move.
	// ToString is needed because every property is actually an object (though that's usually
	// hidden from you) and comparing an object to any other object in JavaScript (1.5, at least)
	// yields false. ToString converts them to their actual values (i.e. the four character
	// string) first.
	
	evt.defaultAction = NMT_Goto;
	evt.defaultCursor = "arrow-default";
	if( evt.target )
	{
	    if( this.actions.attack && 
			( evt.target.player != gaiaPlayer ) &&
			( evt.target.player != this.player ) )
		{
			evt.defaultAction = NMT_AttackMelee;
			evt.defaultCursor = "action-attack";
		}
	    if( this.actions.gather && evt.target.traits.supply &&
			this.actions.gather[evt.target.traits.supply.type] &&
			( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) )
		{
		    evt.defaultAction = NMT_Gather;
		    // Set cursor (eg "action-gather-fruit").
		    evt.defaultCursor = "action-gather-" + evt.target.traits.supply.subtype;
		}
	}

}

// ====================================================================

function entity_event_prepareorder( evt ) {

	// This event gives us a chance to veto any order we're given before we execute it.
	// Not sure whether this really belongs here like this: the alternative is to override it in
	// subtypes - then you wouldn't need to check tags, you could hardcode results.
	switch( evt.orderType )
	{
	case ORDER_GOTO:
		if( !this.actions.move )
			evt.preventDefault();
		break;
	case ORDER_PATROL:
		if( !this.actions.patrol )
			evt.preventDefault();
		break;	
	case ORDER_ATTACK:
		if( !this.actions.attack || 
		    !evt.target )
			evt.preventDefault();
		break;
	case ORDER_GATHER:
		if( !this.actions.gather ||
		    !( this.actions.gather[evt.target.traits.supply.type] ) ||
		    ( ( evt.target.traits.supply.curr == 0 ) && ( evt.target.traits.supply.max > 0 ) ) )
			evt.preventDefault();
		break;
	default:
		evt.preventDefault();
	}

}

// ====================================================================

function entity_add_create_queue( template, list, tab ) {

	// Make sure we have a queue to put things in...
	if( !this.actions.create.queue )
		this.actions.create.queue      = new Array();
	// Construct template object.
	comboTemplate = template;
	comboTemplate.list = list;
	comboTemplate.tab = tab;
	// Append to the end of this queue
	this.actions.create.queue.push( template );
	// If we're not already building something...
	if( !this.actions.create.progress )
	{
		console.write( "Starting work on (unqueued) ", template.tag );
		 // Start the progress timer.
		 // - First parameter is target value (in this case, base build time in seconds)
		 // - Second parameter is increment per millisecond (use build rate modifier and correct for milliseconds)
		 // - Third parameter is the function to call when the timer finishes.
		 // - Fourth parameter is the scope under which to run that function (what the 'this' parameter should be)
		 this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
	}

}

// ====================================================================

// This is the syntax to add a function (or a property) to absolutely every entity.

Entity.prototype.add_create_queue = entity_add_create_queue;

// ====================================================================

function entity_create_complete() {

	// Get the unit that was at the head of our queue, and remove it.
	// (Oh, for information about all these nifty properties and functions
	//  of the Array object that I use, see the ECMA-262 documentation
	//  at http://www.mozilla.org/js/language/E262-3.pdf. Bit technical but
	//  the sections on 'Native ECMAScript Objects' are quite useful)
	
	var template = this.actions.create.queue.shift();
	// Code to find a free space around an object is tedious and slow, so 
	// I wrote it in C. Takes the template object so it can determine how
	// much space it needs to leave.
	position = this.getSpawnPoint( template );
	
	// The above function returns null if it couldn't find a large enough space.
	if( !position )
	{
		console.write( "Couldn't train unit - not enough space" );
		// Oh well. The player's just lost all the resources and time they put into
		// construction - serves them right for not paying attention to the land
		// around their barracks, doesn't it?
	}
	else
	{
		created = new Entity( template, position );
	
		// Above shouldn't ever fail, but just in case...
		if( created )
		{
			console.write( "Created: ", template.tag );
	
			// Entities start under Gaia control - make the controller
			// the same as our controller
			created.player = this.player;
		}
	}		
	// If there's something else in the build queue...
	if( this.actions.create.queue.length > 0 )
	{
		// Start on the next item.
		template = this.actions.create.queue[0];
		console.write( "Starting work on (queued) ", template.tag );
		this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
	}
	else
	{
		// Otherwise, delete the timer.
		this.actions.create.progress = null;
	}

}

// ====================================================================

function attempt_add_to_build_queue( entity, create_tag, list, tab ) {

	result = entity_CheckQueueReq (entity);
	if (result == "true") // If the entry meets requirements to be added to the queue (eg sufficient resources) 
	{
		// Cycle through all costs of this entry.
		pool = entity.traits.creation.resource;
		for( resource in pool )
		{
			switch( resource.toString().toUpperCase() )
			{
				case "POPULATION" || "HOUSING":
				break;
				default:
					// Deduct the given quantity of resources.
					localPlayer.resource[resource.toString().toUpperCase()] -= pool[resource].cost;
					console.write("Spent " + pool[resource].cost + " " + resource + ".");
				break;
			}
		}
		// Add entity to queue.
		console.write( "Adding ", create_tag, " to build queue..." );
		entity.add_create_queue( getEntityTemplate( create_tag ), list, tab );
	}
	else		// If not, output the error message.
		console.write(result);

}

// ====================================================================

function entity_CheckQueueReq (entry) {

	// Determines if the given entity meets requirements for production by the player, and returns an appropriate 
	// error string.
	// A return value of 0 equals success -- entry meets requirements for production. 
	
	// Cycle through all resources that this item costs, and check the player can afford the cost.
	resources = entry.traits.creation.resource;
	for( resource in resources )
	{
		resourceU = resource.toString().toUpperCase();
		
		switch( resourceU )
		{
			case "POPULATION":
				// If the item costs more of this resource type than we have,
				if (resources[resource].cost > (localPlayer.resource["HOUSING"]-localPlayer.resource[resourceU]))
				{
					// Return an error.
					return ("Insufficient Housing; " + (resources[resource].cost-localPlayer.resource["HOUSING"]-localPlayer.resource.valueOf()[resourceU].toString()) + " required."); 
				}
			break;
			case "HOUSING": // Ignore housing. It's handled in combination with population.
			break
			default:
				// If the item costs more of this resource type than we have,
				if (resources[resource].cost > localPlayer.resource[resourceU])
				{
					// Return an error.
					return ("Insufficient " + resource + "; " + (localPlayer.resource[resourceU]-resources[resource].cost)*-1 + " required."); 
				}
				else
					console.write("Player has at least " + resources[resource].cost + " " + resource + ".");
			break;
		}
	}
	// Check if another entity must first exist. 
	// Check if another tech must first be researched. 
	// Check if the limit for this type of entity has been reached. 
	// If we passed all checks, return success. Entity can be queued.
	return "true"; 

}

// ====================================================================