Changes between Initial Version and Version 1 of Exposed_Entity_Other_Functions


Ignore:
Timestamp:
Feb 23, 2008, 4:18:59 AM (16 years ago)
Author:
trac
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Exposed_Entity_Other_Functions

    v1 v1  
     1= Functions =
     2
     3== issueCommand ==
     4 * '''Overview:'''
     5  * Issue a command (network message) to an entity or collection.
     6 * '''Syntax:'''
     7  * issueCommand(entity_or_collection, NMT_Goto);
     8 * '''Parameters:'''
     9  * either an entity- or entity collection object, message ID [int], any further params needed by CNetMessage::!CommandFromJSArgs
     10 * '''Returns:'''
     11  * command in serialized form [string]
     12 * '''Notes:'''
     13  * It is now possible for JS to give network synchronized orders to entities by using the issueCommand method like this:
     14   * selection[0].issueCommand(NMT_Goto, X, Y); or
     15   * selection[0].issueCommand(NMT_Gather, targetResourceEntity);
     16  * 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 ;-)
     17
     18// Call a function when the entity performs a certain event:
     19{{{
     20        <Event On="Attack" Function="entity_event_attack" />
     21}}}
     22
     23localPlayer.resource
     24
     25!SelectUnit (!UnitIndex) // Set the current selection to a certain unit index in the current selection.
     26
     27!SelectGroup (2) // Select a given ctrl group (eg set the selection to the group mapped to Ctrl+2).
     28
     29selection.length // Number of units in current selection; if (selection.length) returns true if units are currently selected.
     30
     31selection[0] // Reference the entity that's first in the selection.
     32
     33'''(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.)
     34
     35// Container type for damage properties (crush, hack, pierce).
     36{{{
     37        dmg = new DamageType();
     38        dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
     39        dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
     40        dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
     41}}}
     42
     43// Inflict wound damage on a target.
     44{{{
     45        evt.target.damage( dmg, this );
     46}}}
     47// Inflict wound damage on a target using a projectile.
     48{{{
     49        evt.impacted.damage( this.damage, evt.originator );
     50        if( evt.impacted.player == evt.originator.player )
     51                console.write( "Friendly fire!" );
     52}}}
     53// Kill an entity.
     54evt.target.kill();
     55
     56// Get a function that's part of the GUI scope.
     57getGUIGlobal().!GiveResources("Ore", parseInt(this.traits.loot.ore));
     58
     59// If unit is idle, tell it to attack the person attacking him.
     60{{{
     61                if( this.isIdle() )
     62                        this.order( ORDER_ATTACK, evt.inflictor );
     63}}}
     64
     65{{{
     66        evt.defaultAction = NMT_Goto;
     67        evt.defaultCursor = "arrow-default";
     68                        evt.defaultAction = NMT_AttackMelee;
     69                        evt.defaultCursor = "action-attack";
     70}}}
     71
     72{{{
     73        // The parameters for Projectile are:
     74        // 1 - The actor to use as the projectile. There are two ways of specifying this:
     75        //     the first is by giving an entity. The projectile's actor is found by looking
     76        //     in the actor of that entity. This way is usual, and preferred - visual
     77        //     information, like the projectile model, should be stored in the actor files.
     78        //     The second way is to give a actor/file name string (e.g. "props/weapon/weap_
     79        //     arrow_front.xml"). This is only meant to be used for 'Acts of Gaia' where
     80        //     there is no originating entity. Right now, this entity is the one doing the
     81        //     firing, so pass this.
     82        // 2 - Where the projectile is coming from. This can be an entity or a Vector3D.
     83        //     For now, that's also us.
     84        // 3 - Where the projectile is going to. Again, either a vector (attack ground?)
     85        //     or an entity. Let's shoot at our target, lest we get people terribly confused.
     86        // 4 - How fast the projectile should go. To keep things clear, we'll set it to
     87        //     just a bit faster than the average cavalry.
     88        // 5 - Who fired it? Erm... yep, us again.
     89        // 6 - The function you'd like to call when it hits an entity.
     90        // There's also a seventh parameter, for a function to call when it misses (more
     91        //  accurately, when it hits the floor). At the moment, however, the default
     92        //  action (do nothing) is what we want.
     93        // Parameters 5, 6, and 7 are all optional.
     94       
     95        projectile = new Projectile( this, this, evt.target, 12.0, this, projectile_event_impact )
     96}}}
     97
     98{{{
     99        // We'll attach the damage information to the projectile, just to show you can
     100        // do that like you can with most other objects. Could also do this by making
     101        // the function we pass a closure.
     102       
     103        projectile.damage = dmg;
     104}}}
     105
     106{{{
     107        // Finally, tell the engine not to send this event to anywhere else -
     108        // in particular, this shouldn't get to the melee event handler, above.
     109       
     110        evt.stopPropagation();
     111}}}
     112
     113{{{
     114                 // Start the progress timer.
     115                 // - First parameter is target value (in this case, base build time in seconds)
     116                 // - Second parameter is increment per millisecond (use build rate modifier and correct for milliseconds)
     117                 // - Third parameter is the function to call when the timer finishes.
     118                 // - Fourth parameter is the scope under which to run that function (what the 'this' parameter should be)
     119                 this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
     120}}}
     121
     122{{{
     123        // Code to find a free space around an object is tedious and slow, so
     124        // I wrote it in C. Takes the template object so it can determine how
     125        // much space it needs to leave.
     126        position = this.getSpawnPoint( template );
     127}}}
     128
     129
     130{{{
     131                if (!selection.length)         // If no entity selected,
     132}}}
     133
     134{{{
     135        DudeSpawnPoint = new Vector3D(x, y, z);
     136        new Entity(getEntityTemplate(MakeUnitName), DudeSpawnPoint, 1.0);
     137        // writeConsole(MakeUnitName + " created at " + DudeSpawnPoint);
     138}}}
     139
     140
     141{{{
     142        // Returns how many units selected.
     143}}}
     144
     145{{{
     146        if( selection.length > 0 )
     147                return( selection[0] );
     148        return( null );
     149}}}
     150
     151// ====================================================================
     152
     153function selectEntity(handler)
     154{
     155{{{
     156        endSelection();
     157        startSelection(function (event) {
     158                        // Selection is performed when single-clicking the right mouse
     159                        // button.
     160                        if (event.button == SDL_BUTTON_RIGHT && event.clicks == 1)
     161                        {
     162                                handler(event.entity);
     163                        }
     164                        // End selection on first mouse-click
     165                        endSelection();
     166                });
     167}}}
     168}
     169
     170function selectLocation(handler)
     171{
     172{{{
     173        endSelection();
     174        startSelection(function (event) {
     175                        // Selection is performed when single-clicking the right mouse
     176                        // button.
     177                        if (event.button == SDL_BUTTON_RIGHT && event.clicks == 1)
     178                        {
     179                                handler(event.x, event.y);
     180                        }
     181                        // End selection on first mouse-click
     182                        endSelection();
     183                });
     184}}}
     185}
     186
     187function startSelection(handler)
     188{
     189{{{
     190        gameView.startCustomSelection();
     191        getGlobal().selectionWorldClickHandler=handler;
     192        console.write("isSelecting(): "+isSelecting());
     193}}}
     194}
     195
     196function endSelection()
     197{
     198{{{
     199        if (!isSelecting())
     200                return;
     201       
     202        gameView.endCustomSelection();
     203        getGlobal().selectionWorldClickHandler = null;
     204}}}
     205}
     206
     207function isSelecting()
     208{
     209{{{
     210        return getGlobal().selectionWorldClickHandler != null;
     211}}}
     212}
     213
     214// The world-click handler - called whenever the user clicks the terrain
     215function worldClickHandler(event)
     216{
     217{{{
     218        args=new Array(null, null);
     219}}}
     220
     221{{{
     222        console.write("worldClickHandler: button "+event.button+", clicks "+event.clicks);
     223}}}
     224
     225{{{
     226        if (isSelecting())
     227        {
     228                getGlobal().selectionWorldClickHandler(event);
     229                return;
     230        }
     231}}}
     232
     233
     234{{{
     235        // Right button single- or double-clicks
     236        if (event.button == SDL_BUTTON_RIGHT && event.clicks <= 2)
     237        {
     238                if (event.clicks == 1)
     239                        cmd = event.command;
     240                else if (event.clicks == 2)
     241                {
     242                        console.write("Issuing secondary command");
     243                        cmd = event.secondaryCommand;
     244                }
     245        }
     246        else
     247                return;
     248}}}
     249
     250{{{
     251        switch (cmd)
     252        {
     253                // location target commands
     254                case NMT_Goto:
     255                case NMT_Patrol:
     256                        if (event.queued)
     257                        {
     258                                cmd = NMT_AddWaypoint;
     259                        }
     260                case NMT_AddWaypoint:
     261                        args[0]=event.x;
     262                        args[1]=event.y;
     263                        break;
     264                // entity target commands
     265                case NMT_AttackMelee:
     266                case NMT_Gather:
     267                        args[0]=event.entity;
     268                        args[1]=null;
     269                        break;
     270                default:
     271                        console.write("worldClickHandler: Unknown command: "+cmd);
     272                        return;
     273        }
     274}}}
     275
     276{{{
     277        issueCommand(selection, cmd, args[0], args[1]);
     278}}}
     279}
     280
     281addGlobalHandler("worldClick", worldClickHandler);
     282
     283{{{
     284                        switch (SN_STATUS_PANE_COMMAND[list][tab].name)
     285                        {
     286                        case action_patrol:
     287                                // setCursor(...)
     288                                selectLocation(
     289                                        function (x, y) {
     290                                                issueCommand(selection, NMT_Patrol, x, y);
     291                                        });
     292                                break;
     293                        case action_attack:
     294                                // setCursor(...)
     295                                selectEntity(
     296                                        function (target) {
     297                                                issueCommand(selection, NMT_AttackMelee, target);
     298                                        });
     299                                break;
     300                        }
     301}}}
     302
     303{{{
     304                // Attempt to add the entry to the queue.
     305                attempt_add_to_build_queue( selection[0], selection[0].traits.id.civ_code + "_" + SN_STATUS_PANE_COMMAND[list][tab].name, list, tab);
     306}}}
     307
     308// ====================================================================
     309
     310// Update-on-alteration trickery...
     311
     312// We don't really want to update every single time we get a
     313// selection-changed or property-changed event; that could happen
     314// a lot. Instead, use this bunch of globals to cache any changes
     315// that happened between GUI updates.
     316
     317// This boolean determines whether the selection has been changed.
     318var selectionChanged = false;
     319
     320// This boolean determines what the template of the selected object
     321// was when last we looked
     322var selectionTemplate = null;
     323
     324// This array holds the name of all properties that need to be updated
     325var selectionPropertiesChanged = new Array();
     326
     327// This array holds a list of all the objects we hold property-change
     328// watches on
     329var propertyWatches = new Array();
     330 
     331// This function resets all the update variables, above
     332function resetUpdateVars()
     333{
     334{{{
     335        if( selectionChanged )
     336        {
     337                for( watchedObject in propertyWatches )
     338                        propertyWatches[watchedObject].unwatchAll( selectionWatchHandler ); // Remove the handler
     339               
     340                propertyWatches = new Array();
     341                if( selection[0] )
     342                {
     343                        // Watch the object itself
     344                        selection[0].watchAll( selectionWatchHandler );
     345                        propertyWatches.push( selection[0] );
     346                        // And every parent back up the tree (changes there will affect
     347                        // displayed properties via inheritance)
     348                        var parent = selection[0].template
     349                        while( parent )
     350                        {
     351                                parent.watchAll( selectionWatchHandler );
     352                                propertyWatches.push( selection[0] );
     353                                parent = parent.parent;
     354                        }
     355                }
     356        }
     357        selectionChanged = false;
     358        if( selection[0] )
     359        {
     360                selectionTemplate = selection[0].template;
     361        }
     362        else
     363                selectionTemplate = null;
     364               
     365        selectionPropertiesChanged = new Array();
     366}}}
     367}
     368
     369// This function returns whether we should update a particular statistic
     370// in the GUI (e.g. "actions.attack") - this should happen if: the selection
     371// changed, the selection had its template altered (changing lots of stuff)
     372// or an assignment has been made to that stat or any property within that
     373// stat.
     374function shouldUpdateStat( statname )
     375{
     376{{{
     377        if( selectionChanged || ( selectionTemplate != selection[0].template ) )
     378                return( true );
     379        for( var property in selectionPropertiesChanged )
     380        {
     381                // If property starts with statname
     382                if( selectionPropertiesChanged[property].substring( 0, statname.length ) == statname )
     383                        return( true );
     384        }
     385        return( false );       
     386}}}
     387}
     388
     389// This function is a handler for the 'selectionChanged' event,
     390// it updates the selectionChanged flag
     391function selectionChangedHandler()
     392{
     393{{{
     394        selectionChanged = true;
     395}}}
     396}
     397
     398// Register it.
     399addGlobalHandler( "selectionChanged", selectionChangedHandler );
     400
     401// This function is a handler for a watch event; it updates the
     402// selectionPropertiesChanged array
     403function selectionWatchHandler( propname, oldvalue, newvalue )
     404{
     405{{{
     406        selectionPropertiesChanged.push( propname );
     407        // This bit's important (watches allow the handler to change the value
     408        // before it gets written; we don't want to affect things, so make sure
     409        // the value we send back is the one that was going to be written)
     410        return( newvalue );
     411}}}
     412}
     413
     414function entity_event_attack( evt )
     415{
     416{{{
     417        curr_hit = getGUIGlobal().newRandomSound("voice", "hit", this.traits.audio.path);
     418        curr_hit.play();
     419}}}
     420
     421{{{
     422        // Attack logic.
     423        dmg = new DamageType();
     424        dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
     425        dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
     426        dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
     427        evt.target.damage( dmg, this );
     428}}}
     429}
     430
     431// ====================================================================
     432
     433function entity_event_attack_ranged( evt )
     434{
     435{{{
     436        // Create a projectile from us, to the target, that will do some damage when it hits them.
     437        dmg = new DamageType();
     438        dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
     439        dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
     440        dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
     441       
     442        // The parameters for Projectile are:
     443        // 1 - The actor to use as the projectile. There are two ways of specifying this:
     444        //     the first is by giving an entity. The projectile's actor is found by looking
     445        //     in the actor of that entity. This way is usual, and preferred - visual
     446        //     information, like the projectile model, should be stored in the actor files.
     447        //     The second way is to give a actor/file name string (e.g. "props/weapon/weap_
     448        //     arrow_front.xml"). This is only meant to be used for 'Acts of Gaia' where
     449        //     there is no originating entity. Right now, this entity is the one doing the
     450        //     firing, so pass this.
     451        // 2 - Where the projectile is coming from. This can be an entity or a Vector3D.
     452        //     For now, that's also us.
     453        // 3 - Where the projectile is going to. Again, either a vector (attack ground?)
     454        //     or an entity. Let's shoot at our target, lest we get people terribly confused.
     455        // 4 - How fast the projectile should go. To keep things clear, we'll set it to
     456        //     just a bit faster than the average cavalry.
     457        // 5 - Who fired it? Erm... yep, us again.
     458        // 6 - The function you'd like to call when it hits an entity.
     459        // There's also a seventh parameter, for a function to call when it misses (more
     460        //  accurately, when it hits the floor). At the moment, however, the default
     461        //  action (do nothing) is what we want.
     462        // Parameters 5, 6, and 7 are all optional.
     463       
     464        projectile = new Projectile( this, this, evt.target, 12.0, this, projectile_event_impact )
     465       
     466        // We'll attach the damage information to the projectile, just to show you can
     467        // do that like you can with most other objects. Could also do this by making
     468        // the function we pass a closure.
     469       
     470        projectile.damage = dmg;
     471       
     472        // Finally, tell the engine not to send this event to anywhere else -
     473        // in particular, this shouldn't get to the melee event handler, above.
     474       
     475        evt.stopPropagation();
     476       
     477        console.write( "Fire!" );
     478}}}
     479}
     480
     481// ====================================================================
     482
     483function projectile_event_impact( evt )
     484{
     485{{{
     486        console.write( "Hit!" );
     487        evt.impacted.damage( this.damage, evt.originator );
     488       
     489        // Just so you know - there's no guarantee that evt.impacted is the thing you were
     490        // aiming at. This function gets called when the projectile hits *anything*.
     491        // For example:
     492       
     493        if( evt.impacted.player == evt.originator.player )
     494                console.write( "Friendly fire!" );
     495               
     496        // The three properties of the ProjectileImpact event are:
     497        // - impacted, the thing it hit
     498        // - originator, the thing that fired it (the fifth parameter of Projectile's
     499        //   constructor) - may be null
     500        // - position, the position the arrow was in the world when it hit.
     501       
     502        // The properties of the ProjectileMiss event (the one that gets sent to the
     503        // handler that was the seventh parameter of the constructor) are similar,
     504        // but it doesn't have 'impacted' - for obvious reasons.
     505}}}
     506}
     507
     508// ====================================================================
     509
     510function entity_event_gather( evt )
     511{
     512{{{
     513        if (this.actions.gather[evt.target.traits.supply.type][evt.target.traits.supply.subtype])
     514                gather_amt = parseInt( this.actions.gather[evt.target.traits.supply.type][evt.target.traits.supply.subtype].speed );
     515        else
     516                gather_amt = parseInt( this.actions.gather[evt.target.traits.supply.type].speed );
     517}}}
     518
     519{{{
     520        if( evt.target.traits.supply.max > 0 )
     521        {
     522            if( evt.target.traits.supply.curr <= gather_amt )
     523            {
     524                        gather_amt = evt.target.traits.supply.curr;
     525                        evt.target.kill();
     526            }
     527            evt.target.traits.supply.curr -= gather_amt;
     528            this.player.resource[evt.target.traits.supply.type.toString().toUpperCase()] += gather_amt;
     529        }
     530}}}
     531}
     532
     533// ====================================================================
     534
     535function entity_event_takesdamage( evt )
     536{
     537{{{
     538        // Apply armour and work out how much damage we actually take
     539        crushDamage = parseInt(evt.damage.crush - this.traits.armour.value * this.traits.armour.crush);
     540        if ( crushDamage < 0 ) crushDamage = 0;
     541        pierceDamage = parseInt(evt.damage.pierce - this.traits.armour.value * this.traits.armour.pierce);
     542        if ( pierceDamage < 0 ) pierceDamage = 0;
     543        hackDamage = parseInt(evt.damage.hack - this.traits.armour.value * this.traits.armour.hack);
     544        if ( hackDamage < 0 ) hackDamage = 0;
     545}}}
     546
     547{{{
     548        totalDamage = parseInt(evt.damage.typeless + crushDamage + pierceDamage + hackDamage);
     549}}}
     550
     551{{{
     552        // Minimum of 1 damage
     553}}}
     554
     555{{{
     556        if( totalDamage < 1 ) totalDamage = 1;
     557}}}
     558
     559{{{
     560        this.traits.health.curr -= totalDamage;
     561}}}
     562
     563{{{
     564        if( this.traits.health.curr <= 0 )
     565        {
     566                // If the inflictor gains promotions, and he's capable of earning more ranks,
     567                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 != "")
     568                {
     569                        // Give him the fallen's upgrade points (if he has any).
     570                        if (this.traits.loot.up)
     571                                evt.inflictor.traits.up.curr = parseInt(evt.inflictor.traits.up.curr) + parseInt(this.traits.loot.up);
     572                        // Notify player.
     573                        if (this.traits.id.specific)
     574                                console.write(this.traits.id.specific + " has earned " + this.traits.loot.up + " upgrade points!");
     575                        else
     576                                console.write("One of your units has earned " + this.traits.loot.up + " upgrade points!");
     577}}}
     578
     579{{{
     580                        // If he now has maximum upgrade points for his rank,
     581                        if (evt.inflictor.traits.up.curr >= evt.inflictor.traits.up.req)
     582                        {
     583                                // Notify the player.
     584                                if (this.traits.id.specific)
     585                                        console.write(this.traits.id.specific + " has gained a promotion!");
     586                                else
     587                                        console.write("One of your units has gained a promotion!");
     588                               
     589                                // Reset his upgrade points.
     590                                evt.inflictor.traits.up.curr = 0;
     591}}}
     592
     593{{{
     594                                // Upgrade his portrait to the next level.
     595                                evt.inflictor.traits.id.icon_cell++;
     596}}}
     597
     598{{{
     599                                // Transmogrify him into his next rank.
     600                                evt.inflictor.template = getEntityTemplate(evt.inflictor.traits.up.newentity);
     601                        }
     602                }
     603}}}
     604
     605{{{
     606                // If the fallen is worth any loot,
     607                if (this.traits.loot && (this.traits.loot.food || this.traits.loot.wood || this.traits.loot.stone || this.traits.loot.ore))
     608                {
     609                        // Give the inflictor his resources.
     610                        if (this.traits.loot.food)
     611                                getGUIGlobal().GiveResources("Food", parseInt(this.traits.loot.food));
     612                        if (this.traits.loot.wood)
     613                                getGUIGlobal().GiveResources("Wood", parseInt(this.traits.loot.wood));
     614                        if (this.traits.loot.stone)
     615                                getGUIGlobal().GiveResources("Stone", parseInt(this.traits.loot.stone));
     616                        if (this.traits.loot.ore)
     617                                getGUIGlobal().GiveResources("Ore", parseInt(this.traits.loot.ore));
     618                }
     619}}}
     620
     621{{{
     622                // Notify player.
     623                if( evt.inflictor )
     624                        console.write( this.traits.id.generic + " got the point of " + evt.inflictor.traits.id.generic + "'s weapon." );
     625                else
     626                        console.write( this.traits.id.generic + " died in mysterious circumstances." );
     627}}}
     628
     629{{{
     630                // Make him cry out in pain.
     631                curr_pain = getGUIGlobal().newRandomSound("voice", "pain", this.traits.audio.path);
     632                curr_pain.play();
     633}}}
     634
     635{{{
     636                // We've taken what we need. Kill the swine.
     637                this.kill();
     638        }
     639        else if( evt.inflictor && this.actions.attack )
     640        {
     641                // If we're not already doing something else, take a measured response - hit 'em back.
     642                // You know, I think this is quite possibly the first AI code the AI divlead has written
     643                // for 0 A.D....
     644                if( this.isIdle() )
     645                        this.order( ORDER_ATTACK, evt.inflictor );
     646        }
     647}}}
     648}
     649
     650function entity_event_targetchanged( evt )
     651{
     652{{{
     653        // This event lets us know when the user moves his/her cursor to a different unit (provided this
     654        // unit is selected) - use it to tell the engine what context cursor should be displayed, given
     655        // the target.
     656}}}
     657
     658{{{
     659        // Attack iff there's a target, it's our enemy, and we're armed. Otherwise, if we can gather, and
     660        // the target supplies, gather. If all else fails, move.
     661        // ToString is needed because every property is actually an object (though that's usually
     662        // hidden from you) and comparing an object to any other object in JavaScript (1.5, at least)
     663        // yields false. ToString converts them to their actual values (i.e. the four character
     664        // string) first.
     665       
     666        evt.defaultAction = NMT_Goto;
     667        evt.defaultCursor = "arrow-default";
     668        if( evt.target )
     669        {
     670            if( this.actions.attack &&
     671                        ( evt.target.player != gaiaPlayer ) &&
     672                        ( evt.target.player != this.player ) )
     673                {
     674                        evt.defaultAction = NMT_AttackMelee;
     675                        evt.defaultCursor = "action-attack";
     676                }
     677            if( this.actions.gather && evt.target.traits.supply &&
     678                        this.actions.gather[evt.target.traits.supply.type] &&
     679                        ( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) )
     680                {
     681                    evt.defaultAction = NMT_Gather;
     682                    // Set cursor (eg "action-gather-fruit").
     683                    evt.defaultCursor = "action-gather-" + evt.target.traits.supply.subtype;
     684                }
     685        }
     686}}}
     687}
     688
     689// ====================================================================
     690
     691function entity_event_prepareorder( evt )
     692{
     693{{{
     694        // This event gives us a chance to veto any order we're given before we execute it.
     695        // Not sure whether this really belongs here like this: the alternative is to override it in
     696        // subtypes - then you wouldn't need to check tags, you could hardcode results.
     697        switch( evt.orderType )
     698        {
     699        case ORDER_GOTO:
     700                if( !this.actions.move )
     701                        evt.preventDefault();
     702                break;
     703        case ORDER_PATROL:
     704                if( !this.actions.patrol )
     705                        evt.preventDefault();
     706                break; 
     707        case ORDER_ATTACK:
     708                if( !this.actions.attack ||
     709                    !evt.target )
     710                        evt.preventDefault();
     711                break;
     712        case ORDER_GATHER:
     713                if( !this.actions.gather ||
     714                    !( this.actions.gather[evt.target.traits.supply.type] ) ||
     715                    ( ( evt.target.traits.supply.curr == 0 ) && ( evt.target.traits.supply.max > 0 ) ) )
     716                        evt.preventDefault();
     717                break;
     718        default:
     719                evt.preventDefault();
     720        }
     721}}}
     722}
     723
     724// ====================================================================
     725
     726function entity_add_create_queue( template, list, tab )
     727{
     728{{{
     729        // Make sure we have a queue to put things in...
     730        if( !this.actions.create.queue )
     731                this.actions.create.queue      = new Array();
     732}}}
     733
     734{{{
     735        // Construct template object.
     736        comboTemplate = template;
     737        comboTemplate.list = list;
     738        comboTemplate.tab = tab;
     739}}}
     740
     741{{{
     742        // Append to the end of this queue
     743}}}
     744
     745{{{
     746        this.actions.create.queue.push( template );
     747}}}
     748
     749{{{
     750        // If we're not already building something...
     751        if( !this.actions.create.progress )
     752        {
     753                console.write( "Starting work on (unqueued) ", template.tag );
     754                 // Start the progress timer.
     755                 // - First parameter is target value (in this case, base build time in seconds)
     756                 // - Second parameter is increment per millisecond (use build rate modifier and correct for milliseconds)
     757                 // - Third parameter is the function to call when the timer finishes.
     758                 // - Fourth parameter is the scope under which to run that function (what the 'this' parameter should be)
     759                 this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
     760        }
     761}}}
     762}
     763
     764// ====================================================================
     765
     766// This is the syntax to add a function (or a property) to absolutely every entity.
     767
     768Entity.prototype.add_create_queue = entity_add_create_queue;
     769
     770// ====================================================================
     771
     772function entity_create_complete()
     773{
     774{{{
     775        // Get the unit that was at the head of our queue, and remove it.
     776        // (Oh, for information about all these nifty properties and functions
     777        //  of the Array object that I use, see the ECMA-262 documentation
     778        //  at http://www.mozilla.org/js/language/E262-3.pdf. Bit technical but
     779        //  the sections on 'Native ECMAScript Objects' are quite useful)
     780       
     781        var template = this.actions.create.queue.shift();
     782}}}
     783
     784{{{
     785        // Code to find a free space around an object is tedious and slow, so
     786        // I wrote it in C. Takes the template object so it can determine how
     787        // much space it needs to leave.
     788        position = this.getSpawnPoint( template );
     789       
     790        // The above function returns null if it couldn't find a large enough space.
     791        if( !position )
     792        {
     793                console.write( "Couldn't train unit - not enough space" );
     794                // Oh well. The player's just lost all the resources and time they put into
     795                // construction - serves them right for not paying attention to the land
     796                // around their barracks, doesn't it?
     797        }
     798        else
     799        {
     800                created = new Entity( template, position );
     801       
     802                // Above shouldn't ever fail, but just in case...
     803                if( created )
     804                {
     805                        console.write( "Created: ", template.tag );
     806       
     807                        // Entities start under Gaia control - make the controller
     808                        // the same as our controller
     809                        created.player = this.player;
     810                }
     811        }               
     812        // If there's something else in the build queue...
     813        if( this.actions.create.queue.length > 0 )
     814        {
     815                // Start on the next item.
     816                template = this.actions.create.queue[0];
     817                console.write( "Starting work on (queued) ", template.tag );
     818                this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
     819        }
     820        else
     821        {
     822                // Otherwise, delete the timer.
     823                this.actions.create.progress = null;
     824        }
     825}}}
     826}
     827
     828// ====================================================================
     829
     830function attempt_add_to_build_queue( entity, create_tag, list, tab )
     831{
     832{{{
     833        result = entity_CheckQueueReq (entity);
     834}}}
     835
     836{{{
     837        if (result == "true") // If the entry meets requirements to be added to the queue (eg sufficient resources)
     838        {
     839                // Cycle through all costs of this entry.
     840                pool = entity.traits.creation.resource;
     841                for( resource in pool )
     842                {
     843                        switch( resource.toString().toUpperCase() )
     844                        {
     845                                case "POPULATION" || "HOUSING":
     846                                break;
     847                                default:
     848                                        // Deduct the given quantity of resources.
     849                                        localPlayer.resource[resource.toString().toUpperCase()] -= pool[resource].cost;
     850}}}
     851
     852{{{
     853                                        console.write("Spent " + pool[resource].cost + " " + resource + ".");
     854                                break;
     855                        }
     856                }
     857}}}
     858
     859{{{
     860                // Add entity to queue.
     861                console.write( "Adding ", create_tag, " to build queue..." );
     862                entity.add_create_queue( getEntityTemplate( create_tag ), list, tab );
     863        }
     864        else            // If not, output the error message.
     865                console.write(result);
     866}}}
     867}
     868
     869// ====================================================================
     870
     871function entity_CheckQueueReq (entry)
     872{
     873{{{
     874        // Determines if the given entity meets requirements for production by the player, and returns an appropriate
     875        // error string.
     876        // A return value of 0 equals success -- entry meets requirements for production.
     877       
     878        // Cycle through all resources that this item costs, and check the player can afford the cost.
     879        resources = entry.traits.creation.resource;
     880        for( resource in resources )
     881        {
     882                resourceU = resource.toString().toUpperCase();
     883               
     884                switch( resourceU )
     885                {
     886                        case "POPULATION":
     887                                // If the item costs more of this resource type than we have,
     888                                if (resources[resource].cost > (localPlayer.resource["HOUSING"]-localPlayer.resource[resourceU]))
     889                                {
     890                                        // Return an error.
     891                                        return ("Insufficient Housing; " + (resources[resource].cost-localPlayer.resource["HOUSING"]-localPlayer.resource.valueOf()[resourceU].toString()) + " required.");
     892                                }
     893                        break;
     894                        case "HOUSING": // Ignore housing. It's handled in combination with population.
     895                        break
     896                        default:
     897                                // If the item costs more of this resource type than we have,
     898                                if (resources[resource].cost > localPlayer.resource[resourceU])
     899                                {
     900                                        // Return an error.
     901                                        return ("Insufficient " + resource + "; " + (localPlayer.resource[resourceU]-resources[resource].cost)*-1 + " required.");
     902                                }
     903                                else
     904                                        console.write("Player has at least " + resources[resource].cost + " " + resource + ".");
     905                        break;
     906                }
     907        }
     908}}}
     909
     910{{{
     911        // Check if another entity must first exist.
     912}}}
     913
     914{{{
     915        // Check if another tech must first be researched.
     916}}}
     917
     918{{{
     919        // Check if the limit for this type of entity has been reached.
     920}}}
     921
     922{{{
     923        // If we passed all checks, return success. Entity can be queued.
     924        return "true";
     925}}}
     926}
     927
     928// ====================================================================