From aa50820d9a9d5db1d9ee12fcb5185fa798783c67 Mon Sep 17 00:00:00 2001 From: Matei Date: Sat, 9 Sep 2006 00:00:23 +0000 Subject: [PATCH] #Unit AI: Aggressive, defensive and stand ground stances. Also split Entity.cpp further, moving the rendering code to EntityRendering.cpp and the JS interface code to EntityScriptInterface.cpp. This makes each individual file smaller (about 1000 lines still!) and lets them be compiled independently. This was SVN commit r4314. --- source/simulation/Entity.cpp | 1397 +------------------ source/simulation/Entity.h | 40 +- source/simulation/EntityManager.cpp | 7 +- source/simulation/EntityManager.h | 1 + source/simulation/EntityOrders.h | 19 +- source/simulation/EntityRendering.cpp | 587 ++++++++ source/simulation/EntityScriptInterface.cpp | 883 ++++++++++++ source/simulation/EntityStateProcessing.cpp | 73 +- source/simulation/EntityTemplate.cpp | 1 + source/simulation/EntityTemplate.h | 2 + source/simulation/PathfindEngine.cpp | 9 +- source/simulation/PathfindEngine.h | 9 +- source/simulation/Stance.cpp | 168 +++ source/simulation/Stance.h | 114 ++ 14 files changed, 1919 insertions(+), 1391 deletions(-) create mode 100644 source/simulation/EntityRendering.cpp create mode 100644 source/simulation/EntityScriptInterface.cpp create mode 100644 source/simulation/Stance.cpp create mode 100644 source/simulation/Stance.h diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index 12d9ba394e..9e912bcdee 100644 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -27,6 +27,7 @@ #include "FormationManager.h" #include "PathfindEngine.h" #include "ProductionQueue.h" +#include "Stance.h" #include "TechnologyCollection.h" #include "TerritoryManager.h" @@ -50,6 +51,7 @@ CEntity::CEntity( CEntityTemplate* base, CVector3D position, float orientation, m_player = NULL; m_productionQueue = new CProductionQueue( this ); + m_stance = new CPassiveStance( this ); for( int t = 0; t < EVENT_LAST; t++ ) { @@ -126,6 +128,8 @@ CEntity::~CEntity() delete m_productionQueue; + delete m_stance; + for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) { delete it->second; @@ -350,6 +354,13 @@ void CEntity::update( size_t timestep ) m_lastRunTime = g_Game->GetTime(); } + if( m_orderQueue.empty() ) + { + // We are idle. Tell our stance in case it wants us to do something. + PROFILE( "unit ai" ); + m_stance->onIdle(); + } + while( !m_orderQueue.empty() ) { CEntityOrder* current = &m_orderQueue.front(); @@ -751,6 +762,8 @@ void CEntity::DispatchFormationEvent( int type ) void CEntity::repath() { CVector2D destination; + CEntityOrder::EOrderSource orderSource = CEntityOrder::SOURCE_PLAYER; + if( m_orderQueue.empty() ) return; @@ -760,9 +773,10 @@ void CEntity::repath() || ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_SMOOTHED ) ) ) { destination = m_orderQueue.front().m_data[0].location; + orderSource = m_orderQueue.front().m_source; m_orderQueue.pop_front(); } - g_Pathfinder.requestPath( me, destination ); + g_Pathfinder.requestPath( me, destination, orderSource ); } void CEntity::reorient() @@ -780,10 +794,34 @@ void CEntity::teleport() { m_position = m_graphics_position; m_bounds->setPosition( m_position.X, m_position.Z ); + updateActorTransforms(); updateCollisionPatch(); repath(); } +void CEntity::stanceChanged() +{ + delete m_stance; + m_stance = 0; + + if( m_stanceName == "aggress" ) + { + m_stance = new CAggressStance( this ); + } + else if( m_stanceName == "defend" ) + { + m_stance = new CDefendStance( this ); + } + else if( m_stanceName == "stand" ) + { + m_stance = new CStandStance( this ); + } + else + { + m_stance = new CPassiveStance( this ); + } +} + void CEntity::checkSelection() { if( m_selected ) @@ -861,90 +899,6 @@ void CEntity::invalidateActor() m_actor_transform_valid = false; } -void CEntity::render() -{ - if( !m_visible ) return; - - if( !m_orderQueue.empty() ) - { - std::deque::iterator it; - CBoundingObject* destinationCollisionObject; - float x0, y0, x, y; - - x = m_orderQueue.front().m_data[0].location.x; - y = m_orderQueue.front().m_data[0].location.y; - - for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) - { - if( it->m_type == CEntityOrder::ORDER_PATROL ) - break; - x = it->m_data[0].location.x; - y = it->m_data[0].location.y; - } - destinationCollisionObject = getContainingObject( CVector2D( x, y ) ); - - glShadeModel( GL_FLAT ); - glBegin( GL_LINE_STRIP ); - - glVertex3f( m_position.X, m_position.Y + 0.25f, m_position.Z ); - - x = m_position.X; - y = m_position.Z; - - for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) - { - x0 = x; - y0 = y; - x = it->m_data[0].location.x; - y = it->m_data[0].location.y; - rayIntersectionResults r; - CVector2D fwd( x - x0, y - y0 ); - float l = fwd.length(); - fwd = fwd.normalize(); - CVector2D rgt = fwd.beta(); - if( getRayIntersection( CVector2D( x0, y0 ), fwd, rgt, l, m_bounds->m_radius, destinationCollisionObject, &r ) ) - { - glEnd(); - glBegin( GL_LINES ); - glColor3f( 1.0f, 0.0f, 0.0f ); - glVertex3f( x0 + fwd.x * r.distance, getAnchorLevel( x0 + fwd.x * r.distance, y0 + fwd.y * r.distance ) + 0.25f, y0 + fwd.y * r.distance ); - glVertex3f( r.position.x, getAnchorLevel( r.position.x, r.position.y ) + 0.25f, r.position.y ); - glEnd(); - glBegin( GL_LINE_STRIP ); - glVertex3f( x0, getAnchorLevel( x0, y0 ), y0 ); - } - switch( it->m_type ) - { - case CEntityOrder::ORDER_GOTO: - glColor3f( 1.0f, 0.0f, 0.0f ); - break; - case CEntityOrder::ORDER_GOTO_COLLISION: - glColor3f( 1.0f, 0.5f, 0.5f ); - break; - case CEntityOrder::ORDER_GOTO_NOPATHING: - case CEntityOrder::ORDER_GOTO_SMOOTHED: - glColor3f( 0.5f, 0.5f, 0.5f ); - break; - case CEntityOrder::ORDER_PATROL: - glColor3f( 0.0f, 1.0f, 0.0f ); - break; - default: - continue; - } - - glVertex3f( x, getAnchorLevel( x, y ) + 0.25f, y ); - } - - glEnd(); - glShadeModel( GL_SMOOTH ); - } - - glColor3f( 1.0f, 1.0f, 1.0f ); - if( getCollisionObject( this ) ) - glColor3f( 0.5f, 0.5f, 1.0f ); - m_bounds->render( getAnchorLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f ); -} - float CEntity::getAnchorLevel( float x, float z ) { CTerrain *pTerrain = g_Game->GetWorld()->GetTerrain(); @@ -958,6 +912,7 @@ float CEntity::getAnchorLevel( float x, float z ) return max( groundLevel, g_Renderer.GetWaterManager()->m_WaterHeight ); } } + int CEntity::findSector( int divs, float angle, float maxAngle, bool negative ) { float step=maxAngle/divs; @@ -991,471 +946,6 @@ int CEntity::findSector( int divs, float angle, float maxAngle, bool negative ) debug_warn("CEntity::findSector() - invalid parameters passed."); return -1; } -void CEntity::renderSelectionOutline( float alpha ) -{ - if( !m_bounds || !m_visible ) - return; - - if( getCollisionObject( m_bounds, m_player, &m_base->m_socket ) ) - { - glColor4f( 1.0f, 0.5f, 0.5f, alpha ); // We're colliding with another unit; colour outline pink - } - else - { - const SPlayerColour& col = m_player->GetColour(); - glColor3f( col.r, col.g, col.b ); // Colour outline with player colour - } - - glBegin( GL_LINE_LOOP ); - - CVector3D pos = m_graphics_position; - - 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, getAnchorLevel( 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 d = ((CBoundingBox*)m_bounds)->m_d; - float w = ((CBoundingBox*)m_bounds)->m_w; - - u.x = sin( m_graphics_orientation.Y ); - u.y = cos( m_graphics_orientation.Y ); - 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 * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - } - - for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) - { - p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) - v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - } - - for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) - { - p = q - u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - } - - for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) - { - p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) + v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - } -#else - p = q + u * h + v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - - p = q + u * h - v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - - p = q - u * h + v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); - - p = q + u * h + v * w; - glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); -#endif - - - break; - } - } - - glEnd(); -} -void CEntity::renderAuras() -{ - if( !(m_bounds && m_visible && !m_auras.empty()) ) - return; - - const SPlayerColour& col = m_player->GetColour(); - glPushMatrix(); - glTranslatef(m_graphics_position.X, m_graphics_position.Y, - m_graphics_position.Z); - glBegin(GL_TRIANGLE_FAN); - glVertex3f(0.0f, getAnchorLevel(m_graphics_position.X, - m_graphics_position.Z)-m_graphics_position.Y+.5f, 0.0f); - size_t i=0; - - for ( AuraTable::iterator it=m_auras.begin(); it!=m_auras.end(); ++it, ++i ) - { - CVector4D color = it->second->m_color; - glColor4f(color.m_X, color.m_Y, color.m_Z, color.m_W); - -#ifdef SELECTION_TERRAIN_CONFORMANCE - //This starts to break when the radius is bigger - if ( it->second->m_radius < 15.0f ) - { - for ( int j=0; jsecond->m_radius < 15.0f ) - { - for ( int j=0; jGetView()->GetCamera(); - - float sx, sy; - CVector3D above; - above.X = m_position.X; - above.Z = m_position.Z; - above.Y = getAnchorLevel(m_position.X, m_position.Z) + height; - camera.GetScreenCoordinates(above, sx, sy); - return CVector2D( sx, sy ); -} - -void CEntity::drawRect( CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2 ) -{ - glBegin(GL_QUADS); - const int X[] = {1,1,0,0}; // which X and Y to choose at each vertex - const int Y[] = {0,1,1,0}; - for( int i=0; i<4; i++ ) - { - CVector3D vec = centre + right * (X[i] ? x1 : x2) + up * (Y[i] ? y1 : y2); - glTexCoord2f( X[i], Y[i] ); - glVertex3fv( &vec.X ); - } - glEnd(); -} - -void CEntity::drawBar( CVector3D& centre, CVector3D& up, CVector3D& right, - float x1, float y1, float x2, float y2, - SColour col1, SColour col2, float currVal, float maxVal ) -{ - // Figure out fraction that should be col1 - float fraction; - if(maxVal == 0) fraction = 1.0f; - else fraction = clamp( currVal / maxVal, 0.0f, 1.0f ); - - /*// Draw the border at full size - ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); - drawRect( centre, up, right, x1, y1, x2, y2 ); - ogl_tex_bind( 0 ); - - // Make the bar contents slightly smaller than the border - x1 += m_base->m_barBorderSize; - y1 += m_base->m_barBorderSize; - x2 -= m_base->m_barBorderSize; - y2 -= m_base->m_barBorderSize;*/ - - // Draw the bar contents - float xMid = x2 * fraction + x1 * (1.0f - fraction); - glColor3fv( &col1.r ); - drawRect( centre, up, right, x1, y1, xMid, y2 ); - glColor3fv( &col2.r ); - drawRect( centre, up, right, xMid, y1, x2, y2 ); -} - -void CEntity::renderBars() -{ - if( !m_base->m_barsEnabled || !m_bounds || !m_visible) - return; - - snapToGround(); - CVector3D centre = m_graphics_position; - centre.Y += m_base->m_barOffset; - CVector3D up = g_Game->GetView()->GetCamera()->m_Orientation.GetUp(); - CVector3D right = -g_Game->GetView()->GetCamera()->m_Orientation.GetLeft(); - - float w = m_base->m_barWidth; - float h = m_base->m_barHeight; - float borderSize = m_base->m_barBorderSize; - - // Draw the health and stamina bars; if the unit has no stamina, the health bar is - // drawn centered, otherwise it's offset slightly up and the stamina bar is offset - // slightly down so that they overlap over an area of size borderSize. - - bool hasStamina = (m_staminaMax > 0); - - float backgroundW = w+2*borderSize; - float backgroundH = hasStamina ? 2*h+2*borderSize : h+2*borderSize; - ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); - drawRect( centre, up, right, -backgroundW/2, -backgroundH/2, backgroundW/2, backgroundH/2 ); - ogl_tex_bind( 0 ); - - float off = hasStamina ? h/2 : 0; - drawBar( centre, up, right, -w/2, off-h/2, w/2, off+h/2, - SColour(0,1,0), SColour(1,0,0), m_healthCurr, m_healthMax ); - - if( hasStamina ) - { - drawBar( centre, up, right, -w/2, -h, w/2, 0, - SColour(0,0,1), SColour(0.4f,0.4f,0.1f), m_staminaCurr, m_staminaMax ); - } - - // Draw the rank icon - - std::map::iterator it = g_Selection.m_unitUITextures.find( m_rankName ); - if( it != g_Selection.m_unitUITextures.end() ) - { - float size = 2*h + borderSize; - ogl_tex_bind( it->second ); - drawRect( centre, up, right, w/2+borderSize, -size/2, w/2+borderSize+size, size/2 ); - ogl_tex_bind( 0 ); - } -} - -void CEntity::renderBarBorders() -{ - if( !m_visible ) - return; - - if ( m_base->m_staminaBarHeight >= 0 && - g_Selection.m_unitUITextures.find(m_base->m_healthBorderName) != g_Selection.m_unitUITextures.end() ) - { - ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_healthBorderName] ); - CVector2D pos = getScreenCoords( m_base->m_healthBarHeight ); - - float left = pos.x - m_base->m_healthBorderWidth/2; - float right = pos.x + m_base->m_healthBorderWidth/2; - pos.y = g_yres - pos.y; - float bottom = pos.y + m_base->m_healthBorderHeight/2; - float top = pos.y - m_base->m_healthBorderHeight/2; - - glBegin(GL_QUADS); - - glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); - glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); - glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); - glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); - - glEnd(); - } - if ( m_base->m_staminaBarHeight >= 0 && - g_Selection.m_unitUITextures.find(m_base->m_staminaBorderName) != g_Selection.m_unitUITextures.end() ) - { - ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_staminaBorderName] ); - - CVector2D pos = getScreenCoords( m_base->m_staminaBarHeight ); - float left = pos.x - m_base->m_staminaBorderWidth/2; - float right = pos.x + m_base->m_staminaBorderWidth/2; - pos.y = g_yres - pos.y; - float bottom = pos.y + m_base->m_staminaBorderHeight/2; - float top = pos.y - m_base->m_staminaBorderHeight/2; - - glBegin(GL_QUADS); - - glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); - glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); - glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); - glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); - - glEnd(); - } -} - -void CEntity::renderHealthBar() -{ - if( !m_bounds || !m_visible ) - return; - if( m_base->m_healthBarHeight < 0 ) - return; // negative bar height means don't display health bar - - float fraction; - if(m_healthMax == 0) fraction = 1.0f; - else fraction = clamp(m_healthCurr / m_healthMax, 0.0f, 1.0f); - - CVector2D pos = getScreenCoords( m_base->m_healthBarHeight ); - float x1 = pos.x - m_base->m_healthBarSize/2; - float x2 = pos.x + m_base->m_healthBarSize/2; - float y = g_yres - pos.y; - - glLineWidth( m_base->m_healthBarWidth ); - glBegin(GL_LINES); - - // green part of bar - glColor3f( 0, 1, 0 ); - glVertex3f( x1, y, 0 ); - glColor3f( 0, 1, 0 ); - glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); - - // red part of bar - glColor3f( 1, 0, 0 ); - glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); - glColor3f( 1, 0, 0 ); - glVertex3f( x2, y, 0 ); - - glEnd(); - - glLineWidth(1.0f); -} - -void CEntity::renderStaminaBar() -{ - if( !m_bounds || !m_visible ) - return; - if( m_base->m_staminaBarHeight < 0 ) - return; // negative bar height means don't display stamina bar - - float fraction; - if(m_staminaMax == 0) fraction = 1.0f; - else fraction = clamp(m_staminaCurr / m_staminaMax, 0.0f, 1.0f); - - CVector2D pos = getScreenCoords( m_base->m_staminaBarHeight ); - float x1 = pos.x - m_base->m_staminaBarSize/2; - float x2 = pos.x + m_base->m_staminaBarSize/2; - float y = g_yres - pos.y; - - glLineWidth( m_base->m_staminaBarWidth ); - glBegin(GL_LINES); - - // blue part of bar - glColor3f( 0.1f, 0.1f, 1 ); - glVertex3f( x1, y, 0 ); - glColor3f( 0.1f, 0.1f, 1 ); - glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); - - // purple part of bar - glColor3f( 0.3f, 0, 0.3f ); - glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); - glColor3f( 0.3f, 0, 0.3f ); - glVertex3f( x2, y, 0 ); - - glEnd(); - glLineWidth(1.0f); -} - -void CEntity::renderRank() -{ - if( !m_bounds || !m_visible ) - return; - if( m_base->m_rankHeight < 0 ) - return; // negative height means don't display rank - //Check for valid texture - if( g_Selection.m_unitUITextures.find( m_rankName ) == g_Selection.m_unitUITextures.end() ) - return; - - CCamera *camera = g_Game->GetView()->GetCamera(); - - float sx, sy; - CVector3D above; - above.X = m_position.X; - above.Z = m_position.Z; - above.Y = getAnchorLevel(m_position.X, m_position.Z) + m_base->m_rankHeight; - camera->GetScreenCoordinates(above, sx, sy); - int size = m_base->m_rankWidth/2; - - float x1 = sx + m_base->m_healthBarSize/2; - float x2 = sx + m_base->m_healthBarSize/2 + 2*size; - float y1 = g_yres - (sy - size); //top - float y2 = g_yres - (sy + size); //bottom - - ogl_tex_bind(g_Selection.m_unitUITextures[m_rankName]); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); - glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); - - glBegin(GL_QUADS); - - glTexCoord2f(1.0f, 0.0f); glVertex3f( x2, y2, 0 ); - glTexCoord2f(1.0f, 1.0f); glVertex3f( x2, y1, 0 ); - glTexCoord2f(0.0f, 1.0f); glVertex3f( x1, y1, 0 ); - glTexCoord2f(0.0f, 0.0f); glVertex3f( x1, y2, 0 ); - - glEnd(); -} - -void CEntity::renderRallyPoint() -{ - if( !m_visible ) - return; - - if ( !entf_get(ENTF_HAS_RALLY_POINT) || g_Selection.m_unitUITextures.find(m_base->m_rallyName) == - g_Selection.m_unitUITextures.end() ) - { - return; - } - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); - - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); - glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); - - CSprite sprite; - CTexture tex; - tex.SetHandle( g_Selection.m_unitUITextures[m_base->m_rallyName] ); - sprite.SetTexture(&tex); - CVector3D rally = m_rallyPoint; - rally.Y += m_base->m_rallyHeight/2.f + .1f; - sprite.SetTranslation(rally); - sprite.Render(); -} - static inline float regen(float cur, float limit, float timestep, float regen_rate) { @@ -1471,9 +961,6 @@ static inline float decay(float cur, float limit, float timestep, float decay_ra return std::max(0.0f, cur - timestep / 1000.0f * decay_rate * limit); } - - - void CEntity::CalculateRegen(float timestep) { // Health regen @@ -1490,808 +977,4 @@ void CEntity::CalculateRegen(float timestep) else if(m_orderQueue.empty()) m_staminaCurr = regen(m_staminaCurr, m_staminaMax, timestep, m_runRegenRate); } - -} - -/* - - Scripting interface - -*/ - -// Scripting initialization - -void CEntity::ScriptingInit() -{ - AddMethod( "toString", 0 ); - AddMethod( "order", 1 ); - AddMethod( "orderQueued", 1 ); - AddMethod( "terminateOrder", 1 ); - AddMethod( "kill", 0 ); - AddMethod( "isIdle", 0 ); - AddMethod( "hasClass", 1 ); - AddMethod( "getSpawnPoint", 1 ); - AddMethod( "addAura", 3 ); - AddMethod( "removeAura", 1 ); - AddMethod( "setActionParams", 5 ); - AddMethod( "getCurrentRequest", 0 ); - AddMethod( "forceCheckListeners", 2 ); - AddMethod( "requestNotification", 4 ); - AddMethod( "destroyNotifier", 1 ); - AddMethod( "destroyAllNotifiers", 0 ); - AddMethod( "triggerRun", 1 ); - AddMethod( "setRun", 1 ); - AddMethod( "getRunState", 0 ); - AddMethod( "isInFormation", 0 ); - AddMethod( "getFormationBonus", 0 ); - AddMethod( "getFormationBonusType", 0 ); - AddMethod( "getFormationBonusVal", 0 ); - AddMethod( "getFormationPenalty", 0 ); - AddMethod( "getFormationPenaltyType", 0 ); - AddMethod( "getFormationPenaltyVal", 0 ); - AddMethod( "registerDamage", 0 ); - AddMethod( "registerOrderChange", 0 ); - AddMethod( "getAttackDirections", 0 ); - AddMethod("findSector", 4); - AddMethod("getHeight", 0 ); - AddMethod("hasRallyPoint", 0 ); - AddMethod("setRallyPoint", 0 ); - AddMethod("getRallyPoint", 0 ); - - AddClassProperty( L"traits.id.classes", (GetFn)&CEntity::getClassSet, (SetFn)&CEntity::setClassSet ); - AddClassProperty( L"template", (CEntityTemplate* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase ); - - /* Any inherited property MUST be added to EntityTemplate.cpp as well */ - - AddClassProperty( L"actions.move.speedCurr", &CEntity::m_speed ); - AddClassProperty( L"actions.move.run.speed", &CEntity::m_runSpeed ); - AddClassProperty( L"actions.move.run.rangemin", &CEntity::m_runMinRange ); - AddClassProperty( L"actions.move.run.range", &CEntity::m_runMaxRange ); - AddClassProperty( L"actions.move.run.regenRate", &CEntity::m_runRegenRate ); - AddClassProperty( L"actions.move.run.decayRate", &CEntity::m_runDecayRate ); - AddClassProperty( L"selected", &CEntity::m_selected, false, (NotifyFn)&CEntity::checkSelection ); - AddClassProperty( L"group", &CEntity::m_grouped, false, (NotifyFn)&CEntity::checkGroup ); - AddClassProperty( L"traits.extant", &CEntity::m_extant ); - AddClassProperty( L"actions.move.turningRadius", &CEntity::m_turningRadius ); - AddClassProperty( L"position", &CEntity::m_graphics_position, false, (NotifyFn)&CEntity::teleport ); - AddClassProperty( L"orientation", &CEntity::m_orientation, false, (NotifyFn)&CEntity::reorient ); - AddClassProperty( L"player", (GetFn)&CEntity::JSI_GetPlayer, (SetFn)&CEntity::JSI_SetPlayer ); - AddClassProperty( L"traits.health.curr", &CEntity::m_healthCurr ); - AddClassProperty( L"traits.health.max", &CEntity::m_healthMax ); - AddClassProperty( L"traits.health.regenRate", &CEntity::m_healthRegenRate ); - AddClassProperty( L"traits.health.regenStart", &CEntity::m_healthRegenStart ); - AddClassProperty( L"traits.health.decayRate", &CEntity::m_healthDecayRate ); - AddClassProperty( L"traits.stamina.curr", &CEntity::m_staminaCurr ); - AddClassProperty( L"traits.stamina.max", &CEntity::m_staminaMax ); - AddClassProperty( L"traits.rank.name", &CEntity::m_rankName ); - AddClassProperty( L"traits.vision.los", &CEntity::m_los ); - AddClassProperty( L"lastCombatTime", &CEntity::m_lastCombatTime ); - AddClassProperty( L"lastRunTime", &CEntity::m_lastRunTime ); - AddClassProperty( L"building", &CEntity::m_building ); - AddClassProperty( L"visible", &CEntity::m_visible ); - AddClassProperty( L"productionQueue", &CEntity::m_productionQueue ); - - CJSComplex::ScriptingInit( "Entity", Construct, 2 ); -} - -// Script constructor - -JSBool CEntity::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) -{ - debug_assert( argc >= 2 ); - - CVector3D position; - float orientation = (float)( PI * ( (double)( rand() & 0x7fff ) / (double)0x4000 ) ); - - JSObject* jsEntityTemplate = JSVAL_TO_OBJECT( argv[0] ); - CStrW templateName; - - CPlayer* player = g_Game->GetPlayer( 0 ); - - CEntityTemplate* baseEntity = NULL; - if( JSVAL_IS_OBJECT( argv[0] ) ) // only set baseEntity if jsEntityTemplate is a valid object - baseEntity = ToNative( cx, jsEntityTemplate ); - - if( !baseEntity ) - { - try - { - templateName = g_ScriptingHost.ValueToUCString( argv[0] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - *rval = JSVAL_NULL; - JS_ReportError( cx, "Invalid template identifier" ); - return( JS_TRUE ); - } - baseEntity = g_EntityTemplateCollection.getTemplate( templateName ); - } - - if( !baseEntity ) - { - *rval = JSVAL_NULL; - JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() ); - return( JS_TRUE ); - } - - JSI_Vector3D::Vector3D_Info* jsVector3D = NULL; - if( JSVAL_IS_OBJECT( argv[1] ) ) - jsVector3D = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( argv[1] ), &JSI_Vector3D::JSI_class, NULL ); - - if( jsVector3D ) - { - position = *( jsVector3D->vector ); - } - - if( argc >= 3 ) - { - try - { - orientation = ToPrimitive( argv[2] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - // TODO: Net-safe random for this parameter. - orientation = 0.0f; - } - } - - if( argc >= 4 ) - { - try - { - player = ToPrimitive( argv[3] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - player = g_Game->GetPlayer( 0 ); - } - } - - std::set selections; // TODO: let scripts specify selections? - HEntity handle = g_EntityManager.create( baseEntity, position, orientation, selections ); - handle->m_actor->SetPlayerID( player->GetPlayerID() ); - handle->Initialize(); - - *rval = ToJSVal( *handle ); - return( JS_TRUE ); -} - -// Script-bound methods - -jsval CEntity::ToString( JSContext* cx, uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - wchar_t buffer[256]; - swprintf( buffer, 256, L"[object Entity: %ls]", m_base->m_Tag.c_str() ); - buffer[255] = 0; - utf16string str16(buffer, buffer+wcslen(buffer)); - return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, str16.c_str() ) ) ); -} - -jsval CEntity::JSI_GetPlayer() -{ - return ToJSVal( m_player ); -} - -void CEntity::JSI_SetPlayer( jsval val ) -{ - CPlayer* newPlayer = 0; - - try - { - newPlayer = ToPrimitive( val ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - JS_ReportError( g_ScriptingHost.getContext(), "Invalid value given to entity.player - should be a Player object." ); - return; - } - - // Cancel all production to refund the old player - m_productionQueue->CancelAll(); - - // Exit all our auras so we can re-enter them as the new player - for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ ) - { - (*it)->Remove( this ); - } - - if( m_actor ) - m_actor->SetPlayerID( newPlayer->GetPlayerID() ); // calls this->SetPlayer - else - SetPlayer(newPlayer); -} - -bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued ) -{ - // This needs to be sorted (uses Scheduler rather than network messaging) - - int orderCode; - debug_assert(argc >= 1); - try - { - orderCode = ToPrimitive( argv[0] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - JS_ReportError( cx, "Invalid order type" ); - return( false ); - } - - CEntityOrder newOrder; - CEntity* target; - - (int&)newOrder.m_type = orderCode; - - switch( orderCode ) - { - case CEntityOrder::ORDER_GOTO: - case CEntityOrder::ORDER_RUN: - case CEntityOrder::ORDER_PATROL: - JSU_REQUIRE_PARAMS_CPP(3); - try - { - newOrder.m_data[0].location.x = ToPrimitive( argv[1] ); - newOrder.m_data[0].location.y = ToPrimitive( argv[2] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - JS_ReportError( cx, "Invalid location" ); - return( false ); - } - if ( orderCode == CEntityOrder::ORDER_RUN ) - entf_set(ENTF_TRIGGER_RUN); - //It's not a notification order - if ( argc == 3 ) - { - if ( entf_get(ENTF_DESTROY_NOTIFIERS) ) - { - m_currentRequest=0; - DestroyAllNotifiers(); - } - } - break; - case CEntityOrder::ORDER_GENERIC: - JSU_REQUIRE_PARAMS_CPP(3); - target = ToNative( argv[1] ); - if( !target ) - { - JS_ReportError( cx, "Invalid target" ); - return( false ); - } - newOrder.m_data[0].entity = target->me; - try - { - newOrder.m_data[1].data = ToPrimitive( argv[2] ); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - JS_ReportError( cx, "Invalid generic order type" ); - return( false ); - } - //It's not a notification order - if ( argc == 3 ) - { - if ( entf_get(ENTF_DESTROY_NOTIFIERS) ) - { - m_currentRequest=0; - DestroyAllNotifiers(); - } - } - break; - case CEntityOrder::ORDER_PRODUCE: - JSU_REQUIRE_PARAMS_CPP(3); - try { - newOrder.m_data[0].string = ToPrimitive(argv[2]); - newOrder.m_data[1].data = ToPrimitive(argv[1]); - } - catch( PSERROR_Scripting_ConversionFailed ) - { - JS_ReportError( cx, "Invalid parameter types" ); - return( false ); - } - break; - default: - JS_ReportError( cx, "Invalid order type" ); - return( false ); - } - - if( !Queued ) - clearOrders(); - pushOrder( newOrder ); - - return( true ); -} - -bool CEntity::Kill( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - CEventDeath evt; - DispatchEvent( &evt ); - - for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) - { - it->second->RemoveAll(); - delete it->second; - } - m_auras.clear(); - - for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ ) - { - (*it)->Remove( this ); - } - m_aurasInfluencingMe.clear(); - - if( m_bounds ) - { - delete( m_bounds ); - m_bounds = NULL; - } - - if( m_extant ) - { - m_extant = false; - } - - updateCollisionPatch(); - - g_Selection.removeAll( me ); - - clearOrders(); - - g_EntityManager.SetDeath(true); - - - if( m_actor ) - { - m_actor->SetEntitySelection( "death" ); - m_actor->SetRandomAnimation( "death", true ); - } - - return( true ); -} - -jsval CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) -{ - float spawn_clearance = 2.0f; - if( argc >= 1 ) - { - CEntityTemplate* be = ToNative( argv[0] ); - if( be ) - { - switch( be->m_bound_type ) - { - case CBoundingObject::BOUND_CIRCLE: - spawn_clearance = be->m_bound_circle->m_radius; - break; - case CBoundingObject::BOUND_OABB: - spawn_clearance = be->m_bound_box->m_radius; - break; - default: - debug_warn("No bounding information for spawned object!" ); - } - } - else - spawn_clearance = ToPrimitive( argv[0] ); - } - else - debug_warn("No arguments to Entity::GetSpawnPoint()" ); - - // TODO: Make netsafe. - CBoundingCircle spawn( 0.0f, 0.0f, spawn_clearance, 0.0f ); - - if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) - { - CBoundingBox* oabb = (CBoundingBox*)m_bounds; - - // Pick a start point - - int edge = rand() & 3; - int point; - - double max_w = oabb->m_w + spawn_clearance + 1.0; - double max_d = oabb->m_d + spawn_clearance + 1.0; - int w_count = (int)( max_w * 2 ); - int d_count = (int)( max_d * 2 ); - - CVector2D w_step = oabb->m_v * (float)( max_w / w_count ); - CVector2D d_step = oabb->m_u * (float)( max_d / d_count ); - CVector2D pos( m_position ); - if( edge & 1 ) - { - point = rand() % ( 2 * d_count ) - d_count; - pos += ( oabb->m_v * (float)max_w + d_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); - } - else - { - point = rand() % ( 2 * w_count ) - w_count; - pos += ( oabb->m_u * (float)max_d + w_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); - } - - int start_edge = edge; - int start_point = point; - - spawn.m_pos = pos; - - // Then step around the edge (clockwise) until a free space is found, or - // we've gone all the way around. - while( getCollisionObject( &spawn ) ) - { - switch( edge ) - { - case 0: - point++; - pos += w_step; - if( point >= w_count ) - { - edge = 1; - point = -d_count; - } - break; - case 1: - point++; - pos -= d_step; - if( point >= d_count ) - { - edge = 2; - point = w_count; - } - break; - case 2: - point--; - pos -= w_step; - if( point <= -w_count ) - { - edge = 3; - point = d_count; - } - break; - case 3: - point--; - pos += d_step; - if( point <= -d_count ) - { - edge = 0; - point = -w_count; - } - break; - } - if( ( point == start_point ) && ( edge == start_edge ) ) - return( JSVAL_NULL ); - spawn.m_pos = pos; - } - CVector3D rval( pos.x, getAnchorLevel( pos.x, pos.y ), pos.y ); - return( ToJSVal( rval ) ); - } - else if( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE ) - { - float ang; - ang = (float)( rand() & 0x7fff ) / (float)0x4000; /* 0...2 */ - ang *= PI; - float radius = m_bounds->m_radius + 1.0f + spawn_clearance; - float d_ang = spawn_clearance / ( 2.0f * radius ); - float ang_end = ang + 2.0f * PI; - float x = 0.0f, y = 0.0f; // make sure they're initialized - for( ; ang < ang_end; ang += d_ang ) - { - x = m_position.X + radius * cos( ang ); - y = m_position.Z + radius * sin( ang ); - spawn.setPosition( x, y ); - if( !getCollisionObject( &spawn ) ) - break; - } - if( ang < ang_end ) - { - // Found a satisfactory position... - CVector3D pos( x, 0, y ); - pos.Y = getAnchorLevel( x, y ); - return( ToJSVal( pos ) ); - } - else - return( JSVAL_NULL ); - } - return( JSVAL_NULL ); -} - -jsval CEntity::AddAura( JSContext* cx, uintN argc, jsval* argv ) -{ - debug_assert( argc >= 8 ); - debug_assert( JSVAL_IS_OBJECT(argv[7]) ); - - CStrW name = ToPrimitive( argv[0] ); - float radius = ToPrimitive( argv[1] ); - size_t tickRate = max( 0, ToPrimitive( argv[2] ) ); // since it's a size_t we don't want it to be negative - float r = ToPrimitive( argv[3] ); - float g = ToPrimitive( argv[4] ); - float b = ToPrimitive( argv[5] ); - float a = ToPrimitive( argv[6] ); - CVector4D color(r, g, b, a); - JSObject* handler = JSVAL_TO_OBJECT( argv[7] ); - - if( m_auras[name] ) - { - delete m_auras[name]; - } - m_auras[name] = new CAura( cx, this, name, radius, tickRate, color, handler ); - - return JSVAL_VOID; -} - -jsval CEntity::RemoveAura( JSContext* UNUSED(cx), uintN argc, jsval* argv ) -{ - debug_assert( argc >= 1 ); - CStrW name = ToPrimitive( argv[0] ); - if( m_auras[name] ) - { - delete m_auras[name]; - m_auras.erase(name); - } - return JSVAL_VOID; -} - -jsval CEntity::SetActionParams( JSContext* UNUSED(cx), uintN argc, jsval* argv ) -{ - debug_assert( argc == 5 ); - - int id = ToPrimitive( argv[0] ); - float minRange = ToPrimitive( argv[1] ); - float maxRange = ToPrimitive( argv[2] ); - uint speed = ToPrimitive( argv[3] ); - CStr8 animation = ToPrimitive( argv[4] ); - - m_actions[id] = SEntityAction( minRange, maxRange, speed, animation ); - - return JSVAL_VOID; -} - -bool CEntity::RequestNotification( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(4); - - CEntityListener notify; - CEntity *target = ToNative( argv[0] ); - (int&)notify.m_type = ToPrimitive( argv[1] ); - bool tmpDestroyNotifiers = ToPrimitive( argv[2] ); - entf_set_to(ENTF_DESTROY_NOTIFIERS, !ToPrimitive( argv[3] )); - - if (target == this) - return false; - - notify.m_sender = this; - - //Clean up old requests - if ( tmpDestroyNotifiers ) - DestroyAllNotifiers(); - //If new request is not the same and we're destroy notifiers, reset - else if ( !(notify.m_type & m_currentRequest) && entf_get(ENTF_DESTROY_NOTIFIERS)) - DestroyAllNotifiers(); - - m_currentRequest = notify.m_type; - m_notifiers.push_back( target ); - int result = target->m_currentNotification & notify.m_type; - - //If our target isn't stationary and it's doing something we want to follow, send notification - if ( result && !target->m_orderQueue.empty() ) - { - CEntityOrder order = target->m_orderQueue.front(); - switch( result ) - { - case CEntityListener::NOTIFY_GOTO: - case CEntityListener::NOTIFY_RUN: - DispatchNotification( order, result ); - break; - - case CEntityListener::NOTIFY_HEAL: - case CEntityListener::NOTIFY_ATTACK: - case CEntityListener::NOTIFY_GATHER: - case CEntityListener::NOTIFY_DAMAGE: - DispatchNotification( order, result ); - break; - default: - JS_ReportError( cx, "Invalid order type" ); - break; - } - target->m_listeners.push_back( notify ); - return true; - } - - target->m_listeners.push_back( notify ); - return false; -} -int CEntity::GetCurrentRequest( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return m_currentRequest; -} -bool CEntity::ForceCheckListeners( JSContext *cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(2); - int type = ToPrimitive( argv[0] ); //notify code - m_currentNotification = type; - - CEntity *target = ToNative( argv[1] ); - if ( target->m_orderQueue.empty() ) - return false; - - CEntityOrder order = target->m_orderQueue.front(); - for (size_t i=0; iDispatchNotification( order, result ); - break; - - default: - JS_ReportError( cx, "Invalid order type" ); - break; - } - } - } - return true; -} -void CEntity::CheckListeners( int type, CEntity *target) -{ - m_currentNotification = type; - - debug_assert(target); - if ( target->m_orderQueue.empty() ) - return; - - CEntityOrder order = target->m_orderQueue.front(); - for (size_t i=0; iDispatchNotification( order, result ); - break; - default: - debug_warn("Invalid notification: CheckListeners()"); - continue; - } - } - } -} -jsval CEntity::DestroyAllNotifiers( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - DestroyAllNotifiers(); - return JS_TRUE; -} -jsval CEntity::DestroyNotifier( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(1); - DestroyNotifier( ToNative( argv[0] ) ); - return JS_TRUE; -} - -jsval CEntity::TriggerRun( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - entf_set(ENTF_TRIGGER_RUN); - return JSVAL_VOID; -} - -jsval CEntity::SetRun( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(1); - bool should_run = ToPrimitive ( argv[0] ); - entf_set_to(ENTF_SHOULD_RUN, should_run); - entf_set_to(ENTF_IS_RUNNING, should_run); - return JSVAL_VOID; -} -jsval CEntity::GetRunState( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return BOOLEAN_TO_JSVAL( entf_get(ENTF_SHOULD_RUN) ); -} -jsval CEntity::GetFormationPenalty( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetPenalty() ); -} -jsval CEntity::GetFormationPenaltyBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetPenaltyBase() ); -} -jsval CEntity::GetFormationPenaltyType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetPenaltyType() ); -} -jsval CEntity::GetFormationPenaltyVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetPenaltyVal() ); -} -jsval CEntity::GetFormationBonus( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetBonus() ); -} -jsval CEntity::GetFormationBonusBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetBonusBase() ); -} -jsval CEntity::GetFormationBonusType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetBonusType() ); -} -jsval CEntity::GetFormationBonusVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( GetFormation()->GetBase()->GetBonusVal() ); -} - -jsval CEntity::RegisterDamage( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(1); - CEntity* inflictor = ToNative( argv[0] ); - CVector2D up(1.0f, 0.0f); - CVector2D pos = CVector2D( inflictor->m_position.X, inflictor->m_position.Z ); - CVector2D posDelta = (pos - m_position).normalize(); - - float angle = acosf( up.dot(posDelta) ); - //Find what section it is between and "activate" it - int sector = findSector(m_base->m_sectorDivs, angle, DEGTORAD(360.0f))-1; - m_sectorValues[sector]=true; - return JS_TRUE; -} -jsval CEntity::RegisterOrderChange( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(1); - CEntity* idleEntity = ToNative( argv[0] ); - - CVector2D up(1.0f, 0.0f); - CVector2D pos = CVector2D( idleEntity->m_position.X, idleEntity->m_position.Z ); - CVector2D posDelta = (pos - m_position).normalize(); - - float angle = acosf( up.dot(posDelta) ); - //Find what section it is between and "deactivate" it - int sector = MAX( 0.0, findSector(m_base->m_sectorDivs, angle, DEGTORAD(360.0f)) ); - m_sectorValues[sector]=false; - return JS_TRUE; -} -jsval CEntity::GetAttackDirections( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - int directions=0; - - for ( std::vector::iterator it=m_sectorValues.begin(); it != m_sectorValues.end(); it++ ) - { - if ( *it ) - ++directions; - } - return ToJSVal( directions ); -} -jsval CEntity::FindSector( JSContext* cx, uintN argc, jsval* argv ) -{ - JSU_REQUIRE_PARAMS_CPP(4); - int divs = ToPrimitive( argv[0] ); - float angle = ToPrimitive( argv[1] ); - float maxAngle = ToPrimitive( argv[2] ); - bool negative = ToPrimitive( argv[3] ); - - return ToJSVal( findSector(divs, angle, maxAngle, negative) ); -} -jsval CEntity::HasRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( entf_get(ENTF_HAS_RALLY_POINT) ); -} -jsval CEntity::GetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return ToJSVal( m_rallyPoint ); -} -jsval CEntity::SetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - entf_set(ENTF_HAS_RALLY_POINT); - m_rallyPoint = g_Game->GetView()->GetCamera()->GetWorldCoordinates(); - return JS_TRUE; } diff --git a/source/simulation/Entity.h b/source/simulation/Entity.h index e37104205f..fe4b01e76c 100644 --- a/source/simulation/Entity.h +++ b/source/simulation/Entity.h @@ -46,9 +46,8 @@ class CProductionQueue; class CSkeletonAnim; class CUnit; class CTerritory; - class CEntityFormation; - +class CStance; // enum EntityFlags @@ -96,6 +95,9 @@ public: // Production queue CProductionQueue* m_productionQueue; + // Unit AI stance + CStance* m_stance; + // Movement properties float m_speed; float m_turningRadius; @@ -153,9 +155,12 @@ public: float m_healthCurr; float m_healthMax; - //Rank properties + // Rank properties CStr m_rankName; + // Stance name + CStr m_stanceName; + // LOS int m_los; @@ -226,7 +231,7 @@ private: uint processGotoHelper( CEntityOrder* current, size_t timestep_milli, HEntity& collide ); - bool processContactAction( CEntityOrder* current, size_t timestep_millis, int transition, SEntityAction* action ); + bool processContactAction( CEntityOrder* current, size_t timestep_millis, CEntityOrder::EOrderType transition, SEntityAction* action ); bool processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, const CStr& animation, CScriptEvent* contactEvent, SEntityAction* action ); bool processGeneric( CEntityOrder* current, size_t timestep_milli ); @@ -240,7 +245,7 @@ private: bool processPatrol( CEntityOrder* current, size_t timestep_milli ); - float processChooseMovement( float distance ); + float chooseMovementSpeed( float distance ); bool shouldRun( float distance ); // Given our distance to a target, can we be running? @@ -272,6 +277,25 @@ public: // Process tick. void Tick(); + // Calculate distances along the terrain + + float distance2D( float x, float z ) + { + float dx = x - m_position.X; + float dz = z - m_position.Z; + return sqrt( dx*dx + dz*dz ); + } + + float distance2D( CEntity* other ) + { + return distance2D( other->m_position.X, other->m_position.Z ); + } + + float distance2D( CVector2D p ) + { + return distance2D( p.x, p.y ); + } + private: // The player that owns this entity. // (Player is set publicly via CUnit::SetPlayerID) @@ -327,6 +351,7 @@ public: void reorient(); // Orientation void teleport(); // Fixes things if the position is changed by something externally. + void stanceChanged(); // Sets m_stance to the right CStance object when our stance property is changed through scripts void checkSelection(); // In case anyone tries to select/deselect this through JavaScript. void checkGroup(); // Groups void checkExtant(); // Existence @@ -382,6 +407,8 @@ public: jsval SetRun( JSContext* cx, uintN argc, jsval* argv ); jsval IsRunning( JSContext* cx, uintN argc, jsval* argv ); jsval GetRunState( JSContext* cx, uintN argc, jsval* argv ); + + jsval OnDamaged( JSContext* cx, uintN argc, jsval* argv ); bool RequestNotification( JSContext* cx, uintN argc, jsval* argv ); //Just in case we want to explicitly check the listeners without waiting for the order to be pushed @@ -434,6 +461,9 @@ public: } static void ScriptingInit(); + // Functions that call script code + int GetAttackAction( HEntity target ); + NO_COPY_CTOR(CEntity); }; diff --git a/source/simulation/EntityManager.cpp b/source/simulation/EntityManager.cpp index 022d833234..3bf9e5fbe1 100644 --- a/source/simulation/EntityManager.cpp +++ b/source/simulation/EntityManager.cpp @@ -190,7 +190,7 @@ void CEntityManager::GetInRange( float x, float z, float radius, std::vectorm_position.X; float dz = z - e->m_position.Z; - if(dx*dx + dz*dz <= radiusSq) + if( dx*dx + dz*dz <= radiusSq ) { results.push_back( e ); } @@ -199,6 +199,11 @@ void CEntityManager::GetInRange( float x, float z, float radius, std::vector& results ) +{ + GetInRange( entity->m_position.X, entity->m_position.Z, entity->m_los*CELL_SIZE, results ); +} + /* void CEntityManager::dispatchAll( CMessage* msg ) { diff --git a/source/simulation/EntityManager.h b/source/simulation/EntityManager.h index 5210de459d..65e2d78f59 100644 --- a/source/simulation/EntityManager.h +++ b/source/simulation/EntityManager.h @@ -108,6 +108,7 @@ public: } void GetInRange( float x, float z, float radius, std::vector& results ); + void GetInLOS( CEntity* entity, std::vector& results ); std::vector* getCollisionPatch( CEntity* e ); }; diff --git a/source/simulation/EntityOrders.h b/source/simulation/EntityOrders.h index a83c1952d1..2e9fc81428 100644 --- a/source/simulation/EntityOrders.h +++ b/source/simulation/EntityOrders.h @@ -76,12 +76,12 @@ public: CEntity* m_sender; }; - class CEntityOrder { public: - enum + enum EOrderType { + ORDER_INVALID, ORDER_GOTO_NOPATHING, ORDER_GOTO_SMOOTHED, ORDER_GOTO_COLLISION, @@ -97,9 +97,22 @@ public: ORDER_START_CONSTRUCTION, ORDER_NOTIFY_REQUEST, ORDER_LAST - } m_type; + }; + enum EOrderSource + { + SOURCE_PLAYER, + SOURCE_UNIT_AI + }; + + EOrderType m_type; + EOrderSource m_source; SOrderData m_data[ORDER_MAX_DATA]; + + CEntityOrder(): m_type(ORDER_INVALID), m_source(SOURCE_PLAYER) {} + + CEntityOrder(EOrderType type, EOrderSource source=SOURCE_PLAYER) + : m_type(type), m_source(source) {} }; #endif diff --git a/source/simulation/EntityRendering.cpp b/source/simulation/EntityRendering.cpp new file mode 100644 index 0000000000..e4394bae94 --- /dev/null +++ b/source/simulation/EntityRendering.cpp @@ -0,0 +1,587 @@ +#include "precompiled.h" + +#include "graphics/GameView.h" +#include "graphics/Model.h" +#include "graphics/Sprite.h" +#include "graphics/Terrain.h" +#include "graphics/Unit.h" +#include "graphics/UnitManager.h" +#include "maths/MathUtil.h" +#include "maths/scripting/JSInterface_Vector3D.h" +#include "ps/Game.h" +#include "ps/Interact.h" +#include "ps/Profile.h" +#include "renderer/Renderer.h" +#include "renderer/WaterManager.h" +#include "scripting/ScriptableComplex.inl" + +#include "Aura.h" +#include "Collision.h" +#include "Entity.h" +#include "EntityFormation.h" +#include "EntityManager.h" +#include "EntityTemplate.h" +#include "EntityTemplateCollection.h" +#include "EventHandlers.h" +#include "Formation.h" +#include "FormationManager.h" +#include "PathfindEngine.h" +#include "ProductionQueue.h" +#include "TechnologyCollection.h" +#include "TerritoryManager.h" + +extern int g_xres, g_yres; + +#include +using namespace std; + +void CEntity::render() +{ + if( !m_visible ) return; + + if( !m_orderQueue.empty() ) + { + std::deque::iterator it; + CBoundingObject* destinationCollisionObject; + float x0, y0, x, y; + + x = m_orderQueue.front().m_data[0].location.x; + y = m_orderQueue.front().m_data[0].location.y; + + for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) + { + if( it->m_type == CEntityOrder::ORDER_PATROL ) + break; + x = it->m_data[0].location.x; + y = it->m_data[0].location.y; + } + destinationCollisionObject = getContainingObject( CVector2D( x, y ) ); + + glShadeModel( GL_FLAT ); + glBegin( GL_LINE_STRIP ); + + glVertex3f( m_position.X, m_position.Y + 0.25f, m_position.Z ); + + x = m_position.X; + y = m_position.Z; + + for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) + { + x0 = x; + y0 = y; + x = it->m_data[0].location.x; + y = it->m_data[0].location.y; + rayIntersectionResults r; + CVector2D fwd( x - x0, y - y0 ); + float l = fwd.length(); + fwd = fwd.normalize(); + CVector2D rgt = fwd.beta(); + if( getRayIntersection( CVector2D( x0, y0 ), fwd, rgt, l, m_bounds->m_radius, destinationCollisionObject, &r ) ) + { + glEnd(); + glBegin( GL_LINES ); + glColor3f( 1.0f, 0.0f, 0.0f ); + glVertex3f( x0 + fwd.x * r.distance, getAnchorLevel( x0 + fwd.x * r.distance, y0 + fwd.y * r.distance ) + 0.25f, y0 + fwd.y * r.distance ); + glVertex3f( r.position.x, getAnchorLevel( r.position.x, r.position.y ) + 0.25f, r.position.y ); + glEnd(); + glBegin( GL_LINE_STRIP ); + glVertex3f( x0, getAnchorLevel( x0, y0 ), y0 ); + } + switch( it->m_type ) + { + case CEntityOrder::ORDER_GOTO: + glColor3f( 1.0f, 0.0f, 0.0f ); + break; + case CEntityOrder::ORDER_GOTO_COLLISION: + glColor3f( 1.0f, 0.5f, 0.5f ); + break; + case CEntityOrder::ORDER_GOTO_NOPATHING: + case CEntityOrder::ORDER_GOTO_SMOOTHED: + glColor3f( 0.5f, 0.5f, 0.5f ); + break; + case CEntityOrder::ORDER_PATROL: + glColor3f( 0.0f, 1.0f, 0.0f ); + break; + default: + continue; + } + + glVertex3f( x, getAnchorLevel( x, y ) + 0.25f, y ); + } + + glEnd(); + glShadeModel( GL_SMOOTH ); + } + + glColor3f( 1.0f, 1.0f, 1.0f ); + if( getCollisionObject( this ) ) + glColor3f( 0.5f, 0.5f, 1.0f ); + m_bounds->render( getAnchorLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f ); +} + +void CEntity::renderSelectionOutline( float alpha ) +{ + if( !m_bounds || !m_visible ) + return; + + if( getCollisionObject( m_bounds, m_player, &m_base->m_socket ) ) + { + glColor4f( 1.0f, 0.5f, 0.5f, alpha ); // We're colliding with another unit; colour outline pink + } + else + { + const SPlayerColour& col = m_player->GetColour(); + glColor3f( col.r, col.g, col.b ); // Colour outline with player colour + } + + glBegin( GL_LINE_LOOP ); + + CVector3D pos = m_graphics_position; + + 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, getAnchorLevel( 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 d = ((CBoundingBox*)m_bounds)->m_d; + float w = ((CBoundingBox*)m_bounds)->m_w; + + u.x = sin( m_graphics_orientation.Y ); + u.y = cos( m_graphics_orientation.Y ); + 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 * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) + { + p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) - v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) + { + p = q - u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + } + + for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) + { + p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) + v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + } +#else + p = q + u * h + v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q + u * h - v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q - u * h + v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); + + p = q + u * h + v * w; + glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); +#endif + + + break; + } + } + + glEnd(); +} + +void CEntity::renderAuras() +{ + if( !(m_bounds && m_visible && !m_auras.empty()) ) + return; + + const SPlayerColour& col = m_player->GetColour(); + glPushMatrix(); + glTranslatef(m_graphics_position.X, m_graphics_position.Y, + m_graphics_position.Z); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(0.0f, getAnchorLevel(m_graphics_position.X, + m_graphics_position.Z)-m_graphics_position.Y+.5f, 0.0f); + size_t i=0; + + for ( AuraTable::iterator it=m_auras.begin(); it!=m_auras.end(); ++it, ++i ) + { + CVector4D color = it->second->m_color; + glColor4f(color.m_X, color.m_Y, color.m_Z, color.m_W); + +#ifdef SELECTION_TERRAIN_CONFORMANCE + //This starts to break when the radius is bigger + if ( it->second->m_radius < 15.0f ) + { + for ( int j=0; jsecond->m_radius < 15.0f ) + { + for ( int j=0; jGetView()->GetCamera(); + + float sx, sy; + CVector3D above; + above.X = m_position.X; + above.Z = m_position.Z; + above.Y = getAnchorLevel(m_position.X, m_position.Z) + height; + camera.GetScreenCoordinates(above, sx, sy); + return CVector2D( sx, sy ); +} + +void CEntity::drawRect( CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2 ) +{ + glBegin(GL_QUADS); + const int X[] = {1,1,0,0}; // which X and Y to choose at each vertex + const int Y[] = {0,1,1,0}; + for( int i=0; i<4; i++ ) + { + CVector3D vec = centre + right * (X[i] ? x1 : x2) + up * (Y[i] ? y1 : y2); + glTexCoord2f( X[i], Y[i] ); + glVertex3fv( &vec.X ); + } + glEnd(); +} + +void CEntity::drawBar( CVector3D& centre, CVector3D& up, CVector3D& right, + float x1, float y1, float x2, float y2, + SColour col1, SColour col2, float currVal, float maxVal ) +{ + // Figure out fraction that should be col1 + float fraction; + if(maxVal == 0) fraction = 1.0f; + else fraction = clamp( currVal / maxVal, 0.0f, 1.0f ); + + /*// Draw the border at full size + ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); + drawRect( centre, up, right, x1, y1, x2, y2 ); + ogl_tex_bind( 0 ); + + // Make the bar contents slightly smaller than the border + x1 += m_base->m_barBorderSize; + y1 += m_base->m_barBorderSize; + x2 -= m_base->m_barBorderSize; + y2 -= m_base->m_barBorderSize;*/ + + // Draw the bar contents + float xMid = x2 * fraction + x1 * (1.0f - fraction); + glColor3fv( &col1.r ); + drawRect( centre, up, right, x1, y1, xMid, y2 ); + glColor3fv( &col2.r ); + drawRect( centre, up, right, xMid, y1, x2, y2 ); +} + +void CEntity::renderBars() +{ + if( !m_base->m_barsEnabled || !m_bounds || !m_visible) + return; + + snapToGround(); + CVector3D centre = m_graphics_position; + centre.Y += m_base->m_barOffset; + CVector3D up = g_Game->GetView()->GetCamera()->m_Orientation.GetUp(); + CVector3D right = -g_Game->GetView()->GetCamera()->m_Orientation.GetLeft(); + + float w = m_base->m_barWidth; + float h = m_base->m_barHeight; + float borderSize = m_base->m_barBorderSize; + + // Draw the health and stamina bars; if the unit has no stamina, the health bar is + // drawn centered, otherwise it's offset slightly up and the stamina bar is offset + // slightly down so that they overlap over an area of size borderSize. + + bool hasStamina = (m_staminaMax > 0); + + float backgroundW = w+2*borderSize; + float backgroundH = hasStamina ? 2*h+2*borderSize : h+2*borderSize; + ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); + drawRect( centre, up, right, -backgroundW/2, -backgroundH/2, backgroundW/2, backgroundH/2 ); + ogl_tex_bind( 0 ); + + float off = hasStamina ? h/2 : 0; + drawBar( centre, up, right, -w/2, off-h/2, w/2, off+h/2, + SColour(0,1,0), SColour(1,0,0), m_healthCurr, m_healthMax ); + + if( hasStamina ) + { + drawBar( centre, up, right, -w/2, -h, w/2, 0, + SColour(0,0,1), SColour(0.4f,0.4f,0.1f), m_staminaCurr, m_staminaMax ); + } + + // Draw the rank icon + + std::map::iterator it = g_Selection.m_unitUITextures.find( m_rankName ); + if( it != g_Selection.m_unitUITextures.end() ) + { + float size = 2*h + borderSize; + ogl_tex_bind( it->second ); + drawRect( centre, up, right, w/2+borderSize, -size/2, w/2+borderSize+size, size/2 ); + ogl_tex_bind( 0 ); + } +} + +void CEntity::renderBarBorders() +{ + if( !m_visible ) + return; + + if ( m_base->m_staminaBarHeight >= 0 && + g_Selection.m_unitUITextures.find(m_base->m_healthBorderName) != g_Selection.m_unitUITextures.end() ) + { + ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_healthBorderName] ); + CVector2D pos = getScreenCoords( m_base->m_healthBarHeight ); + + float left = pos.x - m_base->m_healthBorderWidth/2; + float right = pos.x + m_base->m_healthBorderWidth/2; + pos.y = g_yres - pos.y; + float bottom = pos.y + m_base->m_healthBorderHeight/2; + float top = pos.y - m_base->m_healthBorderHeight/2; + + glBegin(GL_QUADS); + + glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); + glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); + glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); + glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); + + glEnd(); + } + if ( m_base->m_staminaBarHeight >= 0 && + g_Selection.m_unitUITextures.find(m_base->m_staminaBorderName) != g_Selection.m_unitUITextures.end() ) + { + ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_staminaBorderName] ); + + CVector2D pos = getScreenCoords( m_base->m_staminaBarHeight ); + float left = pos.x - m_base->m_staminaBorderWidth/2; + float right = pos.x + m_base->m_staminaBorderWidth/2; + pos.y = g_yres - pos.y; + float bottom = pos.y + m_base->m_staminaBorderHeight/2; + float top = pos.y - m_base->m_staminaBorderHeight/2; + + glBegin(GL_QUADS); + + glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); + glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); + glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); + glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); + + glEnd(); + } +} + +void CEntity::renderHealthBar() +{ + if( !m_bounds || !m_visible ) + return; + if( m_base->m_healthBarHeight < 0 ) + return; // negative bar height means don't display health bar + + float fraction; + if(m_healthMax == 0) fraction = 1.0f; + else fraction = clamp(m_healthCurr / m_healthMax, 0.0f, 1.0f); + + CVector2D pos = getScreenCoords( m_base->m_healthBarHeight ); + float x1 = pos.x - m_base->m_healthBarSize/2; + float x2 = pos.x + m_base->m_healthBarSize/2; + float y = g_yres - pos.y; + + glLineWidth( m_base->m_healthBarWidth ); + glBegin(GL_LINES); + + // green part of bar + glColor3f( 0, 1, 0 ); + glVertex3f( x1, y, 0 ); + glColor3f( 0, 1, 0 ); + glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); + + // red part of bar + glColor3f( 1, 0, 0 ); + glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); + glColor3f( 1, 0, 0 ); + glVertex3f( x2, y, 0 ); + + glEnd(); + + glLineWidth(1.0f); +} + +void CEntity::renderStaminaBar() +{ + if( !m_bounds || !m_visible ) + return; + if( m_base->m_staminaBarHeight < 0 ) + return; // negative bar height means don't display stamina bar + + float fraction; + if(m_staminaMax == 0) fraction = 1.0f; + else fraction = clamp(m_staminaCurr / m_staminaMax, 0.0f, 1.0f); + + CVector2D pos = getScreenCoords( m_base->m_staminaBarHeight ); + float x1 = pos.x - m_base->m_staminaBarSize/2; + float x2 = pos.x + m_base->m_staminaBarSize/2; + float y = g_yres - pos.y; + + glLineWidth( m_base->m_staminaBarWidth ); + glBegin(GL_LINES); + + // blue part of bar + glColor3f( 0.1f, 0.1f, 1 ); + glVertex3f( x1, y, 0 ); + glColor3f( 0.1f, 0.1f, 1 ); + glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); + + // purple part of bar + glColor3f( 0.3f, 0, 0.3f ); + glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); + glColor3f( 0.3f, 0, 0.3f ); + glVertex3f( x2, y, 0 ); + + glEnd(); + glLineWidth(1.0f); +} + +void CEntity::renderRank() +{ + if( !m_bounds || !m_visible ) + return; + if( m_base->m_rankHeight < 0 ) + return; // negative height means don't display rank + //Check for valid texture + if( g_Selection.m_unitUITextures.find( m_rankName ) == g_Selection.m_unitUITextures.end() ) + return; + + CCamera *camera = g_Game->GetView()->GetCamera(); + + float sx, sy; + CVector3D above; + above.X = m_position.X; + above.Z = m_position.Z; + above.Y = getAnchorLevel(m_position.X, m_position.Z) + m_base->m_rankHeight; + camera->GetScreenCoordinates(above, sx, sy); + int size = m_base->m_rankWidth/2; + + float x1 = sx + m_base->m_healthBarSize/2; + float x2 = sx + m_base->m_healthBarSize/2 + 2*size; + float y1 = g_yres - (sy - size); //top + float y2 = g_yres - (sy + size); //bottom + + ogl_tex_bind(g_Selection.m_unitUITextures[m_rankName]); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); + + glBegin(GL_QUADS); + + glTexCoord2f(1.0f, 0.0f); glVertex3f( x2, y2, 0 ); + glTexCoord2f(1.0f, 1.0f); glVertex3f( x2, y1, 0 ); + glTexCoord2f(0.0f, 1.0f); glVertex3f( x1, y1, 0 ); + glTexCoord2f(0.0f, 0.0f); glVertex3f( x1, y2, 0 ); + + glEnd(); +} + +void CEntity::renderRallyPoint() +{ + if( !m_visible ) + return; + + if ( !entf_get(ENTF_HAS_RALLY_POINT) || g_Selection.m_unitUITextures.find(m_base->m_rallyName) == + g_Selection.m_unitUITextures.end() ) + { + return; + } + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); + + CSprite sprite; + CTexture tex; + tex.SetHandle( g_Selection.m_unitUITextures[m_base->m_rallyName] ); + sprite.SetTexture(&tex); + CVector3D rally = m_rallyPoint; + rally.Y += m_base->m_rallyHeight/2.f + .1f; + sprite.SetTranslation(rally); + sprite.Render(); +} + diff --git a/source/simulation/EntityScriptInterface.cpp b/source/simulation/EntityScriptInterface.cpp new file mode 100644 index 0000000000..6b41603d1d --- /dev/null +++ b/source/simulation/EntityScriptInterface.cpp @@ -0,0 +1,883 @@ +#include "precompiled.h" + +#include "graphics/GameView.h" +#include "graphics/Model.h" +#include "graphics/Sprite.h" +#include "graphics/Terrain.h" +#include "graphics/Unit.h" +#include "graphics/UnitManager.h" +#include "maths/MathUtil.h" +#include "maths/scripting/JSInterface_Vector3D.h" +#include "ps/Game.h" +#include "ps/Interact.h" +#include "ps/Profile.h" +#include "renderer/Renderer.h" +#include "renderer/WaterManager.h" +#include "scripting/ScriptableComplex.inl" + +#include "Aura.h" +#include "Collision.h" +#include "Entity.h" +#include "EntityFormation.h" +#include "EntityManager.h" +#include "EntityTemplate.h" +#include "EntityTemplateCollection.h" +#include "EventHandlers.h" +#include "Formation.h" +#include "FormationManager.h" +#include "PathfindEngine.h" +#include "ProductionQueue.h" +#include "TechnologyCollection.h" +#include "TerritoryManager.h" +#include "Stance.h" + +extern int g_xres, g_yres; + +#include +using namespace std; + +/* + + Scripting interface + +*/ + +// Scripting initialization + +void CEntity::ScriptingInit() +{ + AddMethod( "toString", 0 ); + AddMethod( "order", 1 ); + AddMethod( "orderQueued", 1 ); + AddMethod( "terminateOrder", 1 ); + AddMethod( "kill", 0 ); + AddMethod( "isIdle", 0 ); + AddMethod( "hasClass", 1 ); + AddMethod( "getSpawnPoint", 1 ); + AddMethod( "addAura", 3 ); + AddMethod( "removeAura", 1 ); + AddMethod( "setActionParams", 5 ); + AddMethod( "getCurrentRequest", 0 ); + AddMethod( "forceCheckListeners", 2 ); + AddMethod( "requestNotification", 4 ); + AddMethod( "destroyNotifier", 1 ); + AddMethod( "destroyAllNotifiers", 0 ); + AddMethod( "triggerRun", 1 ); + AddMethod( "setRun", 1 ); + AddMethod( "getRunState", 0 ); + AddMethod( "isInFormation", 0 ); + AddMethod( "getFormationBonus", 0 ); + AddMethod( "getFormationBonusType", 0 ); + AddMethod( "getFormationBonusVal", 0 ); + AddMethod( "getFormationPenalty", 0 ); + AddMethod( "getFormationPenaltyType", 0 ); + AddMethod( "getFormationPenaltyVal", 0 ); + AddMethod( "registerDamage", 0 ); + AddMethod( "registerOrderChange", 0 ); + AddMethod( "getAttackDirections", 0 ); + AddMethod( "findSector", 4); + AddMethod( "getHeight", 0 ); + AddMethod( "hasRallyPoint", 0 ); + AddMethod( "setRallyPoint", 0 ); + AddMethod( "getRallyPoint", 0 ); + AddMethod( "onDamaged", 1 ); + + AddClassProperty( L"traits.id.classes", (GetFn)&CEntity::getClassSet, (SetFn)&CEntity::setClassSet ); + AddClassProperty( L"template", (CEntityTemplate* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase ); + + /* Any inherited property MUST be added to EntityTemplate.cpp as well */ + + AddClassProperty( L"actions.move.speedCurr", &CEntity::m_speed ); + AddClassProperty( L"actions.move.run.speed", &CEntity::m_runSpeed ); + AddClassProperty( L"actions.move.run.rangemin", &CEntity::m_runMinRange ); + AddClassProperty( L"actions.move.run.range", &CEntity::m_runMaxRange ); + AddClassProperty( L"actions.move.run.regenRate", &CEntity::m_runRegenRate ); + AddClassProperty( L"actions.move.run.decayRate", &CEntity::m_runDecayRate ); + AddClassProperty( L"selected", &CEntity::m_selected, false, (NotifyFn)&CEntity::checkSelection ); + AddClassProperty( L"group", &CEntity::m_grouped, false, (NotifyFn)&CEntity::checkGroup ); + AddClassProperty( L"traits.extant", &CEntity::m_extant ); + AddClassProperty( L"actions.move.turningRadius", &CEntity::m_turningRadius ); + AddClassProperty( L"position", &CEntity::m_graphics_position, false, (NotifyFn)&CEntity::teleport ); + AddClassProperty( L"orientation", &CEntity::m_orientation, false, (NotifyFn)&CEntity::reorient ); + AddClassProperty( L"player", (GetFn)&CEntity::JSI_GetPlayer, (SetFn)&CEntity::JSI_SetPlayer ); + AddClassProperty( L"traits.health.curr", &CEntity::m_healthCurr ); + AddClassProperty( L"traits.health.max", &CEntity::m_healthMax ); + AddClassProperty( L"traits.health.regenRate", &CEntity::m_healthRegenRate ); + AddClassProperty( L"traits.health.regenStart", &CEntity::m_healthRegenStart ); + AddClassProperty( L"traits.health.decayRate", &CEntity::m_healthDecayRate ); + AddClassProperty( L"traits.stamina.curr", &CEntity::m_staminaCurr ); + AddClassProperty( L"traits.stamina.max", &CEntity::m_staminaMax ); + AddClassProperty( L"traits.rank.name", &CEntity::m_rankName ); + AddClassProperty( L"traits.vision.los", &CEntity::m_los ); + AddClassProperty( L"traits.ai.stance.curr", &CEntity::m_stanceName, false, (NotifyFn)&CEntity::stanceChanged ); + AddClassProperty( L"lastCombatTime", &CEntity::m_lastCombatTime ); + AddClassProperty( L"lastRunTime", &CEntity::m_lastRunTime ); + AddClassProperty( L"building", &CEntity::m_building ); + AddClassProperty( L"visible", &CEntity::m_visible ); + AddClassProperty( L"productionQueue", &CEntity::m_productionQueue ); + + CJSComplex::ScriptingInit( "Entity", Construct, 2 ); +} + +// Script constructor + +JSBool CEntity::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) +{ + debug_assert( argc >= 2 ); + + CVector3D position; + float orientation = (float)( PI * ( (double)( rand() & 0x7fff ) / (double)0x4000 ) ); + + JSObject* jsEntityTemplate = JSVAL_TO_OBJECT( argv[0] ); + CStrW templateName; + + CPlayer* player = g_Game->GetPlayer( 0 ); + + CEntityTemplate* baseEntity = NULL; + if( JSVAL_IS_OBJECT( argv[0] ) ) // only set baseEntity if jsEntityTemplate is a valid object + baseEntity = ToNative( cx, jsEntityTemplate ); + + if( !baseEntity ) + { + try + { + templateName = g_ScriptingHost.ValueToUCString( argv[0] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + *rval = JSVAL_NULL; + JS_ReportError( cx, "Invalid template identifier" ); + return( JS_TRUE ); + } + baseEntity = g_EntityTemplateCollection.getTemplate( templateName ); + } + + if( !baseEntity ) + { + *rval = JSVAL_NULL; + JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() ); + return( JS_TRUE ); + } + + JSI_Vector3D::Vector3D_Info* jsVector3D = NULL; + if( JSVAL_IS_OBJECT( argv[1] ) ) + jsVector3D = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( argv[1] ), &JSI_Vector3D::JSI_class, NULL ); + + if( jsVector3D ) + { + position = *( jsVector3D->vector ); + } + + if( argc >= 3 ) + { + try + { + orientation = ToPrimitive( argv[2] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + // TODO: Net-safe random for this parameter. + orientation = 0.0f; + } + } + + if( argc >= 4 ) + { + try + { + player = ToPrimitive( argv[3] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + player = g_Game->GetPlayer( 0 ); + } + } + + std::set selections; // TODO: let scripts specify selections? + HEntity handle = g_EntityManager.create( baseEntity, position, orientation, selections ); + handle->m_actor->SetPlayerID( player->GetPlayerID() ); + handle->Initialize(); + + *rval = ToJSVal( *handle ); + return( JS_TRUE ); +} + +// Script-bound methods + +jsval CEntity::ToString( JSContext* cx, uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + wchar_t buffer[256]; + swprintf( buffer, 256, L"[object Entity: %ls]", m_base->m_Tag.c_str() ); + buffer[255] = 0; + utf16string str16(buffer, buffer+wcslen(buffer)); + return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, str16.c_str() ) ) ); +} + +jsval CEntity::JSI_GetPlayer() +{ + return ToJSVal( m_player ); +} + +void CEntity::JSI_SetPlayer( jsval val ) +{ + CPlayer* newPlayer = 0; + + try + { + newPlayer = ToPrimitive( val ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( g_ScriptingHost.getContext(), "Invalid value given to entity.player - should be a Player object." ); + return; + } + + // Cancel all production to refund the old player + m_productionQueue->CancelAll(); + + // Exit all our auras so we can re-enter them as the new player + for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ ) + { + (*it)->Remove( this ); + } + + if( m_actor ) + m_actor->SetPlayerID( newPlayer->GetPlayerID() ); // calls this->SetPlayer + else + SetPlayer(newPlayer); +} + +bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued ) +{ + // This needs to be sorted (uses Scheduler rather than network messaging) + + int orderCode; + debug_assert(argc >= 1); + try + { + orderCode = ToPrimitive( argv[0] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( cx, "Invalid order type" ); + return( false ); + } + + CEntityOrder newOrder; + newOrder.m_source = CEntityOrder::SOURCE_PLAYER; + CEntity* target; + + (int&)newOrder.m_type = orderCode; + + switch( orderCode ) + { + case CEntityOrder::ORDER_GOTO: + case CEntityOrder::ORDER_RUN: + case CEntityOrder::ORDER_PATROL: + JSU_REQUIRE_PARAMS_CPP(3); + try + { + newOrder.m_data[0].location.x = ToPrimitive( argv[1] ); + newOrder.m_data[0].location.y = ToPrimitive( argv[2] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( cx, "Invalid location" ); + return( false ); + } + if ( orderCode == CEntityOrder::ORDER_RUN ) + entf_set(ENTF_TRIGGER_RUN); + //It's not a notification order + if ( argc == 3 ) + { + if ( entf_get(ENTF_DESTROY_NOTIFIERS) ) + { + m_currentRequest=0; + DestroyAllNotifiers(); + } + } + break; + case CEntityOrder::ORDER_GENERIC: + JSU_REQUIRE_PARAMS_CPP(3); + target = ToNative( argv[1] ); + if( !target ) + { + JS_ReportError( cx, "Invalid target" ); + return( false ); + } + newOrder.m_data[0].entity = target->me; + try + { + newOrder.m_data[1].data = ToPrimitive( argv[2] ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( cx, "Invalid generic order type" ); + return( false ); + } + //It's not a notification order + if ( argc == 3 ) + { + if ( entf_get(ENTF_DESTROY_NOTIFIERS) ) + { + m_currentRequest=0; + DestroyAllNotifiers(); + } + } + break; + case CEntityOrder::ORDER_PRODUCE: + JSU_REQUIRE_PARAMS_CPP(3); + try { + newOrder.m_data[0].string = ToPrimitive(argv[2]); + newOrder.m_data[1].data = ToPrimitive(argv[1]); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( cx, "Invalid parameter types" ); + return( false ); + } + break; + default: + JS_ReportError( cx, "Invalid order type" ); + return( false ); + } + + if( !Queued ) + clearOrders(); + pushOrder( newOrder ); + + return( true ); +} + +bool CEntity::Kill( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + CEventDeath evt; + DispatchEvent( &evt ); + + for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) + { + it->second->RemoveAll(); + delete it->second; + } + m_auras.clear(); + + for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ ) + { + (*it)->Remove( this ); + } + m_aurasInfluencingMe.clear(); + + if( m_bounds ) + { + delete( m_bounds ); + m_bounds = NULL; + } + + if( m_extant ) + { + m_extant = false; + } + + updateCollisionPatch(); + + g_Selection.removeAll( me ); + + clearOrders(); + + g_EntityManager.SetDeath(true); + + + if( m_actor ) + { + m_actor->SetEntitySelection( "death" ); + m_actor->SetRandomAnimation( "death", true ); + } + + return( true ); +} + +jsval CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) +{ + float spawn_clearance = 2.0f; + if( argc >= 1 ) + { + CEntityTemplate* be = ToNative( argv[0] ); + if( be ) + { + switch( be->m_bound_type ) + { + case CBoundingObject::BOUND_CIRCLE: + spawn_clearance = be->m_bound_circle->m_radius; + break; + case CBoundingObject::BOUND_OABB: + spawn_clearance = be->m_bound_box->m_radius; + break; + default: + debug_warn("No bounding information for spawned object!" ); + } + } + else + spawn_clearance = ToPrimitive( argv[0] ); + } + else + debug_warn("No arguments to Entity::GetSpawnPoint()" ); + + // TODO: Make netsafe. + CBoundingCircle spawn( 0.0f, 0.0f, spawn_clearance, 0.0f ); + + if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) + { + CBoundingBox* oabb = (CBoundingBox*)m_bounds; + + // Pick a start point + + int edge = rand() & 3; + int point; + + double max_w = oabb->m_w + spawn_clearance + 1.0; + double max_d = oabb->m_d + spawn_clearance + 1.0; + int w_count = (int)( max_w * 2 ); + int d_count = (int)( max_d * 2 ); + + CVector2D w_step = oabb->m_v * (float)( max_w / w_count ); + CVector2D d_step = oabb->m_u * (float)( max_d / d_count ); + CVector2D pos( m_position ); + if( edge & 1 ) + { + point = rand() % ( 2 * d_count ) - d_count; + pos += ( oabb->m_v * (float)max_w + d_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); + } + else + { + point = rand() % ( 2 * w_count ) - w_count; + pos += ( oabb->m_u * (float)max_d + w_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); + } + + int start_edge = edge; + int start_point = point; + + spawn.m_pos = pos; + + // Then step around the edge (clockwise) until a free space is found, or + // we've gone all the way around. + while( getCollisionObject( &spawn ) ) + { + switch( edge ) + { + case 0: + point++; + pos += w_step; + if( point >= w_count ) + { + edge = 1; + point = -d_count; + } + break; + case 1: + point++; + pos -= d_step; + if( point >= d_count ) + { + edge = 2; + point = w_count; + } + break; + case 2: + point--; + pos -= w_step; + if( point <= -w_count ) + { + edge = 3; + point = d_count; + } + break; + case 3: + point--; + pos += d_step; + if( point <= -d_count ) + { + edge = 0; + point = -w_count; + } + break; + } + if( ( point == start_point ) && ( edge == start_edge ) ) + return( JSVAL_NULL ); + spawn.m_pos = pos; + } + CVector3D rval( pos.x, getAnchorLevel( pos.x, pos.y ), pos.y ); + return( ToJSVal( rval ) ); + } + else if( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE ) + { + float ang; + ang = (float)( rand() & 0x7fff ) / (float)0x4000; /* 0...2 */ + ang *= PI; + float radius = m_bounds->m_radius + 1.0f + spawn_clearance; + float d_ang = spawn_clearance / ( 2.0f * radius ); + float ang_end = ang + 2.0f * PI; + float x = 0.0f, y = 0.0f; // make sure they're initialized + for( ; ang < ang_end; ang += d_ang ) + { + x = m_position.X + radius * cos( ang ); + y = m_position.Z + radius * sin( ang ); + spawn.setPosition( x, y ); + if( !getCollisionObject( &spawn ) ) + break; + } + if( ang < ang_end ) + { + // Found a satisfactory position... + CVector3D pos( x, 0, y ); + pos.Y = getAnchorLevel( x, y ); + return( ToJSVal( pos ) ); + } + else + return( JSVAL_NULL ); + } + return( JSVAL_NULL ); +} + +jsval CEntity::AddAura( JSContext* cx, uintN argc, jsval* argv ) +{ + debug_assert( argc >= 8 ); + debug_assert( JSVAL_IS_OBJECT(argv[7]) ); + + CStrW name = ToPrimitive( argv[0] ); + float radius = ToPrimitive( argv[1] ); + size_t tickRate = max( 0, ToPrimitive( argv[2] ) ); // since it's a size_t we don't want it to be negative + float r = ToPrimitive( argv[3] ); + float g = ToPrimitive( argv[4] ); + float b = ToPrimitive( argv[5] ); + float a = ToPrimitive( argv[6] ); + CVector4D color(r, g, b, a); + JSObject* handler = JSVAL_TO_OBJECT( argv[7] ); + + if( m_auras[name] ) + { + delete m_auras[name]; + } + m_auras[name] = new CAura( cx, this, name, radius, tickRate, color, handler ); + + return JSVAL_VOID; +} + +jsval CEntity::RemoveAura( JSContext* UNUSED(cx), uintN argc, jsval* argv ) +{ + debug_assert( argc >= 1 ); + CStrW name = ToPrimitive( argv[0] ); + if( m_auras[name] ) + { + delete m_auras[name]; + m_auras.erase(name); + } + return JSVAL_VOID; +} + +jsval CEntity::SetActionParams( JSContext* UNUSED(cx), uintN argc, jsval* argv ) +{ + debug_assert( argc == 5 ); + + int id = ToPrimitive( argv[0] ); + float minRange = ToPrimitive( argv[1] ); + float maxRange = ToPrimitive( argv[2] ); + uint speed = ToPrimitive( argv[3] ); + CStr8 animation = ToPrimitive( argv[4] ); + + m_actions[id] = SEntityAction( minRange, maxRange, speed, animation ); + + return JSVAL_VOID; +} + +bool CEntity::RequestNotification( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(4); + + CEntityListener notify; + CEntity *target = ToNative( argv[0] ); + (int&)notify.m_type = ToPrimitive( argv[1] ); + bool tmpDestroyNotifiers = ToPrimitive( argv[2] ); + entf_set_to(ENTF_DESTROY_NOTIFIERS, !ToPrimitive( argv[3] )); + + if (target == this) + return false; + + notify.m_sender = this; + + //Clean up old requests + if ( tmpDestroyNotifiers ) + DestroyAllNotifiers(); + //If new request is not the same and we're destroy notifiers, reset + else if ( !(notify.m_type & m_currentRequest) && entf_get(ENTF_DESTROY_NOTIFIERS)) + DestroyAllNotifiers(); + + m_currentRequest = notify.m_type; + m_notifiers.push_back( target ); + int result = target->m_currentNotification & notify.m_type; + + //If our target isn't stationary and it's doing something we want to follow, send notification + if ( result && !target->m_orderQueue.empty() ) + { + CEntityOrder order = target->m_orderQueue.front(); + switch( result ) + { + case CEntityListener::NOTIFY_GOTO: + case CEntityListener::NOTIFY_RUN: + DispatchNotification( order, result ); + break; + + case CEntityListener::NOTIFY_HEAL: + case CEntityListener::NOTIFY_ATTACK: + case CEntityListener::NOTIFY_GATHER: + case CEntityListener::NOTIFY_DAMAGE: + DispatchNotification( order, result ); + break; + default: + JS_ReportError( cx, "Invalid order type" ); + break; + } + target->m_listeners.push_back( notify ); + return true; + } + + target->m_listeners.push_back( notify ); + return false; +} +int CEntity::GetCurrentRequest( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return m_currentRequest; +} +bool CEntity::ForceCheckListeners( JSContext *cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(2); + int type = ToPrimitive( argv[0] ); //notify code + m_currentNotification = type; + + CEntity *target = ToNative( argv[1] ); + if ( target->m_orderQueue.empty() ) + return false; + + CEntityOrder order = target->m_orderQueue.front(); + for (size_t i=0; iDispatchNotification( order, result ); + break; + + default: + JS_ReportError( cx, "Invalid order type" ); + break; + } + } + } + return true; +} +void CEntity::CheckListeners( int type, CEntity *target) +{ + m_currentNotification = type; + + debug_assert(target); + if ( target->m_orderQueue.empty() ) + return; + + CEntityOrder order = target->m_orderQueue.front(); + for (size_t i=0; iDispatchNotification( order, result ); + break; + default: + debug_warn("Invalid notification: CheckListeners()"); + continue; + } + } + } +} +jsval CEntity::DestroyAllNotifiers( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + DestroyAllNotifiers(); + return JS_TRUE; +} +jsval CEntity::DestroyNotifier( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(1); + DestroyNotifier( ToNative( argv[0] ) ); + return JS_TRUE; +} + +jsval CEntity::TriggerRun( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + entf_set(ENTF_TRIGGER_RUN); + return JSVAL_VOID; +} + +jsval CEntity::SetRun( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(1); + bool should_run = ToPrimitive ( argv[0] ); + entf_set_to(ENTF_SHOULD_RUN, should_run); + entf_set_to(ENTF_IS_RUNNING, should_run); + return JSVAL_VOID; +} +jsval CEntity::GetRunState( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return BOOLEAN_TO_JSVAL( entf_get(ENTF_SHOULD_RUN) ); +} +jsval CEntity::GetFormationPenalty( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetPenalty() ); +} +jsval CEntity::GetFormationPenaltyBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetPenaltyBase() ); +} +jsval CEntity::GetFormationPenaltyType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetPenaltyType() ); +} +jsval CEntity::GetFormationPenaltyVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetPenaltyVal() ); +} +jsval CEntity::GetFormationBonus( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetBonus() ); +} +jsval CEntity::GetFormationBonusBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetBonusBase() ); +} +jsval CEntity::GetFormationBonusType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetBonusType() ); +} +jsval CEntity::GetFormationBonusVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( GetFormation()->GetBase()->GetBonusVal() ); +} + +jsval CEntity::RegisterDamage( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(1); + CEntity* inflictor = ToNative( argv[0] ); + CVector2D up(1.0f, 0.0f); + CVector2D pos = CVector2D( inflictor->m_position.X, inflictor->m_position.Z ); + CVector2D posDelta = (pos - m_position).normalize(); + + float angle = acosf( up.dot(posDelta) ); + //Find what section it is between and "activate" it + int sector = findSector(m_base->m_sectorDivs, angle, DEGTORAD(360.0f))-1; + m_sectorValues[sector]=true; + return JS_TRUE; +} +jsval CEntity::RegisterOrderChange( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(1); + CEntity* idleEntity = ToNative( argv[0] ); + + CVector2D up(1.0f, 0.0f); + CVector2D pos = CVector2D( idleEntity->m_position.X, idleEntity->m_position.Z ); + CVector2D posDelta = (pos - m_position).normalize(); + + float angle = acosf( up.dot(posDelta) ); + //Find what section it is between and "deactivate" it + int sector = MAX( 0.0, findSector(m_base->m_sectorDivs, angle, DEGTORAD(360.0f)) ); + m_sectorValues[sector]=false; + return JS_TRUE; +} +jsval CEntity::GetAttackDirections( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + int directions=0; + + for ( std::vector::iterator it=m_sectorValues.begin(); it != m_sectorValues.end(); it++ ) + { + if ( *it ) + ++directions; + } + return ToJSVal( directions ); +} +jsval CEntity::FindSector( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(4); + + try + { + int divs = ToPrimitive( argv[0] ); + float angle = ToPrimitive( argv[1] ); + float maxAngle = ToPrimitive( argv[2] ); + bool negative = ToPrimitive( argv[3] ); + + return ToJSVal( findSector(divs, angle, maxAngle, negative) ); + } + catch( PSERROR_Scripting_ConversionFailed ) + { + JS_ReportError( cx, "Invalid parameters for findSector" ); + return 0; + } +} +jsval CEntity::HasRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( entf_get(ENTF_HAS_RALLY_POINT) ); +} +jsval CEntity::GetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return ToJSVal( m_rallyPoint ); +} +jsval CEntity::SetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + entf_set(ENTF_HAS_RALLY_POINT); + m_rallyPoint = g_Game->GetView()->GetCamera()->GetWorldCoordinates(); + return JS_TRUE; +} + +jsval CEntity::OnDamaged( JSContext* cx, uintN argc, jsval* argv ) +{ + JSU_REQUIRE_PARAMS_CPP(1); + CEntity* damageSource = ToNative( argv[0] ); + m_stance->onDamaged( damageSource ); + return JSVAL_VOID; +} + +/* + +Methods that provide an interface from C++ to JavaScript functions. + +*/ + +int CEntity::GetAttackAction( HEntity target ) +{ + jsval attackFunc; + if( GetProperty( g_ScriptingHost.GetContext(), L"getAttackAction", &attackFunc ) ) + { + jsval arg = ToJSVal( target ); + jsval rval; + if( JS_CallFunctionValue( g_ScriptingHost.GetContext(), GetScript(), attackFunc, 1, &arg, &rval ) == JS_TRUE ) + { + return ToPrimitive( rval ); + } + } + + // Default return value is an invalid action ID + return 0; +} diff --git a/source/simulation/EntityStateProcessing.cpp b/source/simulation/EntityStateProcessing.cpp index b06fcd177a..55ed3f0729 100644 --- a/source/simulation/EntityStateProcessing.cpp +++ b/source/simulation/EntityStateProcessing.cpp @@ -16,6 +16,7 @@ #include "PathfindEngine.h" #include "LOSManager.h" #include "graphics/Terrain.h" +#include "Stance.h" #include "ps/Game.h" #include "ps/World.h" @@ -29,7 +30,8 @@ enum EGotoSituation COLLISION_NEAR_DESTINATION, COLLISION_OVERLAPPING_OBJECTS, COLLISION_OTHER, - WOULD_LEAVE_MAP + WOULD_LEAVE_MAP, + STANCE_DISALLOWS }; bool CEntity::shouldRun(float distance) @@ -51,7 +53,7 @@ bool CEntity::shouldRun(float distance) return true; } -float CEntity::processChooseMovement( float distance ) +float CEntity::chooseMovementSpeed( float distance ) { bool should_run = shouldRun(distance); @@ -98,7 +100,7 @@ uint CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis, // Curve smoothing. // Here there be trig. - float scale = processChooseMovement( len ) * timestep; + float scale = chooseMovementSpeed( len ) * timestep; // Note: Easy optimization: flag somewhere that this unit // is already pointing the way, and don't do this @@ -245,6 +247,17 @@ uint CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis, return( WOULD_LEAVE_MAP ); } + // Does our stance not allow us to go there? + if( current->m_source==CEntityOrder::SOURCE_UNIT_AI && !m_stance->checkMovement( m_position ) ) + { + m_position.X -= delta.x; + m_position.Z -= delta.y; + if( m_bounds ) + m_bounds->setPosition( m_position.X, m_position.Z ); + + return( STANCE_DISALLOWS ); + } + // No. I suppose it's OK to go there, then. *disappointed* return( rc ); @@ -342,6 +355,9 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli //entf_clear(ENTF_IS_RUNNING); //entf_clear(ENTF_SHOULD_RUN); return( false ); + case STANCE_DISALLOWS: + return( false ); // The stance will have cleared our order queue already + default: return( false ); @@ -349,7 +365,7 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli } // Handles processing common to (at the moment) gather and melee attack actions -bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timestep_millis), int transition, SEntityAction* action ) +bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timestep_millis), CEntityOrder::EOrderType transition, SEntityAction* action ) { HEntity target = current->m_data[0].entity; @@ -360,23 +376,31 @@ bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timeste return false; current->m_data[0].location = target->m_position; - float Distance = (current->m_data[0].location - m_position).length(); + float Distance = distance2D(current->m_data[0].location); if( Distance < action->m_MaxRange ) { - (int&)current->m_type = transition; + current->m_type = transition; entf_clear(ENTF_IS_RUNNING); - return( true ); + return true; } - - processChooseMovement( Distance ); + else + { + if( current->m_source == CEntityOrder::SOURCE_UNIT_AI && !m_stance->allowsMovement() ) + { + popOrder(); + return false; // We're not allowed to move at all by the current stance + } - // The pathfinder will push its result back into this unit's queue and - // add back the current order at the end with the transition type. - (int&)current->m_type = transition; - g_Pathfinder.requestContactPath( me, current, action->m_MaxRange ); + chooseMovementSpeed( Distance ); - return( true ); + // The pathfinder will push its result back into this unit's queue and + // add back the current order at the end with the transition type. + current->m_type = transition; + g_Pathfinder.requestContactPath( me, current, action->m_MaxRange ); + + return true; + } } bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, const CStr& animation, CScriptEvent* contactEvent, SEntityAction* action ) { @@ -446,7 +470,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times return( false ); } - CVector2D delta = target->m_position - m_position; + CVector2D delta = CVector2D(target->m_position) - CVector2D(m_position); float deltaLength = delta.length(); float adjRange = action->m_MaxRange + m_bounds->m_radius + target->m_bounds->m_radius; @@ -465,12 +489,18 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times if( !delta.within( adjRange ) ) { - // Too far away at the moment, chase after the target... + // Too far away at the moment, chase after the target if allowed... + if( current->m_source == CEntityOrder::SOURCE_UNIT_AI && !m_stance->allowsMovement() ) + { + popOrder(); + return false; + } + // We're aiming to end up at a location just inside our maximum range // (is this good enough?) delta = delta.normalize() * ( adjRange - m_bounds->m_radius ); - processChooseMovement(deltaLength); + chooseMovementSpeed(deltaLength); current->m_data[0].location = (CVector2D)target->m_position - delta; @@ -481,6 +511,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times case REACHED_DESTINATION: case COLLISION_WITH_DESTINATION: case WOULD_LEAVE_MAP: + case STANCE_DISALLOWS: // Not too far any more... break; case NORMAL: @@ -618,11 +649,11 @@ bool CEntity::processGoto( CEntityOrder* current, size_t UNUSED(timestep_millis) return( false ); } - processChooseMovement( Distance ); + chooseMovementSpeed( Distance ); // The pathfinder will push its result back into this unit's queue. - g_Pathfinder.requestPath( me, path_to ); + g_Pathfinder.requestPath( me, path_to, current->m_source ); return( true ); } @@ -642,10 +673,10 @@ bool CEntity::processGotoWaypoint( CEntityOrder* current, size_t UNUSED(timestep return( false ); } - processChooseMovement( Distance ); + chooseMovementSpeed( Distance ); float radius = *((float*)¤t->m_data[0].data); - g_Pathfinder.requestLowLevelPath( me, path_to, contact, radius ); + g_Pathfinder.requestLowLevelPath( me, path_to, contact, radius, current->m_source ); return( true ); } diff --git a/source/simulation/EntityTemplate.cpp b/source/simulation/EntityTemplate.cpp index 5051e0ba3b..6184fb7e16 100644 --- a/source/simulation/EntityTemplate.cpp +++ b/source/simulation/EntityTemplate.cpp @@ -425,6 +425,7 @@ void CEntityTemplate::ScriptingInit() AddClassProperty( L"traits.rank.width", &CEntityTemplate::m_rankWidth ); AddClassProperty( L"traits.rank.height", &CEntityTemplate::m_rankHeight ); AddClassProperty( L"traits.rank.name", &CEntityTemplate::m_rankName ); + AddClassProperty( L"traits.ai.stance.curr", &CEntityTemplate::m_stanceName ); AddClassProperty( L"traits.miniMap.type", &CEntityTemplate::m_minimapType ); AddClassProperty( L"traits.miniMap.red", &CEntityTemplate::m_minimapR ); AddClassProperty( L"traits.miniMap.green", &CEntityTemplate::m_minimapG ); diff --git a/source/simulation/EntityTemplate.h b/source/simulation/EntityTemplate.h index 7287e9551e..e1a73ff94b 100644 --- a/source/simulation/EntityTemplate.h +++ b/source/simulation/EntityTemplate.h @@ -100,6 +100,8 @@ public: float m_rankWidth; float m_rankHeight; + // Stance name + CStr m_stanceName; //Rank properties CStr m_rankName; diff --git a/source/simulation/PathfindEngine.cpp b/source/simulation/PathfindEngine.cpp index 1496b05e87..b2385b2da9 100644 --- a/source/simulation/PathfindEngine.cpp +++ b/source/simulation/PathfindEngine.cpp @@ -12,7 +12,8 @@ CPathfindEngine::CPathfindEngine() { } -void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination ) +void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination, + CEntityOrder::EOrderSource orderSource ) { /* TODO: Add code to generate high level path For now, just the one high level waypoint to the final @@ -20,12 +21,14 @@ void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination */ CEntityOrder waypoint; waypoint.m_type = CEntityOrder::ORDER_GOTO_WAYPOINT; + waypoint.m_source = orderSource; waypoint.m_data[0].location = destination; *((float*)&waypoint.m_data[0].data) = 0.0f; entity->m_orderQueue.push_front( waypoint ); } -void CPathfindEngine::requestLowLevelPath( HEntity entity, const CVector2D& destination, bool UNUSED(contact), float radius ) +void CPathfindEngine::requestLowLevelPath( HEntity entity, const CVector2D& destination, bool UNUSED(contact), + float radius, CEntityOrder::EOrderSource orderSource ) { PROFILE_START("Pathfinding"); @@ -40,6 +43,7 @@ void CPathfindEngine::requestLowLevelPath( HEntity entity, const CVector2D& dest // so that we run through it before continuing other orders. CEntityOrder node; + node.m_source = orderSource; // Hack to make pathfinding slightly more precise: // If the radius was 0, make the final node be exactly at the destination @@ -89,6 +93,7 @@ void CPathfindEngine::requestContactPath( HEntity entity, CEntityOrder* current, /* TODO: Same as non-contact: need high-level planner */ CEntityOrder waypoint; waypoint.m_type = CEntityOrder::ORDER_GOTO_WAYPOINT_CONTACT; + waypoint.m_source = current->m_source; waypoint.m_data[0].location = current->m_data[0].entity->m_position; *((float*)&waypoint.m_data[0].data) = std::max( current->m_data[0].entity->m_bounds->m_radius, range ); entity->m_orderQueue.push_front( waypoint ); diff --git a/source/simulation/PathfindEngine.h b/source/simulation/PathfindEngine.h index 49b8f54d02..94276035ac 100644 --- a/source/simulation/PathfindEngine.h +++ b/source/simulation/PathfindEngine.h @@ -31,8 +31,13 @@ class CPathfindEngine : public Singleton { public: CPathfindEngine(); - void requestPath( HEntity entity, const CVector2D& destination ); - void requestLowLevelPath( HEntity entity, const CVector2D& destination, bool contact, float radius ); + + void requestPath( HEntity entity, const CVector2D& destination, + CEntityOrder::EOrderSource orderSource ); + + void requestLowLevelPath( HEntity entity, const CVector2D& destination, bool contact, + float radius, CEntityOrder::EOrderSource orderSource ); + void requestContactPath( HEntity entity, CEntityOrder* current, float range ); private: CAStarEngineLowLevel mLowPathfinder; diff --git a/source/simulation/Stance.cpp b/source/simulation/Stance.cpp new file mode 100644 index 0000000000..d0047c2a85 --- /dev/null +++ b/source/simulation/Stance.cpp @@ -0,0 +1,168 @@ +#include "precompiled.h" + +#include "Stance.h" + +#include "EntityManager.h" +#include "Entity.h" +#include "ps/Player.h" +#include "graphics/Terrain.h" + +#include + +// AggressStance //////////////////////////////////////////////////// + +void CAggressStance::onIdle() +{ + CEntity* target = CStanceUtils::chooseTarget( m_Entity ); + if( target ) + CStanceUtils::attack( m_Entity, target ); +} + +void CAggressStance::onDamaged(CEntity *source) +{ + if( source && m_Entity->m_orderQueue.empty() ) + CStanceUtils::attack( m_Entity, source ); +} + +// StandStance ////////////////////////////////////////////////////// + +void CStandStance::onIdle() +{ + CEntity* target = CStanceUtils::chooseTarget( m_Entity ); + if( target ) + CStanceUtils::attack( m_Entity, target ); +} + +// DefendStance ///////////////////////////////////////////////////// + +void CDefendStance::onIdle() +{ + idlePos = CVector2D( m_Entity->m_position.X, m_Entity->m_position.Z ); + + CEntity* target = CStanceUtils::chooseTarget( m_Entity ); + if( target ) + CStanceUtils::attack( m_Entity, target ); +} + +void CDefendStance::onDamaged(CEntity *source) +{ + if( source && m_Entity->m_orderQueue.empty() ) + { + // Retaliate only if we can reach the enemy unit without walking farther than our LOS + // radius away from idlePos. + int action = m_Entity->GetAttackAction( source->me ); + if( action ) + { + float range = m_Entity->m_actions[action].m_MaxRange; + if( ( range + m_Entity->m_los * CELL_SIZE ) >= m_Entity->distance2D( source ) ) + { + CEntityOrder order( CEntityOrder::ORDER_GENERIC, CEntityOrder::SOURCE_UNIT_AI ); + order.m_data[0].entity = source->me; + order.m_data[1].data = action; + m_Entity->pushOrder( order ); + } + } + } +} + +bool CDefendStance::checkMovement( CVector2D proposedPos ) +{ + float los = m_Entity->m_los*CELL_SIZE; + + // Check that we haven't moved too far from the place where we were stationed. + if( (proposedPos - idlePos).length() > los ) + { + // TODO: Make sure we don't clear any player orders here; the best way would be to make + // shift-clicked player orders either unqueue any AI orders or convert those AI orders + // to player orders (since the player wants us to finish our attack then do other stuff). + m_Entity->m_orderQueue.clear(); + + // Try to find some other nearby enemy to attack, provided it's also in range of our idle spot + // TODO: really we should be attack-moving to our spot somehow + + std::vector results; + g_EntityManager.GetInRange( m_Entity->m_position.X, m_Entity->m_position.Z, los, results ); + + float bestDist = 1e20f; + CEntity* bestTarget = 0; + + for( size_t i=0; im_actions[m_Entity->GetAttackAction(ent->me)].m_MaxRange; + if( m_Entity->GetPlayer()->GetDiplomaticStance( ent->GetPlayer() ) == DIPLOMACY_ENEMY ) + { + float distToMe = ent->distance2D( m_Entity ); + float distToIdlePos = ent->distance2D( idlePos ); + if( distToIdlePos <= los+range && distToMe < bestDist ) + { + bestDist = distToMe; + bestTarget = ent; + } + } + } + + if( bestTarget != 0 ) + { + CStanceUtils::attack( m_Entity, bestTarget ); + } + else + { + // Let's just walk back to our idle spot + CEntityOrder order( CEntityOrder::ORDER_GOTO, CEntityOrder::SOURCE_UNIT_AI ); + order.m_data[0].location = idlePos; + m_Entity->pushOrder( order ); + } + + return false; + } + else + { + return true; + } +} + +// StanceUtils ////////////////////////////////////////////////////// + +void CStanceUtils::attack(CEntity* entity, CEntity* target) +{ + int action = entity->GetAttackAction( target->me ); + if( action ) + { + CEntityOrder order( CEntityOrder::ORDER_GENERIC, CEntityOrder::SOURCE_UNIT_AI ); + order.m_data[0].entity = target->me; + order.m_data[1].data = action; + entity->pushOrder( order ); + } +} + +CEntity* CStanceUtils::chooseTarget( CEntity* entity ) +{ + return chooseTarget( entity->m_position.X, entity->m_position.Z, entity->m_los*CELL_SIZE, entity->GetPlayer() ); +} + +CEntity* CStanceUtils::chooseTarget( float x, float z, float radius, CPlayer* myPlayer ) +{ + std::vector results; + g_EntityManager.GetInRange( x, z, radius, results ); + + float bestDist = 1e20f; + CEntity* bestTarget = 0; + + for( size_t i=0; iGetDiplomaticStance( ent->GetPlayer() ) == DIPLOMACY_ENEMY ) + { + float dist = ent->distance2D( x, z ); + if( dist < bestDist ) + { + bestDist = dist; + bestTarget = ent; + } + } + } + + return bestTarget; +} + diff --git a/source/simulation/Stance.h b/source/simulation/Stance.h new file mode 100644 index 0000000000..e483ffebb7 --- /dev/null +++ b/source/simulation/Stance.h @@ -0,0 +1,114 @@ +#ifndef __STANCE_H__ +#define __STANCE_H__ + +#include "EntityHandles.h" +#include "ps/Vector2D.h" + +class CEntity; +class CPlayer; + +/** + * A combat stance. This object is given various events by an entity and can choose what the + * entity will do when it does not have any player orders. + **/ +class CStance +{ +protected: + CEntity* m_Entity; +public: + CStance(CEntity* ent): m_Entity(ent) {} + virtual ~CStance() {} + + // Called each tick if the unit is idle. + virtual void onIdle() = 0; + + // Called when the unit is damaged. Source might be NULL for damage from "acts of Gaia" + // (but we get notified anyway in case we want to run around in fear). Most stances will + // probably want to retaliate here. + virtual void onDamaged(CEntity* source) = 0; + + // Does this stance allow movement at all during AI control? + virtual bool allowsMovement() = 0; + + // Called when the unit is under AI control and wishes to move to the given position; + // the stance may choose to cancel if it is too far. + virtual bool checkMovement(CVector2D proposedPos) = 0; +}; + +/** + * Passive stance: Unit never attacks, not even to fight back. + **/ +class CPassiveStance : public CStance +{ +public: + CPassiveStance(CEntity* ent): CStance(ent) {}; + virtual ~CPassiveStance() {}; + virtual void onIdle() {}; + virtual void onDamaged(CEntity* UNUSED(source)) {}; + virtual bool allowsMovement() { return false; }; + virtual bool checkMovement(CVector2D UNUSED(proposedPos)) { return false; } +}; + +/** + * Aggressive stance: The unit will attack any enemy it sees and pursue it indefinitely. + **/ +class CAggressStance : public CStance +{ +public: + CAggressStance(CEntity* ent): CStance(ent) {}; + virtual ~CAggressStance() {}; + virtual void onIdle(); + virtual void onDamaged(CEntity* source); + virtual bool allowsMovement() { return true; }; + virtual bool checkMovement(CVector2D UNUSED(proposedPos)) { return true; } +}; + +/** + * Stand Ground stance: The unit will attack enemies in LOS but never move. + **/ +class CStandStance : public CStance +{ +public: + CStandStance(CEntity* ent): CStance(ent) {}; + virtual ~CStandStance() {}; + virtual void onIdle(); + virtual void onDamaged(CEntity* UNUSED(source)) {}; // Empty because onIdle will ensure we're attacking stuff in LOS + virtual bool allowsMovement() { return false; }; + virtual bool checkMovement(CVector2D UNUSED(proposedPos)) { return false; } +}; + +/** + * Defensive stance: The unit will attack enemies but will never pursue them further + * than its LOS away from its original position (the point where it last became idle). + **/ +class CDefendStance : public CStance +{ + CVector2D idlePos; +public: + CDefendStance(CEntity* ent): CStance(ent) {}; + virtual ~CDefendStance() {}; + virtual void onIdle(); + virtual void onDamaged(CEntity* source); + virtual bool allowsMovement() { return true; }; + virtual bool checkMovement(CVector2D proposedPos); +}; + +/** + * Utility functions used by the various stances. + **/ +class CStanceUtils +{ +private: + CStanceUtils() {}; +public: + // Attacks the given target using the appropriate attack action (obtained from JavaScript). + static void attack(CEntity* entity, CEntity* target); + + // Picks a visible entity to attack. + static CEntity* chooseTarget(CEntity* entity); + + // Picks a visible entity within the given circle to attack + static CEntity* chooseTarget(float x, float y, float radius, CPlayer* player); +}; + +#endif