#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 "ps/scripting/JSCollection.h"

#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"

#include <algorithm>

extern int g_xres, g_yres;

/*

 Scripting interface

*/

// Scripting initialization

void CEntity::ScriptingInit()
{
    // TODO: lots of these return jsval when they should return proper types
    // and make use of automatic type conversion
    AddMethod<jsval_t, &CEntity::ToString>( "toString", 0 );
    AddMethod<bool, &CEntity::OrderSingle>( "order", 1 );
    AddMethod<bool, &CEntity::OrderQueued>( "orderQueued", 1 );
    AddMethod<bool, &CEntity::OrderFromTriggers>( "orderFromTriggers", 1 );
    AddMethod<jsval_t, &CEntity::TerminateOrder>( "terminateOrder", 1 );
    AddMethod<bool, &CEntity::Kill>( "kill", 0 );
    AddMethod<bool, &CEntity::IsIdle>( "isIdle", 0 );
    AddMethod<bool, &CEntity::HasClass>( "hasClass", 1 );
    AddMethod<jsval_t, &CEntity::GetSpawnPoint>( "getSpawnPoint", 1 );
    AddMethod<jsval_t, &CEntity::AddAura>( "addAura", 3 );
    AddMethod<jsval_t, &CEntity::RemoveAura>( "removeAura", 1 );
    AddMethod<jsval_t, &CEntity::SetActionParams>( "setActionParams", 5 );
    AddMethod<int, &CEntity::GetCurrentRequest>( "getCurrentRequest", 0 );
    AddMethod<bool, &CEntity::ForceCheckListeners>( "forceCheckListeners", 2 );
    AddMethod<bool, &CEntity::RequestNotification>( "requestNotification", 4 );
    AddMethod<jsval_t, &CEntity::DestroyNotifier>( "destroyNotifier", 1 );
    AddMethod<jsval_t, &CEntity::DestroyAllNotifiers>( "destroyAllNotifiers", 0 );
    AddMethod<jsval_t, &CEntity::TriggerRun>( "triggerRun", 1 );
    AddMethod<jsval_t, &CEntity::SetRun>( "setRun", 1 );
    AddMethod<jsval_t, &CEntity::GetRunState>( "getRunState", 0 );
    AddMethod<bool, &CEntity::IsInFormation>( "isInFormation", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationBonus>( "getFormationBonus", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationBonusType>( "getFormationBonusType", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationBonusVal>( "getFormationBonusVal", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationPenalty>( "getFormationPenalty", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationPenaltyType>( "getFormationPenaltyType", 0 );
    AddMethod<jsval_t, &CEntity::GetFormationPenaltyVal>( "getFormationPenaltyVal", 0 );
    AddMethod<jsval_t, &CEntity::RegisterDamage>( "registerDamage", 0 );
    AddMethod<jsval_t, &CEntity::RegisterOrderChange>( "registerOrderChange", 0 );
    AddMethod<jsval_t, &CEntity::GetAttackDirections>( "getAttackDirections", 0 );
    AddMethod<jsval_t, &CEntity::FindSector>( "findSector", 4);
    AddMethod<jsval_t, &CEntity::GetHeight>( "getHeight", 0 );
    AddMethod<jsval_t, &CEntity::HasRallyPoint>( "hasRallyPoint", 0 );
    AddMethod<jsval_t, &CEntity::SetRallyPoint>( "setRallyPoint", 0 );
    AddMethod<jsval_t, &CEntity::GetRallyPoint>( "getRallyPoint", 0 );
    AddMethod<jsval_t, &CEntity::OnDamaged>( "onDamaged", 1 );
    AddMethod<jsval_t, &CEntity::GetVisibleEntities>( "getVisibleEntities", 0 );
    AddMethod<float, &CEntity::GetDistance>( "getDistance", 1 );
    AddMethod<jsval_t, &CEntity::FlattenTerrain>( "flattenTerrain", 0 );

    AddClassProperty( L"traits.id.classes", static_cast<GetFn>(&CEntity::GetClassSet), static_cast<SetFn>(&CEntity::SetClassSet) );
    AddClassProperty( L"template", static_cast<CEntityTemplate* CEntity::*>(&CEntity::m_base), false, static_cast<NotifyFn>(&CEntity::LoadBase) );

    /* Any inherited property MUST be added to EntityTemplate.cpp as well */

    AddClassProperty( L"actions.move.speed", &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, static_cast<NotifyFn>(&CEntity::CheckSelection) );
    AddClassProperty( L"group", &CEntity::m_grouped, false, static_cast<NotifyFn>(&CEntity::CheckGroup) );
    AddClassProperty( L"traits.extant", &CEntity::m_extant );
    AddClassProperty( L"actions.move.turningRadius", &CEntity::m_turningRadius );
    AddClassProperty( L"position", &CEntity::m_position, false, static_cast<NotifyFn>(&CEntity::Teleport) );
    AddClassProperty( L"orientation", &CEntity::m_orientation, false, static_cast<NotifyFn>(&CEntity::Reorient) );
    AddClassProperty( L"player", static_cast<GetFn>(&CEntity::JSI_GetPlayer), static_cast<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, static_cast<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 );
    AddClassProperty( L"traits.creation.buildingLimitCategory", &CEntity::m_buildingLimitCategory );


    CJSComplex<CEntity>::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<CEntityTemplate>( 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<float>( argv[2] );
        }
        catch( PSERROR_Scripting_ConversionFailed )
        {
            // TODO: Net-safe random for this parameter.
            orientation = 0.0f;
        }
    }

    if( argc >= 4 )
    {
        try
        {
            player = ToPrimitive<CPlayer*>( argv[3] );
        }
        catch( PSERROR_Scripting_ConversionFailed )
        {
            player = g_Game->GetPlayer( 0 );
        }
    }

    std::set<CStr8> 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<CEntity>( *handle );
    return( JS_TRUE );
}

// Script-bound methods

jsval_t CEntity::ToString( JSContext* cx, uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    CStrW name( L"[object Entity: " + m_base->m_Tag + L"]" );
    return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, name.utf16().c_str() ) ) );
}

jsval CEntity::JSI_GetPlayer()
{
    return ToJSVal<CPlayer>( m_player );
}

void CEntity::JSI_SetPlayer( jsval val )
{
    CPlayer* newPlayer = 0;

    try 
    {
        newPlayer = ToPrimitive<CPlayer*>( 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
    ExitAuras();
    
    if( m_actor )
        m_actor->SetPlayerID( newPlayer->GetPlayerID() ); // calls this->SetPlayer
    else
        SetPlayer(newPlayer);
}

bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, CEntityOrder::EOrderSource source, bool Queued )
{
    // This needs to be sorted (uses Scheduler rather than network messaging)

    int orderCode;
    debug_assert(argc >= 1);
    try
    {
        orderCode = ToPrimitive<int>( argv[0] );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid order type" );
        return( false );
    }

    CEntityOrder newOrder;

    newOrder.m_source = source;

    CEntity* target;

    newOrder.m_type = (CEntityOrder::EOrderType)orderCode;

    switch( orderCode )
    {
        case CEntityOrder::ORDER_GOTO:
        case CEntityOrder::ORDER_RUN:
        case CEntityOrder::ORDER_PATROL:
            JSU_REQUIRE_PARAMS_CPP(3);
            try
            {
                newOrder.m_target_location.x = ToPrimitive<float>( argv[1] );
                newOrder.m_target_location.y = ToPrimitive<float>( 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<CEntity>( argv[1] );
            if( !target )
            {
                JS_ReportError( cx, "Invalid target" );
                return( false );
            }
            newOrder.m_target_entity = target->me;
            try
            {
                newOrder.m_action = ToPrimitive<int>( 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_produce_name = ToPrimitive<CStrW>(argv[2]);
                newOrder.m_produce_type = ToPrimitive<int>(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 );

    Kill(true);

    return( true );
}

jsval_t CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv )
{
    float spawn_clearance = 2.0f;
    if( argc >= 1 )
    {
        CEntityTemplate* be = ToNative<CEntityTemplate>( 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<float>( 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_t CEntity::AddAura( JSContext* cx, uintN argc, jsval* argv )
{
    debug_assert( argc >= 8 );
    debug_assert( JSVAL_IS_OBJECT(argv[7]) );

    CStrW name = ToPrimitive<CStrW>( argv[0] );
    float radius = ToPrimitive<float>( argv[1] );
    size_t tickRate = std::max( 0, ToPrimitive<int>( argv[2] ) );   // since it's a size_t we don't want it to be negative
    float r = ToPrimitive<float>( argv[3] );
    float g = ToPrimitive<float>( argv[4] );
    float b = ToPrimitive<float>( argv[5] );
    float a = ToPrimitive<float>( 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 );

    initAuraData();

    return JSVAL_VOID;
}

jsval_t CEntity::RemoveAura( JSContext* UNUSED(cx), uintN argc, jsval* argv )
{
    debug_assert( argc >= 1 );
    CStrW name = ToPrimitive<CStrW>( argv[0] );
    if( m_auras[name] )
    {
        delete m_auras[name];
        m_auras.erase(name);
    }

    initAuraData();

    return JSVAL_VOID;
}

jsval_t CEntity::SetActionParams( JSContext* UNUSED(cx), uintN argc, jsval* argv )
{
    debug_assert( argc == 5 );

    int id = ToPrimitive<int>( argv[0] );
    float minRange = ToPrimitive<int>( argv[1] );
    float maxRange = ToPrimitive<int>( argv[2] );
    uint speed = ToPrimitive<uint>( argv[3] );
    CStr8 animation = ToPrimitive<CStr8>( argv[4] );

    m_actions[id] = SEntityAction( id, 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<CEntity>( argv[0] );
    notify.m_type = (CEntityListener::EType)ToPrimitive<int>( argv[1] );
    bool tmpDestroyNotifiers = ToPrimitive<bool>( argv[2] );
    entf_set_to(ENTF_DESTROY_NOTIFIERS, !ToPrimitive<bool>( 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<int>( argv[0] );    //notify code
    m_currentNotification = type;

    CEntity *target = ToNative<CEntity>( argv[1] );
    if ( target->m_orderQueue.empty() )
        return false;

    CEntityOrder order = target->m_orderQueue.front();
    for (size_t i=0; i<m_listeners.size(); i++)
    {
        int result = m_listeners[i].m_type & type;
        if ( result )
        {
             switch( result )
             {
                 case CEntityListener::NOTIFY_GOTO:
                 case CEntityListener::NOTIFY_RUN:
                 case CEntityListener::NOTIFY_HEAL:
                 case CEntityListener::NOTIFY_ATTACK:
                 case CEntityListener::NOTIFY_GATHER:
                 case CEntityListener::NOTIFY_DAMAGE:
                 case CEntityListener::NOTIFY_IDLE: //target should be 'this'
                    m_listeners[i].m_sender->DispatchNotification( 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; i<m_listeners.size(); i++)
    {
        int result = m_listeners[i].m_type & type;
        if ( result )
        {
             switch( result )
             {
                 case CEntityListener::NOTIFY_GOTO:
                 case CEntityListener::NOTIFY_RUN:
                 case CEntityListener::NOTIFY_HEAL:
                 case CEntityListener::NOTIFY_ATTACK:
                 case CEntityListener::NOTIFY_GATHER:
                 case CEntityListener::NOTIFY_DAMAGE:
                     m_listeners[i].m_sender->DispatchNotification( order, result );
                     break;
                 default:
                    debug_warn("Invalid notification: CheckListeners()");
                    continue;
             }
        }
    }
}
jsval_t CEntity::DestroyAllNotifiers( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    DestroyAllNotifiers();
    return JS_TRUE;
}
jsval_t CEntity::DestroyNotifier( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    DestroyNotifier( ToNative<CEntity>( argv[0] ) );
    return JS_TRUE;
}

jsval_t CEntity::TriggerRun( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    entf_set(ENTF_TRIGGER_RUN);
    return JSVAL_VOID;
}

jsval_t CEntity::SetRun( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    bool should_run = ToPrimitive<bool> ( argv[0] );
    entf_set_to(ENTF_SHOULD_RUN, should_run);
    entf_set_to(ENTF_IS_RUNNING, should_run);
    return JSVAL_VOID;
}
jsval_t CEntity::GetRunState( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return BOOLEAN_TO_JSVAL( entf_get(ENTF_SHOULD_RUN) );
}
jsval_t CEntity::GetFormationPenalty( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetPenalty() );
}
jsval_t CEntity::GetFormationPenaltyBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetPenaltyBase() );
}
jsval_t CEntity::GetFormationPenaltyType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetPenaltyType() );
}
jsval_t CEntity::GetFormationPenaltyVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetPenaltyVal() );
}
jsval_t CEntity::GetFormationBonus( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetBonus() );
}
jsval_t CEntity::GetFormationBonusBase( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetBonusBase() );
}
jsval_t CEntity::GetFormationBonusType( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetBonusType() );
}
jsval_t CEntity::GetFormationBonusVal( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( GetFormation()->GetBase()->GetBonusVal() );
}

jsval_t CEntity::RegisterDamage( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CEntity* inflictor = ToNative<CEntity>( 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_t CEntity::RegisterOrderChange( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CEntity* idleEntity = ToNative<CEntity>( 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 = std::max(0, FindSector(m_base->m_sectorDivs, angle, DEGTORAD(360.0f)));
    m_sectorValues[sector]=false;
    return JS_TRUE;
}
jsval_t CEntity::GetAttackDirections( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    int directions=0;

    for ( std::vector<bool>::iterator it=m_sectorValues.begin(); it != m_sectorValues.end(); it++ )
    {
        if ( *it )
            ++directions;
    }
    return ToJSVal( directions );
}
jsval_t CEntity::FindSector( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(4);

    try
    {
        int divs = ToPrimitive<int>( argv[0] );
        float angle = ToPrimitive<float>( argv[1] );
        float maxAngle = ToPrimitive<float>( argv[2] );
        bool negative = ToPrimitive<bool>( argv[3] );

        return ToJSVal( FindSector(divs, angle, maxAngle, negative) );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid parameters for FindSector" );
        return 0;
    }
}
jsval_t CEntity::HasRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( entf_get(ENTF_HAS_RALLY_POINT) );
}
jsval_t CEntity::GetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    return ToJSVal( m_rallyPoint );
}
jsval_t CEntity::SetRallyPoint( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    entf_set(ENTF_HAS_RALLY_POINT);
    m_rallyPoint = g_Game->GetView()->GetCamera()->GetWorldCoordinates(true);
    return JS_TRUE;
}

// Called by the script when the entity is damaged, to let it retaliate
jsval_t CEntity::OnDamaged( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CEntity* damageSource = ToNative<CEntity>( argv[0] );
    m_stance->OnDamaged( damageSource );
    return JSVAL_VOID;
}

jsval_t CEntity::GetVisibleEntities( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    std::vector<CEntity*> pointers;
    g_EntityManager.GetInLOS( this, pointers );
    std::vector<HEntity> handles( pointers.size() );
    for( size_t i=0; i<pointers.size(); i++ )
        handles[i] = pointers[i]->me;
    return OBJECT_TO_JSVAL( EntityCollection::Create( handles ) );
}

float CEntity::GetDistance( JSContext* cx, uintN argc, jsval* argv )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CEntity* target = ToNative<CEntity>( argv[0] );
    if( !target )
        return -1.0f;
    return this->Distance2D( target );
}

/*

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<int>( rval );
        }
    }

    // Default return value is an invalid action ID
    return 0;
}

jsval_t CEntity::FlattenTerrain( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
    float xDiff, yDiff;
    CVector3D pos = m_position;
    CVector2D pos2D(m_position.X, m_position.Z);

    if ( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE )
        xDiff = yDiff = m_bounds->m_radius;
    else
    {
        CBoundingBox* box = static_cast<CBoundingBox*>( m_bounds );
        xDiff = box->m_w;
        yDiff = box->m_d;
    }
    //If we ever need to take rotated bounding boxes into account...
    //CVector2D points[4] = { CVector2D(xDiff, yDiff), CVector2D(-xDiff, -yDiff),
        //CVector2D(-xDiff, yDiff), CVector2D(xDiff, yDiff) };
    //CVector3D circle = points[0].FindCircumCircle(points[1], points[2]);
    //float cos = cosf(m_graphics_orientation.Y), sin = sinf(m_graphics_orientation.Y);

    //for ( size_t i = 0; i<4; ++i )
    //{
    //  points[i].x *= cos*circle.Z;
    //  points[i].y *= sin*circle.Z;
    //}
    g_Game->GetWorld()->GetTerrain()->FlattenArea(pos.X-xDiff, pos.X+xDiff, pos.Z-yDiff, pos.Z+yDiff);
    SnapToGround();
    return JS_TRUE;
}