From 194fdf9b4b45c652d8538059611d39a005a78823 Mon Sep 17 00:00:00 2001 From: MarkT Date: Tue, 20 Jul 2004 19:30:35 +0000 Subject: [PATCH] Unit selection and ordering, setTimeout, scheduler, fixed timestep code, various fixes and reinforcement of the JS<->Native code. This was SVN commit r783. --- source/graphics/Camera.cpp | 40 + source/graphics/Camera.h | 14 + source/graphics/Unit.h | 13 +- source/graphics/UnitManager.cpp | 23 +- source/lib/input.cpp | 2 +- source/main.cpp | 138 ++- source/maths/MathUtil.h | 8 +- .../maths/scripting/JSInterface_Vector3D.cpp | 18 +- source/ps/CConsole.cpp | 18 +- source/ps/CConsole.h | 2 - source/ps/Interact.cpp | 837 ++++++++++++++++++ source/ps/Interact.h | 121 +++ source/scripting/ScriptGlue.cpp | 182 ++++ source/scripting/ScriptGlue.h | 9 + source/scripting/ScriptingHost.cpp | 24 +- source/scripting/ScriptingHost.h | 12 +- source/simulation/BaseEntity.h | 2 +- source/simulation/BaseEntityCollection.h | 2 +- source/simulation/BoundingObjects.cpp | 2 +- source/simulation/BoundingObjects.h | 2 +- source/simulation/Collision.cpp | 6 +- source/simulation/Collision.h | 2 +- source/simulation/Entity.cpp | 248 +++++- source/simulation/Entity.h | 49 +- source/simulation/EntityHandles.cpp | 6 + source/simulation/EntityHandles.h | 3 +- source/simulation/EntityManager.cpp | 39 +- source/simulation/EntityManager.h | 12 +- source/simulation/EntityMessage.h | 15 +- source/simulation/EntityOrders.h | 7 +- source/simulation/EntityProperties.cpp | 80 +- source/simulation/EntityProperties.h | 52 +- source/simulation/EntityStateProcessing.cpp | 91 +- source/simulation/PathfindEngine.h | 2 +- source/simulation/PathfindSparse.cpp | 3 + source/simulation/PathfindSparse.h | 2 +- source/simulation/Scheduler.cpp | 108 +++ source/simulation/Scheduler.h | 85 ++ .../scripting/JSInterface_BaseEntity.cpp | 8 +- .../scripting/JSInterface_Entity.cpp | 20 +- source/terrain/terrainMain.cpp | 22 +- 41 files changed, 2087 insertions(+), 242 deletions(-) create mode 100755 source/ps/Interact.cpp create mode 100755 source/ps/Interact.h create mode 100755 source/simulation/Scheduler.cpp create mode 100755 source/simulation/Scheduler.h diff --git a/source/graphics/Camera.cpp b/source/graphics/Camera.cpp index c1d8950e5c..3bdef98161 100755 --- a/source/graphics/Camera.cpp +++ b/source/graphics/Camera.cpp @@ -13,6 +13,7 @@ #include "precompiled.h" #include "Camera.h" +#include "Renderer.h" CCamera::CCamera () { @@ -146,3 +147,42 @@ void CCamera::GetFrustumPoints(CVector3D pts[8]) const m_Orientation.Transform(cpts[i],pts[i]); } } + +void CCamera::BuildCameraRay( int px, int py, CVector3D& origin, CVector3D& dir ) +{ + CVector3D cPts[4]; + GetCameraPlanePoints( m_FarPlane, cPts ); + + // transform to world space + CVector3D wPts[4]; + for( int i = 0; i < 4; i++) + wPts[i] = m_Orientation.Transform( cPts[i] ); + + // get world space position of mouse point + float dx = (float)px/(float)g_Renderer.GetWidth(); + float dz=1-(float)py/(float)g_Renderer.GetHeight(); + + CVector3D vdx = wPts[1] - wPts[0]; + CVector3D vdz = wPts[3] - wPts[0]; + CVector3D pt = wPts[0] + ( vdx * dx ) + ( vdz * dz ); + + // copy origin + origin = m_Orientation.GetTranslation(); + // build direction + dir = pt - origin; + dir.Normalize(); +} + +void CCamera::GetScreenCoordinates( const CVector3D& world, float& x, float& y ) +{ + CMatrix3D transform; + m_Orientation.GetInverse( transform ); + transform.Concatenate( m_ProjMat ); + + CVector3D screenspace = transform.Transform( world ); + + x = screenspace.X / screenspace.Z; + y = screenspace.Y / screenspace.Z; + x = ( x + 1 ) * 0.5f * g_Renderer.GetWidth(); + y = ( 1 - y ) * 0.5f * g_Renderer.GetHeight(); +} \ No newline at end of file diff --git a/source/graphics/Camera.h b/source/graphics/Camera.h index 0ad8c7ace3..86bb560b87 100755 --- a/source/graphics/Camera.h +++ b/source/graphics/Camera.h @@ -16,6 +16,8 @@ #include "Frustum.h" #include "Matrix3D.h" +extern int mouse_x, mouse_y; + //view port struct SViewPort { @@ -57,6 +59,18 @@ class CCamera // return four points in camera space at given distance from camera void GetCameraPlanePoints(float dist,CVector3D pts[4]) const; + // Build a ray passing through the screen coordinate (px, py) and the camera + ///////////////////////////////////////////////////////////////////////////////////////// +// BuildCameraRay: calculate origin and ray direction of a ray through +// the pixel (px,py) on the screen + void BuildCameraRay( int px, int py, CVector3D& origin, CVector3D& dir ); +// BuildCameraRay: as previous, using global mouse position + void BuildCameraRay( CVector3D& origin, CVector3D& dir ) + { + BuildCameraRay( mouse_x, mouse_y, origin, dir ); + } + + void GetScreenCoordinates( const CVector3D& world, float& x, float& y ); public: //This is the orientation matrix. The inverse of this //is the view matrix diff --git a/source/graphics/Unit.h b/source/graphics/Unit.h index 6c58271a73..22ea8499f0 100755 --- a/source/graphics/Unit.h +++ b/source/graphics/Unit.h @@ -5,16 +5,21 @@ #include "Model.h" class CObjectEntry; +class CEntity; ///////////////////////////////////////////////////////////////////////////////////////////// // CUnit: simple "actor" definition - defines a sole object within the world class CUnit { public: - // sole constructor - unit invalid without a model and object - CUnit(CObjectEntry* object,CModel* model) : m_Object(object), m_Model(model) { + // constructor - unit invalid without a model and object + CUnit(CObjectEntry* object,CModel* model) : m_Object(object), m_Model(model), m_Entity( NULL ) { assert(object && model); } + CUnit(CObjectEntry* object,CModel* model, CEntity* entity) : m_Object(object), m_Model(model), m_Entity( entity ) { + assert(object && model); + } + // destructor ~CUnit() { delete m_Model; @@ -25,12 +30,16 @@ public: CObjectEntry* GetObject() { return m_Object; } // get unit's model data CModel* GetModel() { return m_Model; } + // get actor's entity + CEntity* GetEntity() { return m_Entity; } private: // object from which unit was created CObjectEntry* m_Object; // object model representation CModel* m_Model; + // the entity that this actor represents, if any + CEntity* m_Entity; }; #endif diff --git a/source/graphics/UnitManager.cpp b/source/graphics/UnitManager.cpp index e0bf2849d3..fff6740203 100755 --- a/source/graphics/UnitManager.cpp +++ b/source/graphics/UnitManager.cpp @@ -62,16 +62,31 @@ void CUnitManager::DeleteAll() CUnit* CUnitManager::PickUnit(const CVector3D& origin,const CVector3D& dir) const { // closest object found so far - CUnit* hit=0; + CUnit* hit = 0; // distance to closest object found so far - float dist=1.0e30f; + float dist = 1.0e30f; + // closest approach offset (easier to pick small stuff in forests than standard ScEd style selection) + float minrel = 1.0e30f; + for (uint i=0;iGetModel()->GetBounds().RayIntersect(origin,dir,tmin,tmax)) { - if (!hit || tminGetModel()->GetBounds().RayIntersect(origin,dir,tmin,tmax)) + { + // Point of closest approach + CVector3D obj; + unit->GetModel()->GetBounds().GetCentre( obj ); + CVector3D delta = obj - origin; + float distance = delta.Dot( dir ); + CVector3D closest = origin + dir * distance; + CVector3D offset = obj - closest; + + float rel = offset.GetLength(); + if (!hit || rel < minrel ) { hit=unit; dist=tmin; + minrel = rel; } } } diff --git a/source/lib/input.cpp b/source/lib/input.cpp index 53033abe86..cd423c9dba 100755 --- a/source/lib/input.cpp +++ b/source/lib/input.cpp @@ -28,7 +28,7 @@ #include -#define MAX_HANDLERS 4 +#define MAX_HANDLERS 8 static EventHandler handler_stack[MAX_HANDLERS]; static int handler_stack_top = 0; diff --git a/source/main.cpp b/source/main.cpp index 94dfb47be7..cf3b39dfb5 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -30,12 +30,13 @@ #include "Model.h" #include "UnitManager.h" - +#include "Interact.h" #include "BaseEntityCollection.h" #include "Entity.h" #include "EntityHandles.h" #include "EntityManager.h" #include "PathfindEngine.h" +#include "Scheduler.h" #include "scripting/ScriptingHost.h" #include "scripting/JSInterface_Entity.h" @@ -57,19 +58,19 @@ CConsole* g_Console = 0; extern int conInputHandler(const SDL_Event* ev); - +// Globals u32 game_ticks; bool keys[SDLK_LAST]; bool mouseButtons[5]; +int mouse_x=50, mouse_y=50; -// Globals int g_xres, g_yres; int g_bpp; int g_freq; bool g_active = true; - +const int SIM_FRAMERATE = 10; // flag to disable extended GL extensions until fix found - specifically, crashes // using VBOs on laptop Radeon cards @@ -111,7 +112,8 @@ extern void sle(int); -static size_t frameCount=0; +size_t frameCount=0; +size_t simulationTime = 0; static bool quit = false; // break out of main loop @@ -317,6 +319,11 @@ static int handler(const SDL_Event* ev) g_active = ev->active.gain != 0; break; + case SDL_MOUSEMOTION: + mouse_x = ev->motion.x; + mouse_y = ev->motion.y; + break; + case SDL_KEYDOWN: c = ev->key.keysym.sym; keys[c] = true; @@ -447,20 +454,24 @@ static void Render() MICROLOG(L"flush frame"); g_Renderer.FlushFrame(); + glPushAttrib( GL_ENABLE_BIT ); + glDisable( GL_LIGHTING ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_DEPTH_TEST ); + if( g_EntGraph ) { - glPushAttrib( GL_ENABLE_BIT ); - glDisable( GL_LIGHTING ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_DEPTH_TEST ); glColor3f( 1.0f, 0.0f, 1.0f ); MICROLOG(L"render entities"); g_EntityManager.renderAll(); // <-- collision outlines, pathing routes - - glPopAttrib(); } + g_Mouseover.renderSelectionOutlines(); + g_Selection.renderSelectionOutlines(); + + glPopAttrib(); + MICROLOG(L"render fonts"); // overlay mode glPushAttrib(GL_ENABLE_BIT); @@ -509,6 +520,9 @@ static void Render() MICROLOG(L"render console"); g_Console->Render(); + g_Mouseover.renderOverlays(); + g_Selection.renderOverlays(); + // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); @@ -526,17 +540,27 @@ static void do_tick() } ////////////////////////////////////////////////////////////////////////////////////////////// -// UpdateWorld: update time dependent data in the world to account for changes over -// the given time (in s) +// UpdateWorld: update time dependent data in the simulation to account for changes over +// some fixed simulation timestep. + void UpdateWorld(float time) { - const std::vector& units=g_UnitMan.GetUnits(); - for (uint i=0;iGetModel()->Update(time); - } - + g_Scheduler.update(); + simulationTime += (size_t)( time * 1000.0f ); + frameCount++; g_EntityManager.updateAll( time ); +} +////////////////////////////////////////////////////////////////////////////////////////////// +// InterpolateWorld: interpolate a data point for rendering between simulation frames. + +void InterpolateWorld( float delta, float offset ) +{ + const std::vector& units=g_UnitMan.GetUnits(); + for (uint i=0;iGetModel()->Update( delta ); + + g_EntityManager.interpolateAll( offset ); } @@ -640,6 +664,9 @@ static void 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 JSI_Entity::init(); JSI_BaseEntity::init(); @@ -712,6 +739,11 @@ static void Shutdown() { psShutdown(); // Must delete g_GUI before g_ScriptingHost + delete &g_Scheduler; + + delete &g_Mouseover; + delete &g_Selection; + delete &g_ScriptingHost; delete &g_Pathfinder; delete &g_EntityManager; @@ -888,6 +920,8 @@ PREVTSC=CURTSC; new CBaseEntityCollection; new CEntityManager; new CPathfindEngine; + new CSelectedEntities; + new CMouseoverEntities; g_EntityTemplateCollection.loadTemplates(); @@ -917,13 +951,17 @@ if(!g_MapFile) CMessage init_msg (CMessage::EMSG_INIT); g_EntityManager.dispatchAll(&init_msg); -#ifndef NO_GUI - in_add_handler(gui_handler); -#endif in_add_handler(handler); in_add_handler(terr_handler); + in_add_handler(interactInputHandler); + in_add_handler(conInputHandler); +#ifndef NO_GUI + in_add_handler(gui_handler); +#endif + + MICROLOG(L"render blank"); // render everything to a blank frame to force renderer to load everything @@ -1021,6 +1059,8 @@ static void Frame() // Non-movie code: static double last_time; + static double delta_time = 0.0; // The amount by which the displayed frame is lagging + // the simulation. const double time = get_time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; @@ -1036,37 +1076,43 @@ static void Frame() // TODO: limiter in case simulation can't keep up? const double TICK_TIME = 30e-3; // [s] -#if 0 - double time1 = get_time(); - while((time1-time0) > TICK_TIME) - { - game_ticks++; - - in_get_events(); - do_tick(); - time0 += TICK_TIME; - } - -#endif + const float SIM_UPDATE_INTERVAL = 1.0f / (float)SIM_FRAMERATE; // Simulation runs at 50 fps. MICROLOG(L"input"); in_get_events(); - if(TimeSinceLastFrame > 0.0f) + delta_time += TimeSinceLastFrame; + + if( delta_time >= 0.0 ) { - MICROLOG(L"update world"); - - UpdateWorld(TimeSinceLastFrame); - if (!g_FixedFrameTiming) - terr_update(float(TimeSinceLastFrame)); - g_Console->Update(TimeSinceLastFrame); - - // ugly, but necessary. these are one-shot events, have to be reset. - mouseButtons[SDL_BUTTON_WHEELUP] = false; - mouseButtons[SDL_BUTTON_WHEELDOWN] = false; + // A new simulation frame is required. + MICROLOG( L"calculate simulation" ); + UpdateWorld( SIM_UPDATE_INTERVAL ); + delta_time -= SIM_UPDATE_INTERVAL; + if( delta_time >= 0.0 ) + { + // The desired sim frame rate can't be achieved. Settle for process & render + // frames as fast as possible. + // g_Console->InsertMessage( L"Can't maintain %d FPS simulation rate!", SIM_FRAMERATE ); + delta_time = 0.0; + } } + + MICROLOG(L"interpolate frame"); + + InterpolateWorld( TimeSinceLastFrame, (float)( delta_time / (double)SIM_UPDATE_INTERVAL ) + 1.0f ); + + if (!g_FixedFrameTiming) + terr_update(float(TimeSinceLastFrame)); + g_Mouseover.update( TimeSinceLastFrame ); + g_Console->Update(TimeSinceLastFrame); + + // ugly, but necessary. these are one-shot events, have to be reset. + mouseButtons[SDL_BUTTON_WHEELUP] = false; + mouseButtons[SDL_BUTTON_WHEELDOWN] = false; + if(g_active) { MICROLOG(L"render"); @@ -1081,13 +1127,13 @@ static void Frame() SDL_Delay(10); calc_fps(); - frameCount++; if (g_FixedFrameTiming && frameCount==100) quit=true; } #ifdef _WIN32 // Define/undefine this as desired: + #ifndef NDEBUG # define CUSTOM_EXCEPTION_HANDLER #endif @@ -1110,7 +1156,7 @@ int main(int argc, char* argv[]) while(!quit) { - MICROLOG(L"Frame"); + MICROLOG(L"(Simulation) Frame"); Frame(); } diff --git a/source/maths/MathUtil.h b/source/maths/MathUtil.h index 83c41f0677..9c93f8925d 100755 --- a/source/maths/MathUtil.h +++ b/source/maths/MathUtil.h @@ -11,6 +11,12 @@ #define MAX3(a,b,c) ( MAX (MAX(a,b), c) ) #define ABS(a) ((a > 0) ? (a) : (-a)) +template +T Interpolate( T& a, T& b, float l ) +{ + return( a + ( b - a ) * l ); +} + #if 0 /* @@ -185,8 +191,6 @@ namespace MathUtil return 0; } - - //-------------------------------------------------------- // Non-template functions //-------------------------------------------------------- diff --git a/source/maths/scripting/JSInterface_Vector3D.cpp b/source/maths/scripting/JSInterface_Vector3D.cpp index 49ef025617..265316972b 100755 --- a/source/maths/scripting/JSInterface_Vector3D.cpp +++ b/source/maths/scripting/JSInterface_Vector3D.cpp @@ -67,7 +67,11 @@ JSBool JSI_Vector3D::getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* return( JS_TRUE ); Vector3D_Info* vectorInfo = (Vector3D_Info*)JS_GetPrivate( cx, obj ); - if( !vectorInfo ) return( JS_TRUE ); + if( !vectorInfo ) + { + JS_ReportError( cx, "[Vector3D] Invalid reference" ); + return( JS_TRUE ); + } CVector3D* vectorData = vectorInfo->vector; switch( g_ScriptingHost.ValueToInt( id ) ) @@ -86,7 +90,11 @@ JSBool JSI_Vector3D::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* return( JS_TRUE ); Vector3D_Info* vectorInfo = (Vector3D_Info*)JS_GetPrivate( cx, obj ); - if( !vectorInfo ) return( JS_TRUE ); + if( !vectorInfo ) + { + JS_ReportError( cx, "[Vector3D] Invalid reference" ); + return( JS_TRUE ); + } CVector3D* vectorData = vectorInfo->vector; try @@ -100,8 +108,8 @@ JSBool JSI_Vector3D::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* } catch (PSERROR_Scripting_ConversionFailed) { - JS_ReportError(cx, "Invalid parameter value for Vector3D"); - return( JS_FALSE ); + JS_ReportError( cx, "Invalid parameter value for Vector3D" ); + return( JS_TRUE ); } if( vectorInfo->owner && vectorInfo->updateFn ) ( (vectorInfo->owner)->*(vectorInfo->updateFn) )(); @@ -130,7 +138,7 @@ JSBool JSI_Vector3D::construct( JSContext* cx, JSObject* obj, uintN argc, jsval* { // Invalid input (i.e. can't be coerced into doubles) - fail JS_ReportError( cx, "Invalid parameters to Vector3D constructor" ); - return( JS_FALSE ); + return( JS_TRUE ); } } else diff --git a/source/ps/CConsole.cpp b/source/ps/CConsole.cpp index 1f0d10012d..26ad0688a3 100755 --- a/source/ps/CConsole.cpp +++ b/source/ps/CConsole.cpp @@ -477,30 +477,27 @@ void CConsole::ProcessBuffer(const wchar_t* szLine){ // Process it as JavaScript // Convert Unicode to 8-bit sort-of-ASCII - size_t len = wcstombs(NULL, szLine, 0); - assert(len != (size_t)-1 && "Cannot convert unicode->multibyte"); - char* szMBLine = new char[len+1]; - wcstombs(szMBLine, szLine, len+1); + + g_ScriptingHost.ExecuteScript( CStr16( szLine + 1 ) ); - g_ScriptingHost.ExecuteScript( szMBLine + 1 ); - - delete[] szMBLine; } else if( szLine[0] == '?' ) { // Process it as JavaScript and display the result + /* // Convert Unicode to 8-bit sort-of-ASCII size_t len = wcstombs(NULL, szLine, 0); assert(len != (size_t)-1 && "Cannot convert unicode->multibyte"); char* szMBLine = new char[len+1]; wcstombs(szMBLine, szLine, len+1); + */ - jsval rval = g_ScriptingHost.ExecuteScript( szMBLine + 1 ); + jsval rval = g_ScriptingHost.ExecuteScript( CStr16( szLine + 1 ) ); if( rval ) InsertMessage( L"%hs", g_ScriptingHost.ValueToString( rval ).c_str() ); - delete[] szMBLine; + // delete[] szMBLine; } else InsertMessage(L": %ls", szLine); @@ -529,6 +526,9 @@ int conInputHandler(const SDL_Event* ev) // bail, because we'd fall under if(sym) .. else below } + if( sym == SDLK_ESCAPE ) + return EV_PASS; // Not being able to quit while the console is up is annoying. + if(!g_Console->IsActive()) return EV_PASS; diff --git a/source/ps/CConsole.h b/source/ps/CConsole.h index 427a4042a8..2ea56d4731 100755 --- a/source/ps/CConsole.h +++ b/source/ps/CConsole.h @@ -99,7 +99,5 @@ public: int m_iFontOffset; // distance to move up before drawing }; -// TODO MT: Better solution to character translation than 'const int cooked'? Anyone? - #endif diff --git a/source/ps/Interact.cpp b/source/ps/Interact.cpp new file mode 100755 index 0000000000..a1f597d97b --- /dev/null +++ b/source/ps/Interact.cpp @@ -0,0 +1,837 @@ +#include "precompiled.h" +#include "Interact.h" +#include "Renderer.h" +#include "input.h" +#include "CConsole.h" +#include "HFTracer.h" + +extern CCamera g_Camera; +extern CConsole* g_Console; +extern int mouse_x, mouse_y; +extern bool keys[SDLK_LAST]; + +static const float SELECT_DBLCLICK_RATE = 0.5f; +static const int ORDER_DELAY = 5; + + +#include "timer.h" + +void CSelectedEntities::addSelection( CEntity* entity ) +{ + m_group = 255; + assert( !isSelected( entity ) ); + m_selected.push_back( entity ); + entity->m_selected = true; +} + +void CSelectedEntities::removeSelection( CEntity* entity ) +{ + m_group = 255; + assert( isSelected( entity ) ); + entity->m_selected = false; + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + if( (*it) == entity ) + { + m_selected.erase( it ); + break; + } + } +} + +void CSelectedEntities::renderSelectionOutlines() +{ + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + (*it)->renderSelectionOutline(); + + if( m_group_highlight != 255 ) + { + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_BLEND ); + + std::vector::iterator it; + for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) + (*it)->renderSelectionOutline( 0.5f ); + + glDisable( GL_BLEND ); + } +} + +void CSelectedEntities::renderOverlays() +{ + glPushMatrix(); + glEnable( GL_TEXTURE_2D ); + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + if( (*it)->m_grouped != 255 ) + { + assert( (*it)->m_bounds ); + + glLoadIdentity(); + float x, y; + CVector3D labelpos = (*it)->m_graphics_position - g_Camera.m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; + labelpos.X += (*it)->m_bounds->m_offset.x; + labelpos.Z += (*it)->m_bounds->m_offset.y; +#ifdef SELECTION_TERRAIN_CONFORMANCE + labelpos.Y = (*it)->getExactGroundLevel( labelpos.X, labelpos.Z ); +#endif + g_Camera.GetScreenCoordinates( labelpos, x, y ); + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); + glScalef( 1.0f, -1.0f, 1.0f ); + glwprintf( L"%d", (*it)->m_grouped ); + + } + } + if( m_group_highlight != 255 ) + { + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_BLEND ); + + std::vector::iterator it; + for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) + { + assert( (*it)->m_bounds ); + + glLoadIdentity(); + float x, y; + CVector3D labelpos = (*it)->m_graphics_position - g_Camera.m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; + labelpos.X += (*it)->m_bounds->m_offset.x; + labelpos.Z += (*it)->m_bounds->m_offset.y; +#ifdef SELECTION_TERRAIN_CONFORMANCE + labelpos.Y = (*it)->getExactGroundLevel( labelpos.X, labelpos.Z ); +#endif + g_Camera.GetScreenCoordinates( labelpos, x, y ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); + glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); + glScalef( 1.0f, -1.0f, 1.0f ); + glwprintf( L"%d", (*it)->m_grouped ); + } + + glDisable( GL_BLEND ); + } + + glLoadIdentity(); + glTranslatef( (float)( mouse_x + 16 ), (float)( g_Renderer.GetHeight() - mouse_y - 8 ), 0.0f ); + glScalef( 1.0f, -1.0f, 1.0f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); + switch( m_contextOrder ) + { + case CEntityOrder::ORDER_GOTO: + glwprintf( L"Go to" ); + break; + case CEntityOrder::ORDER_PATROL: + glwprintf( L"Patrol to" ); + break; + } + + glDisable( GL_TEXTURE_2D ); + glPopMatrix(); +} + +void CSelectedEntities::setSelection( CEntity* entity ) +{ + m_group = 255; + clearSelection(); + m_selected.push_back( entity ); +} + +void CSelectedEntities::clearSelection() +{ + m_group = 255; + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + (*it)->m_selected = false; + m_selected.clear(); +} + +void CSelectedEntities::removeAll( CEntity* entity ) +{ + // Remove a reference to an entity from everywhere + // (for use when said entity is being destroyed) + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + if( (*it) == entity ) + { + m_selected.erase( it ); + break; + } + } + for( u8 group = 0; group < 10; group++ ) + { + for( it = m_groups[group].begin(); it < m_groups[group].end(); it++ ) + { + if( (*it) == entity ) + { + m_groups[group].erase( it ); + break; + } + } + } +} + +CVector3D CSelectedEntities::getSelectionPosition() +{ + CVector3D avg; + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + avg += (*it)->m_graphics_position; + avg.X += (*it)->m_bounds->m_offset.x; + avg.Z += (*it)->m_bounds->m_offset.y; + } + return( avg * ( 1.0f / m_selected.size() ) ); +} + +void CSelectedEntities::saveGroup( u8 groupid ) +{ + std::vector::iterator it; + // Clear all entities in the group... + for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) + { + (*it)->m_grouped = 255; + (*it)->m_grouped_mirror = 0; + } + m_groups[groupid].clear(); + // Remove selected entities from each group they're in, and flag them as + // members of the new group + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + if( (*it)->m_grouped < 255 ) + { + std::vector& group = m_groups[(*it)->m_grouped]; + std::vector::iterator it2; + for( it2 = group.begin(); it2 < group.end(); it2++ ) + { + if( (*it2) == &(**it) ) + { + group.erase( it2 ); + break; + } + } + } + (*it)->m_grouped = groupid; + (*it)->m_grouped_mirror = ( groupid != 0 ) ? groupid : 10; + } + // Copy the group across + m_groups[groupid] = m_selected; + // Set the group selection memory + m_group = groupid; +} + +void CSelectedEntities::loadGroup( u8 groupid ) +{ + clearSelection(); + m_selected = m_groups[groupid]; + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + (*it)->m_selected = true; + m_group = groupid; +} + +void CSelectedEntities::addGroup( u8 groupid ) +{ + std::vector::iterator it; + for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) + { + if( !isSelected( *it ) ) + addSelection( *it ); + } + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + (*it)->m_selected = true; +} + +void CSelectedEntities::changeGroup( CEntity* entity, u8 groupid ) +{ + // Remove from current group + u8 current = entity->m_grouped; + if( current != 255 ) + { + std::vector::iterator it; + for( it = m_groups[current].begin(); it < m_groups[current].end(); it++ ) + { + if( (*it) == entity ) + { + m_groups[current].erase( it ); + break; + } + } + } + if( groupid != 255 ) + m_groups[groupid].push_back( entity ); + entity->m_grouped = groupid; +} + +bool CSelectedEntities::isSelected( CEntity* entity ) +{ + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + { + if( (*it) == entity ) + return( true ); + } + return( false ); +} + +void CSelectedEntities::highlightGroup( u8 groupid ) +{ + if( m_group_highlight != 255 ) + return; + if( !getGroupCount( groupid ) ) + return; + m_group_highlight = groupid; + pushCameraTarget( getGroupPosition( groupid ) ); +} + +void CSelectedEntities::highlightNone() +{ + if( m_group_highlight != 255 ) + popCameraTarget(); + m_group_highlight = 255; +} + +int CSelectedEntities::getGroupCount( u8 groupid ) +{ + return( (int)m_groups[groupid].size() ); +} + +CVector3D CSelectedEntities::getGroupPosition( u8 groupid ) +{ + CVector3D avg; + std::vector::iterator it; + for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) + { + avg += (*it)->m_graphics_position; + avg.X += (*it)->m_bounds->m_offset.x; + avg.Z += (*it)->m_bounds->m_offset.y; + } + return( avg * ( 1.0f / m_groups[groupid].size() ) ); +} + +void CSelectedEntities::updateContextSet() +{ + if( !isContextValid( m_contextOrder ) ) + { + // This order isn't valid for the current selection and/or target. + for( int t = 0; t < CEntityOrder::ORDER_LAST; t++ ) + if( isContextValid( t ) ) + { + m_contextOrder = t; return; + } + m_contextOrder = -1; + } +} + +bool CSelectedEntities::nextContext() +{ + // No valid orders? + if( m_contextOrder == -1 ) return( false ); + int t = m_contextOrder + 1; + while( t != m_contextOrder ) + { + if( t == CEntityOrder::ORDER_LAST ) t = 0; + if( isContextValid( t ) ) break; + t++; + } + if( m_contextOrder == t ) + return( false ); + m_contextOrder = t; + return( true ); + + +} + +bool CSelectedEntities::previousContext() +{ + // No valid orders? + if( m_contextOrder == -1 ) return( false ); + int t = m_contextOrder - 1; + while( t != m_contextOrder ) + { + if( isContextValid( t ) ) break; + if( t == 0 ) t = CEntityOrder::ORDER_LAST; + t--; + } + if( m_contextOrder == t ) + return( false ); + m_contextOrder = t; + return( true ); +} + +bool CSelectedEntities::isContextValid( int contextOrder ) +{ + if( contextOrder == -1 ) return( false ); + + // Check to see if any member of the selection supports this order type. + std::vector::iterator it; + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + if( (*it)->acceptsOrder( contextOrder, g_Mouseover.m_target ) ) + return( true ); + return( false ); +} + +void CSelectedEntities::contextOrder( bool pushQueue ) +{ + std::vector::iterator it; + CEntityOrder context; + (int&)context.m_type = m_contextOrder; + CVector3D origin, dir; + g_Camera.BuildCameraRay( origin, dir ); + + switch( m_contextOrder ) + { + case CEntityOrder::ORDER_GOTO: + case CEntityOrder::ORDER_PATROL: + { + CHFTracer maptracer( g_Terrain.GetHeightMap(), g_Terrain.GetVerticesPerSide(), (float)CELL_SIZE, HEIGHT_SCALE ); + int x, y; CVector3D ipt; + maptracer.RayIntersect( origin, dir, x, y, ipt ); + context.m_data[0].location.x = ipt.X; + context.m_data[0].location.y = ipt.Z; + } + break; + + default: + break; + } + + for( it = m_selected.begin(); it < m_selected.end(); it++ ) + if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) ) + g_Scheduler.pushFrame( ORDER_DELAY, (*it)->me, new CMessageOrder( context, pushQueue ) ); + +} + +void CMouseoverEntities::update( float timestep ) +{ + CVector3D origin, dir; + g_Camera.BuildCameraRay( origin, dir ); + + CUnit* hit = g_UnitMan.PickUnit( origin, dir ); + + m_target = NULL; + if( hit ) + m_target = hit->GetEntity(); + if( m_viewall ) + { + // 'O' key. Show selection outlines for all player units on screen + // (as if bandboxing them all). + // These aren't selectable; clicking when 'O' is pressed won't select + // all units on screen. + + m_mouseover.clear(); + + std::vector* onscreen = g_EntityManager.matches( isOnScreen ); + std::vector::iterator it; + + for( it = onscreen->begin(); it < onscreen->end(); it++ ) + m_mouseover.push_back( SMouseoverFader( &(**it), m_fademaximum, false ) ); + + delete( onscreen ); + } + else if( m_bandbox ) + { + m_x2 = mouse_x; + m_y2 = mouse_y; + // Here's the fun bit: + // Get the screen-space coordinates of all onscreen entities + // then find the ones falling within the box. + // + // Fade in the ones in the box at (in+out) speed, then fade everything + // out at (out) speed. + std::vector* onscreen = g_EntityManager.matches( isOnScreen ); + std::vector::iterator it; + + // Reset active flags on everything... + + std::vector::iterator it2; + for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) + it2->isActive = false; + + for( it = onscreen->begin(); it < onscreen->end(); it++ ) + { + CVector3D worldspace = (*it)->m_graphics_position; + worldspace.X += (*it)->m_bounds->m_offset.x; + worldspace.Z += (*it)->m_bounds->m_offset.y; + + float x, y; + + g_Camera.GetScreenCoordinates( worldspace, x, y ); + + bool inBox; + if( m_x1 < m_x2 ) + { + inBox = ( x >= m_x1 ) && ( x < m_x2 ); + } + else + { + inBox = ( x >= m_x2 ) && ( x < m_x1 ); + } + + if( m_y1 < m_y2 ) + { + inBox &= ( y >= m_y1 ) && ( y < m_y2 ); + } + else + { + inBox &= ( y >= m_y2 ) && ( y < m_y1 ); + } + + if( inBox ) + { + bool found = false; + for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) + if( it2->entity == &(**it) ) + { + found = true; + it2->fade += ( m_fadeinrate + m_fadeoutrate ) * timestep; + it2->isActive = true; + } + if( !found ) + m_mouseover.push_back( SMouseoverFader( &(**it), ( m_fadeinrate + m_fadeoutrate ) * timestep ) ); + } + } + delete( onscreen ); + + for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); ) + { + it2->fade -= m_fadeoutrate * timestep; + if( it2->fade > m_fademaximum ) it2->fade = m_fademaximum; + if( it2->fade < 0.0f ) + { + it2 = m_mouseover.erase( it2 ); + } + else + it2++; + } + } + else + { + std::vector::iterator it; + bool found = false; + + for( it = m_mouseover.begin(); it < m_mouseover.end(); ) + { + if( it->entity == m_target ) + { + found = true; + it->fade += m_fadeinrate * timestep; + if( it->fade > m_fademaximum ) it->fade = m_fademaximum; + it->isActive = true; + it++; continue; + } + else + { + it->fade -= m_fadeoutrate * timestep; + if( it->fade <= 0.0f ) + { + it = m_mouseover.erase( it ); continue; + } + it++; continue; + } + } + if( !found && m_target ) + { + float initial = m_fadeinrate * timestep; + if( initial > m_fademaximum ) initial = m_fademaximum; + m_mouseover.push_back( SMouseoverFader( m_target, initial ) ); + } + } + g_Selection.updateContextSet(); +} + +void CMouseoverEntities::addSelection() +{ + std::vector::iterator it; + for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) + if( it->isActive && !g_Selection.isSelected( it->entity ) ) + g_Selection.addSelection( it->entity ); +} + +void CMouseoverEntities::removeSelection() +{ + std::vector::iterator it; + for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) + if( it->isActive && g_Selection.isSelected( it->entity ) ) + g_Selection.removeSelection( it->entity ); +} + +void CMouseoverEntities::setSelection() +{ + g_Selection.clearSelection(); + addSelection(); +} + +void CMouseoverEntities::expandAcrossScreen() +{ + std::vector* activeset = g_EntityManager.matches( isMouseoverType, isOnScreen ); + m_mouseover.clear(); + std::vector::iterator it; + for( it = activeset->begin(); it < activeset->end(); it++ ) + { + m_mouseover.push_back( SMouseoverFader( &(**it) ) ); + } + delete( activeset ); +} + +void CMouseoverEntities::expandAcrossWorld() +{ + std::vector* activeset = g_EntityManager.matches( isMouseoverType ); + m_mouseover.clear(); + std::vector::iterator it; + for( it = activeset->begin(); it < activeset->end(); it++ ) + { + m_mouseover.push_back( SMouseoverFader( &(**it) ) ); + } + delete( activeset ); +} + +void CMouseoverEntities::renderSelectionOutlines() +{ + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_BLEND ); + + std::vector::iterator it; + for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) + it->entity->renderSelectionOutline( it->fade ); + + glDisable( GL_BLEND ); +} + +void CMouseoverEntities::renderOverlays() +{ + glLoadIdentity(); + glDisable( GL_TEXTURE_2D ); + if( m_bandbox ) + { + //glPushMatrix(); + glColor3f( 1.0f, 1.0f, 1.0f ); + glBegin( GL_LINE_LOOP ); + glVertex2i( m_x1, g_Renderer.GetHeight() - m_y1 ); + glVertex2i( m_x2, g_Renderer.GetHeight() - m_y1 ); + glVertex2i( m_x2, g_Renderer.GetHeight() - m_y2 ); + glVertex2i( m_x1, g_Renderer.GetHeight() - m_y2 ); + glEnd(); + //glPopMatrix(); + } + glEnable( GL_TEXTURE_2D ); + + std::vector::iterator it; + for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) + { + if( it->entity->m_grouped != 255 ) + { + assert( it->entity->m_bounds ); + glPushMatrix(); + glEnable( GL_TEXTURE_2D ); + glLoadIdentity(); + float x, y; + CVector3D labelpos = it->entity->m_graphics_position - g_Camera.m_Orientation.GetLeft() * it->entity->m_bounds->m_radius; + labelpos.X += it->entity->m_bounds->m_offset.x; + labelpos.Z += it->entity->m_bounds->m_offset.y; +#ifdef SELECTION_TERRAIN_CONFORMANCE + labelpos.Y = it->entity->getExactGroundLevel( labelpos.X, labelpos.Z ); +#endif + g_Camera.GetScreenCoordinates( labelpos, x, y ); + glColor4f( 1.0f, 1.0f, 1.0f, it->fade ); + glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); + glScalef( 1.0f, -1.0f, 1.0f ); + glwprintf( L"%d", it->entity->m_grouped ); + glDisable( GL_TEXTURE_2D ); + glPopMatrix(); + } + } +} + +void CMouseoverEntities::startBandbox( u16 x, u16 y ) +{ + m_bandbox = true; + m_x1 = x; m_y1 = y; +} + +void CMouseoverEntities::stopBandbox() +{ + m_bandbox = false; +} + +int interactInputHandler( const SDL_Event* ev ) +{ + + static float lastclicktime = 0.0f; + static CEntity* lastclickobject = NULL; + static u8 clicks = 0; + + static u16 button_down_x, button_down_y; + static bool button_down = false; + switch( ev->type ) + { + case SDL_KEYDOWN: + if( ( ev->key.keysym.sym >= SDLK_0 ) && ( ev->key.keysym.sym <= SDLK_9 ) ) + { + u8 groupid = ev->key.keysym.sym - SDLK_0; + if( keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT] ) + { + g_Selection.addGroup( groupid ); + } + else if( keys[SDLK_LCTRL] || keys[SDLK_RCTRL] ) + { + g_Selection.saveGroup( groupid ); + } + else if( keys[SDLK_LALT] || keys[SDLK_RALT] ) + { + g_Selection.highlightGroup( groupid ); + } + else + { + if( ( g_Selection.m_group == groupid ) && g_Selection.getGroupCount( groupid ) ) + { + setCameraTarget( g_Selection.getGroupPosition( groupid ) ); + } + else + g_Selection.loadGroup( groupid ); + } + } + else + { + switch( ev->key.keysym.sym ) + { + case SDLK_o: + g_Mouseover.m_viewall = true; + break; + case SDLK_HOME: + if( g_Selection.m_selected.size() ) + setCameraTarget( g_Selection.getSelectionPosition() ); + } + } + break; + case SDL_KEYUP: + switch( ev->key.keysym.sym ) + { + case SDLK_LALT: + case SDLK_RALT: + if( g_Selection.m_group_highlight != 255 ) + g_Selection.highlightNone(); + break; + case SDLK_o: + g_Mouseover.m_viewall = false; + break; + } + break; + case SDL_MOUSEBUTTONUP: + switch( ev->button.button ) + { + case SDL_BUTTON_LEFT: + if( g_Mouseover.m_viewall ) + break; + float time; + time = (float)get_time(); + // Reset clicks counter if too slow or if the cursor's + // hovering over something else now. + + if( time - lastclicktime >= SELECT_DBLCLICK_RATE ) + clicks = 0; + if( g_Mouseover.m_target != lastclickobject ) + clicks = 0; + clicks++; + + if( clicks == 2 ) + { + // Double click + g_Mouseover.expandAcrossScreen(); + } + else if( clicks == 3 ) + { + // Triple click + g_Mouseover.expandAcrossWorld(); + } + lastclicktime = time; + lastclickobject = g_Mouseover.m_target; + + button_down = false; + g_Mouseover.stopBandbox(); + if( keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT] ) + { + g_Mouseover.addSelection(); + } + else if( keys[SDLK_LCTRL] || keys[SDLK_RCTRL] ) + { + g_Mouseover.removeSelection(); + } + else + g_Mouseover.setSelection(); + break; + case SDL_BUTTON_RIGHT: + g_Selection.contextOrder( keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT] ); + break; + } + break; + case SDL_MOUSEBUTTONDOWN: + switch( ev->button.button ) + { + case SDL_BUTTON_LEFT: + button_down = true; + button_down_x = ev->button.x; + button_down_y = ev->button.y; + break; +#ifdef CONTEXT_VIA_MOUSEWHEEL + case SDL_BUTTON_WHEELDOWN: + if( g_Selection.nextContext() ) + return( EV_HANDLED ); + break; + case SDL_BUTTON_WHEELUP: + if( g_Selection.previousContext() ) + return( EV_HANDLED ); + break; +#endif + } + break; + case SDL_MOUSEMOTION: + if( !g_Mouseover.isBandbox() && button_down ) + { + int deltax = ev->motion.x - button_down_x; + int deltay = ev->motion.y - button_down_y; + if( ABS( deltax ) > 2 || ABS( deltay ) > 2 ) + g_Mouseover.startBandbox( button_down_x, button_down_y ); + } + break; + } + return( EV_PASS ); +} + +bool isOnScreen( CEntity* ev ) +{ + CFrustum frustum = g_Camera.GetFustum(); + return( frustum.IsBoxVisible( CVector3D(), ev->m_actor->GetModel()->GetBounds() ) ); +} + +bool isMouseoverType( CEntity* ev ) +{ + std::vector::iterator it; + for( it = g_Mouseover.m_mouseover.begin(); it < g_Mouseover.m_mouseover.end(); it++ ) + { + if( it->isActive && ( (CBaseEntity*)it->entity->m_base == (CBaseEntity*)ev->m_base ) ) + return( true ); + } + return( false ); +} + +void pushCameraTarget( const CVector3D& target ) +{ + cameraTargets.push_back( g_Camera.m_Orientation.GetTranslation() ); + setCameraTarget( target ); +} + +void setCameraTarget( const CVector3D& target ) +{ + CVector3D cameraForward = g_Camera.m_Orientation.GetIn(); + cameraDelta = ( target - ( cameraForward * 160.0f ) ) - g_Camera.m_Orientation.GetTranslation(); +} + +void popCameraTarget() +{ + cameraDelta = cameraTargets.back() - g_Camera.m_Orientation.GetTranslation(); + cameraTargets.pop_back(); +} \ No newline at end of file diff --git a/source/ps/Interact.h b/source/ps/Interact.h new file mode 100755 index 0000000000..379466a19d --- /dev/null +++ b/source/ps/Interact.h @@ -0,0 +1,121 @@ +// Interact.h +// +// Manages main-screen interaction (screen->worldspace mapping, unit selection, ordering, etc.) +// Does this belong in GUI? + +// Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com) + +#include +#include "Singleton.h" +#include "Entity.h" +#include "EntityManager.h" +#include "EntityMessage.h" +#include "Scheduler.h" +#include "Camera.h" + +// In it's current incarnation, inefficient but pretty +#define SELECTION_TERRAIN_CONFORMANCE + +#define SELECTION_CIRCLE_POINTS 25 +#define SELECTION_BOX_POINTS 10 + +// CSelectedEntities: the singleton containing entities currently selected on the local machine. +// (including group allocations on the local machine) + +struct CSelectedEntities : public Singleton +{ + CSelectedEntities() { clearSelection(); m_group = 255; m_group_highlight = 255; m_contextOrder = -1; } + std::vector m_selected; + std::vector m_groups[10]; + u8 m_group, m_group_highlight; + int m_contextOrder; + + void addSelection( CEntity* entity ); + void removeSelection( CEntity* entity ); + void setSelection( CEntity* entity ); + void clearSelection(); + void removeAll( CEntity* entity ); + bool isSelected( CEntity* entity ); + CVector3D getSelectionPosition(); + + void saveGroup( u8 groupid ); + void loadGroup( u8 groupid ); + void addGroup( u8 groupid ); + void changeGroup( CEntity* entity, u8 groupid ); + void highlightGroup( u8 groupid ); + void highlightNone(); + int getGroupCount( u8 groupid ); + CVector3D getGroupPosition( u8 groupid ); + + void updateContextSet(); + bool isContextValid( int contextOrder ); + void contextOrder( bool pushQueue = false ); + bool nextContext(); + bool previousContext(); + + void renderSelectionOutlines(); + void renderOverlays(); +}; + +// CMouseoverEntities: the singleton containing entities the mouse is currently hovering over or bandboxing +// ( for mouseover selection outlines ) + +struct SMouseoverFader +{ + CEntity* entity; + float fade; + bool isActive; + SMouseoverFader( CEntity* _entity, float _fade = 0.0f, bool _active = true ) : entity( _entity ), fade( _fade ), isActive( _active ) {} +}; + +struct CMouseoverEntities : public Singleton +{ + float m_fadeinrate; + float m_fadeoutrate; + float m_fademaximum; + CEntity* m_target; + + bool m_bandbox, m_viewall; + u16 m_x1, m_y1, m_x2, m_y2; + + CMouseoverEntities() + { + m_bandbox = false; + m_viewall = false; + m_fadeinrate = 1.0f; + m_fadeoutrate = 2.0f; + m_fademaximum = 0.5f; + m_mouseover.clear(); + } + std::vector m_mouseover; + void update( float timestep ); + + void addSelection(); + void removeSelection(); + void setSelection(); + + void expandAcrossScreen(); + void expandAcrossWorld(); + + void renderSelectionOutlines(); + void renderOverlays(); + bool isBandbox() { return( m_bandbox ); } + void startBandbox( u16 x, u16 y ); + void stopBandbox(); +}; + +int interactInputHandler( const SDL_Event* ev ); +bool isMouseoverType( CEntity* ev ); +bool isOnScreen( CEntity* ev ); + +void pushCameraTarget( const CVector3D& target ); +void setCameraTarget( const CVector3D& target ); +void popCameraTarget(); + +extern std::vector cameraTargets; +extern CVector3D cameraDelta; + + +#define g_Selection CSelectedEntities::GetSingleton() +#define g_Mouseover CMouseoverEntities::GetSingleton() + diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index 7a28cbf887..55d286a792 100755 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -8,6 +8,8 @@ #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" +#include "Scheduler.h" +#include "Interact.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" @@ -29,6 +31,9 @@ JSFunctionSpec ScriptFunctionTable[] = {"writeConsole", writeConsole, 1, 0, 0 }, {"getEntityByHandle", getEntityByHandle, 1, 0, 0 }, {"getEntityTemplate", getEntityTemplate, 1, 0, 0 }, + {"setTimeout", setTimeout, 2, 0, 0 }, + {"setInterval", setInterval, 2, 0, 0 }, + {"cancelInterval", cancelInterval, 0, 0, 0 }, {"getGUIObjectByName", JSI_IGUIObject::getByName, 1, 0, 0 }, {"getGlobal", getGlobal, 0, 0, 0 }, {"getGUIGlobal", getGUIGlobal, 0, 0, 0 }, @@ -37,6 +42,19 @@ JSFunctionSpec ScriptFunctionTable[] = {0, 0, 0, 0, 0}, }; +enum ScriptGlobalTinyIDs +{ + GLOBAL_SELECTED, + GLOBAL_SELECTIONARRAY +}; + +JSPropertySpec ScriptGlobalTable[] = +{ + { "selected", GLOBAL_SELECTED, JSPROP_PERMANENT, getSelected, setSelected }, + { "selection", GLOBAL_SELECTIONARRAY, JSPROP_PERMANENT, getSelection, setSelection }, + { 0, 0, 0, 0, 0 }, +}; + // Allow scripts to output to the global log file JSBool WriteLog(JSContext * context, JSObject * globalObject, unsigned int argc, jsval * argv, jsval * rval) { @@ -88,11 +106,13 @@ JSBool getEntityByHandle( JSContext* context, JSObject* globalObject, unsigned i catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; + JS_ReportError( context, "Invalid handle" ); return( JS_TRUE ); } HEntity* v = g_EntityManager.getByHandle( (u16)handle ); if( !v ) { + JS_ReportError( context, "No entity occupying handle: %d", handle ); *rval = JSVAL_NULL; return( JS_TRUE ); } @@ -113,12 +133,14 @@ JSBool getEntityTemplate( JSContext* context, JSObject* globalObject, unsigned i catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; + JS_ReportError( context, "Invalid template identifier" ); return( JS_TRUE ); } CBaseEntity* v = g_EntityTemplateCollection.getTemplate( templateName ); if( !v ) { *rval = JSVAL_NULL; + JS_ReportError( context, "No such template: %s", (const char*)templateName ); return( JS_TRUE ); } JSObject* baseEntity = JS_NewObject( context, &JSI_BaseEntity::JSI_class, NULL, NULL ); @@ -127,6 +149,166 @@ JSBool getEntityTemplate( JSContext* context, JSObject* globalObject, unsigned i return( JS_TRUE ); } +JSBool setTimeout( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ) +{ + assert( argc >= 2 ); + size_t delay; + try + { + delay = g_ScriptingHost.ValueToInt( argv[1] ); + } + catch( ... ) + { + JS_ReportError( context, "Invalid timer parameters" ); + return( JS_TRUE ); + } + + switch( JS_TypeOfValue( context, argv[0] ) ) + { + case JSTYPE_STRING: + { + CStr16 fragment = g_ScriptingHost.ValueToUCString( argv[0] ); + g_Scheduler.pushTime( delay, fragment, JS_GetScopeChain( context ) ); + return( JS_TRUE ); + } + case JSTYPE_FUNCTION: + { + JSFunction* fn = JS_ValueToFunction( context, argv[0] ); + g_Scheduler.pushTime( delay, fn, JS_GetScopeChain( context ) ); + return( JS_TRUE ); + } + default: + JS_ReportError( context, "Invalid timer script" ); + return( JS_TRUE ); + } +} + +JSBool setInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ) +{ + assert( argc >= 2 ); + size_t first, interval; + try + { + first = g_ScriptingHost.ValueToInt( argv[1] ); + if( argc == 3 ) + { + // toDo, first, interval + interval = g_ScriptingHost.ValueToInt( argv[2] ); + } + else + { + // toDo, interval (first = interval) + interval = first; + } + } + catch( ... ) + { + JS_ReportError( context, "Invalid timer parameters" ); + return( JS_TRUE ); + } + + switch( JS_TypeOfValue( context, argv[0] ) ) + { + case JSTYPE_STRING: + { + CStr16 fragment = g_ScriptingHost.ValueToUCString( argv[0] ); + g_Scheduler.pushInterval( first, interval, fragment, JS_GetScopeChain( context ) ); + return( JS_TRUE ); + } + case JSTYPE_FUNCTION: + { + JSFunction* fn = JS_ValueToFunction( context, argv[0] ); + g_Scheduler.pushInterval( first, interval, fn, JS_GetScopeChain( context ) ); + return( JS_TRUE ); + } + default: + JS_ReportError( context, "Invalid timer script" ); + return( JS_TRUE ); + } +} + +JSBool cancelInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ) +{ + g_Scheduler.m_abortInterval = true; + return( JS_TRUE ); +} + +JSBool getSelected( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) +{ + if( g_Selection.m_selected.size() ) + { + JSObject* entity = JS_NewObject( context, &JSI_Entity::JSI_class, NULL, NULL ); + JS_SetPrivate( context, entity, new HEntity( g_Selection.m_selected[0]->me ) ); + *vp = OBJECT_TO_JSVAL( entity ); + return( JS_TRUE ); + } + else + { + *vp = JSVAL_NULL; + return( JS_TRUE ); + } +} + +JSBool setSelected( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) +{ + g_Selection.clearSelection(); + JSObject* selection = JSVAL_TO_OBJECT( *vp ); + if( !JSVAL_IS_NULL( *vp ) && JSVAL_IS_OBJECT( *vp ) && ( JS_GetClass( selection ) == &JSI_Entity::JSI_class ) ) + { + HEntity* entity = (HEntity*)JS_GetPrivate( context, selection ); + g_Selection.addSelection( *entity ); + } + else + JS_ReportError( context, "[Entity] Invalid reference" ); + return( JS_TRUE ); +} + +JSBool getSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) +{ + JSObject* selectionArray = JS_NewArrayObject( context, 0, NULL ); + std::vector::iterator it; int i; + for( it = g_Selection.m_selected.begin(), i = 0; it < g_Selection.m_selected.end(); it++, i++ ) + { + JSObject* entity = JS_NewObject( context, &JSI_Entity::JSI_class, NULL, NULL ); + JS_SetPrivate( context, entity, new HEntity( (*it)->me ) ); + jsval j = OBJECT_TO_JSVAL( entity ); + JS_SetElement( context, selectionArray, i, &j ); + } + *vp = OBJECT_TO_JSVAL( selectionArray ); + return( JS_TRUE ); +} + +JSBool setSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) +{ + g_Selection.clearSelection(); + JSObject* selectionArray = JSVAL_TO_OBJECT( *vp ); + if( JSVAL_IS_NULL( *vp ) || !JSVAL_IS_OBJECT( *vp ) || !JS_IsArrayObject( context, selectionArray ) ) + { + JS_ReportError( context, "Not an array" ); + return( JS_TRUE ); + } + jsuint selectionCount; + if( !JS_GetArrayLength( context, selectionArray, &selectionCount ) ) + { + JS_ReportError( context, "Not an array" ); + return( JS_TRUE ); + } + for( jsuint i = 0; i < selectionCount; i++ ) + { + jsval entry; + JS_GetElement( context, selectionArray, i, &entry ); + JSObject* selection = JSVAL_TO_OBJECT( entry ); + if( !JSVAL_IS_NULL( entry ) && JSVAL_IS_OBJECT( entry ) && ( JS_GetClass( selection ) == &JSI_Entity::JSI_class ) ) + { + HEntity* entity = (HEntity*)JS_GetPrivate( context, JSVAL_TO_OBJECT( entry ) ); + g_Selection.addSelection( &(**entity) ); + } + else + JS_ReportError( context, "[Entity] Invalid reference" ); + } + return( JS_TRUE ); +} + JSBool getGUIGlobal( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ) { *rval = OBJECT_TO_JSVAL( g_GUI.GetScriptObject() ); diff --git a/source/scripting/ScriptGlue.h b/source/scripting/ScriptGlue.h index 6d81e95d39..88965fbe14 100755 --- a/source/scripting/ScriptGlue.h +++ b/source/scripting/ScriptGlue.h @@ -11,6 +11,14 @@ JSBool writeConsole( JSContext* context, JSObject* globalObject, unsigned int ar JSBool getEntityByHandle( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); JSBool getEntityTemplate( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); +JSBool setTimeout( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); +JSBool setInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); +JSBool cancelInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); +// Getter/Setter for interface global objects (e.g. selection object) +JSBool getSelected( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); +JSBool setSelected( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); +JSBool getSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); +JSBool setSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ); // Returns the sort-of-global object associated with the current GUI JSBool getGUIGlobal(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); @@ -25,5 +33,6 @@ JSBool exitProgram(JSContext* context, JSObject* globalObject, unsigned int argc JSBool crash(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); extern JSFunctionSpec ScriptFunctionTable[]; +extern JSPropertySpec ScriptGlobalTable[]; #endif diff --git a/source/scripting/ScriptingHost.cpp b/source/scripting/ScriptingHost.cpp index 2e81e34c66..434922d0a0 100755 --- a/source/scripting/ScriptingHost.cpp +++ b/source/scripting/ScriptingHost.cpp @@ -62,6 +62,9 @@ ScriptingHost::ScriptingHost() : m_RunTime(NULL), m_Context(NULL), m_GlobalObjec if (JS_DefineFunctions(m_Context, m_GlobalObject, ScriptFunctionTable) == JS_FALSE) throw PSERROR_Scripting_NativeFunctionSetupFailed(); + if( JS_DefineProperties( m_Context, m_GlobalObject, ScriptGlobalTable ) == JS_FALSE ) + throw( std::string( "ScriptingHost: Failed to setup native objects" ) ); + std::cout << "Scripting environment initialized" << std::endl; } @@ -123,11 +126,20 @@ jsval ScriptingHost::CallFunction(const std::string & functionName, jsval * para return result; } -jsval ScriptingHost::ExecuteScript(const std::string & script) +jsval ScriptingHost::ExecuteScript( const CStr16& script, const CStr16& calledFrom, JSObject* contextObject ) { jsval rval; - JSBool ok = JS_EvaluateScript(m_Context, m_GlobalObject, script.c_str(), (int)script.length(), "Console", 0, &rval); + /* Unicode->ASCII conversion (mostly) for calledFrom */ + + size_t len = wcstombs( NULL, calledFrom, 0 ); + assert( len != (size_t)-1 ); + char* asciiName = new char[len + 1]; + wcstombs( asciiName, calledFrom, len + 1 ); + + JSBool ok = JS_EvaluateUCScript(m_Context, contextObject ? contextObject : m_GlobalObject, script, (int)script.Length(), asciiName, 0, &rval); + + delete( asciiName ); if (!ok) return JSVAL_NULL; @@ -276,6 +288,12 @@ std::string ScriptingHost::ValueToString(const jsval value) return std::string(bytes); } +CStrW ScriptingHost::ValueToUCString( const jsval value ) +{ + JSString* string = JS_ValueToString( m_Context, value ); + return( CStrW( JS_GetStringChars( string ) ) ); +} + double ScriptingHost::ValueToDouble(const jsval value) { jsdouble d; @@ -318,6 +336,7 @@ void ScriptingHost::ErrorReporter(JSContext * context, const char * message, JSE } } +/* void ScriptingHost::Tick(float timeElapsed) { if (timeElapsed > 0.2f) timeElapsed = 0.2f; @@ -342,6 +361,7 @@ void ScriptingHost::AddDelayedScript(const std::string & functionName, float del { m_DelayedScripts.push_back(DelayedScriptExecutor(functionName, delaySeconds)); } +*/ void ScriptingHost::_CollectGarbage() { diff --git a/source/scripting/ScriptingHost.h b/source/scripting/ScriptingHost.h index b0c23d0a02..5b3c8bc138 100755 --- a/source/scripting/ScriptingHost.h +++ b/source/scripting/ScriptingHost.h @@ -41,7 +41,9 @@ ERROR_TYPE(Scripting_DefineType, CreationFailed); #include #include "Singleton.h" +#include "CStr.h" +/* class DelayedScriptExecutor { public: @@ -53,6 +55,7 @@ public: std::string m_FunctionName; float m_SecondsToExecution; }; +*/ class CustomType { @@ -70,7 +73,7 @@ private: JSErrorReport m_ErrorReport; - std::vector < DelayedScriptExecutor > m_DelayedScripts; + //std::vector < DelayedScriptExecutor > m_DelayedScripts; std::map < std::string, CustomType > m_CustomObjectTypes; void _CollectGarbage(); @@ -86,7 +89,7 @@ public: jsval CallFunction(const std::string & functionName, jsval * params, int numParams); - jsval ExecuteScript(const std::string & script); + jsval ScriptingHost::ExecuteScript( const CStr16& script, const CStr16& calledFrom = CStr16( L"Console" ), JSObject* contextObject = NULL ); void RegisterFunction(const std::string & functionName, JSNative function, int numArgs); @@ -106,11 +109,16 @@ public: int ValueToInt(const jsval value); bool ValueToBool(const jsval value); std::string ValueToString(const jsval value); + CStrW ValueToUCString( const jsval value ); double ValueToDouble(const jsval value); + /* + All herald the new way of doing this! + void Tick(float timeElapsed); void AddDelayedScript(const std::string & functionName, float delaySeconds); + */ static void ErrorReporter(JSContext * context, const char * message, JSErrorReport * report); }; diff --git a/source/simulation/BaseEntity.h b/source/simulation/BaseEntity.h index 7dd268c8a9..8322b820c2 100755 --- a/source/simulation/BaseEntity.h +++ b/source/simulation/BaseEntity.h @@ -1,6 +1,6 @@ // BaseEntity.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity Templates // diff --git a/source/simulation/BaseEntityCollection.h b/source/simulation/BaseEntityCollection.h index bc258b920b..133c7067d8 100755 --- a/source/simulation/BaseEntityCollection.h +++ b/source/simulation/BaseEntityCollection.h @@ -1,6 +1,6 @@ // BaseEntityCollection.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Keeps tabs on the various types of entity that roam the world. // diff --git a/source/simulation/BoundingObjects.cpp b/source/simulation/BoundingObjects.cpp index b365c1ec7b..840161daa4 100755 --- a/source/simulation/BoundingObjects.cpp +++ b/source/simulation/BoundingObjects.cpp @@ -48,7 +48,7 @@ CBoundingCircle::CBoundingCircle( float x, float y, CBoundingCircle* copy ) void CBoundingObject::setPosition( float x, float y ) { m_pos.x = x; m_pos.y = y; - m_pos -= m_offset; + m_pos += m_offset; } void CBoundingCircle::setRadius( float radius ) diff --git a/source/simulation/BoundingObjects.h b/source/simulation/BoundingObjects.h index 0a08ebd8b3..3cb7621be0 100755 --- a/source/simulation/BoundingObjects.h +++ b/source/simulation/BoundingObjects.h @@ -1,6 +1,6 @@ // BoundingObjects.h // -// Last modified: 26 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Bounding circle and object-aligned bounding box. 2D, for simulation code. // diff --git a/source/simulation/Collision.cpp b/source/simulation/Collision.cpp index 47cec65928..ff957c7d2c 100755 --- a/source/simulation/Collision.cpp +++ b/source/simulation/Collision.cpp @@ -5,7 +5,7 @@ CBoundingObject* getContainingObject( const CVector2D& point ) { - std::vector* entities = g_EntityManager.getActive(); + std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; for( it = entities->begin(); it != entities->end(); it++ ) @@ -27,7 +27,7 @@ HEntity getCollisionObject( CEntity* entity ) { assert( entity->m_bounds ); - std::vector* entities = g_EntityManager.getActive(); + std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; for( it = entities->begin(); it != entities->end(); it++ ) @@ -58,7 +58,7 @@ HEntity getCollisionObject( CEntity* entity, float x, float y ) bool getRayIntersection( const CVector2D& source, const CVector2D& forward, const CVector2D& right, float length, float maxDistance, CBoundingObject* destinationCollisionObject, rayIntersectionResults* results ) { - std::vector* entities = g_EntityManager.getActive(); + std::vector* entities = g_EntityManager.getExtant(); std::vector::iterator it; float closestApproach, dist; diff --git a/source/simulation/Collision.h b/source/simulation/Collision.h index ca2cfdd951..f671e3945b 100755 --- a/source/simulation/Collision.h +++ b/source/simulation/Collision.h @@ -1,6 +1,6 @@ // Collision.h // -// Last modified: 28 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Collision detection functions // diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index cc40128f79..063ff37c43 100755 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -9,10 +9,13 @@ #include "Renderer.h" #include "Model.h" #include "Terrain.h" +#include "Interact.h" #include "Collision.h" #include "PathfindEngine.h" +extern CCamera g_Camera; + CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) { m_position = position; @@ -24,9 +27,12 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) m_base.associate( this, "template", ( void( IPropertyOwner::* )() )&CEntity::loadBase ); m_name.associate( this, "name" ); m_speed.associate( this, "speed" ); + m_selected.associate( this, "selected", ( void( IPropertyOwner::* )() )&CEntity::checkSelection ); + m_grouped_mirror.associate( this, "group", ( void( IPropertyOwner::* )() )&CEntity::checkGroup ); + m_extant_mirror.associate( this, "extant", ( void( IPropertyOwner::* )() )&CEntity::checkExtant ); m_turningRadius.associate( this, "turningRadius" ); - m_position.associate( this, "position", ( void( IPropertyOwner::* )() )&CEntity::teleport ); - m_orientation.associate( this, "orientation", ( void( IPropertyOwner::* )() )&CEntity::reorient ); + m_graphics_position.associate( this, "position", ( void( IPropertyOwner::* )() )&CEntity::teleport ); + m_graphics_orientation.associate( this, "orientation", ( void( IPropertyOwner::* )() )&CEntity::reorient ); // Set our parent unit and build us an actor. m_actor = NULL; @@ -35,10 +41,14 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) m_base = base; loadBase(); + + // We can now freely admit that this exists. + m_extant = true; + m_extant_mirror = true; - snapToGround(); - updateActorTransforms(); - + m_selected = false; + m_grouped = 255; + m_grouped_mirror = 0; } CEntity::~CEntity() @@ -67,7 +77,7 @@ void CEntity::loadBase() delete( m_bounds ); } - m_actor = new CUnit(m_base->m_actorObject,m_base->m_actorObject->m_Model->Clone()); + m_actor = new CUnit( m_base->m_actorObject, m_base->m_actorObject->m_Model->Clone(), this ); // Register the actor with the renderer. @@ -88,6 +98,30 @@ void CEntity::loadBase() } } +void CEntity::kill() +{ + g_Selection.removeAll( this ); + if( m_bounds ) delete( m_bounds ); + m_actor = NULL; + m_bounds = NULL; + + m_extant = false; + m_extant_mirror = false; + + return; // TODO: Help! Wierd heap corruption error I've not been able to fix + // but stopping entities being deallocated supresses it. + // Have a hunch it's to do with deallocating actors that haven't + // had renderdata set up yet. + + if( m_actor ) + { + g_UnitMan.RemoveUnit( m_actor ); + delete( m_actor ); + } + + me = HEntity(); +} + bool isWaypoint( CEntity* e ) { return( e->m_base->m_name == CStr( "Waypoint" ) ); @@ -97,24 +131,19 @@ void CEntity::updateActorTransforms() { CMatrix3D m; - m._11 = -m_ahead.y; m._12 = 0.0f; m._13 = -m_ahead.x; m._14 = m_position.X; - m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = m_position.Y; - m._31 = m_ahead.x; m._32 = 0.0f; m._33 = -m_ahead.y; m._34 = m_position.Z; - m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f; + float s = sin( m_graphics_orientation ); + float c = cos( m_graphics_orientation ); - /* Equivalent to: - m.SetYRotation( m_orientation ); - m.Translate( m_position ); - But the matrix multiplication seemed such a waste when we already have a forward vector - */ + m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = m_graphics_position.X; + m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = m_graphics_position.Y; + m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = m_graphics_position.Z; + m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f; m_actor->GetModel()->SetTransform( m ); } float CEntity::getExactGroundLevel( float x, float y ) { - // TODO MT: If OK with Rich, move to terrain core. Once this works, that is. - x /= 4.0f; y /= 4.0f; @@ -163,11 +192,14 @@ float CEntity::getExactGroundLevel( float x, float y ) void CEntity::snapToGround() { - m_position.Y = getExactGroundLevel( m_position.X, m_position.Z ); + m_graphics_position.Y = getExactGroundLevel( m_graphics_position.X, m_graphics_position.Z ); } void CEntity::update( float timestep ) { + m_position_previous = m_position; + m_orientation_previous = m_orientation; + while( !m_orderQueue.empty() ) { CEntityOrder* current = &m_orderQueue.front(); @@ -193,7 +225,7 @@ void CEntity::update( float timestep ) m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_IdleAnim ); } -void CEntity::dispatch( CMessage* msg ) +void CEntity::dispatch( const CMessage* msg ) { switch( msg->type ) { @@ -205,7 +237,7 @@ void CEntity::dispatch( CMessage* msg ) if( getCollisionObject( this ) ) { // Prometheus telefragging. (Appeared inside another object) - g_EntityManager.kill( me ); + kill(); return; } std::vector* waypoints = g_EntityManager.matches( isWaypoint ); @@ -225,14 +257,38 @@ void CEntity::dispatch( CMessage* msg ) delete( waypoints ); } break; + case CMessage::EMSG_ORDER: + CMessageOrder* m; + m = (CMessageOrder*)msg; + if( !m->queue ) + clearOrders(); + pushOrder( m->order ); + break; } } + +void CEntity::clearOrders() +{ + m_orderQueue.clear(); +} void CEntity::pushOrder( CEntityOrder& order ) { m_orderQueue.push_back( order ); } +bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget ) +{ + // Hardcoding... + switch( orderType ) + { + case CEntityOrder::ORDER_GOTO: + case CEntityOrder::ORDER_PATROL: + return( m_speed > 0.0f ); + } + return( false ); +} + void CEntity::repath() { CVector2D destination; @@ -251,6 +307,7 @@ void CEntity::repath() void CEntity::reorient() { + m_orientation = m_graphics_orientation; m_ahead.x = sin( m_orientation ); m_ahead.y = cos( m_orientation ); if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) @@ -260,18 +317,66 @@ void CEntity::reorient() void CEntity::teleport() { - snapToGround(); - updateActorTransforms(); + m_position = m_graphics_position; m_bounds->setPosition( m_position.X, m_position.Z ); repath(); } -void CEntity::render() +void CEntity::checkSelection() { - // Rich! Help! ;) + if( m_selected ) + { + if( !g_Selection.isSelected( this ) ) + g_Selection.addSelection( this ); + } + else + { + if( g_Selection.isSelected( this ) ) + g_Selection.removeSelection( this ); + } +} - // HACK: As in this entire function is a... +void CEntity::checkGroup() +{ + if( m_grouped != m_grouped_mirror ) + { + if( ( m_grouped_mirror >= 0 ) && ( m_grouped_mirror <= 10 ) ) + { + u8 newgroup = m_grouped_mirror; + if( newgroup == 0 ) newgroup = 255; + if( newgroup == 10 ) newgroup = 0; + + g_Selection.changeGroup( this, newgroup ); + } + else + m_grouped_mirror = m_grouped; + } +} + +void CEntity::checkExtant() +{ + if( m_extant && !( (bool)m_extant_mirror ) ) + kill(); + // Sorry. Dead stuff stays dead. +} + +void CEntity::interpolate( float relativeoffset ) +{ + m_graphics_position = Interpolate( m_position_previous, m_position, relativeoffset ); + // Avoid wraparound glitches for interpolating angles. + while( m_orientation < m_orientation_previous - PI ) + m_orientation_previous -= 2 * PI; + while( m_orientation > m_orientation_previous + PI ) + m_orientation_previous += 2 * PI; + + m_graphics_orientation = Interpolate( m_orientation_previous, m_orientation, relativeoffset ); + snapToGround(); + updateActorTransforms(); +} + +void CEntity::render() +{ if( !m_orderQueue.empty() ) { std::deque::iterator it; @@ -345,14 +450,97 @@ void CEntity::render() } glColor3f( 1.0f, 1.0f, 1.0f ); - - if( getCollisionObject( this ) ) glColor3f( 0.5f, 0.5f, 1.0f ); m_bounds->render( getExactGroundLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f ); - } -void PASAPScenario() +void CEntity::renderSelectionOutline( float alpha ) { - // Got rid of all the hardcoding that was here. + if( !m_bounds ) return; + + glColor4f( 1.0f, 1.0f, 1.0f, alpha ); + + glBegin( GL_LINE_LOOP ); + + CVector3D pos = m_graphics_position; + pos.X += m_bounds->m_offset.x; + pos.Z += m_bounds->m_offset.y; + + switch( m_bounds->m_type ) + { + case CBoundingObject::BOUND_CIRCLE: + { + float radius = ((CBoundingCircle*)m_bounds)->m_radius; + for( int i = 0; i < SELECTION_CIRCLE_POINTS; i++ ) + { + float ang = i * 2 * PI / (float)SELECTION_CIRCLE_POINTS; + float x = pos.X + radius * sin( ang ); + float y = pos.Z + radius * cos( ang ); +#ifdef SELECTION_TERRAIN_CONFORMANCE + glVertex3f( x, getExactGroundLevel( x, y ) + 0.25f, y ); +#else + glVertex3f( x, pos.Y + 0.25f, y ); +#endif + } + break; + } + case CBoundingObject::BOUND_OABB: + { + CVector2D p, q; + CVector2D u, v; + q.x = pos.X; q.y = pos.Z; + float h = ((CBoundingBox*)m_bounds)->m_h; + float w = ((CBoundingBox*)m_bounds)->m_w; + + u.x = sin( m_graphics_orientation ); + u.y = cos( m_graphics_orientation ); + v.x = u.y; + v.y = -u.x; + +#ifdef SELECTION_TERRAIN_CONFORMANCE + for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) + { + p = q + u * h + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) + { + p = q + u * ( h * (float)i / (float)SELECTION_BOX_POINTS ) - v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) + { + p = q - u * h + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) + { + p = q + u * ( h * (float)i / (float)SELECTION_BOX_POINTS ) + v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + } +#else + p = q + u * h + v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q + u * h - v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q - u * h + v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q + u * h + v * w; + glVertex3f( p.x, getExactGroundLevel( p.x, p.y ) + 0.25f, p.y ); +#endif + + + break; + } + } + + glEnd(); + } + diff --git a/source/simulation/Entity.h b/source/simulation/Entity.h index 41becacc19..0d227859f0 100755 --- a/source/simulation/Entity.h +++ b/source/simulation/Entity.h @@ -1,6 +1,6 @@ // Entity.h // -// Last modified: 26 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity class. // @@ -13,7 +13,7 @@ // Destroying entities: An entity is destroyed when all references to it expire. // It is somewhat unfunny if this happens while a method from this // class is still executing. If you need to kill an entity, -// use g_EntityManager.kill( entity ). If kill() releases the final handle, +// use CEntity::kill(). If kill() releases the final handle, // the entity is placed in the reaper queue and is deleted immediately // prior to its next update cycle. // @@ -27,7 +27,6 @@ // Some notes: update() and dispatch() /can/ be called directly without ill effects, // but it's preferable to go through the Entity manager and the Scheduler, respectively. // -// Collision detection/avoidance is now present in some form; this is a work in progress. #ifndef ENTITY_INCLUDED #define ENTITY_INCLUDED @@ -55,11 +54,28 @@ public: CProperty_CStr m_name; CProperty_float m_speed; CProperty_float m_turningRadius; - CProperty_CVector3D m_position; + CProperty_bool m_selected; + + bool m_extant; // Don't want JS to have direct write-access to these. (Things that should be done might not be) + CProperty_bool m_extant_mirror; // plus this way limits the number of nasty semantics to work around. + u8 m_grouped; + CProperty_i32 m_grouped_mirror; + + + //-- Interpolated property + CVector3D m_position; + CVector3D m_position_previous; + CProperty_CVector3D m_graphics_position; + CBoundingObject* m_bounds; float m_targetorientation; CVector2D m_ahead; - CProperty_float m_orientation; + + //-- Interpolated property + float m_orientation; + float m_orientation_previous; + CProperty_float m_graphics_orientation; + CUnit* m_actor; std::deque m_orderQueue; @@ -76,18 +92,31 @@ public: // Handle-to-self. HEntity me; - void dispatch( CMessage* msg ); + + void dispatch( const CMessage* msg ); void update( float timestep ); - void updateActorTransforms(); - void render(); + void kill(); + + void interpolate( float relativeoffset ); float getExactGroundLevel( float x, float y ); void snapToGround(); + void updateActorTransforms(); + void render(); + void renderSelectionOutline( float alpha = 1.0f ); + void repath(); void loadBase(); - void reorient(); - void teleport(); // Fixes things if the position is changed by something externally. + void reorient(); // Orientation + void teleport(); // Fixes things if the position is changed by something externally. + void checkSelection(); // In case anyone tries to select/deselect this through JavaScript. You'd think they'd have something better to do. + void checkGroup(); // Groups + void checkExtant(); // Existance + + bool acceptsOrder( int orderType, CEntity* orderTarget ); + + void clearOrders(); void pushOrder( CEntityOrder& order ); }; diff --git a/source/simulation/EntityHandles.cpp b/source/simulation/EntityHandles.cpp index 5e7e1d2a44..c9a0c68061 100755 --- a/source/simulation/EntityHandles.cpp +++ b/source/simulation/EntityHandles.cpp @@ -66,6 +66,12 @@ CEntity* HEntity::operator->() const return( g_EntityManager.m_entities[m_handle].m_entity ); } +HEntity::operator CEntity*() const +{ + assert( m_handle != INVALID_HANDLE ); + assert( g_EntityManager.m_entities[m_handle].m_refcount ); + return( g_EntityManager.m_entities[m_handle].m_entity ); +} CEntity& HEntity::operator*() const { assert( m_handle != INVALID_HANDLE ); diff --git a/source/simulation/EntityHandles.h b/source/simulation/EntityHandles.h index 091af56cd7..9cfed566ac 100755 --- a/source/simulation/EntityHandles.h +++ b/source/simulation/EntityHandles.h @@ -1,6 +1,6 @@ // EntityHandles.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity smart pointer definitions. // @@ -51,6 +51,7 @@ public: void operator=( const HEntity& copy ); bool operator==( const HEntity& test ) const; operator bool() const { return( m_handle != INVALID_HANDLE ); } + operator CEntity*() const; ~HEntity(); }; diff --git a/source/simulation/EntityManager.cpp b/source/simulation/EntityManager.cpp index 6aa91bc3a1..ca03d6f194 100755 --- a/source/simulation/EntityManager.cpp +++ b/source/simulation/EntityManager.cpp @@ -44,17 +44,27 @@ std::vector* CEntityManager::matches( EntityPredicate predicate ) { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) if( predicate( m_entities[i].m_entity ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); } -std::vector* CEntityManager::getActive() +std::vector* CEntityManager::matches( EntityPredicate predicate1, EntityPredicate predicate2 ) +{ + std::vector* matchlist = new std::vector; + for( int i = 0; i < MAX_HANDLES; i++ ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + if( predicate1( m_entities[i].m_entity ) && predicate2( m_entities[i].m_entity ) ) + matchlist->push_back( HEntity( i ) ); + return( matchlist ); +} + +std::vector* CEntityManager::getExtant() { std::vector* activelist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) activelist->push_back( HEntity( i ) ); return( activelist ); } @@ -62,36 +72,39 @@ std::vector* CEntityManager::getActive() void CEntityManager::dispatchAll( CMessage* msg ) { for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->dispatch( msg ); } -void CEntityManager::kill( HEntity entity ) -{ - m_reaper.push_back( entity ); -} - void CEntityManager::updateAll( float timestep ) { - std::vector::iterator it; + std::vector::iterator it; for( it = m_reaper.begin(); it < m_reaper.end(); it++ ) - (*it)->me = HEntity(); + delete( *it ); m_reaper.clear(); for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->update( timestep ); } +void CEntityManager::interpolateAll( float relativeoffset ) +{ + for( int i = 0; i < MAX_HANDLES; i++ ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) + m_entities[i].m_entity->interpolate( relativeoffset ); +} + void CEntityManager::renderAll() { for( int i = 0; i < MAX_HANDLES; i++ ) - if( m_entities[i].m_refcount ) + if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->render(); } void CEntityManager::destroy( u16 handle ) { + m_reaper.push_back( m_entities[handle].m_entity ); m_entities[handle].m_entity->me.m_handle = INVALID_HANDLE; delete( m_entities[handle].m_entity ); } diff --git a/source/simulation/EntityManager.h b/source/simulation/EntityManager.h index a265955bf8..5b2e8cc190 100755 --- a/source/simulation/EntityManager.h +++ b/source/simulation/EntityManager.h @@ -1,6 +1,6 @@ // EntityManager.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Maintains entity id->object mappings. Does most of the work involved in creating an entity. // @@ -11,10 +11,11 @@ // Perform updates on all world entities by g_EntityManager.updateAll( timestep ) // Dispatch an identical message to all world entities by g_EntityManager.dispatchAll( message_pointer ) // Get an STL vector container of all entities with a certain property with g_EntityManager.matches( predicate ) -// or just get all entities with g_EntityManager.getActive(). +// or just get all entities with g_EntityManager.getExtant(). // // Those last two functions - caller has responsibility for deleting the collection when you're done with it. + #ifndef ENTITY_MANAGER_INCLUDED #define ENTITY_MANAGER_INCLUDED @@ -32,7 +33,7 @@ class CEntityManager : public Singleton friend class HEntity; friend class CHandle; CHandle m_entities[MAX_HANDLES]; - std::vector m_reaper; + std::vector m_reaper; int m_nextalloc; static bool m_extant; void destroy( u16 handle ); @@ -43,12 +44,13 @@ public: HEntity create( CBaseEntity* base, CVector3D position, float orientation ); HEntity create( CStr templatename, CVector3D position, float orientation ); HEntity* getByHandle( u16 index ); - void kill( HEntity ent ); void updateAll( float timestep ); + void interpolateAll( float relativeoffset ); void dispatchAll( CMessage* msg ); void renderAll(); std::vector* matches( EntityPredicate predicate ); - std::vector* getActive(); + std::vector* matches( EntityPredicate predicate1, EntityPredicate predicate2 ); + std::vector* getExtant(); static inline bool extant() // True if the singleton is actively maintaining handles. When false, system is shutting down, handles are quietly dumped. { return( m_extant ); diff --git a/source/simulation/EntityMessage.h b/source/simulation/EntityMessage.h index 9de5183f8a..6b18084a5d 100755 --- a/source/simulation/EntityMessage.h +++ b/source/simulation/EntityMessage.h @@ -1,6 +1,6 @@ // EntityMessage.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity message structure. // @@ -11,16 +11,20 @@ // for each entity as it is loaded; instead, wait for all // entities to be created, then issue this message to all // of them simultaneously. +// EMSG_ORDER:To push a message into the entity's order queue #ifndef MESSAGING_INCLUDED #define MESSAGING_INCLUDED +#include "EntityOrders.h" + struct CMessage { enum EMessageType { EMSG_TICK, - EMSG_INIT + EMSG_INIT, + EMSG_ORDER, } type; CMessage( EMessageType _type ) { @@ -28,4 +32,11 @@ struct CMessage } }; +struct CMessageOrder : public CMessage +{ + CMessageOrder( CEntityOrder& _order, bool _queue = false ) : CMessage( EMSG_ORDER ), order( _order ), queue( _queue ) {} + CEntityOrder order; + bool queue; +}; + #endif diff --git a/source/simulation/EntityOrders.h b/source/simulation/EntityOrders.h index 30a854a8ee..07f7a4aa81 100755 --- a/source/simulation/EntityOrders.h +++ b/source/simulation/EntityOrders.h @@ -1,6 +1,6 @@ // EntityOrders.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Entity orders structure. // @@ -49,9 +49,10 @@ public: { ORDER_GOTO_NOPATHING, ORDER_GOTO_SMOOTHED, - ORDER_GOTO_COLLISION, + ORDER_GOTO_COLLISION, ORDER_GOTO, - ORDER_PATROL + ORDER_PATROL, + ORDER_LAST } m_type; SOrderData m_data[ORDER_MAX_DATA]; }; diff --git a/source/simulation/EntityProperties.cpp b/source/simulation/EntityProperties.cpp index f4b9232e74..9d1615b184 100755 --- a/source/simulation/EntityProperties.cpp +++ b/source/simulation/EntityProperties.cpp @@ -37,7 +37,7 @@ CProperty_i32::~CProperty_i32() delete( modifier ); } -inline CProperty_i32& CProperty_i32::operator =( i32 value ) +CProperty_i32& CProperty_i32::operator =( i32 value ) { if( !modifier ) modifier = new SProperty_NumericModifier(); @@ -83,11 +83,70 @@ inline CProperty_i32::operator i32() return( data ); } -CProperty_i32::operator jsval() +jsval CProperty_i32::tojsval() { return( INT_TO_JSVAL( data ) ); } +CProperty_bool::CProperty_bool() +{ + modifier = NULL; +} + +CProperty_bool::~CProperty_bool() +{ + if( modifier ) + delete( modifier ); +} + +CProperty_bool& CProperty_bool::operator=( const bool value ) +{ + if( !modifier ) + modifier = new SProperty_BooleanModifier(); + *modifier = value; + data = value; + return( *this ); +} + +void CProperty_bool::set( jsval value ) +{ + if( !modifier ) + modifier = new SProperty_BooleanModifier(); + try + { + *modifier = g_ScriptingHost.ValueToBool( value ); + } + catch( ... ) + { + *modifier = false; + } +} + +bool CProperty_bool::rebuild( CProperty* parent, bool triggerFn ) +{ + CProperty_bool* _parent = (CProperty_bool*)parent; + bool newvalue = false; + if( _parent ) + newvalue = *_parent; + if( modifier ) + newvalue = modifier->replacement; + if( data == newvalue ) + return( false ); // No change. + data = newvalue; + if( triggerFn && m_updateFn ) (m_owner->*m_updateFn)(); + return( true ); +} + +inline CProperty_bool::operator bool() +{ + return( data ); +} + +jsval CProperty_bool::tojsval() +{ + return( BOOLEAN_TO_JSVAL( data ) ); +} + CProperty_float::CProperty_float() { modifier = NULL; @@ -145,7 +204,7 @@ CProperty_float::operator float() return( data ); } -CProperty_float::operator jsval() +jsval CProperty_float::tojsval() { return( DOUBLE_TO_JSVAL( JS_NewDouble( g_ScriptingHost.getContext(), (jsdouble)data ) ) ); } @@ -241,7 +300,7 @@ bool CProperty_CStr::rebuild( CProperty* parent, bool triggerFn ) return( true ); } -CProperty_CStr::operator jsval() +jsval CProperty_CStr::tojsval() { return( STRING_TO_JSVAL( JS_NewStringCopyZ( g_ScriptingHost.getContext(), m_String.c_str() ) ) ); } @@ -255,7 +314,7 @@ CProperty_CVector3D& CProperty_CVector3D::operator =( const CVector3D& value ) void CProperty_CVector3D::set( jsval value ) { JSObject* vector3d = JSVAL_TO_OBJECT( value ); - if( !JSVAL_IS_OBJECT( value ) || ( JS_GetClass( vector3d ) != &JSI_Vector3D::JSI_class ) ) + if( JSVAL_IS_NULL( value ) || !JSVAL_IS_OBJECT( value ) || ( JS_GetClass( vector3d ) != &JSI_Vector3D::JSI_class ) ) { X = 0.0f; Y = 0.0f; Z = 0.0f; } @@ -274,7 +333,7 @@ bool CProperty_CVector3D::rebuild( CProperty* parent, bool triggerFn ) return( false ); // Vector properties aren't inheritable. } -CProperty_CVector3D::operator jsval() +jsval CProperty_CVector3D::tojsval() { JSObject* vector3d = JS_NewObject( g_ScriptingHost.getContext(), &JSI_Vector3D::JSI_class, NULL, NULL ); JS_SetPrivate( g_ScriptingHost.getContext(), vector3d, new JSI_Vector3D::Vector3D_Info( this, m_owner, m_updateFn ) ); @@ -290,8 +349,13 @@ CProperty_CBaseEntityPtr& CProperty_CBaseEntityPtr::operator =( CBaseEntity* val void CProperty_CBaseEntityPtr::set( jsval value ) { JSObject* baseEntity = JSVAL_TO_OBJECT( value ); - if( JSVAL_IS_OBJECT( value ) && ( JS_GetClass( baseEntity ) == &JSI_BaseEntity::JSI_class ) ) + if( !JSVAL_IS_NULL( value ) && JSVAL_IS_OBJECT( value ) && ( JS_GetClass( baseEntity ) == &JSI_BaseEntity::JSI_class ) ) + { data = (CBaseEntity*)JS_GetPrivate( g_ScriptingHost.getContext(), baseEntity ); + } + else + JS_ReportError( g_ScriptingHost.getContext(), "[BaseEntity] Invalid reference" ); + } bool CProperty_CBaseEntityPtr::rebuild( CProperty* parent, bool triggerFn ) @@ -300,7 +364,7 @@ bool CProperty_CBaseEntityPtr::rebuild( CProperty* parent, bool triggerFn ) return( false ); // CBaseEntity* properties aren't inheritable. } -CProperty_CBaseEntityPtr::operator jsval() +jsval CProperty_CBaseEntityPtr::tojsval() { JSObject* baseEntity = JS_NewObject( g_ScriptingHost.getContext(), &JSI_BaseEntity::JSI_class, NULL, NULL ); JS_SetPrivate( g_ScriptingHost.getContext(), baseEntity, data ); diff --git a/source/simulation/EntityProperties.h b/source/simulation/EntityProperties.h index 08e51ae43d..440d486541 100755 --- a/source/simulation/EntityProperties.h +++ b/source/simulation/EntityProperties.h @@ -1,6 +1,6 @@ // EntityProperties.h // -// Last modified: 22 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Extended properties table, primarily intended for data-inheritable properties and those defined by JavaScript functions. // @@ -43,6 +43,9 @@ class CBaseEntity; class CProperty; struct SProperty_NumericModifier; struct SProperty_StringModifier; +struct SProperty_BooleanModifier; + +// Property abstract. class CProperty { @@ -52,12 +55,14 @@ protected: virtual void set( const jsval value ) = 0; public: CProperty& operator=( const jsval value ); - virtual operator jsval() = 0; + virtual jsval tojsval() = 0; virtual bool rebuild( CProperty* parent, bool triggerFn = true ) = 0; // Returns true if the rebuild changed the value of this property. void associate( IPropertyOwner* owner, const CStr& name ); void associate( IPropertyOwner* owner, const CStr& name, void (IPropertyOwner::*updateFn)() ); }; +// Integer specialization + class CProperty_i32 : public CProperty { i32 data; @@ -66,12 +71,30 @@ public: CProperty_i32(); ~CProperty_i32(); void set( const jsval value ); - operator jsval(); + jsval tojsval(); bool rebuild( CProperty* parent, bool triggerFn = true ); CProperty_i32& operator=( const i32 value ); operator i32(); }; +// Boolean specialization + +class CProperty_bool : public CProperty +{ + bool data; + SProperty_BooleanModifier* modifier; +public: + CProperty_bool(); + ~CProperty_bool(); + void set( const jsval value ); + jsval tojsval(); + bool rebuild( CProperty* parent, bool triggerFn = true ); + CProperty_bool& operator=( const bool value ); + operator bool(); +}; + +// Floating-point specialization + class CProperty_float : public CProperty { float data; @@ -80,7 +103,7 @@ public: CProperty_float(); ~CProperty_float(); void set( const jsval value ); - operator jsval(); + jsval tojsval(); bool rebuild( CProperty* parent, bool triggerFn = true ); CProperty_float& operator=( const float& value ); operator float(); @@ -94,6 +117,8 @@ public: bool operator==( float value ); }; +// String specialization + class CProperty_CStr : public CProperty, public CStr { SProperty_StringModifier* modifier; @@ -101,26 +126,30 @@ public: CProperty_CStr(); ~CProperty_CStr(); void set( const jsval value ); - operator jsval(); + jsval tojsval(); bool rebuild( CProperty* parent, bool triggerFn = true ); CProperty_CStr& operator=( const CStr& value ); }; +// 3-Vector specialization + class CProperty_CVector3D : public CProperty, public CVector3D { public: void set( const jsval value ); - operator jsval(); + jsval tojsval(); bool rebuild( CProperty* parent, bool triggerFn = true ); CProperty_CVector3D& operator=( const CVector3D& value ); }; +// CBaseEntity* specialization + class CProperty_CBaseEntityPtr : public CProperty { CBaseEntity* data; public: void set( const jsval value ); - operator jsval(); + jsval tojsval(); bool rebuild( CProperty* parent, bool triggerFn = true ); operator CBaseEntity*(); operator bool(); @@ -160,5 +189,14 @@ struct SProperty_StringModifier } }; +struct SProperty_BooleanModifier +{ + bool replacement; + void operator=( const bool value ) + { + replacement = value; + } +}; + #endif diff --git a/source/simulation/EntityStateProcessing.cpp b/source/simulation/EntityStateProcessing.cpp index 9600524076..aa6bd69931 100755 --- a/source/simulation/EntityStateProcessing.cpp +++ b/source/simulation/EntityStateProcessing.cpp @@ -93,7 +93,14 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, float timestep ) if( collide ) { - // Hit something. Take a step back. + // Hit something. Is it our destination? + if( collide->m_bounds->contains( current->m_data[0].location ) ) + { + m_orderQueue.pop_front(); + return( false ); + } + + // No? Take a step back. m_position.X -= delta.x; m_position.Z -= delta.y; @@ -110,78 +117,44 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, float timestep ) m_bounds->setPosition( m_position.X, m_position.Z ); return( false ); } - - if( collide->m_bounds->m_type == CBoundingObject::BOUND_OABB ) - { - // And it's square. - // TODO: Implement this case properly. - - // HACK: See if this thing we've hit is likely to be our destination. If so, just skip to our next waypoint. - // Otherwise, turn right (as with circle collisions) - - if( len < collide->m_bounds->m_radius * 2.0f ) - { - m_orderQueue.pop_front(); - return( false ); - } - else - { - CEntityOrder avoidance; - avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION; - CVector2D right; - right.x = m_ahead.y; right.y = -m_ahead.x; - CVector2D avoidancePosition = collide->m_bounds->m_pos + right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); - avoidance.m_data[0].location = avoidancePosition; - if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) - m_orderQueue.pop_front(); - m_orderQueue.push_front( avoidance ); - return( false ); - } + + // No? Path around it. + CEntityOrder avoidance; + avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION; + CVector2D right; + right.x = m_ahead.y; right.y = -m_ahead.x; + CVector2D avoidancePosition; + + if( ( collide->m_bounds->m_pos - m_bounds->m_pos ).dot( right ) < 1 ) + { + // Turn right. + avoidancePosition = collide->m_bounds->m_pos + right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); } else { - // A circle. - // TODO: Implement this properly. - // Work out if our path goes to the left or to the right - // of this obstacle. Go that way. - // Weight a little to the right, too (helps unit-unit collisions) - - CEntityOrder avoidance; - avoidance.m_type = CEntityOrder::ORDER_GOTO_COLLISION; - CVector2D right; - right.x = m_ahead.y; right.y = -m_ahead.x; - CVector2D avoidancePosition; - - if( ( collide->m_bounds->m_pos - m_bounds->m_pos ).dot( right ) < 1 ) - { - // Turn right. - avoidancePosition = collide->m_bounds->m_pos + right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); - } - else - { - // Turn left. - avoidancePosition = collide->m_bounds->m_pos - right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); - } - - avoidance.m_data[0].location = avoidancePosition; - if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) - m_orderQueue.pop_front(); - m_orderQueue.push_front( avoidance ); - return( false ); + // Turn left. + avoidancePosition = collide->m_bounds->m_pos - right * ( collide->m_bounds->m_radius + m_bounds->m_radius * 2.5f ); } - } - snapToGround(); - updateActorTransforms(); + avoidance.m_data[0].location = avoidancePosition; + if( current->m_type == CEntityOrder::ORDER_GOTO_COLLISION ) + m_orderQueue.pop_front(); + m_orderQueue.push_front( avoidance ); + return( false ); + + } return( false ); } bool CEntity::processGoto( CEntityOrder* current, float timestep ) { + CVector2D pos( m_position.X, m_position.Z ); CVector2D path_to = current->m_data[0].location; m_orderQueue.pop_front(); + if( ( path_to - pos ).length() < 0.1f ) + return( false ); if( m_actor->GetModel()->GetAnimation() != m_actor->GetObject()->m_WalkAnim ) { m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_WalkAnim ); diff --git a/source/simulation/PathfindEngine.h b/source/simulation/PathfindEngine.h index a80bb152db..ac6d558406 100755 --- a/source/simulation/PathfindEngine.h +++ b/source/simulation/PathfindEngine.h @@ -1,6 +1,6 @@ // PathfindEngine.h // -// Last modified: 28 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // The pathfinding engine singleton. // diff --git a/source/simulation/PathfindSparse.cpp b/source/simulation/PathfindSparse.cpp index 9c927c6fc0..d5f8e4d657 100755 --- a/source/simulation/PathfindSparse.cpp +++ b/source/simulation/PathfindSparse.cpp @@ -190,6 +190,9 @@ void pathSparse( HEntity entity, CVector2D destination ) { std::vector pathnodes; CVector2D source( entity->m_position.X, entity->m_position.Z ); + // Sanity check: + if( source.length() < 0.01f ) return; + sparsePathTree sparseEngine( source, destination, entity, getContainingObject( destination ) ); while( sparseEngine.type & sparsePathTree::SPF_OPEN ) sparseEngine.slice(); diff --git a/source/simulation/PathfindSparse.h b/source/simulation/PathfindSparse.h index d56e324a4b..9bdcae4590 100755 --- a/source/simulation/PathfindSparse.h +++ b/source/simulation/PathfindSparse.h @@ -1,6 +1,6 @@ // PathfindSparse.h // -// Last modified: 28 May 04, Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com // // Sparse pathfinder. // diff --git a/source/simulation/Scheduler.cpp b/source/simulation/Scheduler.cpp new file mode 100755 index 0000000000..e89f1bf552 --- /dev/null +++ b/source/simulation/Scheduler.cpp @@ -0,0 +1,108 @@ +#include "precompiled.h" +#include "Scheduler.h" +#include "Entity.h" + +extern size_t simulationTime; +extern size_t frameCount; + +void CScheduler::pushTime( size_t delay, const HEntity& destination, const CMessage* message ) +{ + timeMessage.push( SDispatchObjectMessage( destination, simulationTime + delay, message ) ); +} + +void CScheduler::pushFrame( size_t delay, const HEntity& destination, const CMessage* message ) +{ + frameMessage.push( SDispatchObjectMessage( destination, frameCount + delay, message ) ); +} + +void CScheduler::pushTime( size_t delay, const CStr16& fragment, JSObject* operateOn ) +{ + timeScript.push( SDispatchObjectScript( fragment, simulationTime + delay, operateOn ) ); +} + +void CScheduler::pushFrame( size_t delay, const CStr16& fragment, JSObject* operateOn ) +{ + frameScript.push( SDispatchObjectScript( fragment, frameCount + delay, operateOn ) ); +} + +void CScheduler::pushInterval( size_t first, size_t interval, const CStr16& fragment, JSObject* operateOn ) +{ + timeScript.push( SDispatchObjectScript( fragment, simulationTime + first, operateOn, interval ) ); +} + +void CScheduler::pushTime( size_t delay, JSFunction* script, JSObject* operateOn ) +{ + timeFunction.push( SDispatchObjectFunction( script, simulationTime + delay, operateOn ) ); +} + +void CScheduler::pushFrame( size_t delay, JSFunction* script, JSObject* operateOn ) +{ + frameFunction.push( SDispatchObjectFunction( script, frameCount + delay, operateOn ) ); +} + +void CScheduler::pushInterval( size_t first, size_t interval, JSFunction* function, JSObject* operateOn ) +{ + timeFunction.push( SDispatchObjectFunction( function, simulationTime + first, operateOn, interval ) ); +} + +void CScheduler::update() +{ + while( !timeMessage.empty() ) + { + SDispatchObjectMessage top = timeMessage.top(); + if( top.deliveryTime > simulationTime ) + break; + timeMessage.pop(); + top.destination->dispatch( top.message ); + delete( top.message ); + } + while( !frameMessage.empty() ) + { + SDispatchObjectMessage top = frameMessage.top(); + if( top.deliveryTime > frameCount ) + break; + frameMessage.pop(); + top.destination->dispatch( top.message ); + delete( top.message ); + } + while( !timeScript.empty() ) + { + SDispatchObjectScript top = timeScript.top(); + if( top.deliveryTime > simulationTime ) + break; + timeScript.pop(); + m_abortInterval = false; + g_ScriptingHost.ExecuteScript( top.script, CStr16( L"timer" ), top.operateOn ); + if( top.isRecurrent && !m_abortInterval ) + pushInterval( top.delay, top.delay, top.script, top.operateOn ); + } + while( !frameScript.empty() ) + { + SDispatchObjectScript top = frameScript.top(); + if( top.deliveryTime > frameCount ) + break; + frameScript.pop(); + g_ScriptingHost.ExecuteScript( top.script, CStr16( L"timer" ), top.operateOn ); + } + while( !timeFunction.empty() ) + { + SDispatchObjectFunction top = timeFunction.top(); + if( top.deliveryTime > simulationTime ) + break; + timeFunction.pop(); + jsval rval; + m_abortInterval = false; + JS_CallFunction( g_ScriptingHost.getContext(), top.operateOn, top.function, 0, NULL, &rval ); + if( top.isRecurrent && !m_abortInterval ) + pushInterval( top.delay, top.delay, top.function, top.operateOn ); + } + while( !frameFunction.empty() ) + { + SDispatchObjectFunction top = frameFunction.top(); + if( top.deliveryTime > frameCount ) + break; + frameFunction.pop(); + jsval rval; + JS_CallFunction( g_ScriptingHost.getContext(), top.operateOn, top.function, 0, NULL, &rval ); + } +} \ No newline at end of file diff --git a/source/simulation/Scheduler.h b/source/simulation/Scheduler.h new file mode 100755 index 0000000000..5b37cd39a1 --- /dev/null +++ b/source/simulation/Scheduler.h @@ -0,0 +1,85 @@ +// Scheduler.h +// +// Mark Thompson mot20@cam.ac.uk / mark@wildfiregames.com +// +// Message scheduler +// + +#ifndef SCHEDULER_INCLUDED +#define SCHEDULER_INCLUDED + +#include +#include "EntityMessage.h" +#include "EntityHandles.h" +#include "Singleton.h" +#include "CStr.h" +#include "scripting/ScriptingHost.h" + +// 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 SDispatchObjectMessage : public SDispatchObject +{ + HEntity destination; + const CMessage* message; + inline SDispatchObjectMessage( const HEntity& _destination, size_t _deliveryTime, const CMessage* _message ) + : SDispatchObject( _deliveryTime ), destination( _destination ), message( _message ) {} + inline SDispatchObjectMessage( const HEntity& _destination, size_t _deliveryTime, const CMessage* _message, const size_t recurrence ) + : SDispatchObject( _deliveryTime, recurrence ), destination( _destination ), message( _message ) {} + +}; + +struct SDispatchObjectScript : public SDispatchObject +{ + CStr16 script; + JSObject* operateOn; + inline SDispatchObjectScript( const CStr16& _script, const size_t _deliveryTime, JSObject* _operateOn = NULL ) + : SDispatchObject( _deliveryTime ), script( _script ), operateOn( _operateOn ) {} + inline SDispatchObjectScript( const CStr16& _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 timeMessage, frameMessage; + std::priority_queue timeScript, frameScript; + std::priority_queue timeFunction, frameFunction; + + bool m_abortInterval; + + void pushTime( size_t delay, const HEntity& destination, const CMessage* message ); + void pushFrame( size_t delay, const HEntity& destination, const CMessage* message ); + void pushTime( size_t delay, const CStr16& fragment, JSObject* operateOn = NULL ); + void pushFrame( size_t delay, const CStr16& fragment, JSObject* operateOn = NULL ); + void pushInterval( size_t first, size_t interval, const CStr16& 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 update(); +}; + +#define g_Scheduler CScheduler::GetSingleton() + +#endif \ No newline at end of file diff --git a/source/simulation/scripting/JSInterface_BaseEntity.cpp b/source/simulation/scripting/JSInterface_BaseEntity.cpp index 65fd5fb694..267e151817 100755 --- a/source/simulation/scripting/JSInterface_BaseEntity.cpp +++ b/source/simulation/scripting/JSInterface_BaseEntity.cpp @@ -31,9 +31,12 @@ JSBool JSI_BaseEntity::getProperty( JSContext* cx, JSObject* obj, jsval id, jsva if( e->m_properties.find( propName ) != e->m_properties.end() ) { - *vp = *(e->m_properties[propName]); + *vp = e->m_properties[propName]->tojsval(); return( JS_TRUE ); } + else + JS_ReportError( cx, "No such property on %s: %s", (const char*)e->m_name, (const char*)propName ); + return( JS_TRUE ); } @@ -48,6 +51,9 @@ JSBool JSI_BaseEntity::setProperty( JSContext* cx, JSObject* obj, jsval id, jsva e->rebuild( propName ); return( JS_TRUE ); } + else + JS_ReportError( cx, "No such property on %s: %s", (const char*)e->m_name, (const char*)propName ); + return( JS_TRUE ); } diff --git a/source/simulation/scripting/JSInterface_Entity.cpp b/source/simulation/scripting/JSInterface_Entity.cpp index daea7284d3..a07b933f97 100755 --- a/source/simulation/scripting/JSInterface_Entity.cpp +++ b/source/simulation/scripting/JSInterface_Entity.cpp @@ -35,15 +35,18 @@ JSBool JSI_Entity::getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* v if( !e ) { *vp = JSVAL_NULL; + JS_ReportError( cx, "[Entity] Invalid reference" ); return( JS_TRUE ); } CStr propName = g_ScriptingHost.ValueToString( id ); if( (*e)->m_properties.find( propName ) != (*e)->m_properties.end() ) { - *vp = *((*e)->m_properties[propName]); + *vp = (*e)->m_properties[propName]->tojsval(); return( JS_TRUE ); } + else + JS_ReportError( cx, "No such property on %s: %s", (const char*)((*e)->m_name), (const char*)propName ); return( JS_TRUE ); } @@ -58,6 +61,8 @@ JSBool JSI_Entity::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* v (*e)->rebuild( propName ); return( JS_TRUE ); } + else + JS_ReportError( cx, "No such property on %s: %s", (const char*)((*e)->m_name), (const char*)propName ); return( JS_TRUE ); } @@ -68,13 +73,14 @@ JSBool JSI_Entity::construct( JSContext* cx, JSObject* obj, unsigned int argc, j CVector3D position; float orientation = 0.0f; JSObject* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] ); - if( JSVAL_IS_OBJECT( argv[0] ) && ( JS_GetClass( jsBaseEntity ) == &JSI_BaseEntity::JSI_class ) ) + CStr templateName; + + if( !JSVAL_IS_NULL( argv[0] ) && JSVAL_IS_OBJECT( argv[0] ) && ( JS_GetClass( jsBaseEntity ) == &JSI_BaseEntity::JSI_class ) ) { baseEntity = (CBaseEntity*)JS_GetPrivate( cx, jsBaseEntity ); } else - { - CStr templateName; + { try { templateName = g_ScriptingHost.ValueToString( argv[0] ); @@ -82,6 +88,7 @@ JSBool JSI_Entity::construct( JSContext* cx, JSObject* obj, unsigned int argc, j catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; + JS_ReportError( cx, "Invalid template identifier" ); return( JS_TRUE ); } baseEntity = g_EntityTemplateCollection.getTemplate( templateName ); @@ -89,11 +96,14 @@ JSBool JSI_Entity::construct( JSContext* cx, JSObject* obj, unsigned int argc, j if( !baseEntity ) { *rval = JSVAL_NULL; + JS_ReportError( cx, "No such template: %s", (const char*)templateName ); return( JS_TRUE ); } JSObject* jsVector3D = JSVAL_TO_OBJECT( argv[1] ); - if( JSVAL_IS_OBJECT( argv[1] ) && ( JS_GetClass( jsVector3D ) == &JSI_Vector3D::JSI_class ) ) + if( !JSVAL_IS_NULL( argv[1] ) && JSVAL_IS_OBJECT( argv[1] ) && ( JS_GetClass( jsVector3D ) == &JSI_Vector3D::JSI_class ) ) + { position = *( ( (JSI_Vector3D::Vector3D_Info*)JS_GetPrivate( cx, jsVector3D ) )->vector ); + } if( argc >= 3 ) { try diff --git a/source/terrain/terrainMain.cpp b/source/terrain/terrainMain.cpp index 2792af3b35..b0ab8f8189 100755 --- a/source/terrain/terrainMain.cpp +++ b/source/terrain/terrainMain.cpp @@ -20,7 +20,7 @@ void RenderScene (); extern bool keys[512]; // SDL also defines non-ascii keys; 512 should be enough extern bool mouseButtons[5]; - +extern int mouse_x, mouse_y; extern bool g_active; @@ -32,16 +32,18 @@ CLightEnv g_LightEnv; float g_CameraZoom = 10; +std::vector cameraTargets; +CVector3D cameraDelta; + const float ViewScrollSpeed = 60; const float ViewRotateSensitivity = 0.002f; const float ViewDragSensitivity = 0.5f; const float ViewZoomSensitivityWheel = 16.0f; const float ViewZoomSensitivityKey = 256.0f; const float ViewZoomSmoothness = 0.02f; // 0.0 = instantaneous zooming, 1.0 = so slow it never moves +const float ViewSnapSmoothness = 0.02f; // Just the same. float ViewFOV; -int mouse_x=50, mouse_y=50; - extern int g_xres, g_yres; void terr_init() @@ -60,6 +62,9 @@ void terr_init() static void move_camera(float DeltaTime) { + float delta = powf( ViewSnapSmoothness, DeltaTime ); + g_Camera.m_Orientation.Translate( cameraDelta * ( 1.0f - delta ) ); + cameraDelta *= delta; #define CAMERASTYLE 2 // 0 = old style, 1 = relatively new style, 2 = newest style @@ -284,11 +289,6 @@ void terr_update(const float DeltaTime) move_camera(DeltaTime); } - - - - - int terr_handler(const SDL_Event* ev) { // put any events that must be processed even if inactive here @@ -298,11 +298,6 @@ int terr_handler(const SDL_Event* ev) switch(ev->type) { - case SDL_MOUSEMOTION: - mouse_x = ev->motion.x; - mouse_y = ev->motion.y; - break; - case SDL_KEYDOWN: switch(ev->key.keysym.sym) @@ -421,3 +416,4 @@ void InitResources() g_Renderer.LoadAlphaMaps(fns); } +