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