| 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 | |
| 23 | localPlayer.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 | |
| 29 | selection.length // Number of units in current selection; if (selection.length) returns true if units are currently selected. |
| 30 | |
| 31 | selection[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. |
| 54 | evt.target.kill(); |
| 55 | |
| 56 | // Get a function that's part of the GUI scope. |
| 57 | getGUIGlobal().!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 | |
| 153 | function 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 | |
| 170 | function 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 | |
| 187 | function startSelection(handler) |
| 188 | { |
| 189 | {{{ |
| 190 | gameView.startCustomSelection(); |
| 191 | getGlobal().selectionWorldClickHandler=handler; |
| 192 | console.write("isSelecting(): "+isSelecting()); |
| 193 | }}} |
| 194 | } |
| 195 | |
| 196 | function endSelection() |
| 197 | { |
| 198 | {{{ |
| 199 | if (!isSelecting()) |
| 200 | return; |
| 201 | |
| 202 | gameView.endCustomSelection(); |
| 203 | getGlobal().selectionWorldClickHandler = null; |
| 204 | }}} |
| 205 | } |
| 206 | |
| 207 | function isSelecting() |
| 208 | { |
| 209 | {{{ |
| 210 | return getGlobal().selectionWorldClickHandler != null; |
| 211 | }}} |
| 212 | } |
| 213 | |
| 214 | // The world-click handler - called whenever the user clicks the terrain |
| 215 | function 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 | |
| 281 | addGlobalHandler("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. |
| 318 | var selectionChanged = false; |
| 319 | |
| 320 | // This boolean determines what the template of the selected object |
| 321 | // was when last we looked |
| 322 | var selectionTemplate = null; |
| 323 | |
| 324 | // This array holds the name of all properties that need to be updated |
| 325 | var selectionPropertiesChanged = new Array(); |
| 326 | |
| 327 | // This array holds a list of all the objects we hold property-change |
| 328 | // watches on |
| 329 | var propertyWatches = new Array(); |
| 330 | |
| 331 | // This function resets all the update variables, above |
| 332 | function 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. |
| 374 | function 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 |
| 391 | function selectionChangedHandler() |
| 392 | { |
| 393 | {{{ |
| 394 | selectionChanged = true; |
| 395 | }}} |
| 396 | } |
| 397 | |
| 398 | // Register it. |
| 399 | addGlobalHandler( "selectionChanged", selectionChangedHandler ); |
| 400 | |
| 401 | // This function is a handler for a watch event; it updates the |
| 402 | // selectionPropertiesChanged array |
| 403 | function 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 | |
| 414 | function 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 | |
| 433 | function 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 | |
| 483 | function 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 | |
| 510 | function 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 | |
| 535 | function 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 | |
| 650 | function 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 | |
| 691 | function 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 | |
| 726 | function 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 | |
| 768 | Entity.prototype.add_create_queue = entity_add_create_queue; |
| 769 | |
| 770 | // ==================================================================== |
| 771 | |
| 772 | function 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 | |
| 830 | function 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 | |
| 871 | function 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 | // ==================================================================== |