0ad/source/simulation/EntityScriptInterface.cpp
Matei 207d1367ec # A number of network synchronization fixes.
- The server will no longer send a new turn until the previous one has
been "acknowledged". (TODO: this may create lag between turns in its
current form; investigate and possibly allow executing two turns while
the third is being negotiated).
- Mutexes are now being used in NetServer and NetClient to ensure
commands go into the right batches.
- Commented out some orders in the GUI code that should not be there and
are not 100% working anyway (they were part of the follow/formation
system).
- Units that spawn or are created by scripts now have net-safe position
and orientation.
- Added a debug flag that can be turned on to print details about when
commands are received and executed (DEBUG_SYNCHRONIZATION). This is
especially useful if you diff the stdouts of two running games. There
should be no differences if everything is in synch.

This was SVN commit r5463.
2007-11-18 09:09:06 +00:00

906 lines
27 KiB
C++

#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 "Simulation.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 = g_Game->GetSimulation()->RandFloat() * 2 * PI;
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 = g_Game->GetSimulation()->RandInt( 4 );
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 = g_Game->GetSimulation()->RandInt( 2 * d_count ) - d_count;
pos += ( oabb->m_v * (float)max_w + d_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f );
}
else
{
point = g_Game->GetSimulation()->RandInt( 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 = g_Game->GetSimulation()->RandFloat() * 2 * 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;
}