diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 0bac6d9cbb..d0b689ebf2 100755 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -34,7 +34,9 @@ CGameView::CGameView(CGame *pGame): m_Camera(), m_ViewScrollSpeed(60), m_ViewRotateSensitivity(0.002f), + m_ViewRotateSensitivityKeyboard(1.0f), m_ViewRotateAboutTargetSensitivity(0.010f), + m_ViewRotateAboutTargetSensitivityKeyboard(2.0f), m_ViewDragSensitivity(0.5f), m_ViewZoomSensitivityWheel(16.0f), m_ViewZoomSensitivity(256.0f), @@ -69,7 +71,9 @@ void CGameView::Initialize(CGameAttributes *pAttribs) getViewParameter( "view.scroll.speed", m_ViewScrollSpeed ); getViewParameter( "view.rotate.speed", m_ViewRotateSensitivity ); + getViewParameter( "view.rotate.keyboard.speed", m_ViewRotateSensitivityKeyboard ); getViewParameter( "view.rotate.abouttarget.speed", m_ViewRotateAboutTargetSensitivity ); + getViewParameter( "view.rotate.keyboard.abouttarget.speed", m_ViewRotateAboutTargetSensitivityKeyboard ); getViewParameter( "view.drag.speed", m_ViewDragSensitivity ); getViewParameter( "view.zoom.speed", m_ViewZoomSensitivity ); getViewParameter( "view.zoom.wheel.speed", m_ViewZoomSensitivityWheel ); @@ -243,7 +247,7 @@ void CGameView::Update(float DeltaTime) forwards_horizontal.Y = 0.0f; forwards_horizontal.Normalize(); - if( hotkeys[HOTKEY_CAMERA_ROTATE] ) + if( hotkeys[HOTKEY_CAMERA_ROTATE] || hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { // Ctrl + middle-drag or left-and-right-drag to rotate view @@ -252,11 +256,36 @@ void CGameView::Update(float DeltaTime) m_Camera.m_Orientation.Translate(position*-1); // Sideways rotation - m_Camera.m_Orientation.RotateY(m_ViewRotateSensitivity * (float)(mouse_dx)); + + float rightways = 0.0f; + if( hotkeys[HOTKEY_CAMERA_ROTATE] ) + rightways = (float)mouse_dx * m_ViewRotateSensitivity; + if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) + { + if( hotkeys[HOTKEY_CAMERA_LEFT] ) + rightways -= m_ViewRotateSensitivityKeyboard * DeltaTime; + if( hotkeys[HOTKEY_CAMERA_RIGHT] ) + rightways += m_ViewRotateSensitivityKeyboard * DeltaTime; + } + + m_Camera.m_Orientation.RotateY( rightways ); // Up/down rotation + + float upways = 0.0f; + if( hotkeys[HOTKEY_CAMERA_ROTATE] ) + upways = (float)mouse_dy * m_ViewRotateSensitivity; + if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) + { + if( hotkeys[HOTKEY_CAMERA_UP] ) + upways -= m_ViewRotateSensitivityKeyboard * DeltaTime; + if( hotkeys[HOTKEY_CAMERA_DOWN] ) + upways += m_ViewRotateSensitivityKeyboard * DeltaTime; + } + CQuaternion temp; - temp.FromAxisAngle(rightwards, m_ViewRotateSensitivity * (float)(mouse_dy)); + temp.FromAxisAngle(rightwards, upways); + m_Camera.m_Orientation.Rotate(temp); // Retranslate back to the right position @@ -270,11 +299,16 @@ void CGameView::Update(float DeltaTime) CQuaternion rotateH, rotateV; CMatrix3D rotateM; - // Side-to-side rotation - rotateH.FromAxisAngle( upwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dx ); + // Sideways rotation + + float rightways = (float)mouse_dx * m_ViewRotateAboutTargetSensitivity; + + rotateH.FromAxisAngle( upwards, rightways ); - // Up-down rotation - rotateV.FromAxisAngle( rightwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dy ); + // Up/down rotation + + float upways = (float)mouse_dy * m_ViewRotateAboutTargetSensitivity; + rotateV.FromAxisAngle( rightwards, upways ); rotateH *= rotateV; rotateH.ToMatrix( rotateM ); @@ -295,6 +329,55 @@ void CGameView::Update(float DeltaTime) m_Camera.m_Orientation.Translate( m_CameraPivot + delta ); } + } + else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD] ) + { + // Split up because the keyboard controls use the centre of the screen, not the mouse position. + CVector3D origin = m_Camera.m_Orientation.GetTranslation(); + CVector3D pivot = m_Camera.GetFocus(); + CVector3D delta = origin - pivot; + + CQuaternion rotateH, rotateV; CMatrix3D rotateM; + + // Sideways rotation + + float rightways = 0.0f; + if( hotkeys[HOTKEY_CAMERA_LEFT] ) + rightways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; + if( hotkeys[HOTKEY_CAMERA_RIGHT] ) + rightways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; + + rotateH.FromAxisAngle( upwards, rightways ); + + // Up/down rotation + + float upways = 0.0f; + if( hotkeys[HOTKEY_CAMERA_UP] ) + upways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; + if( hotkeys[HOTKEY_CAMERA_DOWN] ) + upways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; + + rotateV.FromAxisAngle( rightwards, upways ); + + rotateH *= rotateV; + rotateH.ToMatrix( rotateM ); + + delta = rotateM.Rotate( delta ); + + // Lock the inclination to a rather arbitrary values (for the sake of graphical decency) + + float scan = sqrt( delta.X * delta.X + delta.Z * delta.Z ) / delta.Y; + if( ( scan >= 0.5f ) ) + { + // Move the camera to the origin (in preparation for rotation ) + m_Camera.m_Orientation.Translate( origin * -1.0f ); + + m_Camera.m_Orientation.Rotate( rotateH ); + + // Move the camera back to where it belongs + m_Camera.m_Orientation.Translate( pivot + delta ); + } + } else if( hotkeys[HOTKEY_CAMERA_PAN] ) { @@ -321,15 +404,18 @@ void CGameView::Update(float DeltaTime) // Keyboard movement (added to mouse movement, so you can go faster if you want) - if( hotkeys[HOTKEY_CAMERA_PAN_RIGHT] ) - m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime)); - if( hotkeys[HOTKEY_CAMERA_PAN_LEFT] ) - m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime)); + if( hotkeys[HOTKEY_CAMERA_PAN_KEYBOARD] ) + { + if( hotkeys[HOTKEY_CAMERA_RIGHT] ) + m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime)); + if( hotkeys[HOTKEY_CAMERA_LEFT] ) + m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime)); - if( hotkeys[HOTKEY_CAMERA_PAN_BACKWARD] ) - m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); - if( hotkeys[HOTKEY_CAMERA_PAN_FORWARD] ) - m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); + if( hotkeys[HOTKEY_CAMERA_DOWN] ) + m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); + if( hotkeys[HOTKEY_CAMERA_UP] ) + m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); + } // Smoothed zooming (move a certain percentage towards the desired zoom distance every frame) diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h index f18f561a40..3af3057e6d 100755 --- a/source/graphics/GameView.h +++ b/source/graphics/GameView.h @@ -21,7 +21,9 @@ class CGameView // Settings float m_ViewScrollSpeed; float m_ViewRotateSensitivity; + float m_ViewRotateSensitivityKeyboard; float m_ViewRotateAboutTargetSensitivity; + float m_ViewRotateAboutTargetSensitivityKeyboard; float m_ViewDragSensitivity; float m_ViewZoomSensitivityWheel; float m_ViewZoomSensitivity; diff --git a/source/graphics/MeshManager.cpp b/source/graphics/MeshManager.cpp index 9522036969..472a07a8b1 100755 --- a/source/graphics/MeshManager.cpp +++ b/source/graphics/MeshManager.cpp @@ -43,14 +43,29 @@ CModelDef *CMeshManager::GetMesh(const char *filename) } } -int CMeshManager::ReleaseMesh(CModelDef *mesh) +int CMeshManager::ReleaseMesh(CModelDef* mesh) { if(!mesh) return 0; - mesh_map::iterator iter = m_MeshMap.find(mesh->m_Hash); - if(iter == m_MeshMap.end()) - return 0; + // FIXME: Someone sort this out. I'm tired. + // Looks like it might be a multiple delete; not sure best way + // to resolve it. MT. + + mesh_map::iterator iter; + + try + { + iter = m_MeshMap.find(mesh->m_Hash); + } + catch( ... ) + { + debug_out( "FIXME: Do something with %s(%d)", __FILE__, __LINE__ ); + return( 0 ); + } + + if(iter == m_MeshMap.end()) + return 0; mesh->m_RefCount--; if(mesh->m_RefCount <= 0) diff --git a/source/graphics/MeshManager.h b/source/graphics/MeshManager.h index 397322196f..eed4af686f 100755 --- a/source/graphics/MeshManager.h +++ b/source/graphics/MeshManager.h @@ -15,7 +15,7 @@ public: ~CMeshManager(); CModelDef *GetMesh(const char *filename); - int ReleaseMesh(CModelDef *mesh); + int ReleaseMesh(CModelDef* mesh); private: mesh_map m_MeshMap; }; diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 56ab4fe2b3..778b21cc32 100755 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -195,6 +195,8 @@ void CModel::Update(float time) float duration=m_Anim->m_AnimDef->GetDuration(); if (m_AnimTime>duration) { + if( m_Flags & MODELFLAG_NOLOOPANIMATION ) + SetAnimation( NULL ); m_AnimTime=(float) fmod(m_AnimTime,duration); } @@ -248,10 +250,14 @@ void CModel::GenerateBoneMatrices() ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetAnimation: set the given animation as the current animation on this model; // return false on error, else true -bool CModel::SetAnimation(CSkeletonAnim* anim) +bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) { m_Anim=anim; if (m_Anim) { + m_Flags &= ~MODELFLAG_NOLOOPANIMATION; + if( once ) + m_Flags |= MODELFLAG_NOLOOPANIMATION; + if (!m_BoneMatrices) { // not boned, can't animate return false; diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 11f660eef7..2f9a4bbdde 100755 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -18,6 +18,7 @@ #include "Material.h" #define MODELFLAG_CASTSHADOWS (1<<0) +#define MODELFLAG_NOLOOPANIMATION (1<<1) /////////////////////////////////////////////////////////////////////////////// // CModel: basically, a mesh object - holds the texturing and skinning @@ -58,7 +59,7 @@ public: CMaterial &GetMaterial() { return m_Material; } // set the given animation as the current animation on this model - bool SetAnimation(CSkeletonAnim* anim); + bool SetAnimation(CSkeletonAnim* anim, bool once = false); // get the currently playing animation, if any CSkeletonAnim* GetAnimation() { return m_Anim; } diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index 7baa04b608..474583f27b 100755 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -19,6 +19,7 @@ CObjectEntry::CObjectEntry(int type) : m_Model(0), m_Type(type) m_IdleAnim=0; m_WalkAnim=0; m_DeathAnim=0; + m_CorpseAnim=0; m_MeleeAnim=0; m_RangedAnim=0; m_Properties.m_CastShadows=true; @@ -84,6 +85,12 @@ bool CObjectEntry::BuildModel() m_IdleAnim = m_Animations[t].m_AnimData; if( m_Animations[t].m_AnimName.LowerCase() == CStr( "walk" ) ) m_WalkAnim = m_Animations[t].m_AnimData; + if( m_Animations[t].m_AnimName.LowerCase() == CStr( "attack" ) ) + m_MeleeAnim = m_Animations[t].m_AnimData; + if( m_Animations[t].m_AnimName.LowerCase() == CStr( "death" ) ) + m_DeathAnim = m_Animations[t].m_AnimData; + if( m_Animations[t].m_AnimName.LowerCase() == CStr( "decay" ) ) + m_CorpseAnim = m_Animations[t].m_AnimData; } else { diff --git a/source/graphics/ObjectEntry.h b/source/graphics/ObjectEntry.h index b3df1413e3..df2f3a2b71 100755 --- a/source/graphics/ObjectEntry.h +++ b/source/graphics/ObjectEntry.h @@ -59,6 +59,8 @@ public: CSkeletonAnim* m_DeathAnim; CSkeletonAnim* m_MeleeAnim; CSkeletonAnim* m_RangedAnim; + CSkeletonAnim* m_CorpseAnim; + CSkeletonAnim* GetNamedAnimation( CStr animationName ); // list of props attached to object std::vector m_Props; diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp index 1c3a3200f9..33c27e4b25 100755 --- a/source/gui/CGUI.cpp +++ b/source/gui/CGUI.cpp @@ -1196,9 +1196,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec ATTR(style); ATTR(type); ATTR(name); -// MT - temp tag ATTR(hotkey); -// -- MT ATTR(z); ATTR(on); ATTR(file); @@ -1233,9 +1231,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec bool NameSet = false; bool ManuallySetZ = false; // if z has been manually set, this turn true -// MT - temp tag CStr hotkeyTag; -// -- MT // Now we can iterate all attributes and store for (i=0; iGetName(), hotkeyTag ); -// -- MT CStrW caption (Element.getText()); if (caption.Length()) diff --git a/source/lib/res/snd.cpp b/source/lib/res/snd.cpp index b2a0e75eef..af6ade0b5f 100755 --- a/source/lib/res/snd.cpp +++ b/source/lib/res/snd.cpp @@ -52,7 +52,6 @@ # pragma comment(lib, "alut.lib") // alutLoadWAVMemory #endif - // components: // - alc*: OpenAL context // readies OpenAL for use; allows specifying the device. @@ -81,7 +80,6 @@ // - vm*: voice management // grants the most currently 'important' sounds a hardware voice. - static bool al_initialized = false; // indicates OpenAL is ready for use. checked by other components // when deciding if they can pass settings changes to OpenAL directly, diff --git a/source/main.cpp b/source/main.cpp index a40d00ac27..3c36c17a3b 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -554,7 +554,7 @@ static void Render() static void InitDefaultGameAttributes() { - g_GameAttributes.SetValue("mapFile", L"test01.pmp"); + g_GameAttributes.SetValue( "mapFile", L"combattest.pmp" ); } @@ -644,7 +644,7 @@ static void ParseArgs(int argc, char* argv[]) break; case 'm': if(strncmp(name, "m=", 2) == 0) - g_GameAttributes.SetValue("mapFile", CStr(argv[i]+3)); + g_GameAttributes.SetValue( "mapFile", CStr( argv[i] + 3 ) ); break; case 'n': if(strncmp(name, "novbo", 5) == 0) @@ -689,16 +689,22 @@ static void InitScripting() // Register the JavaScript interfaces with the runtime CEntity::ScriptingInit(); - CBaseEntity::ScriptingInit(); JSI_IGUIObject::init(); JSI_GUITypes::init(); JSI_Vector3D::init(); EntityCollection::Init( "EntityCollection" ); + // 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. CScriptEvent::ScriptingInit(); + g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 ); + g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO ); + g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL ); + g_ScriptingHost.DefineConstant( "ORDER_ATTACK", CEntityOrder::ORDER_ATTACK_MELEE ); + JSI_Camera::init(); JSI_Console::init(); } @@ -795,6 +801,9 @@ static void Shutdown() { psShutdown(); // Must delete g_GUI before g_ScriptingHost + // Release script references to the globals before ScriptingHost shuts down + // g_GameAttributes.ReleaseScriptObject(); + if (g_Game) delete g_Game; @@ -1036,8 +1045,6 @@ PREVTSC=CURTSC; _CrtSetBreakAlloc(36367); //*/ - // Initialize entities - in_add_handler(handler); in_add_handler(game_view_handler); @@ -1186,9 +1193,9 @@ static void Frame() // Choose when to override the standard exception handling behaviour // (opening the debugger when available, or crashing when not) with // code that generates a crash log/dump. -#if defined(_WIN32) && ( defined(NDEBUG) || defined(TESTING) ) -# define CUSTOM_EXCEPTION_HANDLER -#endif +//#if defined(_WIN32) && ( defined(NDEBUG) || defined(TESTING) ) +// # define CUSTOM_EXCEPTION_HANDLER +// #endif #ifdef CUSTOM_EXCEPTION_HANDLER #include diff --git a/source/ps/CConsole.cpp b/source/ps/CConsole.cpp index e08611a414..fcd9bc3390 100755 --- a/source/ps/CConsole.cpp +++ b/source/ps/CConsole.cpp @@ -13,6 +13,8 @@ #include "Network/Client.h" #include "Network/Server.h" +#include "Interact.h" + extern bool keys[SDLK_LAST]; CConsole::CConsole() @@ -497,14 +499,24 @@ void CConsole::ProcessBuffer(const wchar_t* szLine){ { // Process it as JavaScript - g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ) ); + // (Cheating) run it as the first selected entity, if there is one. + JSObject* RunAs = NULL; + if( g_Selection.m_selected.size() ) + RunAs = g_Selection.m_selected[0]->GetScript(); + + g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ), L"Console", RunAs ); } else if (szLine[0] == '?') { // Process it as JavaScript and display the result - jsval rval = g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ) ); + // (Cheating) run it as the first selected entity, if there is one. + JSObject* RunAs = NULL; + if( g_Selection.m_selected.size() ) + RunAs = g_Selection.m_selected[0]->GetScript(); + + jsval rval = g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ), L"Console", RunAs ); if (rval) { try { diff --git a/source/ps/CLogger.h b/source/ps/CLogger.h index 2006eb4230..294c8cda08 100755 --- a/source/ps/CLogger.h +++ b/source/ps/CLogger.h @@ -14,7 +14,7 @@ enum ELogMethod { NORMAL, - MESSAGE=NORMAL, + MESSAGE = NORMAL, ERROR, WARNING }; diff --git a/source/ps/Hotkey.cpp b/source/ps/Hotkey.cpp index 6e476e0dd1..e6e58e6bc0 100755 --- a/source/ps/Hotkey.cpp +++ b/source/ps/Hotkey.cpp @@ -3,6 +3,7 @@ #include "Hotkey.h" #include "input.h" #include "ConfigDB.h" +#include "CLogger.h" #include "CConsole.h" #include "CStr.h" @@ -16,6 +17,7 @@ bool unified[5]; struct SHotkeyMapping { int mapsTo; + bool negation; std::vector requires; }; @@ -66,12 +68,15 @@ static SHotkeyInfo hotkeyInfo[] = { HOTKEY_CAMERA_ZOOM_WHEEL_IN, "camera.zoom.wheel.in", MOUSE_WHEELUP, 0 }, { HOTKEY_CAMERA_ZOOM_WHEEL_OUT, "camera.zoom.wheel.out", MOUSE_WHEELDOWN, 0 }, { HOTKEY_CAMERA_ROTATE, "camera.rotate", 0, 0 }, + { HOTKEY_CAMERA_ROTATE_KEYBOARD, "camera.rotate.keyboard", 0, 0 }, { HOTKEY_CAMERA_ROTATE_ABOUT_TARGET, "camera.rotate.abouttarget", 0, 0 }, + { HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD, "camera.rotate.abouttarget.keyboard", 0, 0 }, { HOTKEY_CAMERA_PAN, "camera.pan", MOUSE_MIDDLE, 0 }, - { HOTKEY_CAMERA_PAN_LEFT, "camera.pan.left", SDLK_LEFT, 0 }, - { HOTKEY_CAMERA_PAN_RIGHT, "camera.pan.right", SDLK_RIGHT, 0 }, - { HOTKEY_CAMERA_PAN_FORWARD, "camera.pan.forward", SDLK_UP, 0 }, - { HOTKEY_CAMERA_PAN_BACKWARD, "camera.pan.backward", SDLK_DOWN, 0 }, + { HOTKEY_CAMERA_PAN_KEYBOARD, "camera.pan.keyboard", 0, 0 }, + { HOTKEY_CAMERA_LEFT, "camera.left", SDLK_LEFT, 0 }, + { HOTKEY_CAMERA_RIGHT, "camera.right", SDLK_RIGHT, 0 }, + { HOTKEY_CAMERA_UP, "camera.up", SDLK_UP, 0 }, + { HOTKEY_CAMERA_DOWN, "camera.down", SDLK_DOWN, 0 }, { HOTKEY_CAMERA_BOOKMARK_0, "camera.bookmark.0", SDLK_F5, 0, }, { HOTKEY_CAMERA_BOOKMARK_1, "camera.bookmark.1", SDLK_F6, 0, }, { HOTKEY_CAMERA_BOOKMARK_2, "camera.bookmark.2", SDLK_F7, 0, }, @@ -128,6 +133,7 @@ static SHotkeyInfo hotkeyInfo[] = struct SHotkeyMappingGUI { CStr mapsTo; + bool negation; std::vector requires; }; @@ -153,7 +159,7 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) CConfigValueSet::iterator it; CParser multikeyParser; - multikeyParser.InputTaskType( "multikey", "<_$value_+_>_$value" ); + multikeyParser.InputTaskType( "multikey", "<[!$arg(_negate)][~$arg(_negate)]$value_+_>_[!$arg(_negate)][~$arg(_negate)]$value" ); // Iterate through the bindings for this event... @@ -169,15 +175,39 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) // Iterate through multiple-key bindings (e.g. Ctrl+I) + bool negateNext = false; + for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) { if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) { - mapping = getKeyCode( hotkey ); // Attempt decode as key name + if( hotkey == "_negate" ) + { + negateNext = true; + continue; + } + + // Attempt decode as key name + mapping = getKeyCode( hotkey ); + + // Attempt to decode as a negation of a keyname + // Yes, it's going a bit far, perhaps. + // Too powerful for most uses, probably. + // However, it got some hardcoding out of the engine. + // Thus it makes me happy. + if( !mapping ) if( !it->GetInt( mapping ) ) // Attempt decode as key code + { + LOG( WARNING, "hotkey", "Couldn't map '%s'", hotkey.c_str() ); continue; + } + + if( negateNext ) mapping |= HOTKEY_NEGATION_FLAG; + + negateNext = false; + keyCombination.push_back( mapping ); } } @@ -190,10 +220,12 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ ) { bindName.mapsTo = hotkeyName; + bindName.negation = (bool)( *itKey & HOTKEY_NEGATION_FLAG ); bindName.requires.clear(); if( integerMapping != -1 ) { bindCode.mapsTo = integerMapping; + bindCode.negation = (bool)( *itKey & HOTKEY_NEGATION_FLAG ); bindCode.requires.clear(); } for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ ) @@ -207,9 +239,9 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) } } - hotkeyMapGUI[*itKey].push_back( bindName ); + hotkeyMapGUI[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindName ); if( integerMapping != -1 ) - hotkeyMap[*itKey].push_back( bindCode ); + hotkeyMap[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindCode ); } } } @@ -221,6 +253,8 @@ void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) bind[1].mapsTo = integerMapping; bind[0].requires.clear(); bind[1].requires.clear(); + bind[0].negation = false; + bind[1].negation = false; hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping1 ].push_back( bind[0] ); if( hotkeyInfo[integerMapping].defaultmapping2 ) hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping2 ].push_back( bind[1] ); @@ -230,9 +264,35 @@ void loadHotkeys() { initKeyNameMap(); - for( int i = 0; i < HOTKEY_LAST; i++ ) + int i; + + for( i = 0; i < HOTKEY_LAST; i++ ) setBindings( hotkeyInfo[i].name, i ); + // Set up the state of the hotkeys given no key is down. + // i.e. find those hotkeys triggered by all negations. + + std::vector::iterator it; + std::vector::iterator j; + bool allNegated; + + for( i = 1; i < HK_MAX_KEYCODES; i++ ) + { + for( it = hotkeyMap[i].begin(); it != hotkeyMap[i].end(); it++ ) + { + if( !it->negation ) + continue; + + allNegated = true; + + for( j = it->requires.begin(); j != it->requires.end(); j++ ) + if( !( *j & HOTKEY_NEGATION_FLAG ) ) + allNegated = false; + + if( allNegated ) + hotkeys[it->mapsTo] = true; + } + } } void hotkeyRegisterGUIObject( const CStr& objName, const CStr& hotkeyName ) @@ -329,153 +389,180 @@ int hotkeyInputHandler( const SDL_Event* ev ) // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) - if( ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ) ) - { - // SDL-events bit + bool typeKeyDown = ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ); + + // -- KEYDOWN SECTION -- + + // SDL-events bit + + unsigned int closestMap; + size_t closestMapMatch = 0; + + for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) + { + // If a key has been pressed, and this event triggers on it's release, skip it. + // Similarly, if the key's been released and the event triggers on a keypress, skip it. + if( it->negation == typeKeyDown ) + continue; + + // Check to see if all auxiliary keys are down - unsigned int closestMap; - size_t closestMapMatch = 0; + std::vector::iterator itKey; + bool accept = true; + isCapturable = true; - for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) - { - // Check to see if all auxiliary keys are down - - std::vector::iterator itKey; - bool accept = true; - isCapturable = true; - - for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) - { - if( *itKey < SDLK_LAST ) - { - if( !keys[*itKey] ) accept = false; - } - else if( *itKey < UNIFIED_SHIFT ) - { - if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; - } - else - if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false; - - // If this event requires a multiple keypress (with the exception - // of shift+key combinations) the console won't inhibit it. - if( ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) - isCapturable = false; - } - - if( it->mapsTo == HOTKEY_CONSOLE_TOGGLE ) isCapturable = false; // Because that would be silly. - - if( accept && !( isCapturable && consoleCapture ) ) - { - hotkeys[it->mapsTo] = true; - if( it->requires.size() >= closestMapMatch ) - { - // Only if it's a more precise match, and it either isn't capturable or the console won't capture it. - closestMap = it->mapsTo; - closestMapMatch = it->requires.size() + 1; - } - } - } - - if( closestMapMatch ) + for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) { - hotkeyNotification.type = SDL_HOTKEYDOWN; - hotkeyNotification.user.code = closestMap; - SDL_PushEvent( &hotkeyNotification ); - } - // GUI bit... could do with some optimization later. + int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit + bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG ); - CStr closestMapName = -1; - closestMapMatch = 0; + // assert( !rqdState ); - for( itGUI = hotkeyMapGUI[keycode].begin(); itGUI != hotkeyMapGUI[keycode].end(); itGUI++ ) - { - // Check to see if all auxiliary keys are down - - std::vector::iterator itKey; - bool accept = true; - isCapturable = true; - - for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ ) + if( keyCode < SDLK_LAST ) { - if( *itKey < SDLK_LAST ) - { - if( !keys[*itKey] ) accept = false; - } - else if( *itKey < UNIFIED_SHIFT ) - { - if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; - } - else - if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false; - - // If this event requires a multiple keypress (with the exception - // of shift+key combinations) the console won't inhibit it. - if( ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) - isCapturable = false; + if( keys[keyCode] != rqdState ) accept = false; } - - if( accept && !( isCapturable && consoleCapture ) ) + else if( *itKey < UNIFIED_SHIFT ) { - if( itGUI->requires.size() >= closestMapMatch ) - { - closestMapName = itGUI->mapsTo; - closestMapMatch = itGUI->requires.size() + 1; - } + if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false; } - } - // GUI-objects bit - // This fragment is an obvious candidate for rewriting when speed becomes an issue. + else + if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false; - if( closestMapMatch ) + // If this event requires a multiple keypress (with the exception + // of shift+key combinations) the console won't inhibit it. + if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) + isCapturable = false; + } + + if( it->mapsTo == HOTKEY_CONSOLE_TOGGLE ) isCapturable = false; // Because that would be silly. + + if( accept && !( isCapturable && consoleCapture ) ) { - GUIHotkeyMap::iterator map_it; - GUIObjectList::iterator obj_it; - map_it = guiHotkeyMap.find( closestMapName ); - if( map_it != guiHotkeyMap.end() ) + hotkeys[it->mapsTo] = true; + if( it->requires.size() >= closestMapMatch ) { - GUIObjectList& targets = map_it->second; - for( obj_it = targets.begin(); obj_it != targets.end(); obj_it++ ) - { - hotkeyNotification.type = SDL_GUIHOTKEYPRESS; - hotkeyNotification.user.code = (intptr_t)&(*obj_it); - SDL_PushEvent( &hotkeyNotification ); - } - } + // Only if it's a more precise match, and it either isn't capturable or the console won't capture it. + closestMap = it->mapsTo; + closestMapMatch = it->requires.size() + 1; + } } } - else + + if( closestMapMatch ) { - for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) + hotkeyNotification.type = SDL_HOTKEYDOWN; + hotkeyNotification.user.code = closestMap; + SDL_PushEvent( &hotkeyNotification ); + } + // GUI bit... could do with some optimization later. + + CStr closestMapName = -1; + closestMapMatch = 0; + + for( itGUI = hotkeyMapGUI[keycode].begin(); itGUI != hotkeyMapGUI[keycode].end(); itGUI++ ) + { + // If a key has been pressed, and this event triggers on it's release, skip it. + // Similarly, if the key's been released and the event triggers on a keypress, skip it. + if( itGUI->negation == typeKeyDown ) + continue; + + // Check to see if all auxiliary keys are down + + std::vector::iterator itKey; + bool accept = true; + isCapturable = true; + + for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ ) { - // Check to see if all auxiliary keys are down + int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit + bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG ); - std::vector::iterator itKey; - bool accept = true; - - for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) + if( keyCode < SDLK_LAST ) { - if( *itKey < SDLK_LAST ) - { - if( !keys[*itKey] ) accept = false; - } - else if( *itKey < UNIFIED_SHIFT ) - { - if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; - } - else - if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false; + if( keys[keyCode] != rqdState ) accept = false; } - - if( accept ) + else if( *itKey < UNIFIED_SHIFT ) { - hotkeys[it->mapsTo] = false; - hotkeyNotification.type = SDL_HOTKEYUP; - hotkeyNotification.user.code = it->mapsTo; + if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false; + } + else + if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false; + + // If this event requires a multiple keypress (with the exception + // of shift+key combinations) the console won't inhibit it. + if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) + isCapturable = false; + } + + if( accept && !( isCapturable && consoleCapture ) ) + { + if( itGUI->requires.size() >= closestMapMatch ) + { + closestMapName = itGUI->mapsTo; + closestMapMatch = itGUI->requires.size() + 1; + } + } + } + // GUI-objects bit + // This fragment is an obvious candidate for rewriting when speed becomes an issue. + + if( closestMapMatch ) + { + GUIHotkeyMap::iterator map_it; + GUIObjectList::iterator obj_it; + map_it = guiHotkeyMap.find( closestMapName ); + if( map_it != guiHotkeyMap.end() ) + { + GUIObjectList& targets = map_it->second; + for( obj_it = targets.begin(); obj_it != targets.end(); obj_it++ ) + { + hotkeyNotification.type = SDL_GUIHOTKEYPRESS; + hotkeyNotification.user.code = (intptr_t)&(*obj_it); SDL_PushEvent( &hotkeyNotification ); } + } + } + + // -- KEYUP SECTION -- + + for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) + { + // If it's a keydown event, won't cause HotKeyUps in anything that doesn't + // use this key negated => skip them + // If it's a keyup event, won't cause HotKeyUps in anything that does use + // this key negated => skip them too. + if( it->negation != typeKeyDown ) + continue; + + // Check to see if all auxiliary keys are down + + std::vector::iterator itKey; + bool accept = true; + + for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) + { + if( *itKey < SDLK_LAST ) + { + if( !keys[*itKey] ) accept = false; + } + else if( *itKey < UNIFIED_SHIFT ) + { + if( !mouseButtons[(*itKey)-SDLK_LAST] ) accept = false; + } + else + if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false; + } + + if( accept ) + { + hotkeys[it->mapsTo] = false; + hotkeyNotification.type = SDL_HOTKEYUP; + hotkeyNotification.user.code = it->mapsTo; + SDL_PushEvent( &hotkeyNotification ); } } + return( EV_PASS ); } diff --git a/source/ps/Hotkey.h b/source/ps/Hotkey.h index 8dcee61d0d..3e5c2bb389 100755 --- a/source/ps/Hotkey.h +++ b/source/ps/Hotkey.h @@ -37,12 +37,15 @@ enum HOTKEY_CAMERA_ZOOM_WHEEL_IN, HOTKEY_CAMERA_ZOOM_WHEEL_OUT, HOTKEY_CAMERA_ROTATE, + HOTKEY_CAMERA_ROTATE_KEYBOARD, HOTKEY_CAMERA_ROTATE_ABOUT_TARGET, + HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD, HOTKEY_CAMERA_PAN, - HOTKEY_CAMERA_PAN_LEFT, - HOTKEY_CAMERA_PAN_RIGHT, - HOTKEY_CAMERA_PAN_FORWARD, - HOTKEY_CAMERA_PAN_BACKWARD, + HOTKEY_CAMERA_PAN_KEYBOARD, + HOTKEY_CAMERA_LEFT, + HOTKEY_CAMERA_RIGHT, + HOTKEY_CAMERA_UP, + HOTKEY_CAMERA_DOWN, HOTKEY_CAMERA_BOOKMARK_0, HOTKEY_CAMERA_BOOKMARK_1, HOTKEY_CAMERA_BOOKMARK_2, @@ -92,6 +95,8 @@ enum HOTKEY_PLAYMUSIC, HOTKEY_LAST, + + HOTKEY_NEGATION_FLAG = 65536 }; void loadHotkeys(); diff --git a/source/ps/Interact.cpp b/source/ps/Interact.cpp index d645bba583..b9b74ee3da 100755 --- a/source/ps/Interact.cpp +++ b/source/ps/Interact.cpp @@ -13,6 +13,7 @@ extern CConsole* g_Console; extern int mouse_x, mouse_y; extern bool keys[SDLK_LAST]; extern bool g_active; +extern CStr g_CursorName; static const float SELECT_DBLCLICK_RATE = 0.5f; const int ORDER_DELAY = 5; @@ -23,6 +24,7 @@ void CSelectedEntities::addSelection( HEntity entity ) assert( !isSelected( entity ) ); m_selected.push_back( entity ); entity->m_selected = true; + m_selectionChanged = true; } void CSelectedEntities::removeSelection( HEntity entity ) @@ -36,6 +38,7 @@ void CSelectedEntities::removeSelection( HEntity entity ) if( (*it) == entity ) { m_selected.erase( it ); + m_selectionChanged = true; break; } } @@ -126,6 +129,9 @@ void CSelectedEntities::renderOverlays() case CEntityOrder::ORDER_PATROL: glwprintf( L"Patrol to" ); break; + case CEntityOrder::ORDER_ATTACK_MELEE: + glwprintf( L"Attack" ); + break; } glDisable( GL_TEXTURE_2D ); @@ -146,6 +152,7 @@ void CSelectedEntities::clearSelection() for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = false; m_selected.clear(); + m_selectionChanged = true; } void CSelectedEntities::removeAll( HEntity entity ) @@ -158,6 +165,7 @@ void CSelectedEntities::removeAll( HEntity entity ) if( (*it) == entity ) { m_selected.erase( it ); + m_selectionChanged = true; break; } } @@ -168,6 +176,7 @@ void CSelectedEntities::removeAll( HEntity entity ) if( (*it) == entity ) { m_groups[group].erase( it ); + m_selectionChanged = true; break; } } @@ -242,12 +251,18 @@ void CSelectedEntities::addToGroup( i8 groupid, HEntity entity ) void CSelectedEntities::loadGroup( i8 groupid ) { + if( m_group == groupid ) + return; + clearSelection(); m_selected = m_groups[groupid]; + std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; m_group = groupid; + + m_selectionChanged = true; } void CSelectedEntities::addGroup( i8 groupid ) @@ -329,6 +344,52 @@ CVector3D CSelectedEntities::getGroupPosition( i8 groupid ) void CSelectedEntities::update() { + if( m_selectionChanged || g_Mouseover.m_targetChanged ) + { + // Can't order anything off the map + if( !g_Game->GetWorld()->GetTerrain()->isOnMap( g_Mouseover.m_worldposition ) ) + { + m_contextOrder = -1; + return; + } + + // Quick count to see which is the modal default order. + + int defaultPoll[CEntityOrder::ORDER_LAST]; + int t, vote; + for( t = 0; t < CEntityOrder::ORDER_LAST; t++ ) + defaultPoll[t] = 0; + + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + vote = (*it)->defaultOrder( g_Mouseover.m_target ); + if( ( vote >= 0 ) && ( vote < CEntityOrder::ORDER_LAST ) ) + defaultPoll[vote]++; + } + + vote = -1; + for( t = 0; t < CEntityOrder::ORDER_LAST; t++ ) + { + if( ( vote == -1 ) || ( defaultPoll[t] > defaultPoll[vote] ) ) + vote = t; + } + + m_contextOrder = vote; + switch( m_contextOrder ) + { + case CEntityOrder::ORDER_ATTACK_MELEE: + g_CursorName = "action-attack"; + break; + default: + g_CursorName = "arrow-default"; + break; + } + + m_selectionChanged = false; + g_Mouseover.m_targetChanged = false; + } + /* if( !isContextValid( m_contextOrder ) ) { // This order isn't valid for the current selection and/or target. @@ -339,6 +400,7 @@ void CSelectedEntities::update() } m_contextOrder = -1; } + */ if( ( m_group_highlight != -1 ) && getGroupCount( m_group_highlight ) ) g_Game->GetView()->SetCameraTarget( getGroupPosition( m_group_highlight ) ); @@ -413,15 +475,31 @@ void CSelectedEntities::contextOrder( bool pushQueue ) switch( m_contextOrder ) { // PATROL order: temporatily disabled until we define the network command for it -// case CEntityOrder::ORDER_PATROL: + case CEntityOrder::ORDER_PATROL: case CEntityOrder::ORDER_GOTO: { + context.m_data[0].location = g_Mouseover.m_worldposition; + break; +/* CGotoCommand *msg=new CGotoCommand(); msg->m_Entity=m_selected[0]->me; msg->m_TargetX=(u32)g_Mouseover.m_worldposition.x; msg->m_TargetY=(u32)g_Mouseover.m_worldposition.y; g_Game->GetSimulation()->QueueLocalCommand(msg); break; +*/ + } + case CEntityOrder::ORDER_ATTACK_MELEE: + { + context.m_data[0].entity = g_Mouseover.m_target; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) ) + { + if( !pushQueue ) + (*it)->clearOrders(); + (*it)->pushOrder( context ); + } + return; } default: break; @@ -447,8 +525,7 @@ void CSelectedEntities::contextOrder( bool pushQueue ) pathfinder? */ - /* - float radius = 2.0f * sqrt( (float)m_selected.size() - 1 ); // A decent enough approximation + float radius = 2.0f * sqrt( (float)m_selected.size() - 1 ); float _x, _y; @@ -478,9 +555,12 @@ void CSelectedEntities::contextOrder( bool pushQueue ) if( contextRandomized.m_data[0].location.y >= mapsize ) contextRandomized.m_data[0].location.y = mapsize; - g_Scheduler.pushFrame( ORDER_DELAY, (*it)->me, new CMessageOrder( contextRandomized, pushQueue ) ); + if( !pushQueue ) + (*it)->clearOrders(); + + (*it)->pushOrder( contextRandomized ); } - */ + } void CMouseoverEntities::update( float timestep ) @@ -495,13 +575,19 @@ void CMouseoverEntities::update( float timestep ) m_worldposition = pCamera->GetWorldCoordinates(); - if( hit && hit->GetEntity() ) + if( hit && hit->GetEntity() && hit->GetEntity()->m_extant ) { m_target = hit->GetEntity()->me; } else m_target = HEntity(); + if( m_target != m_lastTarget ) + { + m_targetChanged = true; + m_lastTarget = m_target; + } + if( m_viewall ) { // 'O' key. Show selection outlines for all player units on screen @@ -515,7 +601,8 @@ void CMouseoverEntities::update( float timestep ) std::vector::iterator it; for( it = onscreen->begin(); it < onscreen->end(); it++ ) - m_mouseover.push_back( SMouseoverFader( *it, m_fademaximum, false ) ); + if( (*it)->m_extant ) + m_mouseover.push_back( SMouseoverFader( *it, m_fademaximum, false ) ); delete( onscreen ); } @@ -540,6 +627,9 @@ void CMouseoverEntities::update( float timestep ) for( it = onscreen->begin(); it < onscreen->end(); it++ ) { + if( !(*it)->m_extant ) + continue; + CVector3D worldspace = (*it)->m_graphics_position; float x, y; @@ -655,9 +745,8 @@ void CMouseoverEntities::expandAcrossScreen() m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) - { - m_mouseover.push_back( SMouseoverFader( *it ) ); - } + if( (*it)->m_extant ) + m_mouseover.push_back( SMouseoverFader( *it ) ); delete( activeset ); } @@ -667,9 +756,8 @@ void CMouseoverEntities::expandAcrossWorld() m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) - { - m_mouseover.push_back( SMouseoverFader( *it ) ); - } + if( (*it)->m_extant ) + m_mouseover.push_back( SMouseoverFader( *it ) ); delete( activeset ); } diff --git a/source/ps/Interact.h b/source/ps/Interact.h index 7cb559fe28..ad84e3d49c 100755 --- a/source/ps/Interact.h +++ b/source/ps/Interact.h @@ -22,10 +22,18 @@ struct CSelectedEntities : public Singleton { - CSelectedEntities() { clearSelection(); m_group = -1; m_group_highlight = -1; m_contextOrder = -1; } + CSelectedEntities() + { + clearSelection(); + m_group = -1; + m_group_highlight = -1; + m_contextOrder = -1; + m_selectionChanged = true; + } std::vector m_selected; std::vector m_groups[MAX_GROUPS]; i8 m_group, m_group_highlight; + bool m_selectionChanged; int m_contextOrder; void addSelection( HEntity entity ); @@ -75,6 +83,8 @@ struct CMouseoverEntities : public Singleton float m_fademaximum; CVector2D m_worldposition; HEntity m_target; + HEntity m_lastTarget; + bool m_targetChanged; bool m_bandbox, m_viewall; u16 m_x1, m_y1, m_x2, m_y2; @@ -87,7 +97,7 @@ struct CMouseoverEntities : public Singleton m_fadeoutrate = 2.0f; m_fademaximum = 0.5f; m_mouseover.clear(); - m_target; + m_targetChanged = true; } std::vector m_mouseover; void update( float timestep ); diff --git a/source/ps/Network/AllNetMessages.h b/source/ps/Network/AllNetMessages.h index 740628cbb1..d137e9317d 100755 --- a/source/ps/Network/AllNetMessages.h +++ b/source/ps/Network/AllNetMessages.h @@ -3,6 +3,7 @@ #include "types.h" #include "CStr.h" +#include "scripting/JSSerialization.h" enum ENetMessageType { diff --git a/source/ps/StringConvert.cpp b/source/ps/StringConvert.cpp index 6f0c95e7d4..dfb94aa70e 100755 --- a/source/ps/StringConvert.cpp +++ b/source/ps/StringConvert.cpp @@ -33,6 +33,8 @@ JSString* StringConvert::wstring_to_jsstring(JSContext* cx, const std::wstring& JSString* StringConvert::wchars_to_jsstring(JSContext* cx, const wchar_t* chars) { size_t len = wcslen(chars); + if( !len ) + return( JSVAL_TO_STRING( JS_GetEmptyStringValue( cx ) ) ); jschar* data = (jschar*)JS_malloc(cx, len*sizeof(jschar)); if (!data) return NULL; diff --git a/source/ps/World.cpp b/source/ps/World.cpp index 21529ebefd..f8881b1845 100755 --- a/source/ps/World.cpp +++ b/source/ps/World.cpp @@ -21,7 +21,9 @@ void CWorld::Initialize(CGameAttributes *pAttribs) g_EntityTemplateCollection.loadTemplates(); CStr mapfilename("maps/scenarios/"); - mapfilename += (CStr)pAttribs->GetValue("mapFile"); + + mapfilename += (CStr)pAttribs->GetValue( "mapFile" ); + try { CMapReader reader; reader.LoadMap(mapfilename, &m_Terrain, &m_UnitManager, &g_LightEnv); diff --git a/source/ps/XeroXMB.cpp b/source/ps/XeroXMB.cpp index 78a50f6335..34b9bd4e3c 100755 --- a/source/ps/XeroXMB.cpp +++ b/source/ps/XeroXMB.cpp @@ -1,4 +1,4 @@ -// $Id: XeroXMB.cpp,v 1.7 2004/10/07 20:49:25 philip Exp $ +// $Id: XeroXMB.cpp,v 1.8 2004/11/11 07:09:32 markt Exp $ #include "precompiled.h" @@ -198,7 +198,7 @@ XMBElement XMBElementList::item(const int id) Pos += *(int*)Pos; } // Cache information about this node - m_LastItemID = id; + m_LastItemID = id; m_LastPointer = Pos; return XMBElement(Pos); @@ -245,5 +245,5 @@ XMBAttribute XMBAttributeList::item(const int id) m_LastItemID = id; m_LastPointer = Pos; - return XMBAttribute(*(int*)Pos, utf16string( (utf16_t*)(Pos+8) )); + return XMBAttribute(*(int*)Pos, utf16string( (utf16_t*)(Pos+8) )); } diff --git a/source/ps/XeroXMB.h b/source/ps/XeroXMB.h index 63eb23f350..1b61bdb3f4 100755 --- a/source/ps/XeroXMB.h +++ b/source/ps/XeroXMB.h @@ -1,4 +1,4 @@ -/* $Id: XeroXMB.h,v 1.6 2004/10/07 20:49:25 philip Exp $ +/* $Id: XeroXMB.h,v 1.7 2004/11/11 07:09:32 markt Exp $ Xeromyces - XMB reading library @@ -200,7 +200,7 @@ class XMBAttributeList { public: XMBAttributeList(char* offset, int count) - : Count(count), m_Pointer(offset) {}; + : Count(count), m_Pointer(offset), m_LastItemID( -2 ) {}; // Get the attribute value directly (unlike Xerces) utf16string getNamedItem(const int AttributeName) const; diff --git a/source/ps/scripting/JSCollection.h b/source/ps/scripting/JSCollection.h index 079ec55962..0cde09cd4c 100755 --- a/source/ps/scripting/JSCollection.h +++ b/source/ps/scripting/JSCollection.h @@ -158,7 +158,7 @@ template JSBool CJSCollection::G return( JS_TRUE ); } - *vp = ToJSVal( set->at( index ) ); + *vp = ToJSVal( set->at( index ) ); return( JS_TRUE ); } @@ -276,7 +276,7 @@ template JSBool CJSCollection::S int i = 0; for( it = Set->begin(); it != Set->end(); it++ ) - if( Predicate.Run( ToScript( &( *it ) ) ) ) + if( Predicate.Run( ToScript( (T*)&( *it ) ) ) ) CollectionData->m_Data->push_back( *it ); *rval = OBJECT_TO_JSVAL( Collection ); diff --git a/source/scripting/DOMEvent.h b/source/scripting/DOMEvent.h index 2cacaa8f10..c154908841 100755 --- a/source/scripting/DOMEvent.h +++ b/source/scripting/DOMEvent.h @@ -22,7 +22,7 @@ public: // Target (currently unused) // EventTarget* m_Target; - // Listening object currently being processed (currentky unused) + // Listening object currently being processed (currently unused) // EventTarget* m_CurrentTarget; // Phase type (currently unused) diff --git a/source/scripting/JSConversions.cpp b/source/scripting/JSConversions.cpp index 1017da5b3e..22296c0652 100755 --- a/source/scripting/JSConversions.cpp +++ b/source/scripting/JSConversions.cpp @@ -33,9 +33,9 @@ template<> JSObject* ToScript( CBaseEntity** Native ) return( ToScript( *Native ) ); } -// CObjectEntry* +// CObjectEntry -template<> bool ToPrimitive( JSContext* cx, jsval v, CObjectEntry*& Storage ) +template<> bool ToPrimitive( JSContext* cx, jsval v, CObjectEntry*& Storage ) { CStrW ActorName; if( !ToPrimitive( cx, v, ActorName ) ) @@ -44,7 +44,7 @@ template<> bool ToPrimitive( JSContext* cx, jsval v, CObjectEntry return( true ); } -template<> jsval ToJSVal( CObjectEntry*& Native ) +template<> jsval ToJSVal( CObjectEntry*& Native ) { if( !Native ) return( ToJSVal( CStrW( L"[No actor]" ) ) ); @@ -80,19 +80,19 @@ template<> bool ToPrimitive( JSContext* cx, jsval v, CScriptObjec return( true ); } -// i32 +// int -template<> jsval ToJSVal( const i32& Native ) +template<> jsval ToJSVal( const int& Native ) { return( INT_TO_JSVAL( Native ) ); } -template<> jsval ToJSVal( i32& Native ) +template<> jsval ToJSVal( int& Native ) { return( INT_TO_JSVAL( Native ) ); } -template<> bool ToPrimitive( JSContext* cx, jsval v, i32& Storage ) +template<> bool ToPrimitive( JSContext* cx, jsval v, int& Storage ) { try { @@ -105,6 +105,33 @@ template<> bool ToPrimitive( JSContext* cx, jsval v, i32& Storage ) return( true ); } +// uint + +template<> jsval ToJSVal( const uint& Native ) +{ + return( INT_TO_JSVAL( Native ) ); +} + +template<> jsval ToJSVal( uint& Native ) +{ + return( INT_TO_JSVAL( Native ) ); +} + +template<> bool ToPrimitive( JSContext* cx, jsval v, uint& Storage ) +{ + try + { + int t = g_ScriptingHost.ValueToInt( v ); + if( t < 0 ) return( false ); + Storage = t; + } + catch( PSERROR_Scripting_ConversionFailed ) + { + return( false ); + } + return( true ); +} + // double template<> jsval ToJSVal( const double& Native ) diff --git a/source/scripting/JSConversions.h b/source/scripting/JSConversions.h index 0f22ce2594..359e1e3fe7 100755 --- a/source/scripting/JSConversions.h +++ b/source/scripting/JSConversions.h @@ -22,6 +22,12 @@ class CVector3D; template T* ToNative( JSContext* cx, JSObject* obj ) { +#ifndef NDEBUG + if( OBJECT_TO_JSVAL( obj ) == JSVAL_NULL ) + return( NULL ); + assert( JS_GetClass( obj ) == &T::JSI_class ); + return( (T*)JS_GetPrivate( cx, obj ) ); +#endif return( (T*)JS_GetInstancePrivate( cx, obj, &T::JSI_class, NULL ) ); } @@ -46,14 +52,35 @@ template bool ToPrimitive( JSContext* cx, jsval v, T& Storage ) return( true ); } +// Handle pointer-to-objects sensibly (by automatically dereferencing them one level) +template bool ToPrimitive( JSContext* cx, jsval v, T*& Storage ) +{ + T* Native = ToNative( v ); + if( !Native ) return( false ); + Storage = Native; + 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 ) ); } +template inline T ToPrimitive( jsval v ) { return( ToPrimitive( g_ScriptingHost.GetContext(), v ) ); } template jsval ToJSVal( T& Native ) { return( OBJECT_TO_JSVAL( ToScript( &Native ) ) ); } +template jsval ToJSVal( T*& Native ) +{ + return( OBJECT_TO_JSVAL( ToScript( Native ) ) ); +} + template jsval ToJSVal( const T& Native ); // ----- @@ -71,8 +98,8 @@ template<> bool ToPrimitive( JSContext* cx, jsval v, CBaseEntity*& template<> JSObject* ToScript( CBaseEntity** Native ); // CObjectEntry -template<> bool ToPrimitive( JSContext* cx, jsval v, CObjectEntry*& Storage ); -template<> jsval ToJSVal( CObjectEntry*& Native ); +template<> bool ToPrimitive( JSContext* cx, jsval v, CObjectEntry*& Storage ); +template<> jsval ToJSVal( CObjectEntry*& Native ); // HEntity template<> HEntity* ToNative( JSContext* cx, JSObject* obj ); @@ -82,10 +109,15 @@ template<> JSObject* ToScript( HEntity* Native ); template<> bool ToPrimitive( JSContext* cx, jsval v, CScriptObject& Storage ); template<> jsval ToJSVal( CScriptObject& Native ); -// i32 -template<> bool ToPrimitive( JSContext* cx, jsval v, i32& Storage ); -template<> jsval ToJSVal( const i32& Native ); -template<> jsval ToJSVal( i32& Native ); +// int +template<> bool ToPrimitive( JSContext* cx, jsval v, int& Storage ); +template<> jsval ToJSVal( const int& Native ); +template<> jsval ToJSVal( int& Native ); + +// uint +template<> bool ToPrimitive( JSContext* cx, jsval v, uint& Storage ); +template<> jsval ToJSVal( const uint& Native ); +template<> jsval ToJSVal( uint& Native ); // double template<> bool ToPrimitive( JSContext* cx, jsval v, double& Storage ); diff --git a/source/scripting/JSSerialization.h b/source/scripting/JSSerialization.h new file mode 100755 index 0000000000..c85acf806a --- /dev/null +++ b/source/scripting/JSSerialization.h @@ -0,0 +1,134 @@ +// Functions for (de)serialization of jsvals +// +// Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk) + +#include "Serialization.h" +#include "JSConversions.h" +#include "CStr.h" + +class jsval_ser : public ISerializable +{ + enum + { + TAG_BOOLEAN_FALSE, + TAG_BOOLEAN_TRUE, + TAG_INT, + TAG_DOUBLE, + TAG_STRING, + TAG_NOT_SERIALIZABLE = -1 + } m_tag; + jsval m_data; +public: + jsval_ser() : m_tag( TAG_NOT_SERIALIZABLE ) + { + } + jsval_ser( jsval data ) : m_data( data ) + { + if( m_data == JSVAL_FALSE ) + m_tag = TAG_BOOLEAN_FALSE; + if( m_data == JSVAL_TRUE ) + m_tag = TAG_BOOLEAN_TRUE; + if( JSVAL_IS_INT( m_data ) ) + m_tag = TAG_INT; + if( JSVAL_IS_DOUBLE( m_data ) ) + m_tag = TAG_DOUBLE; + if( JSVAL_IS_STRING( m_data ) ) + m_tag = TAG_STRING; + m_tag = TAG_NOT_SERIALIZABLE; + } + operator jsval() const + { + return( m_data ); + } + operator CStr() const + { + return( ToPrimitive( m_data ) ); + } + uint GetSerializedLength() const + { + switch( m_tag ) + { + case TAG_BOOLEAN_FALSE: + case TAG_BOOLEAN_TRUE: + return( 1 ); + case TAG_INT: + return( 5 ); + case TAG_DOUBLE: + return( 9 ); + case TAG_STRING: + return( 1 + (ToPrimitive(m_data)).GetSerializedLength() ); + default: + assert( 0 && "An attempt was made to serialize a jsval other than a number, boolean or string." ); + return( 1 ); + } + } + u8* Serialize( u8* buffer ) const + { + Serialize_int_1( buffer, m_tag ); + switch( m_tag ) + { + case TAG_BOOLEAN_FALSE: + case TAG_BOOLEAN_TRUE: + break; + case TAG_INT: + { + u32 ival = JSVAL_TO_INT( m_data ); + Serialize_int_4( buffer, ival ); + } + break; + case TAG_DOUBLE: + // Ehm. I think this works, but I can't say as it's something I've tried before. + { + u64 ival = *( (u64*)JSVAL_TO_DOUBLE( m_data ) ); + Serialize_int_8( buffer, ival ); + } + break; + case TAG_STRING: + buffer = ( ToPrimitive( m_data ) ).Serialize( buffer ); + break; + default: + assert( 0 && "An attempt was made to serialize a jsval other than a number, boolean or string." ); + break; + } + return( buffer ); + } + const u8* Deserialize( const u8* buffer, const u8* end ) + { + Deserialize_int_1( buffer, (u8&)m_tag ); + switch( m_tag ) + { + case TAG_BOOLEAN_FALSE: + m_data = JSVAL_FALSE; + break; + case TAG_BOOLEAN_TRUE: + m_data = JSVAL_TRUE; + break; + case TAG_INT: + { + u32 ival; + Deserialize_int_4( buffer, ival ); + m_data = INT_TO_JSVAL( ival ); + } + break; + case TAG_DOUBLE: + // Ehm. I think this works, but I can't say as it's something I've tried before. + { + u64 ival; + Deserialize_int_8( buffer, ival ); + JS_NewDoubleValue( g_ScriptingHost.GetContext(), *( (double*)(&ival) ), &m_data ); + } + break; + case TAG_STRING: + { + CStrW ival; + buffer = ival.Deserialize( buffer, end ); + m_data = ToJSVal( ival ); + } + break; + default: + assert( 0 && "An attempt was made to deserialize a jsval other than a number, boolean or string." ); + break; + } + return( buffer ); + } +}; \ No newline at end of file diff --git a/source/scripting/ScriptGlue.h b/source/scripting/ScriptGlue.h index 014cfec44a..34ba2d45c8 100755 --- a/source/scripting/ScriptGlue.h +++ b/source/scripting/ScriptGlue.h @@ -31,6 +31,7 @@ JSFunc getGlobal; JSFunc setCursor; +JSFunc GetGameObject; JSFunc startServer; JSFunc joinGame; JSFunc startGame; diff --git a/source/scripting/ScriptableObject.h b/source/scripting/ScriptableObject.h index 50df4aa545..cb04f0edbf 100755 --- a/source/scripting/ScriptableObject.h +++ b/source/scripting/ScriptableObject.h @@ -4,13 +4,40 @@ // // Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk) // -// I really, really hope this is the last time I touch this code. +// 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" -// The Last Redesign - #ifndef SCRIPTABLE_INCLUDED #define SCRIPTABLE_INCLUDED @@ -31,7 +58,7 @@ public: jsval Get() { return( Get( g_ScriptingHost.GetContext() ) ); } void Set( jsval Value ) { return( Set( g_ScriptingHost.GetContext(), Value ) ); } - + virtual ~IJSProperty() {} }; @@ -44,6 +71,10 @@ public: // Used for freshen/update typedef void (IJSObject::*NotifyFn)(); + // Property getters and setters + typedef jsval (IJSObject::*GetFn)(); + typedef void (IJSObject::*SetFn)( jsval ); + // Properties of this object PropertyTable m_Properties; @@ -55,26 +86,28 @@ public: // Set the base, and rebuild void SetBase( IJSObject* m_Parent ); + + // Rebuild any intrinsic (mapped-to-C++-variable) properties virtual void Rebuild() = 0; // Check for a property virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0; - // Add a property (inherits value from parent) - virtual void ReplicateProperty( CStrW PropertyName, jsval Value ) = 0; + // Retrieve the value of a property + virtual void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) = 0; // Add a property (with immediate value) virtual void AddProperty( CStrW PropertyName, jsval Value ) = 0; virtual void AddProperty( CStrW PropertyName, CStrW Value ) = 0; }; -template class CJSObject; +template class CJSObject; template class CJSPropertyAccessor { T* m_Owner; CStrW m_PropertyRoot; - template friend class CJSObject; + template friend class CJSObject; public: CJSPropertyAccessor( T* Owner, CStrW PropertyRoot ) @@ -95,6 +128,7 @@ public: 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 ); @@ -115,7 +149,21 @@ public: CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj ); if( !Instance ) return( JS_TRUE ); - *rval = Instance->m_Owner->m_Properties[Instance->m_PropertyRoot]->Get( cx ); + // 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 ); + break; + } + Target = Target->m_Parent; + } return( JS_TRUE ); } @@ -124,7 +172,23 @@ public: CJSPropertyAccessor* Instance = (CJSPropertyAccessor*)JS_GetPrivate( cx, obj ); if( !Instance ) return( JS_TRUE ); - JSString* str = JS_ValueToString( cx, Instance->m_Owner->m_Properties[Instance->m_PropertyRoot]->Get( cx ) ); + // 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 ) ); + break; + } + Target = Target->m_Parent; + } + *rval = STRING_TO_JSVAL( str ); return( JS_TRUE ); @@ -148,7 +212,6 @@ template JSClass CJSPropertyAccessor::JSI_Class = { NULL, NULL, NULL, NULL }; - template class CJSProperty : public IJSProperty { T* m_Data; @@ -175,7 +238,7 @@ public: jsval Get( JSContext* cx ) { if( m_Freshen ) (m_Owner->*m_Freshen)(); - return( ToJSVal( *m_Data ) ); + return( ToJSVal( *m_Data ) ); } void ImmediateCopy( IJSProperty* Copy ) { @@ -183,29 +246,46 @@ public: } void Set( JSContext* cx, jsval Value ) { - if( !ReadOnly ) // I think all our compilers are intelligent enough to optimize this away. + if( !ReadOnly ) { if( m_Freshen ) (m_Owner->*m_Freshen)(); - if( ToPrimitive( cx, Value, *m_Data ) ) + if( ToPrimitive( cx, Value, *m_Data ) ) if( m_Update ) (m_Owner->*m_Update)(); } } + }; -class CJSValProperty : public IJSProperty +class CJSReflector { - template friend class CJSObject; + 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; + } +}; + +class CJSValProperty : public CJSDynamicProperty +{ + template friend class CJSObject; jsval m_Data; - JSObject* m_JSAccessor; public: CJSValProperty( jsval Data, bool Inherited ) { m_Inherited = Inherited; m_Data = Data; - m_Intrinsic = false; - m_JSAccessor = NULL; Root(); } ~CJSValProperty() @@ -215,12 +295,16 @@ public: void Root() { if( JSVAL_IS_GCTHING( m_Data ) ) - JS_AddRoot( g_ScriptingHost.GetContext(), &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(), &m_Data ); + JS_RemoveRoot( g_ScriptingHost.GetContext(), (void*)&m_Data ); } jsval Get( JSContext* cx ) { @@ -240,6 +324,43 @@ public: } }; +class CJSFunctionProperty : public IJSProperty +{ + IJSObject* m_Owner; + + // Function on Owner to get the value + IJSObject::GetFn m_Getter; + + // Function on Owner to set the value + IJSObject::SetFn m_Setter; + +public: + CJSFunctionProperty( IJSObject* Owner, IJSObject::GetFn Getter, IJSObject::SetFn Setter ) + { + m_Inherited = false; + m_Intrinsic = true; + m_Owner = Owner; + m_Getter = Getter; + m_Setter = Setter; + // Must at least be able to read + assert( m_Owner && m_Getter ); + } + jsval Get( JSContext* cx ) + { + return( (m_Owner->*m_Getter)() ); + } + void Set( JSContext* cx, jsval Value ) + { + if( m_Setter ) + (m_Owner->*m_Setter)( Value ); + } + void ImmediateCopy( IJSProperty* Copy ) + { + assert( 0 && "ImmediateCopy called on a property wrapping getter/setter functions" ); + } +}; + + // Wrapper around native functions that are attached to CJSObjects template class CNativeFunction @@ -257,10 +378,19 @@ public: } }; -template class CJSObject : public IJSObject +template class CJSObject : public IJSObject { + typedef STL_HASH_MAP ReflectorTable; + JSObject* m_JS; + ReflectorTable m_Reflectors; + public: + // Whether native code is responsible for managing this object. + // Script constructors should clear this *BEFORE* creating a JS + // mirror (otherwise it'll be rooted). + + bool m_EngineOwned; // JS Property access void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ); @@ -275,25 +405,23 @@ public: // Already exists prop->Set( cx, *vp ); - if( AllowInheritance ) + // If it's a C++ property, reflect this change in objects that inherit this. + if( prop->m_AllowsInheritance && prop->m_Intrinsic ) { - if( prop->m_AllowsInheritance ) - { - // Run along and update this property in any inheritors - InheritorsList UpdateSet( m_Inheritors ); + InheritorsList UpdateSet( m_Inheritors ); - while( !UpdateSet.empty() ) + while( !UpdateSet.empty() ) + { + IJSObject* UpdateObj = UpdateSet.back(); + UpdateSet.pop_back(); + IJSProperty* UpdateProp = UpdateObj->m_Properties[PropertyName]; + // Property must exist, also be a C++ property, and not have its value specified. + if( UpdateProp && UpdateProp->m_Intrinsic && UpdateProp->m_Inherited ) { - IJSObject* UpdateObj = UpdateSet.back(); - UpdateSet.pop_back(); - IJSProperty* UpdateProp = UpdateObj->m_Properties[PropertyName]; - if( UpdateProp->m_Inherited ) - { - UpdateProp->Set( cx, *vp ); - InheritorsList::iterator it2; - for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ ) - UpdateSet.push_back( *it2 ); - } + UpdateProp->Set( cx, *vp ); + InheritorsList::iterator it2; + for( it2 = UpdateObj->m_Inheritors.begin(); it2 != UpdateObj->m_Inheritors.end(); it2++ ) + UpdateSet.push_back( *it2 ); } } } @@ -311,7 +439,7 @@ public: // static JSBool JSGetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { - CJSObject* Instance = ToNative( cx, obj ); + CJSObject* Instance = ToNative( cx, obj ); if( !Instance ) return( JS_TRUE ); @@ -323,7 +451,7 @@ public: } static JSBool JSSetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { - CJSObject* Instance = ToNative( cx, obj ); + CJSObject* Instance = ToNative( cx, obj ); if( !Instance ) return( JS_TRUE ); @@ -347,14 +475,17 @@ public: delete[]( JSI_methods ); } -private: - void CreateScriptObject() + + static void DefaultFinalize( JSContext *cx, JSObject *obj ) { - assert( !m_JS ); - m_JS = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL ); - JS_AddRoot( g_ScriptingHost.GetContext(), m_JS ); - JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, this ); + CJSObject* Instance = ToNative< CJSObject >( cx, obj ); + if( !Instance || Instance->m_EngineOwned ) + return; + + delete( Instance ); + JS_SetPrivate( cx, obj, NULL ); } + public: static JSClass JSI_class; JSObject* GetScript() @@ -363,89 +494,132 @@ public: 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, 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 void JSFinalize( JSContext* cx, JSObject* obj ); public: CJSObject() { m_Parent = NULL; m_JS = NULL; + m_EngineOwned = true; } - ~CJSObject() + virtual ~CJSObject() + { + Shutdown(); + } + void Shutdown() { PropertyTable::iterator it; for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) { if( !it->second->m_Intrinsic ) { - CJSValProperty* extProp = (CJSValProperty*)it->second; + CJSDynamicProperty* extProp = (CJSValProperty*)it->second; if( extProp->m_JSAccessor ) { - CJSPropertyAccessor< CJSObject >* accessor = (CJSPropertyAccessor< CJSObject >*)JS_GetPrivate( g_ScriptingHost.GetContext(), 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 ); - } + JS_RemoveRoot( g_ScriptingHost.GetContext(), &( extProp->m_JSAccessor ) ); + } } delete( it->second ); } - if( m_JS ) + + + ReflectorTable::iterator it_a; + for( it_a = m_Reflectors.begin(); it_a != m_Reflectors.end(); it_a++ ) { - JS_SetPrivate( g_ScriptingHost.GetContext(), m_JS, NULL ); - JS_RemoveRoot( g_ScriptingHost.GetContext(), m_JS ); - } + 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 ); + } + ReleaseScriptObject(); } void SetBase( IJSObject* Parent ) - { - if( AllowInheritance ) +{ + if( m_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 ); - // TODO: Remove any properties we were inheriting from this parent that we didn't specify ourselves - } - m_Parent = Parent; - if( m_Parent ) - { - // Place this in the list of our parent's inheritors - m_Parent->m_Inheritors.push_back( this ); - Rebuild(); - } + // 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() { - if( AllowInheritance ) + PropertyTable::iterator it; + // For each intrinsic property we have, + for( it = m_Properties.begin(); it != m_Properties.end(); it++ ) { - PropertyTable::iterator it; - // For each property in the parent - for( it = m_Parent->m_Properties.begin(); it != m_Parent->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 ) { - if( !it->second->m_AllowsInheritance ) - continue; - PropertyTable::iterator cp; - // Attempt to locate it in this object - cp = m_Properties.find( it->first ); - if( cp != m_Properties.end() ) - { - if( cp->second->m_Inherited ) - cp->second->ImmediateCopy( it->second ); - } - else - m_Properties[it->first] = new CJSValProperty( it->second->Get(), true ); + assert( cp->m_Intrinsic ); + it->second->ImmediateCopy( cp ); } - InheritorsList::iterator c; - for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ ) - (*c)->Rebuild(); } + + // Now recurse. + InheritorsList::iterator c; + for( c = m_Inheritors.begin(); c != m_Inheritors.end(); c++ ) + (*c)->Rebuild(); + } IJSProperty* HasProperty( CStrW PropertyName ) { @@ -456,33 +630,22 @@ public: return( it->second ); } - void ReplicateProperty( CStrW PropertyName, jsval Value ) - { - if( AllowInheritance ) - { - m_Properties[PropertyName] = new CJSValProperty( Value, true ); - // Run through our descendants to add the property to all of them that don't - // already have it. - InheritorsList::iterator it; - for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ ) - if( !((*it)->HasProperty( PropertyName ) ) ) - (*it)->ReplicateProperty( PropertyName, Value ); - } - } - void AddProperty( CStrW PropertyName, jsval Value ) { assert( !HasProperty( PropertyName ) ); - m_Properties[PropertyName] = new CJSValProperty( Value, false ); + CJSDynamicProperty* newProp = new CJSValProperty( Value, false ); + m_Properties[PropertyName] = newProp; - if( AllowInheritance ) + ReflectorTable::iterator it; + it = m_Reflectors.find( PropertyName ); + if( it != m_Reflectors.end() ) { - // Run through our descendants to add the property to all of them that don't - // already have it. - InheritorsList::iterator it; - for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ ) - if( !((*it)->HasProperty( PropertyName ) ) ) - (*it)->ReplicateProperty( PropertyName, Value ); + // We had an accessor pointing to this property before it was defined. + newProp->m_JSAccessor = it->second->m_JSAccessor; + JS_RemoveRoot( g_ScriptingHost.GetContext(), &( it->second->m_JSAccessor ) ); + JS_AddRoot( g_ScriptingHost.GetContext(), &( newProp->m_JSAccessor ) ); + delete( it->second ); + m_Reflectors.erase( it ); } } @@ -490,58 +653,102 @@ public: { AddProperty( PropertyName, ToJSVal( Value ) ); } + void AddProperty( CStrW PropertyName, GetFn Getter, SetFn Setter = NULL ) + { + m_Properties[PropertyName] = new CJSFunctionProperty( this, 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 void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = AllowInheritance, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + template void AddProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) { m_Properties[PropertyName] = new CJSProperty( Native, this, PropAllowInheritance, Update, Refresh ); } - template void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = AllowInheritance, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) + template void AddReadOnlyProperty( CStrW PropertyName, PropType* Native, bool PropAllowInheritance = true, NotifyFn Update = NULL, NotifyFn Refresh = NULL ) { - assert( !( PropAllowInheritance && !AllowInheritance ) ); m_Properties[PropertyName] = new CJSProperty( Native, this, PropAllowInheritance, Update, Refresh ); } }; -template JSClass CJSObject::JSI_class = { +template JSClass CJSObject::JSI_class = { NULL, JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JSGetProperty, JSSetProperty, JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, + JS_ConvertStub, DefaultFinalize, NULL, NULL, NULL, NULL }; -template JSPropertySpec CJSObject::JSI_props[] = { +template JSPropertySpec CJSObject::JSI_props[] = { { 0 }, }; -template std::vector CJSObject::m_Methods; +template std::vector CJSObject::m_Methods; -template void CJSObject::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) +template void CJSObject::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) { IJSProperty* Property = HasProperty( PropertyName ); - if( Property ) + if( Property && Property->m_Intrinsic ) { - if( Property->m_Intrinsic ) + *vp = Property->Get( cx ); + } + else + { + CJSDynamicProperty* extProp; + + if( Property ) { - *vp = Property->Get( cx ); - } - else - { - CJSValProperty* extProp = (CJSValProperty*)Property; + extProp = (CJSValProperty*)Property; + if( !extProp->m_JSAccessor ) { - extProp->m_JSAccessor = CJSPropertyAccessor< CJSObject >::CreateAccessor( cx, this, PropertyName ); - JS_AddRoot( cx, 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; + + // 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 ); + } } } diff --git a/source/scripting/ScriptingHost.h b/source/scripting/ScriptingHost.h index ba50eaee01..229e6e639a 100755 --- a/source/scripting/ScriptingHost.h +++ b/source/scripting/ScriptingHost.h @@ -31,7 +31,7 @@ ERROR_TYPE(Scripting_DefineType, CreationFailed); // Make JS debugging a little easier by automatically naming GC roots #ifndef NDEBUG // Don't simply #define NAME_ALL_GC_ROOTS, because jsapi.h is horridly broken -# define JS_AddRoot(cx, rp) JS_AddNamedRoot((cx), (rp), __FILE__) +# define JS_AddRoot(cx, rp) JS_AddNamedRoot((cx), (rp), __FILE__ ) #endif #include diff --git a/source/simulation/BaseEntity.cpp b/source/simulation/BaseEntity.cpp index af6395829f..9ab5872ffb 100755 --- a/source/simulation/BaseEntity.cpp +++ b/source/simulation/BaseEntity.cpp @@ -15,7 +15,11 @@ CBaseEntity::CBaseEntity() AddProperty( L"parent", (CBaseEntity**)&m_base, false ); AddProperty( L"actions.move.speed", &m_speed ); AddProperty( L"actions.move.turningradius", &m_turningRadius ); + AddProperty( L"actions.attack.range", &m_meleeRange ); + AddProperty( L"actions.attack.rangemin", &m_meleeRangeMin ); AddProperty( L"actor", &m_actorObject ); + AddProperty( L"traits.extant", &m_extant ); + AddProperty( L"traits.corpse", &m_corpse ); for( int t = 0; t < EVENT_LAST; t++ ) AddProperty( EventNames[t], &m_EventHandlers[t] ); @@ -23,6 +27,11 @@ CBaseEntity::CBaseEntity() m_base = NULL; m_actorObject = NULL; + + // Initialize, make life a little easier on the scriptors + m_speed = m_turningRadius = m_meleeRange = m_meleeRangeMin = 0.0f; + m_extant = true; m_corpse = NULL; + m_bound_type = CBoundingObject::BOUND_NONE; m_bound_circle = NULL; m_bound_box = NULL; @@ -244,8 +253,8 @@ void CBaseEntity::XMLLoadProperty( const CXeromyces& XeroFile, const XMBElement& void CBaseEntity::ScriptingInit() { - AddMethod( "toString", 0 ); - CJSObject::ScriptingInit( "EntityTemplate" ); + AddMethod( "toString", 0 ); + CJSObject::ScriptingInit( "EntityTemplate" ); } // Script-bound functions diff --git a/source/simulation/BaseEntity.h b/source/simulation/BaseEntity.h index 31a79bccad..f3ac59b463 100755 --- a/source/simulation/BaseEntity.h +++ b/source/simulation/BaseEntity.h @@ -27,7 +27,7 @@ #include "ScriptObject.h" #include "Xeromyces.h" -class CBaseEntity : public CJSObject +class CBaseEntity : public CJSObject { public: CBaseEntity(); @@ -40,10 +40,14 @@ public: // Base stats CBaseEntity* m_base; + CStrW m_corpse; + bool m_extant; + CStrW m_Base_Name; // <- We don't guarantee the order XML files will be loaded in, so we'll store the name of the // parent entity referenced, then, after all files are loaded, attempt to match names to objects. CObjectEntry* m_actorObject; + CStrW m_Tag; CBoundingCircle* m_bound_circle; @@ -51,6 +55,9 @@ public: CBoundingObject::EBoundingType m_bound_type; float m_speed; + float m_meleeRange; + float m_meleeRangeMin; + float m_turningRadius; CScriptObject m_EventHandlers[EVENT_LAST]; diff --git a/source/simulation/BaseEntityCollection.cpp b/source/simulation/BaseEntityCollection.cpp index c54d888514..6499d1281d 100755 --- a/source/simulation/BaseEntityCollection.cpp +++ b/source/simulation/BaseEntityCollection.cpp @@ -48,22 +48,46 @@ void CBaseEntityCollection::loadTemplates() // Fix up parent links in the templates. - std::vector::iterator it; - for( it = m_templates.begin(); it != m_templates.end(); it++ ) + std::vector::iterator it, it_done; + std::vector done; + + // TODO: MT: Circular references check. + + while( done.size() < m_templates.size() ) { - if( !( (*it)->m_Base_Name.Length() ) ) - continue; - - CBaseEntity* Base = getTemplate( (*it)->m_Base_Name ); - if( Base ) + for( it = m_templates.begin(); it != m_templates.end(); it++ ) { - (*it)->m_base = Base; - (*it)->loadBase(); - } - else - LOG( WARNING, LOG_CATEGORY, "Parent template %s does not exist in template %s", CStr8( (*it)->m_Base_Name ).c_str(), CStr8( (*it)->m_Tag ).c_str() ); - } + if( !( (*it)->m_Base_Name.Length() ) ) + { + done.push_back( *it ); + continue; + } + CBaseEntity* Base = getTemplate( (*it)->m_Base_Name ); + if( Base ) + { + // Check whether it's been loaded yet. + for( it_done = done.begin(); it_done != done.end(); it_done++ ) + { + if( *it_done == Base ) + { + (*it)->m_base = Base; + (*it)->loadBase(); + Base = NULL; + break; + } + } + if( !Base ) + { + // Done + done.push_back( *it ); + continue; + } + } + else + LOG( WARNING, LOG_CATEGORY, "Parent template %s does not exist in template %s", CStr8( (*it)->m_Base_Name ).c_str(), CStr8( (*it)->m_Tag ).c_str() ); + } + } } void CBaseEntityCollection::LoadDirectory( Handle directory, CStr pathname ) diff --git a/source/simulation/Collision.cpp b/source/simulation/Collision.cpp index 3a13cec3db..80b5661dba 100755 --- a/source/simulation/Collision.cpp +++ b/source/simulation/Collision.cpp @@ -94,7 +94,9 @@ bool getRayIntersection( const CVector2D& source, const CVector2D& forward, cons assert( (*it)->m_bounds ); if( (*it)->m_bounds == destinationCollisionObject ) continue; - if( (*it)->m_moving ) continue; + // TODO MT: Replace this with something based on whether the unit is actually moving. + if( (*it)->m_orderQueue.size() ) continue; + CBoundingObject* obj = (*it)->m_bounds; delta = obj->m_pos - source; closestApproach = delta.dot( right ); diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index e890620b4c..908e079895 100755 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -29,11 +29,13 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) AddProperty( L"actions.move.speed", &m_speed ); AddProperty( L"selected", &m_selected, false, (NotifyFn)&CEntity::checkSelection ); AddProperty( L"group", &m_grouped, false, (NotifyFn)&CEntity::checkGroup ); - AddProperty( L"extant", &m_extant, false, (NotifyFn)&CEntity::checkExtant ); + AddProperty( L"traits.extant", &m_extant ); + AddProperty( L"traits.corpse", &m_corpse ); AddProperty( L"actions.move.turningradius", &m_turningRadius ); + AddProperty( L"actions.attack.range", &m_meleeRange ); + AddProperty( L"actions.attack.rangemin", &m_meleeRangeMin ); AddProperty( L"position", &m_graphics_position, false, (NotifyFn)&CEntity::teleport ); AddProperty( L"orientation", &m_graphics_orientation, false, (NotifyFn)&CEntity::reorient ); - for( int t = 0; t < EVENT_LAST; t++ ) AddProperty( EventNames[t], &m_EventHandlers[t] ); @@ -42,7 +44,8 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) m_actor = NULL; m_bounds = NULL; - m_moving = false; + m_lastState = -1; + m_transition = true; m_base = base; @@ -57,8 +60,7 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) m_graphics_position = m_position; m_graphics_orientation = m_orientation; - m_extant = true; - m_extant_mirror = true; + m_destroyed = false; m_selected = false; @@ -114,9 +116,9 @@ void CEntity::kill() if( m_bounds ) delete( m_bounds ); m_bounds = NULL; - m_extant = false; - m_extant_mirror = false; - + m_destroyed = true; + Shutdown(); + if( m_actor ) { g_UnitMan.RemoveUnit( m_actor ); @@ -154,10 +156,17 @@ void CEntity::update( size_t timestep ) m_position_previous = m_position; m_orientation_previous = m_orientation; + // The process[...] functions return 'true' if the order at the top of the stack + // still needs to be (re-)evaluated; else 'false' to terminate the processing of + // this entity in this timestep. + while( !m_orderQueue.empty() ) { CEntityOrder* current = &m_orderQueue.front(); + m_transition = ( current->m_type != m_lastState ); + m_lastState = current->m_type; + switch( current->m_type ) { case CEntityOrder::ORDER_GOTO_NOPATHING: @@ -165,24 +174,55 @@ void CEntity::update( size_t timestep ) case CEntityOrder::ORDER_GOTO_SMOOTHED: if( processGotoNoPathing( current, timestep ) ) break; return; + case CEntityOrder::ORDER_ATTACK_MELEE: + if( processAttackMeleeNoPathing( current, timestep ) ) break; + return; + case CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING: + if( processAttackMeleeNoPathing( current, timestep ) ) break; + return; case CEntityOrder::ORDER_GOTO: if( processGoto( current, timestep ) ) break; return; case CEntityOrder::ORDER_PATROL: if( processPatrol( current, timestep ) ) break; return; + case CEntityOrder::ORDER_PATH_END_MARKER: + m_orderQueue.pop_front(); + break; default: assert( 0 && "Invalid entity order" ); } } - if( m_moving ) + if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() ) { - m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_IdleAnim ); - m_moving = false; + if( m_extant ) + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_IdleAnim ); + else + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_CorpseAnim ); + m_lastState = -1; } } +void CEntity::Initialize() +{ + CEventInitialize evt; + DispatchEvent( &evt ); +} + +void CEntity::Tick() +{ + CEventTick evt; + DispatchEvent( &evt ); +} + +void CEntity::Damage( CDamageType& damage, CEntity* inflictor ) +{ + CEventDamage evt( inflictor, &damage ); + DispatchEvent( &evt ); +} + +/* void CEntity::dispatch( const CMessage* msg ) { @@ -197,7 +237,9 @@ void CEntity::dispatch( const CMessage* msg ) case CMessage::EMSG_INIT: { CEventInitialize Init; - DispatchEvent( &Init ); + if( !DispatchEvent( &Init ) ) + break; + if( m_base->m_Tag == CStrW( L"Prometheus Dude" ) ) { if( getCollisionObject( this ) ) @@ -206,23 +248,6 @@ void CEntity::dispatch( const CMessage* msg ) kill(); return; } - /* - std::vector* waypoints = g_EntityManager.matches( isWaypoint ); - while( !waypoints->empty() ) - { - CEntityOrder patrol; - size_t id = rand() % waypoints->size(); - std::vector::iterator it = waypoints->begin(); - it += id; - HEntity waypoint = *it; - patrol.m_type = CEntityOrder::ORDER_PATROL; - patrol.m_data[0].location.x = waypoint->m_position.X; - patrol.m_data[0].location.y = waypoint->m_position.Z; - pushOrder( patrol ); - waypoints->erase( it ); - } - delete( waypoints ); - */ } break; } @@ -233,13 +258,15 @@ void CEntity::dispatch( const CMessage* msg ) clearOrders(); pushOrder( m->order ); break; + case CMessage::EMSG_DAMAGE: + CEntityOrder* o; } } +*/ bool CEntity::DispatchEvent( CScriptEvent* evt ) { - m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt ); - return( false ); + return( m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt ) ); } void CEntity::clearOrders() @@ -252,16 +279,30 @@ 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 ); + return( DispatchEvent( &evt ) ); + + /* // Hardcoding... switch( orderType ) { case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_PATROL: return( m_speed > 0.0f ); + case CEntityOrder::ORDER_ATTACK_MELEE: + return( orderTarget && ( m_meleeRange > 0.0f ) ); } return( false ); + */ } void CEntity::repath() @@ -318,13 +359,6 @@ void CEntity::checkGroup() g_Selection.changeGroup( me, m_grouped ); } -void CEntity::checkExtant() -{ - if( m_extant && !( (bool)m_extant_mirror ) ) - kill(); - // Sorry. Dead stuff stays dead. -} - void CEntity::interpolate( float relativeoffset ) { m_graphics_position = Interpolate( m_position_previous, m_position, relativeoffset ); @@ -521,10 +555,13 @@ void CEntity::renderSelectionOutline( float alpha ) void CEntity::ScriptingInit() { - AddMethod( "toString", 0 ); - AddMethod( "order", 1 ); - AddMethod( "orderQueued", 1 ); - CJSObject::ScriptingInit( "Entity", Construct, 2 ); + AddMethod( "toString", 0 ); + AddMethod( "order", 1 ); + AddMethod( "orderQueued", 1 ); + AddMethod( "kill", 0 ); + AddMethod( "damage", 1 ); + AddMethod( "isIdle", 0 ); + CJSObject::ScriptingInit( "Entity", Construct, 2 ); } // Script constructor @@ -582,8 +619,7 @@ JSBool CEntity::Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsva HEntity handle = g_EntityManager.create( baseEntity, position, orientation ); - CMessage message( CMessage::EMSG_INIT ); - handle->dispatch( &message ); + handle->Initialize(); *rval = ToJSVal( *handle ); return( JS_TRUE ); @@ -640,11 +676,87 @@ bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued ) JS_ReportError( cx, "Invalid location" ); return( false ); } - g_Scheduler.pushFrame( ORDER_DELAY, me, new CMessageOrder( newOrder, Queued ) ); - return( true ); + break; + case CEntityOrder::ORDER_ATTACK_MELEE: + if( argc < 1 ) + { + JS_ReportError( cx, "Too few parameters" ); + return( false ); + } + CEntity* target; + target = ToNative( argv[1] ); + if( !target ) + { + JS_ReportError( cx, "Invalid target" ); + return( false ); + } + newOrder.m_data[0].entity = target->me; + break; default: JS_ReportError( cx, "Invalid order type" ); return( false ); } + + if( !Queued ) + clearOrders(); + pushOrder( newOrder ); + + return( true ); } +bool CEntity::Damage( JSContext* cx, uintN argc, jsval* argv ) +{ + CEntity* inflictor = NULL; + + if( argc >= 4 ) + inflictor = ToNative( argv[3] ); + + if( argc >= 3 ) + { + Damage( CDamageType( ToPrimitive( argv[0] ), ToPrimitive( argv[1] ), ToPrimitive( argv[2] ) ), inflictor ); + return( true ); + } + + if( argc >= 2 ) + inflictor = ToNative( argv[1] ); + + // If it's a DamageType, use that. Otherwise, see if it's a float, if so, use + // that as the 'typeless' unblockable damage type. + + CDamageType* dmg = ToNative( argv[0] ); + + if( !dmg ) + { + float dmgN; + if( !ToPrimitive( cx, argv[0], dmgN ) ) + return( false ); + + Damage( CDamageType( dmgN ), inflictor ); + return( true ); + } + + Damage( *dmg, inflictor ); + return( true ); +} + +bool CEntity::Kill( JSContext* cx, uintN argc, jsval* argv ) +{ + // Change this entity's template to the corpse entity - but note + // we don't fiddle with the actors or bounding information that we + // usually do when changing templates. + + CBaseEntity* corpse = g_EntityTemplateCollection.getTemplate( m_corpse ); + if( corpse ) + { + m_base = corpse; + SetBase( m_base ); + } + + g_Selection.removeAll( me ); + + clearOrders(); + + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_DeathAnim, true ); + + return( true ); +} diff --git a/source/simulation/Entity.h b/source/simulation/Entity.h index fc83b81457..66ab33ad41 100755 --- a/source/simulation/Entity.h +++ b/source/simulation/Entity.h @@ -45,22 +45,33 @@ #include "EntityMessage.h" #include "EventHandlers.h" -class CEntityManager; +#include "EntitySupport.h" -class CEntity : public CJSObject +// TODO MT: Put this is /some/ sort of order... + +class CEntity : public CJSObject { friend class CEntityManager; public: // Intrinsic properties CBaseEntity* m_base; + + // The entity to switch to when this dies. + CStrW m_corpse; float m_speed; float m_turningRadius; + float m_meleeRange; + float m_meleeRangeMin; bool m_selected; i32 m_grouped; - bool m_extant; // Don't want JS to have direct write-access to these. (Things that should be done might not be) - bool m_extant_mirror; // plus this way limits the number of nasty semantics to work around. + // If this unit has been removed from the gameworld but has still + // has references. + bool m_destroyed; + + // If this unit is still active in the gameworld - i.e. not a corpse. + bool m_extant; //-- Interpolated property CVector3D m_position; @@ -95,13 +106,19 @@ public: bool DispatchEvent( CScriptEvent* evt ); CUnit* m_actor; - bool m_moving; + + // State transition in the FSM (animations should be reset) + bool m_transition; + int m_lastState; std::deque m_orderQueue; private: CEntity( CBaseEntity* base, CVector3D position, float orientation ); + EGotoSituation processGotoHelper( CEntityOrder* current, size_t timestep_milli, HEntity& collide ); + bool processAttackMelee( CEntityOrder* current, size_t timestep_milli ); + bool processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_milli ); bool processGotoNoPathing( CEntityOrder* current, size_t timestep_milli ); bool processGoto( CEntityOrder* current, size_t timestep_milli ); bool processPatrol( CEntityOrder* current, size_t timestep_milli ); @@ -112,9 +129,6 @@ public: // Handle-to-self. HEntity me; - // Process an event - void dispatch( const CMessage* msg ); - // Updates gameplay information for the specified timestep void update( size_t timestep_millis ); // Updates graphical information for a point between the last and current simulation frame; 0 < relativeoffset < 1. @@ -123,6 +137,15 @@ public: // Removes entity from the gameworld and deallocates it, but not neccessarily immediately. void kill(); + // Process initialization + void Initialize(); + + // Process tick. + void Tick(); + + // Process damage + void Damage( CDamageType& damage, CEntity* inflictor = NULL ); + void snapToGround(); void updateActorTransforms(); @@ -138,10 +161,12 @@ public: void reorient(); // Orientation void teleport(); // Fixes things if the position is changed by something externally. - void checkSelection(); // In case anyone tries to select/deselect this through JavaScript. You'd think they'd have something better to do. + void checkSelection(); // In case anyone tries to select/deselect this through JavaScript. void checkGroup(); // Groups void checkExtant(); // Existance + // 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 ); @@ -164,7 +189,12 @@ public: { return( Order( cx, argc, argv, true ) ); } - + bool Damage( JSContext* cx, uintN argc, jsval* argv ); + bool Kill( JSContext* cx, uintN argc, jsval* argv ); + bool IsIdle( JSContext* cx, uintN argc, jsval* argv ) + { + return( m_orderQueue.empty() ); + } static void ScriptingInit(); }; @@ -178,5 +208,4 @@ extern int SELECTION_CIRCLE_POINTS; extern int SELECTION_BOX_POINTS; extern int SELECTION_SMOOTHNESS_UNIFIED; - #endif diff --git a/source/simulation/EntityHandles.cpp b/source/simulation/EntityHandles.cpp index 8a70bb6546..d49f120390 100755 --- a/source/simulation/EntityHandles.cpp +++ b/source/simulation/EntityHandles.cpp @@ -45,6 +45,24 @@ bool HEntity::operator ==( const HEntity& test ) const return( m_handle == test.m_handle ); } +HEntity::operator bool() const +{ + if( m_handle == INVALID_HANDLE ) + return( false ); + + assert( g_EntityManager.m_entities[m_handle].m_refcount ); + return( !g_EntityManager.m_entities[m_handle].m_entity->m_destroyed ); +} + +bool HEntity::operator!() const +{ + if( m_handle == INVALID_HANDLE ) + return( true ); + + assert( g_EntityManager.m_entities[m_handle].m_refcount ); + return( g_EntityManager.m_entities[m_handle].m_entity->m_destroyed ); +} + void HEntity::addRef() { if( m_handle != INVALID_HANDLE ) diff --git a/source/simulation/EntityHandles.h b/source/simulation/EntityHandles.h index 761492d2b1..36b6146023 100755 --- a/source/simulation/EntityHandles.h +++ b/source/simulation/EntityHandles.h @@ -53,7 +53,8 @@ public: void operator=( const HEntity& copy ); bool operator==( const HEntity& test ) const; bool operator!=( const HEntity& test ) const { return( !operator==( test ) ); } - operator bool() const { return( m_handle != INVALID_HANDLE ); } + operator bool() const; + bool operator!() const; operator CEntity*() const; ~HEntity(); diff --git a/source/simulation/EntityManager.cpp b/source/simulation/EntityManager.cpp index bde1329732..c531fa6bf9 100755 --- a/source/simulation/EntityManager.cpp +++ b/source/simulation/EntityManager.cpp @@ -54,7 +54,7 @@ std::vector* CEntityManager::matches( EntityPredicate predicate ) { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) if( predicate( m_entities[i].m_entity ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); @@ -64,7 +64,7 @@ std::vector* CEntityManager::matches( EntityPredicate predicate1, Entit { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) if( predicate1( m_entities[i].m_entity ) && predicate2( m_entities[i].m_entity ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); @@ -74,17 +74,33 @@ std::vector* CEntityManager::getExtant() { std::vector* activelist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) activelist->push_back( HEntity( i ) ); return( activelist ); } +/* void CEntityManager::dispatchAll( CMessage* msg ) { for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->dispatch( msg ); } +*/ + +void CEntityManager::InitializeAll() +{ + for( int i = 0; i < MAX_HANDLES; i++ ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) + m_entities[i].m_entity->Initialize(); +} + +void CEntityManager::TickAll() +{ + for( int i = 0; i < MAX_HANDLES; i++ ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) + m_entities[i].m_entity->Tick(); +} void CEntityManager::updateAll( size_t timestep ) { @@ -93,25 +109,24 @@ void CEntityManager::updateAll( size_t timestep ) delete( *it ); m_reaper.clear(); - CMessage Tick_msg( CMessage::EMSG_TICK ); - dispatchAll( &Tick_msg ); + TickAll(); for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) m_entities[i].m_entity->update( timestep ); } void CEntityManager::interpolateAll( float relativeoffset ) { for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) m_entities[i].m_entity->interpolate( relativeoffset ); } void CEntityManager::renderAll() { for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( m_entities[i].m_refcount && !m_entities[i].m_entity->m_destroyed ) m_entities[i].m_entity->render(); } diff --git a/source/simulation/EntityManager.h b/source/simulation/EntityManager.h index 774fa3d272..c7b6b44b33 100755 --- a/source/simulation/EntityManager.h +++ b/source/simulation/EntityManager.h @@ -46,7 +46,8 @@ public: HEntity* getByHandle( u16 index ); void updateAll( size_t timestep ); void interpolateAll( float relativeoffset ); - void dispatchAll( CMessage* msg ); + void InitializeAll(); + void TickAll(); void renderAll(); std::vector* matches( EntityPredicate predicate ); std::vector* matches( EntityPredicate predicate1, EntityPredicate predicate2 ); diff --git a/source/simulation/EntityMessage.h b/source/simulation/EntityMessage.h index 6b18084a5d..f81f313720 100755 --- a/source/simulation/EntityMessage.h +++ b/source/simulation/EntityMessage.h @@ -4,8 +4,7 @@ // // Entity message structure. // -// Usage: Currently, does not support any data to be included with messages. -// Message types are currently: EMSG_TICK: Unused. +// Usage: Message types are currently: EMSG_TICK: Sent once per sim frame. // EMSG_INIT: When a new entity is instantiated. // At map loading, do not issue this message immediately // for each entity as it is loaded; instead, wait for all @@ -13,10 +12,12 @@ // of them simultaneously. // EMSG_ORDER:To push a message into the entity's order queue +/* #ifndef MESSAGING_INCLUDED #define MESSAGING_INCLUDED #include "EntityOrders.h" +#include "EntitySupport.h" struct CMessage { @@ -25,6 +26,7 @@ struct CMessage EMSG_TICK, EMSG_INIT, EMSG_ORDER, + EMSG_DAMAGE } type; CMessage( EMessageType _type ) { @@ -39,4 +41,12 @@ struct CMessageOrder : public CMessage bool queue; }; +struct CMessageDamage : public CMessage +{ + CMessageDamage( HEntity _inflictor, CDamageType _damage ) : CMessage( EMSG_DAMAGE ), inflictor( inflictor ), damage( damage ) {} + HEntity inflictor; + CDamageType damage; +} + #endif +*/ \ No newline at end of file diff --git a/source/simulation/EntityOrders.h b/source/simulation/EntityOrders.h index 07f7a4aa81..66cc1be931 100755 --- a/source/simulation/EntityOrders.h +++ b/source/simulation/EntityOrders.h @@ -23,6 +23,10 @@ // order queue after it's executed. In this way, the entity will // circle round a list of patrol points. // Create this order when a standard patrol order is required. +// ORDER_ATTACK_MELEE: Move towards target entity; start bashing it when close enough. +// If we collide with something (=> line-of-sight tracking no longer +// sufficient) spawns a ORDER_GOTO to target's location and pushes it +// immediately in front of this order. // // Entities which exhaust all orders from their queue go to idle status; there is no specific order // type for this status. @@ -52,6 +56,9 @@ public: ORDER_GOTO_COLLISION, ORDER_GOTO, ORDER_PATROL, + ORDER_ATTACK_MELEE, + ORDER_ATTACK_MELEE_NOPATHING, + ORDER_PATH_END_MARKER, ORDER_LAST } m_type; SOrderData m_data[ORDER_MAX_DATA]; diff --git a/source/simulation/EntityStateProcessing.cpp b/source/simulation/EntityStateProcessing.cpp index 287d4e2f63..f7b5f2116b 100755 --- a/source/simulation/EntityStateProcessing.cpp +++ b/source/simulation/EntityStateProcessing.cpp @@ -11,7 +11,21 @@ #include "Game.h" -bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_millis ) +enum EGotoSituation +{ + NORMAL = 0, + ALREADY_AT_DESTINATION, + REACHED_DESTINATION, + COLLISION_WITH_DESTINATION, + COLLISION_NEAR_DESTINATION, + COLLISION_OVERLAPPING_OBJECTS, + COLLISION_OTHER, + WOULD_LEAVE_MAP +}; + +// Does all the shared processing for line-of-sight gotos + +EGotoSituation CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis, HEntity& collide ) { float timestep=timestep_millis/1000.0f; @@ -22,15 +36,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli float len = delta.length(); if( len < 0.1f ) - { - if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) - { - repath(); - } - else - m_orderQueue.pop_front(); - return( false ); - } + return( ALREADY_AT_DESTINATION ); // Curve smoothing. // Here there be trig. @@ -74,8 +80,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli // place until we're looking the right way. At least, that's what // seems logical. But in most cases that looks worse. So actually, // let's not. - - + if( current->m_type != CEntityOrder::ORDER_GOTO_SMOOTHED ) m_orientation = m_targetorientation; @@ -86,13 +91,16 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli m_orientation = m_targetorientation; } - - if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) ((CBoundingBox*)m_bounds)->setOrientation( m_ahead ); + EGotoSituation rc = NORMAL; + if( scale > len ) + { scale = len; + rc = REACHED_DESTINATION; + } delta = m_ahead * scale; @@ -103,7 +111,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli m_bounds->setPosition( m_position.X, m_position.Z ); - HEntity collide = getCollisionObject( this ); + collide = getCollisionObject( this ); if( collide ) { @@ -123,17 +131,13 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli // Erm... do nothing? - return( false ); + return( COLLISION_OVERLAPPING_OBJECTS ); } // No. Is our destination within the obstacle? if( collide->m_bounds->contains( current->m_data[0].location ) ) - { - // Yes? All well and good, then. Stop here. - m_orderQueue.pop_front(); - return( false ); - } - + return( COLLISION_WITH_DESTINATION ); + // No. Are we nearing our destination, do we wish to stop there, and is it obstructed? if( ( m_orderQueue.size() == 1 ) && ( len <= 10.0f ) ) @@ -142,30 +146,84 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli if( getCollisionObject( &destinationObs ) ) { // Yes. (Chances are a bunch of units were tasked to the same destination) - // Here's a wierd idea: (I hope it works) - // Spiral round the destination until a free point is found. - float interval = destinationObs.m_radius; - float r = interval, theta = 0.0f, delta; - float _x = current->m_data[0].location.x, _y = current->m_data[0].location.y; - - while( true ) - { - delta = interval / r; - theta += delta; - r += ( interval * delta ) / ( 2 * PI ); - destinationObs.setPosition( _x + r * cosf( theta ), _y + r * sinf( theta ) ); - if( !getCollisionObject( &destinationObs ) ) break; - } - - // Reset our destination - current->m_data[0].location.x = _x + r * cosf( theta ); - current->m_data[0].location.y = _y + r * sinf( theta ); - - return( false ); + return( COLLISION_NEAR_DESTINATION ); } } - // No? Path around it. + // No? + return( COLLISION_OTHER ); + + } + + // Will we step off the map? + if( !g_Game->GetWorld()->GetTerrain()->isOnMap( m_position.X, m_position.Z ) ) + { + // Yes. That's not a particularly good idea, either. + + m_position.X -= delta.x; + m_position.Z -= delta.y; + m_bounds->setPosition( m_position.X, m_position.Z ); + + // All things being equal, we should only get here while on a collision path + // (No destination should be off the map) + + return( WOULD_LEAVE_MAP ); + } + + // No. I suppose it's OK to go there, then. *disappointed* + + return( rc ); +} + + +bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_millis ) +{ + HEntity collide; + switch( processGotoHelper( current, timestep_millis, collide ) ) + { + case ALREADY_AT_DESTINATION: + // If on a collision path; decide where to go next. Otherwise, proceed to the next waypoint. + if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) + { + repath(); + } + else + m_orderQueue.pop_front(); + return( false ); + case COLLISION_OVERLAPPING_OBJECTS: + return( false ); + case COLLISION_WITH_DESTINATION: + // We're here... + m_orderQueue.pop_front(); + return( false ); + case COLLISION_NEAR_DESTINATION: + { + // Here's a wierd idea: (I hope it works) + // Spiral round the destination until a free point is found. + CBoundingCircle destinationObs( current->m_data[0].location.x, current->m_data[0].location.y, m_bounds->m_radius ); + + float interval = destinationObs.m_radius; + float r = interval, theta = 0.0f, delta; + float _x = current->m_data[0].location.x, _y = current->m_data[0].location.y; + + while( true ) + { + delta = interval / r; + theta += delta; + r += ( interval * delta ) / ( 2 * PI ); + destinationObs.setPosition( _x + r * cosf( theta ), _y + r * sinf( theta ) ); + if( !getCollisionObject( &destinationObs ) ) break; + } + + // Reset our destination + current->m_data[0].location.x = _x + r * cosf( theta ); + current->m_data[0].location.y = _y + r * sinf( theta ); + + return( false ); + } + case COLLISION_OTHER: + { + // Path around it. CEntityOrder avoidance; avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION; @@ -194,27 +252,145 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli m_orderQueue.pop_front(); m_orderQueue.push_front( avoidance ); return( false ); - } - - // Will we step off the map? - if( !g_Game->GetWorld()->GetTerrain()->isOnMap( m_position.X, m_position.Z ) ) - { - // Yes. That's not a particularly good idea, either. - - m_position.X -= delta.x; - m_position.Z -= delta.y; - m_bounds->setPosition( m_position.X, m_position.Z ); - - // All things being equal, we should only get here while on a collision path - // (No destination should be off the map) - + case WOULD_LEAVE_MAP: // Just stop here, repath if necessary. - m_orderQueue.pop_front(); + return( false ); + default: + return( false ); + } +} + +bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis ) +{ + m_orderQueue.pop_front(); + + if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant ) + return( false ); + + current->m_data[0].location = current->m_data[0].entity->m_position; + + if( ( current->m_data[0].location - m_position ).length() < m_meleeRange ) + { + current->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING; + return( true ); } - // No. I suppose it's OK to go there, then. *disappointed* + if( m_transition ) + { + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim ); + // Animation desync + m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f ); + } + + // The pathfinder will push its result back into this unit's queue. + + g_Pathfinder.requestMeleeAttackPath( me, current->m_data[0].entity ); + + return( true ); +} + +bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_millis ) +{ + // Target's dead? Then our work here is done. + if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant ) + { + m_orderQueue.pop_front(); + return( false ); + } + + // Still playing attack animation? Suspend processing. + if( m_actor->GetModel()->GetAnimation() == m_actor->GetObject()->m_MeleeAnim ) + return( false ); + + // Just transitioned? No animation? (=> melee just finished) Play walk. + if( m_transition || !m_actor->GetModel()->GetAnimation() ) + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim ); + + CVector2D delta = current->m_data[0].entity->m_position - m_position; + + float adjRange = m_meleeRange + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius; + float adjMinRange = m_meleeRangeMin + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius; + + if( delta.within( adjMinRange ) ) + { + // Too close... do nothing. + return( false ); + } + + if( !delta.within( adjRange ) ) + { + // Too far away at the moment, chase after the target... + // We're aiming to end up at a location just inside our maximum range + // (is this good enough?) + + delta = delta.normalize() * ( adjRange - m_bounds->m_radius ); + + current->m_data[0].location = (CVector2D)current->m_data[0].entity->m_position - delta; + + HEntity collide; + switch( processGotoHelper( current, timestep_millis, collide ) ) + { + case ALREADY_AT_DESTINATION: + case REACHED_DESTINATION: + case COLLISION_WITH_DESTINATION: + // Not too far any more... + break; + case NORMAL: + // May or may not be close enough, check... + // (Assuming the delta above will never take us within minimum range) + delta = current->m_data[0].entity->m_position - m_position; + if( delta.within( adjRange ) ) + break; + // Otherwise, continue chasing + return( false ); + default: + // Path around it. + + CEntityOrder avoidance; + avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION; + CVector2D right; + right.x = m_ahead.y; right.y = -m_ahead.x; + CVector2D avoidancePosition; + + // Which is the shortest diversion, going left or right? + // (Weight a little towards the right, to stop both units dodging the same way) + + if( ( collide->m_bounds->m_pos - m_bounds->m_pos ).dot( right ) < 1 ) + { + // Turn right. + avoidancePosition = collide->m_bounds->m_pos + right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); + } + else + { + // Turn left. + avoidancePosition = collide->m_bounds->m_pos - right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); + } + + // Create a short path representing this detour + + avoidance.m_data[0].location = avoidancePosition; + if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) + m_orderQueue.pop_front(); + m_orderQueue.push_front( avoidance ); + return( false ); + } + } + else + { + // Close enough, but turn to face them. + m_orientation = atan2( delta.x, delta.y ); + m_ahead = delta.normalize(); + } + + // Now we've got this far: + // Pointy end goes into the other man... + + CEventAttack AttackEvent( current->m_data[0].entity ); + + if( DispatchEvent( &AttackEvent ) ) + m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_MeleeAnim, true ); return( false ); } @@ -231,11 +407,11 @@ bool CEntity::processGoto( CEntityOrder* current, size_t timestep_millis ) if( ( path_to - pos ).length() < 0.1f ) return( false ); - if( !m_moving ) + if( m_transition ) { m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim ); + // Animation desync m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f ); - m_moving = true; } // The pathfinder will push its result back into this unit's queue. diff --git a/source/simulation/EntitySupport.h b/source/simulation/EntitySupport.h new file mode 100755 index 0000000000..b918e68a57 --- /dev/null +++ b/source/simulation/EntitySupport.h @@ -0,0 +1,71 @@ +// Supporting data types for CEntity and related + +class CEntityManager; +enum EGotoSituation; + +class CDamageType : public CJSObject +{ +public: + float m_Crush; + float m_Hack; + float m_Pierce; + float m_Typeless; + CDamageType() + { + Init( 0.0f, 0.0f, 0.0f, 0.0f ); + } + virtual ~CDamageType() {} + CDamageType( float Crush, float Hack, float Pierce ) + { + Init( Crush, Hack, Pierce, 0.0f ); + } + CDamageType( float Typeless ) + { + Init( 0.0f, 0.0f, 0.0f, Typeless ); + } + CDamageType( float Crush, float Hack, float Pierce, float Typeless ) + { + Init( Crush, Hack, Pierce, Typeless ); + } + void Init( float Crush, float Hack, float Pierce, float Typeless ) + { + m_Crush = Crush; + m_Hack = Hack; + m_Pierce = Pierce; + m_Typeless = Typeless; + AddProperty( L"crush", &m_Crush ); + AddProperty( L"hack", &m_Hack ); + AddProperty( L"pierce", &m_Pierce ); + AddProperty( L"typeless", &m_Typeless ); + } + static void ScriptingInit() + { + CJSObject::ScriptingInit( "DamageType", Construct, 3 ); + } + static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) + { + CDamageType* dt; + + if( argc == 0 ) + dt = new CDamageType(); + else if( argc == 1 ) + dt = new CDamageType( ToPrimitive( argv[0] ) ); + else if( argc == 3 ) + dt = new CDamageType( ToPrimitive( argv[0] ), + ToPrimitive( argv[1] ), + ToPrimitive( argv[2] ) ); + else if( argc == 4 ) + dt = new CDamageType( ToPrimitive( argv[0] ), + ToPrimitive( argv[1] ), + ToPrimitive( argv[2] ), + ToPrimitive( argv[3] ) ); + else + return( JS_FALSE ); + + dt->m_EngineOwned = false; // Let this object be deallocated when JS GCs it. + + *rval = OBJECT_TO_JSVAL( dt->GetScript() ); + + return( JS_TRUE ); + } +}; diff --git a/source/simulation/EventHandlers.cpp b/source/simulation/EventHandlers.cpp new file mode 100755 index 0000000000..c97cae0fb0 --- /dev/null +++ b/source/simulation/EventHandlers.cpp @@ -0,0 +1,33 @@ +#include "precompiled.h" +#include "EventHandlers.h" +#include "Entity.h" + +CEventAttack::CEventAttack( CEntity* target ) : CScriptEvent( L"attack", true, EVENT_ATTACK ) +{ + m_target = target; + AddProperty( L"target", &m_target ); +} + +CEventDamage::CEventDamage( CEntity* inflictor, CDamageType* damage ) : CScriptEvent( L"takesDamage", true, EVENT_DAMAGE ) +{ + m_inflictor = inflictor; + m_damage = damage; + AddReadOnlyProperty( L"inflictor", &m_inflictor ); + AddProperty( L"damage", &m_damage ); +} + +CEventTargetChanged::CEventTargetChanged( CEntity* target ) : CScriptEvent( L"targetChanged", false, EVENT_TARGET_CHANGED ) +{ + m_target = target; + m_defaultAction = -1; + AddReadOnlyProperty( L"target", &m_target ); + AddProperty( L"defaultAction", &m_defaultAction ); +} + +CEventPrepareOrder::CEventPrepareOrder( CEntity* target, int orderType ) : CScriptEvent( L"prepareOrder", true, EVENT_PREPARE_ORDER ) +{ + m_target = target; + m_orderType = orderType; + AddReadOnlyProperty( L"target", &m_target ); + AddReadOnlyProperty( L"orderType", &m_orderType ); +} \ No newline at end of file diff --git a/source/simulation/EventHandlers.h b/source/simulation/EventHandlers.h index 9d64965a49..95bb6c0a66 100755 --- a/source/simulation/EventHandlers.h +++ b/source/simulation/EventHandlers.h @@ -6,28 +6,72 @@ #define EVENT_HANDLERS_INCLUDED #include "scripting/DOMEvent.h" +#include "Vector3D.h" + +class CDamageType; enum EEventType { EVENT_INITIALIZE, EVENT_TICK, + EVENT_ATTACK, + EVENT_DAMAGE, + EVENT_TARGET_CHANGED, + EVENT_PREPARE_ORDER, EVENT_LAST, }; static const wchar_t* EventNames[] = { /* EVENT_INITIALIZE */ L"onInitialize", - /* EVENT_TICK */ L"onTick" + /* EVENT_TICK */ L"onTick", + /* EVENT_ATTACK */ L"onAttack", /* This unit is the one doing the attacking... */ + /* 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 */ }; class CEventInitialize : public CScriptEvent { -public: CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {} +public: + CEventInitialize() : CScriptEvent( L"initialize", false, EVENT_INITIALIZE ) {} }; class CEventTick : public CScriptEvent { -public: CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {} +public: + CEventTick() : CScriptEvent( L"tick", false, EVENT_TICK ) {} +}; + +class CEventAttack : public CScriptEvent +{ + CEntity* m_target; +public: + CEventAttack( CEntity* target ); +}; + +class CEventDamage : public CScriptEvent +{ + CEntity* m_inflictor; + CDamageType* m_damage; +public: + CEventDamage( CEntity* inflictor, CDamageType* damage ); +}; + +class CEventTargetChanged : public CScriptEvent +{ + CEntity* m_target; +public: + int m_defaultAction; + CEventTargetChanged( CEntity* target ); +}; + +class CEventPrepareOrder : public CScriptEvent +{ + CEntity* m_target; + int m_orderType; +public: + CEventPrepareOrder( CEntity* target, int orderType ); }; #endif diff --git a/source/simulation/PathfindEngine.cpp b/source/simulation/PathfindEngine.cpp index 01b5d92d4b..8181bc1b28 100755 --- a/source/simulation/PathfindEngine.cpp +++ b/source/simulation/PathfindEngine.cpp @@ -15,3 +15,21 @@ void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination { pathSparse( entity, destination ); } + +void CPathfindEngine::requestMeleeAttackPath( HEntity entity, HEntity target ) +{ + pathSparse( entity, target->m_position ); + // For attack orders, do some additional postprocessing (replace goto/nopathing + // with attack/nopathing, up until the attack order marker) + std::deque::iterator it; + for( it = entity->m_orderQueue.begin(); it != entity->m_orderQueue.end(); it++ ) + { + if( it->m_type == CEntityOrder::ORDER_PATH_END_MARKER ) + break; + if( it->m_type == CEntityOrder::ORDER_GOTO_NOPATHING ) + { + it->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING; + it->m_data[0].entity = target; + } + } +} \ No newline at end of file diff --git a/source/simulation/PathfindEngine.h b/source/simulation/PathfindEngine.h index dc633daabf..b87727dfdf 100755 --- a/source/simulation/PathfindEngine.h +++ b/source/simulation/PathfindEngine.h @@ -17,11 +17,18 @@ #define g_Pathfinder CPathfindEngine::GetSingleton() +enum EPathType +{ + PF_STANDARD, + PF_ATTACK_MELEE, +}; + class CPathfindEngine : public Singleton { public: CPathfindEngine(); void requestPath( HEntity entity, const CVector2D& destination ); + void requestMeleeAttackPath( HEntity entity, HEntity target ); }; #endif diff --git a/source/simulation/PathfindSparse.cpp b/source/simulation/PathfindSparse.cpp index cdb90a11c1..c9163397b4 100755 --- a/source/simulation/PathfindSparse.cpp +++ b/source/simulation/PathfindSparse.cpp @@ -45,7 +45,12 @@ bool sparsePathTree::slice() CVector2D forward = to - from; float len = forward.length(); - assert( len != 0.0f ); + if( len == 0.0f ) + { + // Too wierd. (Heavy traffic, obstacles in positions leading to this degenerate state. + type = SPF_IMPOSSIBLE; + return( true ); + } forward /= len; CVector2D v_right = CVector2D( forward.y, -forward.x ); @@ -82,6 +87,7 @@ bool sparsePathTree::slice() float length = delta.length(); float offsetDistance = ( turningRadius * length / sqrt( length * length - turningRadius * turningRadius ) ); + left = r.position - v_right * offsetDistance; right = r.position + v_right * offsetDistance; } @@ -189,12 +195,15 @@ void sparsePathTree::pushResults( std::vector& nodelist ) } } -void nodeSmooth( HEntity entity, std::vector& nodelist ) +void nodePostProcess( HEntity entity, std::vector& nodelist ) { std::vector::iterator it; CVector2D next = nodelist.front(); CEntityOrder node; + node.m_type = CEntityOrder::ORDER_PATH_END_MARKER; + entity->m_orderQueue.push_front( node ); + node.m_type = CEntityOrder::ORDER_GOTO_SMOOTHED; node.m_data[0].location = next; @@ -230,7 +239,7 @@ void pathSparse( HEntity entity, CVector2D destination ) // Sanity check: if( source.length() < 0.01f ) return; - sparsePathTree sparseEngine( source, destination, entity, getContainingObject( destination ), SPF_RECURSION_DEPTH ); + sparsePathTree sparseEngine( source, destination, entity, getContainingObject( destination ), 3 ); while( sparseEngine.type & sparsePathTree::SPF_OPEN ) sparseEngine.slice(); // assert( sparseEngine.type & sparsePathTree::SPF_SOLVED ); // Shouldn't be any impossible cases yet. @@ -239,7 +248,7 @@ void pathSparse( HEntity entity, CVector2D destination ) { sparseEngine.pushResults( pathnodes ); pathnodes.push_back( source ); - nodeSmooth( entity, pathnodes ); + nodePostProcess( entity, pathnodes ); } else { diff --git a/source/simulation/PathfindSparse.h b/source/simulation/PathfindSparse.h index f9ff4bbf19..fccb4c250d 100755 --- a/source/simulation/PathfindSparse.h +++ b/source/simulation/PathfindSparse.h @@ -68,7 +68,7 @@ struct sparsePathTree extern int SPF_RECURSION_DEPTH; -void nodeSmooth( HEntity entity, std::vector& nodelist ); +void nodePostProcess( HEntity entity, std::vector& nodelist ); void pathSparse( HEntity entity, CVector2D destination ); bool pathSparseRecursive( HEntity entity, CVector2D from, CVector2D to, CBoundingObject* destinationCollisionObject ); diff --git a/source/simulation/Scheduler.cpp b/source/simulation/Scheduler.cpp index 91a7a3f3e4..af2824534f 100755 --- a/source/simulation/Scheduler.cpp +++ b/source/simulation/Scheduler.cpp @@ -5,6 +5,7 @@ size_t simulationTime; size_t frameCount; +/* void CScheduler::pushTime( size_t delay, const HEntity& destination, const CMessage* message ) { timeMessage.push( SDispatchObjectMessage( destination, simulationTime + delay, message ) ); @@ -14,6 +15,7 @@ void CScheduler::pushFrame( size_t delay, const HEntity& destination, const CMes { frameMessage.push( SDispatchObjectMessage( destination, frameCount + delay, message ) ); } +*/ void CScheduler::pushTime( size_t delay, const CStrW& fragment, JSObject* operateOn ) { @@ -47,27 +49,6 @@ void CScheduler::pushInterval( size_t first, size_t interval, JSFunction* functi void CScheduler::update(size_t simElapsed) { - simulationTime += simElapsed; - frameCount++; - - while( !timeMessage.empty() ) - { - SDispatchObjectMessage top = timeMessage.top(); - if( top.deliveryTime > simulationTime ) - break; - timeMessage.pop(); - top.destination->dispatch( top.message ); - delete( top.message ); - } - while( !frameMessage.empty() ) - { - SDispatchObjectMessage top = frameMessage.top(); - if( top.deliveryTime > frameCount ) - break; - frameMessage.pop(); - top.destination->dispatch( top.message ); - delete( top.message ); - } while( !timeScript.empty() ) { SDispatchObjectScript top = timeScript.top(); diff --git a/source/simulation/Scheduler.h b/source/simulation/Scheduler.h index 72e8248d72..e21b3cbfc1 100755 --- a/source/simulation/Scheduler.h +++ b/source/simulation/Scheduler.h @@ -30,6 +30,7 @@ struct SDispatchObject } }; +/* struct SDispatchObjectMessage : public SDispatchObject { HEntity destination; @@ -40,6 +41,7 @@ struct SDispatchObjectMessage : public SDispatchObject : SDispatchObject( _deliveryTime, recurrence ), destination( _destination ), message( _message ) {} }; +*/ struct SDispatchObjectScript : public SDispatchObject { @@ -63,14 +65,17 @@ struct SDispatchObjectFunction : public SDispatchObject struct CScheduler : public Singleton { - std::priority_queue timeMessage, frameMessage; + // std::priority_queue timeMessage, frameMessage; std::priority_queue timeScript, frameScript; std::priority_queue timeFunction, frameFunction; bool m_abortInterval; + /* void pushTime( size_t delay, const HEntity& destination, const CMessage* message ); void pushFrame( size_t delay, const HEntity& destination, const CMessage* message ); + */ + void pushTime( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL ); void pushFrame( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL ); void pushInterval( size_t first, size_t interval, const CStrW& fragment, JSObject* operateOn = NULL ); diff --git a/source/simulation/ScriptObject.cpp b/source/simulation/ScriptObject.cpp index 530d977207..6ec4f67e6f 100755 --- a/source/simulation/ScriptObject.cpp +++ b/source/simulation/ScriptObject.cpp @@ -69,13 +69,14 @@ bool CScriptObject::Run( JSObject* Context ) return( g_ScriptingHost.ValueToBool( Temp ) ); } -// Treat this as an event handler and dispatch an event to it. -void CScriptObject::DispatchEvent( JSObject* Context, CScriptEvent* evt ) +// 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 ) JS_CallFunction( g_ScriptingHost.GetContext(), Context, Function, 1, &EventObject, &Temp ); + return( !evt->m_Cancelled ); } void CScriptObject::Compile( CStrW FileNameTag, CStrW FunctionBody ) diff --git a/source/simulation/ScriptObject.h b/source/simulation/ScriptObject.h index 2a7388bf83..610f2f27fe 100755 --- a/source/simulation/ScriptObject.h +++ b/source/simulation/ScriptObject.h @@ -39,7 +39,7 @@ public: bool Run( JSObject* Context ); // Treat this as an event handler and dispatch an event to it. - void DispatchEvent( JSObject* Context, CScriptEvent* evt ); + bool DispatchEvent( JSObject* Context, CScriptEvent* evt ); }; #endif diff --git a/source/simulation/Simulation.cpp b/source/simulation/Simulation.cpp index c35e3d2ff8..956261ce52 100755 --- a/source/simulation/Simulation.cpp +++ b/source/simulation/Simulation.cpp @@ -32,8 +32,7 @@ void CSimulation::Initialize(CGameAttributes *pAttribs) { m_pTurnManager->Initialize(m_pGame->GetNumPlayers()); - CMessage init_msg (CMessage::EMSG_INIT); - g_EntityManager.dispatchAll(&init_msg); + g_EntityManager.InitializeAll(); } void CSimulation::Update(double frameTime) @@ -89,7 +88,7 @@ uint CSimulation::TranslateMessage(CNetMessage *pMsg, uint clientMask, void *use entOrder.m_data[0].location.x=(float)msg->m_TargetX; entOrder.m_data[0].location.y=(float)msg->m_TargetY; CEntity *ent=msg->m_Entity; - ent->dispatch(new CMessageOrder(entOrder)); + ent->pushOrder( entOrder ); break; }