Index: ps/trunk/source/graphics/GameView.h =================================================================== --- ps/trunk/source/graphics/GameView.h (revision 2621) +++ ps/trunk/source/graphics/GameView.h (revision 2622) @@ -1,100 +1,107 @@ #ifndef _GameView_H #define _GameView_H #include "Camera.h" #include "Vector3D.h" #include "scripting/ScriptableObject.h" +// note: cannot forward declare "struct SDL_Event" - that triggers an +// internal error in VC7.1 at msc1.cpp(2701). +// we include the full header instead. *sigh* +#include "sdl.h" + class CGame; class CGameAttributes; class CWorld; class CTerrain; class CUnitManager; class CProjectileManager; class CModel; class CGameView: public CJSObject { CGame *m_pGame; CWorld *m_pWorld; CCamera m_Camera; //////////////////////////////////////// // 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; float m_ViewZoomSmoothness; // 0.0 = instantaneous zooming, 1.0 = so slow it never moves float m_ViewSnapSmoothness; // Just the same. //////////////////////////////////////// // Camera Controls State CVector3D m_CameraDelta; CVector3D m_CameraPivot; //float m_CameraZoom; std::vector m_CameraTargets; // RenderTerrain: iterate through all terrain patches and submit all patches // in viewing frustum to the renderer void RenderTerrain(CTerrain *pTerrain); // RenderModels: iterate through model list and submit all models in viewing // frustum to the Renderer void RenderModels(CUnitManager *pUnitMan, CProjectileManager *pProjectileManager); // SubmitModelRecursive: recurse down given model, submitting it and all its // descendents to the renderer void SubmitModelRecursive(CModel *pModel); // InitResources(): Load all graphics resources (textures, actor objects and // alpha maps) required by the game void InitResources(); // UnloadResources(): Unload all graphics resources loaded by InitResources void UnloadResources(); // JS Interface bool JSI_StartCustomSelection(JSContext *cx, uintN argc, jsval *argv); bool JSI_EndCustomSelection(JSContext *cx, uintN argc, jsval *argv); static void ScriptingInit(); public: CGameView(CGame *pGame); ~CGameView(); void RegisterInit(CGameAttributes *pAttribs); int Initialize(CGameAttributes *pGameAttributes); // Update: Update all the view information (i.e. rotate camera, scroll, // whatever). This will *not* change any World information - only the // *presentation* void Update(float DeltaTime); // Render: Render the World void Render(); // RenderNoCull: render absolutely everything to a blank frame to force // renderer to load required assets void RenderNoCull(); // Camera Control Functions (used by input handler) void ResetCamera(); void ResetCameraOrientation(); void RotateAboutTarget(); void PushCameraTarget( const CVector3D& target ); void SetCameraTarget( const CVector3D& target ); void PopCameraTarget(); inline CCamera *GetCamera() { return &m_Camera; } }; +extern int game_view_handler(const SDL_Event* ev); + #endif Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 2621) +++ ps/trunk/source/graphics/GameView.cpp (revision 2622) @@ -1,714 +1,714 @@ #include "precompiled.h" #include "Terrain.h" #include "Renderer.h" #include "GameView.h" #include "Game.h" #include "Camera.h" #include "Interact.h" #include "Matrix3D.h" #include "Renderer.h" #include "Terrain.h" #include "LightEnv.h" #include "HFTracer.h" #include "TextureManager.h" #include "ObjectManager.h" #include "Pyrogenesis.h" #include "Hotkey.h" #include "ConfigDB.h" #include "Loader.h" #include "Profile.h" #include "LoaderThunks.h" #include "Quaternion.h" #include "Unit.h" #include "Model.h" #include "Projectile.h" #include "sdl.h" #include "input.h" #include "lib.h" #include "timer.h" extern int g_xres, g_yres; extern bool g_active; CVector3D cameraBookmarks[10]; bool bookmarkInUse[10] = { false, false, false, false, false, false, false, false, false, false }; i8 currentBookmark = -1; CGameView::CGameView(CGame *pGame): m_pGame(pGame), m_pWorld(pGame->GetWorld()), 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), m_ViewZoomSmoothness(0.02f), m_ViewSnapSmoothness(0.02f), m_CameraPivot(), m_CameraDelta() { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m_Camera.SetViewPort(&vp); m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); m_Camera.m_Orientation.Translate (100, 150, -100); g_Renderer.SetCamera(m_Camera); ONCE( ScriptingInit(); ); } CGameView::~CGameView() { UnloadResources(); } void CGameView::ScriptingInit() { AddMethod("startCustomSelection", 0); AddMethod("endCustomSelection", 0); CJSObject::ScriptingInit("GameView"); } int CGameView::Initialize(CGameAttributes* UNUSED(pAttribs)) { CFG_GET_SYS_VAL( "view.scroll.speed", Float, m_ViewScrollSpeed ); CFG_GET_SYS_VAL( "view.rotate.speed", Float, m_ViewRotateSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.speed", Float, m_ViewRotateSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.rotate.abouttarget.speed", Float, m_ViewRotateAboutTargetSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.abouttarget.speed", Float, m_ViewRotateAboutTargetSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.drag.speed", Float, m_ViewDragSensitivity ); CFG_GET_SYS_VAL( "view.zoom.speed", Float, m_ViewZoomSensitivity ); CFG_GET_SYS_VAL( "view.zoom.wheel.speed", Float, m_ViewZoomSensitivityWheel ); CFG_GET_SYS_VAL( "view.zoom.smoothness", Float, m_ViewZoomSmoothness ); CFG_GET_SYS_VAL( "view.snap.smoothness", Float, m_ViewSnapSmoothness ); if( ( m_ViewZoomSmoothness < 0.0f ) || ( m_ViewZoomSmoothness > 1.0f ) ) m_ViewZoomSmoothness = 0.02f; if( ( m_ViewSnapSmoothness < 0.0f ) || ( m_ViewSnapSmoothness > 1.0f ) ) m_ViewSnapSmoothness = 0.02f; return 0; } void CGameView::RegisterInit(CGameAttributes *pAttribs) { // CGameView init RegMemFun1(this, &CGameView::Initialize, pAttribs, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 80); RegMemFun(g_ObjMan.GetSingletonPtr(), &CObjectManager::LoadObjects, L"LoadObjects", 1); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 40); } void CGameView::Render() { g_Renderer.SetCamera(m_Camera); MICROLOG(L"render terrain"); PROFILE_START( "render terrain" ); RenderTerrain(m_pWorld->GetTerrain()); PROFILE_END( "render terrain" ); MICROLOG(L"render models"); PROFILE_START( "render models" ); RenderModels(m_pWorld->GetUnitManager(), m_pWorld->GetProjectileManager()); PROFILE_END( "render models" ); } void CGameView::RenderTerrain(CTerrain *pTerrain) { CFrustum frustum=m_Camera.GetFrustum(); u32 patchesPerSide=pTerrain->GetPatchesPerSide(); for (uint j=0; jGetPatch(i,j); if (frustum.IsBoxVisible (CVector3D(0,0,0),patch->GetBounds())) { g_Renderer.Submit(patch); } } } } void CGameView::RenderModels(CUnitManager *pUnitMan, CProjectileManager *pProjectileMan) { CFrustum frustum=m_Camera.GetFrustum(); const std::vector& units=pUnitMan->GetUnits(); for (uint i=0;iGetModel()->GetBounds())) { PROFILE( "submit models" ); SubmitModelRecursive(units[i]->GetModel()); } } const std::vector& projectiles=pProjectileMan->GetProjectiles(); for (uint i=0;iGetModel()->GetBounds())) { PROFILE( "submit projectiles" ); SubmitModelRecursive(projectiles[i]->GetModel()); } } } void CGameView::SubmitModelRecursive(CModel* model) { g_Renderer.Submit(model); const std::vector& props=model->GetProps(); for (uint i=0;iGetUnitManager(); CTerrain *pTerrain=m_pWorld->GetTerrain(); g_Renderer.SetCamera(m_Camera); uint i,j; const std::vector& units=pUnitMan->GetUnits(); for (i=0;iGetModel()); } u32 patchesPerSide=pTerrain->GetPatchesPerSide(); for (j=0; jGetPatch(i,j); g_Renderer.Submit(patch); } } } void CGameView::UnloadResources() { // Should probably do something like: // g_TexMan.UnloadTerrainTextures(); // g_ObjMan.UnloadObjects(); g_Renderer.UnloadAlphaMaps(); } void CGameView::ResetCamera() { // quick hack to return camera home, for screenshots (after alt+tabbing) m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); m_Camera.m_Orientation.Translate (100, 150, -100); } void CGameView::ResetCameraOrientation() { CVector3D origin = m_Camera.m_Orientation.GetTranslation(); CVector3D dir = m_Camera.m_Orientation.GetIn(); CVector3D target = origin + dir * ( ( 50.0f - origin.Y ) / dir.Y ); target -= CVector3D( -22.474480f, 50.0f, 22.474480f ); m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); target += CVector3D( 100.0f, 150.0f, -100.0f ); m_Camera.m_Orientation.Translate( target ); } void CGameView::RotateAboutTarget() { m_CameraPivot = m_Camera.GetWorldCoordinates(); } void CGameView::Update(float DeltaTime) { if (!g_active) return; float delta = powf( m_ViewSnapSmoothness, DeltaTime ); m_Camera.m_Orientation.Translate( m_CameraDelta * ( 1.0f - delta ) ); m_CameraDelta *= delta; #define CAMERASTYLE 2 // 0 = old style, 1 = relatively new style, 2 = newest style // #if CAMERASTYLE == 2 // This could be rewritten much more reliably, so it doesn't e.g. accidentally tilt // the camera, assuming we know exactly what limits the camera should have. // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; // Miscellaneous vectors CVector3D forwards = m_Camera.m_Orientation.GetIn(); CVector3D rightwards = m_Camera.m_Orientation.GetLeft() * -1.0f; // upwards.Cross(forwards); CVector3D upwards( 0.0f, 1.0f, 0.0f ); // rightwards.Normalize(); CVector3D forwards_horizontal = forwards; forwards_horizontal.Y = 0.0f; forwards_horizontal.Normalize(); if( hotkeys[HOTKEY_CAMERA_ROTATE] || hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { // Ctrl + middle-drag or left-and-right-drag to rotate view // Untranslate the camera, so it rotates around the correct point CVector3D position = m_Camera.m_Orientation.GetTranslation(); m_Camera.m_Orientation.Translate(position*-1); // Sideways rotation 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, upways); m_Camera.m_Orientation.Rotate(temp); // Retranslate back to the right position m_Camera.m_Orientation.Translate(position); } else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { CVector3D origin = m_Camera.m_Orientation.GetTranslation(); CVector3D delta = origin - m_CameraPivot; CQuaternion rotateH, rotateV; CMatrix3D rotateM; // Sideways rotation float rightways = (float)mouse_dx * m_ViewRotateAboutTargetSensitivity; rotateH.FromAxisAngle( upwards, rightways ); // Up/down rotation float upways = (float)mouse_dy * m_ViewRotateAboutTargetSensitivity; 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( 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] ) { // Middle-drag to pan m_Camera.m_Orientation.Translate(rightwards * (m_ViewDragSensitivity * mouse_dx)); m_Camera.m_Orientation.Translate(forwards_horizontal * (-m_ViewDragSensitivity * mouse_dy)); } // Mouse movement if( !hotkeys[HOTKEY_CAMERA_ROTATE] && !hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { if (g_mouse_x >= g_xres-2 && g_mouse_x < g_xres) m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime)); else if (g_mouse_x <= 3 && g_mouse_x >= 0) m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime)); if (g_mouse_y >= g_yres-2 && g_mouse_y < g_yres) m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); else if (g_mouse_y <= 3 && g_mouse_y >= 0) m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); } // Keyboard movement (added to mouse movement, so you can go faster if you want) 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_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) static float zoom_delta = 0.0f; if( hotkeys[HOTKEY_CAMERA_ZOOM_WHEEL_IN] ) zoom_delta += m_ViewZoomSensitivityWheel; else if( hotkeys[HOTKEY_CAMERA_ZOOM_WHEEL_OUT] ) zoom_delta -= m_ViewZoomSensitivityWheel; if( hotkeys[HOTKEY_CAMERA_ZOOM_IN] ) zoom_delta += m_ViewZoomSensitivity*DeltaTime; else if( hotkeys[HOTKEY_CAMERA_ZOOM_OUT] ) zoom_delta -= m_ViewZoomSensitivity*DeltaTime; if (fabsf(zoom_delta) > 0.1f) // use a fairly high limit to avoid nasty flickering when zooming { float zoom_proportion = powf(m_ViewZoomSmoothness, DeltaTime); m_Camera.m_Orientation.Translate(forwards * (zoom_delta * (1.0f-zoom_proportion))); zoom_delta *= zoom_proportion; } /* Just commented out to make it more obvious it's not in use. #elif CAMERASTYLE == 1 // Remember previous mouse position, to calculate changes static mouse_last_x = 0; static mouse_last_y = 0; // Miscellaneous vectors CVector3D forwards = m_Camera.m_Orientation.GetIn(); CVector3D upwards (0.0f, 1.0f, 0.0f); CVector3D rightwards = upwards.Cross(forwards); // Click and drag to look around - if (mouseButtons[0]) + if (mouse_buttons[0]) { // Untranslate the camera, so it rotates around the correct point CVector3D position = m_Camera.m_Orientation.GetTranslation(); m_Camera.m_Orientation.Translate(position*-1); // Sideways rotation m_Camera.m_Orientation.RotateY(m_ViewRotateSpeed*(float)(g_mouse_x-mouse_last_x)); // Up/down rotation CQuaternion temp; temp.FromAxisAngle(rightwards, m_ViewRotateSpeed*(float)(g_mouse_y-mouse_last_y)); m_Camera.m_Orientation.Rotate(temp); // Retranslate back to the right position m_Camera.m_Orientation.Translate(position); } mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; // Calculate the necessary vectors for movement rightwards.Normalize(); CVector3D forwards_horizontal = upwards.Cross(rightwards); forwards_horizontal.Normalize(); // Move when desirable if (g_mouse_x >= g_xres-2) m_Camera.m_Orientation.Translate(rightwards); else if (g_mouse_x <= 3) m_Camera.m_Orientation.Translate(-rightwards); if (g_mouse_y >= g_yres-2) m_Camera.m_Orientation.Translate(forwards_horizontal); else if (g_mouse_y <= 3) m_Camera.m_Orientation.Translate(-forwards_horizontal); // Smoothed height-changing (move a certain percentage towards the desired height every frame) static float height_delta = 0.0f; - if (mouseButtons[SDL_BUTTON_WHEELUP]) + if (mouse_buttons[SDL_BUTTON_WHEELUP]) height_delta -= 4.0f; - else if (mouseButtons[SDL_BUTTON_WHEELDOWN]) + else if (mouse_buttons[SDL_BUTTON_WHEELDOWN]) height_delta += 4.0f; const float height_speed = 0.2f; m_Camera.m_Orientation.Translate(0.0f, height_delta*height_speed, 0.0f); height_delta *= (1.0f - height_speed); #else // CAMERASTYLE == 0 const float dx = m_ViewScrollSpeed * DeltaTime; const CVector3D Right(dx,0, dx); const CVector3D Up (dx,0,-dx); if (g_mouse_x >= g_xres-2) m_Camera.m_Orientation.Translate(Right); if (g_mouse_x <= 3) m_Camera.m_Orientation.Translate(Right*-1); if (g_mouse_y >= g_yres-2) m_Camera.m_Orientation.Translate(Up); if (g_mouse_y <= 3) m_Camera.m_Orientation.Translate(Up*-1); /* janwas: grr, plotted the zoom vector on paper twice, but it appears to be completely wrong. sticking with the FOV hack for now. if anyone sees what's wrong, or knows how to correctly implement zoom, please put this code out of its misery :) *//* // RC - added ScEd style zoom in and out (actually moving camera, rather than fudging fov) float dir=0; - if (mouseButtons[SDL_BUTTON_WHEELUP]) dir=-1; - else if (mouseButtons[SDL_BUTTON_WHEELDOWN]) dir=1; + if (mouse_buttons[SDL_BUTTON_WHEELUP]) dir=-1; + else if (mouse_buttons[SDL_BUTTON_WHEELDOWN]) dir=1; float factor=dir*dir; if (factor) { if (dir<0) factor=-factor; CVector3D forward=m_Camera.m_Orientation.GetIn(); // check we're not going to zoom into the terrain, or too far out into space float h=m_Camera.m_Orientation.GetTranslation().Y+forward.Y*factor*m_Camera.Zoom; float minh=65536*HEIGHT_SCALE*1.05f; if (h1500) { // yup, we will; don't move anywhere (do clamped move instead, at some point) } else { // do a full move m_Camera.Zoom-=(factor)*0.1f; if (m_Camera.Zoom<0.01f) m_Camera.Zoom=0.01f; m_Camera.m_Orientation.Translate(forward*(factor*m_Camera.Zoom)); } } #endif // CAMERASTYLE */ m_Camera.UpdateFrustum (); } void CGameView::PushCameraTarget( const CVector3D& target ) { // Save the current position m_CameraTargets.push_back( m_Camera.m_Orientation.GetTranslation() ); // And set the camera SetCameraTarget( target ); } void CGameView::SetCameraTarget( const CVector3D& target ) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference beteen that position and the camera point, and restoring // that difference to our new target) CVector3D CurrentTarget = m_Camera.GetFocus(); m_CameraDelta = target - CurrentTarget; } void CGameView::PopCameraTarget() { m_CameraDelta = m_CameraTargets.back() - m_Camera.m_Orientation.GetTranslation(); m_CameraTargets.pop_back(); } int game_view_handler(const SDL_Event* ev) { // put any events that must be processed even if inactive here if(!g_active || !g_Game) return EV_PASS; CGameView *pView=g_Game->GetView(); switch(ev->type) { case SDL_HOTKEYDOWN: switch(ev->user.code) { case HOTKEY_WIREFRAME: if (g_Renderer.GetTerrainRenderMode()==WIREFRAME) { g_Renderer.SetTerrainRenderMode(SOLID); } else { g_Renderer.SetTerrainRenderMode(WIREFRAME); } return( EV_HANDLED ); case HOTKEY_CAMERA_RESET_ORIGIN: pView->ResetCamera(); return( EV_HANDLED ); case HOTKEY_CAMERA_RESET: pView->ResetCameraOrientation(); return( EV_HANDLED ); case HOTKEY_CAMERA_ROTATE_ABOUT_TARGET: pView->RotateAboutTarget(); return( EV_HANDLED ); default: if( ( ev->user.code >= HOTKEY_CAMERA_BOOKMARK_0 ) && ( ev->user.code <= HOTKEY_CAMERA_BOOKMARK_9 ) ) { // The above test limits it to 10 bookmarks, so don't worry about overflowing i8 id = (i8)( ev->user.code - HOTKEY_CAMERA_BOOKMARK_0 ); if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SAVE] ) { // Attempt to track the ground we're looking at cameraBookmarks[id] = pView->GetCamera()->GetFocus(); bookmarkInUse[id] = true; } else if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SNAP] ) { if( bookmarkInUse[id] && ( currentBookmark == -1 ) ) { pView->PushCameraTarget( cameraBookmarks[id] ); currentBookmark = id; } } else { if( bookmarkInUse[id] ) pView->SetCameraTarget( cameraBookmarks[id] ); } return( EV_HANDLED ); } } case SDL_HOTKEYUP: switch( ev->user.code ) { case HOTKEY_CAMERA_BOOKMARK_SNAP: if( currentBookmark != -1 ) pView->PopCameraTarget(); currentBookmark = -1; break; default: return( EV_PASS ); } return( EV_HANDLED ); } return EV_PASS; } bool CGameView::JSI_StartCustomSelection( JSContext* UNUSED(context), uint UNUSED(argc), jsval* UNUSED(argv)) { StartCustomSelection(); return true; } bool CGameView::JSI_EndCustomSelection( JSContext* UNUSED(context), uint UNUSED(argc), jsval* UNUSED(argv)) { ResetInteraction(); return true; } Index: ps/trunk/source/ps/CConsole.h =================================================================== --- ps/trunk/source/ps/CConsole.h (revision 2621) +++ ps/trunk/source/ps/CConsole.h (revision 2622) @@ -1,120 +1,127 @@ /*************************************************************************************** AUTHOR: John M. Mena EMAIL: JohnMMena@hotmail.com FILE: CConsole.h CREATED: 12/3/03 COMPLETED: NULL DESCRIPTION: The class CConsole provides an interface to the scripting abilities of an engine. ****************************************************************************************/ #include #include #include #include #include "lib/res/graphics/unifont.h" #include "ogl.h" #include "lib.h" #include "sdl.h" #include "CStr.h" +// note: cannot forward declare "struct SDL_Event" - that triggers an +// internal error in VC7.1 at msc1.cpp(2701). +// we include the full header instead. *sigh* +#include "lib/sdl.h" + #ifndef CCONSOLE_H #define CCONSOLE_H #define CONSOLE_BUFFER_SIZE 1024 // for text being typed into the console #define CONSOLE_MESSAGE_SIZE 1024 // for messages being printed into the console typedef void(*fptr)(void); class CConsole { private: float m_fX; float m_fY; float m_fHeight; float m_fWidth; // "position" in show/hide animation, how visible the console is (0..1). // allows implementing other animations than sliding, e.g. fading in/out. float m_fVisibleFrac; std::map m_mapFuncList; std::deque m_deqMsgHistory; std::deque m_deqBufHistory; int m_iMsgHistPos; wchar_t* m_szBuffer; int m_iBufferPos; int m_iBufferLength; CStr m_sHistoryFile; int m_MaxHistoryLines; bool m_bFocus; bool m_bVisible; // console is to be drawn bool m_bToggle; // show/hide animation is currently active void ToLower(wchar_t* szMessage, uint iSize = 0); void Trim(wchar_t* szMessage, const wchar_t cChar = 32, uint iSize = 0); void DrawHistory(void); void DrawWindow(void); void DrawBuffer(void); void DrawCursor(void); bool IsEOB(void) {return (m_iBufferPos == m_iBufferLength);}; //Is end of Buffer? bool IsBOB(void) {return (m_iBufferPos == 0);}; //Is beginning of Buffer? bool IsFull(void) {return (m_iBufferLength == CONSOLE_BUFFER_SIZE);}; bool IsEmpty(void) {return (m_iBufferLength == 0);}; void InsertBuffer(void){InsertMessage(L"%ls", m_szBuffer);}; void ProcessBuffer(const wchar_t* szLine); void LoadHistory(); void SaveHistory(); public: CConsole(); ~CConsole(); void SetSize(float X = 300, float Y = 0, float W = 800, float H = 600); void UpdateScreenSize(int w, int h); void ToggleVisible(); void SetVisible( bool visible ); void Update(float DeltaTime); void Render(); void InsertMessage(const wchar_t* szMessage, ...); void InsertChar(const int szChar, const wchar_t cooked); void SendChatMessage(const wchar_t *szMessage); void ReceivedChatMessage(const wchar_t *pSender, const wchar_t *szMessage); void SetBuffer(const wchar_t* szMessage, ...); void UseHistoryFile( CStr filename, int historysize ); // Only returns a pointer to the buffer; copy out of here if you want to keep it. const wchar_t* GetBuffer(); void FlushBuffer(); void RegisterFunc(fptr F, const wchar_t* szName); bool IsActive() { return m_bVisible; } int m_iFontHeight; int m_iFontOffset; // distance to move up before drawing }; extern CConsole* g_Console; +extern int conInputHandler(const SDL_Event* ev); + #endif Index: ps/trunk/source/ps/World.cpp =================================================================== --- ps/trunk/source/ps/World.cpp (revision 2621) +++ ps/trunk/source/ps/World.cpp (revision 2622) @@ -1,65 +1,74 @@ #include "precompiled.h" #include "CStr.h" #include "CLogger.h" #include "ps/Errors.h" #include "World.h" #include "MapReader.h" #include "Game.h" #include "GameAttributes.h" #include "Terrain.h" #include "LightEnv.h" #include "BaseEntityCollection.h" #include "EntityManager.h" #include "timer.h" #include "Loader.h" #include "LoaderThunks.h" +#include "graphics/MapWriter.h" #define LOG_CATEGORY "world" +// global light settings. this is not a member of CWorld because it is +// passed to the renderer before CWorld exists. CLightEnv g_LightEnv; void CWorld::Initialize(CGameAttributes *pAttribs) { // TODO: Find a better way of handling these global things ONCE(RegMemFun(CBaseEntityCollection::GetSingletonPtr(), &CBaseEntityCollection::loadTemplates, L"LoadTemplates", 30)); // Load the map, if one was specified if (pAttribs->m_MapFile.Length()) { CStr mapfilename("maps/scenarios/"); mapfilename += (CStr)pAttribs->m_MapFile; CMapReader* reader = 0; try { reader = new CMapReader; reader->LoadMap(mapfilename, &m_Terrain, &m_UnitManager, &g_LightEnv); // fails immediately, or registers for delay loading } catch (...) { delete reader; LOG(ERROR, LOG_CATEGORY, "Failed to load map %s", mapfilename.c_str()); throw PSERROR_Game_World_MapLoadFailed(); } } } void CWorld::RegisterInit(CGameAttributes *pAttribs) { Initialize(pAttribs); } CWorld::~CWorld() { // The Entity Manager should perhaps be converted into a CWorld member.. // But for now, we'll just create and delete the global singleton instance // following the creation and deletion of CWorld. // The reason for not keeping the instance around is that we require a // clean slate for each game start. delete &m_EntityManager; delete &m_ProjectileManager; } + + +void CWorld::RewriteMap() +{ + CMapWriter::RewriteAllMaps(&m_Terrain, &m_UnitManager, &g_LightEnv); +} \ No newline at end of file Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 2621) +++ ps/trunk/source/ps/Hotkey.cpp (revision 2622) @@ -1,579 +1,579 @@ #include "precompiled.h" #include "Hotkey.h" #include "input.h" #include "ConfigDB.h" #include "CLogger.h" #include "CConsole.h" #include "CStr.h" extern CConsole* g_Console; extern bool keys[SDLK_LAST]; -extern bool mouseButtons[5]; +extern bool mouse_buttons[5]; bool unified[5]; /* SDL-type */ struct SHotkeyMapping { int mapsTo; bool negation; std::vector requires; }; typedef std::vector KeyMapping; const int HK_MAX_KEYCODES = SDLK_LAST + 10; // A mapping of keycodes onto sets of SDL event codes static KeyMapping hotkeyMap[HK_MAX_KEYCODES]; // An array of the status of virtual keys bool hotkeys[HOTKEY_LAST]; // 'Keycodes' for the mouse buttons const int MOUSE_LEFT = SDLK_LAST + SDL_BUTTON_LEFT; const int MOUSE_RIGHT = SDLK_LAST + SDL_BUTTON_RIGHT; const int MOUSE_MIDDLE = SDLK_LAST + SDL_BUTTON_MIDDLE; const int MOUSE_WHEELUP = SDLK_LAST + SDL_BUTTON_WHEELUP; const int MOUSE_WHEELDOWN = SDLK_LAST + SDL_BUTTON_WHEELDOWN; // 'Keycodes' for the unified modifier keys const int UNIFIED_SHIFT = MOUSE_WHEELDOWN + 1; const int UNIFIED_CTRL = MOUSE_WHEELDOWN + 2; const int UNIFIED_ALT = MOUSE_WHEELDOWN + 3; const int UNIFIED_META = MOUSE_WHEELDOWN + 4; const int UNIFIED_SUPER = MOUSE_WHEELDOWN + 5; struct SHotkeyInfo { int code; const char* name; int defaultmapping1, defaultmapping2; }; // Will phase out the default shortcuts at sometime in the near future // (or, failing that, will update them so they can do the tricky stuff // the config file can.) static SHotkeyInfo hotkeyInfo[] = { { HOTKEY_EXIT, "exit", SDLK_ESCAPE, 0 }, { HOTKEY_SCREENSHOT, "screenshot", SDLK_PRINT, 0 }, { HOTKEY_WIREFRAME, "wireframe", SDLK_w, 0 }, { HOTKEY_CAMERA_RESET, "camera.reset", 0, 0 }, { HOTKEY_CAMERA_RESET_ORIGIN, "camera.reset.origin", SDLK_h, 0 }, { HOTKEY_CAMERA_ZOOM_IN, "camera.zoom.in", SDLK_PLUS, SDLK_KP_PLUS }, { HOTKEY_CAMERA_ZOOM_OUT, "camera.zoom.out", SDLK_MINUS, SDLK_KP_MINUS }, { 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_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, }, { HOTKEY_CAMERA_BOOKMARK_3, "camera.bookmark.3", SDLK_F8, 0, }, { HOTKEY_CAMERA_BOOKMARK_4, "camera.bookmark.4", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_5, "camera.bookmark.5", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_6, "camera.bookmark.6", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_7, "camera.bookmark.7", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_8, "camera.bookmark.8", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_9, "camera.bookmark.9", 0, 0, }, { HOTKEY_CAMERA_BOOKMARK_SAVE, "camera.bookmark.save", 0, 0 }, { HOTKEY_CAMERA_BOOKMARK_SNAP, "camera.bookmark.snap", 0, 0 }, { HOTKEY_CONSOLE_TOGGLE, "console.toggle", SDLK_F1, 0 }, { HOTKEY_CONSOLE_COPY, "console.copy", 0, 0 }, { HOTKEY_CONSOLE_PASTE, "console.paste", 0, 0 }, { HOTKEY_SELECTION_ADD, "selection.add", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_SELECTION_REMOVE, "selection.remove", SDLK_LCTRL, SDLK_RCTRL }, { HOTKEY_SELECTION_GROUP_0, "selection.group.0", SDLK_0, 0, }, { HOTKEY_SELECTION_GROUP_1, "selection.group.1", SDLK_1, 0, }, { HOTKEY_SELECTION_GROUP_2, "selection.group.2", SDLK_2, 0, }, { HOTKEY_SELECTION_GROUP_3, "selection.group.3", SDLK_3, 0, }, { HOTKEY_SELECTION_GROUP_4, "selection.group.4", SDLK_4, 0, }, { HOTKEY_SELECTION_GROUP_5, "selection.group.5", SDLK_5, 0, }, { HOTKEY_SELECTION_GROUP_6, "selection.group.6", SDLK_6, 0, }, { HOTKEY_SELECTION_GROUP_7, "selection.group.7", SDLK_7, 0, }, { HOTKEY_SELECTION_GROUP_8, "selection.group.8", SDLK_8, 0, }, { HOTKEY_SELECTION_GROUP_9, "selection.group.9", SDLK_9, 0, }, { HOTKEY_SELECTION_GROUP_10, "selection.group.10", 0, 0, }, { HOTKEY_SELECTION_GROUP_11, "selection.group.11", 0, 0, }, { HOTKEY_SELECTION_GROUP_12, "selection.group.12", 0, 0, }, { HOTKEY_SELECTION_GROUP_13, "selection.group.13", 0, 0, }, { HOTKEY_SELECTION_GROUP_14, "selection.group.14", 0, 0, }, { HOTKEY_SELECTION_GROUP_15, "selection.group.15", 0, 0, }, { HOTKEY_SELECTION_GROUP_16, "selection.group.16", 0, 0, }, { HOTKEY_SELECTION_GROUP_17, "selection.group.17", 0, 0, }, { HOTKEY_SELECTION_GROUP_18, "selection.group.18", 0, 0, }, { HOTKEY_SELECTION_GROUP_19, "selection.group.19", 0, 0, }, { HOTKEY_SELECTION_GROUP_ADD, "selection.group.add", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_SELECTION_GROUP_SAVE, "selection.group.save", SDLK_LCTRL, SDLK_RCTRL }, { HOTKEY_SELECTION_GROUP_SNAP, "selection.group.snap", SDLK_LALT, SDLK_RALT }, { HOTKEY_SELECTION_SNAP, "selection.snap", SDLK_HOME, 0 }, { HOTKEY_ORDER_QUEUE, "order.queue", SDLK_LSHIFT, SDLK_RSHIFT }, { HOTKEY_CONTEXTORDER_NEXT, "contextorder.next", SDLK_RIGHTBRACKET, 0 }, { HOTKEY_CONTEXTORDER_PREVIOUS, "contextorder.previous", SDLK_LEFTBRACKET, 0 }, { HOTKEY_HIGHLIGHTALL, "highlightall", SDLK_o, 0 }, { HOTKEY_PROFILE_TOGGLE, "profile.toggle", SDLK_F11, 0 }, { HOTKEY_PLAYMUSIC, "playmusic", SDLK_p, 0 } }; /* SDL-type ends */ /* GUI-type */ struct SHotkeyMappingGUI { CStr mapsTo; bool negation; std::vector requires; }; typedef std::vector GuiMapping; // A mapping of keycodes onto sets of hotkey name strings (e.g. '[hotkey.]camera.reset') static GuiMapping hotkeyMapGUI[HK_MAX_KEYCODES]; typedef std::vector GUIObjectList; // A list of GUI objects typedef std::map GUIHotkeyMap; // A mapping of name strings to lists of GUI objects that they trigger static GUIHotkeyMap guiHotkeyMap; // Look up a key binding in the config file and set the mappings for // all key combinations that trigger it. void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) { CConfigValueSet* binding = g_ConfigDB.GetValues( CFG_USER, CStr( "hotkey." ) + hotkeyName ); if( binding ) { int mapping; CConfigValueSet::iterator it; CParser multikeyParser; multikeyParser.InputTaskType( "multikey", "<[!$arg(_negate)][~$arg(_negate)]$value_+_>_[!$arg(_negate)][~$arg(_negate)]$value" ); // Iterate through the bindings for this event... for( it = binding->begin(); it != binding->end(); it++ ) { std::string hotkey; if( it->GetString( hotkey ) ) { std::vector keyCombination; CParserLine multikeyIdentifier; multikeyIdentifier.ParseString( multikeyParser, hotkey ); // 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 ) ) { 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 ); } } std::vector::iterator itKey, itKey2; SHotkeyMapping bindCode; SHotkeyMappingGUI bindName; for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ ) { bindName.mapsTo = hotkeyName; bindName.negation = ( ( *itKey & HOTKEY_NEGATION_FLAG ) ? true : false ); bindName.requires.clear(); if( integerMapping != -1 ) { bindCode.mapsTo = integerMapping; bindCode.negation = ( ( *itKey & HOTKEY_NEGATION_FLAG ) ? true : false ); bindCode.requires.clear(); } for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ ) { // Push any auxiliary keys. if( itKey != itKey2 ) { bindName.requires.push_back( *itKey2 ); if( integerMapping != -1 ) bindCode.requires.push_back( *itKey2 ); } } hotkeyMapGUI[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindName ); if( integerMapping != -1 ) hotkeyMap[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindCode ); } } } } else if( integerMapping != -1 ) { SHotkeyMapping bind[2]; bind[0].mapsTo = integerMapping; 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] ); } } void loadHotkeys() { initKeyNameMap(); 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 ) { GUIObjectList& boundTo = guiHotkeyMap[hotkeyName]; if( boundTo.empty() ) { // Load keybindings from the config file setBindings( hotkeyName ); } boundTo.push_back( objName ); } int hotkeyInputHandler( const SDL_Event* ev ) { int keycode; switch( ev->type ) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: keycode = SDLK_LAST + (int)ev->button.button; break; default: return( EV_PASS ); } // Somewhat hackish: // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. SDL_Event phantom; phantom.type = ( ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ) ) ? SDL_KEYDOWN : SDL_KEYUP; if( ( keycode == SDLK_LSHIFT ) || ( keycode == SDLK_RSHIFT ) ) { (int&)phantom.key.keysym.sym = UNIFIED_SHIFT; unified[0] = ( phantom.type == SDL_KEYDOWN ); hotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LCTRL ) || ( keycode == SDLK_RCTRL ) ) { (int&)phantom.key.keysym.sym = UNIFIED_CTRL; unified[1] = ( phantom.type == SDL_KEYDOWN ); hotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LALT ) || ( keycode == SDLK_RALT ) ) { (int&)phantom.key.keysym.sym = UNIFIED_ALT; unified[2] = ( phantom.type == SDL_KEYDOWN ); hotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA ) ) { (int&)phantom.key.keysym.sym = UNIFIED_META; unified[3] = ( phantom.type == SDL_KEYDOWN ); hotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) ) { (int&)phantom.key.keysym.sym = UNIFIED_SUPER; unified[4] = ( phantom.type == SDL_KEYDOWN ); hotkeyInputHandler( &phantom ); } // Inhibit the dispatch of hotkey events caused by printable or control keys // while the console is up. (But allow multiple-key - 'Ctrl+F' events, and whatever // key toggles the console.) bool consoleCapture = false, isCapturable; if( g_Console->IsActive() && ( ( keycode == 8 ) || ( keycode == 9 ) || ( keycode == 13 ) || /* Editing */ ( ( keycode >= 32 ) && ( keycode < 273 ) ) || /* Printable (<128), 'World' (<256) */ ( ( keycode >= 273 ) && ( keycode < 282 ) && /* Numeric keypad (<273), navigation */ ( keycode != SDLK_INSERT ) ) ) ) /* keys (<282) except insert */ consoleCapture = true; std::vector::iterator it; std::vector::iterator itGUI; SDL_Event hotkeyNotification; // Here's an interesting bit: // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing // 'F' while control is down would normally fire off both. // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) // but only send a HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) bool typeKeyDown = ( ev->type == SDL_KEYDOWN ) || ( ev->type == SDL_MOUSEBUTTONDOWN ); // -- KEYDOWN SECTION -- // SDL-events bit uint closestMap = 0; // avoid "uninitialized" warning 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 std::vector::iterator itKey; bool accept = true; isCapturable = true; for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) { int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG ); // debug_assert( !rqdState ); if( keyCode < SDLK_LAST ) { if( keys[keyCode] != rqdState ) accept = false; } else if( *itKey < UNIFIED_SHIFT ) { - if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false; + if( mouse_buttons[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( 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 ) { 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++ ) { int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit bool rqdState = !( *itKey & HOTKEY_NEGATION_FLAG ); if( keyCode < SDLK_LAST ) { if( keys[keyCode] != rqdState ) accept = false; } else if( *itKey < UNIFIED_SHIFT ) { - if( mouseButtons[keyCode-SDLK_LAST] != rqdState ) accept = false; + if( mouse_buttons[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; + if( !mouse_buttons[(*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 ); } // Returns true if the specified HOTKEY_* responds to the specified SDLK_* // (mainly for the screenshot system to know whether it needs to override // the printscreen screen). Ignores modifier keys. bool keyRespondsTo( int hotkey, int sdlkey ) { for (KeyMapping::iterator it = hotkeyMap[sdlkey].begin(); it != hotkeyMap[sdlkey].end(); ++it) if (it->mapsTo == hotkey) return true; return false; } Index: ps/trunk/source/ps/GameSetup/GameSetup.h =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.h (nonexistent) +++ ps/trunk/source/ps/GameSetup/GameSetup.h (revision 2622) @@ -0,0 +1,22 @@ + +//---------------------------------------------------------------------------- +// GUI integration +//---------------------------------------------------------------------------- + + +extern void GUI_Init(); + +extern void GUI_Shutdown(); + +extern void GUI_ShowMainMenu(); + +// display progress / description in loading screen +extern void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task); + + + +extern void Render(); + +extern void Shutdown(); + +extern void Init(int argc, char* argv[], bool setup_gfx = true); Index: ps/trunk/source/ps/GameSetup/Atlas.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/Atlas.cpp (nonexistent) +++ ps/trunk/source/ps/GameSetup/Atlas.cpp (revision 2622) @@ -0,0 +1,115 @@ +#include "precompiled.h" + +#include "lib/posix.h" +#include "lib/lib.h" +#include "Atlas.h" + +//---------------------------------------------------------------------------- +// Atlas (map editor) integration +//---------------------------------------------------------------------------- + +static void* const ATLAS_SO_UNAVAILABLE = (void*)-1; +static void* atlas_so_handle; + + +// free reference to Atlas UI SO (avoids resource leak report) +void ATLAS_Shutdown() +{ + // (avoid dlclose warnings) + if(atlas_so_handle != 0 && atlas_so_handle != ATLAS_SO_UNAVAILABLE) + dlclose(atlas_so_handle); +} + + +// return true if the Atlas UI shared object is available; +// used to disable the main menu editor button if not. +// note: this actually loads the SO, but that isn't expected to be slow. +// call ATLAS_Shutdown at exit to avoid leaking it. +static bool ATLAS_IsAvailable() +{ + // first time: try to open Atlas UI shared object + // postcondition: atlas_so_handle valid or == ATLAS_SO_UNAVAILABLE. + if(atlas_so_handle == 0) + { + // since this SO exports a C++ interface, it is critical that + // compiler options are the same between app and SO; therefore, + // we need to go with the debug version in debug builds. + // note: on Windows, the extension is replaced with .dll by dlopen. +#ifndef NDEBUG + const char* so_name = "AtlasUI_d.so"; +#else + const char* so_name = "AtlasUI.so"; +#endif + // we don't care when relocations take place because this SO contains + // very few symbols, so RTLD_LAZY or RTLD_NOW aren't needed. + const int flags = RTLD_LOCAL; + atlas_so_handle = dlopen(so_name, flags); + // open failed (mostly likely SO not found) + if(!atlas_so_handle) + atlas_so_handle = ATLAS_SO_UNAVAILABLE; + } + + return atlas_so_handle != ATLAS_SO_UNAVAILABLE; +} + + +enum AtlasRunFlags +{ + // used by ATLAS_RunIfOnCmdLine; makes any error output go through + // DISPLAY_ERROR rather than a GUI dialog box (because GUI init was + // skipped to reduce load time). + ATLAS_NO_GUI = 1 +}; + +// starts the Atlas UI. +static void ATLAS_Run(int argc, char* argv[], int flags = 0) +{ + // first check if we can run at all + if(!ATLAS_IsAvailable()) + { + if(flags & ATLAS_NO_GUI) + DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested."); + else + DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested.");// TODO: implement GUI error message + return; + } + + void(*pStartWindow)(int argc, char* argv[]); + *(void**)&pStartWindow = dlsym(atlas_so_handle, "_StartWindow"); + pStartWindow(argc, argv); +} + + +// starts the Atlas UI if an "-editor" switch is found on the command line. +// this is the alternative to starting the main menu and clicking on +// the editor button; it is much faster because it's called during early +// init and therefore skips GUI setup. +// notes: +// - GUI init still runs, but some GUI setup will be skipped since +// ATLAS_IsRunning() will return true. +// - could be merged into CFG_ParseCommandLineArgs, because that appears +// to be called early enough. it's not really worth it because this +// code is quite simple and we thus avoid startup order dependency. +void ATLAS_RunIfOnCmdLine(int argc, char* argv[]) +{ + for(int i = 1; i < argc; i++) // skip program name argument + { + if(!strcmp(argv[i], "-editor")) + { + // don't bother removing this param (unnecessary) + + ATLAS_Run(argc, argv, ATLAS_NO_GUI); + break; + } + } +} + + + + + + + + + + Index: ps/trunk/source/ps/GameSetup/Config.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/Config.cpp (nonexistent) +++ ps/trunk/source/ps/GameSetup/Config.cpp (revision 2622) @@ -0,0 +1,186 @@ +#include "precompiled.h" + +#include "ps/CLogger.h" +#include "ps/ConfigDB.h" +#include "ps/CConsole.h" +#include "lib/timer.h" +#include "lib/res/sound/snd.h" +#include "lib/res/file/vfs.h" +#include "Config.h" + +#define LOG_CATEGORY "config" + +CStr g_CursorName = "test"; +CStr g_ActiveProfile = "default"; + +// flag to disable extended GL extensions until fix found - specifically, crashes +// using VBOs on laptop Radeon cards +bool g_NoGLVBO=false; +// flag to switch on shadows +bool g_Shadows=false; +// flag to switch off pbuffers +bool g_NoPBuffer=true; +// flag to switch on fixed frame timing (RC: I'm using this for profiling purposes) +bool g_FixedFrameTiming=false; +bool g_VSync = false; +float g_LodBias = 0.0f; +float g_Gamma = 1.0f; +bool g_EntGraph = false; + +// graphics mode +int g_xres, g_yres; +int g_bpp; +int g_freq; + +bool g_Quickstart=false; + + + +//---------------------------------------------------------------------------- +// config and profile +//---------------------------------------------------------------------------- + +static void LoadProfile( CStr profile ) +{ + CStr base = CStr( "profiles/" ) + profile; + g_ConfigDB.SetConfigFile(CFG_USER, true, base + "/settings/user.cfg"); + g_ConfigDB.Reload(CFG_USER); + + int max_history_lines = 50; + CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); + g_Console->UseHistoryFile(base+"/settings/history", max_history_lines); +} + + +// Fill in the globals from the config files. +static void LoadGlobals() +{ + CFG_GET_SYS_VAL("profile", String, g_ActiveProfile); + + // Now load the profile before trying to retrieve the values of the rest of these. + + LoadProfile( g_ActiveProfile ); + + CFG_GET_USER_VAL("xres", Int, g_xres); + CFG_GET_USER_VAL("yres", Int, g_yres); + + CFG_GET_USER_VAL("vsync", Bool, g_VSync); + CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); + CFG_GET_USER_VAL("shadows", Bool, g_Shadows); + + CFG_GET_USER_VAL("lodbias", Float, g_LodBias); + + float gain = -1.0f; + CFG_GET_USER_VAL("sound.mastergain", Float, gain); + if(gain > 0.0f) + WARN_ERR(snd_set_master_gain(gain)); + + LOG(NORMAL, LOG_CATEGORY, "g_x/yres is %dx%d", g_xres, g_yres); + LOG(NORMAL, LOG_CATEGORY, "Active profile is %s", g_ActiveProfile.c_str()); +} + + +static void ParseCommandLineArgs(int argc, char* argv[]) +{ + for(int i = 1; i < argc; i++) + { + // this arg isn't an option; skip + if(argv[i][0] != '-') + continue; + + char* name = argv[i]+1; // no leading '-' + + // switch first letter of option name + switch(argv[i][1]) + { + case 'c': + if(strcmp(name, "conf") == 0) + { + if(argc-i >= 1) // at least one arg left + { + i++; + char* arg = argv[i]; + char* equ = strchr(arg, '='); + if(equ) + { + *equ = 0; + g_ConfigDB.CreateValue(CFG_COMMAND, arg) + ->m_String = (equ+1); + } + } + } + break; + case 'e': + g_EntGraph = true; + break; + case 'f': + if(strncmp(name, "fixedframe", 10) == 0) + g_FixedFrameTiming=true; + break; + case 'g': + if(strncmp(name, "g=", 2) == 0) + { + g_Gamma = (float)atof(argv[i] + 3); + if(g_Gamma == 0.0f) + g_Gamma = 1.0f; + } + break; + case 'l': + if(strncmp(name, "listfiles", 9) == 0) + vfs_enable_file_listing(true); + break; + case 'n': + if(strncmp(name, "novbo", 5) == 0) + g_ConfigDB.CreateValue(CFG_COMMAND, "novbo")->m_String="true"; + else if(strncmp(name, "nopbuffer", 9) == 0) + g_NoPBuffer = true; + break; + case 'q': + if(strncmp(name, "quickstart", 10) == 0) + g_Quickstart = true; + break; + case 's': + if(strncmp(name, "shadows", 7) == 0) + g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String="true"; + break; + case 'v': + g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String="true"; + break; + case 'x': + if(strncmp(name, "xres=", 6) == 0) + g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String=argv[i]+6; + break; + case 'y': + if(strncmp(name, "yres=", 6) == 0) + g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String=argv[i]+6; + break; + case 'p': + if(strncmp(name, "profile=", 8) == 0 ) + g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = argv[i]+9; + break; + } // switch + } +} + + +void CONFIG_Init(int argc, char* argv[]) +{ + debug_printf("CFG_Init &argc=%p &argv=%p\n", &argc, &argv); + + TIMER(CONFIG_Init); + MICROLOG(L"init config"); + + new CConfigDB; + + g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, "config/system.cfg"); + g_ConfigDB.Reload(CFG_SYSTEM); + + g_ConfigDB.SetConfigFile(CFG_MOD, true, "config/mod.cfg"); + // No point in reloading mod.cfg here - we haven't mounted mods yet + + ParseCommandLineArgs(argc, argv); + + // Collect information from system.cfg, the profile file, + // and any command-line overrides to fill in the globals. + LoadGlobals(); +} Index: ps/trunk/source/ps/GameSetup/Atlas.h =================================================================== --- ps/trunk/source/ps/GameSetup/Atlas.h (nonexistent) +++ ps/trunk/source/ps/GameSetup/Atlas.h (revision 2622) @@ -0,0 +1,8 @@ +// free reference to Atlas UI SO (avoids resource leak report) +extern void ATLAS_Shutdown(); + +// starts the Atlas UI if an "-editor" switch is found on the command line. +// this is the alternative to starting the main menu and clicking on +// the editor button; it is much faster because it's called during early +// init and therefore skips GUI setup. +extern void ATLAS_RunIfOnCmdLine(int argc, char* argv[]); Index: ps/trunk/source/ps/GameSetup/Config.h =================================================================== --- ps/trunk/source/ps/GameSetup/Config.h (nonexistent) +++ ps/trunk/source/ps/GameSetup/Config.h (revision 2622) @@ -0,0 +1,27 @@ +#include "CStr.h" + +// flag to disable extended GL extensions until fix found - specifically, crashes +// using VBOs on laptop Radeon cards +extern bool g_NoGLVBO; +// flag to switch on shadows +extern bool g_Shadows; +// flag to switch off pbuffers +extern bool g_NoPBuffer; +// flag to switch on fixed frame timing (RC: I'm using this for profiling purposes) +extern bool g_FixedFrameTiming; +extern bool g_VSync; +extern float g_LodBias; +extern float g_Gamma; +extern bool g_EntGraph; + + +extern int g_xres, g_yres; +extern int g_bpp; +extern int g_freq; +extern bool g_active; +extern bool g_Quickstart; + +extern CStr g_CursorName; + + +extern void CONFIG_Init(int argc, char* argv[]); Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (nonexistent) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 2622) @@ -0,0 +1,847 @@ +#include "precompiled.h" + +#include "lib.h" +#include "lib/sdl.h" +#include "lib/ogl.h" +#include "lib/detect.h" +#include "lib/timer.h" +#include "lib/input.h" +#include "lib/res/res.h" +#include "lib/res/sound/snd.h" +#include "lib/res/graphics/tex.h" +#include "lib/res/graphics/cursor.h" + +#include "ps/Profile.h" +#include "ps/ProfileViewer.h" +#include "ps/Loader.h" +#include "ps/Font.h" +#include "ps/CConsole.h" +#include "ps/Game.h" +#include "ps/Interact.h" +#include "ps/Hotkey.h" +#include "ps/ConfigDB.h" +#include "ps/CLogger.h" +#include "ps/i18n.h" +#include "ps/Overlay.h" +#include "ps/StringConvert.h" + +#include "graphics/MapReader.h" +#include "graphics/Terrain.h" +#include "graphics/TextureManager.h" +#include "graphics/ObjectManager.h" +#include "graphics/SkeletonAnimManager.h" +#include "graphics/LightEnv.h" +#include "graphics/Model.h" +#include "graphics/UnitManager.h" +#include "graphics/MaterialManager.h" +#include "graphics/MeshManager.h" +#include "renderer/Renderer.h" + +#include "simulation/BaseEntityCollection.h" +#include "simulation/Entity.h" +#include "simulation/EntityHandles.h" +#include "simulation/EntityManager.h" +#include "simulation/PathfindEngine.h" +#include "simulation/Scheduler.h" +#include "simulation/Projectile.h" + +#include "scripting/ScriptingHost.h" +#include "scripting/GameEvents.h" +#include "scripting/JSInterface_Entity.h" +#include "scripting/JSInterface_BaseEntity.h" +#include "scripting/JSInterface_Vector3D.h" +#include "scripting/JSInterface_Camera.h" +#include "scripting/JSInterface_Selection.h" +#include "scripting/JSInterface_Console.h" +#include "scripting/JSCollection.h" +#include "scripting/DOMEvent.h" +#ifndef NO_GUI +# include "gui/scripting/JSInterface_IGUIObject.h" +# include "gui/scripting/JSInterface_GUITypes.h" +# include "gui/GUI.h" +#endif + +#include "sound/CMusicPlayer.h" +#include "sound/JSI_Sound.h" + +#include "Network/SessionManager.h" +#include "Network/Server.h" +#include "Network/Client.h" + +#include "Atlas.h" +#include "Config.h" +#include "ps/System.h" + +ERROR_GROUP(System); +ERROR_TYPE(System, SDLInitFailed); +ERROR_TYPE(System, VmodeFailed); +ERROR_TYPE(System, RequiredExtensionsMissing); + +#define LOG_CATEGORY "gamesetup" + + + + + +static int SetVideoMode(int w, int h, int bpp, bool fullscreen) +{ + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + ulong flags = SDL_OPENGL; + if(fullscreen) + flags |= SDL_FULLSCREEN; + if(!SDL_SetVideoMode(w, h, bpp, flags)) + return -1; + + glViewport(0, 0, w, h); + +#ifndef NO_GUI + g_GUI.UpdateResolution(); +#endif + + oglInit(); // required after each mode change + + if(SDL_SetGamma(g_Gamma, g_Gamma, g_Gamma) < 0) + debug_warn("SDL_SetGamma failed"); + + return 0; +} + +//---------------------------------------------------------------------------- +// GUI integration +//---------------------------------------------------------------------------- + + +void GUI_Init() +{ +#ifndef NO_GUI + {TIMER(ps_gui_init); + g_GUI.Initialize();} + + {TIMER(ps_gui_setup_xml); + g_GUI.LoadXMLFile("gui/test/setup.xml");} + {TIMER(ps_gui_styles_xml); + g_GUI.LoadXMLFile("gui/test/styles.xml");} + {TIMER(ps_gui_sprite1_xml); + g_GUI.LoadXMLFile("gui/test/sprite1.xml");} + + // Atlas is running, we won't need these GUI pages (for now! + // what if Atlas switches to in-game mode?!) + // TODO: temporary hack until revised GUI structure is completed. +// if(ATLAS_IsRunning()) +// return; + + {TIMER(ps_gui_1); + g_GUI.LoadXMLFile("gui/test/1_init.xml");} + {TIMER(ps_gui_2); + g_GUI.LoadXMLFile("gui/test/2_mainmenu.xml");} + {TIMER(ps_gui_3); + g_GUI.LoadXMLFile("gui/test/3_loading.xml");} + {TIMER(ps_gui_4); + g_GUI.LoadXMLFile("gui/test/4_session.xml");} + {TIMER(ps_gui_6); + g_GUI.LoadXMLFile("gui/test/6_subwindows.xml");} + {TIMER(ps_gui_6_1); + g_GUI.LoadXMLFile("gui/test/6_1_manual.xml");} + {TIMER(ps_gui_6_2); + g_GUI.LoadXMLFile("gui/test/6_2_jukebox.xml");} + {TIMER(ps_gui_7); + g_GUI.LoadXMLFile("gui/test/7_atlas.xml");} + {TIMER(ps_gui_9); + g_GUI.LoadXMLFile("gui/test/9_global.xml");} +#endif +} + + +void GUI_Shutdown() +{ +#ifndef NO_GUI + g_GUI.Destroy(); + delete &g_GUI; +#endif +} + + +void GUI_ShowMainMenu() +{ + +} + + +// display progress / description in loading screen +void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) +{ +#ifndef NO_GUI + CStrW i18n_description = I18n::translate(pending_task); + JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); + g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(percent)); + g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); + g_GUI.SendEventToAll("progress"); +#endif +} + + + +///////////////////////////////////////////////////////////////////////////////////////////// +// RenderNoCull: render absolutely everything to a blank frame to force renderer +// to load required assets +static void RenderNoCull() +{ + g_Renderer.BeginFrame(); + + if (g_Game) + g_Game->GetView()->RenderNoCull(); + + g_Renderer.FlushFrame(); + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + g_Renderer.EndFrame(); +} + + +void Render() +{ + MICROLOG(L"begin frame"); + + oglCheck(); + +#ifndef NO_GUI // HACK: because colour-parsing requires the GUI + CStr skystring = "61 193 255"; + CFG_GET_USER_VAL("skycolor", String, skystring); + CColor skycol; + GUI::ParseString(skystring, skycol); + g_Renderer.SetClearColor(skycol.Int()); +#endif + + // start new frame + g_Renderer.BeginFrame(); + + oglCheck(); + + if (g_Game && g_Game->IsGameStarted()) + { + g_Game->GetView()->Render(); + + oglCheck(); + + MICROLOG(L"flush frame"); + PROFILE_START( "flush frame" ); + g_Renderer.FlushFrame(); + PROFILE_END( "flush frame" ); + + glPushAttrib( GL_ENABLE_BIT ); + glDisable( GL_LIGHTING ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_DEPTH_TEST ); + + if( g_EntGraph ) + { + PROFILE( "render entity overlays" ); + glColor3f( 1.0f, 0.0f, 1.0f ); + + MICROLOG(L"render entities"); + g_EntityManager.renderAll(); // <-- collision outlines, pathing routes + } + + PROFILE_START( "render selection" ); + g_Mouseover.renderSelectionOutlines(); + g_Selection.renderSelectionOutlines(); + PROFILE_END( "render selection" ); + + glPopAttrib(); + } + else + { + PROFILE_START( "flush frame" ); + g_Renderer.FlushFrame(); + PROFILE_END( "flush frame" ); + } + + oglCheck(); + + PROFILE_START( "render fonts" ); + MICROLOG(L"render fonts"); + // overlay mode + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + PROFILE_END( "render fonts" ); + + oglCheck(); + +#ifndef NO_GUI + // Temp GUI message GeeTODO + glLoadIdentity(); + MICROLOG(L"render GUI"); + PROFILE_START( "render gui" ); + g_GUI.Draw(); + PROFILE_END( "render gui" ); +#endif + + oglCheck(); + + // Text: + + // Use the GL_ALPHA texture as the alpha channel with a flat colouring + glDisable(GL_ALPHA_TEST); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + // Added -- + glEnable(GL_TEXTURE_2D); + // -- GL + + oglCheck(); + + { + PROFILE( "render console" ); + glLoadIdentity(); + + MICROLOG(L"render console"); + CFont font("console"); + font.Bind(); + g_Console->Render(); + } + + oglCheck(); + + + // Profile information + + PROFILE_START( "render profiling" ); + RenderProfile(); + PROFILE_END( "render profiling" ); + + oglCheck(); + + if (g_Game && g_Game->IsGameStarted()) + { + PROFILE( "render selection overlays" ); + g_Mouseover.renderOverlays(); + g_Selection.renderOverlays(); + } + + oglCheck(); + + // Draw the cursor (or set the Windows cursor, on Windows) + cursor_draw(g_CursorName, g_mouse_x, g_mouse_y); + + // restore + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glPopAttrib(); + + MICROLOG(L"end frame"); + g_Renderer.EndFrame(); + + oglCheck(); +} + + + +static void InitScripting() +{ + TIMER(InitScripting); + // Create the scripting host. This needs to be done before the GUI is created. + new ScriptingHost; + + // It would be nice for onLoad code to be able to access the setTimeout() calls. + new CScheduler; + + // Register the JavaScript interfaces with the runtime + CEntity::ScriptingInit(); + CBaseEntity::ScriptingInit(); + JSI_Sound::ScriptingInit(); + CProfileNode::ScriptingInit(); + +#ifndef NO_GUI + JSI_IGUIObject::init(); + JSI_GUITypes::init(); +#endif + JSI_Vector3D::init(); + EntityCollection::Init( "EntityCollection" ); + SColour::ScriptingInit(); + CPlayer::ScriptingInit(); + + PlayerCollection::Init( "PlayerCollection" ); + CDamageType::ScriptingInit(); + CJSComplexPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. + CScriptEvent::ScriptingInit(); + CJSProgressTimer::ScriptingInit(); + CProjectile::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 ); + g_ScriptingHost.DefineConstant( "ORDER_GATHER", CEntityOrder::ORDER_GATHER ); + +#define REG_JS_CONSTANT(_name) g_ScriptingHost.DefineConstant(#_name, _name) + REG_JS_CONSTANT(SDL_BUTTON_LEFT); + REG_JS_CONSTANT(SDL_BUTTON_MIDDLE); + REG_JS_CONSTANT(SDL_BUTTON_RIGHT); + REG_JS_CONSTANT(SDL_BUTTON_WHEELUP); + REG_JS_CONSTANT(SDL_BUTTON_WHEELDOWN); +#undef REG_JS_CONSTANT + + CNetMessage::ScriptingInit(); + + JSI_Camera::init(); + JSI_Console::init(); + + new CGameEvents; +} + + +static void InitVfs(const char* argv0) +{ + TIMER(InitVfs); + // set current directory to "$game_dir/data". + // this is necessary because it is otherwise unknown, + // especially if run from a shortcut / symlink. + // + // "../data" is relative to the executable (in "$game_dir/system"). + // + // rationale for data/ being root: untrusted scripts must not be + // allowed to overwrite critical game (or worse, OS) files. + // the VFS prevents any accesses to files above this directory. + int err = file_rel_chdir(argv0, "../data"); + if(err < 0) + throw err; + + { + vfs_init(); + vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH); + vfs_mount("screenshots/", "screenshots"); + vfs_mount("profiles/", "profiles", VFS_MOUNT_RECURSIVE); + } + extern void vfs_dump_stats(); + vfs_dump_stats(); + // don't try vfs_display yet: SDL_Init hasn't yet redirected stdout +} + + +static void InitPs() +{ + // console + { + TIMER(ps_console); + + g_Console->UpdateScreenSize(g_xres, g_yres); + + // Calculate and store the line spacing + CFont font("console"); + g_Console->m_iFontHeight = font.GetLineSpacing(); + // Offset by an arbitrary amount, to make it fit more nicely + g_Console->m_iFontOffset = 9; + } + + // language and hotkeys + { + TIMER(ps_lang_hotkeys); + + std::string lang = "english"; + CFG_GET_SYS_VAL("language", String, lang); + I18n::LoadLanguage(lang.c_str()); + + loadHotkeys(); + } + + // GUI uses VFS, so this must come after VFS init. + GUI_Init(); +} + + +static void InitInput() +{ + // register input handlers + // This stack is constructed so the first added, will be the last + // one called. This is important, because each of the handlers + // has the potential to block events to go further down + // in the chain. I.e. the last one in the list added, is the + // only handler that can block all messages before they are + // processed. + in_add_handler(game_view_handler); + + in_add_handler(interactInputHandler); + + in_add_handler(conInputHandler); + + in_add_handler(profilehandler); + + in_add_handler(hotkeyInputHandler); +} + + +static void ShutdownPs() +{ + GUI_Shutdown(); + + delete g_Console; + + // disable the special Windows cursor, or free textures for OGL cursors + cursor_draw(0, g_mouse_x, g_mouse_y); + + // close down Xerces if it was loaded + CXeromyces::Terminate(); + + // Unload the real language (since it depends on the scripting engine, + // which is going to be killed later) and use the English fallback messages + I18n::LoadLanguage(NULL); +} + + +static void InitRenderer() +{ + TIMER(InitRenderer); + // create renderer + new CRenderer; + + // set renderer options from command line options - NOVBO must be set before opening the renderer + g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); + g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); + g_Renderer.SetOptionBool(CRenderer::OPT_NOPBUFFER,g_NoPBuffer); + g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); + + // create terrain related stuff + new CTextureManager; + + // create the material manager + new CMaterialManager; + new CMeshManager; + + // create actor related stuff + new CSkeletonAnimManager; + new CObjectManager; + new CUnitManager; + + MICROLOG(L"init renderer"); + g_Renderer.Open(g_xres,g_yres,g_bpp); + + // Setup default lighting environment. Since the Renderer accesses the + // lighting environment through a pointer, this has to be done before + // the first Frame. + g_LightEnv.m_SunColor=RGBColor(1,1,1); + g_LightEnv.SetRotation(DEGTORAD(270)); + g_LightEnv.SetElevation(DEGTORAD(45)); + g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0); + g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f); + g_Renderer.SetLightEnv(&g_LightEnv); + + // I haven't seen the camera affecting GUI rendering and such, but the + // viewport has to be updated according to the video mode + SViewPort vp; + vp.m_X=0; + vp.m_Y=0; + vp.m_Width=g_xres; + vp.m_Height=g_yres; + g_Renderer.SetViewport(vp); +} + +static void InitSDL() +{ + MICROLOG(L"init sdl"); + if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) + { + LOG(ERROR, LOG_CATEGORY, "SDL library initialization failed: %s", SDL_GetError()); + throw PSERROR_System_SDLInitFailed(); + } + atexit(SDL_Quit); + SDL_EnableUNICODE(1); +} + + + +void EndGame() +{ + if (g_NetServer) + { + delete g_NetServer; + g_NetServer=NULL; + } + else if (g_NetClient) + { + delete g_NetClient; + g_NetClient=NULL; + } + + delete g_Game; + g_Game=NULL; +} + + +void Shutdown() +{ + MICROLOG(L"Shutdown"); + + ATLAS_Shutdown(); + + ShutdownPs(); // Must delete g_GUI before g_ScriptingHost + + if (g_Game) + EndGame(); + + delete &g_Scheduler; + + delete &g_SessionManager; + + delete &g_Mouseover; + delete &g_Selection; + + delete &g_Pathfinder; + // Managed by CWorld + // delete &g_EntityManager; + + delete &g_GameAttributes; + delete &g_JSGameEvents; + + delete &g_EntityTemplateCollection; + + delete &g_ScriptingHost; + + // destroy actor related stuff + delete &g_UnitMan; + delete &g_ObjMan; + delete &g_SkelAnimMan; + + delete &g_MaterialManager; + delete &g_MeshManager; + + // destroy terrain related stuff + delete &g_TexMan; + + // destroy renderer + delete &g_Renderer; + + delete &g_ConfigDB; + + // Shut down the network loop + CSocketBase::Shutdown(); + + // Really shut down the i18n system. Any future calls + // to translate() will crash. + I18n::Shutdown(); + + snd_shutdown(); + + vfs_shutdown(); + + h_mgr_shutdown(); + mem_shutdown(); + + debug_shutdown(); + + delete &g_Logger; + + delete &g_Profiler; +} + + +// workaround for VC7 EBP-trashing bug, which confuses the stack trace code. +#if MSC_VERSION +# pragma optimize("", off) +#endif + +void Init(int argc, char* argv[], bool setup_gfx = true) +{ + debug_printf("INIT &argc=%p &argv=%p\n", &argc, &argv); + + MICROLOG(L"Init"); + + debug_set_thread_name("main"); + + // If you ever want to catch a particular allocation: + //_CrtSetBreakAlloc(187); + + // no longer set 24 bit (float) precision by default: for + // very long game uptimes (> 1 day; e.g. dedicated server), + // we need full precision when calculating the time. + // if there's a spot where we want to speed up divides|sqrts, + // we can temporarily change precision there. + // _control87(_PC_24, _MCW_PC); + + // detects CPU clock frequency and capabilities, which are prerequisites + // for using the TSC as a timer (desirable due to its high resolution). + // do this before lengthy init so we can time those accurately. + get_cpu_info(); + + // Do this as soon as possible, because it chdirs + // and will mess up the error reporting if anything + // crashes before the working directory is set. + MICROLOG(L"init vfs"); + const char* argv0 = argc? argv[0] : NULL; + // ScEd doesn't have a main(argc, argv), and so it has no argv. In that + // case, just pass NULL to InitVfs, which will work out the current + // directory for itself. + InitVfs(argv0); + + // This must come after VFS init, which sets the current directory + // (required for finding our output log files). + new CLogger; + + // Call LoadLanguage(NULL) to initialise the I18n system, but + // without loading an actual language file - translate() will + // just show the English key text, which is better than crashing + // from a null pointer when attempting to translate e.g. error messages. + // Real languages can only be loaded when the scripting system has + // been initialised. + // + // this uses LOG and must therefore come after CLogger init. + MICROLOG(L"init i18n"); + I18n::LoadLanguage(NULL); + + // should be done before the bulk of GUI init because it prevents + // most loads from happening (since ATLAS_IsRunning will return true). + ATLAS_RunIfOnCmdLine(argc, argv); + + // Set up the console early, so that debugging + // messages can be logged to it. (The console's size + // and fonts are set later in InitPs()) + g_Console = new CConsole(); + + if(setup_gfx) + InitSDL(); + + // preferred video mode = current desktop settings + // (command line params may override these) + get_cur_vmode(&g_xres, &g_yres, &g_bpp, &g_freq); + + new CProfileManager; // before any script code + + MICROLOG(L"init scripting"); + InitScripting(); // before GUI + + // g_ConfigDB, command line args, globals + CONFIG_Init(argc, argv); + + // GUI is notified in SetVideoMode, so this must come before that. +#ifndef NO_GUI + new CGUI; +#endif + + bool windowed = false; + CFG_GET_SYS_VAL("windowed", Bool, windowed); + + if (setup_gfx) + { + MICROLOG(L"set vmode"); + + if(SetVideoMode(g_xres, g_yres, 32, !windowed) < 0) + { + LOG(ERROR, LOG_CATEGORY, "Could not set %dx%d graphics mode: %s", g_xres, g_yres, SDL_GetError()); + throw PSERROR_System_VmodeFailed(); + } + SDL_WM_SetCaption("0 A.D.", "0 A.D."); + } + + oglCheck(); + + if(!g_Quickstart) + { + WriteSystemInfo(); + vfs_display(); + } + else + { + // speed up startup by disabling all sound + // (OpenAL init will be skipped). + // must be called before first snd_open. + snd_disable(true); + } + + // (must come after SetVideoMode, since it calls oglInit) + const char* missing = oglHaveExtensions(0, "GL_ARB_multitexture", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", 0); + if(missing) + { + wchar_t buf[500]; + const wchar_t* fmt = + L"The %hs extension doesn't appear to be available on your computer. " + L"The game may still work, though - you are welcome to try at your own risk. " + L"If not or it doesn't look right, upgrade your graphics card."; + swprintf(buf, ARRAY_SIZE(buf), fmt, missing); + DISPLAY_ERROR(buf); + // TODO: i18n + } + + // enable/disable VSync + // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. +#if OS_WIN + if(oglHaveExtension("WGL_EXT_swap_control")) + wglSwapIntervalEXT(g_VSync? 1 : 0); +#endif + + MICROLOG(L"init ps"); + InitPs(); + + oglCheck(); + InitRenderer(); + + TIMER(init_after_InitRenderer); + + // This needs to be done after the renderer has loaded all its actors... + new CBaseEntityCollection; + // CEntityManager is managed by CWorld + //new CEntityManager; + new CPathfindEngine; + new CSelectedEntities; + new CMouseoverEntities; + + new CSessionManager; + + new CGameAttributes; + + // Register a few Game/Network JS globals + g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetScript())); + + // Check for heap corruption after every allocation. Very, very slowly. + // (And it highlights the allocation just after the one you care about, + // so you need to run it again and tell it to break on the one before.) + // debug_heap_enable(DEBUG_HEAP_ALL); + + InitInput(); + + oglCheck(); + +#ifndef NO_GUI + g_GUI.SendEventToAll("load"); +#endif + + if (setup_gfx) + { + MICROLOG(L"render blank"); + // render everything to a blank frame to force renderer to load everything + RenderNoCull(); + } + + if (g_FixedFrameTiming) { + CCamera &g_Camera=*g_Game->GetView()->GetCamera(); +#if 0 // TOPDOWN + g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(90)); + g_Camera.m_Orientation.SetIdentity(); + g_Camera.m_Orientation.RotateX(DEGTORAD(90)); + g_Camera.m_Orientation.Translate(CELL_SIZE*250*0.5, 250, CELL_SIZE*250*0.5); +#else // std view + g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(20)); + g_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); + g_Camera.m_Orientation.RotateY(DEGTORAD(-45)); + g_Camera.m_Orientation.Translate(350, 350, -275); +#endif + g_Camera.UpdateFrustum(); + } +} + +#if MSC_VERSION +# pragma optimize("", on) // restore; see above. +#endif + + Index: ps/trunk/source/ps/CStr.cpp =================================================================== --- ps/trunk/source/ps/CStr.cpp (revision 2621) +++ ps/trunk/source/ps/CStr.cpp (revision 2622) @@ -1,584 +1,621 @@ #include "precompiled.h" #ifndef CStr_CPP_FIRST #define CStr_CPP_FIRST #include "posix.h" // for htons, ntohs #include "Network/Serialization.h" #include #include #define UNIDOUBLER_HEADER "CStr.cpp" #include "UniDoubler.h" +#ifndef PERFORM_SELF_TEST +#define PERFORM_SELF_TEST 0 +#endif + // Only include these function definitions in the first instance of CStr.cpp: CStrW::CStrW(const CStr8 &asciStr) : std::wstring(asciStr.begin(), asciStr.end()) {} CStr8::CStr8(const CStrW &wideStr) : std:: string(wideStr.begin(), wideStr.end()) {} // UTF conversion code adapted from http://www.unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; static const char trailingBytesForUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; static const u32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; CStr8 CStrW::ToUTF8() const { CStr8 result; for (size_t i = 0; i < Length(); ++i) { unsigned short bytesToWrite; wchar_t ch = (*this)[i]; if (ch < 0x80) bytesToWrite = 1; else if (ch < 0x800) bytesToWrite = 2; else if (ch < 0x10000) bytesToWrite = 3; else if (ch <= 0x7FFFFFFF) bytesToWrite = 4; else bytesToWrite = 3, ch = 0x0000FFFD; // replacement character char buf[4]; char* target = &buf[bytesToWrite]; switch (bytesToWrite) { case 4: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 3: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 2: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 1: *--target = (ch | firstByteMark[bytesToWrite]); } result += CStr(buf, bytesToWrite); } return result; } static bool isLegalUTF8(const unsigned char *source, int length) { unsigned char a; const unsigned char *srcptr = source+length; switch (length) { default: return false; case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; case 2: if ((a = (*--srcptr)) > 0xBF) return false; switch (*source) { case 0xE0: if (a < 0xA0) return false; break; case 0xED: if (a > 0x9F) return false; break; case 0xF0: if (a < 0x90) return false; break; case 0xF4: if (a > 0x8F) return false; break; default: if (a < 0x80) return false; } case 1: if (*source >= 0x80 && *source < 0xC2) return false; } if (*source > 0xF4) return false; return true; } CStrW CStr8::FromUTF8() const { CStrW result; const unsigned char* source = (const unsigned char*)&*begin(); const unsigned char* sourceEnd = source + length(); while (source < sourceEnd) { wchar_t ch = 0; unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; if (source + extraBytesToRead >= sourceEnd) { debug_warn("Invalid UTF-8 (fell off end)"); return L""; } if (! isLegalUTF8(source, extraBytesToRead+1)) { debug_warn("Invalid UTF-8 (illegal data)"); return L""; } switch (extraBytesToRead) { case 5: ch += *source++; ch <<= 6; case 4: ch += *source++; ch <<= 6; case 3: ch += *source++; ch <<= 6; case 2: ch += *source++; ch <<= 6; case 1: ch += *source++; ch <<= 6; case 0: ch += *source++; } ch -= offsetsFromUTF8[extraBytesToRead]; result += ch; } return result; } #else #include "CStr.h" using namespace std; #include #ifdef _UNICODE #define tstringstream wstringstream #define _tstod wcstod #define _ttoi(a) wcstol(a, NULL, 0) #define _ttol(a) wcstol(a, NULL, 0) #define _istspace iswspace #define _totlower towlower #define _totupper towupper #else #define tstringstream stringstream #define _tstod strtod #define _ttoi atoi #define _ttol atol #define _istspace isspace #define _totlower tolower #define _totupper toupper #endif // Construction and assignment from numbers: #define NUM_TYPE(T) \ CStr::CStr(T Number) \ { \ std::tstringstream ss; \ ss << Number; \ ss >> *this; \ } \ \ CStr& CStr::operator=(T Number) \ { \ std::tstringstream ss; \ ss << Number; \ ss >> *this; \ return *this; \ } NUM_TYPE(int) NUM_TYPE(long) NUM_TYPE(unsigned int) NUM_TYPE(unsigned long) NUM_TYPE(float) NUM_TYPE(double) #undef NUM_TYPE // Conversion to numbers: int CStr::ToInt() const { return _ttoi(c_str()); } unsigned int CStr::ToUInt() const { return uint(_ttoi(c_str())); } long CStr::ToLong() const { return _ttol(c_str()); } unsigned long CStr::ToULong() const { return ulong(_ttol(c_str())); } float CStr::ToFloat() const { return (float)_tstod(c_str(), NULL); } double CStr::ToDouble() const { return _tstod(c_str(), NULL); } // Retrieves at most 'len' characters, starting at 'start' CStr CStr::GetSubstring(size_t start, size_t len) const { return substr(start, len); } // Search the string for another string long CStr::Find(const CStr& Str) const { size_t Pos = find(Str, 0); if (Pos != npos) return (long)Pos; return -1; } // Search the string for another string long CStr::Find(const tchar &chr) const { size_t Pos = find(chr, 0); if (Pos != npos) return (long)Pos; return -1; } // Search the string for another string long CStr::Find(const int &start, const tchar &chr) const { size_t Pos = find(chr, start); if (Pos != npos) return (long)Pos; return -1; } long CStr::FindInsensitive(const int &start, const tchar &chr) const { return LCase().Find(start, _totlower(chr)); } long CStr::FindInsensitive(const tchar &chr) const { return LCase().Find(_totlower(chr)); } long CStr::FindInsensitive(const CStr& Str) const { return LCase().Find(Str.LCase()); } long CStr::ReverseFind(const CStr& Str) const { size_t Pos = rfind(Str, length() ); if (Pos != npos) return (long)Pos; return -1; } // Lowercase and uppercase CStr CStr::LowerCase() const { tstring NewString = *this; for (size_t i = 0; i < length(); i++) NewString[i] = (tchar)_totlower((*this)[i]); return NewString; } CStr CStr::UpperCase() const { tstring NewString = *this; for (size_t i = 0; i < length(); i++) NewString[i] = (tchar)_totupper((*this)[i]); return NewString; } // Lazy versions // code duplication because return by value overhead if they were merely an alias CStr CStr::LCase() const { tstring NewString = *this; for (size_t i = 0; i < length(); i++) NewString[i] = (tchar)_totlower((*this)[i]); return NewString; } CStr CStr::UCase() const { tstring NewString = *this; for (size_t i = 0; i < length(); i++) NewString[i] = (tchar)_totupper((*this)[i]); return NewString; } // Retrieve the substring of the first n characters CStr CStr::Left(size_t len) const { debug_assert(len <= length()); return substr(0, len); } // Retrieve the substring of the last n characters CStr CStr::Right(size_t len) const { debug_assert(len <= length()); return substr(length()-len, len); } // Retrieve the substring following the last occurrence of Str // (or the whole string if it doesn't contain Str) CStr CStr::AfterLast(const CStr& Str) const { long pos = ReverseFind(Str); if (pos == -1) return *this; else return substr(pos + Str.length()); } // Retrieve the substring preceding the last occurrence of Str // (or the whole string if it doesn't contain Str) CStr CStr::BeforeLast(const CStr& Str) const { long pos = ReverseFind(Str); if (pos == -1) return *this; else return substr(0, pos); } // Retrieve the substring following the first occurrence of Str // (or the whole string if it doesn't contain Str) CStr CStr::AfterFirst(const CStr& Str) const { long pos = Find(Str); if (pos == -1) return *this; else return substr(pos + Str.length()); } // Retrieve the substring preceding the first occurrence of Str // (or the whole string if it doesn't contain Str) CStr CStr::BeforeFirst(const CStr& Str) const { long pos = Find(Str); if (pos == -1) return *this; else return substr(0, pos); } // Remove all occurrences of some character or substring void CStr::Remove(const CStr& Str) { size_t FoundAt = 0; while (FoundAt != npos) { FoundAt = find(Str, 0); if (FoundAt != npos) erase(FoundAt, Str.length()); } } // Replace all occurrences of some substring by another void CStr::Replace(const CStr& ToReplace, const CStr& ReplaceWith) { size_t Pos = 0; while (Pos != npos) { Pos = find(ToReplace, Pos); if (Pos != npos) { erase(Pos, ToReplace.length()); insert(Pos, ReplaceWith); Pos += ReplaceWith.length(); } } } CStr CStr::UnescapeBackslashes() { // Currently only handle \n and \\, because they're the only interesting ones CStr NewString; bool escaping = false; for (size_t i = 0; i < length(); i++) { tchar ch = (*this)[i]; if (escaping) { switch (ch) { case 'n': NewString += '\n'; break; default: NewString += ch; break; } escaping = false; } else { if (ch == '\\') escaping = true; else NewString += ch; } } return NewString; } // Returns a trimmed string, removes whitespace from the left/right/both CStr CStr::Trim(PS_TRIM_MODE Mode) const { size_t Left = 0, Right = 0; switch (Mode) { case PS_TRIM_LEFT: { for (Left = 0; Left < length(); Left++) if (_istspace((*this)[Left]) == false) break; // end found, trim 0 to Left-1 inclusive } break; case PS_TRIM_RIGHT: { Right = length(); while (Right--) if (_istspace((*this)[Right]) == false) break; // end found, trim len-1 to Right+1 inclusive } break; case PS_TRIM_BOTH: { for (Left = 0; Left < length(); Left++) if (_istspace((*this)[Left]) == false) break; // end found, trim 0 to Left-1 inclusive Right = length(); while (Right--) if (_istspace((*this)[Right]) == false) break; // end found, trim len-1 to Right+1 inclusive } break; default: debug_warn("CStr::Trim: invalid Mode"); } return substr(Left, Right-Left+1); } // Concatenation: CStr CStr::operator+(const CStr& Str) { return std::operator+(*this, std::tstring(Str)); } CStr CStr::operator+(const tchar* Str) { return std::operator+(*this, std::tstring(Str)); } // Joining ASCII and Unicode strings: #ifndef _UNICODE CStr8 CStr::operator+(const CStrW& Str) { return std::operator+(*this, CStr8(Str)); } #else CStrW CStr::operator+(const CStr8& Str) { return std::operator+(*this, CStrW(Str)); } #endif CStr::operator const tchar*() const { return c_str(); } size_t CStr::GetHashCode() const { return (size_t)fnv_hash(data(), length()); // janwas 2005-03-18: now use 32-bit version; 64 is slower and // the result was truncated down to 32 anyway. } #ifdef _UNICODE /* CStrW is always serialized to/from UTF-16 */ u8 *CStrW::Serialize(u8 *buffer) const { size_t len = length(); size_t i = 0; for (i = 0; i < len; i++) *(u16 *)(buffer + i*2) = htons((*this)[i]); // convert to network order (big-endian) *(u16 *)(buffer+i*2) = 0; return buffer+len*2+2; } const u8 *CStrW::Deserialize(const u8 *buffer, const u8 *bufferend) { const u16 *strend = (const u16 *)buffer; while ((const u8 *)strend < bufferend && *strend) strend++; if ((const u8 *)strend >= bufferend) return NULL; resize(strend - (const u16 *)buffer); const u16 *ptr = (const u16 *)buffer; std::wstring::iterator str = begin(); while (ptr < strend) *(str++) = (tchar)ntohs(*(ptr++)); // convert from network order (big-endian) return (const u8 *)(strend+1); } uint CStr::GetSerializedLength() const { return uint(length()*2 + 2); } #else /* CStr8 is always serialized to/from ASCII (or whatever 8-bit codepage stored in the CStr) */ u8 *CStr8::Serialize(u8 *buffer) const { size_t len = length(); size_t i = 0; for (i = 0; i < len; i++) buffer[i] = (*this)[i]; buffer[i] = 0; return buffer+len+1; } const u8 *CStr8::Deserialize(const u8 *buffer, const u8 *bufferend) { const u8 *strend = buffer; while (strend < bufferend && *strend) strend++; if (strend >= bufferend) return NULL; *this = std::string(buffer, strend); return strend+1; } uint CStr::GetSerializedLength() const { return uint(length() + 1); } #endif // _UNICODE // Clean up, to keep the second pass through unidoubler happy #undef tstringstream #undef _tstod #undef _ttoi #undef _ttol #undef _istspace #undef _totlower #undef _totupper + + +//---------------------------------------------------------------------------- +// built-in self test +//---------------------------------------------------------------------------- + +#if PERFORM_SELF_TEST +namespace test { + +static void test1() +{ + const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffe }; + const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbe }; + CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t)); + CStr8 str_utf8 = str_utf16.ToUTF8(); + debug_assert(str_utf8.length() == sizeof(chr_utf8)); + debug_assert(memcmp(str_utf8.data(), chr_utf8, sizeof(chr_utf8)) == 0); + debug_assert(str_utf8.FromUTF8() == str_utf16); +} + + +static int run_tests() +{ + test1(); + return 0; +} + +static int dummy = run_tests(); + +} // namespace test +#endif // #if PERFORM_SELF_TEST + + #endif // CStr_CPP_FIRST Index: ps/trunk/source/ps/World.h =================================================================== --- ps/trunk/source/ps/World.h (revision 2621) +++ ps/trunk/source/ps/World.h (revision 2622) @@ -1,59 +1,65 @@ #ifndef _ps_World_H #define _ps_World_H #include "Terrain.h" #include "UnitManager.h" #include "EntityManager.h" #include "Projectile.h" class CGame; class CGameAttributes; class CWorld { CGame *m_pGame; CTerrain m_Terrain; // These all point to the respective g_* globals - the plan is to remove // the globals and move them into CWorld members as soon as all code has // been converted CUnitManager &m_UnitManager; CEntityManager &m_EntityManager; CProjectileManager &m_ProjectileManager; public: inline CWorld(CGame *pGame): m_pGame(pGame), m_Terrain(), m_UnitManager(g_UnitMan), m_EntityManager(*(new CEntityManager())), m_ProjectileManager( *(new CProjectileManager())) {} ~CWorld(); void RegisterInit(CGameAttributes *pGameAttributes); /* Initialize the World - load the map and all objects */ void Initialize(CGameAttributes *pGameAttributes); + // provided for JS _rewritemaps function + void RewriteMap(); inline CTerrain *GetTerrain() { return &m_Terrain; } inline CUnitManager *GetUnitManager() { return &m_UnitManager; } inline CProjectileManager *GetProjectileManager() { return &m_ProjectileManager; } private: // squelch "unable to generate" warnings CWorld(const CWorld& rhs); const CWorld& operator=(const CWorld& rhs); }; #include "Game.h" ERROR_SUBGROUP(Game, World); ERROR_TYPE(Game_World, MapLoadFailed); +// rationale: see definition. +class CLightEnv; +extern CLightEnv g_LightEnv; + #endif Index: ps/trunk/source/scripting/ScriptGlue.cpp =================================================================== --- ps/trunk/source/scripting/ScriptGlue.cpp (revision 2621) +++ ps/trunk/source/scripting/ScriptGlue.cpp (revision 2622) @@ -1,920 +1,920 @@ // This module defines the table of all functions callable from JS. // it's required by the interpreter; we make use of the opportunity to // document them all in one spot. we thus obviate having to dig through // all the other headers. most of the functions are implemented here; // as for the rest, we only link to their docs (duplication is bad). #include "precompiled.h" #include "ScriptGlue.h" #include "CLogger.h" #include "CConsole.h" #include "CStr.h" #include "EntityHandles.h" #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" #include "Scheduler.h" #include "timer.h" #include "LightEnv.h" #include "MapWriter.h" #include "GameEvents.h" #include "Interact.h" #include "Renderer.h" #include "Game.h" #include "Network/Server.h" #include "Network/Client.h" #include "gui/CGUI.h" #include "ps/i18n.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSCollection.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "scripting/JSInterface_Selection.h" #include "scripting/JSInterface_Camera.h" #include "scripting/JSInterface_Console.h" #include "scripting/JSInterface_VFS.h" #include "scripting/JSConversions.h" #ifndef NO_GUI # include "gui/scripting/JSInterface_IGUIObject.h" #endif extern CConsole* g_Console; // rationale: the function table is now at the end of the source file to // avoid the need for forward declarations for every function. // all normal function wrappers have the following signature: // JSBool func(JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval); // all property accessors have the following signature: // JSBool accessor(JSContext* cx, JSObject* globalObject, jsval id, jsval* vp); // consistent argc checking for normal function wrappers: reports an // error via JS and returns if number of parameters is incorrect. // .. require exact number (most common case) #define REQUIRE_PARAMS(exact_number, func_name)\ if(argc != exact_number)\ {\ JS_ReportError(cx, #func_name ": number of parameters passed doesn't match expected count");\ return JS_FALSE;\ } // .. require 0 params (avoids L4 warning "unused argv param") #define REQUIRE_NO_PARAMS(func_name)\ UNUSED2(argv);\ if(argc != 0)\ {\ JS_ReportError(cx, #func_name ": number of parameters passed doesn't match expected count");\ return JS_FALSE;\ } // .. accept at most N params (e.g. buildTime) #define REQUIRE_MAX_PARAMS(max_number, func_name)\ if(argc > max_number)\ {\ JS_ReportError(cx, #func_name ": too many parameters passed");\ return JS_FALSE;\ } // .. accept at least N params (e.g. issueCommand) #define REQUIRE_MIN_PARAMS(min_number, func_name)\ if(argc < min_number)\ {\ JS_ReportError(cx, #func_name ": too few parameters passed");\ return JS_FALSE;\ } //----------------------------------------------------------------------------- // Output //----------------------------------------------------------------------------- // Write values to the log file. // params: any number of any type. // returns: // notes: // - Each argument is converted to a string and then written to the log. // - Output is in NORMAL style (see LOG). JSBool WriteLog(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_MIN_PARAMS(1, WriteLog); CStr logMessage; for (int i = 0; i < (int)argc; i++) { try { CStr arg = g_ScriptingHost.ValueToString( argv[i] ); logMessage += arg; } catch( PSERROR_Scripting_ConversionFailed ) { // Do nothing. } } // We should perhaps unicodify (?) the logger at some point. LOG( NORMAL, "script", logMessage ); return JS_TRUE; } //----------------------------------------------------------------------------- // Entity //----------------------------------------------------------------------------- // Retrieve the entity currently occupying the specified handle. // params: handle [int] // returns: entity JSBool getEntityByHandle( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, getEntityByHandle); *rval = JSVAL_NULL; i32 handle; try { handle = ToPrimitive( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid handle" ); return( JS_TRUE ); } HEntity* v = g_EntityManager.getByHandle( (u16)handle ); if( !v ) { JS_ReportError( cx, "No entity occupying handle: %d", handle ); return( JS_TRUE ); } JSObject* entity = (*v)->GetScript(); *rval = OBJECT_TO_JSVAL( entity ); return( JS_TRUE ); } // Look up an EntityTemplate by name. // params: template name [wstring] // returns: entity template object JSBool getEntityTemplate( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, getEntityTemplate); *rval = JSVAL_NULL; CStrW templateName; try { templateName = g_ScriptingHost.ValueToUCString( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid template identifier" ); return( JS_TRUE ); } CBaseEntity* v = g_EntityTemplateCollection.getTemplate( templateName ); if( !v ) { JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() ); return( JS_TRUE ); } *rval = OBJECT_TO_JSVAL( v->GetScript() ); return( JS_TRUE ); } // Issue a command (network message) to an entity or collection. // params: either an entity- or entity collection object, message ID [int], // any further params needed by CNetMessage::CommandFromJSArgs // returns: command in serialized form [string] JSBool issueCommand( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MIN_PARAMS(2, issueCommand); debug_assert(JSVAL_IS_OBJECT(argv[0])); *rval = JSVAL_NULL; CEntityList entities; if (JS_GetClass(JSVAL_TO_OBJECT(argv[0])) == &CEntity::JSI_class) entities.push_back( (ToNative(argv[0])) ->me); else entities = *EntityCollection::RetrieveSet(cx, JSVAL_TO_OBJECT(argv[0])); CNetMessage *msg = CNetMessage::CommandFromJSArgs(entities, cx, argc-1, argv+1); if (msg) { g_Console->InsertMessage(L"issueCommand: %hs", msg->GetString().c_str()); g_Game->GetSimulation()->QueueLocalCommand(msg); *rval = g_ScriptingHost.UCStringToValue(msg->GetString()); } return JS_TRUE; } //----------------------------------------------------------------------------- // Events //----------------------------------------------------------------------------- // Register a global handler for the specified DOM event. // params: event type name [wstring], handler [fragment or function] // returns: whether it was actually newly registered [bool] JSBool AddGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, AddGlobalHandler); *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.AddHandlerJS( cx, argc, argv ) ); return( JS_TRUE ); } // Remove a previously registered global handler for the specified DOM event. // params: event type name [wstring], handler [fragment or function] // returns: whether it was successfully removed [bool] JSBool RemoveGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, RemoveGlobalHandler); *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.RemoveHandlerJS( cx, argc, argv ) ); return( JS_TRUE ); } //----------------------------------------------------------------------------- // Timer //----------------------------------------------------------------------------- // Request a callback be executed after the specified delay. // params: callback [fragment or function], delay in milliseconds [int] // returns: // notes: // - Scripts and functions registered this way are called on the first // simulation frame after the specified period has elapsed. If this causes // multiple segments of code to be executed in the same frame, // relative timing is maintained. Delays of 0 milliseconds cause code to be // executed on the following simulation frame. If more than one script or // function is scheduled to execute in the same millisecond, the order of // execution is undefined. Code is scheduled in simulation time, and is // therefore suspended while the game is paused or frozen. Granularity of // timing is also limited to 1/(Simulation frame rate); currently 100ms. // The called function or script executes in the same scope as the // code that called setTimeout (amongst other things, the // 'this' reference is usually maintained) JSBool setTimeout( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS(2, setTimeout); size_t delay; try { delay = ToPrimitive( argv[1] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid timer parameters" ); return( JS_TRUE ); } switch( JS_TypeOfValue( cx, argv[0] ) ) { case JSTYPE_STRING: { CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] ); g_Scheduler.pushTime( delay, fragment, JS_GetScopeChain( cx ) ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( cx, argv[0] ); g_Scheduler.pushTime( delay, fn, JS_GetScopeChain( cx ) ); return( JS_TRUE ); } default: JS_ReportError( cx, "Invalid timer script" ); return( JS_TRUE ); } } // Request a callback be executed periodically. // params: callback [fragment or function], initial delay in ms [int], period in ms [int] // OR callback [fragment or function], period in ms [int] (initial delay = period) // returns: // notes: // - setTimeout's notes apply here as well. JSBool setInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_MIN_PARAMS(2, setInterval); REQUIRE_MAX_PARAMS(3, setInterval); size_t first, interval; try { first = ToPrimitive( argv[1] ); if( argc == 3 ) { // toDo, first, interval interval = ToPrimitive( argv[2] ); } else { // toDo, interval (first = interval) interval = first; } } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid timer parameters" ); return( JS_TRUE ); } switch( JS_TypeOfValue( cx, argv[0] ) ) { case JSTYPE_STRING: { CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] ); g_Scheduler.pushInterval( first, interval, fragment, JS_GetScopeChain( cx ) ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( cx, argv[0] ); g_Scheduler.pushInterval( first, interval, fn, JS_GetScopeChain( cx ) ); return( JS_TRUE ); } default: JS_ReportError( cx, "Invalid timer script" ); return( JS_TRUE ); } } // Cause all periodic functions registered via setInterval to // no longer be called. // params: // returns: // notes: // - Execution continues until the end of the triggered function or // script fragment, but is not triggered again. JSBool cancelInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(cancelInterval); g_Scheduler.m_abortInterval = true; return( JS_TRUE ); } //----------------------------------------------------------------------------- // Game Setup //----------------------------------------------------------------------------- // Create a new network server object. // params: // returns: net server object JSBool createServer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(createServer); if( !g_Game ) g_Game = new CGame(); if( !g_NetServer ) g_NetServer = new CNetServer(g_Game, &g_GameAttributes); *rval = OBJECT_TO_JSVAL(g_NetServer->GetScript()); return( JS_TRUE ); } // Create a new network client object. // params: // returns: net client object JSBool createClient(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(createClient); if( !g_Game ) g_Game = new CGame(); if( !g_NetClient ) g_NetClient = new CNetClient(g_Game, &g_GameAttributes); *rval = OBJECT_TO_JSVAL(g_NetClient->GetScript()); return( JS_TRUE ); } // Begin the process of starting a game. // params: // returns: success [bool] // notes: // - Performs necessary initialization while calling back into the // main loop, so the game remains responsive to display+user input. // - When complete, the engine calls the reallyStartGame JS function. // TODO: Replace startGame with create(Game|Server|Client)/game.start() - // after merging CGame and CGameAttributes JSBool startGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(startGame); *rval = BOOLEAN_TO_JSVAL(JS_TRUE); // Hosted MP Game if (g_NetServer) *rval = BOOLEAN_TO_JSVAL(g_NetServer->StartGame() == 0); // Joined MP Game: startGame is invalid - do nothing else if (g_NetClient) { } // Start an SP Game Session else if (!g_Game) { g_Game = new CGame(); PSRETURN ret = g_Game->StartGame(&g_GameAttributes); if (ret != PSRETURN_OK) { // Failed to start the game - destroy it, and return false delete g_Game; g_Game = NULL; *rval = BOOLEAN_TO_JSVAL(JS_FALSE); return( JS_TRUE ); } } return( JS_TRUE ); } // Immediately ends the current game (if any). // params: // returns: JSBool endGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_NO_PARAMS(endGame); EndGame(); return JS_TRUE; } //----------------------------------------------------------------------------- // Internationalization //----------------------------------------------------------------------------- // these remain here instead of in the i18n tree because they are // really related to the engine's use of them, as opposed to i18n itself. // contrariwise, translate() cannot be moved here because that would // make i18n dependent on this code and therefore harder to reuse. // Replaces the current language (locale) with a new one. // params: language id [string] as in I18n::LoadLanguage // returns: JSBool loadLanguage(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_PARAMS(1, loadLanguage); CStr lang = g_ScriptingHost.ValueToString(argv[0]); I18n::LoadLanguage(lang); return JS_TRUE; } // Return identifier of the current language (locale) in use. // params: // returns: language id [string] as in I18n::LoadLanguage JSBool getLanguageID(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(getLanguageID); *rval = JSVAL_NULL; JSString* s = JS_NewStringCopyZ(cx, I18n::CurrentLanguageName()); if (!s) { JS_ReportError(cx, "Error creating string"); return JS_FALSE; } *rval = STRING_TO_JSVAL(s); return JS_TRUE; } //----------------------------------------------------------------------------- // Debug //----------------------------------------------------------------------------- // Deliberately cause the game to crash. // params: // returns: // notes: // - currently implemented via access violation (read of address 0) // - useful for testing the crashlog/stack trace code. JSBool crash(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_NO_PARAMS(crash); MICROLOG(L"Crashing at user's request."); return *(JSBool*)0; } // Force a JS GC (garbage collection) cycle to take place immediately. // params: // returns: true [bool] // notes: // - writes an indication of how long this took to the console. JSBool forceGC( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(forceGC); double time = get_time(); JS_GC( cx ); time = get_time() - time; g_Console->InsertMessage( L"Garbage collection completed in: %f", time ); *rval = JSVAL_TRUE; return( JS_TRUE ); } //----------------------------------------------------------------------------- // GUI //----------------------------------------------------------------------------- // Returns the sort-of-global object associated with the current GUI. // params: // returns: global object // notes: // - Useful for accessing an object from another scope. JSBool getGUIGlobal( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getGUIGlobal); *rval = OBJECT_TO_JSVAL( g_GUI.GetScriptObject() ); return( JS_TRUE ); } //----------------------------------------------------------------------------- // Misc. Engine Interface //----------------------------------------------------------------------------- // Return the global frames-per-second value. // params: // returns: FPS [int] // notes: // - This value is recalculated once a frame. We take special care to // filter it, so it is both accurate and free of jitter. JSBool getFPS( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getFPS); *rval = INT_TO_JSVAL(fps); return JS_TRUE; } // Cause the game to exit gracefully. // params: // returns: // notes: // - Exit happens after the current main loop iteration ends // (since this only sets a flag telling it to end) JSBool exitProgram( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(exitProgram); kill_mainloop(); return JS_TRUE; } // Write an indication of total/available video RAM to console. // params: // returns: // notes: // - Not supported on all platforms. // - Only a rough approximation; do not base low-level decisions // ("should I allocate one more texture?") on this. JSBool vmem( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(vmem); #if OS_WIN int left, total; if (GetVRAMInfo(left, total)) g_Console->InsertMessage(L"VRAM: used %d, total %d, free %d", total-left, total, left); else g_Console->InsertMessage(L"VRAM: failed to detect"); #else g_Console->InsertMessage(L"VRAM: [not available on non-Windows]"); #endif return JS_TRUE; } // Change the mouse cursor. // params: cursor name [string] (i.e. basename of definition file and texture) // returns: // notes: // - Cursors are stored in "art\textures\cursors" JSBool setCursor( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS(1, setCursor); g_CursorName = g_ScriptingHost.ValueToString(argv[0]); return JS_TRUE; } // Trigger a rewrite of all maps. // params: // returns: // notes: // - Usefulness is unclear. If you need it, consider renaming this and updating the docs. JSBool _rewriteMaps( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(_rewriteMaps); - CMapWriter::RewriteAllMaps(g_Game->GetWorld()->GetTerrain(), g_Game->GetWorld()->GetUnitManager(), &g_LightEnv); + g_Game->GetWorld()->RewriteMap(); return JS_TRUE; } // Change the LOD bias. // params: LOD bias [float] // returns: // notes: // - value is as required by GL_TEXTURE_LOD_BIAS. // - useful for adjusting image "sharpness" (since it affects which mipmap level is chosen) JSBool _lodbias( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS(1, _lodbias); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, ToPrimitive(argv[0])); return JS_TRUE; } // Focus the game camera on a given position. // params: target position vector [CVector3D] // returns: success [bool] JSBool setCameraTarget( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, setCameraTarget); *rval = JSVAL_NULL; CVector3D* target = ToNative( argv[0] ); if(!target) { JS_ReportError( cx, "Invalid camera target" ); return( JS_TRUE ); } g_Game->GetView()->SetCameraTarget( *target ); *rval = JSVAL_TRUE; return( JS_TRUE ); } //----------------------------------------------------------------------------- // Miscellany //----------------------------------------------------------------------------- // Return the date/time at which the current executable was compiled. // params: none (-> "date time") OR // what to display [int]: 0 (-> "date"); 1 (-> "time") // returns: date and/or time [string] // notes: // - Displayed on main menu screen; tells non-programmers which auto-build // they are running. Could also be determined via .EXE file properties, // but that's a bit more trouble. // - To be exact, the date/time returned is when scriptglue.cpp was // last compiled; since the auto-build does full rebuilds, that is moot. JSBool buildTime( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MAX_PARAMS(1, buildTime); // buildTime( ) = "date time" // buildTime(0) = "date" // buildTime(1) = "time" JSString* s = JS_NewStringCopyZ(cx, argc && argv[0]==JSVAL_ONE ? __TIME__ : argc ? __DATE__ : __DATE__" "__TIME__ ); *rval = STRING_TO_JSVAL(s); return JS_TRUE; } // Return distance between 2 points. // params: 2 position vectors [CVector3D] // returns: Euclidean distance [float] JSBool v3dist( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, v3dist); CVector3D* a = ToNative( argv[0] ); CVector3D* b = ToNative( argv[1] ); float dist = ( *a - *b ).GetLength(); *rval = ToJSVal( dist ); return( JS_TRUE ); } // Returns the global object. // params: // returns: global object // notes: // - Useful for accessing an object from another scope. JSBool getGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getGlobal); *rval = OBJECT_TO_JSVAL( globalObject ); return( JS_TRUE ); } //----------------------------------------------------------------------------- // function table //----------------------------------------------------------------------------- // the JS interpreter expects the table to contain 5-tuples as follows: // - name the function will be called as from script; // - function which will be called; // - number of arguments this function expects // - Flags (deprecated, always zero) // - Extra (reserved for future use, always zero) // // we simplify this a bit with a macro: #define JS_FUNC(script_name, cpp_function, min_params) { #script_name, cpp_function, min_params, 0, 0 }, JSFunctionSpec ScriptFunctionTable[] = { // Console JS_FUNC(writeConsole, JSI_Console::writeConsole, 1) // external // Entity JS_FUNC(getEntityByHandle, getEntityByHandle, 1) JS_FUNC(getEntityTemplate, getEntityTemplate, 1) JS_FUNC(issueCommand, issueCommand, 2) // Camera JS_FUNC(setCameraTarget, setCameraTarget, 1) // GUI #ifndef NO_GUI JS_FUNC(getGUIObjectByName, JSI_IGUIObject::getByName, 1) // external JS_FUNC(getGUIGlobal, getGUIGlobal, 0) #endif // Events JS_FUNC(addGlobalHandler, AddGlobalHandler, 2) JS_FUNC(removeGlobalHandler, RemoveGlobalHandler, 2) // Timer JS_FUNC(setTimeout, setTimeout, 2) JS_FUNC(setInterval, setInterval, 2) JS_FUNC(cancelInterval, cancelInterval, 0) // Game Setup JS_FUNC(startGame, startGame, 0) JS_FUNC(endGame, endGame, 0) JS_FUNC(createClient, createClient, 0) JS_FUNC(createServer, createServer, 0) // VFS (external) JS_FUNC(buildFileList, JSI_VFS::BuildFileList, 1) JS_FUNC(getFileMTime, JSI_VFS::GetFileMTime, 1) JS_FUNC(getFileSize, JSI_VFS::GetFileSize, 1) JS_FUNC(readFile, JSI_VFS::ReadFile, 1) JS_FUNC(readFileLines, JSI_VFS::ReadFileLines, 1) // Internationalization JS_FUNC(loadLanguage, loadLanguage, 1) JS_FUNC(getLanguageID, getLanguageID, 0) // note: i18n/ScriptInterface.cpp registers translate() itself. // rationale: see implementation section above. // Debug JS_FUNC(crash, crash, 0) JS_FUNC(forceGC, forceGC, 0) // Misc. Engine Interface JS_FUNC(writeLog, WriteLog, 1) JS_FUNC(exit, exitProgram, 0) JS_FUNC(vmem, vmem, 0) JS_FUNC(_rewriteMaps, _rewriteMaps, 0) JS_FUNC(_lodbias, _lodbias, 0) JS_FUNC(setCursor, setCursor, 1) JS_FUNC(getFPS, getFPS, 0) // Miscellany JS_FUNC(v3dist, v3dist, 2) JS_FUNC(buildTime, buildTime, 0) JS_FUNC(getGlobal, getGlobal, 0) // end of table marker {0, 0, 0, 0, 0} }; #undef JS_FUNC //----------------------------------------------------------------------------- // property accessors //----------------------------------------------------------------------------- JSBool GetEntitySet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(argv), jsval* vp ) { std::vector* extant = g_EntityManager.getExtant(); *vp = OBJECT_TO_JSVAL( EntityCollection::Create( *extant ) ); delete( extant ); return( JS_TRUE ); } JSBool GetPlayerSet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { std::vector* players = g_Game->GetPlayers(); *vp = OBJECT_TO_JSVAL( PlayerCollection::Create( *players ) ); return( JS_TRUE ); } JSBool GetLocalPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { *vp = OBJECT_TO_JSVAL( g_Game->GetLocalPlayer()->GetScript() ); return( JS_TRUE ); } JSBool GetGaiaPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { *vp = OBJECT_TO_JSVAL( g_Game->GetPlayer( 0 )->GetScript() ); return( JS_TRUE ); } JSBool SetLocalPlayer( JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { CPlayer* newLocalPlayer = ToNative( *vp ); if( !newLocalPlayer ) { JS_ReportError( cx, "Not a valid Player." ); return( JS_TRUE ); } g_Game->SetLocalPlayer( newLocalPlayer ); return( JS_TRUE ); } JSBool GetGameView( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { if (g_Game) *vp = OBJECT_TO_JSVAL( g_Game->GetView()->GetScript() ); else *vp = JSVAL_NULL; return( JS_TRUE ); } enum ScriptGlobalTinyIDs { GLOBAL_SELECTION, GLOBAL_GROUPSARRAY, GLOBAL_CAMERA, GLOBAL_CONSOLE, }; // shorthand #define PERM JSPROP_PERMANENT #define CONST JSPROP_READONLY JSPropertySpec ScriptGlobalTable[] = { { "selection" , GLOBAL_SELECTION, PERM, JSI_Selection::getSelection, JSI_Selection::setSelection }, { "groups" , GLOBAL_GROUPSARRAY, PERM, JSI_Selection::getGroups, JSI_Selection::setGroups }, { "camera" , GLOBAL_CAMERA, PERM, JSI_Camera::getCamera, JSI_Camera::setCamera }, { "console" , GLOBAL_CONSOLE, PERM | CONST, JSI_Console::getConsole, 0 }, { "entities" , 0, PERM | CONST, GetEntitySet, 0 }, { "players" , 0, PERM | CONST, GetPlayerSet, 0 }, { "localPlayer", 0, PERM , GetLocalPlayer, SetLocalPlayer }, { "gaiaPlayer" , 0, PERM | CONST, GetGaiaPlayer, 0 }, { "gameView" , 0, PERM | CONST, GetGameView, 0 }, // end of table marker { 0, 0, 0, 0, 0 }, }; Index: ps/trunk/source/scripting/ScriptGlue.h =================================================================== --- ps/trunk/source/scripting/ScriptGlue.h (revision 2621) +++ ps/trunk/source/scripting/ScriptGlue.h (revision 2622) @@ -1,26 +1,23 @@ #ifndef _SCRIPTGLUE_H_ #define _SCRIPTGLUE_H_ #include "ScriptingHost.h" -#include "LightEnv.h" // required by g_LightEnv declaration below. - // referenced by ScriptingHost.cpp extern JSFunctionSpec ScriptFunctionTable[]; extern JSPropertySpec ScriptGlobalTable[]; // dependencies (moved to header to avoid L4 warnings) // .. from main.cpp: extern "C" int fps; extern void kill_mainloop(); extern CStr g_CursorName; extern void StartGame(); extern void EndGame(); // .. other -extern CLightEnv g_LightEnv; #if OS_WIN extern int GetVRAMInfo(int&, int&); #endif #endif // #ifndef _SCRIPTGLUE_H_ Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 2621) +++ ps/trunk/source/gui/CInput.cpp (revision 2622) @@ -1,1655 +1,1655 @@ /* CInput by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" #include "CInput.h" #include "ps/Font.h" #include "ogl.h" // TODO Gee: new #include "OverlayText.h" #include "lib/res/graphics/unifont.h" #include "ps/Hotkey.h" #include "ps/CLogger.h" #define LOG_CATEGORY "gui" extern bool keys[SDLK_LAST]; -extern bool mouseButtons[5]; +extern bool mouse_buttons[5]; using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CInput::CInput() : m_iBufferPos(-1), m_iBufferPos_Tail(-1), m_SelectingText(false), m_HorizontalScroll(0.f) { AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CStrW, "caption"); AddSetting(GUIST_int, "cell_id"); AddSetting(GUIST_CStr, "font"); AddSetting(GUIST_int, "max_length"); AddSetting(GUIST_bool, "multiline"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar_style"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea"); AddSetting(GUIST_CColor, "textcolor"); AddSetting(GUIST_CColor, "textcolor_selected"); AddSetting(GUIST_CStr, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); // Add scroll-bar CGUIScrollBarVertical * bar = new CGUIScrollBarVertical(); bar->SetRightAligned(true); bar->SetUseEdgeButtons(true); AddScrollBar(bar); } CInput::~CInput() { } int CInput::ManuallyHandleEvent(const SDL_Event* ev) { debug_assert(m_iBufferPos != -1); // Since the GUI framework doesn't handle to set settings // in Unicode (CStrW), we'll simply retrieve the actual // pointer and edit that. CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; if (ev->type == SDL_HOTKEYDOWN) { if (ev->user.code == HOTKEY_CONSOLE_PASTE) { wchar_t* text = clipboard_get(); if (text) { if (m_iBufferPos == (int)pCaption->Length()) *pCaption += text; else *pCaption = pCaption->Left(m_iBufferPos) + text + pCaption->Right((long) pCaption->Length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); m_iBufferPos += (int)wcslen(text); clipboard_free(text); } return EV_HANDLED; } } else if (ev->type == SDL_KEYDOWN) { int szChar = ev->key.keysym.sym; wchar_t cooked = (wchar_t)ev->key.keysym.unicode; switch (szChar) { case '\t': /* Auto Complete */ // TODO Gee: (2004-09-07) What to do with tab? break; case '\b': m_WantedX=0.f; if (SelectingText()) DeleteCurSelection(); else { m_iBufferPos_Tail = -1; if (pCaption->Length() == 0 || m_iBufferPos == 0) break; if (m_iBufferPos == (int)pCaption->Length()) *pCaption = pCaption->Left( (long) pCaption->Length()-1); else *pCaption = pCaption->Left( m_iBufferPos-1 ) + pCaption->Right( (long) pCaption->Length()-m_iBufferPos ); UpdateText(m_iBufferPos-1, m_iBufferPos, m_iBufferPos-1); --m_iBufferPos; } UpdateAutoScroll(); break; case SDLK_DELETE: m_WantedX=0.f; // If selection: if (SelectingText()) { DeleteCurSelection(); } else { if (pCaption->Length() == 0 || m_iBufferPos == (int)pCaption->Length()) break; *pCaption = pCaption->Left( m_iBufferPos ) + pCaption->Right( (long) pCaption->Length()-(m_iBufferPos+1) ); UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); } UpdateAutoScroll(); break; case SDLK_HOME: // If there's not a selection, we should create one now if (!keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } m_iBufferPos = 0; m_WantedX=0.f; UpdateAutoScroll(); break; case SDLK_END: // If there's not a selection, we should create one now if (!keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } m_iBufferPos = (long) pCaption->Length(); m_WantedX=0.f; UpdateAutoScroll(); break; /** Conventions for Left/Right when text is selected: References: Visual Studio Visual Studio has the 'newer' approach, used by newer versions of things, and in newer applications. A left press will always place the pointer on the left edge of the selection, and then of course remove the selection. Right will do the exakt same thing. If you have the pointer on the right edge and press right, it will in other words just remove the selection. Windows (eg. Notepad) A left press always takes the pointer a step to the left and removes the selection as if it were never there in the first place. Right of course does the same thing but to the right. I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN Messenger. **/ case SDLK_LEFT: // reset m_WantedX, very important m_WantedX=0.f; if (keys[SDLK_RSHIFT] || keys[SDLK_LSHIFT] || !SelectingText()) { // If there's not a selection, we should create one now if (!SelectingText() && !keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } if (m_iBufferPos) --m_iBufferPos; } else { if (m_iBufferPos_Tail < m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; case SDLK_RIGHT: m_WantedX=0.f; if (keys[SDLK_RSHIFT] || keys[SDLK_LSHIFT] || !SelectingText()) { // If there's not a selection, we should create one now if (!SelectingText() && !keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } if (m_iBufferPos != (int)pCaption->Length()) ++m_iBufferPos; } else { if (m_iBufferPos_Tail > m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; /** Conventions for Up/Down when text is selected: References: Visual Studio Visual Studio has a very strange approach, down takes you below the selection to the next row, and up to the one prior to the whole selection. The weird part is that it is always aligned as the 'pointer'. I decided this is to much work for something that is a bit arbitrary Windows (eg. Notepad) Just like with left/right, the selection is destroyed and it moves just as if there never were a selection. I chose the Notepad convention even though I use the VS convention with left/right. **/ case SDLK_UP: { // If there's not a selection, we should create one now if (!keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos-current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: if (current != m_CharacterPositions.begin()) { --current; // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); } break; case SDLK_DOWN: { // If there's not a selection, we should create one now if (!keys[SDLK_RSHIFT] && !keys[SDLK_LSHIFT]) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos-current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: // Add first, so we can check if it's .end() ++current; if (current != m_CharacterPositions.end()) { // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); } break; case SDLK_PAGEUP: GetScrollBar(0).ScrollMinusPlenty(); break; case SDLK_PAGEDOWN: GetScrollBar(0).ScrollPlusPlenty(); break; /* END: Message History Lookup */ case '\r': // 'Return' should do nothing for singe liners // otherwise a '\n' character will be added. { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (!multiline) break; cooked = '\n'; // Change to '\n' and do default: // NOTE: Fall-through } default: //Insert a character { // If there's a selection, delete if first. if (cooked == 0) return EV_PASS; // Important, because we didn't use any key // check max length int max_length; GUI::GetSetting(this, "max_length", max_length); if (max_length != 0 && (int)pCaption->Length() >= max_length) break; m_WantedX=0.f; if (SelectingText()) DeleteCurSelection(); m_iBufferPos_Tail = -1; if (m_iBufferPos == (int)pCaption->Length()) *pCaption += cooked; else *pCaption = pCaption->Left(m_iBufferPos) + CStrW(cooked) + pCaption->Right((long) pCaption->Length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); ++m_iBufferPos; UpdateAutoScroll(); } break; } } return EV_HANDLED; } void CInput::HandleMessage(const SGUIMessage &Message) { // TODO Gee: IGUIScrollBarOwner::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: { bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (scrollbar && (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("absolute"))) { GetScrollBar(0).SetX( m_CachedActualSize.right ); GetScrollBar(0).SetY( m_CachedActualSize.top ); GetScrollBar(0).SetZ( GetBufferedZ() ); GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); } // Update scrollbar if (Message.value == CStr("scrollbar_style")) { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); } if (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("font") || Message.value == CStr("absolute") || Message.value == CStr("caption") || Message.value == CStr("scrollbar") || Message.value == CStr("scrollbar_style")) { UpdateText(); } if (Message.value == CStr("multiline")) { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (multiline == false) { GetScrollBar(0).SetLength(0.f); } else { GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); } UpdateText(); } }break; case GUIM_MOUSE_PRESS_LEFT: // Check if we're selecting the scrollbar: { bool scrollbar, multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "multiline", multiline); if (GetScrollBar(0).GetStyle() && multiline) { if (GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width) break; } // Okay, this section is about pressing the mouse and // choosing where the point should be placed. For // instance, if we press between a and b, the point // should of course be placed accordingly. Other // special cases are handled like the input box norms. if (keys[SDLK_RSHIFT] || keys[SDLK_LSHIFT]) { m_iBufferPos = GetMouseHoveringTextPosition(); } else { m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); } m_SelectingText = true; UpdateAutoScroll(); // If we immediately release the button it will just be seen as a click // for the user though. }break; case GUIM_MOUSE_RELEASE_LEFT: if (m_SelectingText) { m_SelectingText = false; } break; case GUIM_MOUSE_MOTION: // If we just pressed down and started to move before releasing // this is one way of selecting larger portions of text. if (m_SelectingText) { // Actually, first we need to re-check that the mouse button is // really pressed (it can be released while outside the control. - if (!mouseButtons[SDL_BUTTON_LEFT]) + if (!mouse_buttons[SDL_BUTTON_LEFT]) m_SelectingText = false; else m_iBufferPos = GetMouseHoveringTextPosition(); UpdateAutoScroll(); } break; case GUIM_MOUSE_WHEEL_DOWN: GetScrollBar(0).ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; case GUIM_MOUSE_WHEEL_UP: GetScrollBar(0).ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; case GUIM_LOAD: { GetScrollBar(0).SetX( m_CachedActualSize.right ); GetScrollBar(0).SetY( m_CachedActualSize.top ); GetScrollBar(0).SetZ( GetBufferedZ() ); GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); UpdateText(); } break; case GUIM_GOT_FOCUS: //m_iBufferPos = 0; // TODO, a keeper? break; case GUIM_LOST_FOCUS: m_iBufferPos = -1; m_iBufferPos_Tail = -1; break; default: break; } } void CInput::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; float buffer_zone; bool multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); if (scrollbar && multiline) { // Draw scrollbar IGUIScrollBarOwner::Draw(); } if (GetGUI()) { CStr font_name; CColor color, color_selected; //CStrW caption; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "textcolor", color); GUI::GetSetting(this, "textcolor_selected", color_selected); // Get pointer of caption, it might be very large, and we don't // want to copy it continuously. CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL; int cell_id; GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea); GUI::GetSetting(this, "cell_id", cell_id); GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); float scroll=0.f; if (scrollbar && multiline) { scroll = GetScrollBar(0).GetPos(); } glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); CFont font(font_name); font.Bind(); glPushMatrix(); // We'll have to setup clipping manually, since we're doing the rendering manually. CRect cliparea(m_CachedActualSize); // First we'll figure out the clipping area, which is the cached actual size // substracted by an optional scrollbar if (scrollbar) { scroll = GetScrollBar(0).GetPos(); // substract scrollbar from cliparea if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } if (cliparea != CRect()) { double eq[4][4] = { { 0.0, 1.0, 0.0, -cliparea.top }, { 1.0, 0.0, 0.0, -cliparea.left }, { 0.0, -1.0, 0.0, cliparea.bottom }, { -1.0, 0.0, 0.0, cliparea.right } }; for (int i=0; i<4; ++i) { glClipPlane(GL_CLIP_PLANE0+i, eq[i]); glEnable(GL_CLIP_PLANE0+i); } } // These are useful later. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } // Get the height of this font. float h = (float)font.GetHeight(); float ls = (float)font.GetLineSpacing(); // Set the Z to somewhat more, so we can draw a selected area between the // the control and the text. glTranslatef((GLfloat)int(m_CachedActualSize.left) + buffer_zone, (GLfloat)int(m_CachedActualSize.top+h) + buffer_zone, bz+0.1f); //glColor4f(1.f, 1.f, 1.f, 1.f); // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE // (sort of like a | which is aligned to the left of most characters) float buffered_y = -scroll+buffer_zone; // When selecting larger areas, we need to draw a rectangle box // around it, and this is to keep track of where the box // started, because we need to follow the iteration until we // reach the end, before we can actually draw it. bool drawing_box = false; float box_x=0.f; float x_pointer=0.f; // If we have a selecting box (i.e. when you have selected letters, not just when // the pointer is between two letters) we need to process all letters once // before we do it the second time and render allt he text. We can't do it // in the same loop because text will have been drawn, so it will disappear when // drawn behind the text that has already been drawn. Confusing, well it's necessary // (I think). if (SelectingText()) { // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos, // just like you can select from right to left, as you can // left to right. Is there a difference? Yes, the pointer // be placed accordingly, so that if you select shift and // expand this selection, it will expand on appropriate side. // Anyway, since the drawing procedure needs "To" to be // greater than from, we need virtual values that might switch // place. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } bool done = false; for (list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls, x_pointer = 0.f) { if (multiline) { if (buffered_y > m_CachedActualSize.GetHeight()) break; } // We might as well use 'i' here to iterate, because we need it // (often compared against ints, so don't make it size_t) for (int i=0; i < (int)it->m_ListOfX.size()+2; ++i) { if (it->m_ListStart + i == VirtualFrom) { // we won't actually draw it now, because we don't // know the width of each glyph to that position. // we need to go along with the iteration, and // make a mark where the box started: drawing_box = true; // will turn false when finally rendered. // Get current x position box_x = x_pointer; } // no else! const bool at_end = (i == (int)it->m_ListOfX.size()+1); if (drawing_box == true && (it->m_ListStart + i == VirtualTo || at_end)) { // Depending on if it's just a row change, or if it's // the end of the select box, do slightly different things. if (at_end) { if (it->m_ListStart + i != VirtualFrom) { // and actually add a white space! yes, this is done in any common input x_pointer += (float)font.GetCharacterWidth(L' '); } } else { drawing_box = false; done = true; } CRect rect; // Set 'rect' depending on if it's a multiline control, or a one-line control if (multiline) { rect = CRect(m_CachedActualSize.left+box_x+buffer_zone, m_CachedActualSize.top+buffered_y, m_CachedActualSize.left+x_pointer+buffer_zone, m_CachedActualSize.top+buffered_y+ls); if (rect.bottom < m_CachedActualSize.top) continue; if (rect.top < m_CachedActualSize.top) rect.top = m_CachedActualSize.top; if (rect.bottom > m_CachedActualSize.bottom) rect.bottom = m_CachedActualSize.bottom; } else // if one-line { rect = CRect(m_CachedActualSize.left+box_x+buffer_zone-m_HorizontalScroll, m_CachedActualSize.top+buffered_y, m_CachedActualSize.left+x_pointer+buffer_zone-m_HorizontalScroll, m_CachedActualSize.top+buffered_y+ls); if (rect.left < m_CachedActualSize.left) rect.left = m_CachedActualSize.left; if (rect.right > m_CachedActualSize.right) rect.right = m_CachedActualSize.right; } glPushMatrix(); guiLoadIdentity(); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); if (sprite_selectarea) GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect); // Blend can have been reset glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); glDisable(GL_ALPHA_TEST); glPopMatrix(); } if (i < (int)it->m_ListOfX.size()) x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]); } if (done) break; // If we're about to draw a box, and all of a sudden changes // line, we need to draw that line's box, and then reset // the box drawing to the beginning of the new line. if (drawing_box) { box_x = 0.f; } } } // Reset some from previous run buffered_y = -scroll; // Setup initial color (then it might change and change back, when drawing selected area) glColor4f(color.r, color.g, color.b, color.a); bool using_selected_color = false; for (list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls) { if (buffered_y + buffer_zone >= -ls || !multiline) { if (multiline) { if (buffered_y + buffer_zone > m_CachedActualSize.GetHeight()) break; } glPushMatrix(); // Text must always be drawn in integer values. So we have to convert scroll if (multiline) glTranslatef(0.f, -(float)(int)scroll, 0.f); else glTranslatef(-(float)(int)m_HorizontalScroll, 0.f, 0.f); // We might as well use 'i' here, because we need it // (often compared against ints, so don't make it size_t) for (int i=0; i < (int)it->m_ListOfX.size()+1; ++i) { if (!multiline && i < (int)it->m_ListOfX.size()) { if (it->m_ListOfX[i] - m_HorizontalScroll < -buffer_zone) { // We still need to translate the OpenGL matrix if (i == 0) glTranslatef(it->m_ListOfX[i], 0.f, 0.f); else glTranslatef(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); continue; } } // End of selected area, change back color if (SelectingText() && it->m_ListStart + i == VirtualTo) { using_selected_color = false; glColor4f(color.r, color.g, color.b, color.a); } if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos) { // selecting only one, then we need only to draw a vertical line glyph. glPushMatrix(); glwprintf(L"%lc", 0xFE33); glPopMatrix(); } // Drawing selected area if (SelectingText() && it->m_ListStart + i >= VirtualFrom && it->m_ListStart + i < VirtualTo && using_selected_color == false) { using_selected_color = true; glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a); } if (i != (int)it->m_ListOfX.size()) glwprintf(L"%lc", (*pCaption)[it->m_ListStart + i]); // check it's now outside a one-liner, then we'll break if (!multiline && i < (int)it->m_ListOfX.size()) { if (it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth()-buffer_zone) break; } } if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos) { glColor4f(color.r, color.g, color.b, color.a); glwprintf(L"%lc", 0xFE33); if (using_selected_color) { glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a); } } glPopMatrix(); } glTranslatef(0.f, ls, 0.f); } glPopMatrix(); // Disable clipping for (int i=0; i<4; ++i) glDisable(GL_CLIP_PLANE0+i); glDisable(GL_TEXTURE_2D); } } void CInput::UpdateText(int from, int to_before, int to_after) { CStrW caption; CStr font_name; float buffer_zone; bool multiline; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "caption", caption); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); if (font_name == CStr()) { // Destroy everything stored, there's no font, so there can be // no data. m_CharacterPositions.clear(); return; } SRow row; row.m_ListStart = 0; int to = 0; // make sure it's initialized if (to_before == -1) to = (int)caption.Length(); CFont font(font_name); list::iterator current_line; // used to replace the last updated copy, because it might contain a "space" // in the end, which shouldn't be there because of word-wrapping. the only // way to know is to keep on going, but we don't want that, so we'll store // a copy. SRow copy; bool copy_used=false; // Used to ... TODO int check_point_row_start = -1; int check_point_row_end = -1; // Reset if (from == 0 && to_before == -1) { m_CharacterPositions.clear(); current_line = m_CharacterPositions.begin(); } else { debug_assert(to_before != -1); list::iterator destroy_row_from, destroy_row_to; // Used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == list::iterator() // ... was used, but it didn't work with GCC. bool destroy_row_from_used=false, destroy_row_to_used=false; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i=0; for (list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from_used == false && it->m_ListStart > from) { // Destroy the previous line, and all to 'to_before' destroy_row_from = it; --destroy_row_from; destroy_row_from_used = true; // For the rare case that we might remove characters to a word // so that it suddenly fits on the previous row, // we need to by standards re-do the whole previous line too // (if one exists) if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; } if (destroy_row_to_used == false && it->m_ListStart > to_before) { destroy_row_to = it; destroy_row_to_used = true; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; } ++destroy_row_to; break; } } if (destroy_row_from_used == false) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; // As usual, let's destroy another row back if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; destroy_row_from_used = true; current_line = destroy_row_from; } if (destroy_row_to_used == false) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = -1; destroy_row_from_used = true; } // set 'from' to the row we'll destroy from // and 'to' to the row we'll destroy to from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. else to = (int)caption.Length(); // Setup the first row row.m_ListStart = destroy_row_from->m_ListStart; // Set current line, new rows will be added before current_line, so // we'll choose the destroy_row_to, because it won't be deleted // in the coming erase. current_line = destroy_row_to; list::iterator temp_it = destroy_row_to; --temp_it; CStr c_caption1(caption.GetSubstring(destroy_row_from->m_ListStart, (temp_it->m_ListStart + temp_it->m_ListOfX.size()) -destroy_row_from->m_ListStart)); m_CharacterPositions.erase(destroy_row_from, destroy_row_to); // If there has been a change in number of characters // we need to change all m_ListStart that comes after // the interval we just destroyed. We'll change all // values with the delta change of the string length. int delta = to_after - to_before; if (delta != 0) { for (list::iterator it=current_line; it!=m_CharacterPositions.end(); ++it) { it->m_ListStart += delta; } // Update our check point too! check_point_row_start += delta; check_point_row_end += delta; if (to != (int)caption.Length()) to += delta; } } int last_word_started=from; //int last_list_start=-1; // unused float x_pos = 0.f; //if (to_before != -1) // return; for (int i=from; i= GetTextAreaWidth() && multiline) { // The following decides whether it will word-wrap a word, // or if it's only one word on the line, where it has to // break the word apart. if (last_word_started == row.m_ListStart) { last_word_started = i; row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started)); //row.m_ListOfX.push_back( x_pos ); //continue; } else { // regular word-wrap row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1)); } // Now, create a new line: // notice: when we enter a newline, you can stand with the cursor // both before and after that character, being on different // rows. With automatic word-wrapping, that is not possible. Which // is intuitively correct. CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size())); current_line = m_CharacterPositions.insert( current_line, row ); ++current_line; // Setup the next row: row.m_ListOfX.clear(); row.m_ListStart = last_word_started; i=last_word_started-1; x_pos = 0.f; } else // Get width of this character: row.m_ListOfX.push_back( x_pos ); } // Check if it's the last iteration, and we're not revising the whole string // because in that case, more word-wrapping might be needed. // also check if the current line isn't the end if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end()) { // check all rows and see if any existing if (row.m_ListStart != check_point_row_start) { list::iterator destroy_row_from, destroy_row_to; // Are used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == list::iterator() // was used, but it didn't work with GCC. bool destroy_row_from_used=false, destroy_row_to_used=false; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i=0; for (list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from_used == false && it->m_ListStart > check_point_row_start) { // Destroy the previous line, and all to 'to_before' //if (i >= 2) // destroy_row_from = it-2; //else // destroy_row_from = it-1; destroy_row_from = it; destroy_row_from_used = true; //--destroy_row_from; } if (destroy_row_to_used == false && it->m_ListStart > check_point_row_end) { destroy_row_to = it; destroy_row_to_used = true; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. // if (destroy_row_to != if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; } else check_point_row_start = check_point_row_end = -1; ++destroy_row_to; break; } } if (destroy_row_from_used == false) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; destroy_row_from_used = true; current_line = destroy_row_from; } if (destroy_row_to_used == false) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = check_point_row_end = -1; destroy_row_to_used = true; } // set 'from' to the from row we'll destroy // and 'to' to 'to_after' from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to. else to = (int)caption.Length(); // Set current line, new rows will be added before current_line, so // we'll choose the destroy_row_to, because it won't be deleted // in the coming erase. current_line = destroy_row_to; copy_used = true; list::iterator temp = destroy_row_to; --temp; copy = *temp; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); CStr c_caption(caption.GetSubstring(from, to-from)); } // else, the for loop will end naturally. } } // This is kind of special, when we renew a some lines, then the last // one will sometimes end with a space (' '), that really should // be omitted when word-wrapping. So we'll check if the last row // we'll add has got the same value as the next row. if (current_line != m_CharacterPositions.end()) { if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart) row.m_ListOfX.resize( row.m_ListOfX.size()-1 ); } // add the final row (even if empty) m_CharacterPositions.insert( current_line, row ); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scollbar if (scrollbar) { GetScrollBar(0).SetScrollRange( m_CharacterPositions.size() * font.GetLineSpacing() + buffer_zone*2.f ); GetScrollBar(0).SetScrollSpace( m_CachedActualSize.GetHeight() ); } } int CInput::GetMouseHoveringTextPosition() { if (m_CharacterPositions.empty()) return 0; // Return position int RetPosition; float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); list::iterator current = m_CharacterPositions.begin(); CPos mouse = GetMousePos(); if (multiline) { CStr font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "scrollbar", scrollbar); float scroll=0.f; if (scrollbar) { scroll = GetScrollBar(0).GetPos(); } // Pointer to caption, will come in handy CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; // Now get the height of the font. // TODO: Get the real font CFont font(font_name); float spacing = (float)font.GetLineSpacing(); //float height = (float)font.GetHeight(); // unused // Change mouse position relative to text. mouse -= m_CachedActualSize.TopLeft(); mouse.x -= buffer_zone; mouse.y += scroll - buffer_zone; //if ((m_CharacterPositions.size()-1) * spacing + height < mouse.y) // m_iBufferPos = pCaption->Length(); int row = (int)((mouse.y) / spacing);//m_CharachterPositions.size() if (row < 0) row = 0; if (row > (int)m_CharacterPositions.size()-1) row = (int)m_CharacterPositions.size()-1; // TODO Gee (2004-11-21): Okay, I need a 'list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. for (int i=0; im_ListStart; // Okay, now loop through the glyphs to find the appropriate X position float dummy; RetPosition += GetXTextPosition(current, mouse.x, dummy); return RetPosition; } // Does not process horizontal scrolling, 'x' must be modified before inputted. int CInput::GetXTextPosition(const list::iterator ¤t, const float &x, float &wanted) { int Ret=0; float previous=0.f; int i=0; for (vector::iterator it=current->m_ListOfX.begin(); it!=current->m_ListOfX.end(); ++it, ++i) { if (*it >= x) { if (x - previous >= *it - x) Ret += i+1; else Ret += i; break; } previous = *it; } // If a position wasn't found, we will assume the last // character of that line. if (i == (int)current->m_ListOfX.size()) { Ret += i; wanted = x; } else wanted = 0.f; return Ret; } void CInput::DeleteCurSelection() { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } *pCaption = pCaption->Left( VirtualFrom ) + pCaption->Right( (long) pCaption->Length()-(VirtualTo) ); UpdateText(VirtualFrom, VirtualTo, VirtualFrom); // Remove selection m_iBufferPos_Tail = -1; m_iBufferPos = VirtualFrom; } bool CInput::SelectingText() const { return m_iBufferPos_Tail != -1 && m_iBufferPos_Tail != m_iBufferPos; } float CInput::GetTextAreaWidth() { bool scrollbar; float buffer_zone; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); if (scrollbar && GetScrollBar(0).GetStyle()) return m_CachedActualSize.GetWidth() - buffer_zone*2.f - GetScrollBar(0).GetStyle()->m_Width; else return m_CachedActualSize.GetWidth() - buffer_zone*2.f; } void CInput::UpdateAutoScroll() { float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Autoscrolling up and down if (multiline) { CStr font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "scrollbar", scrollbar); float scroll=0.f; if (!scrollbar) return; scroll = GetScrollBar(0).GetPos(); // Now get the height of the font. // TODO: Get the real font CFont font(font_name); float spacing = (float)font.GetLineSpacing(); //float height = font.GetHeight(); // TODO Gee (2004-11-21): Okay, I need a 'list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. list::iterator current = m_CharacterPositions.begin(); int row=0; while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; ++row; } // If scrolling down if (-scroll + (float)(row+1) * spacing + buffer_zone*2.f > m_CachedActualSize.GetHeight()) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)(row+1) * spacing - m_CachedActualSize.GetHeight() + buffer_zone*2.f); } else // If scrolling up if (-scroll + (float)row * spacing < 0.f) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)row * spacing); } } else // autoscrolling left and right { // Get X position of position: if (m_CharacterPositions.empty()) return; float x_position = 0.f; float x_total = 0.f; if (!m_CharacterPositions.begin()->m_ListOfX.empty()) { // Get position of m_iBufferPos if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos && m_iBufferPos != 0) x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1]; // Get complete length: x_total = m_CharacterPositions.begin()->m_ListOfX[ m_CharacterPositions.begin()->m_ListOfX.size()-1 ]; } // Check if outside to the right if (x_position - m_HorizontalScroll + buffer_zone*2.f > m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Check if outside to the left if (x_position - m_HorizontalScroll < 0.f) m_HorizontalScroll = x_position; // Check if the text doesn't even fill up to the right edge even though scrolling is done. if (m_HorizontalScroll != 0.f && x_total - m_HorizontalScroll + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Now this is the fail-safe, if x_total isn't even the length of the control, // remove all scrolling if (x_total + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = 0.f; } } Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 2621) +++ ps/trunk/source/gui/MiniMap.cpp (revision 2622) @@ -1,333 +1,333 @@ #include "precompiled.h" #include "gui/MiniMap.h" #include "ps/Game.h" #include #include "ogl.h" #include "renderer/Renderer.h" #include "graphics/TextureEntry.h" #include "graphics/TextureManager.h" #include "graphics/Unit.h" #include "Bound.h" #include "Model.h" -extern bool mouseButtons[5]; +extern bool mouse_buttons[5]; extern int g_mouse_x, g_mouse_y; bool HasClicked=false; static unsigned int ScaleColor(unsigned int color,float x) { unsigned int r=uint(float(color & 0xff)*x); unsigned int g=uint(float((color>>8) & 0xff)*x); unsigned int b=uint(float((color>>16) & 0xff)*x); return (0xff000000 | r | g<<8 | b<<16); } static int RoundUpToPowerOf2(int x) { if ((x & (x-1))==0) return x; int d=x; while (d & (d-1)) { d&=(d-1); } return d<<1; } CMiniMap::CMiniMap() : m_Handle(0), m_Data(NULL), m_MapSize(0), m_Terrain(0), m_UnitManager(0) { AddSetting(GUIST_CColor, "fov_wedge_color"); AddSetting(GUIST_CStr, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::Draw() { // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started if(GetGUI() && g_Game && g_Game->IsGameStarted()) { if(!m_Handle) GenerateMiniMapTexture(); g_Renderer.BindTexture(0, m_Handle); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize); float z = GetBufferedZ(); glBegin(GL_QUADS); // Draw the main textured quad glTexCoord2f(0.0f, 0.0f); glVertex3f(m_CachedActualSize.left, m_CachedActualSize.bottom, z); glTexCoord2f(texCoordMax, 0.0f); glVertex3f(m_CachedActualSize.right, m_CachedActualSize.bottom, z); glTexCoord2f(texCoordMax, texCoordMax); glVertex3f(m_CachedActualSize.right, m_CachedActualSize.top, z); glTexCoord2f(0.0f, texCoordMax); glVertex3f(m_CachedActualSize.left, m_CachedActualSize.top, z); glEnd(); float x = m_CachedActualSize.left; float y = m_CachedActualSize.bottom; const std::vector &units = m_UnitManager->GetUnits(); std::vector::const_iterator iter = units.begin(); CUnit *unit = NULL; CVector2D pos; glDisable(GL_DEPTH_TEST); glEnable(GL_POINT_SMOOTH); glDisable(GL_TEXTURE_2D); glPointSize(3.0f); // REMOVED: glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_POINTS); for(; iter != units.end(); ++iter) { unit = (CUnit *)(*iter); if(unit && unit->GetEntity()) { // Set the player colour const SPlayerColour& colour = unit->GetEntity()->GetPlayer()->GetColour(); glColor3f(colour.r, colour.g, colour.b); pos = GetMapSpaceCoords(unit->GetEntity()->m_position); glVertex3f(x + pos.x, y - pos.y, z); } } glEnd(); //================================================================ //INTERACTIVE MINIMAP STARTS //Have questions on ^. Send email to ajdecker1022@msn.com //Get Camera handle CCamera &g_Camera=*g_Game->GetView()->GetCamera(); //Check for a click - if(mouseButtons[0]==true) + if(mouse_buttons[0]==true) { HasClicked=true; } //Check to see if left button is false (meaning it's been lifted) - if (mouseButtons[0]==false && HasClicked==true) + if (mouse_buttons[0]==false && HasClicked==true) { //Is cursor inside Minimap boundaries? if(g_mouse_x > m_CachedActualSize.left && g_mouse_x < m_CachedActualSize.right && g_mouse_y > m_CachedActualSize.top && g_mouse_y < m_CachedActualSize.bottom) { CVector3D CamOrient=g_Camera.m_Orientation.GetTranslation(); //get center point of screen CVector3D ScreenMiddle=g_Camera.GetWorldCoordinates( g_Renderer.GetWidth()/2,g_Renderer.GetHeight()/2); //Get Vector required to go from camera position to ScreenMiddle CVector3D TransVector; TransVector.X=CamOrient.X-ScreenMiddle.X; TransVector.Z=CamOrient.Z-ScreenMiddle.Z; //world position of where mouse clicked CVector3D Destination; //X and Z according to proportion of mouse position and minimap Destination.X=(CELL_SIZE*m_MapSize)* ((g_mouse_x-m_CachedActualSize.left)/m_CachedActualSize.GetWidth()); Destination.Z=(CELL_SIZE*m_MapSize)*((m_CachedActualSize.bottom-g_mouse_y) /m_CachedActualSize.GetHeight()); g_Camera.m_Orientation._14=Destination.X; g_Camera.m_Orientation._34=Destination.Z; g_Camera.m_Orientation._14+=TransVector.X; g_Camera.m_Orientation._34+=TransVector.Z; g_Camera.UpdateFrustum(); } HasClicked=false; } //END OF INTERACTIVE MINIMAP //==================================================================== // render view rect : John M. Mena // This sets up and draws the rectangle on the mini-map // which represents the view of the camera in the world. // Get a handle to the camera //Get correct world coordinates based off corner of screen start //at Bottom Left and going CW CVector3D hitPt[4]; hitPt[0]=g_Camera.GetWorldCoordinates(0,g_Renderer.GetHeight()); hitPt[1]=g_Camera.GetWorldCoordinates(g_Renderer.GetWidth(),g_Renderer.GetHeight()); hitPt[2]=g_Camera.GetWorldCoordinates(g_Renderer.GetWidth(),0); hitPt[3]=g_Camera.GetWorldCoordinates(0,0); float ViewRect[4][2]; for (int i=0;i<4;i++) { // convert to minimap space float px=hitPt[i].X; float pz=hitPt[i].Z; ViewRect[i][0]=(m_CachedActualSize.GetWidth()*px/float(CELL_SIZE*m_MapSize)); ViewRect[i][1]=(m_CachedActualSize.GetHeight()*pz/float(CELL_SIZE*m_MapSize)); } // Enable Scissoring as to restrict the rectangle // to only the mini-map below by retrieving the mini-maps // screen coords. glScissor((int)m_CachedActualSize.left, 0, (int)m_CachedActualSize.right, (int)m_CachedActualSize.GetHeight()); glEnable(GL_SCISSOR_TEST); glEnable(GL_LINE_SMOOTH); glLineWidth(2); glColor3f(1.0f,0.3f,0.3f); // Draw the viewing rectangle with the ScEd's conversion algorithm glBegin(GL_LINE_LOOP); glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]); glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]); glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]); glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]); glEnd(); glDisable(GL_SCISSOR_TEST); // Reset everything back to normal glPointSize(1.0f); glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); glEnable(GL_TEXTURE_2D); glDisable(GL_POINT_SMOOTH); glEnable(GL_DEPTH_TEST); } } void CMiniMap::GenerateMiniMapTexture() { m_Terrain = g_Game->GetWorld()->GetTerrain(); m_UnitManager = g_Game->GetWorld()->GetUnitManager(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); Destroy(); glGenTextures(1, (GLuint *)&m_Handle); g_Renderer.BindTexture(0, m_Handle); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = RoundUpToPowerOf2(m_MapSize); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0); m_Data = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); Rebuild(); } void CMiniMap::Rebuild() { u32 mapSize = m_Terrain->GetVerticesPerSide(); u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; for(u32 j = 0; j < h; j++) { u32 *dataPtr = m_Data + ((y + j) * (mapSize - 1)) + x; for(u32 i = 0; i < w; i++) { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * mapSize + x + i]) >> 8; int val = (hmap / 3) + 170; CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j); u32 color = 0; if(mp) { CTextureEntry *tex = mp->Tex1 ? g_TexMan.FindTexture(mp->Tex1) : 0; color = tex ? tex->GetBaseColor() : 0xffffffff; } else color = 0xffffffff; *dataPtr++ = ScaleColor(color, ((float)val) / 255.0f); } } UploadTexture(); } void CMiniMap::UploadTexture() { g_Renderer.BindTexture(0, m_Handle); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_Data); } void CMiniMap::Destroy() { if(m_Handle) glDeleteTextures(1, (GLuint *)&m_Handle); if(m_Data) { delete[] m_Data; m_Data = NULL; } } /* * Calefaction * TODO: Speed this up. There has to be some mathematical way to make * this more efficient. This works for now. */ CVector2D CMiniMap::GetMapSpaceCoords(CVector3D worldPos) { u32 x = (u32)(worldPos.X / CELL_SIZE); // Entity's Z coordinate is really its longitudinal coordinate on the terrain u32 y = (u32)(worldPos.Z / CELL_SIZE); // Calculate map space scale float scaleX = float(m_Width) / float(m_MapSize - 1); float scaleY = float(m_Height) / float(m_MapSize - 1); return CVector2D(float(x) * scaleX, float(y) * scaleY); } Index: ps/trunk/source/simulation/Scheduler.h =================================================================== --- ps/trunk/source/simulation/Scheduler.h (revision 2621) +++ ps/trunk/source/simulation/Scheduler.h (revision 2622) @@ -1,90 +1,94 @@ // Scheduler.h // // Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Message scheduler // #ifndef SCHEDULER_INCLUDED #define SCHEDULER_INCLUDED #include #include #include "EntityMessage.h" #include "EntityHandles.h" #include "Singleton.h" #include "CStr.h" #include "scripting/ScriptableObject.h" class CJSProgressTimer; // Message, destination and delivery time information. struct SDispatchObject { size_t deliveryTime; bool isRecurrent; size_t delay; SDispatchObject( const size_t _deliveryTime ) : deliveryTime( _deliveryTime ), isRecurrent( false ) {} SDispatchObject( const size_t _deliveryTime, const size_t _recurrence ) : deliveryTime( _deliveryTime ), isRecurrent( true ), delay( _recurrence ) {} inline bool operator<( const SDispatchObject& compare ) const { return( deliveryTime > compare.deliveryTime ); } }; struct SDispatchObjectScript : public SDispatchObject { CStrW script; JSObject* operateOn; inline SDispatchObjectScript( const CStrW& _script, const size_t _deliveryTime, JSObject* _operateOn = NULL ) : SDispatchObject( _deliveryTime ), script( _script ), operateOn( _operateOn ) {} inline SDispatchObjectScript( const CStrW& _script, const size_t _deliveryTime, JSObject* _operateOn, const size_t recurrence ) : SDispatchObject( _deliveryTime, recurrence ), script( _script ), operateOn( _operateOn ) {} }; struct SDispatchObjectFunction : public SDispatchObject { JSFunction* function; JSObject* operateOn; inline SDispatchObjectFunction( JSFunction* _function, const size_t _deliveryTime, JSObject* _operateOn = NULL ) : SDispatchObject( _deliveryTime ), function( _function ), operateOn( _operateOn ) {} inline SDispatchObjectFunction( JSFunction* _function, const size_t _deliveryTime, JSObject* _operateOn, const size_t recurrence ) : SDispatchObject( _deliveryTime, recurrence ), function( _function ), operateOn( _operateOn ) {} }; struct CScheduler : public Singleton { std::priority_queue timeScript, frameScript; std::priority_queue timeFunction, frameFunction; std::list progressTimers; bool m_abortInterval; 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 ); void pushTime( size_t delay, JSFunction* function, JSObject* operateOn = NULL ); void pushFrame( size_t delay, JSFunction* function, JSObject* operateOn = NULL ); void pushInterval( size_t first, size_t interval, JSFunction* function, JSObject* operateOn = NULL ); void pushProgressTimer( CJSProgressTimer* progressTimer ); void update(size_t elapsedSimulationTime); }; #define g_Scheduler CScheduler::GetSingleton() class CJSProgressTimer : public CJSObject { friend struct CScheduler; double m_Max, m_Current, m_Increment; JSFunction* m_Callback; JSObject* m_OperateOn; CJSProgressTimer( double Max, double Increment, JSFunction* Callback, JSObject* OperateOn ); static JSBool Construct( JSContext* cx, JSObject* obj, uint argc, jsval* argv, jsval* rval ); public: static void ScriptingInit(); }; +// made visible to main.cpp's Frame() so that it can abort after 100 frames +// if g_FixedFrameTiming == true (allows measuring performance). +extern size_t frameCount; + extern const int ORDER_DELAY; #endif Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 2621) +++ ps/trunk/source/main.cpp (revision 2622) @@ -1,1662 +1,370 @@ +/* + +This module drives the game when running without Atlas (our integrated +map editor). It receives input and OS messages via SDL and feeds them +into the input dispatcher, where they are passed on to the game GUI and +simulation. +It also contains main(), which either runs the above controller or +that of Atlas depending on commandline parameters. + +*/ + #include "precompiled.h" #ifdef SCED # include "ui/StdAfx.h" # undef ERROR #endif // SCED -#include -#include -#include -#include -#include - -#include "lib.h" +#include "lib/input.h" #include "lib/sdl.h" -#include "lib/ogl.h" -#include "lib/detect.h" #include "lib/timer.h" -#include "lib/input.h" -#include "lib/res/res.h" +#include "lib/res/file/vfs.h" #include "lib/res/sound/snd.h" -#include "lib/res/graphics/tex.h" -#include "lib/res/graphics/cursor.h" -#include "ps/Profile.h" -#include "ps/ProfileViewer.h" +#include "ps/GameSetup/GameSetup.h" +#include "ps/GameSetup/Atlas.h" +#include "ps/GameSetup/Config.h" #include "ps/Loader.h" -#include "ps/Font.h" +#include "gui/GUI.h" #include "ps/CConsole.h" +#include "ps/Profile.h" +#include "ps/System.h" #include "ps/Game.h" -#include "ps/Interact.h" #include "ps/Hotkey.h" -#include "ps/ConfigDB.h" -#include "ps/CLogger.h" -#include "ps/i18n.h" -#include "ps/Overlay.h" -#include "ps/StringConvert.h" - -#include "graphics/MapReader.h" -#include "graphics/Terrain.h" -#include "graphics/TextureManager.h" -#include "graphics/ObjectManager.h" -#include "graphics/SkeletonAnimManager.h" -#include "graphics/LightEnv.h" -#include "graphics/Model.h" -#include "graphics/UnitManager.h" -#include "graphics/MaterialManager.h" -#include "graphics/MeshManager.h" -#include "renderer/Renderer.h" - -#include "simulation/BaseEntityCollection.h" -#include "simulation/Entity.h" -#include "simulation/EntityHandles.h" -#include "simulation/EntityManager.h" -#include "simulation/PathfindEngine.h" +#include "ps/Interact.h" +#include "ps/Network/SessionManager.h" #include "simulation/Scheduler.h" -#include "simulation/Projectile.h" - -#include "scripting/ScriptingHost.h" -#include "scripting/GameEvents.h" -#include "scripting/JSInterface_Entity.h" -#include "scripting/JSInterface_BaseEntity.h" -#include "scripting/JSInterface_Vector3D.h" -#include "scripting/JSInterface_Camera.h" -#include "scripting/JSInterface_Selection.h" -#include "scripting/JSInterface_Console.h" -#include "scripting/JSCollection.h" -#include "scripting/DOMEvent.h" -#ifndef NO_GUI -# include "gui/scripting/JSInterface_IGUIObject.h" -# include "gui/scripting/JSInterface_GUITypes.h" -# include "gui/GUI.h" -#endif - #include "sound/CMusicPlayer.h" -#include "sound/JSI_Sound.h" - -#include "Network/SessionManager.h" -#include "Network/Server.h" -#include "Network/Client.h" +#define LOG_CATEGORY "main" +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +void kill_mainloop(); +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// -#define LOG_CATEGORY "main" -extern int conInputHandler(const SDL_Event* ev); -// Globals +// +// main app message handler +// bool keys[SDLK_LAST]; -bool mouseButtons[5]; -int g_mouse_x=50, g_mouse_y=50; +bool mouse_buttons[5]; // CAN REMOVE AFTER MOVING RESET TO WSDL -int g_xres, g_yres; -int g_bpp; -int g_freq; bool g_active = true; +int g_mouse_x = 50, g_mouse_y = 50; -// flag to disable extended GL extensions until fix found - specifically, crashes -// using VBOs on laptop Radeon cards -static bool g_NoGLVBO=false; -// flag to switch on shadows -static bool g_Shadows=false; -// flag to switch off pbuffers -static bool g_NoPBuffer=true; -// flag to switch on fixed frame timing (RC: I'm using this for profiling purposes) -static bool g_FixedFrameTiming=false; -static bool g_VSync = false; -static float g_LodBias = 0.0f; - -static bool g_Quickstart=false; - -extern CLightEnv g_LightEnv; - -static bool g_EntGraph = false; - -static float g_Gamma = 1.0f; - -extern int game_view_handler(const SDL_Event* ev); - -static CMusicPlayer MusicPlayer; - -CStr g_CursorName = "test"; -CStr g_ActiveProfile = "default"; - -extern size_t frameCount; -static bool quit = false; // break out of main loop - - -const wchar_t* HardcodedErrorString(int err) -{ -#define E(sym) case sym: return L ## #sym; - - switch(err) - { - E(ERR_NO_MEM) - E(ERR_FILE_NOT_FOUND) - E(ERR_INVALID_HANDLE) - E(ERR_INVALID_PARAM) - E(ERR_EOF) - E(ERR_PATH_NOT_FOUND) - E(ERR_PATH_LENGTH) - default: - return 0; - } -} - -const wchar_t* ErrorString(int err) -{ - // language file not available (yet) - return HardcodedErrorString(err); - - // TODO: load from language file -} - - -ERROR_GROUP(System); -ERROR_TYPE(System, SDLInitFailed); -ERROR_TYPE(System, VmodeFailed); -ERROR_TYPE(System, RequiredExtensionsMissing); - - -//---------------------------------------------------------------------------- -// Atlas (map editor) integration -//---------------------------------------------------------------------------- - -static void* const ATLAS_SO_UNAVAILABLE = (void*)-1; -static void* atlas_so_handle; - - -// free reference to Atlas UI SO (avoids resource leak report) -static void ATLAS_Shutdown() -{ - // (avoid dlclose warnings) - if(atlas_so_handle != 0 && atlas_so_handle != ATLAS_SO_UNAVAILABLE) - dlclose(atlas_so_handle); -} - - -// return true if the Atlas UI shared object is available; -// used to disable the main menu editor button if not. -// note: this actually loads the SO, but that isn't expected to be slow. -// call ATLAS_Shutdown at exit to avoid leaking it. -static bool ATLAS_IsAvailable() -{ - // first time: try to open Atlas UI shared object - // postcondition: atlas_so_handle valid or == ATLAS_SO_UNAVAILABLE. - if(atlas_so_handle == 0) - { - // since this SO exports a C++ interface, it is critical that - // compiler options are the same between app and SO; therefore, - // we need to go with the debug version in debug builds. - // note: on Windows, the extension is replaced with .dll by dlopen. -#ifndef NDEBUG - const char* so_name = "AtlasUI_d.so"; -#else - const char* so_name = "AtlasUI.so"; -#endif - // we don't care when relocations take place because this SO contains - // very few symbols, so RTLD_LAZY or RTLD_NOW aren't needed. - const int flags = RTLD_LOCAL; - atlas_so_handle = dlopen(so_name, flags); - // open failed (mostly likely SO not found) - if(!atlas_so_handle) - atlas_so_handle = ATLAS_SO_UNAVAILABLE; - } - - return atlas_so_handle != ATLAS_SO_UNAVAILABLE; -} - - -static bool atlas_is_running; - -// if Atlas is running, some parts of the GUI need not be loaded -// (reduces startup time). -static bool ATLAS_IsRunning() -{ - return atlas_is_running; -} - - -enum AtlasRunFlags -{ - // used by ATLAS_RunIfOnCmdLine; makes any error output go through - // DISPLAY_ERROR rather than a GUI dialog box (because GUI init was - // skipped to reduce load time). - ATLAS_NO_GUI = 1 -}; - -// starts the Atlas UI. -static void ATLAS_Run(int argc, char* argv[], int flags = 0) -{ - // first check if we can run at all - if(!ATLAS_IsAvailable()) - { - if(flags & ATLAS_NO_GUI) - DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested."); - else - DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested.");// TODO: implement GUI error message - return; - } - - void(*pStartWindow)(int argc, char* argv[]); - *(void**)&pStartWindow = dlsym(atlas_so_handle, "_StartWindow"); - pStartWindow(argc, argv); - - atlas_is_running = true; -} - - -// starts the Atlas UI if an "-editor" switch is found on the command line. -// this is the alternative to starting the main menu and clicking on -// the editor button; it is much faster because it's called during early -// init and therefore skips GUI setup. -// notes: -// - GUI init still runs, but some GUI setup will be skipped since -// ATLAS_IsRunning() will return true. -// - could be merged into CFG_ParseCommandLineArgs, because that appears -// to be called early enough. it's not really worth it because this -// code is quite simple and we thus avoid startup order dependency. -static void ATLAS_RunIfOnCmdLine(int argc, char* argv[]) -{ - for(int i = 1; i < argc; i++) // skip program name argument - { - if(!strcmp(argv[i], "-editor")) - { - // don't bother removing this param (unnecessary) - - ATLAS_Run(argc, argv, ATLAS_NO_GUI); - break; - } - } -} - - -//---------------------------------------------------------------------------- -// GUI integration -//---------------------------------------------------------------------------- - - -static void GUI_Init() -{ -#ifndef NO_GUI - {TIMER(ps_gui_init); - g_GUI.Initialize();} - - {TIMER(ps_gui_setup_xml); - g_GUI.LoadXMLFile("gui/test/setup.xml");} - {TIMER(ps_gui_styles_xml); - g_GUI.LoadXMLFile("gui/test/styles.xml");} - {TIMER(ps_gui_sprite1_xml); - g_GUI.LoadXMLFile("gui/test/sprite1.xml");} - - // Atlas is running, we won't need these GUI pages (for now! - // what if Atlas switches to in-game mode?!) - // TODO: temporary hack until revised GUI structure is completed. - if(ATLAS_IsRunning()) - return; - - {TIMER(ps_gui_1); - g_GUI.LoadXMLFile("gui/test/1_init.xml");} - {TIMER(ps_gui_2); - g_GUI.LoadXMLFile("gui/test/2_mainmenu.xml");} - {TIMER(ps_gui_3); - g_GUI.LoadXMLFile("gui/test/3_loading.xml");} - {TIMER(ps_gui_4); - g_GUI.LoadXMLFile("gui/test/4_session.xml");} - {TIMER(ps_gui_6); - g_GUI.LoadXMLFile("gui/test/6_subwindows.xml");} - {TIMER(ps_gui_6_1); - g_GUI.LoadXMLFile("gui/test/6_1_manual.xml");} - {TIMER(ps_gui_6_2); - g_GUI.LoadXMLFile("gui/test/6_2_jukebox.xml");} - {TIMER(ps_gui_7); - g_GUI.LoadXMLFile("gui/test/7_atlas.xml");} - {TIMER(ps_gui_9); - g_GUI.LoadXMLFile("gui/test/9_global.xml");} -#endif -} - - -static void GUI_Shutdown() -{ -#ifndef NO_GUI - g_GUI.Destroy(); - delete &g_GUI; -#endif -} - - -// display progress / description in loading screen -static void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) -{ -#ifndef NO_GUI - CStrW i18n_description = I18n::translate(pending_task); - JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); - g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(percent)); - g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); - g_GUI.SendEventToAll("progress"); -#endif -} - - -//---------------------------------------------------------------------------- -// config and profile -//---------------------------------------------------------------------------- - -static void CFG_LoadProfile( CStr profile ) -{ - CStr base = CStr( "profiles/" ) + profile; - g_ConfigDB.SetConfigFile(CFG_USER, true, base + "/settings/user.cfg"); - g_ConfigDB.Reload(CFG_USER); - - int max_history_lines = 50; - CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); - g_Console->UseHistoryFile(base+"/settings/history", max_history_lines); -} - - -// Fill in the globals from the config files. -static void CFG_LoadGlobals() -{ - CFG_GET_SYS_VAL("profile", String, g_ActiveProfile); - - // Now load the profile before trying to retrieve the values of the rest of these. - - CFG_LoadProfile( g_ActiveProfile ); - - CFG_GET_USER_VAL("xres", Int, g_xres); - CFG_GET_USER_VAL("yres", Int, g_yres); - - CFG_GET_USER_VAL("vsync", Bool, g_VSync); - CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); - CFG_GET_USER_VAL("shadows", Bool, g_Shadows); - - CFG_GET_USER_VAL("lodbias", Float, g_LodBias); - - float gain = -1.0f; - CFG_GET_USER_VAL("sound.mastergain", Float, gain); - if(gain > 0.0f) - WARN_ERR(snd_set_master_gain(gain)); - - LOG(NORMAL, LOG_CATEGORY, "g_x/yres is %dx%d", g_xres, g_yres); - LOG(NORMAL, LOG_CATEGORY, "Active profile is %s", g_ActiveProfile.c_str()); -} - - -static void CFG_ParseCommandLineArgs(int argc, char* argv[]) -{ - for(int i = 1; i < argc; i++) - { - // this arg isn't an option; skip - if(argv[i][0] != '-') - continue; - - char* name = argv[i]+1; // no leading '-' - - // switch first letter of option name - switch(argv[i][1]) - { - case 'c': - if(strcmp(name, "conf") == 0) - { - if(argc-i >= 1) // at least one arg left - { - i++; - char* arg = argv[i]; - char* equ = strchr(arg, '='); - if(equ) - { - *equ = 0; - g_ConfigDB.CreateValue(CFG_COMMAND, arg) - ->m_String = (equ+1); - } - } - } - break; - case 'e': - g_EntGraph = true; - break; - case 'f': - if(strncmp(name, "fixedframe", 10) == 0) - g_FixedFrameTiming=true; - break; - case 'g': - if(strncmp(name, "g=", 2) == 0) - { - g_Gamma = (float)atof(argv[i] + 3); - if(g_Gamma == 0.0f) - g_Gamma = 1.0f; - } - break; - case 'l': - if(strncmp(name, "listfiles", 9) == 0) - vfs_enable_file_listing(true); - break; - case 'n': - if(strncmp(name, "novbo", 5) == 0) - g_ConfigDB.CreateValue(CFG_COMMAND, "novbo")->m_String="true"; - else if(strncmp(name, "nopbuffer", 9) == 0) - g_NoPBuffer = true; - break; - case 'q': - if(strncmp(name, "quickstart", 10) == 0) - g_Quickstart = true; - break; - case 's': - if(strncmp(name, "shadows", 7) == 0) - g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String="true"; - break; - case 'v': - g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String="true"; - break; - case 'x': - if(strncmp(name, "xres=", 6) == 0) - g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String=argv[i]+6; - break; - case 'y': - if(strncmp(name, "yres=", 6) == 0) - g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String=argv[i]+6; - break; - case 'p': - if(strncmp(name, "profile=", 8) == 0 ) - g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = argv[i]+9; - break; - } // switch - } -} - - -static void CFG_Init(int argc, char* argv[]) -{ - debug_printf("CFG_Init &argc=%p &argv=%p\n", &argc, &argv); - - TIMER(CFG_Init); - MICROLOG(L"init config"); - - new CConfigDB; - - g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, "config/system.cfg"); - g_ConfigDB.Reload(CFG_SYSTEM); - - g_ConfigDB.SetConfigFile(CFG_MOD, true, "config/mod.cfg"); - // No point in reloading mod.cfg here - we haven't mounted mods yet - - CFG_ParseCommandLineArgs(argc, argv); - - // Collect information from system.cfg, the profile file, - // and any command-line overrides to fill in the globals. - CFG_LoadGlobals(); -} - -//---------------------------------------------------------------------------- - - - - -static std::string SplitExts(const char *exts) -{ - std::string str = exts; - std::string ret = ""; - size_t idx = str.find_first_of(" "); - while(idx != std::string::npos) - { - if(idx >= str.length() - 1) - { - ret += str; - break; - } - - ret += str.substr(0, idx); - ret += "\n"; - str = str.substr(idx + 1); - idx = str.find_first_of(" "); - } - - return ret; -} - - -static void WriteSystemInfo() -{ -TIMER(write_sys_info); - - get_gfx_info(); - // get_cpu_info already called during init - see call site - get_snd_info(); - get_mem_status(); - - struct utsname un; - uname(&un); - - FILE* f = fopen("../logs/system_info.txt", "w"); - if(!f) - return; - - // .. OS - fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version); - - // .. CPU - fprintf(f, "CPU : %s, %s", un.machine, cpu_type); - if(cpus > 1) - fprintf(f, " (x%d)", cpus); - if(cpu_freq != 0.0f) - { - if(cpu_freq < 1e9) - fprintf(f, ", %.2f MHz\n", cpu_freq*1e-6); - else - fprintf(f, ", %.2f GHz\n", cpu_freq*1e-9); - } - else - fprintf(f, "\n"); - - // .. memory - fprintf(f, "Memory : %lu MiB; %lu MiB free\n", tot_mem/MiB, avl_mem/MiB); - - // .. graphics - fprintf(f, "Graphics Card : %s\n", gfx_card); - fprintf(f, "OpenGL Drivers : %s; %s\n", glGetString(GL_VERSION), gfx_drv_ver); - fprintf(f, "Video Mode : %dx%d:%d@%d\n", g_xres, g_yres, g_bpp, g_freq); - - // .. sound - fprintf(f, "Sound Card : %s\n", snd_card); - fprintf(f, "Sound Drivers : %s\n", snd_drv_ver); - - - // - // .. network name / ips - // - - // note: can't use un.nodename because it is for an - // "implementation-defined communications network". - char hostname[128] = "(unknown)"; - (void)gethostname(hostname, sizeof(hostname)-1); - // -1 makes sure it's 0-terminated. if the function fails, - // we display "(unknown)" and will skip IP output below. - fprintf(f, "Network Name : %s", hostname); - - { - hostent* host = gethostbyname(hostname); - if(!host) - goto no_ip; - struct in_addr** ips = (struct in_addr**)host->h_addr_list; - if(!ips) - goto no_ip; - - // output all IPs (> 1 if using VMware or dual ethernet) - fprintf(f, " ("); - for(uint i = 0; i < 256 && ips[i]; i++) // safety - { - // separate entries but avoid trailing comma - if(i != 0) - fprintf(f, ", "); - fprintf(f, "%s", inet_ntoa(*ips[i])); - } - fprintf(f, ")"); - } - -no_ip: - fprintf(f, "\n"); - - - // .. OpenGL extensions (write them last, since it's a lot of text) - const char* exts = oglExtList(); - if (!exts) exts = "{unknown}"; - fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str()); - - fclose(f); - f = 0; -} - - -static int SetVideoMode(int w, int h, int bpp, bool fullscreen) -{ - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - ulong flags = SDL_OPENGL; - if(fullscreen) - flags |= SDL_FULLSCREEN; - if(!SDL_SetVideoMode(w, h, bpp, flags)) - return -1; - - glViewport(0, 0, w, h); - -#ifndef NO_GUI - g_GUI.UpdateResolution(); -#endif - - oglInit(); // required after each mode change - - if(SDL_SetGamma(g_Gamma, g_Gamma, g_Gamma) < 0) - debug_warn("SDL_SetGamma failed"); - - return 0; -} - - -// identifies the file format that is to be written -// (case-insensitive). examples: "bmp", "png", "jpg". -// BMP is good for quick output at the expense of large files. -static void WriteScreenshot(const char* extension = "png") -{ - // determine next screenshot number. - // - // current approach: increment number until that file doesn't yet exist. - // this is fairly slow, but it's typically only done once, since the last - // number is cached. binary search shouldn't be necessary. - // - // known bug: after program restart, holes in the number series are - // filled first. example: add 1st and 2nd; [exit] delete 1st; [restart] - // add 3rd -> it gets number 1, not 3. - // could fix via enumerating all files, but it's not worth it ATM. - char fn[VFS_MAX_PATH]; - - const char* file_format_string = "screenshots/screenshot%04d.%s"; - // %04d -> always 4 digits, so sorting by filename works correctly. - - static int next_num = 1; - do - sprintf(fn, file_format_string, next_num++, extension); - while(vfs_exists(fn)); - - const int w = g_xres, h = g_yres; - const int bpp = 24; - GLenum fmt = GL_RGB; - int flags = TEX_BOTTOM_UP; - // we want writing BMP to be as fast as possible, - // so read data from OpenGL in BMP format to obviate conversion. - if(!stricmp(extension, "bmp")) - { - fmt = GL_BGR; - flags |= TEX_BGR; - } - - const size_t size = w * h * bpp; - void* img = mem_alloc(size); - - glReadPixels(0, 0, w, h, fmt, GL_UNSIGNED_BYTE, img); - - if(tex_write(fn, w, h, bpp, flags, img) < 0) - debug_warn("WriteScreenshot: tex_write failed"); - - mem_free(img); -} - - - -// HACK: Let code from other files (i.e. the scripting system) quit -void kill_mainloop() -{ - quit = true; -} static int MainInputHandler(const SDL_Event* ev) { int c; switch(ev->type) { case SDL_ACTIVEEVENT: - g_active = ev->active.gain != 0; + g_active = (ev->active.gain != 0); + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + c = ev->key.keysym.sym; + keys[c] = (ev->type == SDL_KEYDOWN); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + c = ev->button.button; + if(c < ARRAY_SIZE(mouse_buttons)) + mouse_buttons[c] = (ev->type == SDL_MOUSEBUTTONDOWN); + else + debug_warn("MainInputHandler: invalid mouse button"); break; case SDL_MOUSEMOTION: g_mouse_x = ev->motion.x; g_mouse_y = ev->motion.y; break; - case SDL_KEYDOWN: - c = ev->key.keysym.sym; - keys[c] = true; - break; - case SDL_HOTKEYDOWN: - - switch( ev->user.code ) + switch(ev->user.code) { case HOTKEY_EXIT: - quit = true; + kill_mainloop(); break; case HOTKEY_SCREENSHOT: - WriteScreenshot(); + WriteScreenshot("png"); break; case HOTKEY_PLAYMUSIC: - { -// MusicPlayer.open("audio/music/germanic peace 3.ogg"); -// MusicPlayer.play(); - Handle hs = snd_open( - //"audio/music/germanic peace 3.ogg" - "audio/voice/hellenes/soldier/Attack-ZeusSaviourandVictory.ogg" - ); - - - snd_set_pos(hs, 0,0,0, true); - snd_play(hs); - } break; default: - return( EV_PASS ); + return EV_PASS; } - return( EV_HANDLED ); - - case SDL_KEYUP: - c = ev->key.keysym.sym; - keys[c] = false; - break; - case SDL_MOUSEBUTTONDOWN: - c = ev->button.button; - if( c < 5 ) - mouseButtons[c] = true; - else - debug_warn("SDL mouse button defs changed; fix mouseButton array def"); - break; - case SDL_MOUSEBUTTONUP: - c = ev->button.button; - if( c < 5 ) - mouseButtons[c] = false; - else - debug_warn("SDL mouse button defs changed; fix mouseButton array def"); - break; + return EV_HANDLED; } return EV_PASS; } -void EndGame() -{ - if (g_NetServer) - { - delete g_NetServer; - g_NetServer=NULL; - } - else if (g_NetClient) - { - delete g_NetClient; - g_NetClient=NULL; - } - - delete g_Game; - g_Game=NULL; -} - -///////////////////////////////////////////////////////////////////////////////////////////// -// RenderNoCull: render absolutely everything to a blank frame to force renderer -// to load required assets -static void RenderNoCull() -{ - g_Renderer.BeginFrame(); - - if (g_Game) - g_Game->GetView()->RenderNoCull(); - - g_Renderer.FlushFrame(); - glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); - g_Renderer.EndFrame(); -} - - -static void Render() -{ - MICROLOG(L"begin frame"); - - oglCheck(); - -#ifndef NO_GUI // HACK: because colour-parsing requires the GUI - CStr skystring = "61 193 255"; - CFG_GET_USER_VAL("skycolor", String, skystring); - CColor skycol; - GUI::ParseString(skystring, skycol); - g_Renderer.SetClearColor(skycol.Int()); -#endif - - // start new frame - g_Renderer.BeginFrame(); - - oglCheck(); - - if (g_Game && g_Game->IsGameStarted()) - { - g_Game->GetView()->Render(); - - oglCheck(); - - MICROLOG(L"flush frame"); - PROFILE_START( "flush frame" ); - g_Renderer.FlushFrame(); - PROFILE_END( "flush frame" ); - - glPushAttrib( GL_ENABLE_BIT ); - glDisable( GL_LIGHTING ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_DEPTH_TEST ); - - if( g_EntGraph ) - { - PROFILE( "render entity overlays" ); - glColor3f( 1.0f, 0.0f, 1.0f ); - - MICROLOG(L"render entities"); - g_EntityManager.renderAll(); // <-- collision outlines, pathing routes - } - - PROFILE_START( "render selection" ); - g_Mouseover.renderSelectionOutlines(); - g_Selection.renderSelectionOutlines(); - PROFILE_END( "render selection" ); - - glPopAttrib(); - } - else - { - PROFILE_START( "flush frame" ); - g_Renderer.FlushFrame(); - PROFILE_END( "flush frame" ); - } - - oglCheck(); - - PROFILE_START( "render fonts" ); - MICROLOG(L"render fonts"); - // overlay mode - glPushAttrib(GL_ENABLE_BIT); - glEnable(GL_TEXTURE_2D); - glDisable(GL_CULL_FACE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - - PROFILE_END( "render fonts" ); - - oglCheck(); - -#ifndef NO_GUI - // Temp GUI message GeeTODO - glLoadIdentity(); - MICROLOG(L"render GUI"); - PROFILE_START( "render gui" ); - g_GUI.Draw(); - PROFILE_END( "render gui" ); -#endif - - oglCheck(); - - // Text: - - // Use the GL_ALPHA texture as the alpha channel with a flat colouring - glDisable(GL_ALPHA_TEST); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - // Added -- - glEnable(GL_TEXTURE_2D); - // -- GL - - oglCheck(); - - { - PROFILE( "render console" ); - glLoadIdentity(); - - MICROLOG(L"render console"); - CFont font("console"); - font.Bind(); - g_Console->Render(); - } - - oglCheck(); - - - // Profile information - - PROFILE_START( "render profiling" ); - RenderProfile(); - PROFILE_END( "render profiling" ); - - oglCheck(); - - if (g_Game && g_Game->IsGameStarted()) - { - PROFILE( "render selection overlays" ); - g_Mouseover.renderOverlays(); - g_Selection.renderOverlays(); - } - - oglCheck(); - - // Draw the cursor (or set the Windows cursor, on Windows) - cursor_draw(g_CursorName, g_mouse_x, g_mouse_y); - - // restore - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glPopAttrib(); - - MICROLOG(L"end frame"); - g_Renderer.EndFrame(); - - oglCheck(); -} - - - -static void InitScripting() -{ -TIMER(InitScripting); - // Create the scripting host. This needs to be done before the GUI is created. - new ScriptingHost; - - // It would be nice for onLoad code to be able to access the setTimeout() calls. - new CScheduler; - - // Register the JavaScript interfaces with the runtime - CEntity::ScriptingInit(); - CBaseEntity::ScriptingInit(); - JSI_Sound::ScriptingInit(); - CProfileNode::ScriptingInit(); - -#ifndef NO_GUI - JSI_IGUIObject::init(); - JSI_GUITypes::init(); -#endif - JSI_Vector3D::init(); - EntityCollection::Init( "EntityCollection" ); - SColour::ScriptingInit(); - CPlayer::ScriptingInit(); - - PlayerCollection::Init( "PlayerCollection" ); - CDamageType::ScriptingInit(); - CJSComplexPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. - CScriptEvent::ScriptingInit(); - CJSProgressTimer::ScriptingInit(); - CProjectile::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 ); - g_ScriptingHost.DefineConstant( "ORDER_GATHER", CEntityOrder::ORDER_GATHER ); - -#define REG_JS_CONSTANT(_name) g_ScriptingHost.DefineConstant(#_name, _name) - REG_JS_CONSTANT(SDL_BUTTON_LEFT); - REG_JS_CONSTANT(SDL_BUTTON_MIDDLE); - REG_JS_CONSTANT(SDL_BUTTON_RIGHT); - REG_JS_CONSTANT(SDL_BUTTON_WHEELUP); - REG_JS_CONSTANT(SDL_BUTTON_WHEELDOWN); -#undef REG_JS_CONSTANT - - CNetMessage::ScriptingInit(); - - JSI_Camera::init(); - JSI_Console::init(); - - new CGameEvents; -} - - -static void InitVfs(const char* argv0) -{ -TIMER(InitVfs); - // set current directory to "$game_dir/data". - // this is necessary because it is otherwise unknown, - // especially if run from a shortcut / symlink. - // - // "../data" is relative to the executable (in "$game_dir/system"). - // - // rationale for data/ being root: untrusted scripts must not be - // allowed to overwrite critical game (or worse, OS) files. - // the VFS prevents any accesses to files above this directory. - int err = file_rel_chdir(argv0, "../data"); - if(err < 0) - throw err; - - { - vfs_init(); - vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH); - vfs_mount("screenshots/", "screenshots"); - vfs_mount("profiles/", "profiles", VFS_MOUNT_RECURSIVE); - } -extern void vfs_dump_stats(); -vfs_dump_stats(); - // don't try vfs_display yet: SDL_Init hasn't yet redirected stdout -} - - -static void InitPs() -{ - // console - { - TIMER(ps_console); - - g_Console->UpdateScreenSize(g_xres, g_yres); - - // Calculate and store the line spacing - CFont font("console"); - g_Console->m_iFontHeight = font.GetLineSpacing(); - // Offset by an arbitrary amount, to make it fit more nicely - g_Console->m_iFontOffset = 9; - } - - // language and hotkeys - { - TIMER(ps_lang_hotkeys); - - std::string lang = "english"; - CFG_GET_SYS_VAL("language", String, lang); - I18n::LoadLanguage(lang.c_str()); - - loadHotkeys(); - } - - // GUI uses VFS, so this must come after VFS init. - GUI_Init(); -} - - -static void InitInput() -{ - // register input handlers - // This stack is constructed so the first added, will be the last - // one called. This is important, because each of the handlers - // has the potential to block events to go further down - // in the chain. I.e. the last one in the list added, is the - // only handler that can block all messages before they are - // processed. - in_add_handler(game_view_handler); - - in_add_handler(interactInputHandler); - - in_add_handler(conInputHandler); - - in_add_handler(profilehandler); - - in_add_handler(hotkeyInputHandler); - - // gui_handler needs to be after (i.e. called before!) the hotkey handler - // so that input boxes can be typed in without setting off hotkeys. -#ifndef NO_GUI - in_add_handler(gui_handler); -#endif - - // must be after gui_handler. Should mayhap even be last. - in_add_handler(MainInputHandler); -} - - -static void ShutdownPs() +// dispatch all pending events to the various receivers. +static void PumpEvents() { - GUI_Shutdown(); + in_dispatch_recorded_events(); - delete g_Console; - - // disable the special Windows cursor, or free textures for OGL cursors - cursor_draw(0, g_mouse_x, g_mouse_y); - - // close down Xerces if it was loaded - CXeromyces::Terminate(); - - MusicPlayer.release(); - - // Unload the real language (since it depends on the scripting engine, - // which is going to be killed later) and use the English fallback messages - I18n::LoadLanguage(NULL); -} - - -static void InitRenderer() -{ -TIMER(InitRenderer); - // create renderer - new CRenderer; - - // set renderer options from command line options - NOVBO must be set before opening the renderer - g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); - g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); - g_Renderer.SetOptionBool(CRenderer::OPT_NOPBUFFER,g_NoPBuffer); - g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); - - // create terrain related stuff - new CTextureManager; - - // create the material manager - new CMaterialManager; - new CMeshManager; - - // create actor related stuff - new CSkeletonAnimManager; - new CObjectManager; - new CUnitManager; - - MICROLOG(L"init renderer"); - g_Renderer.Open(g_xres,g_yres,g_bpp); - - // Setup default lighting environment. Since the Renderer accesses the - // lighting environment through a pointer, this has to be done before - // the first Frame. - g_LightEnv.m_SunColor=RGBColor(1,1,1); - g_LightEnv.SetRotation(DEGTORAD(270)); - g_LightEnv.SetElevation(DEGTORAD(45)); - g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0); - g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f); - g_Renderer.SetLightEnv(&g_LightEnv); - - // I haven't seen the camera affecting GUI rendering and such, but the - // viewport has to be updated according to the video mode - SViewPort vp; - vp.m_X=0; - vp.m_Y=0; - vp.m_Width=g_xres; - vp.m_Height=g_yres; - g_Renderer.SetViewport(vp); + SDL_Event event; + while(SDL_PollEvent(&event)) + in_dispatch_event(&event); } -static void InitSDL() -{ - MICROLOG(L"init sdl"); - if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) - { - LOG(ERROR, LOG_CATEGORY, "SDL library initialization failed: %s", SDL_GetError()); - throw PSERROR_System_SDLInitFailed(); - } - atexit(SDL_Quit); - SDL_EnableUNICODE(1); -} static int ProgressiveLoad() { wchar_t description[100]; int progress_percent; int ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent); switch(ret) { - // no load active => no-op (skip code below) + // no load active => no-op (skip code below) case 0: return 0; - // current task didn't complete. we only care about this insofar as the - // load process is therefore not yet finished. + // current task didn't complete. we only care about this insofar as the + // load process is therefore not yet finished. case ERR_TIMED_OUT: break; - // just finished loading + // just finished loading case LDR_ALL_FINISHED: g_Game->ReallyStartGame(); wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting.."); - // LDR_ProgressiveLoad returns L""; set to valid text to - // avoid problems in converting to JSString + // LDR_ProgressiveLoad returns L""; set to valid text to + // avoid problems in converting to JSString break; - // error! + // error! default: CHECK_ERR(ret); - // can't do this above due to legit ERR_TIMED_OUT + // can't do this above due to legit ERR_TIMED_OUT break; } GUI_DisplayLoadProgress(progress_percent, description); return 0; } - - -static void Shutdown() -{ - MICROLOG(L"Shutdown"); - - ATLAS_Shutdown(); - - ShutdownPs(); // Must delete g_GUI before g_ScriptingHost - - if (g_Game) - EndGame(); - - delete &g_Scheduler; - - delete &g_SessionManager; - - delete &g_Mouseover; - delete &g_Selection; - - delete &g_Pathfinder; - // Managed by CWorld - // delete &g_EntityManager; - - delete &g_GameAttributes; - delete &g_JSGameEvents; - - delete &g_EntityTemplateCollection; - - delete &g_ScriptingHost; - - // destroy actor related stuff - delete &g_UnitMan; - delete &g_ObjMan; - delete &g_SkelAnimMan; - - delete &g_MaterialManager; - delete &g_MeshManager; - - // destroy terrain related stuff - delete &g_TexMan; - - // destroy renderer - delete &g_Renderer; - - delete &g_ConfigDB; - - // Shut down the network loop - CSocketBase::Shutdown(); - - // Really shut down the i18n system. Any future calls - // to translate() will crash. - I18n::Shutdown(); - - snd_shutdown(); - - vfs_shutdown(); - - h_mgr_shutdown(); - mem_shutdown(); - - debug_shutdown(); - - delete &g_Logger; - - delete &g_Profiler; -} - - -// workaround for VC7 EBP-trashing bug, which confuses the stack trace code. -#if MSC_VERSION -# pragma optimize("", off) -#endif - -static void Init(int argc, char* argv[], bool setup_gfx = true) -{ -debug_printf("INIT &argc=%p &argv=%p\n", &argc, &argv); - - MICROLOG(L"Init"); - - debug_set_thread_name("main"); - - // If you ever want to catch a particular allocation: - //_CrtSetBreakAlloc(187); - - // no longer set 24 bit (float) precision by default: for - // very long game uptimes (> 1 day; e.g. dedicated server), - // we need full precision when calculating the time. - // if there's a spot where we want to speed up divides|sqrts, - // we can temporarily change precision there. -// _control87(_PC_24, _MCW_PC); - - // detects CPU clock frequency and capabilities, which are prerequisites - // for using the TSC as a timer (desirable due to its high resolution). - // do this before lengthy init so we can time those accurately. - get_cpu_info(); - - // Do this as soon as possible, because it chdirs - // and will mess up the error reporting if anything - // crashes before the working directory is set. - MICROLOG(L"init vfs"); - const char* argv0 = argc? argv[0] : NULL; - // ScEd doesn't have a main(argc, argv), and so it has no argv. In that - // case, just pass NULL to InitVfs, which will work out the current - // directory for itself. - InitVfs(argv0); - - // This must come after VFS init, which sets the current directory - // (required for finding our output log files). - new CLogger; - - // Call LoadLanguage(NULL) to initialise the I18n system, but - // without loading an actual language file - translate() will - // just show the English key text, which is better than crashing - // from a null pointer when attempting to translate e.g. error messages. - // Real languages can only be loaded when the scripting system has - // been initialised. - // - // this uses LOG and must therefore come after CLogger init. - MICROLOG(L"init i18n"); - I18n::LoadLanguage(NULL); - - // should be done before the bulk of GUI init because it prevents - // most loads from happening (since ATLAS_IsRunning will return true). - ATLAS_RunIfOnCmdLine(argc, argv); - - // Set up the console early, so that debugging - // messages can be logged to it. (The console's size - // and fonts are set later in InitPs()) - g_Console = new CConsole(); - - if(setup_gfx) - InitSDL(); - - // preferred video mode = current desktop settings - // (command line params may override these) - get_cur_vmode(&g_xres, &g_yres, &g_bpp, &g_freq); - - new CProfileManager; // before any script code - - MICROLOG(L"init scripting"); - InitScripting(); // before GUI - - // g_ConfigDB, command line args, globals - CFG_Init(argc, argv); - - // GUI is notified in SetVideoMode, so this must come before that. -#ifndef NO_GUI - new CGUI; -#endif - - bool windowed = false; - CFG_GET_SYS_VAL("windowed", Bool, windowed); - - if (setup_gfx) - { - MICROLOG(L"set vmode"); - - if(SetVideoMode(g_xres, g_yres, 32, !windowed) < 0) - { - LOG(ERROR, LOG_CATEGORY, "Could not set %dx%d graphics mode: %s", g_xres, g_yres, SDL_GetError()); - throw PSERROR_System_VmodeFailed(); - } - SDL_WM_SetCaption("0 A.D.", "0 A.D."); - } - - oglCheck(); - - if(!g_Quickstart) - { - WriteSystemInfo(); - vfs_display(); - } - else - { - // speed up startup by disabling all sound - // (OpenAL init will be skipped). - // must be called before first snd_open. - snd_disable(true); - } - - // (must come after SetVideoMode, since it calls oglInit) - const char* missing = oglHaveExtensions(0, "GL_ARB_multitexture", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", 0); - if(missing) - { - wchar_t buf[500]; - const wchar_t* fmt = - L"The %hs extension doesn't appear to be available on your computer. " - L"The game may still work, though - you are welcome to try at your own risk. " - L"If not or it doesn't look right, upgrade your graphics card."; - swprintf(buf, ARRAY_SIZE(buf), fmt, missing); - DISPLAY_ERROR(buf); - // TODO: i18n - } - - // enable/disable VSync - // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. -#if OS_WIN - if(oglHaveExtension("WGL_EXT_swap_control")) - wglSwapIntervalEXT(g_VSync? 1 : 0); -#endif - - MICROLOG(L"init ps"); - InitPs(); - - oglCheck(); - InitRenderer(); - -TIMER(init_after_InitRenderer); - - // This needs to be done after the renderer has loaded all its actors... - new CBaseEntityCollection; - // CEntityManager is managed by CWorld - //new CEntityManager; - new CPathfindEngine; - new CSelectedEntities; - new CMouseoverEntities; - - new CSessionManager; - - new CGameAttributes; - - // Register a few Game/Network JS globals - g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetScript())); - - // Check for heap corruption after every allocation. Very, very slowly. - // (And it highlights the allocation just after the one you care about, - // so you need to run it again and tell it to break on the one before.) -// debug_heap_enable(DEBUG_HEAP_ALL); - - InitInput(); - - oglCheck(); - -#ifndef NO_GUI - g_GUI.SendEventToAll("load"); -#endif - - if (setup_gfx) - { - MICROLOG(L"render blank"); - // render everything to a blank frame to force renderer to load everything - RenderNoCull(); - } - - if (g_FixedFrameTiming) { - CCamera &g_Camera=*g_Game->GetView()->GetCamera(); -#if 0 // TOPDOWN - g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(90)); - g_Camera.m_Orientation.SetIdentity(); - g_Camera.m_Orientation.RotateX(DEGTORAD(90)); - g_Camera.m_Orientation.Translate(CELL_SIZE*250*0.5, 250, CELL_SIZE*250*0.5); -#else // std view - g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(20)); - g_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); - g_Camera.m_Orientation.RotateY(DEGTORAD(-45)); - g_Camera.m_Orientation.Translate(350, 350, -275); -#endif - g_Camera.UpdateFrustum(); - } -} - -#if MSC_VERSION -# pragma optimize("", on) // restore; see above. -#endif - - +CMusicPlayer music_player; static void Frame() { MICROLOG(L"Frame"); - oglCheck(); PROFILE_START( "update music" ); - MusicPlayer.update(); + music_player.update(); PROFILE_END( "update music" ); static double last_time; const double time = get_time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; ONCE(return); // first call: set last_time and return debug_assert(TimeSinceLastFrame >= 0.0f); PROFILE_START( "reload changed files" ); MICROLOG(L"reload files"); vfs_reload_changed_files(); PROFILE_END( "reload changed files" ); - oglCheck(); - PROFILE_START( "progressive load" ); ProgressiveLoad(); PROFILE_END( "progressive load" ); PROFILE_START( "input" ); MICROLOG(L"input"); - in_get_events(); + PumpEvents(); g_SessionManager.Poll(); PROFILE_END( "input" ); - oglCheck(); - PROFILE_START( "gui tick" ); #ifndef NO_GUI g_GUI.TickObjects(); #endif PROFILE_END( "gui tick" ); - - oglCheck(); PROFILE_START( "game logic" ); if (g_Game && g_Game->IsGameStarted()) { PROFILE_START( "simulation update" ); g_Game->Update(TimeSinceLastFrame); PROFILE_END( "simulation update" ); if (!g_FixedFrameTiming) { PROFILE( "camera update" ); g_Game->GetView()->Update(float(TimeSinceLastFrame)); } PROFILE_START( "selection and interaction ui" ); // TODO Where does GameView end and other things begin? g_Mouseover.update( TimeSinceLastFrame ); g_Selection.update(); PROFILE_END( "selection and interaction ui" ); PROFILE_START( "sound update" ); CCamera* camera = g_Game->GetView()->GetCamera(); CMatrix3D& orientation = camera->m_Orientation; float* pos = &orientation._data[12]; float* dir = &orientation._data[8]; float* up = &orientation._data[4]; if(snd_update(pos, dir, up) < 0) debug_printf("snd_update failed\n"); PROFILE_END( "sound update" ); } else { // CSimulation would do this with the proper turn length if we were in // a game. This is basically just to keep script timers running. uint ms_elapsed = (uint)(TimeSinceLastFrame*1000); g_Scheduler.update(ms_elapsed); if(snd_update(0, 0, 0) < 0) debug_printf("snd_update (pos=0 version) failed\n"); } PROFILE_END( "game logic" ); PROFILE_START( "update console" ); g_Console->Update(TimeSinceLastFrame); PROFILE_END( "update console" ); - // ugly, but necessary. these are one-shot events, have to be reset. + // TODO: ugly, but necessary. these are one-shot events, have to be reset. // Spoof mousebuttonup events for the hotkey system SDL_Event spoof; spoof.type = SDL_MOUSEBUTTONUP; spoof.button.button = SDL_BUTTON_WHEELUP; - if( mouseButtons[SDL_BUTTON_WHEELUP] ) + if( mouse_buttons[SDL_BUTTON_WHEELUP] ) hotkeyInputHandler( &spoof ); spoof.button.button = SDL_BUTTON_WHEELDOWN; - if( mouseButtons[SDL_BUTTON_WHEELDOWN] ) + if( mouse_buttons[SDL_BUTTON_WHEELDOWN] ) hotkeyInputHandler( &spoof ); - mouseButtons[SDL_BUTTON_WHEELUP] = false; - mouseButtons[SDL_BUTTON_WHEELDOWN] = false; - - oglCheck(); + mouse_buttons[SDL_BUTTON_WHEELUP] = false; + mouse_buttons[SDL_BUTTON_WHEELDOWN] = false; PROFILE_START( "render" ); if(g_active) { MICROLOG(L"render"); Render(); MICROLOG(L"finished render"); PROFILE_START( "swap buffers" ); SDL_GL_SwapBuffers(); PROFILE_END( "swap buffers" ); } // inactive; relinquish CPU for a little while // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored else SDL_Delay(10); PROFILE_END( "render" ); g_Profiler.Frame(); calc_fps(); - if (g_FixedFrameTiming && frameCount==100) quit=true; + if(g_FixedFrameTiming && frameCount==100) + kill_mainloop(); +} + + +static void MainControllerInit() +{ + // add additional input handlers only needed by this controller. + + // gui_handler needs to be after (i.e. called before!) the hotkey handler + // so that input boxes can be typed in without setting off hotkeys. +#ifndef NO_GUI + in_add_handler(gui_handler); +#endif + + // must be after gui_handler. Should mayhap even be last. + in_add_handler(MainInputHandler); } +static void MainControllerShutdown() +{ + music_player.release(); +} + #ifndef SCED + +static bool quit = false; // break out of main loop + +// HACK: Let code from other files (i.e. the scripting system) quit +void kill_mainloop() +{ + quit = true; +} + + int main(int argc, char* argv[]) { debug_printf("MAIN &argc=%p &argv=%p\n", &argc, &argv); - Init(argc, argv, true); + ATLAS_RunIfOnCmdLine(argc, argv); - // Optionally, do some simple tests to ensure things aren't broken - // extern void PerformTests(); - // PerformTests(); + // ELSE + Init(argc, argv, true); + MainControllerInit(); while(!quit) Frame(); Shutdown(); + MainControllerShutdown(); exit(0); } + // Public functions for Atlas to use: // TODO: Make this far less hacky void Init_(int argc, char** argv, bool setup_gfx) { g_Quickstart = true; Init(argc, argv, setup_gfx); } void Shutdown_() { Shutdown(); } void Render_() { Render(); } #else // SCED: void ScEd_Init() { g_Quickstart = true; Init(0, NULL, false); } void ScEd_Shutdown() { Shutdown(); } #endif // SCED Index: ps/trunk/source/lib/input.cpp =================================================================== --- ps/trunk/source/lib/input.cpp (revision 2621) +++ ps/trunk/source/lib/input.cpp (revision 2622) @@ -1,179 +1,176 @@ /* * input layer (dispatch events to multiple handlers; record/playback events) * * Copyright (c) 2002 Jan Wassenberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Contact info: * Jan.Wassenberg@stud.uni-karlsruhe.de * http://www.stud.uni-karlsruhe.de/~urkt/ */ #include "precompiled.h" #include "input.h" #include "sdl.h" #include "lib.h" #include #include #define MAX_HANDLERS 8 static EventHandler handler_stack[MAX_HANDLERS]; static int handler_stack_top = 0; int in_add_handler(EventHandler handler) { if(handler_stack_top >= MAX_HANDLERS || !handler) { debug_warn("in_add_handler"); return -1; } handler_stack[handler_stack_top++] = handler; return 0; } // send event to each handler (newest first) until one returns true -static void dispatch_event(const SDL_Event* event) +void dispatch_event(const SDL_Event* event) { for(int i = handler_stack_top-1; i >= 0; i--) { int ret = handler_stack[i](event); // .. done, return if(ret == EV_HANDLED) return; // .. next handler else if(ret == EV_PASS) continue; // .. invalid return value else debug_warn("dispatch_event: invalid handler return value"); } } static enum { INIT, // first call to in_record() or in_playback(): register cleanup routine IDLE, RECORD, PLAYBACK } state = INIT; static FILE* f; u32 game_ticks; static u32 time_adjust = 0; static u32 next_event_time; void in_stop() { if(f) { fclose(f); f = 0; } state = IDLE; } int in_record(const char* fn) { if(state == INIT) atexit(in_stop); in_stop(); f = fopen(fn, "wb"); if(!f) return -1; fwrite(&game_ticks, sizeof(u32), 1, f); state = RECORD; return 0; } int in_playback(const char* fn) { if(state == INIT) atexit(in_stop); in_stop(); f = fopen(fn, "rb"); if(!f) return -1; u32 rec_start_time; fread(&rec_start_time, sizeof(u32), 1, f); time_adjust = game_ticks-rec_start_time; fread(&next_event_time, sizeof(u32), 1, f); next_event_time += time_adjust; state = PLAYBACK; return 0; } -void in_get_events() + +void in_dispatch_event(const SDL_Event* event) +{ + if(state == RECORD) + { + fwrite(&game_ticks, sizeof(u32), 1, f); + fwrite(event, sizeof(SDL_Event), 1, f); + } + + dispatch_event(event); +} + + +void in_dispatch_recorded_events() { SDL_Event event; while(state == PLAYBACK && next_event_time <= game_ticks) { fread(&event, sizeof(SDL_Event), 1, f); // do this before dispatch_event(), // in case a handler calls in_stop() (setting f to 0) if(!fread(&next_event_time, sizeof(u32), 1, f)) { in_stop(); exit(0x73c07d); // TODO: 'disconnect'? } next_event_time += time_adjust; - dispatch_event(&event); - } - - // get new events - while(SDL_PollEvent(&event)) - { - if(state == RECORD) - { - fwrite(&game_ticks, sizeof(u32), 1, f); - fwrite(&event, sizeof(SDL_Event), 1, f); - } - - if(state == PLAYBACK) - if(event.type == SDL_KEYDOWN) - in_stop(); - - dispatch_event(&event); + in_dispatch_event(&event); } } Index: ps/trunk/source/lib/input.h =================================================================== --- ps/trunk/source/lib/input.h (revision 2621) +++ ps/trunk/source/lib/input.h (revision 2622) @@ -1,63 +1,66 @@ /* * input layer (dispatch events to multiple handlers; record/playback events) * * Copyright (c) 2002 Jan Wassenberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Contact info: * Jan.Wassenberg@stud.uni-karlsruhe.de * http://www.stud.uni-karlsruhe.de/~urkt/ */ #ifndef INPUT_H__ #define INPUT_H__ #include "sdl.h" #ifdef __cplusplus extern "C" { #endif // event handler return value defs (int). // don't require an enum type - simplifies user function decl; // the dispatcher makes sure each return value is correct. enum { // pass the event to the next handler in the chain EV_PASS = 4, // we've handled it; no other handlers will receive this event. EV_HANDLED = 2 }; // declare functions to take SDL_Event*; in_add_handler converts to void* // (avoids header dependency on SDL) typedef int (*EventHandler)(const SDL_Event*); // register an input handler, which will receive all subsequent events first. // events are passed to other handlers if handler returns EV_PASS. extern int in_add_handler(EventHandler handler); -extern void in_get_events(void); +// send event to each handler (newest first) until one returns true +extern void in_dispatch_event(const SDL_Event* event); + +extern void in_dispatch_recorded_events(); extern int in_record(const char* fn); extern int in_playback(const char* fn); extern void in_stop(void); #ifdef __cplusplus } #endif #endif // #ifndef INPUT_H__