diff --git a/binaries/data/mods/official/entities/template_entity_script.js b/binaries/data/mods/official/entities/template_entity_script.js index d17a66551a..c074c69f23 100644 --- a/binaries/data/mods/official/entities/template_entity_script.js +++ b/binaries/data/mods/official/entities/template_entity_script.js @@ -22,13 +22,13 @@ function entity_event_gather( evt ) { if( evt.target.traits.supply.curr <= gather_amt ) { - gather_amt = evt.target.traits.supply.curr; - evt.target.kill(); + gather_amt = evt.target.traits.supply.curr; + evt.target.kill(); } console.write( evt.target.traits.supply.type); console.write( evt.target.traits.supply.type.toString().toUpperCase() ); 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) // yields false. ToString converts them to their actual values (i.e. the four character // string) first. - + evt.defaultAction = ORDER_GOTO; + evt.defaultCursor = "arrow-default"; if( evt.target ) { if( this.actions.attack && - ( evt.target.traits.id.civ_code != "gaia" ) && - ( evt.target.traits.id.civ_code.toString() != this.traits.id.civ_code.toString() ) ) - evt.defaultAction = ORDER_ATTACK; + ( evt.target.player != gaiaPlayer ) && + ( evt.target.player != this.player ) ) + { + evt.defaultAction = ORDER_ATTACK; + evt.defaultCursor = "action-attack"; + } if( this.actions.gather && evt.target.traits.supply && - this.actions.gather[evt.target.traits.supply.type] && - ( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) ) + this.actions.gather[evt.target.traits.supply.type] && + ( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) ) + { 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 ) @@ -161,11 +173,8 @@ function entity_event_prepareorder( evt ) evt.preventDefault(); break; 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 || - !evt.target || - ( evt.target.traits.id.civ_code.toString() == this.traits.id.civ_code.toString() ) ) + !evt.target ) evt.preventDefault(); break; case ORDER_GATHER: @@ -191,10 +200,11 @@ function entity_add_create_queue( template, list, tab ) comboTemplate.tab = tab; // 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( !this.actions.create.progress || !this.actions.create.progress.valueOf() ) + if( !this.actions.create.progress ) { console.write( "Starting work on (unqueued) ", template.tag ); // 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 // 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 // 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 // construction - serves them right for not paying attention to the land // 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 ) - return; - - console.write( "Created: ", template.tag ); - - // Entities start under Gaia control - make the controller - // the same as our controller - created.player = this.player; + // Above shouldn't ever fail, but just in case... + if( created ) + { + console.write( "Created: ", template.tag ); + // Entities start under Gaia control - make the controller + // the same as our controller + created.player = this.player; + } + } // If there's something else in the build queue... - if( this.actions.create.queue.valueOf().length > 0 ) + if( this.actions.create.queue.length > 0 ) { // 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 ); this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this ) } diff --git a/binaries/data/mods/official/gui/test/functions_pregame_load.js b/binaries/data/mods/official/gui/test/functions_pregame_load.js index da41a32e0b..8f787883ad 100644 --- a/binaries/data/mods/official/gui/test/functions_pregame_load.js +++ b/binaries/data/mods/official/gui/test/functions_pregame_load.js @@ -98,7 +98,7 @@ function setupSession() CreateResources(); // Start refreshing the session controls. - setInterval( getObjectInfo, 1, 1000 ); + setInterval( getObjectInfo, 1, 100 ); } // ==================================================================== diff --git a/binaries/data/mods/official/gui/test/functions_session_status_orb.js b/binaries/data/mods/official/gui/test/functions_session_status_orb.js index 31e8b39180..1aa42d1be1 100755 --- a/binaries/data/mods/official/gui/test/functions_session_status_orb.js +++ b/binaries/data/mods/official/gui/test/functions_session_status_orb.js @@ -463,31 +463,41 @@ function PressCommandButton(GUIObject, list, tab) function UpdateCommandButtons() { - // 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++; - - // 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++) + if( shouldUpdateStat( "actions.create.list" ) ) { - 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"); + // Everything in this block is tied to properties in + // actions.create.list, the above check should limit the + // 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. @@ -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() { // Update heading. - GUIObject = getGUIObjectByName("SN_STATUS_PANE_HEADING"); - 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; - + if( shouldUpdateStat( "player" ) || shouldUpdateStat( "traits.id.civ" ) ) + { + GUIObject = getGUIObjectByName("SN_STATUS_PANE_HEADING"); + 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. - GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1"); - 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) + if( shouldUpdateStat( "traits.id" ) ) { GUIObject = getGUIObjectByName("SN_STATUS_PANE_NAME1"); - GUIObject.caption += selection[0].traits.id.ranked + "\n"; - } - else{ - if (selection[0].traits.id.specific) + 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.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"; + } } } - - // 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) + if( shouldUpdateStat( "traits.id.icon" ) ) { - GUIObject.sprite = "ui_icon_sheet_statistic"; - GUIObject.cell_id = stat_rank1 + (selection[0].traits.up.rank-2); + // 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); } - else - GUIObject.sprite = ""; - - // Update hitpoints - if (selection[0].traits.health.curr && selection[0].traits.health.max) + if( shouldUpdateStat( "traits.up.rank" ) ) { - 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; - } - - // 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) + // Update rank. + GUIObject = getGUIObjectByName("SN_STATUS_PANE_ICON_RANK"); + if (selection[0].traits.up.rank > 1) { - 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. - 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] '; + 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; } } + 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 + if( shouldUpdateStat( "actions.attack" ) ) + { 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; 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) + '%'; else getGUIObjectByName("SN_STATUS_PANE_STAT6").caption = ""; - + } // Update Armour & Other stats + if( shouldUpdateStat( "traits.armour" ) ) + { 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; 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) 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 = ""; - - if (selection[0].actions.move && selection[0].actions.move.speed) - 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].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 = ""; - + } + if( shouldUpdateStat( "actions.move" ) ) + { + if (selection[0].actions.move && selection[0].actions.move.speed) + getGUIObjectByName("SN_STATUS_PANE_STAT11").caption = '[icon="icon_statistic_speed"]' + selection[0].actions.move.speed; + else getGUIObjectByName("SN_STATUS_PANE_STAT11").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 getGUIObjectByName("session_status_orb").hidden = false; // Update Command Buttons. UpdateCommandButtons(); + + resetUpdateVars(); } diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 12497da7f8..cd3fe11f11 100755 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -199,7 +199,7 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result) ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildAnimation: load raw animation frame animation from given file, and build a // 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); if (!def) return 0; @@ -207,6 +207,9 @@ CSkeletonAnim* CModel::BuildAnimation(const char* filename,float speed) CSkeletonAnim* anim=new CSkeletonAnim; anim->m_AnimDef=def; 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(); InvalidateBounds(); diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 10bb9a762c..55cbaad3ed 100755 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -102,7 +102,7 @@ public: // load raw animation frame animation from given file, and build a // 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 void AddProp(SPropPoint* point,CModel* model); diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 20a52b0771..251755c75a 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -159,6 +159,7 @@ bool CObjectBase::Load(const char* filename) AT(file); AT(name); AT(speed); + AT(actionpos); AT(attachpoint); AT(actor); AT(frequency); @@ -220,6 +221,11 @@ bool CObjectBase::Load(const char* filename) anim.m_Speed = CStr(ae.Value).ToInt() / 100.f; 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 ; // unrecognised element } diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index 52a1db337a..358923561f 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -13,7 +13,7 @@ public: struct Anim { // 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 CStr m_AnimName; @@ -21,6 +21,8 @@ public: CStr m_FileName; // animation speed, as specified in XML actor file 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 CSkeletonAnim* m_AnimData; }; diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index 7366af7b56..44503a0cb8 100755 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -144,7 +144,7 @@ bool CObjectEntry::BuildRandomVariant(CObjectBase::variation_key& vars, CObjectB if (m_Animations[t].m_FileName.Length() > 0) { 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(); diff --git a/source/graphics/SkeletonAnim.h b/source/graphics/SkeletonAnim.h index 924738dbf3..d0f8cde780 100755 --- a/source/graphics/SkeletonAnim.h +++ b/source/graphics/SkeletonAnim.h @@ -23,6 +23,8 @@ public: CSkeletonAnimDef* m_AnimDef; // speed at which this animation runs 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 CBound m_ObjectBounds; }; diff --git a/source/main.cpp b/source/main.cpp index b6a690ca2b..6f752a804b 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -58,6 +58,7 @@ #include "StringConvert.h" #include "scripting/ScriptingHost.h" +#include "scripting/GameEvents.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" @@ -789,7 +790,7 @@ TIMER(InitScripting) PlayerCollection::Init( "PlayerCollection" ); CDamageType::ScriptingInit(); - CJSPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. + CJSComplexPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. CScriptEvent::ScriptingInit(); CJSProgressTimer::ScriptingInit(); @@ -801,6 +802,8 @@ TIMER(InitScripting) JSI_Camera::init(); JSI_Console::init(); + + new CGameEvents; } @@ -1069,6 +1072,7 @@ static void Shutdown() // delete &g_EntityManager; delete &g_GameAttributes; + delete &g_JSGameEvents; delete &g_EntityTemplateCollection; diff --git a/source/ps/GameAttributes.cpp b/source/ps/GameAttributes.cpp index 6528baaf2b..dca08563c2 100644 --- a/source/ps/GameAttributes.cpp +++ b/source/ps/GameAttributes.cpp @@ -21,8 +21,8 @@ CPlayerSlot::CPlayerSlot(int slotID, CPlayer *pPlayer): ); //AddProperty(L"session", (GetFn)&CPlayerSlot::JSI_GetSession); - AddReadOnlyProperty(L"session", &m_pSession); - AddReadOnlyProperty(L"player", &m_pPlayer); + AddLocalProperty(L"session", &m_pSession, true ); + AddLocalProperty(L"player", &m_pPlayer, true ); } CPlayerSlot::~CPlayerSlot() @@ -34,7 +34,7 @@ void CPlayerSlot::ScriptingInit() AddMethod("assignToSession", 1); AddMethod("assignLocal", 0); AddMethod("assignOpen", 0); - AddClassProperty(L"assignment", (GetFn)&CPlayerSlot::JSI_GetAssignment); + AddProperty(L"assignment", (GetFn)&CPlayerSlot::JSI_GetAssignment); // AddMethod("assignAI", ); CJSObject::ScriptingInit("PlayerSlot"); @@ -258,7 +258,7 @@ void CGameAttributes::ScriptingInit() PlayerSlotArray_JS::Construct, 0, NULL, NULL, NULL, NULL); AddMethod("getOpenSlot", 0); - AddClassProperty(L"slots", (GetFn)&CGameAttributes::JSI_GetPlayerSlots); + AddProperty(L"slots", (GetFn)&CGameAttributes::JSI_GetPlayerSlots); CJSObject::ScriptingInit("GameAttributes"); } diff --git a/source/ps/Interact.cpp b/source/ps/Interact.cpp index 8b6729c76f..37438d49c7 100755 --- a/source/ps/Interact.cpp +++ b/source/ps/Interact.cpp @@ -11,6 +11,7 @@ #include "BoundingObjects.h" #include "Unit.h" #include "Model.h" +#include "scripting/GameEvents.h" extern CConsole* g_Console; extern int g_mouse_x, g_mouse_y; @@ -120,10 +121,12 @@ void CSelectedEntities::renderOverlays() glDisable( GL_BLEND ); } + /* glLoadIdentity(); glTranslatef( (float)( g_mouse_x + 16 ), (float)( g_Renderer.GetHeight() - g_mouse_y - 8 ), 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); + switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: @@ -139,6 +142,7 @@ void CSelectedEntities::renderOverlays() glwprintf( L"Gather" ); break; } + */ glDisable( GL_TEXTURE_2D ); glPopMatrix(); @@ -350,6 +354,14 @@ CVector3D CSelectedEntities::getGroupPosition( i8 groupid ) void CSelectedEntities::update() { + static std::vector lastSelection; + + if( !( m_selected == lastSelection ) ) + { + g_JSGameEvents.FireSelectionChanged( m_selectionChanged ); + lastSelection = m_selected; + } + if( m_selectionChanged || g_Mouseover.m_targetChanged ) { // Can't order anything off the map @@ -362,6 +374,8 @@ void CSelectedEntities::update() // Quick count to see which is the modal default order. int defaultPoll[CEntityOrder::ORDER_LAST]; + std::map defaultCursor[CEntityOrder::ORDER_LAST]; + int t, vote; for( t = 0; t < CEntityOrder::ORDER_LAST; t++ ) defaultPoll[t] = 0; @@ -369,9 +383,15 @@ void CSelectedEntities::update() std::vector::iterator 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 ) ) + { defaultPoll[vote]++; + defaultCursor[vote][evt.m_defaultCursor]++; + } } vote = -1; @@ -381,16 +401,17 @@ void CSelectedEntities::update() vote = t; } + std::map::iterator itv; m_contextOrder = vote; - switch( m_contextOrder ) - { - case CEntityOrder::ORDER_ATTACK_MELEE: - g_CursorName = "action-attack"; - break; - default: - g_CursorName = "arrow-default"; - break; - } + + // Now find the most appropriate cursor + t = 0; + for( itv = defaultCursor[vote].begin(); itv != defaultCursor[vote].end(); itv++ ) + if( itv->second > t ) + { + t = itv->second; + g_CursorName = itv->first; + } m_selectionChanged = false; g_Mouseover.m_targetChanged = false; diff --git a/source/ps/Network/Client.cpp b/source/ps/Network/Client.cpp index b1fcc03062..e6a01150fd 100755 --- a/source/ps/Network/Client.cpp +++ b/source/ps/Network/Client.cpp @@ -22,13 +22,13 @@ CNetClient::CServerSession::CServerSession(int sessionID, const CStrW &name): m_Name(name) { ONCE( ScriptingInit(); ); - - AddReadOnlyProperty(L"id", &m_SessionID); - AddReadOnlyProperty(L"name", &m_Name); } void CNetClient::CServerSession::ScriptingInit() { + AddProperty(L"id", &CNetClient::CServerSession::m_SessionID, true); + AddProperty(L"name", &CNetClient::CServerSession::m_Name, true); + CJSObject::ScriptingInit("NetClient_ServerSession"); } @@ -57,18 +57,18 @@ void CNetClient::ScriptingInit() - AddClassProperty(L"onStartGame", &CNetClient::m_OnStartGame); - AddClassProperty(L"onChat", &CNetClient::m_OnChat); - AddClassProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete); - AddClassProperty(L"onDisconnect", &CNetClient::m_OnDisconnect); - AddClassProperty(L"onClientConnect", &CNetClient::m_OnClientConnect); - AddClassProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect); + AddProperty(L"onStartGame", &CNetClient::m_OnStartGame); + AddProperty(L"onChat", &CNetClient::m_OnChat); + AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete); + AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect); + AddProperty(L"onClientConnect", &CNetClient::m_OnClientConnect); + AddProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect); - AddClassProperty(L"password", &CNetClient::m_Password); - AddClassProperty(L"playerName", &CNetClient::m_Name); + AddProperty(L"password", &CNetClient::m_Password); + AddProperty(L"playerName", &CNetClient::m_Name); - AddClassProperty(L"sessions", &CNetClient::m_JSI_ServerSessions); + AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions); CJSMap::ScriptingInit("NetClient_SessionMap"); CJSObject::ScriptingInit("NetClient"); } diff --git a/source/ps/Network/JSEvents.h b/source/ps/Network/JSEvents.h index 36b6e95180..8bd160366e 100644 --- a/source/ps/Network/JSEvents.h +++ b/source/ps/Network/JSEvents.h @@ -18,7 +18,7 @@ class CStartGameEvent: public CScriptEvent { public: 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: 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_Message(message) { - AddReadOnlyProperty(L"sender", &m_Sender); - AddReadOnlyProperty(L"message", &m_Message); + AddLocalProperty(L"sender", &m_Sender, true); + AddLocalProperty(L"message", &m_Message, true); } }; @@ -45,12 +45,12 @@ class CConnectCompleteEvent: public CScriptEvent public: 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_Success(success) { - AddReadOnlyProperty(L"message", &m_Message); - AddReadOnlyProperty(L"success", &m_Success); + AddLocalProperty(L"message", &m_Message, true); + AddLocalProperty(L"success", &m_Success, true); } }; @@ -60,10 +60,10 @@ class CDisconnectEvent: public CScriptEvent public: CDisconnectEvent(CStrW message): - CScriptEvent(L"disconnect", false, NET_JS_EVENT_DISCONNECT), + CScriptEvent(L"disconnect", NET_JS_EVENT_DISCONNECT, false), m_Message(message) { - AddReadOnlyProperty(L"message", &m_Message); + AddLocalProperty(L"message", &m_Message, true); } }; @@ -76,15 +76,15 @@ class CClientConnectDisconnectCommon: public CScriptEvent public: CClientConnectDisconnectCommon(const wchar_t* eventName, int eventType, 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_Name(name), m_pSession(pSession) { - AddReadOnlyProperty(L"id", &m_SessionID); - AddReadOnlyProperty(L"name", &m_Name); + AddLocalProperty(L"id", &m_SessionID, true); + AddLocalProperty(L"name", &m_Name, true); if (m_pSession) - AddReadOnlyProperty(L"session", &m_pSession); + AddLocalProperty(L"session", &m_pSession, true); } }; diff --git a/source/ps/Network/Server.cpp b/source/ps/Network/Server.cpp index a97f868b92..f105a688c3 100755 --- a/source/ps/Network/Server.cpp +++ b/source/ps/Network/Server.cpp @@ -60,18 +60,6 @@ CNetServer::CNetServer(CGame *pGame, CGameAttributes *pGameAttribs): 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->SetPlayerUpdateCallback(PlayerAttributeUpdate, this); m_pGameAttributes->SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCallback, this); @@ -102,6 +90,18 @@ void CNetServer::ScriptingInit() AddMethod("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::ScriptingInit("NetServer"); } diff --git a/source/ps/Network/ServerSession.cpp b/source/ps/Network/ServerSession.cpp index 4911f260cd..a72c691677 100644 --- a/source/ps/Network/ServerSession.cpp +++ b/source/ps/Network/ServerSession.cpp @@ -19,9 +19,6 @@ CNetServerSession::CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, ONCE( ScriptingInit(); ); - - AddProperty(L"id", &m_ID); - AddProperty(L"name", &m_Name); } CNetServerSession::~CNetServerSession() @@ -209,6 +206,9 @@ void CNetServerSession::ScriptingInit() AddMethod("close", 0); CJSObject::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) diff --git a/source/ps/Player.cpp b/source/ps/Player.cpp index 934f5adbe9..50a4617097 100755 --- a/source/ps/Player.cpp +++ b/source/ps/Player.cpp @@ -35,12 +35,12 @@ void CPlayer::ScriptingInit() AddMethod( "toString", 0 ); AddMethod( "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... // AddClassProperty( L"name", &CPlayer::m_Name ); // AddClassProperty( L"colour", &CPlayer::m_Colour ); - AddClassProperty( L"controlled", (IJSObject::GetFn)JSI_GetControlledEntities ); + AddProperty( L"controlled", (IJSObject::GetFn)JSI_GetControlledEntities ); CJSObject::ScriptingInit( "Player" ); } diff --git a/source/ps/Profile.cpp b/source/ps/Profile.cpp index f903620924..122d592721 100644 --- a/source/ps/Profile.cpp +++ b/source/ps/Profile.cpp @@ -173,7 +173,7 @@ bool CProfileNode::Return() 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"callsPerFrame", &CProfileNode::calls_frame_last ); diff --git a/source/ps/scripting/JSInterface_Selection.cpp b/source/ps/scripting/JSInterface_Selection.cpp index 99f0505fec..16c074a012 100755 --- a/source/ps/scripting/JSInterface_Selection.cpp +++ b/source/ps/scripting/JSInterface_Selection.cpp @@ -38,7 +38,7 @@ JSBool JSI_Selection::setSelection( JSContext* context, JSObject* globalObject, for( it = Info->m_Data->begin(); it < Info->m_Data->end(); it++ ) g_Selection.addSelection( *it ); - + return( JS_TRUE ); } diff --git a/source/scripting/DOMEvent.cpp b/source/scripting/DOMEvent.cpp index ee25d03da6..de39a3d88e 100755 --- a/source/scripting/DOMEvent.cpp +++ b/source/scripting/DOMEvent.cpp @@ -1,19 +1,157 @@ #include "precompiled.h" #include "DOMEvent.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 ); - AddReadOnlyProperty( L"type", &m_Type ); - AddReadOnlyProperty( L"cancelable", &m_Cancelable ); - AddReadOnlyProperty( L"timeStamp", &m_Timestamp ); + HandlerMap::iterator it; + for( it = m_Handlers_name.begin(); it != m_Handlers_name.end(); it++ ) + delete( it->second ); +} + +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( 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( 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() { AddMethod( "toString", 0 ); AddMethod( "preventDefault", 0 ); + AddMethod( "cancel", 0 ); + AddMethod( "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::ScriptingInit( "Event" ); } @@ -25,6 +163,13 @@ jsval CScriptEvent::PreventDefault( JSContext* cx, uintN argc, jsval* argv ) 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 ) { wchar_t buffer[256]; diff --git a/source/scripting/DOMEvent.h b/source/scripting/DOMEvent.h index d6ec1ec1a0..ae6cd31536 100755 --- a/source/scripting/DOMEvent.h +++ b/source/scripting/DOMEvent.h @@ -9,6 +9,56 @@ #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 HandlerList; + HandlerList m_Handlers_id[EVENT_LAST]; + typedef STL_HASH_MULTIMAP HandlerMap; + HandlerMap m_Handlers_name; + typedef std::pair 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 { public: @@ -20,10 +70,10 @@ public: }; // Target (currently unused) - // EventTarget* m_Target; + IEventTarget* m_Target; // Listening object currently being processed (currently unused) - // EventTarget* m_CurrentTarget; + IEventTarget* m_CurrentTarget; // Phase type (currently unused) // EPhaseType m_EventPhase; @@ -34,6 +84,9 @@ public: // Can be cancelled (default actions prevented) bool m_Cancelable; + // Can be blocked (prevented from propogating along the handler chain) + bool m_Blockable; + // Timestamp (milliseconds since epoch (start of game?)) i32 m_Timestamp; @@ -46,13 +99,17 @@ public: // Has been 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 PreventDefault( JSContext* cx, uintN argc, jsval* argv ); + jsval StopPropagation( JSContext* cx, uintN argc, jsval* argv ); 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(); }; diff --git a/source/scripting/JSConversions.h b/source/scripting/JSConversions.h index 19e906e3e9..d81a2a8f03 100755 --- a/source/scripting/JSConversions.h +++ b/source/scripting/JSConversions.h @@ -62,13 +62,6 @@ template bool ToPrimitive( JSContext* cx, jsval v, T*& Storage ) return( true ); } -/* -template JSObject* ToScript( T** Native ) -{ - return( ToScript( *Native ) ); -} -*/ - template inline T ToPrimitive( JSContext* cx, jsval v ) { T Temp; ToPrimitive( cx, v, Temp ); return( Temp ); } template inline T ToPrimitive( jsval v ) { return( ToPrimitive( g_ScriptingHost.GetContext(), v ) ); } diff --git a/source/scripting/ScriptCustomTypes.cpp b/source/scripting/ScriptCustomTypes.cpp index 8c32544090..6f06998a13 100755 --- a/source/scripting/ScriptCustomTypes.cpp +++ b/source/scripting/ScriptCustomTypes.cpp @@ -48,10 +48,10 @@ void SColour::SColourInit( float _r, float _g, float _b, float _a ) void SColour::ScriptingInit() { AddMethod( "toString", 0 ); - AddClassProperty( L"r", (float IJSObject::*)&SColour::r ); - AddClassProperty( L"g", (float IJSObject::*)&SColour::g ); - AddClassProperty( L"b", (float IJSObject::*)&SColour::b ); - AddClassProperty( L"a", (float IJSObject::*)&SColour::a ); + AddProperty( L"r", (float IJSObject::*)&SColour::r ); + AddProperty( L"g", (float IJSObject::*)&SColour::g ); + AddProperty( L"b", (float IJSObject::*)&SColour::b ); + AddProperty( L"a", (float IJSObject::*)&SColour::a ); CJSObject::ScriptingInit( "Colour", SColour::Construct, 3 ); } diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index 10db4d82cf..96d4fc4f3f 100755 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -12,6 +12,7 @@ #include "timer.h" #include "LightEnv.h" #include "MapWriter.h" +#include "GameEvents.h" #include "Game.h" #include "Network/Server.h" @@ -59,6 +60,8 @@ JSFunctionSpec ScriptFunctionTable[] = {"getGlobal", getGlobal, 0, 0, 0 }, {"getGUIGlobal", getGUIGlobal, 0, 0, 0 }, {"setCursor", setCursor, 1, 0, 0 }, + {"addGlobalHandler", AddGlobalHandler, 2, 0, 0 }, + {"removeGlobalHandler", RemoveGlobalHandler, 2, 0, 0 }, {"setCameraTarget", setCameraTarget, 1, 0, 0 }, {"startGame", startGame, 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 ); } +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 ) { CVector3D* target; diff --git a/source/scripting/ScriptGlue.h b/source/scripting/ScriptGlue.h index 4c76407b03..bd0b29e110 100755 --- a/source/scripting/ScriptGlue.h +++ b/source/scripting/ScriptGlue.h @@ -20,7 +20,11 @@ JSBool GetPlayerSet( JSContext* context, JSObject* globalObject, jsval id, jsval JSBool GetLocalPlayer( 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 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 JSFunc setCameraTarget; diff --git a/source/scripting/ScriptableObject.h b/source/scripting/ScriptableObject.h index 3279a60772..341bd5b896 100755 --- a/source/scripting/ScriptableObject.h +++ b/source/scripting/ScriptableObject.h @@ -4,36 +4,6 @@ // // 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 -// 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( 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 "JSConversions.h" @@ -41,72 +11,27 @@ #ifndef SCRIPTABLE_INCLUDED #define SCRIPTABLE_INCLUDED +#define ALLOW_NONSHARED_NATIVES + class IJSObject; class IJSProperty { public: - - bool m_AllowsInheritance; - 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() {} + virtual jsval Get( JSContext* cx, IJSObject* obj ) = 0; + virtual void Set( JSContext* cx, IJSObject* obj, jsval value ) = 0; }; 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: typedef STL_HASH_MAP PropertyTable; - typedef std::vector InheritorsList; - - // Used for freshen/update - typedef void (IJSObject::*NotifyFn)(); // Property getters and setters typedef jsval (IJSObject::*GetFn)(); - typedef void (IJSObject::*SetFn)( jsval ); + typedef void (IJSObject::*SetFn)( jsval value ); - // Properties of this object - 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 + // Return a pointer to a property, if it exists virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0; // Retrieve the value of a property (returning false if that property is not defined) @@ -121,263 +46,49 @@ public: template class CJSObject; -template class CJSPropertyAccessor -{ - T* m_Owner; - CStrW m_PropertyRoot; - template 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 JSClass CJSPropertyAccessor::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 class CJSSharedProperty : public IJSProperty +template class CJSProperty : public IJSProperty { 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: - CJSSharedProperty( T IJSObject::*Data, bool AllowsInheritance = false, IJSObject::NotifyFn Update = NULL, IJSObject::NotifyFn Freshen = NULL ) + CJSProperty( T IJSObject::*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 ) { - if( m_Freshen ) (owner->*m_Freshen)(); 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 ) { if( !ReadOnly ) - { - if( m_Freshen ) (owner->*m_Freshen)(); - if( ToPrimitive( cx, Value, owner->*m_Data ) ) - if( m_Update ) (owner->*m_Update)(); - } + ToPrimitive( cx, Value, owner->*m_Data ); } - }; -template class CJSProperty : public IJSProperty +#ifdef ALLOW_NONSHARED_NATIVES + +template class CJSNonsharedProperty : public IJSProperty { 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: - CJSProperty( T* Data, bool AllowsInheritance = false, IJSObject::NotifyFn Update = NULL, IJSObject::NotifyFn Freshen = NULL ) + CJSNonsharedProperty( T* Data ) { m_Data = Data; - m_AllowsInheritance = AllowsInheritance; - m_Update = Update; - m_Freshen = Freshen; - m_Intrinsic = true; } jsval Get( JSContext* cx, IJSObject* owner ) { - if( m_Freshen ) (owner->*m_Freshen)(); 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 ) { if( !ReadOnly ) - { - if( m_Freshen ) (owner->*m_Freshen)(); - if( ToPrimitive( cx, Value, *m_Data ) ) - if( m_Update ) (owner->*m_Update)(); - } - } - -}; - -class CJSReflector -{ - template friend class CJSObject; - JSObject* m_JSAccessor; -}; - -class CJSDynamicProperty : public IJSProperty -{ - template friend class CJSObject; - - JSObject* m_JSAccessor; -public: - CJSDynamicProperty() - { - m_JSAccessor = NULL; - m_Intrinsic = false; - m_Inherited = false; + ToPrimitive( cx, Value, *m_Data ); } }; -class CJSValProperty : public CJSDynamicProperty -{ - template 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)" ); - } -}; +#endif /* ALLOW_NONSHARED_NATIVES */ class CJSFunctionProperty : public IJSProperty { @@ -390,28 +101,59 @@ class CJSFunctionProperty : public IJSProperty public: CJSFunctionProperty( IJSObject::GetFn Getter, IJSObject::SetFn Setter ) { - m_Inherited = false; - m_Intrinsic = true; m_Getter = Getter; m_Setter = Setter; // Must at least be able to read 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 ) - (owner->*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)" ); + (obj->*m_Setter)( value ); } }; +class CJSValProperty : public IJSProperty +{ + template 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 @@ -432,14 +174,17 @@ public: template class CJSObject : public IJSObject { - typedef STL_HASH_MAP ReflectorTable; - + // This object JSObject* m_JS; - ReflectorTable m_Reflectors; - -public: - static JSClass JSI_class; +protected: + // The properties defined by the engine + static PropertyTable m_NativeProperties; +#ifdef ALLOW_NONSHARED_NATIVES + PropertyTable m_NonsharedProperties; +#endif + // Properties added by script + PropertyTable m_ScriptProperties; // Whether native code is responsible for managing this object. // Script constructors should clear this *BEFORE* creating a JS @@ -447,8 +192,45 @@ public: 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 - 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 ) { if( !ReadOnly ) @@ -459,29 +241,6 @@ public: { // Already exists 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 { @@ -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 + static void AddMethod( const char* Name, uintN MinArgs ) + { + JSFunctionSpec FnInfo = { Name, CNativeFunction::JSFunction, MinArgs, 0, 0 }; + m_Methods.push_back( FnInfo ); + } + template static void AddProperty( CStrW PropertyName, PropType T::*Native, bool PropReadOnly = ReadOnly ) + { + IJSProperty* prop; + if( PropReadOnly ) + { + prop = new CJSProperty( (PropType IJSObject::*)Native ); + } + else + prop = new CJSProperty( (PropType IJSObject::*)Native ); + m_NativeProperties[PropertyName] = prop; + } +#ifdef ALLOW_NONSHARED_NATIVES + template void AddLocalProperty( CStrW PropertyName, PropType* Native, bool PropReadOnly = ReadOnly ) + { + IJSProperty* prop; + if( PropReadOnly ) + { + prop = new CJSNonsharedProperty( Native ); + } + else + prop = new CJSNonsharedProperty( 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 ) { T* Instance = ToNative( cx, obj ); @@ -519,28 +379,6 @@ public: 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 ) { T* Instance = ToNative( cx, obj ); @@ -551,52 +389,14 @@ public: 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 std::vector m_Methods; - static PropertyTable m_IntrinsicProperties; - public: + // Standard constructors/destructors CJSObject() { - m_Parent = NULL; m_JS = NULL; m_EngineOwned = true; } @@ -607,163 +407,15 @@ public: void Shutdown() { PropertyTable::iterator it; - for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) - { - if( !it->second->m_Intrinsic ) - { - CJSDynamicProperty* extProp = (CJSValProperty*)it->second; - if( extProp->m_JSAccessor ) - { - CJSPropertyAccessor< CJSObject >* accessor = (CJSPropertyAccessor< CJSObject >*)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 ) ); - } - } + for( it = m_ScriptProperties.begin(); it != m_ScriptProperties.end(); it++ ) delete( it->second ); - } - - - ReflectorTable::iterator it_a; - for( it_a = m_Reflectors.begin(); it_a != m_Reflectors.end(); it_a++ ) - { - CJSPropertyAccessor< CJSObject >* accessor = (CJSPropertyAccessor< CJSObject >*)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 ); - } + m_ScriptProperties.clear(); ReleaseScriptObject(); - } - void SetBase( IJSObject* Parent ) - { - 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 ) ); +#ifdef ALLOW_NONSHARED_NATIVES + for( it = m_NonsharedProperties.begin(); it != m_NonsharedProperties.end(); it++ ) delete( it->second ); - m_Reflectors.erase( it ); - } - } - - 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 - static void AddMethod( const char* Name, uintN MinArgs ) - { - JSFunctionSpec FnInfo = { Name, CNativeFunction::JSFunction, MinArgs, 0, 0 }; - T::m_Methods.push_back( FnInfo ); - } - template static void AddClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) - { - T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty( (PropType IJSObject::*)Native, PropAllowInheritance, Update, Refresh ); - } - template static void AddReadOnlyClassProperty( CStrW PropertyName, PropType T::*Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) - { - T::m_IntrinsicProperties[PropertyName] = new CJSSharedProperty( (PropType IJSObject::*)Native, PropAllowInheritance, Update, Refresh ); - } - template void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) - { - m_Properties[PropertyName] = new CJSProperty( Native, PropAllowInheritance, Update, Refresh ); - } - template void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) - { - m_Properties[PropertyName] = new CJSProperty( Native, PropAllowInheritance, Update, Refresh ); + m_NonsharedProperties.clear(); +#endif } }; @@ -781,72 +433,8 @@ template JSPropertySpec CJSObject::JSI_p }; template std::vector CJSObject::m_Methods; -template typename CJSObject::PropertyTable CJSObject::m_IntrinsicProperties; -template bool CJSObject::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) -{ - 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 >::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 >::CreateAccessor( cx, this, PropertyName ); - JS_AddRoot( cx, &reflector->m_JSAccessor ); - m_Reflectors.insert( std::pair( PropertyName, reflector ) ); - *vp = OBJECT_TO_JSVAL( reflector->m_JSAccessor ); - } - else - *vp = OBJECT_TO_JSVAL( it->second->m_JSAccessor ); - } - } - return( true ); -} +template typename CJSObject::PropertyTable CJSObject::m_NativeProperties; #endif diff --git a/source/scripting/SynchedJSObject.h b/source/scripting/SynchedJSObject.h index 8532d14314..26172dbebf 100644 --- a/source/scripting/SynchedJSObject.h +++ b/source/scripting/SynchedJSObject.h @@ -114,8 +114,6 @@ struct CSynchedJSObjectBase m_Owner(owner), m_Update(update) { - m_AllowsInheritance = false; - m_Intrinsic = true; } }; @@ -140,7 +138,7 @@ protected: template void AddSynchedProperty(CStrW name, T *native, UpdateFn update=NULL) { ISynchedJSProperty *prop=new CSynchedJSProperty(name, native, this, update); - m_Properties[name]=prop; + m_NonsharedProperties[name]=prop; m_SynchedProperties[name]=prop; } }; diff --git a/source/simulation/BaseEntity.cpp b/source/simulation/BaseEntity.cpp index 05297a53ca..4bcff6b431 100755 --- a/source/simulation/BaseEntity.cpp +++ b/source/simulation/BaseEntity.cpp @@ -24,7 +24,10 @@ CBaseEntity::CBaseEntity() AddProperty( L"traits.corpse", &m_corpse ); 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 m_speed = m_turningRadius = m_meleeRange = m_meleeRangeMin = 0.0f; @@ -62,6 +65,7 @@ void CBaseEntity::loadBase() } SetBase( m_base ); + SetNextObject( m_base ); } 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. CStrW PropertyName = BasePropertyName + CStr8( XeroFile.getElementString( Source.getNodeName() ) ); - IJSProperty* Existing = HasProperty( PropertyName ); + IJSComplexProperty* Existing = HasProperty( PropertyName ); if( Existing ) { if( !Existing->m_Intrinsic ) @@ -264,11 +268,16 @@ void CBaseEntity::ScriptingInit() { AddMethod( "toString", 0 ); - CJSObject::ScriptingInit( "EntityTemplate" ); + CJSComplex::ScriptingInit( "EntityTemplate" ); } // Script-bound functions +JSObject* CBaseEntity::GetScriptExecContext( IEventTarget* target ) +{ + return( target->GetScriptExecContext( target ) ); +} + jsval CBaseEntity::ToString( JSContext* cx, uintN argc, jsval* argv ) { wchar_t buffer[256]; diff --git a/source/simulation/BaseEntity.h b/source/simulation/BaseEntity.h index add3edfe4d..d172fd83f4 100755 --- a/source/simulation/BaseEntity.h +++ b/source/simulation/BaseEntity.h @@ -21,13 +21,13 @@ #include "CStr.h" #include "ObjectEntry.h" -#include "scripting/ScriptableObject.h" +#include "scripting/ScriptableComplex.h" #include "BoundingObjects.h" #include "EventHandlers.h" #include "ScriptObject.h" #include "Xeromyces.h" -class CBaseEntity : public CJSObject +class CBaseEntity : public CJSComplex, public IEventTarget { public: CBaseEntity(); @@ -65,6 +65,9 @@ public: // 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 ); static void ScriptingInit(); diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index c3ba852bf6..b22dbce06b 100755 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -41,7 +41,10 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) AddProperty( L"player", &m_player ); 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. m_actor = NULL; @@ -102,6 +105,7 @@ void CEntity::loadBase() // Set up our instance data SetBase( m_base ); + SetNextObject( m_base ); 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() { m_orderQueue.clear(); @@ -346,13 +340,6 @@ void CEntity::pushOrder( CEntityOrder& 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 ) { CEventPrepareOrder evt( orderTarget, orderType ); @@ -624,7 +611,7 @@ void CEntity::ScriptingInit() AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase ); - CJSObject::ScriptingInit( "Entity", Construct, 2 ); + CJSComplex::ScriptingInit( "Entity", Construct, 2 ); } // Script constructor diff --git a/source/simulation/Entity.h b/source/simulation/Entity.h index 909e03ff03..d331870d6e 100755 --- a/source/simulation/Entity.h +++ b/source/simulation/Entity.h @@ -32,7 +32,7 @@ #define ENTITY_INCLUDED #include -#include "scripting/ScriptableObject.h" +#include "scripting/ScriptableComplex.h" #include "Player.h" #include "Vector2D.h" @@ -52,7 +52,7 @@ class CUnit; // TODO MT: Put this is /some/ sort of order... -class CEntity : public CJSObject +class CEntity : public CJSComplex, public IEventTarget { friend class CEntityManager; private: @@ -96,22 +96,15 @@ public: //-- 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? //typedef std::vector ExtendedHandlerList; //typedef STL_HASH_MAP ExtendedHandlerTable; 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; // State transition in the FSM (animations should be reset) @@ -185,8 +178,6 @@ public: void checkGroup(); // Groups 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. bool acceptsOrder( int orderType, CEntity* orderTarget ); diff --git a/source/simulation/EntityStateProcessing.cpp b/source/simulation/EntityStateProcessing.cpp index 45681206b1..e8c127f38f 100755 --- a/source/simulation/EntityStateProcessing.cpp +++ b/source/simulation/EntityStateProcessing.cpp @@ -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 ) { + static size_t cyclepos = 0; + // Target's dead (or exhausted)? Then our work here is done. 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 ) { - // Still playing attack animation? Suspend processing. - if( m_actor->GetModel()->GetAnimation() == animation ) + if( animation && ( 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 ); + } // Just transitioned? No animation? (=> melee just finished) Play walk. if( m_transition || !m_actor->GetModel()->GetAnimation() ) @@ -396,8 +416,12 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times m_ahead = delta.normalize(); } - if( DispatchEvent( contactEvent ) && m_actor ) - m_actor->GetModel()->SetAnimation( animation, true ); + // Start the animation. Actual damage/gather will be done in a + // few hundred msec, at the 'action point' of the animation we're + // now setting. + + m_actor->GetModel()->SetAnimation( animation, true ); + cyclepos = 0; return( false ); } diff --git a/source/simulation/EntitySupport.h b/source/simulation/EntitySupport.h index 9e237f485b..9e62d68bdd 100755 --- a/source/simulation/EntitySupport.h +++ b/source/simulation/EntitySupport.h @@ -35,10 +35,10 @@ public: } static void ScriptingInit() { - AddClassProperty( L"crush", &CDamageType::m_Crush ); - AddClassProperty( L"hack", &CDamageType::m_Hack ); - AddClassProperty( L"pierce", &CDamageType::m_Pierce ); - AddClassProperty( L"typeless", &CDamageType::m_Typeless ); + AddProperty( L"crush", &CDamageType::m_Crush ); + AddProperty( L"hack", &CDamageType::m_Hack ); + AddProperty( L"pierce", &CDamageType::m_Pierce ); + AddProperty( L"typeless", &CDamageType::m_Typeless ); CJSObject::ScriptingInit( "DamageType", Construct, 3 ); } static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) diff --git a/source/simulation/EventHandlers.cpp b/source/simulation/EventHandlers.cpp index a332b3585b..df2268800c 100755 --- a/source/simulation/EventHandlers.cpp +++ b/source/simulation/EventHandlers.cpp @@ -2,50 +2,52 @@ #include "EventHandlers.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; - 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; - 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_damage = damage; - AddReadOnlyProperty( L"inflictor", &m_inflictor ); - AddProperty( L"damage", &m_damage ); + AddLocalProperty( L"inflictor", &m_inflictor, true ); + 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_defaultAction = -1; - AddReadOnlyProperty( L"target", &m_target ); - AddProperty( L"defaultAction", &m_defaultAction ); + m_defaultCursor = L"arrow-default"; + 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_orderType = orderType; - AddReadOnlyProperty( L"target", &m_target ); - AddReadOnlyProperty( L"orderType", &m_orderType ); + AddLocalProperty( L"target", &m_target, true ); + 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_orderCurrent = orderCurrent; m_target = ⌖ m_worldPosition = &worldPosition; - AddReadOnlyProperty( L"orderPrevious", &m_orderPrevious ); - AddProperty( L"orderCurrent", &m_orderCurrent ); - AddProperty( L"target", m_target ); - AddProperty( L"position", m_worldPosition ); + AddLocalProperty( L"orderPrevious", &m_orderPrevious, true ); + AddLocalProperty( L"orderCurrent", &m_orderCurrent ); + AddLocalProperty( L"target", m_target ); + AddLocalProperty( L"position", m_worldPosition ); } diff --git a/source/simulation/EventHandlers.h b/source/simulation/EventHandlers.h index ef9c2f6233..268a15b9dc 100755 --- a/source/simulation/EventHandlers.h +++ b/source/simulation/EventHandlers.h @@ -10,41 +10,16 @@ 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 { public: - CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {} + CEventInitialize() : CScriptEvent( L"initialize", EVENT_INITIALIZE, false ) {} }; class CEventTick : public CScriptEvent { public: - CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {} + CEventTick() : CScriptEvent( L"tick", EVENT_TICK, false ) {} }; class CEventAttack : public CScriptEvent @@ -74,6 +49,7 @@ class CEventTargetChanged : public CScriptEvent CEntity* m_target; public: int m_defaultAction; + CStrW m_defaultCursor; CEventTargetChanged( CEntity* target ); }; diff --git a/source/simulation/Scheduler.cpp b/source/simulation/Scheduler.cpp index eb9b1587c7..6f0590ec74 100755 --- a/source/simulation/Scheduler.cpp +++ b/source/simulation/Scheduler.cpp @@ -121,9 +121,9 @@ CJSProgressTimer::CJSProgressTimer( double Max, double Increment, JSFunction* Ca void CJSProgressTimer::ScriptingInit() { - AddClassProperty( L"max", &CJSProgressTimer::m_Max ); - AddClassProperty( L"current", &CJSProgressTimer::m_Current ); - AddClassProperty( L"increment", &CJSProgressTimer::m_Increment ); + AddProperty( L"max", &CJSProgressTimer::m_Max ); + AddProperty( L"current", &CJSProgressTimer::m_Current ); + AddProperty( L"increment", &CJSProgressTimer::m_Increment ); CJSObject::ScriptingInit( "ProgressTimer", Construct, 2 ); } diff --git a/source/simulation/ScriptObject.cpp b/source/simulation/ScriptObject.cpp index af60d5e594..5be0b42470 100755 --- a/source/simulation/ScriptObject.cpp +++ b/source/simulation/ScriptObject.cpp @@ -76,15 +76,15 @@ JSObject* CScriptObject::GetFunctionObject() // Executes a script attached to a JS object. // Returns false if the script isn't defined, if the script can't be executed, // 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 ) 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. -bool CScriptObject::Run( JSObject* Context ) +bool CScriptObject::Run( JSObject* Context, uintN argc, jsval* argv ) { jsval 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. bool CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt ) { - jsval Temp; - jsval EventObject = OBJECT_TO_JSVAL( evt->GetScript() ); if( Function ) + { + jsval Temp; + jsval EventObject = OBJECT_TO_JSVAL( evt->GetScript() ); 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 ) diff --git a/source/simulation/ScriptObject.h b/source/simulation/ScriptObject.h index 5e89b7f29e..968be382be 100755 --- a/source/simulation/ScriptObject.h +++ b/source/simulation/ScriptObject.h @@ -37,15 +37,19 @@ public: 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* GetFunctionObject(); // Executes a script attached to a JS object. // Returns false if the script isn't defined, if the script can't be executed, // 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. - 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. bool DispatchEvent( JSObject* Context, CScriptEvent* evt );