1
0
forked from 0ad/0ad

Script integration work - valueOf() is no longer required most of the time; also property-change watches and enumeration over entities (but not yet other native objects), rudimentary beginnings of a global events system, and adjusted the status orb to update only on changes.

This was SVN commit r2157.
This commit is contained in:
MarkT 2005-04-22 07:12:55 +00:00
parent 498d58ee61
commit bcabe3aa53
38 changed files with 942 additions and 959 deletions

View File

@ -22,13 +22,13 @@ function entity_event_gather( evt )
{ {
if( evt.target.traits.supply.curr <= gather_amt ) if( evt.target.traits.supply.curr <= gather_amt )
{ {
gather_amt = evt.target.traits.supply.curr; gather_amt = evt.target.traits.supply.curr;
evt.target.kill(); evt.target.kill();
} }
console.write( evt.target.traits.supply.type); console.write( evt.target.traits.supply.type);
console.write( evt.target.traits.supply.type.toString().toUpperCase() ); console.write( evt.target.traits.supply.type.toString().toUpperCase() );
evt.target.traits.supply.curr -= gather_amt; evt.target.traits.supply.curr -= gather_amt;
this.player.resource.valueOf()[evt.target.traits.supply.type.toString().toUpperCase()] += gather_amt; this.player.resource[evt.target.traits.supply.type.toString().toUpperCase()] += gather_amt;
} }
} }
@ -130,19 +130,31 @@ function entity_event_targetchanged( evt )
// hidden from you) and comparing an object to any other object in JavaScript (1.5, at least) // 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 // yields false. ToString converts them to their actual values (i.e. the four character
// string) first. // string) first.
evt.defaultAction = ORDER_GOTO; evt.defaultAction = ORDER_GOTO;
evt.defaultCursor = "arrow-default";
if( evt.target ) if( evt.target )
{ {
if( this.actions.attack && if( this.actions.attack &&
( evt.target.traits.id.civ_code != "gaia" ) && ( evt.target.player != gaiaPlayer ) &&
( evt.target.traits.id.civ_code.toString() != this.traits.id.civ_code.toString() ) ) ( evt.target.player != this.player ) )
evt.defaultAction = ORDER_ATTACK; {
evt.defaultAction = ORDER_ATTACK;
evt.defaultCursor = "action-attack";
}
if( this.actions.gather && evt.target.traits.supply && if( this.actions.gather && evt.target.traits.supply &&
this.actions.gather[evt.target.traits.supply.type] && this.actions.gather[evt.target.traits.supply.type] &&
( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) ) ( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) )
{
evt.defaultAction = ORDER_GATHER; evt.defaultAction = ORDER_GATHER;
} if( evt.target.traits.supply.type == "wood" )
{
evt.defaultCursor = "action-chop";
}
else
evt.defaultCursor = "action-mine";
}
}
} }
function entity_event_prepareorder( evt ) function entity_event_prepareorder( evt )
@ -161,11 +173,8 @@ function entity_event_prepareorder( evt )
evt.preventDefault(); evt.preventDefault();
break; break;
case ORDER_ATTACK: case ORDER_ATTACK:
// If we can't attack, we're not targeting a unit, or that unit is the same civ as us.
// (Should of course be same /player/ as us - not ready yet.)
if( !this.actions.attack || if( !this.actions.attack ||
!evt.target || !evt.target )
( evt.target.traits.id.civ_code.toString() == this.traits.id.civ_code.toString() ) )
evt.preventDefault(); evt.preventDefault();
break; break;
case ORDER_GATHER: case ORDER_GATHER:
@ -191,10 +200,11 @@ function entity_add_create_queue( template, list, tab )
comboTemplate.tab = tab; comboTemplate.tab = tab;
// Append to the end of this queue // Append to the end of this queue
this.actions.create.queue.valueOf().push( comboTemplate );
this.actions.create.queue.push( template );
// If we're not already building something... // If we're not already building something...
if( !this.actions.create.progress || !this.actions.create.progress.valueOf() ) if( !this.actions.create.progress )
{ {
console.write( "Starting work on (unqueued) ", template.tag ); console.write( "Starting work on (unqueued) ", template.tag );
// Start the progress timer. // Start the progress timer.
@ -218,7 +228,7 @@ function entity_create_complete()
// at http://www.mozilla.org/js/language/E262-3.pdf. Bit technical but // at http://www.mozilla.org/js/language/E262-3.pdf. Bit technical but
// the sections on 'Native ECMAScript Objects' are quite useful) // the sections on 'Native ECMAScript Objects' are quite useful)
var template = this.actions.create.queue.valueOf().shift(); var template = this.actions.create.queue.shift();
// Code to find a free space around an object is tedious and slow, so // 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 // I wrote it in C. Takes the template object so it can determine how
@ -232,26 +242,26 @@ function entity_create_complete()
// Oh well. The player's just lost all the resources and time they put into // 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 // construction - serves them right for not paying attention to the land
// around their barracks, doesn't it? // around their barracks, doesn't it?
return;
} }
else
{
created = new Entity( template, position );
created = new Entity( template, position ); // Above shouldn't ever fail, but just in case...
if( created )
// Above shouldn't ever fail, but just in case... {
if( !created ) console.write( "Created: ", template.tag );
return;
console.write( "Created: ", template.tag );
// Entities start under Gaia control - make the controller
// the same as our controller
created.player = this.player;
// 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 there's something else in the build queue...
if( this.actions.create.queue.valueOf().length > 0 ) if( this.actions.create.queue.length > 0 )
{ {
// Start on the next item. // Start on the next item.
template = this.actions.create.queue.valueOf()[0]; template = this.actions.create.queue[0];
console.write( "Starting work on (queued) ", template.tag ); 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 ) this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
} }

View File

@ -98,7 +98,7 @@ function setupSession()
CreateResources(); CreateResources();
// Start refreshing the session controls. // Start refreshing the session controls.
setInterval( getObjectInfo, 1, 1000 ); setInterval( getObjectInfo, 1, 100 );
} }
// ==================================================================== // ====================================================================

View File

@ -463,31 +463,41 @@ function PressCommandButton(GUIObject, list, tab)
function UpdateCommandButtons() function UpdateCommandButtons()
{ {
// Update train/research/build lists. if( shouldUpdateStat( "actions.create.list" ) )
listCounter = 1;
unitArray = UpdateList(action_tab_train, listCounter); if (unitArray != 0) listCounter++;
structcivArray = UpdateList(action_tab_buildciv, listCounter); if (structcivArray != 0) listCounter++;
structmilArray = UpdateList(action_tab_buildmil, listCounter); if (structmilArray != 0) listCounter++;
techArray = UpdateList(action_tab_research, listCounter); if (techArray != 0) listCounter++;
formationArray = UpdateList(action_tab_formation, listCounter); if (formationArray != 0) listCounter++;
stanceArray = UpdateList(action_tab_stance, listCounter); if (stanceArray != 0) listCounter++;
// Update commands.
commandCounter = SN_STATUS_PANE_COMMAND.tab.max;
commandCounter = UpdateCommand(action_attack, commandCounter);
commandCounter = UpdateCommand(action_patrol, commandCounter);
commandCounter = UpdateCommand(action_repair, commandCounter);
commandCounter = UpdateCommand(action_gather_food, commandCounter);
commandCounter = UpdateCommand(action_gather_wood, commandCounter);
commandCounter = UpdateCommand(action_gather_stone, commandCounter);
commandCounter = UpdateCommand(action_gather_ore, commandCounter);
// Clear remaining buttons between them.
for (commandClearLoop = listCounter; commandClearLoop <= commandCounter; commandClearLoop++)
{ {
GUIObjectHide("SN_STATUS_PANE_COMMAND_" + commandClearLoop + "_1"); // Everything in this block is tied to properties in
// If this slot could possibly contain a list, hide that too. // actions.create.list, the above check should limit the
GUIObjectHide("SN_STATUS_PANE_COMMAND_" + commandClearLoop + "_GROUP"); // number of times this update is needlessly made.
// Update train/research/build lists.
listCounter = 1;
unitArray = UpdateList(action_tab_train, listCounter); if (unitArray != 0) listCounter++;
structcivArray = UpdateList(action_tab_buildciv, listCounter); if (structcivArray != 0) listCounter++;
structmilArray = UpdateList(action_tab_buildmil, listCounter); if (structmilArray != 0) listCounter++;
techArray = UpdateList(action_tab_research, listCounter); if (techArray != 0) listCounter++;
formationArray = UpdateList(action_tab_formation, listCounter); if (formationArray != 0) listCounter++;
stanceArray = UpdateList(action_tab_stance, listCounter); if (stanceArray != 0) listCounter++;
}
if( shouldUpdateStat( "actions" ) )
{
// Update commands.
commandCounter = SN_STATUS_PANE_COMMAND.tab.max;
commandCounter = UpdateCommand(action_attack, commandCounter);
commandCounter = UpdateCommand(action_patrol, commandCounter);
commandCounter = UpdateCommand(action_repair, commandCounter);
commandCounter = UpdateCommand(action_gather_food, commandCounter);
commandCounter = UpdateCommand(action_gather_wood, commandCounter);
commandCounter = UpdateCommand(action_gather_stone, commandCounter);
commandCounter = UpdateCommand(action_gather_ore, commandCounter);
// Clear remaining buttons between them.
for (commandClearLoop = listCounter; commandClearLoop <= commandCounter; commandClearLoop++)
{
GUIObjectHide("SN_STATUS_PANE_COMMAND_" + commandClearLoop + "_1");
// If this slot could possibly contain a list, hide that too.
GUIObjectHide("SN_STATUS_PANE_COMMAND_" + commandClearLoop + "_GROUP");
}
} }
// Update production queue. // Update production queue.
@ -521,107 +531,224 @@ function UpdateCommandButtons()
// ==================================================================== // ====================================================================
// 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 UpdateStatusOrb() function UpdateStatusOrb()
{ {
// Update heading. // Update heading.
GUIObject = getGUIObjectByName("SN_STATUS_PANE_HEADING"); if( shouldUpdateStat( "player" ) || shouldUpdateStat( "traits.id.civ" ) )
GUIObject.caption = selection[0].player.name; // Player name (placeholder; replace with proper callsign). {
if (selection[0].traits.id.civ) GUIObject = getGUIObjectByName("SN_STATUS_PANE_HEADING");
GUIObject.caption += " [icon=bullet_icon] " + selection[0].traits.id.civ; GUIObject.caption = selection[0].player.name; // Player name (placeholder; replace with proper callsign).
if (selection[0].traits.id.civ)
GUIObject.caption += " [icon=bullet_icon] " + selection[0].traits.id.civ;
}
// Update name text. // Update name text.
GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1"); if( shouldUpdateStat( "traits.id" ) )
GUIObject.caption = "";
// Personal name.
if (selection[0].traits.id.personal && selection[0].traits.id.personal != "")
{
GUIObject.caption += selection[0].traits.id.personal + "\n";
}
// Generic name.
if (selection[0].traits.id.generic)
{
GUIObject.caption += selection[0].traits.id.generic + "\n";
}
// Specific/ranked name.
if (selection[0].traits.id.ranked)
{ {
GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1"); GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1");
GUIObject.caption += selection[0].traits.id.ranked + "\n"; GUIObject.caption = "";
}
else{ // Personal name.
if (selection[0].traits.id.specific) if (selection[0].traits.id.personal && selection[0].traits.id.personal != "")
GUIObject.caption += selection[0].traits.id.personal + "\n";
// Generic name.
if (selection[0].traits.id.generic)
GUIObject.caption += selection[0].traits.id.generic + "\n";
// Specific/ranked name.
if (selection[0].traits.id.ranked)
{ {
GUIObject.caption += selection[0].traits.id.specific + "\n"; GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1");
GUIObject.caption += selection[0].traits.id.ranked + "\n";
}
else{
if (selection[0].traits.id.specific)
{
GUIObject.caption += selection[0].traits.id.specific + "\n";
}
} }
} }
if( shouldUpdateStat( "traits.id.icon" ) )
// Update portrait
if (selection[0].traits.id.icon)
setPortrait("SN_STATUS_PANE_PORTRAIT", selection[0].traits.id.icon, selection[0].traits.id.civ_code, selection[0].traits.id.icon_cell);
// Update rank.
GUIObject = getGUIObjectByName("SN_STATUS_PANE_ICON_RANK");
if (selection[0].traits.up.rank > 1)
{ {
GUIObject.sprite = "ui_icon_sheet_statistic"; // Update portrait
GUIObject.cell_id = stat_rank1 + (selection[0].traits.up.rank-2); if (selection[0].traits.id.icon)
setPortrait("SN_STATUS_PANE_PORTRAIT", selection[0].traits.id.icon, selection[0].traits.id.civ_code, selection[0].traits.id.icon_cell);
} }
else if( shouldUpdateStat( "traits.up.rank" ) )
GUIObject.sprite = "";
// Update hitpoints
if (selection[0].traits.health.curr && selection[0].traits.health.max)
{ {
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").caption = Math.round(selection[0].traits.health.curr) + "/" + Math.round(selection[0].traits.health.max); // Update rank.
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").hidden = false; GUIObject = getGUIObjectByName("SN_STATUS_PANE_ICON_RANK");
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").caption = ((Math.round(selection[0].traits.health.curr) * 100 ) / Math.round(selection[0].traits.health.max)); if (selection[0].traits.up.rank > 1)
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").hidden = false;
}
else
{
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").hidden = true;
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").hidden = true;
}
// Update upgrade points
if (selection[0].traits.up && selection[0].traits.up.curr && selection[0].traits.up.req)
{
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").caption = Math.round(selection[0].traits.up.curr) + "/" + Math.round(selection[0].traits.up.req);
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").hidden = false;
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").caption = ((Math.round(selection[0].traits.up.curr) * 100 ) / Math.round(selection[0].traits.up.req));
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").hidden = false;
}
else
{
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").hidden = true;
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").hidden = true;
}
// Update Supply/Garrison
GUIObject = getGUIObjectByName("SN_STATUS_PANE_2STAT");
GUIObject.caption = '';
if (selection[0].traits.garrison)
{
if (selection[0].traits.garrison.curr && selection[0].traits.garrison.max)
{ {
GUIObject.caption += '[icon="icon_statistic_garrison"] [color="100 100 255"]' + selection[0].traits.garrison.curr + '/' + selection[0].traits.garrison.max + '[/color] '; GUIObject.sprite = "ui_icon_sheet_statistic";
GUIObject.cell_id = stat_rank1 + (selection[0].traits.up.rank-2);
}
else
GUIObject.sprite = "";
}
if( shouldUpdateStat( "traits.health" ) )
{
// Update hitpoints
if (selection[0].traits.health.curr && selection[0].traits.health.max)
{
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").caption = Math.round(selection[0].traits.health.curr) + "/" + Math.round(selection[0].traits.health.max);
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").hidden = false;
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").caption = ((Math.round(selection[0].traits.health.curr) * 100 ) / Math.round(selection[0].traits.health.max));
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").hidden = false;
}
else
{
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_TEXT").hidden = true;
getGUIObjectByName("SN_STATUS_PANE_ICON_HP_BAR").hidden = true;
} }
} }
if (selection[0].traits.supply) if( shouldUpdateStat( "traits.up" ) )
{ {
if (selection[0].traits.supply.curr && selection[0].traits.supply.max && selection[0].traits.supply.type) // Update upgrade points
if (selection[0].traits.up && selection[0].traits.up.curr && selection[0].traits.up.req)
{ {
// Special case for infinity. getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").caption = Math.round(selection[0].traits.up.curr) + "/" + Math.round(selection[0].traits.up.req);
if (selection[0].traits.supply.curr == "0" && selection[0].traits.supply.max == "0") getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").hidden = false;
GUIObject.caption += '[icon="icon_resource_' + selection[0].traits.supply.type + '"] [color="100 100 255"] [icon="infinity_icon"] [/color] '; getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").caption = ((Math.round(selection[0].traits.up.curr) * 100 ) / Math.round(selection[0].traits.up.req));
else getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").hidden = false;
GUIObject.caption += '[icon="icon_resource_' + selection[0].traits.supply.type + '"] [color="100 100 255"]' + selection[0].traits.supply.curr + '/' + selection[0].traits.supply.max + '[/color] '; }
else
{
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_TEXT").hidden = true;
getGUIObjectByName("SN_STATUS_PANE_ICON_XP_BAR").hidden = true;
} }
} }
if( shouldUpdateStat( "traits.garrison" ) )
{
// Update Supply/Garrison
GUIObject = getGUIObjectByName("SN_STATUS_PANE_2STAT");
GUIObject.caption = '';
if (selection[0].traits.garrison)
{
if (selection[0].traits.garrison.curr && selection[0].traits.garrison.max)
{
GUIObject.caption += '[icon="icon_statistic_garrison"] [color="100 100 255"]' + selection[0].traits.garrison.curr + '/' + selection[0].traits.garrison.max + '[/color] ';
}
}
}
if( shouldUpdateStat( "traits.supply" ) )
{
GUIObject = getGUIObjectByName("SN_STATUS_PANE_2STAT");
GUIObject.caption = '';
if (selection[0].traits.supply)
{
if (selection[0].traits.supply.curr && selection[0].traits.supply.max && selection[0].traits.supply.type)
{
// Special case for infinity.
if (selection[0].traits.supply.curr == "0" && selection[0].traits.supply.max == "0")
GUIObject.caption += '[icon="icon_resource_' + selection[0].traits.supply.type + '"] [color="100 100 255"] [icon="infinity_icon"] [/color] ';
else
GUIObject.caption += '[icon="icon_resource_' + selection[0].traits.supply.type + '"] [color="100 100 255"]' + selection[0].traits.supply.curr + '/' + selection[0].traits.supply.max + '[/color] ';
}
}
}
// Update Attack stats // Update Attack stats
if( shouldUpdateStat( "actions.attack" ) )
{
if (selection[0].actions.attack && selection[0].actions.attack.damage && selection[0].actions.attack.damage > 0) if (selection[0].actions.attack && selection[0].actions.attack.damage && selection[0].actions.attack.damage > 0)
getGUIObjectByName("SN_STATUS_PANE_STAT1").caption = '[icon="icon_statistic_attack"]' + selection[0].actions.attack.damage; getGUIObjectByName("SN_STATUS_PANE_STAT1").caption = '[icon="icon_statistic_attack"]' + selection[0].actions.attack.damage;
else else
@ -651,8 +778,10 @@ function UpdateStatusOrb()
getGUIObjectByName("SN_STATUS_PANE_STAT6").caption = '[icon="icon_statistic_accuracy"]' + Math.round(selection[0].actions.attack.accuracy*100) + '%'; getGUIObjectByName("SN_STATUS_PANE_STAT6").caption = '[icon="icon_statistic_accuracy"]' + Math.round(selection[0].actions.attack.accuracy*100) + '%';
else else
getGUIObjectByName("SN_STATUS_PANE_STAT6").caption = ""; getGUIObjectByName("SN_STATUS_PANE_STAT6").caption = "";
}
// Update Armour & Other stats // Update Armour & Other stats
if( shouldUpdateStat( "traits.armour" ) )
{
if (selection[0].traits.armour && selection[0].traits.armour.value && selection[0].traits.armour.value > 0) if (selection[0].traits.armour && selection[0].traits.armour.value && selection[0].traits.armour.value > 0)
getGUIObjectByName("SN_STATUS_PANE_STAT7").caption = '[icon="icon_statistic_armour"]' + selection[0].traits.armour.value; getGUIObjectByName("SN_STATUS_PANE_STAT7").caption = '[icon="icon_statistic_armour"]' + selection[0].traits.armour.value;
else getGUIObjectByName("SN_STATUS_PANE_STAT7").caption = ""; else getGUIObjectByName("SN_STATUS_PANE_STAT7").caption = "";
@ -665,18 +794,26 @@ function UpdateStatusOrb()
if (selection[0].traits.armour && selection[0].traits.armour.crush && selection[0].traits.armour.crush > 0) if (selection[0].traits.armour && selection[0].traits.armour.crush && selection[0].traits.armour.crush > 0)
getGUIObjectByName("SN_STATUS_PANE_STAT10").caption = '[icon="icon_statistic_crush"]' + Math.round(selection[0].traits.armour.crush*100) + '%'; getGUIObjectByName("SN_STATUS_PANE_STAT10").caption = '[icon="icon_statistic_crush"]' + Math.round(selection[0].traits.armour.crush*100) + '%';
else getGUIObjectByName("SN_STATUS_PANE_STAT10").caption = ""; else getGUIObjectByName("SN_STATUS_PANE_STAT10").caption = "";
}
if (selection[0].actions.move && selection[0].actions.move.speed) if( shouldUpdateStat( "actions.move" ) )
getGUIObjectByName("SN_STATUS_PANE_STAT11").caption = '[icon="icon_statistic_speed"]' + selection[0].actions.move.speed; {
else getGUIObjectByName("SN_STATUS_PANE_STAT11").caption = ""; if (selection[0].actions.move && selection[0].actions.move.speed)
if (selection[0].traits.vision && selection[0].traits.vision.los) getGUIObjectByName("SN_STATUS_PANE_STAT11").caption = '[icon="icon_statistic_speed"]' + selection[0].actions.move.speed;
getGUIObjectByName("SN_STATUS_PANE_STAT12").caption = '[icon="icon_statistic_los"]' + selection[0].traits.vision.los; else getGUIObjectByName("SN_STATUS_PANE_STAT11").caption = "";
else getGUIObjectByName("SN_STATUS_PANE_STAT12").caption = ""; }
if( shouldUpdateStat( "traits.vision" ) )
{
if (selection[0].traits.vision && selection[0].traits.vision.los)
getGUIObjectByName("SN_STATUS_PANE_STAT12").caption = '[icon="icon_statistic_los"]' + selection[0].traits.vision.los;
else getGUIObjectByName("SN_STATUS_PANE_STAT12").caption = "";
}
// Reveal Status Orb // Reveal Status Orb
getGUIObjectByName("session_status_orb").hidden = false; getGUIObjectByName("session_status_orb").hidden = false;
// Update Command Buttons. // Update Command Buttons.
UpdateCommandButtons(); UpdateCommandButtons();
resetUpdateVars();
} }

View File

@ -199,7 +199,7 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a // BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model // animation specific to this model
CSkeletonAnim* CModel::BuildAnimation(const char* filename,float speed) CSkeletonAnim* CModel::BuildAnimation(const char* filename,float speed,size_t actionpos)
{ {
CSkeletonAnimDef* def=g_SkelAnimMan.GetAnimation(filename); CSkeletonAnimDef* def=g_SkelAnimMan.GetAnimation(filename);
if (!def) return 0; if (!def) return 0;
@ -207,6 +207,9 @@ CSkeletonAnim* CModel::BuildAnimation(const char* filename,float speed)
CSkeletonAnim* anim=new CSkeletonAnim; CSkeletonAnim* anim=new CSkeletonAnim;
anim->m_AnimDef=def; anim->m_AnimDef=def;
anim->m_Speed=speed; anim->m_Speed=speed;
anim->m_ActionPos=actionpos;
if( actionpos > anim->m_AnimDef->GetDuration() )
anim->m_ActionPos = anim->m_AnimDef->GetDuration();
anim->m_ObjectBounds.SetEmpty(); anim->m_ObjectBounds.SetEmpty();
InvalidateBounds(); InvalidateBounds();

View File

@ -102,7 +102,7 @@ public:
// load raw animation frame animation from given file, and build a // load raw animation frame animation from given file, and build a
// animation specific to this model // animation specific to this model
CSkeletonAnim* BuildAnimation(const char* filename,float speed); CSkeletonAnim* BuildAnimation(const char* filename,float speed,size_t actionpos);
// add a prop to the model on the given point // add a prop to the model on the given point
void AddProp(SPropPoint* point,CModel* model); void AddProp(SPropPoint* point,CModel* model);

View File

@ -159,6 +159,7 @@ bool CObjectBase::Load(const char* filename)
AT(file); AT(file);
AT(name); AT(name);
AT(speed); AT(speed);
AT(actionpos);
AT(attachpoint); AT(attachpoint);
AT(actor); AT(actor);
AT(frequency); AT(frequency);
@ -220,6 +221,11 @@ bool CObjectBase::Load(const char* filename)
anim.m_Speed = CStr(ae.Value).ToInt() / 100.f; anim.m_Speed = CStr(ae.Value).ToInt() / 100.f;
if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f; if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f;
} }
else if (ae.Name == at_actionpos)
{
anim.m_ActionPos = CStr(ae.Value).ToInt();
if (anim.m_ActionPos < 0) anim.m_ActionPos = 0;
}
else else
; // unrecognised element ; // unrecognised element
} }

View File

@ -13,7 +13,7 @@ public:
struct Anim { struct Anim {
// constructor // constructor
Anim() : m_Speed(1), m_AnimData(0) {} Anim() : m_Speed(1), m_ActionPos( 0 ), m_AnimData(0) {}
// name of the animation - "Idle", "Run", etc // name of the animation - "Idle", "Run", etc
CStr m_AnimName; CStr m_AnimName;
@ -21,6 +21,8 @@ public:
CStr m_FileName; CStr m_FileName;
// animation speed, as specified in XML actor file // animation speed, as specified in XML actor file
float m_Speed; float m_Speed;
// time during the animation at which the interesting bit happens (msec)
size_t m_ActionPos;
// the animation data, specific to the this model // the animation data, specific to the this model
CSkeletonAnim* m_AnimData; CSkeletonAnim* m_AnimData;
}; };

View File

@ -144,7 +144,7 @@ bool CObjectEntry::BuildRandomVariant(CObjectBase::variation_key& vars, CObjectB
if (m_Animations[t].m_FileName.Length() > 0) if (m_Animations[t].m_FileName.Length() > 0)
{ {
const char* animfilename = m_Animations[t].m_FileName; const char* animfilename = m_Animations[t].m_FileName;
m_Animations[t].m_AnimData = m_Model->BuildAnimation(animfilename, m_Animations[t].m_Speed); m_Animations[t].m_AnimData = m_Model->BuildAnimation(animfilename, m_Animations[t].m_Speed, m_Animations[t].m_ActionPos);
CStr AnimNameLC = m_Animations[t].m_AnimName.LowerCase(); CStr AnimNameLC = m_Animations[t].m_AnimName.LowerCase();

View File

@ -23,6 +23,8 @@ public:
CSkeletonAnimDef* m_AnimDef; CSkeletonAnimDef* m_AnimDef;
// speed at which this animation runs // speed at which this animation runs
float m_Speed; float m_Speed;
// time during the animation at which the interesting bit happens (msec)
size_t m_ActionPos;
// object space bounds of the model when this animation is applied to it // object space bounds of the model when this animation is applied to it
CBound m_ObjectBounds; CBound m_ObjectBounds;
}; };

View File

@ -58,6 +58,7 @@
#include "StringConvert.h" #include "StringConvert.h"
#include "scripting/ScriptingHost.h" #include "scripting/ScriptingHost.h"
#include "scripting/GameEvents.h"
#include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_Entity.h"
#include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_BaseEntity.h"
#include "scripting/JSInterface_Vector3D.h" #include "scripting/JSInterface_Vector3D.h"
@ -789,7 +790,7 @@ TIMER(InitScripting)
PlayerCollection::Init( "PlayerCollection" ); PlayerCollection::Init( "PlayerCollection" );
CDamageType::ScriptingInit(); CDamageType::ScriptingInit();
CJSPropertyAccessor<CEntity>::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor<T> is already being compiled for T = CEntity. CJSComplexPropertyAccessor<CEntity>::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor<T> is already being compiled for T = CEntity.
CScriptEvent::ScriptingInit(); CScriptEvent::ScriptingInit();
CJSProgressTimer::ScriptingInit(); CJSProgressTimer::ScriptingInit();
@ -801,6 +802,8 @@ TIMER(InitScripting)
JSI_Camera::init(); JSI_Camera::init();
JSI_Console::init(); JSI_Console::init();
new CGameEvents;
} }
@ -1069,6 +1072,7 @@ static void Shutdown()
// delete &g_EntityManager; // delete &g_EntityManager;
delete &g_GameAttributes; delete &g_GameAttributes;
delete &g_JSGameEvents;
delete &g_EntityTemplateCollection; delete &g_EntityTemplateCollection;

View File

@ -21,8 +21,8 @@ CPlayerSlot::CPlayerSlot(int slotID, CPlayer *pPlayer):
); );
//AddProperty(L"session", (GetFn)&CPlayerSlot::JSI_GetSession); //AddProperty(L"session", (GetFn)&CPlayerSlot::JSI_GetSession);
AddReadOnlyProperty(L"session", &m_pSession); AddLocalProperty(L"session", &m_pSession, true );
AddReadOnlyProperty(L"player", &m_pPlayer); AddLocalProperty(L"player", &m_pPlayer, true );
} }
CPlayerSlot::~CPlayerSlot() CPlayerSlot::~CPlayerSlot()
@ -34,7 +34,7 @@ void CPlayerSlot::ScriptingInit()
AddMethod<bool, &CPlayerSlot::JSI_AssignToSession>("assignToSession", 1); AddMethod<bool, &CPlayerSlot::JSI_AssignToSession>("assignToSession", 1);
AddMethod<bool, &CPlayerSlot::JSI_AssignLocal>("assignLocal", 0); AddMethod<bool, &CPlayerSlot::JSI_AssignLocal>("assignLocal", 0);
AddMethod<bool, &CPlayerSlot::JSI_AssignOpen>("assignOpen", 0); AddMethod<bool, &CPlayerSlot::JSI_AssignOpen>("assignOpen", 0);
AddClassProperty(L"assignment", (GetFn)&CPlayerSlot::JSI_GetAssignment); AddProperty(L"assignment", (GetFn)&CPlayerSlot::JSI_GetAssignment);
// AddMethod<bool, &CPlayerSlot::JSI_AssignAI>("assignAI", <num_args>); // AddMethod<bool, &CPlayerSlot::JSI_AssignAI>("assignAI", <num_args>);
CJSObject<CPlayerSlot>::ScriptingInit("PlayerSlot"); CJSObject<CPlayerSlot>::ScriptingInit("PlayerSlot");
@ -258,7 +258,7 @@ void CGameAttributes::ScriptingInit()
PlayerSlotArray_JS::Construct, 0, NULL, NULL, NULL, NULL); PlayerSlotArray_JS::Construct, 0, NULL, NULL, NULL, NULL);
AddMethod<jsval, &CGameAttributes::JSI_GetOpenSlot>("getOpenSlot", 0); AddMethod<jsval, &CGameAttributes::JSI_GetOpenSlot>("getOpenSlot", 0);
AddClassProperty(L"slots", (GetFn)&CGameAttributes::JSI_GetPlayerSlots); AddProperty(L"slots", (GetFn)&CGameAttributes::JSI_GetPlayerSlots);
CJSObject<CGameAttributes>::ScriptingInit("GameAttributes"); CJSObject<CGameAttributes>::ScriptingInit("GameAttributes");
} }

View File

@ -11,6 +11,7 @@
#include "BoundingObjects.h" #include "BoundingObjects.h"
#include "Unit.h" #include "Unit.h"
#include "Model.h" #include "Model.h"
#include "scripting/GameEvents.h"
extern CConsole* g_Console; extern CConsole* g_Console;
extern int g_mouse_x, g_mouse_y; extern int g_mouse_x, g_mouse_y;
@ -120,10 +121,12 @@ void CSelectedEntities::renderOverlays()
glDisable( GL_BLEND ); glDisable( GL_BLEND );
} }
/*
glLoadIdentity(); glLoadIdentity();
glTranslatef( (float)( g_mouse_x + 16 ), (float)( g_Renderer.GetHeight() - g_mouse_y - 8 ), 0.0f ); glTranslatef( (float)( g_mouse_x + 16 ), (float)( g_Renderer.GetHeight() - g_mouse_y - 8 ), 0.0f );
glScalef( 1.0f, -1.0f, 1.0f ); glScalef( 1.0f, -1.0f, 1.0f );
glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f );
switch( m_contextOrder ) switch( m_contextOrder )
{ {
case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_GOTO:
@ -139,6 +142,7 @@ void CSelectedEntities::renderOverlays()
glwprintf( L"Gather" ); glwprintf( L"Gather" );
break; break;
} }
*/
glDisable( GL_TEXTURE_2D ); glDisable( GL_TEXTURE_2D );
glPopMatrix(); glPopMatrix();
@ -350,6 +354,14 @@ CVector3D CSelectedEntities::getGroupPosition( i8 groupid )
void CSelectedEntities::update() void CSelectedEntities::update()
{ {
static std::vector<HEntity> lastSelection;
if( !( m_selected == lastSelection ) )
{
g_JSGameEvents.FireSelectionChanged( m_selectionChanged );
lastSelection = m_selected;
}
if( m_selectionChanged || g_Mouseover.m_targetChanged ) if( m_selectionChanged || g_Mouseover.m_targetChanged )
{ {
// Can't order anything off the map // Can't order anything off the map
@ -362,6 +374,8 @@ void CSelectedEntities::update()
// Quick count to see which is the modal default order. // Quick count to see which is the modal default order.
int defaultPoll[CEntityOrder::ORDER_LAST]; int defaultPoll[CEntityOrder::ORDER_LAST];
std::map<CStrW, int, CStrW_hash_compare> defaultCursor[CEntityOrder::ORDER_LAST];
int t, vote; int t, vote;
for( t = 0; t < CEntityOrder::ORDER_LAST; t++ ) for( t = 0; t < CEntityOrder::ORDER_LAST; t++ )
defaultPoll[t] = 0; defaultPoll[t] = 0;
@ -369,9 +383,15 @@ void CSelectedEntities::update()
std::vector<HEntity>::iterator it; std::vector<HEntity>::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ ) for( it = m_selected.begin(); it < m_selected.end(); it++ )
{ {
vote = (*it)->defaultOrder( g_Mouseover.m_target ); CEventTargetChanged evt( g_Mouseover.m_target );
(*it)->DispatchEvent( &evt );
vote = evt.m_defaultAction;
if( ( vote >= 0 ) && ( vote < CEntityOrder::ORDER_LAST ) ) if( ( vote >= 0 ) && ( vote < CEntityOrder::ORDER_LAST ) )
{
defaultPoll[vote]++; defaultPoll[vote]++;
defaultCursor[vote][evt.m_defaultCursor]++;
}
} }
vote = -1; vote = -1;
@ -381,16 +401,17 @@ void CSelectedEntities::update()
vote = t; vote = t;
} }
std::map<CStrW, int, CStrW_hash_compare>::iterator itv;
m_contextOrder = vote; m_contextOrder = vote;
switch( m_contextOrder )
{ // Now find the most appropriate cursor
case CEntityOrder::ORDER_ATTACK_MELEE: t = 0;
g_CursorName = "action-attack"; for( itv = defaultCursor[vote].begin(); itv != defaultCursor[vote].end(); itv++ )
break; if( itv->second > t )
default: {
g_CursorName = "arrow-default"; t = itv->second;
break; g_CursorName = itv->first;
} }
m_selectionChanged = false; m_selectionChanged = false;
g_Mouseover.m_targetChanged = false; g_Mouseover.m_targetChanged = false;

View File

@ -22,13 +22,13 @@ CNetClient::CServerSession::CServerSession(int sessionID, const CStrW &name):
m_Name(name) m_Name(name)
{ {
ONCE( ScriptingInit(); ); ONCE( ScriptingInit(); );
AddReadOnlyProperty(L"id", &m_SessionID);
AddReadOnlyProperty(L"name", &m_Name);
} }
void CNetClient::CServerSession::ScriptingInit() void CNetClient::CServerSession::ScriptingInit()
{ {
AddProperty(L"id", &CNetClient::CServerSession::m_SessionID, true);
AddProperty(L"name", &CNetClient::CServerSession::m_Name, true);
CJSObject<CServerSession>::ScriptingInit("NetClient_ServerSession"); CJSObject<CServerSession>::ScriptingInit("NetClient_ServerSession");
} }
@ -57,18 +57,18 @@ void CNetClient::ScriptingInit()
AddClassProperty(L"onStartGame", &CNetClient::m_OnStartGame); AddProperty(L"onStartGame", &CNetClient::m_OnStartGame);
AddClassProperty(L"onChat", &CNetClient::m_OnChat); AddProperty(L"onChat", &CNetClient::m_OnChat);
AddClassProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete); AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete);
AddClassProperty(L"onDisconnect", &CNetClient::m_OnDisconnect); AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect);
AddClassProperty(L"onClientConnect", &CNetClient::m_OnClientConnect); AddProperty(L"onClientConnect", &CNetClient::m_OnClientConnect);
AddClassProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect); AddProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect);
AddClassProperty(L"password", &CNetClient::m_Password); AddProperty(L"password", &CNetClient::m_Password);
AddClassProperty<CStrW>(L"playerName", &CNetClient::m_Name); AddProperty<CStrW>(L"playerName", &CNetClient::m_Name);
AddClassProperty(L"sessions", &CNetClient::m_JSI_ServerSessions); AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions);
CJSMap<SessionMap>::ScriptingInit("NetClient_SessionMap"); CJSMap<SessionMap>::ScriptingInit("NetClient_SessionMap");
CJSObject<CNetClient>::ScriptingInit("NetClient"); CJSObject<CNetClient>::ScriptingInit("NetClient");
} }

View File

@ -18,7 +18,7 @@ class CStartGameEvent: public CScriptEvent
{ {
public: public:
CStartGameEvent(): CStartGameEvent():
CScriptEvent(L"startGame", false, NET_JS_EVENT_START_GAME) CScriptEvent(L"startGame", NET_JS_EVENT_START_GAME, false)
{} {}
}; };
@ -29,12 +29,12 @@ class CChatEvent: public CScriptEvent
public: public:
CChatEvent(CStrW sender, CStrW message): CChatEvent(CStrW sender, CStrW message):
CScriptEvent(L"chat", false, NET_JS_EVENT_CHAT), CScriptEvent(L"chat", NET_JS_EVENT_CHAT, false ),
m_Sender(sender), m_Sender(sender),
m_Message(message) m_Message(message)
{ {
AddReadOnlyProperty(L"sender", &m_Sender); AddLocalProperty(L"sender", &m_Sender, true);
AddReadOnlyProperty(L"message", &m_Message); AddLocalProperty(L"message", &m_Message, true);
} }
}; };
@ -45,12 +45,12 @@ class CConnectCompleteEvent: public CScriptEvent
public: public:
CConnectCompleteEvent(CStrW message, bool success): CConnectCompleteEvent(CStrW message, bool success):
CScriptEvent(L"connectComplete", false, NET_JS_EVENT_CONNECT_COMPLETE), CScriptEvent(L"connectComplete", NET_JS_EVENT_CONNECT_COMPLETE, false),
m_Message(message), m_Message(message),
m_Success(success) m_Success(success)
{ {
AddReadOnlyProperty(L"message", &m_Message); AddLocalProperty(L"message", &m_Message, true);
AddReadOnlyProperty(L"success", &m_Success); AddLocalProperty(L"success", &m_Success, true);
} }
}; };
@ -60,10 +60,10 @@ class CDisconnectEvent: public CScriptEvent
public: public:
CDisconnectEvent(CStrW message): CDisconnectEvent(CStrW message):
CScriptEvent(L"disconnect", false, NET_JS_EVENT_DISCONNECT), CScriptEvent(L"disconnect", NET_JS_EVENT_DISCONNECT, false),
m_Message(message) m_Message(message)
{ {
AddReadOnlyProperty(L"message", &m_Message); AddLocalProperty(L"message", &m_Message, true);
} }
}; };
@ -76,15 +76,15 @@ class CClientConnectDisconnectCommon: public CScriptEvent
public: public:
CClientConnectDisconnectCommon(const wchar_t* eventName, int eventType, CClientConnectDisconnectCommon(const wchar_t* eventName, int eventType,
int sessionID, const CStrW &name, CNetServerSession *pSession): int sessionID, const CStrW &name, CNetServerSession *pSession):
CScriptEvent(L"clientConnect", false, NET_JS_EVENT_CLIENT_CONNECT), CScriptEvent(L"clientConnect", NET_JS_EVENT_CLIENT_CONNECT, false),
m_SessionID(sessionID), m_SessionID(sessionID),
m_Name(name), m_Name(name),
m_pSession(pSession) m_pSession(pSession)
{ {
AddReadOnlyProperty(L"id", &m_SessionID); AddLocalProperty(L"id", &m_SessionID, true);
AddReadOnlyProperty(L"name", &m_Name); AddLocalProperty(L"name", &m_Name, true);
if (m_pSession) if (m_pSession)
AddReadOnlyProperty(L"session", &m_pSession); AddLocalProperty(L"session", &m_pSession, true);
} }
}; };

View File

@ -60,18 +60,6 @@ CNetServer::CNetServer(CGame *pGame, CGameAttributes *pGameAttribs):
ScriptingInit(); ScriptingInit();
); );
AddProperty(L"sessions", &m_JSI_Sessions);
AddProperty(L"serverPlayerName", &m_ServerPlayerName);
AddProperty(L"serverName", &m_ServerName);
AddProperty(L"welcomeMessage", &m_WelcomeMessage);
AddProperty(L"port", &m_Port);
AddProperty(L"onChat", &m_OnChat);
AddProperty(L"onClientConnect", &m_OnClientConnect);
AddProperty(L"onClientDisconnect", &m_OnClientDisconnect);
m_pGameAttributes->SetUpdateCallback(AttributeUpdate, this); m_pGameAttributes->SetUpdateCallback(AttributeUpdate, this);
m_pGameAttributes->SetPlayerUpdateCallback(PlayerAttributeUpdate, this); m_pGameAttributes->SetPlayerUpdateCallback(PlayerAttributeUpdate, this);
m_pGameAttributes->SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCallback, this); m_pGameAttributes->SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCallback, this);
@ -102,6 +90,18 @@ void CNetServer::ScriptingInit()
AddMethod<bool, &CNetServer::JSI_Open>("open", 0); AddMethod<bool, &CNetServer::JSI_Open>("open", 0);
AddProperty(L"sessions", &CNetServer::m_JSI_Sessions);
AddProperty(L"serverPlayerName", &CNetServer::m_ServerPlayerName);
AddProperty(L"serverName", &CNetServer::m_ServerName);
AddProperty(L"welcomeMessage", &CNetServer::m_WelcomeMessage);
AddProperty(L"port", &CNetServer::m_Port);
AddProperty(L"onChat", &CNetServer::m_OnChat);
AddProperty(L"onClientConnect", &CNetServer::m_OnClientConnect);
AddProperty(L"onClientDisconnect", &CNetServer::m_OnClientDisconnect);
CJSObject<CNetServer>::ScriptingInit("NetServer"); CJSObject<CNetServer>::ScriptingInit("NetServer");
} }

View File

@ -19,9 +19,6 @@ CNetServerSession::CNetServerSession(CNetServer *pServer, CSocketInternal *pInt,
ONCE( ONCE(
ScriptingInit(); ScriptingInit();
); );
AddProperty(L"id", &m_ID);
AddProperty(L"name", &m_Name);
} }
CNetServerSession::~CNetServerSession() CNetServerSession::~CNetServerSession()
@ -209,6 +206,9 @@ void CNetServerSession::ScriptingInit()
AddMethod<bool, &CNetServerSession::JSI_Close>("close", 0); AddMethod<bool, &CNetServerSession::JSI_Close>("close", 0);
CJSObject<CNetServerSession>::ScriptingInit("NetSession"); CJSObject<CNetServerSession>::ScriptingInit("NetSession");
// Hope this doesn't break anything...
AddProperty( L"id", &CNetServerSession::m_ID );
AddProperty( L"name", (CStrW CNetServerSession::*)&CNetServerSession::m_Name );
} }
bool CNetServerSession::JSI_Close(JSContext *cx, uintN argc, jsval *argv) bool CNetServerSession::JSI_Close(JSContext *cx, uintN argc, jsval *argv)

View File

@ -35,12 +35,12 @@ void CPlayer::ScriptingInit()
AddMethod<jsval, &CPlayer::JSI_ToString>( "toString", 0 ); AddMethod<jsval, &CPlayer::JSI_ToString>( "toString", 0 );
AddMethod<jsval, &CPlayer::JSI_SetColour>( "setColour", 1); AddMethod<jsval, &CPlayer::JSI_SetColour>( "setColour", 1);
AddReadOnlyClassProperty( L"id", &CPlayer::m_PlayerID ); AddProperty( L"id", &CPlayer::m_PlayerID, true );
// MT: Work out how this fits with the Synched stuff... // MT: Work out how this fits with the Synched stuff...
// AddClassProperty( L"name", &CPlayer::m_Name ); // AddClassProperty( L"name", &CPlayer::m_Name );
// AddClassProperty( L"colour", &CPlayer::m_Colour ); // AddClassProperty( L"colour", &CPlayer::m_Colour );
AddClassProperty( L"controlled", (IJSObject::GetFn)JSI_GetControlledEntities ); AddProperty( L"controlled", (IJSObject::GetFn)JSI_GetControlledEntities );
CJSObject<CPlayer>::ScriptingInit( "Player" ); CJSObject<CPlayer>::ScriptingInit( "Player" );
} }

View File

@ -173,7 +173,7 @@ bool CProfileNode::Return()
void CProfileNode::ScriptingInit() void CProfileNode::ScriptingInit()
{ {
AddClassProperty( L"name", (IJSObject::GetFn)CProfileNode::JS_GetName ); AddProperty( L"name", (IJSObject::GetFn)CProfileNode::JS_GetName );
/* /*
AddReadOnlyClassProperty( L"callsTotal", &CProfileNode::calls_total ); AddReadOnlyClassProperty( L"callsTotal", &CProfileNode::calls_total );
AddReadOnlyClassProperty( L"callsPerFrame", &CProfileNode::calls_frame_last ); AddReadOnlyClassProperty( L"callsPerFrame", &CProfileNode::calls_frame_last );

View File

@ -38,7 +38,7 @@ JSBool JSI_Selection::setSelection( JSContext* context, JSObject* globalObject,
for( it = Info->m_Data->begin(); it < Info->m_Data->end(); it++ ) for( it = Info->m_Data->begin(); it < Info->m_Data->end(); it++ )
g_Selection.addSelection( *it ); g_Selection.addSelection( *it );
return( JS_TRUE ); return( JS_TRUE );
} }

View File

@ -1,19 +1,157 @@
#include "precompiled.h" #include "precompiled.h"
#include "DOMEvent.h" #include "DOMEvent.h"
#include "timer.h" #include "timer.h"
#include "Profile.h"
#include "ScriptObject.h"
CScriptEvent::CScriptEvent( const CStrW& Type, bool Cancelable, unsigned int TypeCode ) IEventTarget::~IEventTarget()
{ {
m_Type = Type; m_TypeCode = TypeCode; m_Cancelable = Cancelable; m_Cancelled = false; m_Timestamp = (long)( get_time() * 1000.0 ); HandlerMap::iterator it;
AddReadOnlyProperty( L"type", &m_Type ); for( it = m_Handlers_name.begin(); it != m_Handlers_name.end(); it++ )
AddReadOnlyProperty( L"cancelable", &m_Cancelable ); delete( it->second );
AddReadOnlyProperty( L"timeStamp", &m_Timestamp ); }
bool IEventTarget::_DispatchEvent( CScriptEvent* evt, IEventTarget* target )
{
if( before && before->_DispatchEvent( evt, target ) )
return( true ); // Stop propagation.
evt->m_CurrentTarget = this;
HandlerList::iterator it;
for( it = m_Handlers_id[evt->m_TypeCode].begin(); it != m_Handlers_id[evt->m_TypeCode].end(); it++ )
{
DOMEventHandler id = *it;
if( id && id->DispatchEvent( GetScriptExecContext( target ), evt ) )
return( true );
}
HandlerRange range = m_Handlers_name.equal_range( evt->m_Type );
HandlerMap::iterator itm;
for( itm = range.first; itm != range.second; itm++ )
{
DOMEventHandler id = itm->second;
if( id && id->DispatchEvent( GetScriptExecContext( target ), evt ) )
return( true );
}
if( after && after->_DispatchEvent( evt, target ) )
return( true ); // Stop propagation.
return( false );
}
bool IEventTarget::DispatchEvent( CScriptEvent* evt )
{
const char* data = g_Profiler.InternString( "script: " + (CStr8)evt->m_Type );
g_Profiler.StartScript( data );
evt->m_Target = this;
_DispatchEvent( evt, this );
g_Profiler.Stop();
return( !evt->m_Cancelled );
}
bool IEventTarget::AddHandler( int TypeCode, DOMEventHandler handler )
{
HandlerList::iterator it;
for( it = m_Handlers_id[TypeCode].begin(); it != m_Handlers_id[TypeCode].end(); it++ )
if( **it == *handler ) return( false );
m_Handlers_id[TypeCode].push_back( handler );
return( true );
}
bool IEventTarget::AddHandler( CStrW TypeString, DOMEventHandler handler )
{
HandlerMap::iterator it;
HandlerRange range = m_Handlers_name.equal_range( TypeString );
for( it = range.first; it != range.second; it++ )
if( *( it->second ) == *handler ) return( false );
m_Handlers_name.insert( HandlerMap::value_type( TypeString, handler ) );
return( true );
}
bool IEventTarget::RemoveHandler( int TypeCode, DOMEventHandler handler )
{
HandlerList::iterator it;
for( it = m_Handlers_id[TypeCode].begin(); it != m_Handlers_id[TypeCode].end(); it++ )
if( **it == *handler )
{
m_Handlers_id[TypeCode].erase( it );
return( true );
}
return( false );
}
bool IEventTarget::RemoveHandler( CStrW TypeString, DOMEventHandler handler )
{
HandlerMap::iterator it;
HandlerRange range = m_Handlers_name.equal_range( TypeString );
for( it = range.first; it != range.second; it++ )
if( *( it->second ) == *handler )
{
delete( it->second );
m_Handlers_name.erase( it );
return( true );
}
return( false );
}
bool IEventTarget::AddHandlerJS( JSContext* cx, uintN argc, jsval* argv )
{
assert( argc >= 2 );
DOMEventHandler handler = new CScriptObject( argv[1] );
if( !handler->Defined() )
{
delete( handler );
return( false );
}
if( !AddHandler( ToPrimitive<CStrW>( argv[0] ), handler ) )
{
delete( handler );
return( false );
}
return( true );
}
bool IEventTarget::RemoveHandlerJS( JSContext* cx, uintN argc, jsval* argv )
{
assert( argc >= 2 );
DOMEventHandler handler = new CScriptObject( argv[1] );
if( !handler->Defined() )
{
delete( handler );
return( false );
}
if( !RemoveHandler( ToPrimitive<CStrW>( argv[0] ), handler ) )
{
delete( handler );
return( false );
}
delete( handler );
return( true );
}
CScriptEvent::CScriptEvent( const CStrW& Type, unsigned int TypeCode, bool Cancelable, bool Blockable )
{
m_Type = Type; m_TypeCode = TypeCode;
m_Cancelable = Cancelable; m_Cancelled = false;
m_Blockable = Blockable; m_Blocked = false;
m_Timestamp = (long)( get_time() * 1000.0 );
} }
void CScriptEvent::ScriptingInit() void CScriptEvent::ScriptingInit()
{ {
AddMethod<jsval, &CScriptEvent::ToString>( "toString", 0 ); AddMethod<jsval, &CScriptEvent::ToString>( "toString", 0 );
AddMethod<jsval, &CScriptEvent::PreventDefault>( "preventDefault", 0 ); AddMethod<jsval, &CScriptEvent::PreventDefault>( "preventDefault", 0 );
AddMethod<jsval, &CScriptEvent::PreventDefault>( "cancel", 0 );
AddMethod<jsval, StopPropagation>( "stopPropagation", 0 );
AddProperty( L"type", &CScriptEvent::m_Type, true );
AddProperty( L"cancelable", &CScriptEvent::m_Cancelable, true );
AddProperty( L"blockable", &CScriptEvent::m_Blockable, true );
AddProperty( L"timestamp", &CScriptEvent::m_Timestamp, true );
CJSObject<CScriptEvent>::ScriptingInit( "Event" ); CJSObject<CScriptEvent>::ScriptingInit( "Event" );
} }
@ -25,6 +163,13 @@ jsval CScriptEvent::PreventDefault( JSContext* cx, uintN argc, jsval* argv )
return( JSVAL_VOID ); return( JSVAL_VOID );
} }
jsval CScriptEvent::StopPropagation( JSContext* cx, uintN argc, jsval* argv )
{
if( m_Blockable )
m_Blocked = true;
return( JSVAL_VOID );
}
jsval CScriptEvent::ToString( JSContext* cx, uintN argc, jsval* argv ) jsval CScriptEvent::ToString( JSContext* cx, uintN argc, jsval* argv )
{ {
wchar_t buffer[256]; wchar_t buffer[256];

View File

@ -9,6 +9,56 @@
#include "ScriptableObject.h" #include "ScriptableObject.h"
#include "EventTypes.h"
class CScriptObject;
class CScriptEvent;
typedef CScriptObject* DOMEventHandler;
class IEventTarget
{
// Return 'true' if we should stop propagating.
bool _DispatchEvent( CScriptEvent* evt, IEventTarget* target );
// Events dispatched to this object are sent here before being processed.
IEventTarget* before;
// Events dispatched to this object are sent here after being processed.
IEventTarget* after;
typedef std::vector<DOMEventHandler> HandlerList;
HandlerList m_Handlers_id[EVENT_LAST];
typedef STL_HASH_MULTIMAP<CStrW, DOMEventHandler, CStrW_hash_compare> HandlerMap;
HandlerMap m_Handlers_name;
typedef std::pair<HandlerMap::iterator, HandlerMap::iterator> HandlerRange;
public:
IEventTarget()
{
before = NULL;
after = NULL;
}
~IEventTarget();
inline void SetPriorObject( IEventTarget* obj ) { before = obj; }
inline void SetNextObject( IEventTarget* obj ) { after = obj; }
// Returns false if the handler was already present
bool AddHandler( int TypeCode, DOMEventHandler handler );
bool AddHandler( CStrW TypeString, DOMEventHandler handler );
// Returns false if the handler was not present
bool RemoveHandler( int TypeCode, DOMEventHandler handler );
bool RemoveHandler( CStrW TypeString, DOMEventHandler handler );
bool AddHandlerJS( JSContext* cx, uintN argc, jsval* argv );
bool RemoveHandlerJS( JSContext* cx, uintN argc, jsval* argv );
// Return the JSObject* we'd like to be the 'this' object
// when executing the handler. The argument is the object
// to which the event is targeted.
virtual JSObject* GetScriptExecContext( IEventTarget* target ) = 0;
bool DispatchEvent( CScriptEvent* evt );
};
class CScriptEvent : public CJSObject<CScriptEvent> class CScriptEvent : public CJSObject<CScriptEvent>
{ {
public: public:
@ -20,10 +70,10 @@ public:
}; };
// Target (currently unused) // Target (currently unused)
// EventTarget* m_Target; IEventTarget* m_Target;
// Listening object currently being processed (currently unused) // Listening object currently being processed (currently unused)
// EventTarget* m_CurrentTarget; IEventTarget* m_CurrentTarget;
// Phase type (currently unused) // Phase type (currently unused)
// EPhaseType m_EventPhase; // EPhaseType m_EventPhase;
@ -34,6 +84,9 @@ public:
// Can be cancelled (default actions prevented) // Can be cancelled (default actions prevented)
bool m_Cancelable; bool m_Cancelable;
// Can be blocked (prevented from propogating along the handler chain)
bool m_Blockable;
// Timestamp (milliseconds since epoch (start of game?)) // Timestamp (milliseconds since epoch (start of game?))
i32 m_Timestamp; i32 m_Timestamp;
@ -46,13 +99,17 @@ public:
// Has been cancelled? // Has been cancelled?
bool m_Cancelled; bool m_Cancelled;
// Has it been blocked (won't be sent to any more handlers)
bool m_Blocked;
// -- // --
jsval ToString( JSContext* cx, uintN argc, jsval* argv ); jsval ToString( JSContext* cx, uintN argc, jsval* argv );
jsval PreventDefault( JSContext* cx, uintN argc, jsval* argv ); jsval PreventDefault( JSContext* cx, uintN argc, jsval* argv );
jsval StopPropagation( JSContext* cx, uintN argc, jsval* argv );
public: public:
CScriptEvent( const CStrW& Type, bool Cancelable, unsigned int TypeCode = (unsigned int)-1 ); CScriptEvent( const CStrW& Type, unsigned int TypeCode = (unsigned int)-1, bool Cancelable = true, bool Blockable = true );
static void ScriptingInit(); static void ScriptingInit();
}; };

View File

@ -62,13 +62,6 @@ template<typename T> bool ToPrimitive( JSContext* cx, jsval v, T*& Storage )
return( true ); return( true );
} }
/*
template<typename T> JSObject* ToScript( T** Native )
{
return( ToScript( *Native ) );
}
*/
template<typename T> inline T ToPrimitive( JSContext* cx, jsval v ) { T Temp; ToPrimitive( cx, v, Temp ); return( Temp ); } template<typename T> inline T ToPrimitive( JSContext* cx, jsval v ) { T Temp; ToPrimitive( cx, v, Temp ); return( Temp ); }
template<typename T> inline T ToPrimitive( jsval v ) { return( ToPrimitive<T>( g_ScriptingHost.GetContext(), v ) ); } template<typename T> inline T ToPrimitive( jsval v ) { return( ToPrimitive<T>( g_ScriptingHost.GetContext(), v ) ); }

View File

@ -48,10 +48,10 @@ void SColour::SColourInit( float _r, float _g, float _b, float _a )
void SColour::ScriptingInit() void SColour::ScriptingInit()
{ {
AddMethod<jsval, &SColour::ToString>( "toString", 0 ); AddMethod<jsval, &SColour::ToString>( "toString", 0 );
AddClassProperty<float>( L"r", (float IJSObject::*)&SColour::r ); AddProperty<float>( L"r", (float IJSObject::*)&SColour::r );
AddClassProperty<float>( L"g", (float IJSObject::*)&SColour::g ); AddProperty<float>( L"g", (float IJSObject::*)&SColour::g );
AddClassProperty<float>( L"b", (float IJSObject::*)&SColour::b ); AddProperty<float>( L"b", (float IJSObject::*)&SColour::b );
AddClassProperty<float>( L"a", (float IJSObject::*)&SColour::a ); AddProperty<float>( L"a", (float IJSObject::*)&SColour::a );
CJSObject<SColour>::ScriptingInit( "Colour", SColour::Construct, 3 ); CJSObject<SColour>::ScriptingInit( "Colour", SColour::Construct, 3 );
} }

View File

@ -12,6 +12,7 @@
#include "timer.h" #include "timer.h"
#include "LightEnv.h" #include "LightEnv.h"
#include "MapWriter.h" #include "MapWriter.h"
#include "GameEvents.h"
#include "Game.h" #include "Game.h"
#include "Network/Server.h" #include "Network/Server.h"
@ -59,6 +60,8 @@ JSFunctionSpec ScriptFunctionTable[] =
{"getGlobal", getGlobal, 0, 0, 0 }, {"getGlobal", getGlobal, 0, 0, 0 },
{"getGUIGlobal", getGUIGlobal, 0, 0, 0 }, {"getGUIGlobal", getGUIGlobal, 0, 0, 0 },
{"setCursor", setCursor, 1, 0, 0 }, {"setCursor", setCursor, 1, 0, 0 },
{"addGlobalHandler", AddGlobalHandler, 2, 0, 0 },
{"removeGlobalHandler", RemoveGlobalHandler, 2, 0, 0 },
{"setCameraTarget", setCameraTarget, 1, 0, 0 }, {"setCameraTarget", setCameraTarget, 1, 0, 0 },
{"startGame", startGame, 0, 0, 0 }, {"startGame", startGame, 0, 0, 0 },
{"endGame", endGame, 0, 0, 0 }, {"endGame", endGame, 0, 0, 0 },
@ -227,6 +230,18 @@ JSBool SetLocalPlayer( JSContext* context, JSObject* obj, jsval id, jsval* vp )
return( JS_TRUE ); return( JS_TRUE );
} }
JSBool AddGlobalHandler( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )
{
*rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.AddHandlerJS( cx, argc, argv ) );
return( JS_TRUE );
}
JSBool RemoveGlobalHandler( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )
{
*rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.RemoveHandlerJS( cx, argc, argv ) );
return( JS_TRUE );
}
JSBool setCameraTarget( JSContext* context, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) JSBool setCameraTarget( JSContext* context, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )
{ {
CVector3D* target; CVector3D* target;

View File

@ -20,7 +20,11 @@ JSBool GetPlayerSet( JSContext* context, JSObject* globalObject, jsval id, jsval
JSBool GetLocalPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); JSBool GetLocalPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp );
JSBool SetLocalPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); JSBool SetLocalPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp );
JSBool GetGaiaPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); JSBool GetGaiaPlayer( JSContext* context, JSObject* globalObject, jsval id, jsval* vp );
JSBool GetLocalPlayer( JSContext* context, JSObject* globalObject, jsval argv, jsval* vp ); // JSBool SetGaiaPlayer( JSContext* context, JSObject* globalObject, jsval argv, jsval* vp );
// Events system
JSFunc AddGlobalHandler;
JSFunc RemoveGlobalHandler;
// Camera // Camera
JSFunc setCameraTarget; JSFunc setCameraTarget;

View File

@ -4,36 +4,6 @@
// //
// Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk) // Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk)
// //
// General idea:
//
// IJSProperty is the interface representing a property of an object.
// Objects contain a mapping of names->IJSProperties
// Some IJSProperties wrap C++ variables that are declared by the engine
// Others wrap pairs of getter/setter functions
// Most, however, wrap a jsval and are defined by scripts and XML files.
// Objects may also have a parent object. If an attempt is made to
// access a property that doesn't exist in this object, the parent's object
// is checked, and so on.
// To allow this parent system to work for C++ properties, when a C++
// is set on an object, it's also set on any object that inherits it
// (Unless that object specifies its own value for that property, or
// the property is marked as being uninheritable)
// Objects and properties may be flagged read-only, causing all attempts to
// set values to become no-ops. If an object is read-only, all properties are
// - even ones that are flagged as writable.
// Usage: Create a class CSomething inheriting CJSObject<CSomething>
// In CSomething's constructor, add properties to the new object with
// AddProperty( name, pointer-to-C++-variable ).
// Also, ScriptingInit( "Some Name" ) must be called at initialization
// - put it in main. There's also AddMethod<ReturnType, NativeFunction>( Name,
// MinArgs ) - call that at initialization, too.
// If you include data members or functions that return types that JSConversions.h
// doesn't handle sensibly, you need to make it do so.
// If you're looking for examples, DOMEvent.h is the simplest user of this class
// CBaseEntity and CEntity do also (and use other stuff not mentioned above)
// but are more complex.
#include "scripting/ScriptingHost.h" #include "scripting/ScriptingHost.h"
#include "JSConversions.h" #include "JSConversions.h"
@ -41,72 +11,27 @@
#ifndef SCRIPTABLE_INCLUDED #ifndef SCRIPTABLE_INCLUDED
#define SCRIPTABLE_INCLUDED #define SCRIPTABLE_INCLUDED
#define ALLOW_NONSHARED_NATIVES
class IJSObject; class IJSObject;
class IJSProperty class IJSProperty
{ {
public: public:
virtual jsval Get( JSContext* cx, IJSObject* obj ) = 0;
bool m_AllowsInheritance; virtual void Set( JSContext* cx, IJSObject* obj, jsval value ) = 0;
bool m_Inherited;
bool m_Intrinsic;
// This is to make sure that all the fields are initialized at construction
inline IJSProperty():
m_AllowsInheritance(true),
m_Inherited(true),
m_Intrinsic(true)
{}
virtual jsval Get( JSContext* cx, IJSObject* owner ) = 0;
virtual void Set( JSContext* cx, IJSObject* owner, jsval Value ) = 0;
// Copies the data directly out of a parent property
// Warning: Don't use if you're not certain the properties are not of the same type.
virtual void ImmediateCopy( IJSObject* CopyTo, IJSObject* CopyFrom, IJSProperty* CopyProperty ) = 0;
jsval Get( IJSObject* owner ) { return( Get( g_ScriptingHost.GetContext(), owner ) ); }
void Set( IJSObject* owner, jsval Value ) { return( Set( g_ScriptingHost.GetContext(), owner, Value ) ); }
virtual ~IJSProperty() {}
}; };
class IJSObject class IJSObject
{ {
// Make copy constructor and assignment operator private - since copying of
// these objects is unsafe unless done specially.
// These will never be implemented (they are, after all, here to *prevent*
// copying)
IJSObject(const IJSObject &other);
IJSObject& operator=(const IJSObject &other);
public: public:
typedef STL_HASH_MAP<CStrW, IJSProperty*, CStrW_hash_compare> PropertyTable; typedef STL_HASH_MAP<CStrW, IJSProperty*, CStrW_hash_compare> PropertyTable;
typedef std::vector<IJSObject*> InheritorsList;
// Used for freshen/update
typedef void (IJSObject::*NotifyFn)();
// Property getters and setters // Property getters and setters
typedef jsval (IJSObject::*GetFn)(); typedef jsval (IJSObject::*GetFn)();
typedef void (IJSObject::*SetFn)( jsval ); typedef void (IJSObject::*SetFn)( jsval value );
// Properties of this object // Return a pointer to a property, if it exists
PropertyTable m_Properties;
// Parent object
IJSObject* m_Parent;
// Objects that inherit from this
InheritorsList m_Inheritors;
// Set the base, and rebuild
void SetBase( IJSObject* m_Parent );
// Rebuild any intrinsic (mapped-to-C++-variable) properties
virtual void Rebuild() = 0;
// Check for a property
virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0; virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0;
// Retrieve the value of a property (returning false if that property is not defined) // Retrieve the value of a property (returning false if that property is not defined)
@ -121,263 +46,49 @@ public:
template<typename T, bool ReadOnly = false> class CJSObject; template<typename T, bool ReadOnly = false> class CJSObject;
template<typename T> class CJSPropertyAccessor template<typename T, bool ReadOnly> class CJSProperty : public IJSProperty
{
T* m_Owner;
CStrW m_PropertyRoot;
template<typename Q, bool ReadOnly> friend class CJSObject;
public:
CJSPropertyAccessor( T* Owner, CStrW PropertyRoot )
{
m_Owner = Owner;
m_PropertyRoot = PropertyRoot;
}
static JSObject* CreateAccessor( JSContext* cx, T* Owner, CStrW PropertyRoot )
{
JSObject* Accessor = JS_NewObject( cx, &JSI_Class, NULL, NULL );
JS_SetPrivate( cx, Accessor, new CJSPropertyAccessor( Owner, PropertyRoot ) );
return( Accessor );
}
static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
if( !Instance ) return( JS_TRUE );
CStrW PropName = Instance->m_PropertyRoot + CStrW( L"." ) + g_ScriptingHost.ValueToUCString( id );
Instance->m_Owner->GetProperty( cx, PropName, vp );
return( JS_TRUE );
}
static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
if( !Instance ) return( JS_TRUE );
CStrW PropName = g_ScriptingHost.ValueToUCString( id );
Instance->m_Owner->SetProperty( cx, Instance->m_PropertyRoot + CStrW( L"." ) + PropName, vp );
return( JS_TRUE );
}
static JSBool JSPrimitive( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
if( !Instance ) return( JS_TRUE );
// Check all along the inheritance tree
// Possible optimization: Store the hashed value over the lookups
IJSObject* Target = Instance->m_Owner;
IJSProperty* Property;
while( Target )
{
Property = Target->HasProperty( Instance->m_PropertyRoot );
if( Property )
{
*rval = Property->Get( cx, Target );
break;
}
Target = Target->m_Parent;
}
return( JS_TRUE );
}
static JSBool JSToString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj );
if( !Instance ) return( JS_TRUE );
// Check all along the inheritance tree
// TODO: Optimization: Store the hashed value over the lookups
IJSObject* Target = Instance->m_Owner;
IJSProperty* Property;
JSString* str;
while( Target )
{
Property = Target->HasProperty( Instance->m_PropertyRoot );
if( Property )
{
str = JS_ValueToString( cx, Property->Get( cx, Target ) );
break;
}
Target = Target->m_Parent;
}
*rval = STRING_TO_JSVAL( str );
return( JS_TRUE );
}
static JSClass JSI_Class;
static void ScriptingInit()
{
JSFunctionSpec JSI_methods[] = { { "valueOf", JSPrimitive, 0, 0, 0 }, { "toString", JSToString, 0, 0, 0 }, { 0 } };
JSPropertySpec JSI_props[] = { { 0 } };
g_ScriptingHost.DefineCustomObjectType( &JSI_Class, NULL, 0, JSI_props, JSI_methods, NULL, NULL );
}
};
template<typename T> JSClass CJSPropertyAccessor<T>::JSI_Class = {
"Property", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
JSGetProperty, JSSetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub,
NULL, NULL, NULL, NULL
};
template<typename T, bool ReadOnly> class CJSSharedProperty : public IJSProperty
{ {
T IJSObject::*m_Data; T IJSObject::*m_Data;
// Function on Owner to call after value is changed
IJSObject::NotifyFn m_Update;
// Function on Owner to call before reading or writing the value
IJSObject::NotifyFn m_Freshen;
public: public:
CJSSharedProperty( T IJSObject::*Data, bool AllowsInheritance = false, IJSObject::NotifyFn Update = NULL, IJSObject::NotifyFn Freshen = NULL ) CJSProperty( T IJSObject::*Data )
{ {
m_Data = Data; m_Data = Data;
m_AllowsInheritance = AllowsInheritance;
m_Update = Update;
m_Freshen = Freshen;
m_Intrinsic = true;
m_Inherited = true;
} }
jsval Get( JSContext* cx, IJSObject* owner ) jsval Get( JSContext* cx, IJSObject* owner )
{ {
if( m_Freshen ) (owner->*m_Freshen)();
return( ToJSVal( owner->*m_Data ) ); return( ToJSVal( owner->*m_Data ) );
} }
void ImmediateCopy( IJSObject* CopyTo, IJSObject* CopyFrom, IJSProperty* CopyProperty )
{
debug_warn( "Inheritance not supported for CJSSharedProperties" );
}
void Set( JSContext* cx, IJSObject* owner, jsval Value ) void Set( JSContext* cx, IJSObject* owner, jsval Value )
{ {
if( !ReadOnly ) if( !ReadOnly )
{ ToPrimitive( cx, Value, owner->*m_Data );
if( m_Freshen ) (owner->*m_Freshen)();
if( ToPrimitive( cx, Value, owner->*m_Data ) )
if( m_Update ) (owner->*m_Update)();
}
} }
}; };
template<typename T, bool ReadOnly> class CJSProperty : public IJSProperty #ifdef ALLOW_NONSHARED_NATIVES
template<typename T, bool ReadOnly> class CJSNonsharedProperty : public IJSProperty
{ {
T* m_Data; T* m_Data;
// Function on Owner to call after value is changed
IJSObject::NotifyFn m_Update;
// Function on Owner to call before reading or writing the value
IJSObject::NotifyFn m_Freshen;
public: public:
CJSProperty( T* Data, bool AllowsInheritance = false, IJSObject::NotifyFn Update = NULL, IJSObject::NotifyFn Freshen = NULL ) CJSNonsharedProperty( T* Data )
{ {
m_Data = Data; m_Data = Data;
m_AllowsInheritance = AllowsInheritance;
m_Update = Update;
m_Freshen = Freshen;
m_Intrinsic = true;
} }
jsval Get( JSContext* cx, IJSObject* owner ) jsval Get( JSContext* cx, IJSObject* owner )
{ {
if( m_Freshen ) (owner->*m_Freshen)();
return( ToJSVal( *m_Data ) ); return( ToJSVal( *m_Data ) );
} }
void ImmediateCopy( IJSObject* CopyTo, IJSObject* CopyFrom, IJSProperty* CopyProperty )
{
*m_Data = *( ( (CJSProperty*)CopyProperty )->m_Data );
}
void Set( JSContext* cx, IJSObject* owner, jsval Value ) void Set( JSContext* cx, IJSObject* owner, jsval Value )
{ {
if( !ReadOnly ) if( !ReadOnly )
{ ToPrimitive( cx, Value, *m_Data );
if( m_Freshen ) (owner->*m_Freshen)();
if( ToPrimitive( cx, Value, *m_Data ) )
if( m_Update ) (owner->*m_Update)();
}
}
};
class CJSReflector
{
template<typename Q, bool ReadOnly> friend class CJSObject;
JSObject* m_JSAccessor;
};
class CJSDynamicProperty : public IJSProperty
{
template<typename Q, bool ReadOnly> friend class CJSObject;
JSObject* m_JSAccessor;
public:
CJSDynamicProperty()
{
m_JSAccessor = NULL;
m_Intrinsic = false;
m_Inherited = false;
} }
}; };
class CJSValProperty : public CJSDynamicProperty #endif /* ALLOW_NONSHARED_NATIVES */
{
template<typename Q, bool ReadOnly> friend class CJSObject;
jsval m_Data;
public:
CJSValProperty( jsval Data, bool Inherited )
{
m_Inherited = Inherited;
m_Data = Data;
Root();
}
~CJSValProperty()
{
Uproot();
}
void Root()
{
if( JSVAL_IS_GCTHING( m_Data ) )
#ifndef NDEBUG
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_Data, "jsval property" );
#else
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
#endif
}
void Uproot()
{
if( JSVAL_IS_GCTHING( m_Data ) )
JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
}
jsval Get( JSContext* cx, IJSObject* owner )
{
return( m_Data );
}
void Set( JSContext* cx, IJSObject* owner, jsval Value )
{
Uproot();
m_Data = Value;
Root();
}
void ImmediateCopy( IJSObject* CopyTo, IJSObject* CopyFrom, IJSProperty* CopyProperty )
{
assert( 0 && "ImmediateCopy called on a CJSValProperty (something's gone wrong with the inheritance on this object)" );
}
};
class CJSFunctionProperty : public IJSProperty class CJSFunctionProperty : public IJSProperty
{ {
@ -390,28 +101,59 @@ class CJSFunctionProperty : public IJSProperty
public: public:
CJSFunctionProperty( IJSObject::GetFn Getter, IJSObject::SetFn Setter ) CJSFunctionProperty( IJSObject::GetFn Getter, IJSObject::SetFn Setter )
{ {
m_Inherited = false;
m_Intrinsic = true;
m_Getter = Getter; m_Getter = Getter;
m_Setter = Setter; m_Setter = Setter;
// Must at least be able to read // Must at least be able to read
assert( m_Getter ); assert( m_Getter );
} }
jsval Get( JSContext* cx, IJSObject* owner ) jsval Get( JSContext* cx, IJSObject* obj )
{ {
return( (owner->*m_Getter)() ); return( (obj->*m_Getter)() );
} }
void Set( JSContext* cx, IJSObject* owner, jsval Value ) void Set( JSContext* cx, IJSObject* obj, jsval value )
{ {
if( m_Setter ) if( m_Setter )
(owner->*m_Setter)( Value ); (obj->*m_Setter)( value );
}
void ImmediateCopy( IJSObject* CopyTo, IJSObject* CopyFrom, IJSProperty* CopyProperty )
{
assert( 0 && "ImmediateCopy called on a property wrapping getter/setter functions (something's gone wrong with the inheritance for this object)" );
} }
}; };
class CJSValProperty : public IJSProperty
{
template<typename Q, bool ReadOnly> friend class CJSObject;
jsval m_Data;
public:
CJSValProperty( jsval Data )
{
m_Data = Data;
Root();
}
~CJSValProperty()
{
Uproot();
}
void Root()
{
if( JSVAL_IS_GCTHING( m_Data ) )
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
}
void Uproot()
{
if( JSVAL_IS_GCTHING( m_Data ) )
JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data );
}
jsval Get( JSContext* cx, IJSObject* object )
{
return( m_Data );
}
void Set( JSContext* cx, IJSObject* owner, jsval value )
{
Uproot();
m_Data = value;
Root();
}
};
// Wrapper around native functions that are attached to CJSObjects // Wrapper around native functions that are attached to CJSObjects
@ -432,14 +174,17 @@ public:
template<typename T, bool ReadOnly> class CJSObject : public IJSObject template<typename T, bool ReadOnly> class CJSObject : public IJSObject
{ {
typedef STL_HASH_MAP<CStrW, CJSReflector*, CStrW_hash_compare> ReflectorTable; // This object
JSObject* m_JS; JSObject* m_JS;
ReflectorTable m_Reflectors; protected:
// The properties defined by the engine
public: static PropertyTable m_NativeProperties;
static JSClass JSI_class; #ifdef ALLOW_NONSHARED_NATIVES
PropertyTable m_NonsharedProperties;
#endif
// Properties added by script
PropertyTable m_ScriptProperties;
// Whether native code is responsible for managing this object. // Whether native code is responsible for managing this object.
// Script constructors should clear this *BEFORE* creating a JS // Script constructors should clear this *BEFORE* creating a JS
@ -447,8 +192,45 @@ public:
bool m_EngineOwned; bool m_EngineOwned;
public:
// Property getters and setters
typedef jsval (T::*TGetFn)();
typedef void (T::*TSetFn)( jsval value );
static JSClass JSI_class;
static void ScriptingInit( const char* ClassName, JSNative Constructor = NULL, uintN ConstructorMinArgs = 0 )
{
JSFunctionSpec* JSI_methods = new JSFunctionSpec[ m_Methods.size() + 1 ];
unsigned int MethodID;
for( MethodID = 0; MethodID < m_Methods.size(); MethodID++ )
JSI_methods[MethodID] = m_Methods[MethodID];
JSI_methods[MethodID].name = 0;
JSI_class.name = ClassName;
g_ScriptingHost.DefineCustomObjectType( &JSI_class, Constructor, ConstructorMinArgs, JSI_props, JSI_methods, NULL, NULL );
delete[]( JSI_methods );
atexit( ScriptingShutdown );
}
static void ScriptingShutdown()
{
PropertyTable::iterator it;
for( it = m_NativeProperties.begin(); it != m_NativeProperties.end(); it++ )
delete( it->second );
}
// JS Property access // JS Property access
bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ); bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
{
IJSProperty* Property = HasProperty( PropertyName );
if( Property )
*vp = Property->Get( cx, this );
return( true );
}
void SetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) void SetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
{ {
if( !ReadOnly ) if( !ReadOnly )
@ -459,29 +241,6 @@ public:
{ {
// Already exists // Already exists
prop->Set( cx, this, *vp ); prop->Set( cx, this, *vp );
prop->m_Inherited = false;
// If it's a C++ property, reflect this change in objects that inherit this.
if( prop->m_AllowsInheritance && prop->m_Intrinsic )
{
InheritorsList UpdateSet( m_Inheritors );
while( !UpdateSet.empty() )
{
IJSObject* UpdateObj = UpdateSet.back();
UpdateSet.pop_back();
IJSProperty* UpdateProp = UpdateObj->HasProperty( PropertyName );
// Property must exist, also be a C++ property, and not have its value specified.
if( UpdateProp && UpdateProp->m_Intrinsic && UpdateProp->m_Inherited )
{
UpdateProp->Set( cx, this, *vp );
InheritorsList::iterator it2;
for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ )
UpdateSet.push_back( *it2 );
}
}
}
} }
else else
{ {
@ -491,9 +250,110 @@ public:
} }
} }
IJSProperty* HasProperty( CStrW PropertyName )
{
PropertyTable::iterator it;
// Engine-defined properties take precedence
it = m_NativeProperties.find( PropertyName );
if( it != m_NativeProperties.end() )
return( it->second );
// Next are the object-local engine-defined properties
// (if they're compiled in)
#ifdef ALLOW_NONSHARED_NATIVES
it = m_NonsharedProperties.find( PropertyName );
if( it != m_NonsharedProperties.end() )
return( it->second );
#endif
// Then check in script properties
it = m_ScriptProperties.find( PropertyName );
if( it != m_ScriptProperties.end() )
return( it->second );
// Otherwise not found
return( NULL );
}
void AddProperty( CStrW PropertyName, jsval Value )
{
assert( !HasProperty( PropertyName ) );
CJSValProperty* newProp = new CJSValProperty( Value );
m_ScriptProperties[PropertyName] = newProp;
}
void AddProperty( CStrW PropertyName, CStrW Value )
{
AddProperty( PropertyName, JSParseString( Value ) );
}
static void AddProperty( CStrW PropertyName, TGetFn Getter, TSetFn Setter = NULL )
{
m_NativeProperties[PropertyName] = new CJSFunctionProperty( (GetFn)Getter, (SetFn)Setter );
}
template<typename ReturnType, ReturnType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
static void AddMethod( const char* Name, uintN MinArgs )
{
JSFunctionSpec FnInfo = { Name, CNativeFunction<T, ReadOnly, ReturnType, NativeFunction>::JSFunction, MinArgs, 0, 0 };
m_Methods.push_back( FnInfo );
}
template<typename PropType> static void AddProperty( CStrW PropertyName, PropType T::*Native, bool PropReadOnly = ReadOnly )
{
IJSProperty* prop;
if( PropReadOnly )
{
prop = new CJSProperty<PropType, true>( (PropType IJSObject::*)Native );
}
else
prop = new CJSProperty<PropType, ReadOnly>( (PropType IJSObject::*)Native );
m_NativeProperties[PropertyName] = prop;
}
#ifdef ALLOW_NONSHARED_NATIVES
template<typename PropType> void AddLocalProperty( CStrW PropertyName, PropType* Native, bool PropReadOnly = ReadOnly )
{
IJSProperty* prop;
if( PropReadOnly )
{
prop = new CJSNonsharedProperty<PropType, true>( Native );
}
else
prop = new CJSNonsharedProperty<PropType, ReadOnly>( Native );
m_NonsharedProperties[PropertyName] = prop;
}
#endif
// Object operations
JSObject* GetScript()
{
if( !m_JS )
CreateScriptObject();
return( m_JS );
}
// Creating and releasing script objects is done automatically most of the time, but you
// can do it explicitly.
void CreateScriptObject()
{
if( !m_JS )
{
m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
if( m_EngineOwned )
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_JS );
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, (T*)this );
}
}
void ReleaseScriptObject()
{
if( m_JS )
{
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
if( m_EngineOwned )
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_JS );
m_JS = NULL;
}
}
// //
// Functions that must be provided to JavaScript // Functions and data that must be provided to JavaScript
// //
private:
static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{ {
T* Instance = ToNative<T>( cx, obj ); T* Instance = ToNative<T>( cx, obj );
@ -519,28 +379,6 @@ public:
return( JS_TRUE ); return( JS_TRUE );
} }
static void ScriptingInit( const char* ClassName, JSNative Constructor = NULL, uintN ConstructorMinArgs = 0 )
{
JSFunctionSpec* JSI_methods = new JSFunctionSpec[ m_Methods.size() + 1 ];
unsigned int MethodID;
for( MethodID = 0; MethodID < m_Methods.size(); MethodID++ )
JSI_methods[MethodID] = m_Methods[MethodID];
JSI_methods[MethodID].name = 0;
JSI_class.name = ClassName;
g_ScriptingHost.DefineCustomObjectType( &JSI_class, Constructor, ConstructorMinArgs, JSI_props, JSI_methods, NULL, NULL );
delete[]( JSI_methods );
atexit( ScriptingShutdown );
}
static void ScriptingShutdown()
{
PropertyTable::iterator it;
for( it = m_IntrinsicProperties.begin(); it != m_IntrinsicProperties.end(); it++ )
delete( it->second );
}
static void DefaultFinalize( JSContext *cx, JSObject *obj ) static void DefaultFinalize( JSContext *cx, JSObject *obj )
{ {
T* Instance = ToNative<T>( cx, obj ); T* Instance = ToNative<T>( cx, obj );
@ -551,52 +389,14 @@ public:
JS_SetPrivate( cx, obj, NULL ); JS_SetPrivate( cx, obj, NULL );
} }
public:
JSObject* GetScript()
{
if( !m_JS )
CreateScriptObject();
return( m_JS );
}
// Creating and releasing script objects is done automatically most of the time, but you
// can do it explicitly.
void CreateScriptObject()
{
if( !m_JS )
{
m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
if( m_EngineOwned )
{
#ifndef NDEBUG // Name the GC roots something more useful than 'ScriptableObject.h'
JS_AddNamedRoot( g_ScriptingHost.GetContext(), (void*)&m_JS, JSI_class.name );
#else
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_JS );
#endif
}
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, (T*)this );
}
}
void ReleaseScriptObject()
{
if( m_JS )
{
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL );
if( m_EngineOwned )
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_JS );
m_JS = NULL;
}
}
private:
static JSPropertySpec JSI_props[]; static JSPropertySpec JSI_props[];
static std::vector<JSFunctionSpec> m_Methods; static std::vector<JSFunctionSpec> m_Methods;
static PropertyTable m_IntrinsicProperties;
public: public:
// Standard constructors/destructors
CJSObject() CJSObject()
{ {
m_Parent = NULL;
m_JS = NULL; m_JS = NULL;
m_EngineOwned = true; m_EngineOwned = true;
} }
@ -607,163 +407,15 @@ public:
void Shutdown() void Shutdown()
{ {
PropertyTable::iterator it; PropertyTable::iterator it;
for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) for( it = m_ScriptProperties.begin(); it != m_ScriptProperties.end(); it++ )
{
if( !it->second->m_Intrinsic )
{
CJSDynamicProperty* extProp = (CJSValProperty*)it->second;
if( extProp->m_JSAccessor )
{
CJSPropertyAccessor< CJSObject<T, ReadOnly> >* accessor = (CJSPropertyAccessor< CJSObject<T, ReadOnly> >*)JS_GetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor );
assert( accessor );
delete( accessor );
JS_SetPrivate( g_ScriptingHost.GetContext(), extProp->m_JSAccessor, NULL );
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( extProp->m_JSAccessor ) );
}
}
delete( it->second ); delete( it->second );
} m_ScriptProperties.clear();
ReflectorTable::iterator it_a;
for( it_a = m_Reflectors.begin(); it_a != m_Reflectors.end(); it_a++ )
{
CJSPropertyAccessor< CJSObject<T, ReadOnly> >* accessor = (CJSPropertyAccessor< CJSObject<T, ReadOnly> >*)JS_GetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor );
assert( accessor );
delete( accessor );
JS_SetPrivate( g_ScriptingHost.GetContext(), it_a->second->m_JSAccessor, NULL );
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it_a->second->m_JSAccessor ) );
delete( it_a->second );
}
ReleaseScriptObject(); ReleaseScriptObject();
} #ifdef ALLOW_NONSHARED_NATIVES
void SetBase( IJSObject* Parent ) for( it = m_NonsharedProperties.begin(); it != m_NonsharedProperties.end(); it++ )
{
if( m_Parent )
{
// Remove this from the list of our parent's inheritors
InheritorsList::iterator it;
for( it = m_Parent->m_Inheritors.begin(); it != m_Parent->m_Inheritors.end(); it++ )
if( (*it) == this )
{
m_Parent->m_Inheritors.erase( it );
break;
}
}
m_Parent = Parent;
if( m_Parent )
{
// Place this in the list of our parent's inheritors
m_Parent->m_Inheritors.push_back( this );
Rebuild();
}
}
void Rebuild()
{
PropertyTable::iterator it;
// For each intrinsic property we have,
for( it = m_Properties.begin(); it != m_Properties.end(); it++ )
{
if( !it->second->m_Intrinsic || !it->second->m_Inherited )
continue;
// Attempt to locate it in the parent
IJSProperty* cp = m_Parent->HasProperty( it->first );
// If it doesn't have it, we've inherited from an object of a different type
// This isn't allowed at the moment; but I don't have an totally convincing
// reason for forbidding it entirely. Mind, I can't think of any use for it,
// either.
// If it can be inherited, inherit it.
if( cp && cp->m_AllowsInheritance )
{
assert( cp->m_Intrinsic );
it->second->ImmediateCopy( this, m_Parent, cp );
}
}
// Do the same for the shared properties table, too
for( it = m_IntrinsicProperties.begin(); it != m_IntrinsicProperties.end(); it++ )
{
if( !it->second->m_Inherited )
continue;
IJSProperty* cp = m_Parent->HasProperty( it->first );
if( cp && cp->m_AllowsInheritance )
{
assert( cp->m_Intrinsic );
it->second->ImmediateCopy( this, m_Parent, cp );
}
}
// Now recurse.
InheritorsList::iterator c;
for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ )
(*c)->Rebuild();
}
IJSProperty* HasProperty( CStrW PropertyName )
{
PropertyTable::iterator it;
it = T::m_IntrinsicProperties.find( PropertyName );
if( it != T::m_IntrinsicProperties.end() )
return( it->second );
it = m_Properties.find( PropertyName );
if( it != m_Properties.end() )
return( it->second );
return( NULL );
}
void AddProperty( CStrW PropertyName, jsval Value )
{
assert( !HasProperty( PropertyName ) );
CJSDynamicProperty* newProp = new CJSValProperty( Value, false );
m_Properties[PropertyName] = newProp;
ReflectorTable::iterator it;
it = m_Reflectors.find( PropertyName );
if( it != m_Reflectors.end() )
{
// We had an accessor pointing to this property before it was defined.
newProp->m_JSAccessor = it->second->m_JSAccessor;
JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it->second->m_JSAccessor ) );
JS_AddRoot( g_ScriptingHost.GetContext(), &( newProp->m_JSAccessor ) );
delete( it->second ); delete( it->second );
m_Reflectors.erase( it ); m_NonsharedProperties.clear();
} #endif
}
void AddProperty( CStrW PropertyName, CStrW Value )
{
AddProperty( PropertyName, JSParseString( Value ) );
}
static void AddClassProperty( CStrW PropertyName, GetFn Getter, SetFn Setter = NULL )
{
T::m_IntrinsicProperties[PropertyName] = new CJSFunctionProperty( Getter, Setter );
}
template<typename ReturnType, ReturnType (T::*NativeFunction)( JSContext* cx, uintN argc, jsval* argv )>
static void AddMethod( const char* Name, uintN MinArgs )
{
JSFunctionSpec FnInfo = { Name, CNativeFunction<T, ReadOnly, ReturnType, NativeFunction>::JSFunction, MinArgs, 0, 0 };
T::m_Methods.push_back( FnInfo );
}
template<typename PropType> static void AddClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
{
T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty<PropType, ReadOnly>( (PropType IJSObject::*)Native, PropAllowInheritance, Update, Refresh );
}
template<typename PropType> static void AddReadOnlyClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
{
T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty<PropType, true>( (PropType IJSObject::*)Native, PropAllowInheritance, Update, Refresh );
}
template<typename PropType> void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
{
m_Properties[PropertyName] = new CJSProperty<PropType, ReadOnly>( Native, PropAllowInheritance, Update, Refresh );
}
template<typename PropType> void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL )
{
m_Properties[PropertyName] = new CJSProperty<PropType, true>( Native, PropAllowInheritance, Update, Refresh );
} }
}; };
@ -781,72 +433,8 @@ template<typename T, bool ReadOnly> JSPropertySpec CJSObject<T, ReadOnly>::JSI_p
}; };
template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods; template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods;
template<typename T, bool ReadOnly> typename CJSObject<typename T, ReadOnly>::PropertyTable CJSObject<T, ReadOnly>::m_IntrinsicProperties;
template<typename T, bool ReadOnly> bool CJSObject<T, ReadOnly>::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) template<typename T, bool ReadOnly> typename CJSObject<typename T, ReadOnly>::PropertyTable CJSObject<T, ReadOnly>::m_NativeProperties;
{
IJSProperty* Property = HasProperty( PropertyName );
if( Property && Property->m_Intrinsic )
{
*vp = Property->Get( cx, this );
}
else
{
CJSDynamicProperty* extProp;
if( Property )
{
extProp = (CJSValProperty*)Property;
if( !extProp->m_JSAccessor )
{
extProp->m_JSAccessor = CJSPropertyAccessor< CJSObject<T, ReadOnly> >::CreateAccessor( cx, this, PropertyName );
JS_AddNamedRoot( cx, &extProp->m_JSAccessor, "property accessor" );
}
*vp = OBJECT_TO_JSVAL( extProp->m_JSAccessor );
}
else
{
// Check to see if it exists on a parent
IJSObject* check = m_Parent;
while( check )
{
if( check->HasProperty( PropertyName ) ) break;
check = check->m_Parent;
}
if( !check )
return( false );
// FIXME: Fiddle a way so this /doesn't/ require multiple kilobytes
// of memory. Can't think of any better way to do it yet. Problem is
// that script may access a property that isn't defined locally, but
// is defined by an ancestor. We can't return an accessor to the
// ancestor's property, because then if it's altered it affects that
// object, not this. At the moment, creating a 'reflector' property
// accessor that references /this/ object to be returned to script.
// (N.B. Can't just put JSObjects* in the table -> table entries can
// move -> root no longer refers to the JSObject.)
ReflectorTable::iterator it;
it = m_Reflectors.find( PropertyName );
if( it == m_Reflectors.end() )
{
CJSReflector* reflector = new CJSReflector();
reflector->m_JSAccessor = CJSPropertyAccessor< CJSObject<T, ReadOnly> >::CreateAccessor( cx, this, PropertyName );
JS_AddRoot( cx, &reflector->m_JSAccessor );
m_Reflectors.insert( std::pair<CStrW, CJSReflector*>( PropertyName, reflector ) );
*vp = OBJECT_TO_JSVAL( reflector->m_JSAccessor );
}
else
*vp = OBJECT_TO_JSVAL( it->second->m_JSAccessor );
}
}
return( true );
}
#endif #endif

View File

@ -114,8 +114,6 @@ struct CSynchedJSObjectBase
m_Owner(owner), m_Owner(owner),
m_Update(update) m_Update(update)
{ {
m_AllowsInheritance = false;
m_Intrinsic = true;
} }
}; };
@ -140,7 +138,7 @@ protected:
template <typename T> void AddSynchedProperty(CStrW name, T *native, UpdateFn update=NULL) template <typename T> void AddSynchedProperty(CStrW name, T *native, UpdateFn update=NULL)
{ {
ISynchedJSProperty *prop=new CSynchedJSProperty<T>(name, native, this, update); ISynchedJSProperty *prop=new CSynchedJSProperty<T>(name, native, this, update);
m_Properties[name]=prop; m_NonsharedProperties[name]=prop;
m_SynchedProperties[name]=prop; m_SynchedProperties[name]=prop;
} }
}; };

View File

@ -24,7 +24,10 @@ CBaseEntity::CBaseEntity()
AddProperty( L"traits.corpse", &m_corpse ); AddProperty( L"traits.corpse", &m_corpse );
for( int t = 0; t < EVENT_LAST; t++ ) for( int t = 0; t < EVENT_LAST; t++ )
AddProperty( EventNames[t], &m_EventHandlers[t] ); {
AddProperty( EventNames[t], &m_EventHandlers[t], false );
AddHandler( t, &m_EventHandlers[t] );
}
// Initialize, make life a little easier on the scriptors // Initialize, make life a little easier on the scriptors
m_speed = m_turningRadius = m_meleeRange = m_meleeRangeMin = 0.0f; m_speed = m_turningRadius = m_meleeRange = m_meleeRangeMin = 0.0f;
@ -62,6 +65,7 @@ void CBaseEntity::loadBase()
} }
SetBase( m_base ); SetBase( m_base );
SetNextObject( m_base );
} }
bool CBaseEntity::loadXML( CStr filename ) bool CBaseEntity::loadXML( CStr filename )
@ -204,7 +208,7 @@ void CBaseEntity::XMLLoadProperty( const CXeromyces& XeroFile, const XMBElement&
// Add a property, put the node text into it. // Add a property, put the node text into it.
CStrW PropertyName = BasePropertyName + CStr8( XeroFile.getElementString( Source.getNodeName() ) ); CStrW PropertyName = BasePropertyName + CStr8( XeroFile.getElementString( Source.getNodeName() ) );
IJSProperty* Existing = HasProperty( PropertyName ); IJSComplexProperty* Existing = HasProperty( PropertyName );
if( Existing ) if( Existing )
{ {
if( !Existing->m_Intrinsic ) if( !Existing->m_Intrinsic )
@ -264,11 +268,16 @@ void CBaseEntity::ScriptingInit()
{ {
AddMethod<jsval, &CBaseEntity::ToString>( "toString", 0 ); AddMethod<jsval, &CBaseEntity::ToString>( "toString", 0 );
CJSObject<CBaseEntity>::ScriptingInit( "EntityTemplate" ); CJSComplex<CBaseEntity>::ScriptingInit( "EntityTemplate" );
} }
// Script-bound functions // Script-bound functions
JSObject* CBaseEntity::GetScriptExecContext( IEventTarget* target )
{
return( target->GetScriptExecContext( target ) );
}
jsval CBaseEntity::ToString( JSContext* cx, uintN argc, jsval* argv ) jsval CBaseEntity::ToString( JSContext* cx, uintN argc, jsval* argv )
{ {
wchar_t buffer[256]; wchar_t buffer[256];

View File

@ -21,13 +21,13 @@
#include "CStr.h" #include "CStr.h"
#include "ObjectEntry.h" #include "ObjectEntry.h"
#include "scripting/ScriptableObject.h" #include "scripting/ScriptableComplex.h"
#include "BoundingObjects.h" #include "BoundingObjects.h"
#include "EventHandlers.h" #include "EventHandlers.h"
#include "ScriptObject.h" #include "ScriptObject.h"
#include "Xeromyces.h" #include "Xeromyces.h"
class CBaseEntity : public CJSObject<CBaseEntity> class CBaseEntity : public CJSComplex<CBaseEntity>, public IEventTarget
{ {
public: public:
CBaseEntity(); CBaseEntity();
@ -65,6 +65,9 @@ public:
// Script-bound functions // Script-bound functions
// Get script execution contexts - always run in the context of the entity that fired it.
JSObject* GetScriptExecContext( IEventTarget* target );
jsval ToString( JSContext* cx, uintN argc, jsval* argv ); jsval ToString( JSContext* cx, uintN argc, jsval* argv );
static void ScriptingInit(); static void ScriptingInit();

View File

@ -41,7 +41,10 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation )
AddProperty( L"player", &m_player ); AddProperty( L"player", &m_player );
for( int t = 0; t < EVENT_LAST; t++ ) for( int t = 0; t < EVENT_LAST; t++ )
AddProperty( EventNames[t], &m_EventHandlers[t] ); {
AddProperty( EventNames[t], &m_EventHandlers[t], false );
AddHandler( t, &m_EventHandlers[t] );
}
// Set our parent unit and build us an actor. // Set our parent unit and build us an actor.
m_actor = NULL; m_actor = NULL;
@ -102,6 +105,7 @@ void CEntity::loadBase()
// Set up our instance data // Set up our instance data
SetBase( m_base ); SetBase( m_base );
SetNextObject( m_base );
if( m_base->m_bound_type == CBoundingObject::BOUND_CIRCLE ) if( m_base->m_bound_type == CBoundingObject::BOUND_CIRCLE )
{ {
@ -326,16 +330,6 @@ void CEntity::dispatch( const CMessage* msg )
} }
*/ */
bool CEntity::DispatchEvent( CScriptEvent* evt )
{
const char* data = g_Profiler.InternString( "script: " + (CStr8)evt->m_Type );
g_Profiler.StartScript( data );
bool rval = m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt );
g_Profiler.Stop();
return( rval );
}
void CEntity::clearOrders() void CEntity::clearOrders()
{ {
m_orderQueue.clear(); m_orderQueue.clear();
@ -346,13 +340,6 @@ void CEntity::pushOrder( CEntityOrder& order )
m_orderQueue.push_back( order ); m_orderQueue.push_back( order );
} }
int CEntity::defaultOrder( CEntity* orderTarget )
{
CEventTargetChanged evt( orderTarget );
DispatchEvent( &evt );
return( evt.m_defaultAction );
}
bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget ) bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget )
{ {
CEventPrepareOrder evt( orderTarget, orderType ); CEventPrepareOrder evt( orderTarget, orderType );
@ -624,7 +611,7 @@ void CEntity::ScriptingInit()
AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase ); AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase );
CJSObject<CEntity>::ScriptingInit( "Entity", Construct, 2 ); CJSComplex<CEntity>::ScriptingInit( "Entity", Construct, 2 );
} }
// Script constructor // Script constructor

View File

@ -32,7 +32,7 @@
#define ENTITY_INCLUDED #define ENTITY_INCLUDED
#include <deque> #include <deque>
#include "scripting/ScriptableObject.h" #include "scripting/ScriptableComplex.h"
#include "Player.h" #include "Player.h"
#include "Vector2D.h" #include "Vector2D.h"
@ -52,7 +52,7 @@ class CUnit;
// TODO MT: Put this is /some/ sort of order... // TODO MT: Put this is /some/ sort of order...
class CEntity : public CJSObject<CEntity> class CEntity : public CJSComplex<CEntity>, public IEventTarget
{ {
friend class CEntityManager; friend class CEntityManager;
private: private:
@ -96,22 +96,15 @@ public:
//-- Scripts //-- Scripts
// Get script execution contexts - always run in the context of the entity that fired it.
JSObject* GetScriptExecContext( IEventTarget* target ) { return( ((CEntity*)target)->GetScript() ); }
// EventListener adaptation? // EventListener adaptation?
//typedef std::vector<CScriptObject> ExtendedHandlerList; //typedef std::vector<CScriptObject> ExtendedHandlerList;
//typedef STL_HASH_MAP<CStrW, HandlerList, CStrW_hash_compare> ExtendedHandlerTable; //typedef STL_HASH_MAP<CStrW, HandlerList, CStrW_hash_compare> ExtendedHandlerTable;
CScriptObject m_EventHandlers[EVENT_LAST]; CScriptObject m_EventHandlers[EVENT_LAST];
// EventListener adaptation?
/*
void AddListener( const CStrW& EventType, CScriptObject* Handler )
{
m_EventHandlers[EventType].push_back( Handler );
}
*/
bool DispatchEvent( CScriptEvent* evt );
CUnit* m_actor; CUnit* m_actor;
// State transition in the FSM (animations should be reset) // State transition in the FSM (animations should be reset)
@ -185,8 +178,6 @@ public:
void checkGroup(); // Groups void checkGroup(); // Groups
void checkExtant(); // Existence void checkExtant(); // Existence
// Returns the default action of the entity upon the target (or -1 if none apply)
int CEntity::defaultOrder( CEntity* orderTarget );
// Returns whether the entity is capable of performing the given orderType on the target. // Returns whether the entity is capable of performing the given orderType on the target.
bool acceptsOrder( int orderType, CEntity* orderTarget ); bool acceptsOrder( int orderType, CEntity* orderTarget );

View File

@ -299,6 +299,8 @@ bool CEntity::processContactAction( CEntityOrder* current, size_t timestep_milli
} }
bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, CSkeletonAnim* animation, CScriptEvent* contactEvent, float range, float minRange = 0.0f ) bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, CSkeletonAnim* animation, CScriptEvent* contactEvent, float range, float minRange = 0.0f )
{ {
static size_t cyclepos = 0;
// Target's dead (or exhausted)? Then our work here is done. // Target's dead (or exhausted)? Then our work here is done.
if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant ) if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant )
{ {
@ -308,9 +310,27 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
if( m_actor ) if( m_actor )
{ {
// Still playing attack animation? Suspend processing. if( animation && ( m_actor->GetModel()->GetAnimation() == animation ) )
if( m_actor->GetModel()->GetAnimation() == animation ) {
size_t cyclenext = cyclepos + timestep_millis;
if( ( cyclepos <= animation->m_ActionPos ) &&
( cyclenext > animation->m_ActionPos ) )
{
// Actually execute the action script in the sim frame
// that contains the animation's 'action point' (as
// specified by the artist that created it)
if( !DispatchEvent( contactEvent ) )
{
// The script is cancelling the attack/gather action.
// Cancel the animation (will probably cause a graphical
// glitch, but can't be helped)
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim );
}
}
cyclepos = cyclenext;
return( false ); return( false );
}
// Just transitioned? No animation? (=> melee just finished) Play walk. // Just transitioned? No animation? (=> melee just finished) Play walk.
if( m_transition || !m_actor->GetModel()->GetAnimation() ) if( m_transition || !m_actor->GetModel()->GetAnimation() )
@ -396,8 +416,12 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
m_ahead = delta.normalize(); m_ahead = delta.normalize();
} }
if( DispatchEvent( contactEvent ) && m_actor ) // Start the animation. Actual damage/gather will be done in a
m_actor->GetModel()->SetAnimation( animation, true ); // few hundred msec, at the 'action point' of the animation we're
// now setting.
m_actor->GetModel()->SetAnimation( animation, true );
cyclepos = 0;
return( false ); return( false );
} }

View File

@ -35,10 +35,10 @@ public:
} }
static void ScriptingInit() static void ScriptingInit()
{ {
AddClassProperty<float>( L"crush", &CDamageType::m_Crush ); AddProperty<float>( L"crush", &CDamageType::m_Crush );
AddClassProperty<float>( L"hack", &CDamageType::m_Hack ); AddProperty<float>( L"hack", &CDamageType::m_Hack );
AddClassProperty<float>( L"pierce", &CDamageType::m_Pierce ); AddProperty<float>( L"pierce", &CDamageType::m_Pierce );
AddClassProperty<float>( L"typeless", &CDamageType::m_Typeless ); AddProperty<float>( L"typeless", &CDamageType::m_Typeless );
CJSObject<CDamageType>::ScriptingInit( "DamageType", Construct, 3 ); CJSObject<CDamageType>::ScriptingInit( "DamageType", Construct, 3 );
} }
static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )

View File

@ -2,50 +2,52 @@
#include "EventHandlers.h" #include "EventHandlers.h"
#include "Entity.h" #include "Entity.h"
CEventAttack::CEventAttack( CEntity* target ) : CScriptEvent( L"attack", true, EVENT_ATTACK ) CEventAttack::CEventAttack( CEntity* target ) : CScriptEvent( L"attack", EVENT_ATTACK, true )
{ {
m_target = target; m_target = target;
AddProperty( L"target", &m_target ); AddLocalProperty( L"target", &m_target );
} }
CEventGather::CEventGather( CEntity* target ) : CScriptEvent( L"gather", true, EVENT_GATHER ) CEventGather::CEventGather( CEntity* target ) : CScriptEvent( L"gather", EVENT_GATHER, true )
{ {
m_target = target; m_target = target;
AddProperty( L"target", &m_target ); AddLocalProperty( L"target", &m_target );
} }
CEventDamage::CEventDamage( CEntity* inflictor, CDamageType* damage ) : CScriptEvent( L"takesDamage", true, EVENT_DAMAGE ) CEventDamage::CEventDamage( CEntity* inflictor, CDamageType* damage ) : CScriptEvent( L"takesDamage", EVENT_DAMAGE, true )
{ {
m_inflictor = inflictor; m_inflictor = inflictor;
m_damage = damage; m_damage = damage;
AddReadOnlyProperty( L"inflictor", &m_inflictor ); AddLocalProperty( L"inflictor", &m_inflictor, true );
AddProperty( L"damage", &m_damage ); AddLocalProperty( L"damage", &m_damage );
} }
CEventTargetChanged::CEventTargetChanged( CEntity* target ) : CScriptEvent( L"targetChanged", false, EVENT_TARGET_CHANGED ) CEventTargetChanged::CEventTargetChanged( CEntity* target ) : CScriptEvent( L"targetChanged", EVENT_TARGET_CHANGED, false )
{ {
m_target = target; m_target = target;
m_defaultAction = -1; m_defaultAction = -1;
AddReadOnlyProperty( L"target", &m_target ); m_defaultCursor = L"arrow-default";
AddProperty( L"defaultAction", &m_defaultAction ); AddLocalProperty( L"target", &m_target, true );
AddLocalProperty( L"defaultAction", &m_defaultAction );
AddLocalProperty( L"defaultCursor", &m_defaultCursor );
} }
CEventPrepareOrder::CEventPrepareOrder( CEntity* target, int orderType ) : CScriptEvent( L"prepareOrder", true, EVENT_PREPARE_ORDER ) CEventPrepareOrder::CEventPrepareOrder( CEntity* target, int orderType ) : CScriptEvent( L"prepareOrder", EVENT_PREPARE_ORDER, true )
{ {
m_target = target; m_target = target;
m_orderType = orderType; m_orderType = orderType;
AddReadOnlyProperty( L"target", &m_target ); AddLocalProperty( L"target", &m_target, true );
AddReadOnlyProperty( L"orderType", &m_orderType ); AddLocalProperty( L"orderType", &m_orderType, true );
} }
CEventOrderTransition::CEventOrderTransition( int orderPrevious, int orderCurrent, CEntity*& target, CVector3D& worldPosition ) : CScriptEvent( L"orderTransition", true, EVENT_ORDER_TRANSITION ) CEventOrderTransition::CEventOrderTransition( int orderPrevious, int orderCurrent, CEntity*& target, CVector3D& worldPosition ) : CScriptEvent( L"orderTransition", EVENT_ORDER_TRANSITION, true )
{ {
m_orderPrevious = orderPrevious; m_orderPrevious = orderPrevious;
m_orderCurrent = orderCurrent; m_orderCurrent = orderCurrent;
m_target = &target; m_target = &target;
m_worldPosition = &worldPosition; m_worldPosition = &worldPosition;
AddReadOnlyProperty( L"orderPrevious", &m_orderPrevious ); AddLocalProperty( L"orderPrevious", &m_orderPrevious, true );
AddProperty( L"orderCurrent", &m_orderCurrent ); AddLocalProperty( L"orderCurrent", &m_orderCurrent );
AddProperty( L"target", m_target ); AddLocalProperty( L"target", m_target );
AddProperty( L"position", m_worldPosition ); AddLocalProperty( L"position", m_worldPosition );
} }

View File

@ -10,41 +10,16 @@
class CDamageType; class CDamageType;
enum EEventType
{
EVENT_INITIALIZE,
EVENT_TICK,
EVENT_ATTACK,
EVENT_GATHER,
EVENT_DAMAGE,
EVENT_TARGET_CHANGED,
EVENT_PREPARE_ORDER,
EVENT_ORDER_TRANSITION,
EVENT_LAST,
};
static const wchar_t* EventNames[] =
{
/* EVENT_INITIALIZE */ L"onInitialize",
/* EVENT_TICK */ L"onTick",
/* EVENT_ATTACK */ L"onAttack", /* This unit is the one doing the attacking... */
/* EVENT_GATHER */ L"onGather", /* This unit is the one doing the gathering... */
/* EVENT_DAMAGE */ L"onTakesDamage",
/* EVENT_TARGET_CHANGED */ L"onTargetChanged", /* If this unit is selected and the mouseover object changes */
/* EVENT_PREPARE_ORDER */ L"onPrepareOrder", /* To check if a unit can execute a given order */
/* EVENT_ORDER_TRANSITION */ L"onOrderTransition" /* When we change orders (sometimes...) */
};
class CEventInitialize : public CScriptEvent class CEventInitialize : public CScriptEvent
{ {
public: public:
CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {} CEventInitialize() : CScriptEvent( L"initialize", EVENT_INITIALIZE, false ) {}
}; };
class CEventTick : public CScriptEvent class CEventTick : public CScriptEvent
{ {
public: public:
CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {} CEventTick() : CScriptEvent( L"tick", EVENT_TICK, false ) {}
}; };
class CEventAttack : public CScriptEvent class CEventAttack : public CScriptEvent
@ -74,6 +49,7 @@ class CEventTargetChanged : public CScriptEvent
CEntity* m_target; CEntity* m_target;
public: public:
int m_defaultAction; int m_defaultAction;
CStrW m_defaultCursor;
CEventTargetChanged( CEntity* target ); CEventTargetChanged( CEntity* target );
}; };

View File

@ -121,9 +121,9 @@ CJSProgressTimer::CJSProgressTimer( double Max, double Increment, JSFunction* Ca
void CJSProgressTimer::ScriptingInit() void CJSProgressTimer::ScriptingInit()
{ {
AddClassProperty( L"max", &CJSProgressTimer::m_Max ); AddProperty( L"max", &CJSProgressTimer::m_Max );
AddClassProperty( L"current", &CJSProgressTimer::m_Current ); AddProperty( L"current", &CJSProgressTimer::m_Current );
AddClassProperty( L"increment", &CJSProgressTimer::m_Increment ); AddProperty( L"increment", &CJSProgressTimer::m_Increment );
CJSObject<CJSProgressTimer>::ScriptingInit( "ProgressTimer", Construct, 2 ); CJSObject<CJSProgressTimer>::ScriptingInit( "ProgressTimer", Construct, 2 );
} }

View File

@ -76,15 +76,15 @@ JSObject* CScriptObject::GetFunctionObject()
// Executes a script attached to a JS object. // Executes a script attached to a JS object.
// Returns false if the script isn't defined, if the script can't be executed, // Returns false if the script isn't defined, if the script can't be executed,
// otherwise true. Script return value is in rval. // otherwise true. Script return value is in rval.
bool CScriptObject::Run( JSObject* Context, jsval* rval ) bool CScriptObject::Run( JSObject* Context, jsval* rval, uintN argc, jsval* argv )
{ {
if( !Function ) if( !Function )
return( false ); return( false );
return( JS_TRUE == JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, 0, NULL, rval ) ); return( JS_TRUE == JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, argc, argv, rval ) );
} }
// This variant casts script return value to a boolean, and passes it back. // This variant casts script return value to a boolean, and passes it back.
bool CScriptObject::Run( JSObject* Context ) bool CScriptObject::Run( JSObject* Context, uintN argc, jsval* argv )
{ {
jsval Temp; jsval Temp;
if( !Run( Context, &Temp ) ) if( !Run( Context, &Temp ) )
@ -95,11 +95,13 @@ bool CScriptObject::Run( JSObject* Context )
// Treat this as an event handler and dispatch an event to it. Return !evt->m_cancelled, as a convenience. // Treat this as an event handler and dispatch an event to it. Return !evt->m_cancelled, as a convenience.
bool CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt ) bool CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt )
{ {
jsval Temp;
jsval EventObject = OBJECT_TO_JSVAL( evt->GetScript() );
if( Function ) if( Function )
{
jsval Temp;
jsval EventObject = OBJECT_TO_JSVAL( evt->GetScript() );
JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, 1, &EventObject, &Temp ); JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, 1, &EventObject, &Temp );
return( !evt->m_Cancelled ); }
return( evt->m_Blocked );
} }
void CScriptObject::Compile( CStrW FileNameTag, CStrW FunctionBody ) void CScriptObject::Compile( CStrW FileNameTag, CStrW FunctionBody )

View File

@ -37,15 +37,19 @@ public:
return( Function != NULL ); return( Function != NULL );
} }
inline operator bool() { return( Function != NULL ); }
inline bool operator!() { return( !Function ); }
inline bool operator==( const CScriptObject& compare ) { return( Function == compare.Function ); }
// JSObject wrapping the function if it's defined, NULL if it isn't. // JSObject wrapping the function if it's defined, NULL if it isn't.
JSObject* GetFunctionObject(); JSObject* GetFunctionObject();
// Executes a script attached to a JS object. // Executes a script attached to a JS object.
// Returns false if the script isn't defined, if the script can't be executed, // Returns false if the script isn't defined, if the script can't be executed,
// otherwise true. Script return value is in rval. // otherwise true. Script return value is in rval.
bool Run( JSObject* Context, jsval* rval ); bool Run( JSObject* Context, jsval* rval, uintN argc = 0, jsval* argv = NULL );
// This variant casts script return value to a boolean, and passes it back. // This variant casts script return value to a boolean, and passes it back.
bool Run( JSObject* Context ); bool Run( JSObject* Context, uintN argc = 0, jsval* argv = NULL );
// Treat this as an event handler and dispatch an event to it. // Treat this as an event handler and dispatch an event to it.
bool DispatchEvent( JSObject* Context, CScriptEvent* evt ); bool DispatchEvent( JSObject* Context, CScriptEvent* evt );